Merge "Allow plugins to reference groups in project config"
diff --git a/.buckversion b/.buckversion
index 46408a5..ab18d5d 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-8204fddf60b25a3c2090f3ef0742fca5d466d562
+7e153d4a69044d059288d353fc1a442e07cbea58
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index dbfdf4c..68976e7 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -107,6 +107,7 @@
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
@@ -117,15 +118,18 @@
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
@@ -145,6 +149,7 @@
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
@@ -161,10 +166,16 @@
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
@@ -176,10 +187,16 @@
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
@@ -227,6 +244,7 @@
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
@@ -245,12 +263,14 @@
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
@@ -274,6 +294,7 @@
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
@@ -301,6 +322,7 @@
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
@@ -329,6 +351,7 @@
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
@@ -338,6 +361,7 @@
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
@@ -361,5 +385,9 @@
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
index 7397758..d990610 100644
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -1,7 +1,7 @@
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_Google Format
-formatter_settings_version=11
+formatter_settings_version=12
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=\#;com.google;com;dk;eu;junit;net;org;java;javax;
org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/BUCK b/BUCK
index 4dd69c3..82f1d72 100644
--- a/BUCK
+++ b/BUCK
@@ -9,6 +9,9 @@
gerrit_war(name = 'release', ui = 'ui_optdbg_r', docs = True, context = ['//plugins:core'], visibility = ['//tools/maven:'])
API_DEPS = [
+ '//gerrit-acceptance-framework:acceptance-framework',
+ '//gerrit-acceptance-framework:acceptance-framework-src',
+ '//gerrit-acceptance-framework:acceptance-framework-javadoc',
'//gerrit-extension-api:extension-api',
'//gerrit-extension-api:extension-api-src',
'//gerrit-extension-api:extension-api-javadoc',
@@ -22,11 +25,9 @@
genrule(
name = 'api',
- cmd = ';'.join(
- ['cd $TMP'] +
- ['ln -s $(location %s) .' % n for n in API_DEPS] +
- ['zip -q0 $OUT *']),
- out = 'api.zip',
+ cmd = 'echo done >$OUT',
+ deps = API_DEPS,
+ out = '__fake.api__',
)
genrule(
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index c648725..e7b7cdc 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -237,6 +237,13 @@
+
Default is -1, permitting infinite time between authentications.
+[[auth.registerEmailPrivateKey]]auth.registerEmailPrivateKey::
++
+Private key to use when generating an email verification token.
++
+If not set, a random key is generated when running the
+link:pgm-init.html[site initialization].
+
[[auth.maxRegisterEmailTokenAge]]auth.maxRegisterEmailTokenAge::
+
Time in seconds before an email verification token sent to a user in
@@ -1713,6 +1720,16 @@
`/Documentation/index.html` can be reached by the browser at app load
time.
+[[gerrit.editGpgKeys]]gerrit.editGpgKeys::
++
+If enabled and server-side signed push validation is also
+link:#receive.enableSignedPush[enabled], enable the
+link:rest-api-accounts.html#list-gpg-keys[REST API endpoints] and web UI
+for editing GPG keys. If disabled, GPG keys can only be added by
+administrators with direct git access to All-Users.
++
+Defaults to true.
+
[[gerrit.installCommitMsgHookCommand]]gerrit.installCommitMsgHookCommand::
+
Optional command to install the `commit-msg` hook. Typically of the
@@ -2346,6 +2363,15 @@
+
Defaults to no limit.
+[[index.maxTerms]]index.maxTerms::
++
+Maximum number of leaf terms to allow in a query. Too-large queries may
+perform poorly, so setting this option causes query parsing to fail fast
+before attempting to send them to the secondary index. Set to 0 for no
+limit.
++
+Defaults to 500.
+
==== Lucene configuration
Open and closed changes are indexed in separate indexes named
@@ -2982,6 +3008,14 @@
+
Common unit suffixes of 'k', 'm', or 'g' are supported.
+[[receive.maxTrustDepth]]receive.maxTrustDepth::
++
+If signed push validation is link:#receive.enableSignedPush[enabled],
+set to the maximum depth to search when checking if a key is
+link:#receive.trustedKey[trusted].
++
+Default is 0, meaning only explicitly trusted keys are allowed.
+
[[receive.threadPoolSize]]receive.threadPoolSize::
+
Maximum size of the thread pool in which the change data in received packs is
@@ -3000,6 +3034,25 @@
Default is 2 minutes. If no unit is specified, milliseconds
is assumed.
+[[receive.trustedKey]]receive.trustedKey::
++
+List of GPG key fingerprints that should be considered trust roots by
+the server when signed push validation is
+link:#receive.enableSignedPush[enabled]. A key is trusted by the server
+if it is either in this list, or a path of trust signatures leads from
+the key to a configured trust root. The maximum length of the path is
+determined by link:#receive.maxTrustDepth[`receive.maxTrustDepth`].
++
+Key fingerprints can be displayed with `gpg --list-keys
+--with-fingerprint`.
++
+Trust signatures can be added to a key using the `tsign` command to
+link:https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html[
+`gpg --edit-key`], after which the signed key should be re-uploaded.
++
+If no keys are specified, web-of-trust checks are disabled. This is the
+default behavior.
+
[[repository]]
=== Section repository
@@ -3861,7 +3914,6 @@
----
[auth]
registerEmailPrivateKey = 2zHNrXE2bsoylzUqDxZp0H1cqUmjgWb6
- restTokenPrivateKey = 7e40PzCjlUKOnXATvcBNXH6oyiu+r0dFk2c=
[database]
username = webuser
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 0d3ff58..7d03681 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -164,6 +164,17 @@
Default is `INHERIT`, which means that this property is inherited from
the parent project.
+[[receive.requireSignedPush]]receive.requireSignedPush::
++
+Controls whether server-side signed push validation is required on the
+project. Only has an effect if signed push validation is enabled on the
+server, and link:#receive.enableSignedPush is set on the project. See
+the link:config-gerrit.html#receive.enableSignedPush[global
+configuration] for details.
++
+Default is `INHERIT`, which means that this property is inherited from
+the parent project.
+
[[submit-section]]
=== Submit section
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index b3b72c4..f19f377 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -18,6 +18,20 @@
If this option interests you, you might want to consider link:install-quick.html[the quick guide].
+[[createdb_derby]]
+=== Apache Derby
+
+If Derby is selected, Gerrit will automatically set up the embedded Derby
+database as backend so no set up or configuration is necessary.
+
+Currently only support for embedded mode is added. There are two other
+deployment options for Apache Derby that can be added later [1]:
++
+* Derby Network Server (standalone mode)
+* Embedded Server (hybrid mode)
++
+[1] http://db.apache.org/derby/papers/DerbyTut/ns_intro.html#ns
+
[[createdb_postgres]]
=== PostgreSQL
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 49a4d85..7d30a91 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -106,7 +106,7 @@
The output executable WAR will be placed in:
----
- buck-out/gen/gerrit.war
+ buck-out/gen/gerrit/gerrit.war
----
@@ -121,7 +121,7 @@
The output executable WAR will be placed in:
----
- buck-out/gen/headless.war
+ buck-out/gen/headless/headless.war
----
=== Extension and Plugin API JAR Files
@@ -137,8 +137,8 @@
----
buck-out/gen/gerrit-plugin-api/plugin-api.jar
+ buck-out/gen/gerrit-plugin-api/plugin-api-javadoc/plugin-api-javadoc.jar
buck-out/gen/gerrit-plugin-api/plugin-api-src.jar
- buck-out/gen/gerrit-plugin-api/plugin-api-javadoc.jar
----
Install {extension,plugin,gwt}-api to the local maven repository:
@@ -170,7 +170,7 @@
The JAR files will also be packaged in:
----
- buck-out/gen/plugins/core.zip
+ buck-out/gen/plugins/core/core.zip
----
To build a specific plugin:
@@ -224,7 +224,7 @@
The html files will also be bundled into `searchfree.zip` in this location:
----
- buck-out/gen/Documentation/searchfree.zip
+ buck-out/gen/Documentation/searchfree/searchfree.zip
----
To build the executable WAR with the documentation included:
@@ -236,7 +236,7 @@
The WAR file will be placed in:
----
- buck-out/gen/withdocs.war
+ buck-out/gen/withdocs/withdocs.war
----
[[soyc]]
@@ -272,7 +272,7 @@
The output release WAR will be placed in:
----
- buck-out/gen/release.war
+ buck-out/gen/release/release.war
----
[[all]]
@@ -318,11 +318,10 @@
* ssh
* slow
-To run a specific test, e.g. the acceptance test
-`com.google.gerrit.acceptance.git.HttpPushForReviewIT`:
+To run a specific test group, e.g. the rest-account test group:
----
- buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:HttpPushForReviewIT
+ buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account:rest-account
----
To create test coverage report:
@@ -605,29 +604,41 @@
needs to be repeated, the unit test cache for that test must be removed first:
----
- rm -rf buck-out/bin/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/.AddRemoveGroupMembersIT/
+ rm -rf buck-out/bin/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/.rest-account/
----
After clearing the cache, the test can be run again:
----
- buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group:AddRemoveGroupMembersIT
- TESTING //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group:AddRemoveGroupMembersIT
- PASS 14,9s 8 Passed 0 Failed com.google.gerrit.acceptance.rest.group.AddRemoveGroupMembersIT
+ buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account:rest-account
+ [-] TESTING...FINISHED 12,3s (12 PASS/0 FAIL)
+ RESULTS FOR //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account:rest-account
+ PASS 970ms 2 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.CapabilitiesIT
+ PASS 999ms 1 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.EditPreferencesIT
+ PASS 1,2s 1 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.GetAccountDetailIT
+ PASS 951ms 2 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.GetAccountIT
+ PASS 6,4s 2 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.GetDiffPreferencesIT
+ PASS 1,2s 4 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.PutUsernameIT
TESTS PASSED
----
An alternative approach is to use Buck's `--filters` (`-f`) option:
----
- buck test -f 'com.google.gerrit.acceptance.rest.change.SubmitByMergeAlwaysIT'
- TESTING SELECTED TESTS
- PASS 14,5s 6 Passed 0 Failed com.google.gerrit.acceptance.rest.change.SubmitByMergeAlwaysIT
+ buck test -f 'com.google.gerrit.acceptance.rest.account.CapabilitiesIT'
+ Using buckd.
+ [-] PROCESSING BUCK FILES...FINISHED 1,0s [100%]
+ [-] BUILDING...FINISHED 2,8s [100%] (334/701 JOBS, 110 UPDATED, 5,1% CACHE MISS)
+ [-] TESTING...FINISHED 9,2s (6 PASS/0 FAIL)
+ RESULTS FOR SELECTED TESTS
+ PASS 8,0s 2 Passed 0 Skipped 0 Failed com.google.gerrit.acceptance.rest.account.CapabilitiesIT
+ PASS <100ms 4 Passed 0 Skipped 0 Failed //tools:util_test
TESTS PASSED
----
When this option is used, the cache is disabled per design and doesn't need to
-be explicitly deleted.
+be explicitly deleted. Note, that this is a known issue, that python tests are
+always executed.
Note that when this option is used, the whole unit test cache is dropped, so
repeating the
@@ -645,6 +656,24 @@
buck test --no-results-cache
----
+== Upgrading Buck
+
+The following tests should be executed, when Buck version is upgraded:
+
+* buck build release
+* buck build api_install
+* buck test
+* buck build gerrit, change some sources in gerrit-server project,
+ repeat buck build gerrit and verify that gerrit.war was updated
+* install and verify new gerrit site
+* upgrade and verify existing gerrit site
+* reindex existing gerrit site
+* verify that tools/eclipse/project.py produces sane Eclipse project
+* verify that tools/eclipse/project.py --src generates sources as well
+* verify that unit test execution from Eclipse works
+* verify that daemon started from Eclipse works
+* verify that GWT SDM debug session started from Eclipse works
+
== Known issues and bugs
=== Symbolic links and `watchman`
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index b64973a..4959ced 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -87,7 +87,7 @@
testing site for development use:
----
- java -jar buck-out/gen/gerrit.war init -d ../gerrit_testsite
+ java -jar buck-out/gen/gerrit/gerrit.war init -d ../gerrit_testsite
----
Accept defaults by pressing Enter until 'init' completes, or add
@@ -130,7 +130,7 @@
copying to the test site:
----
- java -jar buck-out/gen/gerrit.war daemon -d ../gerrit_testsite
+ java -jar buck-out/gen/gerrit/gerrit.war daemon -d ../gerrit_testsite
----
=== Running the Daemon with Gerrit Inspector
@@ -149,7 +149,7 @@
command used to launch the daemon:
----
- java -jar buck-out/gen/gerrit.war daemon -d ../gerrit_testsite -s
+ java -jar buck-out/gen/gerrit/gerrit.war daemon -d ../gerrit_testsite -s
----
Gerrit Inspector examines Java libraries first, then loads
@@ -176,7 +176,7 @@
command line. If the daemon is not currently running:
----
- java -jar buck-out/gen/gerrit.war gsql -d ../gerrit_testsite
+ java -jar buck-out/gen/gerrit/gerrit.war gsql -d ../gerrit_testsite
----
Or, if it is running and the database is in use, connect over SSH
diff --git a/Documentation/doc.css.in b/Documentation/doc.css.in
index 6be89f6..429e81c 100644
--- a/Documentation/doc.css.in
+++ b/Documentation/doc.css.in
@@ -17,6 +17,10 @@
border-bottom: 2px solid silver;
}
+h1 {
+ margin-top: 1.5em;
+}
+
p {
margin: 0.5em 0 0.5em 0;
}
diff --git a/Documentation/error-no-new-changes.txt b/Documentation/error-no-new-changes.txt
index 7bfbfe1..a5c805c 100644
--- a/Documentation/error-no-new-changes.txt
+++ b/Documentation/error-no-new-changes.txt
@@ -45,6 +45,15 @@
it with a new Change-Id (case 1. and 3. above), otherwise the push
will fail with another error message.
+== Fast-forward merges
+
+You will also encounter this error if you did a Fast-forward merge
+and try to push the result. A workaround is to use the
+link:user-upload.html#base[Selecting Merge Base]
+feature or enable the
+link:project-configuration.html#_use_target_branch_when_determining_new_changes_to_open[
+Use target branch when determining new changes to open]
+configuration.
GERRIT
------
diff --git a/Documentation/install-quick.txt b/Documentation/install-quick.txt
index 4a64892..ffd68e7 100644
--- a/Documentation/install-quick.txt
+++ b/Documentation/install-quick.txt
@@ -163,7 +163,7 @@
Download a local clone of the repository and move into it
----
- user@host:~$ git clone ssh://user@host:29418/demo-project
+ user@host:~$ git clone ssh://user@localhost:29418/demo-project
Cloning into demo-project...
remote: Counting objects: 2, done
remote: Finding sources: 100% (2/2)
diff --git a/Documentation/pgm-reindex.txt b/Documentation/pgm-reindex.txt
index b1116d3..e1d8e8b 100644
--- a/Documentation/pgm-reindex.txt
+++ b/Documentation/pgm-reindex.txt
@@ -15,15 +15,6 @@
--threads::
Number of threads to use for indexing.
---recheck-mergeable::
- Recheck the mergeable flag on all open changes. For each open change,
- look up for which commit the mergeability check was last done and if
- this commit is different from the HEAD commit of the change's destination
- branch, recompute the mergeability flag of the change by checking if the
- commit of the current patch set can be merged into the destination branch.
- Because this operation is computationally expensive, it is not enabled
- by default.
-
--schema-version::
Schema version to reindex; default is most recent version.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index fb54601..a320a54 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -713,7 +713,9 @@
"user_ids": [
"John Doe \u003cjohn.doe@example.com\u003e"
],
- "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: BCPG v1.52\n\nmQENBFXUpNcBCACv4paCiyKxZ0EcKy8VaWVNkJlNebRBiyw9WxU85wPOq5Gz/3GT\nRQwKqeY0SxVdQT8VNBw2sBe2m6eqcfZ2iKmesSlbXMe15DA7k8Bg4zEpQ0tXNG1L\nhceZDVQ1Xk06T2sgkunaiPsXi82nwN3UWYtDXxX4is5e6xBNL48Jgz4lbqo6+8D5\nvsVYiYMx4AwRkJyt/oA3IZAtSlY8Yd445nY14VPcnsGRwGWTLyZv9gxKHRUppVhQ\nE3o6ePXKEVgmONnQ4CjqmkGwWZvjMF2EPtAxvQLAuFa8Hqtkq5cgfgVkv/Vrcln4\nnQZVoMm3a3f5ODii2tQzNh6+7LL1bpqAmVEtABEBAAG0H0pvaG4gRG9lIDxqb2hu\nLmRvZUBleGFtcGxlLmNvbT6JATgEEwECACIFAlXUpNcCGwMGCwkIBwMCBhUIAgkK\nCwQWAgMBAh4BAheAAAoJEJNQnkuvyKSbfjoH/2OcSQOu1kJ20ndjhgY2yNChm7gd\ntU7TEBbB0TsLeazkrrLtKvrpW5+CRe07ZAG9HOtp3DikwAyrhSxhlYgVsQDhgB8q\nG0tYiZtQ88YyYrncCQ4hwknrcWXVW9bK3V4ZauxzPv3ADSloyR9tMURw5iHCIeL5\nfIw/pLvA3RjPMx4Sfow/bqRCUELua39prGw5Tv8a2ZRFbj2sgP5j8lUFegyJPQ4z\ntJhe6zZvKOzvIyxHO8llLmdrImsXRL9eqroWGs0VYqe6baQpY6xpSjbYK0J5HYcg\nTO+/u80JI+ROTMHE6unGp5Pgh/xIz6Wd34E0lWL1eOyNfGiPLyRWn1d0"
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: BCPG v1.52\n\nmQENBFXUpNcBCACv4paCiyKxZ0EcKy8VaWVNkJlNebRBiyw9WxU85wPOq5Gz/3GT\nRQwKqeY0SxVdQT8VNBw2sBe2m6eqcfZ2iKmesSlbXMe15DA7k8Bg4zEpQ0tXNG1L\nhceZDVQ1Xk06T2sgkunaiPsXi82nwN3UWYtDXxX4is5e6xBNL48Jgz4lbqo6+8D5\nvsVYiYMx4AwRkJyt/oA3IZAtSlY8Yd445nY14VPcnsGRwGWTLyZv9gxKHRUppVhQ\nE3o6ePXKEVgmONnQ4CjqmkGwWZvjMF2EPtAxvQLAuFa8Hqtkq5cgfgVkv/Vrcln4\nnQZVoMm3a3f5ODii2tQzNh6+7LL1bpqAmVEtABEBAAG0H0pvaG4gRG9lIDxqb2hu\nLmRvZUBleGFtcGxlLmNvbT6JATgEEwECACIFAlXUpNcCGwMGCwkIBwMCBhUIAgkK\nCwQWAgMBAh4BAheAAAoJEJNQnkuvyKSbfjoH/2OcSQOu1kJ20ndjhgY2yNChm7gd\ntU7TEBbB0TsLeazkrrLtKvrpW5+CRe07ZAG9HOtp3DikwAyrhSxhlYgVsQDhgB8q\nG0tYiZtQ88YyYrncCQ4hwknrcWXVW9bK3V4ZauxzPv3ADSloyR9tMURw5iHCIeL5\nfIw/pLvA3RjPMx4Sfow/bqRCUELua39prGw5Tv8a2ZRFbj2sgP5j8lUFegyJPQ4z\ntJhe6zZvKOzvIyxHO8llLmdrImsXRL9eqroWGs0VYqe6baQpY6xpSjbYK0J5HYcg\nTO+/u80JI+ROTMHE6unGp5Pgh/xIz6Wd34E0lWL1eOyNfGiPLyRWn1d0",
+ "status": "TRUSTED",
+ "problems": [],
},
}
----
@@ -747,7 +749,9 @@
"user_ids": [
"John Doe \u003cjohn.doe@example.com\u003e"
],
- "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: BCPG v1.52\n\nmQENBFXUpNcBCACv4paCiyKxZ0EcKy8VaWVNkJlNebRBiyw9WxU85wPOq5Gz/3GT\nRQwKqeY0SxVdQT8VNBw2sBe2m6eqcfZ2iKmesSlbXMe15DA7k8Bg4zEpQ0tXNG1L\nhceZDVQ1Xk06T2sgkunaiPsXi82nwN3UWYtDXxX4is5e6xBNL48Jgz4lbqo6+8D5\nvsVYiYMx4AwRkJyt/oA3IZAtSlY8Yd445nY14VPcnsGRwGWTLyZv9gxKHRUppVhQ\nE3o6ePXKEVgmONnQ4CjqmkGwWZvjMF2EPtAxvQLAuFa8Hqtkq5cgfgVkv/Vrcln4\nnQZVoMm3a3f5ODii2tQzNh6+7LL1bpqAmVEtABEBAAG0H0pvaG4gRG9lIDxqb2hu\nLmRvZUBleGFtcGxlLmNvbT6JATgEEwECACIFAlXUpNcCGwMGCwkIBwMCBhUIAgkK\nCwQWAgMBAh4BAheAAAoJEJNQnkuvyKSbfjoH/2OcSQOu1kJ20ndjhgY2yNChm7gd\ntU7TEBbB0TsLeazkrrLtKvrpW5+CRe07ZAG9HOtp3DikwAyrhSxhlYgVsQDhgB8q\nG0tYiZtQ88YyYrncCQ4hwknrcWXVW9bK3V4ZauxzPv3ADSloyR9tMURw5iHCIeL5\nfIw/pLvA3RjPMx4Sfow/bqRCUELua39prGw5Tv8a2ZRFbj2sgP5j8lUFegyJPQ4z\ntJhe6zZvKOzvIyxHO8llLmdrImsXRL9eqroWGs0VYqe6baQpY6xpSjbYK0J5HYcg\nTO+/u80JI+ROTMHE6unGp5Pgh/xIz6Wd34E0lWL1eOyNfGiPLyRWn1d0"
+ "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: BCPG v1.52\n\nmQENBFXUpNcBCACv4paCiyKxZ0EcKy8VaWVNkJlNebRBiyw9WxU85wPOq5Gz/3GT\nRQwKqeY0SxVdQT8VNBw2sBe2m6eqcfZ2iKmesSlbXMe15DA7k8Bg4zEpQ0tXNG1L\nhceZDVQ1Xk06T2sgkunaiPsXi82nwN3UWYtDXxX4is5e6xBNL48Jgz4lbqo6+8D5\nvsVYiYMx4AwRkJyt/oA3IZAtSlY8Yd445nY14VPcnsGRwGWTLyZv9gxKHRUppVhQ\nE3o6ePXKEVgmONnQ4CjqmkGwWZvjMF2EPtAxvQLAuFa8Hqtkq5cgfgVkv/Vrcln4\nnQZVoMm3a3f5ODii2tQzNh6+7LL1bpqAmVEtABEBAAG0H0pvaG4gRG9lIDxqb2hu\nLmRvZUBleGFtcGxlLmNvbT6JATgEEwECACIFAlXUpNcCGwMGCwkIBwMCBhUIAgkK\nCwQWAgMBAh4BAheAAAoJEJNQnkuvyKSbfjoH/2OcSQOu1kJ20ndjhgY2yNChm7gd\ntU7TEBbB0TsLeazkrrLtKvrpW5+CRe07ZAG9HOtp3DikwAyrhSxhlYgVsQDhgB8q\nG0tYiZtQ88YyYrncCQ4hwknrcWXVW9bK3V4ZauxzPv3ADSloyR9tMURw5iHCIeL5\nfIw/pLvA3RjPMx4Sfow/bqRCUELua39prGw5Tv8a2ZRFbj2sgP5j8lUFegyJPQ4z\ntJhe6zZvKOzvIyxHO8llLmdrImsXRL9eqroWGs0VYqe6baQpY6xpSjbYK0J5HYcg\nTO+/u80JI+ROTMHE6unGp5Pgh/xIz6Wd34E0lWL1eOyNfGiPLyRWn1d0",
+ "status": "TRUSTED",
+ "problems": [],
}
----
@@ -797,6 +801,8 @@
"John Doe \u003cjohn.doe@example.com\u003e"
],
"key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: BCPG v1.52\n\nmQENBFXUpNcBCACv4paCiyKxZ0EcKy8VaWVNkJlNebRBiyw9WxU85wPOq5Gz/3GT\nRQwKqeY0SxVdQT8VNBw2sBe2m6eqcfZ2iKmesSlbXMe15DA7k8Bg4zEpQ0tXNG1L\nhceZDVQ1Xk06T2sgkunaiPsXi82nwN3UWYtDXxX4is5e6xBNL48Jgz4lbqo6+8D5\nvsVYiYMx4AwRkJyt/oA3IZAtSlY8Yd445nY14VPcnsGRwGWTLyZv9gxKHRUppVhQ\nE3o6ePXKEVgmONnQ4CjqmkGwWZvjMF2EPtAxvQLAuFa8Hqtkq5cgfgVkv/Vrcln4\nnQZVoMm3a3f5ODii2tQzNh6+7LL1bpqAmVEtABEBAAG0H0pvaG4gRG9lIDxqb2hu\nLmRvZUBleGFtcGxlLmNvbT6JATgEEwECACIFAlXUpNcCGwMGCwkIBwMCBhUIAgkK\nCwQWAgMBAh4BAheAAAoJEJNQnkuvyKSbfjoH/2OcSQOu1kJ20ndjhgY2yNChm7gd\ntU7TEBbB0TsLeazkrrLtKvrpW5+CRe07ZAG9HOtp3DikwAyrhSxhlYgVsQDhgB8q\nG0tYiZtQ88YyYrncCQ4hwknrcWXVW9bK3V4ZauxzPv3ADSloyR9tMURw5iHCIeL5\nfIw/pLvA3RjPMx4Sfow/bqRCUELua39prGw5Tv8a2ZRFbj2sgP5j8lUFegyJPQ4z\ntJhe6zZvKOzvIyxHO8llLmdrImsXRL9eqroWGs0VYqe6baQpY6xpSjbYK0J5HYcg\nTO+/u80JI+ROTMHE6unGp5Pgh/xIz6Wd34E0lWL1eOyNfGiPLyRWn1d0"
+ "status": "TRUSTED",
+ "problems": [],
}
"DEADBEEF": {}
}
@@ -1314,6 +1320,82 @@
}
----
+[[get-edit-preferences]]
+=== Get Edit Preferences
+--
+'GET /accounts/link:#account-id[\{account-id\}]/preferences.edit'
+--
+
+Retrieves the edit preferences of a user.
+
+.Request
+----
+ GET /a/accounts/self/preferences.edit HTTP/1.0
+----
+
+As result the edit preferences of the user are returned as a
+link:#edit-preferences-info[EditPreferencesInfo] entity.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "theme": "ECLIPSE",
+ "key_map_type": "VIM",
+ "tab_size": 4,
+ "line_length": 80,
+ "cursor_blink_rate": 530,
+ "hide_top_menu": true,
+ "show_whitespace_errors": true,
+ "hide_line_numbers": true,
+ "match_brackets": true,
+ "auto_close_brackets": true
+ }
+----
+
+[[set-edit-preferences]]
+=== Set Edit Preferences
+--
+'PUT /accounts/link:#account-id[\{account-id\}]/preferences.edit'
+--
+
+Sets the edit preferences of a user.
+
+The new edit preferences must be provided in the request body as a
+link:#edit-preferences-info[EditPreferencesInfo] entity.
+
+.Request
+----
+ PUT /a/accounts/self/preferences.edit HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "theme": "ECLIPSE",
+ "key_map_type": "VIM",
+ "tab_size": 4,
+ "line_length": 80,
+ "cursor_blink_rate": 530,
+ "hide_top_menu": true,
+ "show_tabs": true,
+ "show_whitespace_errors": true,
+ "syntax_highlighting": true,
+ "hide_line_numbers": true,
+ "match_brackets": true,
+ "auto_close_brackets": true
+ }
+----
+
+The response is "`204 No Content`"
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
[[get-starred-changes]]
=== Get Starred Changes
--
@@ -1611,7 +1693,7 @@
|`syntax_highlighting` |not set if `false`|
Whether syntax highlighting should be enabled.
|`hide_top_menu` |not set if `false`|
-If true the top menu header and site header is hidden.
+If true the top menu header and site header are hidden.
|`auto_hide_diff_table_header` |not set if `false`|
If true the diff table header is automatically hidden when
scrolling down more than half of a page.
@@ -1677,6 +1759,44 @@
Number of spaces that should be used to display one tab.
|===========================================
+[[edit-preferences-info]]
+=== EditPreferencesInfo
+The `EditPreferencesInfo` entity contains information about the edit
+preferences of a user.
+
+[options="header",cols="1,^1,5"]
+|===========================================
+|Field Name ||Description
+|`theme` ||
+The CodeMirror theme. Currently only a subset of light and dark
+CodeMirror themes are supported. Light themes `DEFAULT`, `ECLIPSE`,
+`ELEGANT`, `NEAT`. Dark themes `MIDNIGHT`, `NIGHT`, `TWILIGHT`.
+|`key_map_type` ||
+The CodeMirror key map. Currently only a subset of key maps are
+supported: `DEFAULT`, `EMACS`, `VIM`.
+|`tab_size` ||
+Number of spaces that should be used to display one tab.
+|`line_length` ||
+Number of characters that should be displayed per line.
+|`cursor_blink_rate` ||
+Half-period in milliseconds used for cursor blinking.
+Setting it to 0 disables cursor blinking.
+|`hide_top_menu` |not set if `false`|
+If true the top menu header and site header is hidden.
+|`show_tabs` |not set if `false`|
+Whether tabs should be shown.
+|`show_whitespace_errors` |not set if `false`|
+Whether whitespace errors should be shown.
+|`syntax_highlighting` |not set if `false`|
+Whether syntax highlighting should be enabled.
+|`hide_line_numbers` |not set if `false`|
+Whether line numbers should be hidden.
+|`match_brackets` |not set if `false`|
+Whether matching brackets should be highlighted.
+|`auto_close_brackets` |not set if `false`|
+Whether brackets and quotes should be auto-closed during typing.
+|===========================================
+
[[email-info]]
=== EmailInfo
The `EmailInfo` entity contains information about an email address of a
@@ -1729,6 +1849,15 @@
link:https://tools.ietf.org/html/rfc4880#section-5.11[OpenPGP User IDs]
associated with the public key.
|`key` |Not set for deleted keys|ASCII armored public key material.
+|`status` |Not set for deleted keys|
+The result of server-side checks on the key; one of `BAD`, `OK`, or `TRUSTED`.
+`BAD` keys have serious problems and should not be used. If a key is `OK,
+inspecting only that key found no problems, but the system does not fully trust
+the key's origin. A `TRUSTED` key is valid, and the system knows enough about
+the key and its origin to trust it.
+|`problems` |Not set for deleted keys|
+A list of human-readable problem strings found in the course of checking whether
+the key is valid and trusted.
|========================
[[gpg-keys-input]]
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 4b57827..7e96556 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -315,6 +315,14 @@
link:#revision-info[RevisionInfo].
--
+[[push-certificates]]
+--
+* `PUSH_CERTIFICATES`: include push certificate information in the
+ link:#revision-info[RevisionInfo]. Ignored if signed push is not
+ link:config-gerrit.html#receive.enableSignedPush[enabled] on the
+ server.
+--
+
.Request
----
GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
@@ -405,29 +413,36 @@
},
"files": {
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java": {
- "lines_deleted": 8
+ "lines_deleted": 8,
+ "size_delta": -412
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java": {
- "lines_inserted": 1
+ "lines_inserted": 1,
+ "size_delta": 23
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java": {
"lines_inserted": 11,
- "lines_deleted": 19
+ "lines_deleted": 19,
+ "size_delta": -298
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java": {
"lines_inserted": 23,
- "lines_deleted": 20
+ "lines_deleted": 20,
+ "size_delta": 132
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java": {
"status": "D",
- "lines_deleted": 139
+ "lines_deleted": 139,
+ "size_delta": -5512
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java": {
"status": "A",
- "lines_inserted": 204
+ "lines_inserted": 204,
+ "size_delta": 8345
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java": {
- "lines_deleted": 9
+ "lines_deleted": 9,
+ "size_delta": -343
}
}
}
@@ -3254,11 +3269,13 @@
{
"/COMMIT_MSG": {
"status": "A",
- "lines_inserted": 7
+ "lines_inserted": 7,
+ "size_delta": 551
},
"gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java": {
"lines_inserted": 5,
- "lines_deleted": 3
+ "lines_deleted": 3,
+ "size_delta": 98
}
}
----
@@ -3347,10 +3364,13 @@
The HTTP resource Content-Type is dependent on the file type: the
applicable type for safe files, or "application/zip" for unsafe files.
-The `suffix` parameter can be specified to decorate the names of the files.
-The suffix is inserted between the base filename and the random component or
-extension, or appended to the filename if neither such component is present.
-Only the lowercase Roman letters a-z are permitted; other characters are ignored.
+The optional, integer-valued `parent` parameter can be specified to request
+the named file from a parent commit of the specified revision. The value is
+the 1-based index of the parent's position in the commit object. If the
+parameter is omitted or the value non-positive, the patch set is referenced.
+
+Filenames are decorated with a suffix of `_new` for the current patch,
+`_old` for the only parent, or `_oldN` for the Nth parent of many.
.Request
----
@@ -4196,6 +4216,8 @@
|`lines_deleted` |optional|
Number of deleted lines. +
Not set for binary files or if no lines were deleted.
+|`size_delta` ||
+Number of bytes by which the file size increased/decreased.
|=============================
[[fix-input]]
@@ -4352,6 +4374,23 @@
outcome of the fix.
|===========================
+[[push-certificate-info]]
+=== PushCertificateInfo
+The `PushCertificateInfo` entity contains information about a push
+certificate provided when the user pushed for review with `git push
+--signed HEAD:refs/for/<branch>`. Only used when signed push is
+link:config-gerrit.html#receive.enableSignedPush[enabled] on the server.
+
+[options="header",cols="1,6"]
+|===========================
+|Field Name|Description
+|`certificate`|Signed certificate payload and GPG signature block.
+|`key` |
+Information about the key that signed the push, along with any problems
+found while checking the signature or the key itself, as a
+link:rest-api-accounts.html#gpg-key-info[GpgKeyInfo] entity.
+|===========================
+
[[rebase-input]]
=== RebaseInput
The `RebaseInput` entity contains information for changing parent when rebasing.
@@ -4563,6 +4602,12 @@
Gerrit-specific commit footers, as if this revision were submitted
using the link:project-configuration.html#cherry_pick[Cherry Pick]
submit type.
+|`push_certificate` |optional|
+If the link:#push-certificates[PUSH_CERTIFICATES] option is requested,
+contains the push certificate provided by the user when uploading this
+patch set as a link:#push-certificate-info[PushCertificateInfo] entity.
+This field is always set if the option is requested; if no push
+certificate was provided, it is set to an empty object.
|===========================
[[rule-input]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 7e117c5..b1b795c 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -60,7 +60,7 @@
]
},
"download": {
- "schemes": [
+ "schemes": {
"anonymous http": {
"url": "http://gerrithost:8080/${project}",
"commands": {
@@ -70,7 +70,7 @@
"Cherry Pick": "git fetch http://gerrithost:8080/${project} ${ref} \u0026\u0026 git cherry-pick FETCH_HEAD"
},
"clone_commands": {
- "Clone": "git clone http://gerrithost:8080/${project}"
+ "Clone": "git clone http://gerrithost:8080/${project}",
"Clone with commit-msg hook": "git clone http://gerrithost:8080/${project} \u0026\u0026 scp -p -P 29418 jdoe@gerrithost:hooks/commit-msg ${project}/.git/hooks/"
}
},
@@ -104,7 +104,7 @@
"Clone with commit-msg hook": "git clone ssh://jdoe@gerrithost:29418/${project} \u0026\u0026 scp -p -P 29418 jdoe@gerrithost:hooks/commit-msg ${project}/.git/hooks/"
}
}
- ],
+ },
"archives": [
"tgz",
"tar",
@@ -1193,6 +1193,8 @@
Custom base URL where Gerrit server documentation is located.
(Documentation may still be available at /Documentation relative to the
Gerrit base path even if this value is unset.)
+|`edit_gpg_keys` |not set if `false`|
+Whether to enable the web UI for editing GPG keys.
|`report_bug_url` |optional|
link:config-gerrit.html#gerrit.reportBugUrl[URL to report bugs].
|`report_bug_text` |optional, not set if default|
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index afdf36d..e0df4ca 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -172,6 +172,46 @@
GET /groups/?n=25&S=50 HTTP/1.0
----
+[[suggest-group]]
+==== Suggest Group
+The `suggest` option indicates a user-entered string that
+should be auto-completed to group names.
+If this option is set and `n` is not set, then `n` defaults to 10.
+
+When using this option,
+the `project` or `p` option can be used to name the current project,
+to allow context-dependent suggestions.
+
+Not compatible with `visible-to-all`, `owned`, `user`, `match`, `q`,
+or `S`.
+(Attempts to use one of those options combined with `suggest` will
+error out.)
+
+.Request
+----
+ GET /groups/?suggest=ad&p=All-Projects HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "Administrators": {
+ "url": "#/admin/groups/uuid-59b92f35489e62c80d1ab1bf0c2d17843038df8b",
+ "options": {},
+ "description": "Gerrit Site Administrators",
+ "group_id": 1,
+ "owner": "Administrators",
+ "owner_id": "59b92f35489e62c80d1ab1bf0c2d17843038df8b",
+ "id": "59b92f35489e62c80d1ab1bf0c2d17843038df8b"
+ }
+ }
+----
+
[[get-group]]
=== Get Group
--
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 4658e2c..59184a8 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -732,6 +732,7 @@
"use_signed_off_by": "INHERIT",
"create_new_change_for_all_not_in_target": "INHERIT",
"enable_signed_push": "INHERIT",
+ "require_signed_push": "INHERIT",
"require_change_id": "TRUE",
"max_object_size_limit": "10m",
"submit_type": "REBASE_IF_NECESSARY",
@@ -780,6 +781,11 @@
"configured_value": "INHERIT",
"inherited_value": false
},
+ "require_signed_push": {
+ "value": false,
+ "configured_value": "INHERIT",
+ "inherited_value": false
+ },
"max_object_size_limit": {
"value": "10m",
"configured_value": "10m",
@@ -1453,6 +1459,80 @@
]
----
+[[tag-options]]
+==== Tag Options
+
+Limit(n)::
+Limit the number of tags to be included in the results.
++
+.Request
+----
+ GET /projects/work%2Fmy-project/tags?n=2 HTTP/1.0
+----
++
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "refs/tags/v1.0",
+ "revision": "49ce77fdcfd3398dc0dedbe016d1a425fd52d666",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Annotated tag",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 07:35:03.000000000",
+ "tz": 540
+ }
+ },
+ {
+ "ref": "refs/tags/v2.0",
+ "revision": "1624f5af8ae89148d1a3730df8c290413e3dcf30"
+ }
+ ]
+----
+
+Skip(s)::
+Skip the given number of tags from the beginning of the list.
++
+.Request
+----
+ GET /projects/work%2Fmy-project/tags?n=2&s=1 HTTP/1.0
+----
++
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "refs/tags/v2.0",
+ "revision": "1624f5af8ae89148d1a3730df8c290413e3dcf30"
+ },
+ {
+ "ref": "refs/tags/v3.0",
+ "revision": "c628685b3c5a3614571ecb5c8fceb85db9112313",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Signed tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.11 (GNU/Linux)\n\niQEcBAABAgAGBQJUMlqYAAoJEPI2qVPgglptp7MH/j+KFcittFbxfSnZjUl8n5IZ\nveZo7wE+syjD9sUbMH4EGv0WYeVjphNTyViBof+stGTNkB0VQzLWI8+uWmOeiJ4a\nzj0LsbDOxodOEMI5tifo02L7r4Lzj++EbqtKv8IUq2kzYoQ2xjhKfFiGjeYOn008\n9PGnhNblEHZgCHguGR6GsfN8bfA2XNl9B5Ysl5ybX1kAVs/TuLZR4oDMZ/pW2S75\nhuyNnSgcgq7vl2gLGefuPs9lxkg5Fj3GZr7XPZk4pt/x1oiH7yXxV4UzrUwg2CC1\nfHrBlNbQ4uJNY8TFcwof52Z0cagM5Qb/ZSLglHbqEDGA68HPqbwf5z2hQyU2/U4\u003d\n\u003dZtUX\n-----END PGP SIGNATURE-----",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 09:02:16.000000000",
+ "tz": 540
+ }
+ }
+ ]
+----
+
+
[[get-tag]]
=== Get Tag
--
@@ -1908,9 +1988,14 @@
valid link:user-changeid.html[Change-Id] footer in any commit uploaded
for review is required. This does not apply to commits pushed directly
to a branch or tag.
-|`enable_signed_push` |optional|
+|`enable_signed_push`|
+optional, not set if signed push is disabled|
link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
signed push validation is enabled on the project.
+|`require_signed_push`|
+optional, not set if signed push is disabled
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
+signed push validation is required on the project.
|`max_object_size_limit` ||
The link:config-gerrit.html#receive.maxObjectSizeLimit[max object size
limit] of this project as a link:#max-object-size-limit-info[
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index e262cb7..05932df 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -59,6 +59,8 @@
To save edits, click the 'Save' button or press `CTRL-S`. To return to the
change screen, click the 'Close' button.
+Note that when editing the commit message, trailing blank lines will be stripped.
+
image::images/inline-edit-full-screen-editor.png[width=800, link="images/inline-edit-full-screen-editor.png"]
If there are unsaved edits when the 'Close' button is pressed, a dialog will
@@ -154,10 +156,8 @@
[[not-implemented-features]]
== Not Implemented Features
-* [PENDING CHANGE]
-The inline editor uses settings decided from the user's diff preferences, but those
-preferences are only modifiable from the side-by-side diff screen. It should be possible
-to open the preferences also from within the editor.
+* Support default configuration options for inline editor that an
+administrator has set in `refs/users/default:preferences.config` file.
* Allow to rename files that are already contained in the change (from the file table).
The same rename file dialog can be used with preselected and disabled original file
@@ -178,9 +178,6 @@
** "save-when-file-was-changed" or
** "close-when-no-changes"
-* Allow to activate different key maps, supported by CM: Emacs, Sublime, Vim. Load key
-maps dynamically. Currently default mode is used.
-
* Implement conflict resolution during rebase of change edit using inline edit
feature by creating new edit on top of current patch set with auto merge content
diff --git a/ReleaseNotes/ReleaseNotes-2.10.7.txt b/ReleaseNotes/ReleaseNotes-2.10.7.txt
new file mode 100644
index 0000000..28cf37b
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.10.7.txt
@@ -0,0 +1,19 @@
+Release notes for Gerrit 2.10.7
+===============================
+
+There are no schema changes from link:ReleaseNotes-2.10.6.html[2.10.6].
+
+Download:
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.7.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.10.7.war]
+
+Bug Fixes
+---------
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3361[Issue 3361]:
+Synchronize Myers diff and Histogram diff invocations to prevent pack file
+corruption.
++
+See also the link:https://bugs.eclipse.org/bugs/show_bug.cgi?id=467467[
+bug report on JGit].
+
diff --git a/ReleaseNotes/ReleaseNotes-2.11.4.txt b/ReleaseNotes/ReleaseNotes-2.11.4.txt
new file mode 100644
index 0000000..6037edd
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.11.4.txt
@@ -0,0 +1,146 @@
+Release notes for Gerrit 2.11.4
+===============================
+
+Gerrit 2.11.4 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.4.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.11.4.war]
+
+Gerrit 2.11.4 includes the bug fixes done with
+link:ReleaseNotes-2.10.7.html[Gerrit 2.10.7]. These bug fixes are *not* listed
+in these release notes.
+
+There are no schema changes from link:ReleaseNotes-2.11.3.html[2.11.3].
+
+
+Bug Fixes
+---------
+
+* Fix NullPointerException in `ls-project` command with `--has-acl-for` option.
++
+Using the `--has-acl-for` option for external groups (e.g. LDAP groups) was
+causing a NullPointerException.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3328[Issue 3328]:
+Allow to push a tag that points to a non-commit object.
++
+When pushing a tag that points to a non-commit object, like
+link:https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tag/?id=v2.6.11[
+`v2.6.11` on linux-stable] which points to a tree, or
+link:https://git.eclipse.org/c/jgit/jgit.git/tag/?id=spearce-gpg-pub[
+`spearce-gpg-pub` on jgit] which points to a blob, Gerrit rejected the push with
+the error message 'missing object(s)'.
++
+Note: This was previously fixed in Gerrit version 2.11.1, but was inadvertently
+reverted in 2.11.2 and 2.11.3.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2817[Issue 2817]:
+Insert `Change-Id` footer into access right changes.
++
+When modifications of access rights were saved for review, the change
+did not have a `Change-Id` footer in the commit message.
+
+* Fix duplicated log lines after reloading a plugin.
++
+If a plugin was reloaded, logs emitted from the plugin were duplicated.
+
+* Remove `--recheck-mergeable` option from `reindex` command documentation.
++
+The `--recheck-mergeable` option was removed in Gerrit version 2.11.
+
+* Use the correct validation policy for commits created by Gerrit.
++
+Commits created by Gerrit were being validated in the same way as commits
+received from users.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3557[Issue 3557]:
+Disallow invalid reference patterns in project configuration.
++
+When editing a project configuration by using the UI or by submitting a change
+to `refs/meta/config`, it was possible to add a permission to an invalid
+reference pattern. This caused the project to be unavailable and the `ls-projects`
+command to fail whenever this project was encountered.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3574[Issue 3574]:
+Fix review labels with `AnyWithBlock` function.
++
+The review labels with `AnyWithBlock` with 0 and +1 values blocked submit when
+reviewers were added.
+
+* Fix ref in tag list for signed/annotated tags.
++
+The tag name from the header was used, rather than the ref name. In some cases
+this resulted in the wrong tag ref being listed.
+
+* Prevent user from bypassing `ref-update` hook through gerrit-created commits.
++
+If the user used the cherry-pick ability in the UI or via the REST API, they
+could put a commit on a branch that bypassed the requirements of the `ref-update`
+hook (such as that certain branches require QA-tickets to be referenced in the
+commit message).
+
+* Allow `InternalUsers` to see drafts.
++
+According to the documentation, `InternalUsers` should have full read access.
+This was not true, since `InternalUsers` could not see drafts.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2683[Issue 2683]:
+Fix non-ASCII password authentication failure under tomcat (LDAP).
++
+The authentication with LDAP failed when the password contained non-ASCII
+characters such as ä, ö, Ä, and Ö.
+
+* Do not double decode the login URL token.
++
+The login URL token used to redirect from the login servlet to the target page
+is already decoded and should not be decoded again.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3020[Issue 3020]:
+Include approvals specified on push in change message.
++
+When using the `%l` option to apply a review label on uploaded changes or
+patch sets, the applied label was not mentioned in the change message.
+
+* Fire the `comment-added` hook for approvals specified on push.
++
+When using the `%l` option to apply a review label on uploaded changes or
+patch sets, the `comment-added` hook was not being fired.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3602[Issue 3602]:
+Use uploader for approvals specified on push, not the committer.
++
+When using the `%l` option to apply a review label on uploaded changes or
+patch sets, the review label was in some cases applied as the committer rather
+than the uploader.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3531[Issue 3531]:
+Fix internal server error on unified diff screen for anonymous users.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2414[Issue 2414]:
+Improve detection of requiring sign-in.
++
+Some queries, such as the `has:*` operators, require the user to be signed in.
++
+Also, when handling a REST API failure, detect 'Invalid authentication' responses
+as also requiring a new session.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3052[Issue 3052]:
+Fix 'Conflicts With' list for merge commits.
++
+The 'Conflicts List' was not being populated correctly if the change being viewed
+was a merge commit, or if the change being viewed conflicted with an open merge
+commit.
+
+Plugin Bugfixes
+---------------
+
+* singleusergroup: Allow to add a user to a project's ACL using `user/username`.
++
+A user could not be added to a project's ACL unless the user already had READ
+permission in the project's ACL.
+
+* replication: Add waiting time and number of retries to replication log.
++
+Only the replication execution time was printed in the 'replication completed'
+log statement. The waiting time and retry count is added, to help debug
+replication delays.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 262dc4f..e1831ad 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,6 +4,7 @@
[[2_11]]
Version 2.11.x
--------------
+* link:ReleaseNotes-2.11.4.html[2.11.4]
* link:ReleaseNotes-2.11.3.html[2.11.3]
* link:ReleaseNotes-2.11.2.html[2.11.2]
* link:ReleaseNotes-2.11.1.html[2.11.1]
@@ -12,6 +13,7 @@
[[2_10]]
Version 2.10.x
--------------
+* link:ReleaseNotes-2.10.7.html[2.10.7]
* link:ReleaseNotes-2.10.6.html[2.10.6]
* link:ReleaseNotes-2.10.5.html[2.10.5]
* link:ReleaseNotes-2.10.4.html[2.10.4]
diff --git a/bucklets/gerrit_plugin.bucklet b/bucklets/gerrit_plugin.bucklet
index ae7e1a2..cd2edae 100644
--- a/bucklets/gerrit_plugin.bucklet
+++ b/bucklets/gerrit_plugin.bucklet
@@ -14,7 +14,8 @@
# When compiling from standalone cookbook-plugin, bucklets directory points
# to cloned bucklets library that includes real gerrit_plugin.bucklet code.
-GERRIT_PLUGIN_API = ['//gerrit-plugin-api:lib']
GERRIT_GWT_API = ['//gerrit-plugin-gwtui/gerrit:gwtui-api']
+GERRIT_PLUGIN_API = ['//gerrit-plugin-api:lib']
+GERRIT_TESTS = ['//gerrit-acceptance-framework:lib']
STANDALONE_MODE = False
diff --git a/gerrit-acceptance-framework/BUCK b/gerrit-acceptance-framework/BUCK
new file mode 100644
index 0000000..d8f0276
--- /dev/null
+++ b/gerrit-acceptance-framework/BUCK
@@ -0,0 +1,87 @@
+SRCS = glob(['src/test/java/com/google/gerrit/acceptance/*.java'])
+
+DEPS = [
+ '//gerrit-gpg:gpg',
+ '//gerrit-pgm:daemon',
+ '//gerrit-pgm:util-nodep',
+ '//gerrit-server:testutil',
+ '//lib/auto:auto-value',
+ '//lib/httpcomponents:fluent-hc',
+ '//lib/httpcomponents:httpclient',
+ '//lib/httpcomponents:httpcore',
+ '//lib/jgit:junit',
+ '//lib/log:impl_log4j',
+ '//lib/log:log4j',
+]
+
+PROVIDED = [
+ '//gerrit-common:annotations',
+ '//gerrit-common:server',
+ '//gerrit-extension-api:api',
+ '//gerrit-httpd:httpd',
+ '//gerrit-lucene:lucene',
+ '//gerrit-pgm:init',
+ '//gerrit-reviewdb:server',
+ '//gerrit-server:server',
+ '//lib:gson',
+ '//lib/jgit:jgit',
+ '//lib:jsch',
+ '//lib/mina:sshd',
+ '//lib:servlet-api-3_1',
+]
+
+java_binary(
+ name = 'acceptance-framework',
+ deps = [':lib'],
+ visibility = ['PUBLIC'],
+)
+
+java_library(
+ name = 'lib',
+ srcs = SRCS,
+ exported_deps = DEPS + [
+ '//lib:truth',
+ ],
+ provided_deps = PROVIDED + [
+ '//lib:gwtorm',
+ '//lib/guice:guice',
+ '//lib/guice:guice-assistedinject',
+ '//lib/guice:guice-servlet',
+ ],
+ visibility = ['PUBLIC'],
+)
+
+java_sources(
+ name = 'src',
+ srcs = SRCS,
+ visibility = ['PUBLIC'],
+)
+
+# The above java_sources produces a .jar somewhere in the depths of
+# buck-out, but it does not bring it to
+# buck-out/gen/gerrit-acceptance-framework/gerrit-acceptance-framework-src.jar.
+# We fix that by the following java_binary.
+java_binary(
+ name = 'acceptance-framework-src',
+ deps = [ ':src' ],
+ visibility = ['PUBLIC'],
+)
+
+java_doc(
+ name = 'acceptance-framework-javadoc',
+ title = 'Gerrit Acceptance Test Framework Documentation',
+ pkgs = [' com.google.gerrit.acceptance'],
+ paths = ['src/test/java'],
+ srcs = SRCS,
+ deps = DEPS + PROVIDED + [
+ '//lib:guava',
+ '//lib/guice:guice-assistedinject',
+ '//lib/guice:guice_library',
+ '//lib/guice:guice-servlet',
+ '//lib/guice:javax-inject',
+ '//lib:gwtorm_client',
+ '//lib:junit__jar',
+ '//lib:truth__jar',
+ ],
+ visibility = ['PUBLIC'],
+)
diff --git a/gerrit-acceptance-framework/pom.xml b/gerrit-acceptance-framework/pom.xml
new file mode 100644
index 0000000..ca1ecd9
--- /dev/null
+++ b/gerrit-acceptance-framework/pom.xml
@@ -0,0 +1,59 @@
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-acceptance-framework</artifactId>
+ <version>2.12-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <name>Gerrit Code Review - Acceptance Test Framework</name>
+ <description>API for Gerrit Plugins</description>
+ <url>https://www.gerritcodereview.com/</url>
+
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <scm>
+ <url>https://gerrit.googlesource.com/gerrit</url>
+ <connection>https://gerrit.googlesource.com/gerrit</connection>
+ </scm>
+
+ <developers>
+ <developer>
+ <name>Dave Borowitz</name>
+ </developer>
+ <developer>
+ <name>David Pursehouse</name>
+ </developer>
+ <developer>
+ <name>Edwin Kempin</name>
+ </developer>
+ <developer>
+ <name>Martin Fick</name>
+ </developer>
+ <developer>
+ <name>Saša Živkov</name>
+ </developer>
+ <developer>
+ <name>Shawn Pearce</name>
+ </developer>
+ </developers>
+
+ <mailingLists>
+ <mailingList>
+ <name>Repo and Gerrit Discussion</name>
+ <post>repo-discuss@googlegroups.com</post>
+ <subscribe>https://groups.google.com/forum/#!forum/repo-discuss</subscribe>
+ <unsubscribe>https://groups.google.com/forum/#!forum/repo-discuss</unsubscribe>
+ <archive>https://groups.google.com/forum/#!forum/repo-discuss</archive>
+ </mailingList>
+ </mailingLists>
+
+ <issueManagement>
+ <url>http://code.google.com/p/gerrit/issues/list</url>
+ <system>Google Code Issue Tracker</system>
+ </issueManagement>
+</project>
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
similarity index 94%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 8d97136..7dbbfb5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -14,12 +14,15 @@
package com.google.gerrit.acceptance;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.project.Util.block;
+import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.primitives.Chars;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
@@ -52,6 +55,7 @@
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -170,6 +174,9 @@
protected ReviewDb db;
protected Project.NameKey project;
+ @Inject
+ protected NotesMigration notesMigration;
+
@Rule
public ExpectedException exception = ExpectedException.none();
@@ -226,12 +233,22 @@
return cfg.getBoolean("change", null, "submitWholeTopic", false);
}
+ private static boolean isNoteDbTestEnabled() {
+ final String[] RUN_FLAGS = {"yes", "y", "true"};
+ String value = System.getenv("GERRIT_ENABLE_NOTEDB");
+ return value != null &&
+ Arrays.asList(RUN_FLAGS).contains(value.toLowerCase());
+ }
+
protected void beforeTest(Description description) throws Exception {
GerritServer.Description classDesc =
GerritServer.Description.forTestClass(description, configName);
GerritServer.Description methodDesc =
GerritServer.Description.forTestMethod(description, configName);
+ if (isNoteDbTestEnabled()) {
+ NotesMigration.setAllEnabledConfig(baseConfig);
+ }
baseConfig.setString("gerrit", null, "tempSiteDir",
tempSiteDir.getRoot().getPath());
if (classDesc.equals(methodDesc)) {
@@ -594,4 +611,17 @@
.revision(1)
.actions();
}
+
+ protected void assertSubmittedTogether(String chId, String... expected)
+ throws Exception {
+ List<ChangeInfo> actual = gApi.changes().id(chId).submittedTogether();
+ assertThat(actual).hasSize(expected.length);
+ assertThat(Iterables.transform(actual,
+ new Function<ChangeInfo, String>() {
+ @Override
+ public String apply(ChangeInfo input) {
+ return input.changeId;
+ }
+ })).containsExactly((Object[])expected).inOrder();
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
similarity index 97%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index 43ad799..34379a1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -76,7 +76,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
if (user == null) {
throw new IllegalStateException("user == null, forgot to set it?");
}
@@ -153,7 +153,7 @@
}
private Context newContinuingContext(Context ctx) {
- return new Context(ctx, ctx.getSession(), ctx.getCurrentUser());
+ return new Context(ctx, ctx.getSession(), ctx.getUser());
}
public Context set(Context ctx) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GcAssert.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GcAssert.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GcAssert.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GcAssert.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GitUtil.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GitUtil.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/HttpSession.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
similarity index 98%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
index c02b9e5..c16eed7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -187,7 +187,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return get(USER_KEY, null);
}
@@ -326,8 +326,7 @@
throw new ServiceNotAuthorizedException();
}
- IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
- rp.setRefLogIdent(user.newRefLogIdent());
+ rp.setRefLogIdent(ctl.getUser().asIdentifiedUser().newRefLogIdent());
rp.setTimeout(config.getTimeout());
rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/NoHttpd.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/NoHttpd.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/NoHttpd.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/NoHttpd.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
similarity index 96%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
index 4913488..f0b9f46 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java
@@ -14,6 +14,8 @@
package com.google.gerrit.acceptance;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.server.config.SitePaths;
@@ -27,7 +29,6 @@
import java.io.InputStream;
import java.lang.ProcessBuilder.Redirect;
import java.net.URL;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -134,7 +135,7 @@
Path buckFile = pluginSource.resolve("BUCK");
byte[] bytes = Files.readAllBytes(buckFile);
String buckContent =
- new String(bytes, StandardCharsets.UTF_8).replaceAll("\\s+", "");
+ new String(bytes, UTF_8).replaceAll("\\s+", "");
Matcher matcher =
Pattern.compile("gerrit_plugin\\(name='(.*?)'").matcher(buckContent);
if (matcher.find()) {
@@ -189,7 +190,7 @@
private Properties loadBuckProperties() throws IOException {
Properties properties = new Properties();
- Path propertiesPath = gen.resolve("tools").resolve("buck.properties");
+ Path propertiesPath = gen.resolve(Paths.get("tools/buck/buck.properties"));
if (Files.exists(propertiesPath)) {
try (InputStream in = Files.newInputStream(propertiesPath)) {
properties.load(in);
@@ -202,6 +203,7 @@
SitePaths sitePath = new SitePaths(testSite);
pluginsSitePath = Files.createDirectories(sitePath.plugins_dir);
Files.createDirectories(sitePath.tmp_dir);
+ Files.createDirectories(sitePath.etc_dir);
}
private void copyJarToTestSite() throws IOException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java
similarity index 87%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java
index 6c7dbfe..261b894 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java
@@ -15,11 +15,11 @@
package com.google.gerrit.acceptance;
import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
+import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
-import java.nio.charset.StandardCharsets;
public class RestResponse extends HttpResponse {
@@ -30,9 +30,8 @@
@Override
public Reader getReader() throws IllegalStateException, IOException {
if (reader == null && response.getEntity() != null) {
- reader =
- new InputStreamReader(response.getEntity().getContent(),
- StandardCharsets.UTF_8);
+ reader = new InputStreamReader(
+ response.getEntity().getContent(), UTF_8);
reader.skip(JSON_MAGIC.length);
}
return reader;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
similarity index 94%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 98459b3..aee629d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -14,7 +14,8 @@
package com.google.gerrit.acceptance;
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Preconditions;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.extensions.restapi.RawInput;
@@ -30,7 +31,6 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
public class RestSession extends HttpSession {
@@ -79,7 +79,7 @@
put.addHeader(new BasicHeader("Content-Type", "application/json"));
put.body(new StringEntity(
OutputFormat.JSON_COMPACT.newGson().toJson(content),
- Charsets.UTF_8.name()));
+ UTF_8));
}
return execute(put);
}
@@ -105,7 +105,7 @@
post.addHeader(new BasicHeader("Content-Type", "application/json"));
post.body(new StringEntity(
OutputFormat.JSON_COMPACT.newGson().toJson(content),
- Charsets.UTF_8.name()));
+ UTF_8));
}
return execute(post);
}
@@ -116,7 +116,7 @@
public static RawInput newRawInput(String content) {
- return newRawInput(content.getBytes(StandardCharsets.UTF_8));
+ return newRawInput(content.getBytes(UTF_8));
}
public static RawInput newRawInput(final byte[] bytes) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestProjectInput.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestProjectInput.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestProjectInput.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestProjectInput.java
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseLocalDisk.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/UseLocalDisk.java
similarity index 100%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseLocalDisk.java
rename to gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/UseLocalDisk.java
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 3adab73..0a39ea7 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -2,10 +2,10 @@
name = 'lib',
srcs = glob(['src/test/java/com/google/gerrit/acceptance/*.java']),
exported_deps = [
+ '//gerrit-acceptance-framework:lib',
'//gerrit-common:annotations',
'//gerrit-common:server',
'//gerrit-extension-api:api',
- '//gerrit-gpg:gpg',
'//gerrit-gpg:testutil',
'//gerrit-launcher:launcher',
'//gerrit-lucene:lucene',
@@ -15,8 +15,8 @@
'//gerrit-pgm:util',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
- '//gerrit-server/src/main/prolog:common',
'//gerrit-server:testutil',
+ '//gerrit-server/src/main/prolog:common',
'//gerrit-sshd:sshd',
'//lib:args4j',
@@ -26,21 +26,13 @@
'//lib:h2',
'//lib:jsch',
'//lib:servlet-api-3_1',
- '//lib:truth',
- '//lib/auto:auto-value',
'//lib/bouncycastle:bcpg',
'//lib/bouncycastle:bcprov',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet',
- '//lib/httpcomponents:fluent-hc',
- '//lib/httpcomponents:httpclient',
- '//lib/httpcomponents:httpcore',
'//lib/jgit:jgit',
- '//lib/jgit:junit',
- '//lib/log:impl_log4j',
- '//lib/log:log4j',
'//lib/mina:sshd',
],
visibility = [
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 5baaa18..39296d0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -19,6 +19,10 @@
import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.gpg.PublicKeyStore.REFS_GPG_KEYS;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import static com.google.gerrit.gpg.testutil.TestKeys.allValidKeys;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithSecondUserId;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
@@ -40,7 +44,6 @@
import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.inject.Inject;
@@ -204,7 +207,7 @@
@Test
public void addGpgKey() throws Exception {
- TestKey key = TestKey.key1();
+ TestKey key = validKeyWithoutExpiration();
String id = key.getKeyIdString();
addExternalIdEmail(admin, "test1@example.com");
@@ -220,7 +223,7 @@
@Test
public void reAddExistingGpgKey() throws Exception {
addExternalIdEmail(admin, "test5@example.com");
- TestKey key = TestKey.key5();
+ TestKey key = validKeyWithSecondUserId();
String id = key.getKeyIdString();
PGPPublicKey pk = key.getPublicKey();
@@ -243,7 +246,7 @@
db.accountExternalIds().insert(Collections.singleton(extId));
- TestKey key = TestKey.key5();
+ TestKey key = validKeyWithSecondUserId();
addGpgKey(key.getPublicKeyArmored());
setApiUser(user);
@@ -254,7 +257,7 @@
@Test
public void listGpgKeys() throws Exception {
- List<TestKey> keys = TestKey.allValidKeys();
+ List<TestKey> keys = allValidKeys();
List<String> toAdd = new ArrayList<>(keys.size());
for (TestKey key : keys) {
addExternalIdEmail(admin,
@@ -267,7 +270,7 @@
@Test
public void deleteGpgKey() throws Exception {
- TestKey key = TestKey.key1();
+ TestKey key = validKeyWithoutExpiration();
String id = key.getKeyIdString();
addExternalIdEmail(admin, "test1@example.com");
addGpgKey(key.getPublicKeyArmored());
@@ -283,13 +286,13 @@
@Test
public void addAndRemoveGpgKeys() throws Exception {
- for (TestKey key : TestKey.allValidKeys()) {
+ for (TestKey key : allValidKeys()) {
addExternalIdEmail(admin,
PushCertificateIdent.parse(key.getFirstUserId()).getEmailAddress());
}
- TestKey key1 = TestKey.key1();
- TestKey key2 = TestKey.key2();
- TestKey key5 = TestKey.key5();
+ TestKey key1 = validKeyWithoutExpiration();
+ TestKey key2 = validKeyWithExpiration();
+ TestKey key5 = validKeyWithSecondUserId();
Map<String, GpgKeyInfo> infos = gApi.accounts().self().putGpgKeys(
ImmutableList.of(
@@ -374,8 +377,7 @@
}
// Check raw external IDs.
- Account.Id currAccountId =
- ((IdentifiedUser) atrScope.get().getCurrentUser()).getAccountId();
+ Account.Id currAccountId = atrScope.get().getUser().getAccountId();
assertThat(
GpgKeys.getGpgExtIds(db, currAccountId)
.transform(new Function<AccountExternalId, String>() {
@@ -411,6 +413,8 @@
assertThat(actual.userIds).named(id).containsExactlyElementsIn(userIds);
assertThat(actual.key).named(id)
.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n");
+ assertThat(actual.status).isEqualTo(GpgKeyInfo.Status.TRUSTED);
+ assertThat(actual.problems).isEmpty();
}
private void addExternalIdEmail(TestAccount account, String email)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/BUCK
index 1152d88..814dcf4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'api-account',
srcs = glob(['*IT.java']),
labels = ['api'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK
index 1152d88..5db2054 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'api-change',
srcs = glob(['*IT.java']),
labels = ['api'],
)
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 967575a..3d24c71 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
@@ -85,20 +85,32 @@
@Test
public void abandon() throws Exception {
PushOneCommit.Result r = createChange();
+ assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.NEW);
gApi.changes()
.id(r.getChangeId())
.abandon();
+ ChangeInfo info = get(r.getChangeId());
+ assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED);
+ assertThat(Iterables.getLast(info.messages).message.toLowerCase())
+ .contains("abandoned");
}
@Test
public void restore() throws Exception {
PushOneCommit.Result r = createChange();
+ assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.NEW);
gApi.changes()
.id(r.getChangeId())
.abandon();
+ assertThat(info(r.getChangeId()).status).isEqualTo(ChangeStatus.ABANDONED);
+
gApi.changes()
.id(r.getChangeId())
.restore();
+ ChangeInfo info = get(r.getChangeId());
+ assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+ assertThat(Iterables.getLast(info.messages).message.toLowerCase())
+ .contains("restored");
}
@Test
@@ -499,4 +511,28 @@
assertThat(approval._accountId).isEqualTo(user.id.get());
assertThat(approval.value).isNull();
}
+
+ @Test
+ public void pushCertificates() throws Exception {
+ PushOneCommit.Result r1 = createChange();
+ PushOneCommit.Result r2 = amendChange(r1.getChangeId());
+
+ ChangeInfo info = gApi.changes()
+ .id(r1.getChangeId())
+ .get(EnumSet.of(
+ ListChangesOption.ALL_REVISIONS,
+ ListChangesOption.PUSH_CERTIFICATES));
+
+ RevisionInfo rev1 = info.revisions.get(r1.getCommit().name());
+ assertThat(rev1).isNotNull();
+ assertThat(rev1.pushCertificate).isNotNull();
+ assertThat(rev1.pushCertificate.certificate).isNull();
+ assertThat(rev1.pushCertificate.key).isNull();
+
+ RevisionInfo rev2 = info.revisions.get(r2.getCommit().name());
+ assertThat(rev2).isNotNull();
+ assertThat(rev2.pushCertificate).isNotNull();
+ assertThat(rev2.pushCertificate.certificate).isNull();
+ assertThat(rev2.pushCertificate.key).isNull();
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/config/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/config/BUCK
index 1152d88..4918a95 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/config/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/config/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'api-config',
srcs = glob(['*IT.java']),
labels = ['api'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/BUCK
index 332459a..06be8ee 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/BUCK
@@ -1,12 +1,13 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'api-group',
srcs = glob(['*IT.java']),
deps = [
':util',
'//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account:util',
],
- labels = ['rest']
+ labels = ['api']
)
java_library(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 5b8b87f..c72edd7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -402,6 +402,13 @@
}
@Test
+ public void testSuggestGroup() throws Exception {
+ Map<String, GroupInfo> groups = gApi.groups().list().withSuggest("adm").getAsMap();
+ assertThat(groups).containsKey("Administrators");
+ assertThat(groups).hasSize(1);
+ }
+
+ @Test
public void testAllGroupInfoFieldsSetCorrectly() throws Exception {
AccountGroup adminGroup = getFromCache("Administrators");
Map<String, GroupInfo> groups =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK
index 1152d88..9dab9f8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'api-project',
srcs = glob(['*IT.java']),
labels = ['api'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK
index 1152d88..c916755 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'api-revision',
srcs = glob(['*IT.java']),
labels = ['api'],
)
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 5429b95..3e970e4 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
@@ -18,6 +18,7 @@
import static com.google.gerrit.acceptance.PushOneCommit.FILE_CONTENT;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import com.google.common.base.Predicate;
@@ -53,13 +54,13 @@
import org.junit.Test;
import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
@NoHttpd
@@ -428,7 +429,7 @@
.content();
ByteArrayOutputStream os = new ByteArrayOutputStream();
bin.writeTo(os);
- String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
+ String res = new String(os.toByteArray(), UTF_8);
assertThat(res).isEqualTo(FILE_CONTENT);
}
@@ -554,11 +555,13 @@
.patch();
ByteArrayOutputStream os = new ByteArrayOutputStream();
bin.writeTo(os);
- String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
- DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
+ String res = new String(os.toByteArray(), UTF_8);
ChangeInfo change = changeApi.get();
RevisionInfo rev = change.revisions.get(change.currentRevision);
- String date = dateFormat.format(rev.commit.author.date);
+ DateFormat df = new SimpleDateFormat(
+ "EEE, dd MMM yyyy HH:mm:ss Z",
+ Locale.US);
+ String date = df.format(rev.commit.author.date);
assertThat(res).isEqualTo(
String.format(PATCH, r.getCommitId().name(), date, r.getChangeId()));
}
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
index be6fcdc..c3274db 100644
--- 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
@@ -1,10 +1,11 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'edit',
srcs = ['ChangeEditIT.java'],
- labels = ['edit'],
deps = [
'//lib/commons:codec',
'//lib/joda:joda-time',
],
+ labels = ['edit'],
)
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
index 50bafbe..ed20e24 100644
--- 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
@@ -77,7 +77,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
@@ -319,7 +318,7 @@
Optional<ChangeEdit> edit = editUtil.byChange(change);
assertThat(edit.get().getEditCommit().getParentCount()).isEqualTo(0);
- String msg = String.format("New commit message\n\nChange-Id: %s",
+ String msg = String.format("New commit message\n\nChange-Id: %s\n",
change.getKey());
assertThat(modifier.modifyMessage(edit.get(), msg))
.isEqualTo(RefUpdate.Result.FORCED);
@@ -346,8 +345,9 @@
assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId)))
.isEqualTo(RefUpdate.Result.NEW);
Optional<ChangeEdit> edit = editUtil.byChange(change);
-
- String msg = String.format("New commit message\n\nChange-Id: %s",
+ assertUnchangedMessage(edit, edit.get().getEditCommit().getFullMessage());
+ assertUnchangedMessage(edit, edit.get().getEditCommit().getFullMessage() + "\n\n");
+ String msg = String.format("New commit message\n\nChange-Id: %s\n",
change.getKey());
assertThat(modifier.modifyMessage(edit.get(), msg)).isEqualTo(
RefUpdate.Result.FORCED);
@@ -374,7 +374,7 @@
.isEqualTo(SC_NOT_FOUND);
EditMessage.Input in = new EditMessage.Input();
in.message = String.format("New commit message\n\n" +
- CONTENT_NEW2_STR + "\n\nChange-Id: %s",
+ CONTENT_NEW2_STR + "\n\nChange-Id: %s\n",
change.getKey());
assertThat(adminSession.put(urlEditMessage(), in).getStatusCode())
.isEqualTo(SC_NO_CONTENT);
@@ -384,7 +384,7 @@
Optional<ChangeEdit> edit = editUtil.byChange(change);
assertThat(edit.get().getEditCommit().getFullMessage())
.isEqualTo(in.message);
- in.message = String.format("New commit message2\n\nChange-Id: %s",
+ in.message = String.format("New commit message2\n\nChange-Id: %s\n",
change.getKey());
assertThat(adminSession.put(urlEditMessage(), in).getStatusCode())
.isEqualTo(SC_NO_CONTENT);
@@ -713,6 +713,14 @@
assertThat(approvals.get(0).value).isEqualTo(1);
}
+ private void assertUnchangedMessage(Optional<ChangeEdit> edit, String message)
+ throws Exception {
+ exception.expect(UnchangedCommitMessageException.class);
+ exception.expectMessage(
+ "New commit message cannot be same as existing commit message");
+ modifier.modifyMessage(edit.get(), message);
+ }
+
@Test
public void testHasEditPredicate() throws Exception {
assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
@@ -749,21 +757,21 @@
private String newChange(PersonIdent ident) throws Exception {
PushOneCommit push =
pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
- new String(CONTENT_OLD, StandardCharsets.UTF_8));
+ new String(CONTENT_OLD, UTF_8));
return push.to("refs/for/master").getChangeId();
}
private String amendChange(PersonIdent ident, String changeId) throws Exception {
PushOneCommit push =
pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME2,
- new String(CONTENT_NEW2, StandardCharsets.UTF_8), changeId);
+ new String(CONTENT_NEW2, UTF_8), changeId);
return push.to("refs/for/master").getChangeId();
}
private String newChange2(PersonIdent ident) throws Exception {
PushOneCommit push =
pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
- new String(CONTENT_OLD, StandardCharsets.UTF_8));
+ new String(CONTENT_OLD, UTF_8));
return push.rm("refs/for/master").getChangeId();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 080b767..900d85a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -16,8 +16,12 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -27,25 +31,20 @@
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.testutil.ConfigSuite;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
public abstract class AbstractPushForReview extends AbstractDaemonTest {
- @ConfigSuite.Config
- public static Config noteDbEnabled() {
- return NotesMigration.allEnabledConfig();
- }
-
- @Inject
- private NotesMigration notesMigration;
-
protected enum Protocol {
// TODO(dborowitz): TEST.
SSH, HTTP
@@ -53,6 +52,24 @@
private String sshUrl;
+ @BeforeClass
+ public static void setTimeForTesting() {
+ 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);
+ }
+ });
+ }
+
+ @AfterClass
+ public static void restoreTime() {
+ DateTimeUtils.setCurrentMillisSystem();
+ }
+
@Before
public void setUp() throws Exception {
sshUrl = sshSession.getUrl();
@@ -177,6 +194,8 @@
assertThat(cr.all).hasSize(1);
assertThat(cr.all.get(0).name).isEqualTo("Administrator");
assertThat(cr.all.get(0).value).isEqualTo(1);
+ assertThat(Iterables.getLast(ci.messages).message).isEqualTo(
+ "Uploaded patch set 1: Code-Review+1.");
PushOneCommit push =
pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
@@ -185,9 +204,64 @@
ci = get(r.getChangeId());
cr = ci.labels.get("Code-Review");
+ assertThat(Iterables.getLast(ci.messages).message).isEqualTo(
+ "Uploaded patch set 2: Code-Review+2.");
+
assertThat(cr.all).hasSize(1);
assertThat(cr.all.get(0).name).isEqualTo("Administrator");
assertThat(cr.all.get(0).value).isEqualTo(2);
+
+ push =
+ pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+ "c.txt", "moreContent", r.getChangeId());
+ r = push.to("refs/for/master/%l=Code-Review+2");
+ ci = get(r.getChangeId());
+ assertThat(Iterables.getLast(ci.messages).message).isEqualTo(
+ "Uploaded patch set 3.");
+ }
+
+ /**
+ * There was a bug that allowed a user with Forge Committer Identity access
+ * right to upload a commit and put *votes on behalf of another user* on it.
+ * This test checks that this is not possible, but that the votes that are
+ * specified on push are applied only on behalf of the uploader.
+ *
+ * This particular bug only occurred when there was more than one label
+ * defined. However to test that the votes that are specified on push are
+ * applied on behalf of the uploader a single label is sufficient.
+ */
+ @Test
+ public void testPushForMasterWithApprovalsForgeCommitterButNoForgeVote()
+ throws Exception {
+ // Create a commit with "User" as author and committer
+ RevCommit c = commitBuilder()
+ .author(user.getIdent())
+ .committer(user.getIdent())
+ .add(PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT)
+ .message(PushOneCommit.SUBJECT)
+ .create();
+
+ // Push this commit as "Administrator" (requires Forge Committer Identity)
+ pushHead(testRepo, "refs/for/master/%l=Code-Review+1", false);
+
+ // Expected Code-Review votes:
+ // 1. 0 from User (committer):
+ // When the committer is forged, the committer is automatically added as
+ // reviewer, hence we expect a dummy 0 vote for the committer.
+ // 2. +1 from Administrator (uploader):
+ // On push Code-Review+1 was specified, hence we expect a +1 vote from
+ // the uploader.
+ ChangeInfo ci = get(GitUtil.getChangeId(testRepo, c).get());
+ LabelInfo cr = ci.labels.get("Code-Review");
+ assertThat(cr.all).hasSize(2);
+ int indexAdmin = admin.fullName.equals(cr.all.get(0).name) ? 0 : 1;
+ int indexUser = indexAdmin == 0 ? 1 : 0;
+ assertThat(cr.all.get(indexAdmin).name).isEqualTo(admin.fullName);
+ assertThat(cr.all.get(indexAdmin).value.intValue()).isEqualTo(1);
+ assertThat(cr.all.get(indexUser).name).isEqualTo(user.fullName);
+ assertThat(cr.all.get(indexUser).value.intValue()).isEqualTo(0);
+ assertThat(Iterables.getLast(ci.messages).message).isEqualTo(
+ "Uploaded patch set 1: Code-Review+1.");
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index 2dbbb16..53412cb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -62,14 +62,14 @@
return pushChangeTo(repo, "refs/heads/" + branch, "some change", "");
}
- protected void createSubscription(TestRepository<?> repo, String branch,
+ protected void createSubmoduleSubscription(TestRepository<?> repo, String branch,
String subscribeToRepo, String subscribeToBranch) throws Exception {
Config config = new Config();
- prepareSubscriptionConfigEntry(config, subscribeToRepo, subscribeToBranch);
- pushSubscriptionConfig(repo, branch, config);
+ prepareSubmoduleConfigEntry(config, subscribeToRepo, subscribeToBranch);
+ pushSubmoduleConfig(repo, branch, config);
}
- protected void prepareSubscriptionConfigEntry(Config config,
+ protected void prepareSubmoduleConfigEntry(Config config,
String subscribeToRepo, String subscribeToBranch) {
subscribeToRepo = name(subscribeToRepo);
// The submodule subscription module checks for gerrit.canonicalWebUrl to
@@ -79,10 +79,12 @@
+ subscribeToRepo;
config.setString("submodule", subscribeToRepo, "path", subscribeToRepo);
config.setString("submodule", subscribeToRepo, "url", url);
- config.setString("submodule", subscribeToRepo, "branch", subscribeToBranch);
+ if (subscribeToBranch != null) {
+ config.setString("submodule", subscribeToRepo, "branch", subscribeToBranch);
+ }
}
- protected void pushSubscriptionConfig(TestRepository<?> repo,
+ protected void pushSubmoduleConfig(TestRepository<?> repo,
String branch, Config config) throws Exception {
repo.branch("HEAD").commit().insertChangeId()
@@ -101,13 +103,14 @@
ObjectId commitId = repo.git().fetch().setRemote("origin").call()
.getAdvertisedRef("refs/heads/" + branch).getObjectId();
- RevWalk rw = repo.getRevWalk();
- RevCommit c = rw.parseCommit(commitId);
- rw.parseBody(c.getTree());
+ try (RevWalk rw = repo.getRevWalk()) {
+ RevCommit c = rw.parseCommit(commitId);
+ rw.parseBody(c.getTree());
- RevTree tree = c.getTree();
- RevObject actualId = repo.get(tree, submodule);
+ RevTree tree = c.getTree();
+ RevObject actualId = repo.get(tree, submodule);
- assertThat(actualId).isEqualTo(expectedId);
+ assertThat(actualId).isEqualTo(expectedId);
+ }
}
-}
\ No newline at end of file
+}
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 446a183..f6796a5 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,28 +1,22 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
- srcs = [
- 'DraftChangeBlockedIT.java',
- 'ForcePushIT.java',
- 'SubmitOnPushIT.java',
- 'SubmoduleSubscriptionsWholeTopicMergeIT.java',
- 'SubmoduleSubscriptionsIT.java',
- 'VisibleRefFilterIT.java',
+ group = 'git',
+ srcs = glob(['*IT.java']),
+ deps = [
+ ':submodule_util',
+ ':push_for_review',
],
- deps = [':submodule_util'],
- labels = ['git'],
-)
-
-acceptance_tests(
- srcs = ['HttpPushForReviewIT.java', 'SshPushForReviewIT.java'],
- deps = [':push_for_review'],
labels = ['git'],
)
java_library(
name = 'push_for_review',
srcs = ['AbstractPushForReview.java'],
- deps = ['//gerrit-acceptance-tests:lib'],
+ deps = [
+ '//gerrit-acceptance-tests:lib',
+ '//lib/joda:joda-time',
+ ],
)
java_library(
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 9a8bb51..78ffa20 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
@@ -80,8 +80,6 @@
@Test
public void submitOnPushWithAnnotatedTag() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
- grant(Permission.CREATE, project, "refs/tags/*");
- grant(Permission.PUSH, project, "refs/tags/*");
PushOneCommit.AnnotatedTag tag =
new PushOneCommit.AnnotatedTag("v1.0", "annotation", admin.getIdent());
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index 707852f..0efeb94 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -33,7 +33,7 @@
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEAD);
@@ -45,7 +45,7 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEAD);
@@ -58,7 +58,7 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
// The first update doesn't include any commit messages
ObjectId subRepoId = pushChangeTo(subRepo, "master");
@@ -82,7 +82,7 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
// The first update doesn't include the rev log
@@ -110,7 +110,7 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
@@ -133,7 +133,7 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
@@ -155,7 +155,7 @@
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
- createSubscription(superRepo, "master", "subscribed-to-project", "foo");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "foo");
ObjectId subFoo = pushChangeTo(subRepo, "foo");
pushChangeTo(subRepo, "master");
@@ -169,8 +169,8 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
- createSubscription(subRepo, "master", "super-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(subRepo, "master", "super-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
pushChangeTo(superRepo, "master");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 086c205..e4a054a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -41,7 +41,7 @@
public void testSubscriptionUpdateOfManyChanges() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
- createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
ObjectId subHEAD = subRepo.branch("HEAD").commit().insertChangeId()
.message("some change")
@@ -97,10 +97,10 @@
TestRepository<?> sub3 = createProjectWithPush("sub3");
Config config = new Config();
- prepareSubscriptionConfigEntry(config, "sub1", "master");
- prepareSubscriptionConfigEntry(config, "sub2", "master");
- prepareSubscriptionConfigEntry(config, "sub3", "master");
- pushSubscriptionConfig(superRepo, "master", config);
+ prepareSubmoduleConfigEntry(config, "sub1", "master");
+ prepareSubmoduleConfigEntry(config, "sub2", "master");
+ prepareSubmoduleConfigEntry(config, "sub3", "master");
+ pushSubmoduleConfig(superRepo, "master", config);
ObjectId superPreviousId = pushChangeTo(superRepo, "master");
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
index 21efc87..a9a7dfa 100644
--- 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
@@ -34,35 +34,20 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.edit.ChangeEditModifier;
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)
@NoHttpd
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 NotesMigration notesMigration;
-
@Inject
private ChangeEditModifier editModifier;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
index 00b53f9..ff167ac 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
@@ -1,7 +1,8 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'pgm',
srcs = glob(['*IT.java']),
- labels = ['pgm'],
source_under_test = ['//gerrit-pgm:pgm'],
+ labels = ['pgm'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java
index c538b85..5d0e7df 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/RebuildNotedbIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.pgm;
import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.Files;
import com.google.gerrit.launcher.GerritLauncher;
@@ -26,7 +27,6 @@
import org.junit.Test;
import java.io.File;
-import java.nio.charset.StandardCharsets;
public class RebuildNotedbIT {
private File sitePath;
@@ -48,7 +48,7 @@
initSite();
Files.append(NotesMigration.allEnabledConfig().toText(),
new File(sitePath.toString(), "etc/gerrit.config"),
- StandardCharsets.UTF_8);
+ UTF_8);
runGerrit("RebuildNotedb", "-d", sitePath.toString(),
"--show-stack-trace");
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
index f081ada..b7c1819 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'rest-account',
srcs = glob(['*IT.java']),
deps = [':util'],
labels = ['rest']
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EditPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EditPreferencesIT.java
new file mode 100644
index 0000000..8770c3c
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EditPreferencesIT.java
@@ -0,0 +1,95 @@
+// 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.rest.account;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
+import com.google.gerrit.extensions.client.KeyMapType;
+import com.google.gerrit.extensions.client.Theme;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class EditPreferencesIT extends AbstractDaemonTest {
+ @Test
+ public void getSetEditPreferences() throws Exception {
+ String endPoint = "/accounts/" + admin.email + "/preferences.edit";
+ RestResponse r = adminSession.get(endPoint);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ EditPreferencesInfo out = getEditPrefInfo(r);
+
+ assertThat(out.lineLength).isEqualTo(100);
+ assertThat(out.tabSize).isEqualTo(8);
+ assertThat(out.cursorBlinkRate).isEqualTo(0);
+ assertThat(out.hideTopMenu).isNull();
+ assertThat(out.showTabs).isTrue();
+ assertThat(out.showWhitespaceErrors).isNull();
+ assertThat(out.syntaxHighlighting).isTrue();
+ assertThat(out.hideLineNumbers).isNull();
+ assertThat(out.matchBrackets).isTrue();
+ assertThat(out.autoCloseBrackets).isNull();
+ assertThat(out.theme).isEqualTo(Theme.DEFAULT);
+ assertThat(out.keyMapType).isEqualTo(KeyMapType.DEFAULT);
+
+ // change some default values
+ out.lineLength = 80;
+ out.tabSize = 4;
+ out.cursorBlinkRate = 500;
+ out.hideTopMenu = true;
+ out.showTabs = false;
+ out.showWhitespaceErrors = true;
+ out.syntaxHighlighting = false;
+ out.hideLineNumbers = true;
+ out.matchBrackets = false;
+ out.autoCloseBrackets = true;
+ out.theme = Theme.TWILIGHT;
+ out.keyMapType = KeyMapType.EMACS;
+
+ r = adminSession.put(endPoint, out);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+
+ r = adminSession.get(endPoint);
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ EditPreferencesInfo info = getEditPrefInfo(r);
+ assertEditPreferences(info, out);
+ }
+
+ private EditPreferencesInfo getEditPrefInfo(RestResponse r)
+ throws IOException {
+ return newGson().fromJson(r.getReader(),
+ EditPreferencesInfo.class);
+ }
+
+ private void assertEditPreferences(EditPreferencesInfo out,
+ EditPreferencesInfo in) {
+ assertThat(out.lineLength).isEqualTo(in.lineLength);
+ assertThat(out.tabSize).isEqualTo(in.tabSize);
+ assertThat(out.cursorBlinkRate).isEqualTo(in.cursorBlinkRate);
+ assertThat(out.hideTopMenu).isEqualTo(in.hideTopMenu);
+ assertThat(out.showTabs).isNull();
+ assertThat(out.showWhitespaceErrors).isEqualTo(in.showWhitespaceErrors);
+ assertThat(out.syntaxHighlighting).isNull();
+ assertThat(out.hideLineNumbers).isEqualTo(in.hideLineNumbers);
+ assertThat(out.matchBrackets).isNull();
+ assertThat(out.autoCloseBrackets).isEqualTo(in.autoCloseBrackets);
+ assertThat(out.theme).isEqualTo(in.theme);
+ assertThat(out.keyMapType).isEqualTo(in.keyMapType);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 438ebec..ef3faa5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -161,7 +161,7 @@
"Change has been successfully cherry-picked as ");
} else {
assertThat(Iterables.getLast(info.messages).message).isEqualTo(
- "Change has been successfully merged into the git repository by Administrator");
+ "Change has been successfully merged by Administrator");
}
}
@@ -253,9 +253,10 @@
assertThat(c.revisions.get(expectedId.name())._number).isEqualTo(expectedNum);
try (Repository repo =
repoManager.openRepository(new Project.NameKey(c.project))) {
- Ref ref = repo.getRef(
- new PatchSet.Id(new Change.Id(c._number), expectedNum).toRefName());
- assertThat(ref).isNotNull();
+ String refName = new PatchSet.Id(new Change.Id(c._number), expectedNum)
+ .toRefName();
+ Ref ref = repo.getRef(refName);
+ assertThat(ref).named(refName).isNotNull();
assertThat(ref.getObjectId()).isEqualTo(expectedId);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index f42a134..1a8e151 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -5,10 +5,11 @@
'AbstractSubmitByMerge.java',
]
-SUBMIT_TESTS = glob(['Submit*IT.java'], excludes = SUBMIT_UTIL_SRCS)
-OTHER_TESTS = glob(['*IT.java'], excludes = SUBMIT_TESTS + SUBMIT_UTIL_SRCS)
+SUBMIT_TESTS = glob(['Submit*IT.java'])
+OTHER_TESTS = glob(['*IT.java'], excludes = SUBMIT_TESTS)
acceptance_tests(
+ group = 'rest-change-other',
srcs = OTHER_TESTS,
deps = [
':submit_util',
@@ -16,8 +17,9 @@
],
labels = ['rest'],
)
-
+
acceptance_tests(
+ group = 'rest-change-submit',
srcs = SUBMIT_TESTS,
deps = [
':submit_util',
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 a56a7f2..d726d70 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
@@ -22,10 +22,8 @@
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.testutil.ConfigSuite;
-import org.eclipse.jgit.lib.Config;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeUtils.MillisProvider;
@@ -42,11 +40,6 @@
private String systemTimeZone;
private volatile long clockStepMs;
- @ConfigSuite.Config
- public static Config noteDbEnabled() {
- return NotesMigration.allEnabledConfig();
- }
-
@Before
public void setTimeForTesting() {
systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
index 6ef53ff..aa7305a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
import com.google.common.collect.Sets;
import com.google.common.truth.IterableSubject;
@@ -22,17 +23,15 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.testutil.ConfigSuite;
-import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class HashtagsIT extends AbstractDaemonTest {
- @ConfigSuite.Default
- public static Config defaultConfig() {
- return NotesMigration.allEnabledConfig();
+ @Before
+ public void before() {
+ assume().that(notesMigration.enabled()).isTrue();
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 2af3c25..a87b7d9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -49,8 +49,10 @@
PushOneCommit.Result change = createChange();
PushOneCommit.Result change2 = createChange();
- approve(change.getChangeId());
- submit(change2.getChangeId());
+ String id1 = change.getChangeId();
+ String id2 = change2.getChangeId();
+ approve(id1);
+ submit(id2);
RevCommit head = getRemoteHead();
assertThat(head.getId()).isEqualTo(change2.getCommitId());
@@ -59,6 +61,8 @@
assertSubmitter(change2.getChangeId(), 1);
assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
+ assertSubmittedTogether(id1, id2, id1);
+ assertSubmittedTogether(id2, id2, id1);
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK
index c89da30..0802e7c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'rest-config',
srcs = glob(['*IT.java']),
labels = ['rest']
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
index ffdfa8b..d991417 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'rest-group',
srcs = glob(['*IT.java']),
labels = ['rest']
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
index 50e6b84..c1618fb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'rest-project',
srcs = glob(['*IT.java']),
deps = [
':branch',
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 1b79bc3..6617127 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
@@ -22,7 +22,7 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.extensions.api.projects.BranchInfo;
-import com.google.gerrit.extensions.api.projects.ProjectApi.ListBranchesRequest;
+import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import org.junit.Test;
@@ -155,7 +155,7 @@
list().withRegex(".*ast.*r").get());
}
- private ListBranchesRequest list() throws Exception {
+ private ListRefsRequest<BranchInfo> list() throws Exception {
return gApi.projects().name(project.get()).branches();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
index 7efefa7..e908675 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -16,27 +16,46 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+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.Permission;
-import com.google.gerrit.extensions.common.TagInfo;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
+import com.google.gerrit.extensions.api.projects.TagInfo;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.PushCommand;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.junit.Test;
import java.util.List;
public class TagsIT extends AbstractDaemonTest {
+ private static final List<String> testTags = ImmutableList.of(
+ "tag-A", "tag-B", "tag-C", "tag-D", "tag-E", "tag-F", "tag-G", "tag-H");
+
@Test
- public void listTagsOfNonExistingProject_NotFound() throws Exception {
+ public void listTagsOfNonExistingProject() throws Exception {
assertThat(adminSession.get("/projects/non-existing/tags").getStatusCode())
.isEqualTo(HttpStatus.SC_NOT_FOUND);
}
@Test
- public void listTagsOfNonVisibleProject_NotFound() throws Exception {
+ public void listTagsOfNonExistingProjectWithApi() throws Exception {
+ exception.expect(ResourceNotFoundException.class);
+ gApi.projects().name("does-not-exist").tags();
+ exception.expect(ResourceNotFoundException.class);
+ gApi.projects().name("does-not-exist").tag("tag").get();
+ }
+
+ @Test
+ public void listTagsOfNonVisibleProject() throws Exception {
blockRead(project, "refs/*");
assertThat(
userSession.get("/projects/" + project.get() + "/tags").getStatusCode())
@@ -44,6 +63,15 @@
}
@Test
+ public void listTagsOfNonVisibleProjectWithApi() throws Exception {
+ blockRead(project, "refs/*");
+ exception.expect(ResourceNotFoundException.class);
+ gApi.projects().name(project.get()).tags();
+ exception.expect(ResourceNotFoundException.class);
+ gApi.projects().name(project.get()).tag("tag").get();
+ }
+
+ @Test
public void listTags() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
grant(Permission.CREATE, project, "refs/tags/*");
@@ -62,20 +90,88 @@
PushOneCommit.Result r2 = push2.to("refs/for/master%submit");
r2.assertOkStatus();
- List<TagInfo> result =
- toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
- assertThat(result).hasSize(2);
+ String tag3Ref = Constants.R_TAGS + "vLatest";
+ PushCommand pushCmd = testRepo.git().push();
+ pushCmd.setRefSpecs(new RefSpec(tag2.name + ":" + tag3Ref));
+ Iterable<PushResult> r = pushCmd.call();
+ assertThat(Iterables.getOnlyElement(r).getRemoteUpdate(tag3Ref).getStatus())
+ .isEqualTo(Status.OK);
+
+ List<TagInfo> result = getTags().get();
+ assertThat(result).hasSize(3);
TagInfo t = result.get(0);
- assertThat(t.ref).isEqualTo("refs/tags/" + tag1.name);
+ assertThat(t.ref).isEqualTo(Constants.R_TAGS + tag1.name);
assertThat(t.revision).isEqualTo(r1.getCommitId().getName());
t = result.get(1);
- assertThat(t.ref).isEqualTo("refs/tags/" + tag2.name);
+ assertThat(t.ref).isEqualTo(Constants.R_TAGS + tag2.name);
assertThat(t.object).isEqualTo(r2.getCommitId().getName());
assertThat(t.message).isEqualTo(tag2.message);
assertThat(t.tagger.name).isEqualTo(tag2.tagger.getName());
assertThat(t.tagger.email).isEqualTo(tag2.tagger.getEmailAddress());
+
+ t = result.get(2);
+ assertThat(t.ref).isEqualTo(tag3Ref);
+ assertThat(t.object).isEqualTo(r2.getCommitId().getName());
+ assertThat(t.message).isEqualTo(tag2.message);
+ assertThat(t.tagger.name).isEqualTo(tag2.tagger.getName());
+ assertThat(t.tagger.email).isEqualTo(tag2.tagger.getEmailAddress());
+ }
+
+ private void assertTagList(FluentIterable<String> expected, List<TagInfo> actual)
+ throws Exception {
+ assertThat(actual).hasSize(expected.size());
+ for (int i = 0; i < expected.size(); i ++) {
+ assertThat(actual.get(i).ref).isEqualTo("refs/tags/" + expected.get(i));
+ }
+ }
+
+ @Test
+ public void listTagsWithoutOptions() throws Exception {
+ createTags();
+ List<TagInfo> result = getTags().get();
+ assertTagList(FluentIterable.from(testTags), result);
+ }
+
+ @Test
+ public void listTagsWithStartOption() throws Exception {
+ createTags();
+ List<TagInfo> result = getTags().withStart(1).get();
+ assertTagList(FluentIterable.from(testTags).skip(1), result);
+ }
+
+ @Test
+ public void listTagsWithLimitOption() throws Exception {
+ createTags();
+ int limit = testTags.size() - 1;
+ List<TagInfo> result = getTags().withLimit(limit).get();
+ assertTagList(FluentIterable.from(testTags).limit(limit), result);
+ }
+
+ @Test
+ public void listTagsWithLimitAndStartOption() throws Exception {
+ createTags();
+ int limit = testTags.size() - 3;
+ List<TagInfo> result = getTags().withStart(1).withLimit(limit).get();
+ assertTagList(FluentIterable.from(testTags).skip(1).limit(limit), result);
+ }
+
+ @Test
+ public void listTagsWithRegexFilter() throws Exception {
+ createTags();
+ List<TagInfo> result = getTags().withRegex("^tag-[C|D]$").get();
+ assertTagList(
+ FluentIterable.from(ImmutableList.of("tag-C", "tag-D")), result);
+ }
+
+ @Test
+ public void listTagsWithSubstringFilter() throws Exception {
+ createTags();
+ List<TagInfo> result = getTags().withSubstring("tag-").get();
+ assertTagList(FluentIterable.from(testTags), result);
+ result = getTags().withSubstring("ag-B").get();
+ assertTagList(FluentIterable.from(ImmutableList.of("tag-B")), result);
}
@Test
@@ -98,8 +194,7 @@
PushOneCommit.Result r2 = push2.to("refs/for/hidden%submit");
r2.assertOkStatus();
- List<TagInfo> result =
- toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ List<TagInfo> result = getTags().get();
assertThat(result).hasSize(2);
assertThat(result.get(0).ref).isEqualTo("refs/tags/" + tag1.name);
assertThat(result.get(0).revision).isEqualTo(r1.getCommitId().getName());
@@ -107,8 +202,7 @@
assertThat(result.get(1).revision).isEqualTo(r2.getCommitId().getName());
blockRead(project, "refs/heads/hidden");
- result =
- toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ result = getTags().get();
assertThat(result).hasSize(1);
assertThat(result.get(0).ref).isEqualTo("refs/tags/" + tag1.name);
assertThat(result.get(0).revision).isEqualTo(r1.getCommitId().getName());
@@ -126,18 +220,29 @@
PushOneCommit.Result r1 = push1.to("refs/for/master%submit");
r1.assertOkStatus();
- RestResponse response =
- adminSession.get("/projects/" + project.get() + "/tags/" + tag1.name);
- TagInfo tagInfo =
- newGson().fromJson(response.getReader(), TagInfo.class);
+ TagInfo tagInfo = getTag(tag1.name);
assertThat(tagInfo.ref).isEqualTo("refs/tags/" + tag1.name);
assertThat(tagInfo.revision).isEqualTo(r1.getCommitId().getName());
}
- private static List<TagInfo> toTagInfoList(RestResponse r) throws Exception {
- List<TagInfo> result =
- newGson().fromJson(r.getReader(),
- new TypeToken<List<TagInfo>>() {}.getType());
- return result;
+ private void createTags() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+ for (String tagname : testTags) {
+ PushOneCommit.Tag tag = new PushOneCommit.Tag(tagname);
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+ push.setTag(tag);
+ PushOneCommit.Result result = push.to("refs/for/master%submit");
+ result.assertOkStatus();
+ }
+ }
+
+ private ListRefsRequest<TagInfo> getTags() throws Exception {
+ return gApi.projects().name(project.get()).tags();
+ }
+
+ private TagInfo getTag(String ref) throws Exception {
+ return gApi.projects().name(project.get()).tag(ref).get();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
index fce853b..94e69da 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'server-change',
srcs = glob(['*IT.java']),
labels = ['server'],
)
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
index 721c712..5592755 100644
--- 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
@@ -39,14 +39,11 @@
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Revisions;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.FakeEmailSender.Message;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
@@ -57,11 +54,6 @@
@NoHttpd
public class CommentsIT extends AbstractDaemonTest {
- @ConfigSuite.Config
- public static Config noteDbEnabled() {
- return NotesMigration.allEnabledConfig();
- }
-
@Inject
private Provider<ChangesCollection> changes;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 9a88f56..42f3fe7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -20,7 +20,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.extensions.common.CommitInfo;
@@ -75,38 +74,41 @@
@Test
public void getRelatedLinear() throws Exception {
+ // 1,1---2,1
RevCommit c1_1 = commitBuilder()
.add("a.txt", "1")
.message("subject: 1")
.create();
- String id1 = getChangeId(c1_1);
- RevCommit c2_2 = commitBuilder()
+ RevCommit c2_1 = commitBuilder()
.add("b.txt", "2")
.message("subject: 2")
.create();
- String id2 = getChangeId(c2_2);
pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_1 = getPatchSetId(c1_1);
+ PatchSet.Id ps2_1 = getPatchSetId(c2_1);
- for (RevCommit c : ImmutableList.of(c2_2, c1_1)) {
- assertRelated(getPatchSetId(c),
- changeAndCommit(id2, c2_2, 1, 1),
- changeAndCommit(id1, c1_1, 1, 1));
+ for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
+ assertRelated(ps,
+ changeAndCommit(ps2_1, c2_1, 1),
+ changeAndCommit(ps1_1, c1_1, 1));
}
}
@Test
public void getRelatedReorder() throws Exception {
+ // 1,1---2,1
+ //
+ // 2,2---1,2
+
// Create two commits and push.
RevCommit c1_1 = commitBuilder()
.add("a.txt", "1")
.message("subject: 1")
.create();
- String id1 = getChangeId(c1_1);
RevCommit c2_1 = commitBuilder()
.add("b.txt", "2")
.message("subject: 2")
.create();
- String id2 = getChangeId(c2_1);
pushHead(testRepo, "refs/for/master", false);
PatchSet.Id ps1_1 = getPatchSetId(c1_1);
PatchSet.Id ps2_1 = getPatchSetId(c2_1);
@@ -121,31 +123,71 @@
for (PatchSet.Id ps : ImmutableList.of(ps2_2, ps1_2)) {
assertRelated(ps,
- changeAndCommit(id1, c1_2, 2, 2),
- changeAndCommit(id2, c2_2, 2, 2));
+ changeAndCommit(ps1_2, c1_2, 2),
+ changeAndCommit(ps2_2, c2_2, 2));
}
for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
assertRelated(ps,
- changeAndCommit(id2, c2_1, 1, 2),
- changeAndCommit(id1, c1_1, 1, 2));
+ changeAndCommit(ps2_1, c2_1, 2),
+ changeAndCommit(ps1_1, c1_1, 2));
}
}
@Test
+ public void getRelatedAmendParentChange() throws Exception {
+ // 1,1---2,1
+ //
+ // 1,2
+
+ // Create two commits and push.
+ RevCommit c1_1 = commitBuilder()
+ .add("a.txt", "1")
+ .message("subject: 1")
+ .create();
+ RevCommit c2_1 = commitBuilder()
+ .add("b.txt", "2")
+ .message("subject: 2")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_1 = getPatchSetId(c1_1);
+ PatchSet.Id ps2_1 = getPatchSetId(c2_1);
+
+ // Amend parent change and push.
+ testRepo.reset("HEAD~1");
+ RevCommit c1_2 = amendBuilder()
+ .add("c.txt", "2")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_2 = getPatchSetId(c1_2);
+
+ for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
+ assertRelated(ps,
+ changeAndCommit(ps2_1, c2_1, 1),
+ changeAndCommit(ps1_1, c1_1, 2));
+ }
+
+ assertRelated(ps1_2,
+ changeAndCommit(ps2_1, c2_1, 1),
+ changeAndCommit(ps1_2, c1_2, 2));
+ }
+
+ @Test
public void getRelatedReorderAndExtend() throws Exception {
+ // 1,1---2,1
+ //
+ // 2,2---1,2---3,1
+
// Create two commits and push.
ObjectId initial = repo().getRef("HEAD").getObjectId();
RevCommit c1_1 = commitBuilder()
.add("a.txt", "1")
.message("subject: 1")
.create();
- String id1 = getChangeId(c1_1);
RevCommit c2_1 = commitBuilder()
.add("b.txt", "2")
.message("subject: 2")
.create();
- String id2 = getChangeId(c2_1);
pushHead(testRepo, "refs/for/master", false);
PatchSet.Id ps1_1 = getPatchSetId(c1_1);
PatchSet.Id ps2_1 = getPatchSetId(c2_1);
@@ -158,7 +200,6 @@
.add("c.txt", "3")
.message("subject: 3")
.create();
- String id3 = getChangeId(c3_1);
pushHead(testRepo, "refs/for/master", false);
PatchSet.Id ps1_2 = getPatchSetId(c1_1);
PatchSet.Id ps2_2 = getPatchSetId(c2_1);
@@ -166,36 +207,248 @@
for (PatchSet.Id ps : ImmutableList.of(ps3_1, ps2_2, ps1_2)) {
assertRelated(ps,
- changeAndCommit(id3, c3_1, 1, 1),
- changeAndCommit(id1, c1_2, 2, 2),
- changeAndCommit(id2, c2_2, 2, 2));
+ changeAndCommit(ps3_1, c3_1, 1),
+ changeAndCommit(ps1_2, c1_2, 2),
+ changeAndCommit(ps2_2, c2_2, 2));
}
for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
assertRelated(ps,
- changeAndCommit(id3, c3_1, 1, 1),
- changeAndCommit(id2, c2_1, 1, 2),
- changeAndCommit(id1, c1_1, 1, 2));
+ changeAndCommit(ps3_1, c3_1, 1),
+ changeAndCommit(ps2_1, c2_1, 2),
+ changeAndCommit(ps1_1, c1_1, 2));
+ }
+ }
+
+ @Test
+ public void getRelatedReworkSeries() throws Exception {
+ // 1,1---2,1---3,1
+ //
+ // 1,2---2,2---3,2
+
+ // Create three commits and push.
+ RevCommit c1_1 = commitBuilder()
+ .add("a.txt", "1")
+ .message("subject: 1")
+ .create();
+ RevCommit c2_1 = commitBuilder()
+ .add("b.txt", "1")
+ .message("subject: 2")
+ .create();
+ RevCommit c3_1 = commitBuilder()
+ .add("b.txt", "1")
+ .message("subject: 3")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_1 = getPatchSetId(c1_1);
+ PatchSet.Id ps2_1 = getPatchSetId(c2_1);
+ PatchSet.Id ps3_1 = getPatchSetId(c3_1);
+
+ // Amend all changes change and push.
+ testRepo.reset(c1_1);
+ RevCommit c1_2 = amendBuilder()
+ .add("a.txt", "2")
+ .create();
+ RevCommit c2_2 = commitBuilder()
+ .add("b.txt", "2")
+ .message(parseBody(c2_1).getFullMessage())
+ .create();
+ RevCommit c3_2 = commitBuilder()
+ .add("b.txt", "3")
+ .message(parseBody(c3_1).getFullMessage())
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_2 = getPatchSetId(c1_2);
+ PatchSet.Id ps2_2 = getPatchSetId(c2_2);
+ PatchSet.Id ps3_2 = getPatchSetId(c3_2);
+
+ for (PatchSet.Id ps : ImmutableList.of(ps1_1, ps2_1, ps3_1)) {
+ assertRelated(ps,
+ changeAndCommit(ps3_1, c3_1, 2),
+ changeAndCommit(ps2_1, c2_1, 2),
+ changeAndCommit(ps1_1, c1_1, 2));
+ }
+
+ for (PatchSet.Id ps : ImmutableList.of(ps1_2, ps2_2, ps3_2)) {
+ assertRelated(ps,
+ changeAndCommit(ps3_2, c3_2, 2),
+ changeAndCommit(ps2_2, c2_2, 2),
+ changeAndCommit(ps1_2, c1_2, 2));
+ }
+ }
+
+ @Test
+ public void getRelatedReworkThenExtendInTheMiddleOfSeries() throws Exception {
+ // 1,1---2,1---3,1
+ //
+ // 1,2---2,2---3,2
+ // \---4,1
+
+ // Create three commits and push.
+ RevCommit c1_1 = commitBuilder()
+ .add("a.txt", "1")
+ .message("subject: 1")
+ .create();
+ RevCommit c2_1 = commitBuilder()
+ .add("b.txt", "1")
+ .message("subject: 2")
+ .create();
+ RevCommit c3_1 = commitBuilder()
+ .add("b.txt", "1")
+ .message("subject: 3")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_1 = getPatchSetId(c1_1);
+ PatchSet.Id ps2_1 = getPatchSetId(c2_1);
+ PatchSet.Id ps3_1 = getPatchSetId(c3_1);
+
+ // Amend all changes change and push.
+ testRepo.reset(c1_1);
+ RevCommit c1_2 = amendBuilder()
+ .add("a.txt", "2")
+ .create();
+ RevCommit c2_2 = commitBuilder()
+ .add("b.txt", "2")
+ .message(parseBody(c2_1).getFullMessage())
+ .create();
+ RevCommit c3_2 = commitBuilder()
+ .add("b.txt", "3")
+ .message(parseBody(c3_1).getFullMessage())
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_2 = getPatchSetId(c1_2);
+ PatchSet.Id ps2_2 = getPatchSetId(c2_2);
+ PatchSet.Id ps3_2 = getPatchSetId(c3_2);
+
+ // Add one more commit 4,1 based on 1,2.
+ testRepo.reset(c1_2);
+ RevCommit c4_1 = commitBuilder()
+ .add("d.txt", "4")
+ .message("subject: 4")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps4_1 = getPatchSetId(c4_1);
+
+ // 1,1 is related indirectly to 4,1.
+ assertRelated(ps1_1,
+ changeAndCommit(ps4_1, c4_1, 1),
+ changeAndCommit(ps3_1, c3_1, 2),
+ changeAndCommit(ps2_1, c2_1, 2),
+ changeAndCommit(ps1_1, c1_1, 2));
+
+ // 2,1 and 3,1 don't include 4,1 since we don't walk forward after walking
+ // backward.
+ for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps3_1)) {
+ assertRelated(ps,
+ changeAndCommit(ps3_1, c3_1, 2),
+ changeAndCommit(ps2_1, c2_1, 2),
+ changeAndCommit(ps1_1, c1_1, 2));
+ }
+
+ // 1,2 is related directly to 4,1, and the 2-3 parallel branch stays intact.
+ assertRelated(ps1_2,
+ changeAndCommit(ps3_2, c3_2, 2),
+ changeAndCommit(ps4_1, c4_1, 1),
+ changeAndCommit(ps2_2, c2_2, 2),
+ changeAndCommit(ps1_2, c1_2, 2));
+
+ // 4,1 is only related to 1,2, since we don't walk forward after walking
+ // backward.
+ assertRelated(ps4_1,
+ changeAndCommit(ps4_1, c4_1, 1),
+ changeAndCommit(ps1_2, c1_2, 2));
+
+ // 2,2 and 3,2 don't include 4,1 since we don't walk forward after walking
+ // backward.
+ for (PatchSet.Id ps : ImmutableList.of(ps2_2, ps3_2)) {
+ assertRelated(ps,
+ changeAndCommit(ps3_2, c3_2, 2),
+ changeAndCommit(ps2_2, c2_2, 2),
+ changeAndCommit(ps1_2, c1_2, 2));
+ }
+ }
+
+ @Test
+ public void getRelatedCrissCrossDependency() throws Exception {
+ // 1,1---2,1---3,2
+ //
+ // 1,2---2,2---3,1
+
+ // Create two commits and push.
+ RevCommit c1_1 = commitBuilder()
+ .add("a.txt", "1")
+ .message("subject: 1")
+ .create();
+ RevCommit c2_1 = commitBuilder()
+ .add("b.txt", "2")
+ .message("subject: 2")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_1 = getPatchSetId(c1_1);
+ PatchSet.Id ps2_1 = getPatchSetId(c2_1);
+
+ // Amend both changes change and push.
+ testRepo.reset(c1_1);
+ RevCommit c1_2 = amendBuilder()
+ .add("a.txt", "2")
+ .create();
+ RevCommit c2_2 = commitBuilder()
+ .add("b.txt", "2")
+ .message(parseBody(c2_1).getFullMessage())
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps1_2 = getPatchSetId(c1_2);
+ PatchSet.Id ps2_2 = getPatchSetId(c2_2);
+
+ // PS 3,1 depends on 2,2.
+ RevCommit c3_1 = commitBuilder()
+ .add("c.txt", "1")
+ .message("subject: 3")
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps3_1 = getPatchSetId(c3_1);
+
+ // PS 3,2 depends on 2,1.
+ testRepo.reset(c2_1);
+ RevCommit c3_2 = commitBuilder()
+ .add("c.txt", "2")
+ .message(parseBody(c3_1).getFullMessage())
+ .create();
+ pushHead(testRepo, "refs/for/master", false);
+ PatchSet.Id ps3_2 = getPatchSetId(c3_2);
+
+ for (PatchSet.Id ps : ImmutableList.of(ps1_1, ps2_1, ps3_2)) {
+ assertRelated(ps,
+ changeAndCommit(ps3_2, c3_2, 2),
+ changeAndCommit(ps2_1, c2_1, 2),
+ changeAndCommit(ps1_1, c1_1, 2));
+ }
+
+ for (PatchSet.Id ps : ImmutableList.of(ps1_2, ps2_2, ps3_1)) {
+ assertRelated(ps,
+ changeAndCommit(ps3_1, c3_1, 2),
+ changeAndCommit(ps2_2, c2_2, 2),
+ changeAndCommit(ps1_2, c1_2, 2));
}
}
@Test
public void getRelatedEdit() throws Exception {
+ // 1,1---2,1---3,1
+ // \---2,E---/
+
RevCommit c1_1 = commitBuilder()
.add("a.txt", "1")
.message("subject: 1")
.create();
- String id1 = getChangeId(c1_1);
RevCommit c2_1 = commitBuilder()
.add("b.txt", "2")
.message("subject: 2")
.create();
- String id2 = getChangeId(c2_1);
RevCommit c3_1 = commitBuilder()
.add("c.txt", "3")
.message("subject: 3")
.create();
- String id3 = getChangeId(c3_1);
pushHead(testRepo, "refs/for/master", false);
Change ch2 = getChange(c2_1).change();
@@ -212,37 +465,38 @@
for (PatchSet.Id ps : ImmutableList.of(ps1_1, ps2_1, ps3_1)) {
assertRelated(ps,
- changeAndCommit(id3, c3_1, 1, 1),
- changeAndCommit(id2, c2_1, 1, 1),
- changeAndCommit(id1, c1_1, 1, 1));
+ changeAndCommit(ps3_1, c3_1, 1),
+ changeAndCommit(ps2_1, c2_1, 1),
+ changeAndCommit(ps1_1, c1_1, 1));
}
assertRelated(ps2_edit,
- changeAndCommit(id3, c3_1, 1, 1),
- changeAndCommit(id2, editRev, 0, 1),
- changeAndCommit(id1, c1_1, 1, 1));
+ changeAndCommit(ps3_1, c3_1, 1),
+ changeAndCommit(new PatchSet.Id(ch2.getId(), 0), editRev, 1),
+ changeAndCommit(ps1_1, c1_1, 1));
}
@Test
public void pushNewPatchSetWhenParentHasNullGroup() throws Exception {
+ // 1,1---2,1
+ // \---2,2
+
RevCommit c1_1 = commitBuilder()
.add("a.txt", "1")
.message("subject: 1")
.create();
- String id1 = getChangeId(c1_1);
RevCommit c2_1 = commitBuilder()
.add("b.txt", "2")
.message("subject: 2")
.create();
- String id2 = getChangeId(c2_1);
pushHead(testRepo, "refs/for/master", false);
PatchSet.Id psId1_1 = getPatchSetId(c1_1);
PatchSet.Id psId2_1 = getPatchSetId(c2_1);
for (PatchSet.Id psId : ImmutableList.of(psId1_1, psId2_1)) {
assertRelated(psId,
- changeAndCommit(id2, c2_1, 1, 1),
- changeAndCommit(id1, c1_1, 1, 1));
+ changeAndCommit(psId2_1, c2_1, 1),
+ changeAndCommit(psId1_1, c1_1, 1));
}
// Pretend PS1,1 was pushed before the groups field was added.
@@ -266,8 +520,8 @@
// Push updated the group for PS1,1, so it shows up in related changes even
// though a new patch set was not pushed.
assertRelated(psId2_2,
- changeAndCommit(id2, c2_2, 2, 2),
- changeAndCommit(id1, c1_1, 1, 1));
+ changeAndCommit(psId2_2, c2_2, 2),
+ changeAndCommit(psId1_1, c1_1, 1));
}
private List<ChangeAndCommit> getRelated(PatchSet.Id ps) throws IOException {
@@ -282,8 +536,9 @@
RelatedInfo.class).changes;
}
- private String getChangeId(RevCommit c) throws Exception {
- return GitUtil.getChangeId(testRepo, c).get();
+ private RevCommit parseBody(RevCommit c) throws IOException {
+ testRepo.getRevWalk().parseBody(c);
+ return c;
}
private PatchSet.Id getPatchSetId(ObjectId c) throws OrmException {
@@ -298,13 +553,13 @@
return Iterables.getOnlyElement(queryProvider.get().byCommit(c));
}
- private static ChangeAndCommit changeAndCommit(String changeId,
- ObjectId commitId, int revisionNum, int currentRevisionNum) {
+ private static ChangeAndCommit changeAndCommit(
+ PatchSet.Id psId, ObjectId commitId, int currentRevisionNum) {
ChangeAndCommit result = new ChangeAndCommit();
- result.changeId = changeId;
+ result._changeNumber = psId.getParentKey().get();
result.commit = new CommitInfo();
result.commit.commit = commitId.name();
- result._revisionNumber = revisionNum;
+ result._revisionNumber = psId.get();
result._currentRevisionNumber = currentRevisionNum;
result.status = "NEW";
return result;
@@ -313,18 +568,18 @@
private void assertRelated(PatchSet.Id psId, ChangeAndCommit... expected)
throws Exception {
List<ChangeAndCommit> actual = getRelated(psId);
- assertThat(actual).hasSize(expected.length);
+ assertThat(actual).named("related to " + psId).hasSize(expected.length);
for (int i = 0; i < actual.size(); i++) {
String name = "index " + i + " related to " + psId;
ChangeAndCommit a = actual.get(i);
ChangeAndCommit e = expected[i];
- assertThat(a.changeId).named("Change-Id of " + name)
- .isEqualTo(e.changeId);
- assertThat(a.commit.commit).named("commit of " + name)
- .isEqualTo(e.commit.commit);
- // Don't bother checking _changeNumber; assume changeId is sufficient.
+ assertThat(a._changeNumber).named("change ID of " + name)
+ .isEqualTo(e._changeNumber);
+ // Don't bother checking changeId; assume _changeNumber is sufficient.
assertThat(a._revisionNumber).named("revision of " + name)
.isEqualTo(e._revisionNumber);
+ assertThat(a.commit.commit).named("commit of " + name)
+ .isEqualTo(e.commit.commit);
assertThat(a._currentRevisionNumber).named("current revision of " + name)
.isEqualTo(e._currentRevisionNumber);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 6ac34ce..5a8a44c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -17,14 +17,12 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.ProjectConfig;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
@@ -33,8 +31,6 @@
import org.junit.Test;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
public class SubmittedTogetherIT extends AbstractDaemonTest {
@@ -145,11 +141,8 @@
}
@Test
+ @TestProjectInput(submitType = SubmitType.CHERRY_PICK)
public void testCherryPickWithoutAncestors() throws Exception {
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
- cfg.getProject().setSubmitType(SubmitType.CHERRY_PICK);
- saveProjectConfig(project, cfg);
-
// Create two commits and push.
RevCommit c1_1 = commitBuilder()
.add("a.txt", "1")
@@ -167,18 +160,35 @@
assertSubmittedTogether(id2);
}
- private void assertSubmittedTogether(String chId, String... expected)
- throws Exception {
- List<ChangeInfo> actual = gApi.changes().id(chId).submittedTogether();
- assertThat(actual).hasSize(expected.length);
- assertThat(Arrays.asList(expected))
- .containsExactlyElementsIn(
- Iterables.transform(actual, new Function<ChangeInfo, String>() {
- @Override
- public String apply(ChangeInfo input) {
- return input.changeId;
- }
- })).inOrder();
+ @Test
+ public void testSubmissionIdSavedOnMergeInOneProject() throws Exception {
+ // Create two commits and push.
+ RevCommit c1_1 = commitBuilder()
+ .add("a.txt", "1")
+ .message("subject: 1")
+ .create();
+ String id1 = getChangeId(c1_1);
+ RevCommit c2_1 = commitBuilder()
+ .add("b.txt", "2")
+ .message("subject: 2")
+ .create();
+ String id2 = getChangeId(c2_1);
+ pushHead(testRepo, "refs/for/master", false);
+
+ assertSubmittedTogether(id1);
+ assertSubmittedTogether(id2, id2, id1);
+
+ approve(id1);
+ approve(id2);
+ submit(id2);
+ assertMerged(id1);
+ assertMerged(id2);
+
+ // Prior to submission this was empty, but the post-merge value is what was
+ // actually submitted.
+ assertSubmittedTogether(id1, id2, id1);
+
+ assertSubmittedTogether(id2, id2, id1);
}
private RevCommit getRemoteHead() throws IOException {
@@ -191,4 +201,19 @@
private String getChangeId(RevCommit c) throws Exception {
return GitUtil.getChangeId(testRepo, c).get();
}
-}
\ No newline at end of file
+
+ private void submit(String changeId) throws Exception {
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit();
+ }
+
+ private void assertMerged(String changeId) throws Exception {
+ assertThat(gApi
+ .changes()
+ .id(changeId)
+ .get()
+ .status).isEqualTo(ChangeStatus.MERGED);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK
index 2ec6f7a..ad7d597 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK
@@ -3,11 +3,13 @@
FLAKY_TEST_CASES=['ProjectWatchIT.java']
acceptance_tests(
+ group = 'server-project',
srcs = glob(['*IT.java'], excludes=FLAKY_TEST_CASES),
labels = ['server'],
)
acceptance_tests(
+ group = 'server-project-flaky',
srcs = FLAKY_TEST_CASES,
labels = ['server', 'flaky'],
)
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 da83157..357f268 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
@@ -24,6 +24,7 @@
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelInfo;
@@ -43,6 +44,10 @@
value(0, "No score"),
value(-1, "Negative"));
+ private final LabelType P = category("CustomLabel2",
+ value(1, "Positive"),
+ value(0, "No score"));
+
@Before
public void setUp() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
@@ -50,6 +55,8 @@
SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
Util.allow(cfg, Permission.forLabel(label.getName()), -1, 1, anonymousUsers,
"refs/heads/*");
+ Util.allow(cfg, Permission.forLabel(P.getName()), 0, 1, anonymousUsers,
+ "refs/heads/*");
saveProjectConfig(project, cfg);
}
@@ -107,6 +114,26 @@
}
@Test
+ public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception {
+ P.setFunctionName("AnyWithBlock");
+ saveLabelConfig();
+ PushOneCommit.Result r = createChange();
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = user.email;
+ gApi.changes()
+ .id(r.getChangeId())
+ .addReviewer(in);
+
+ revision(r).review(new ReviewInput().label(P.getName(), 0));
+ ChangeInfo c = get(r.getChangeId());
+ LabelInfo q = c.labels.get(P.getName());
+ assertThat(q.all).hasSize(2);
+ assertThat(q.disliked).isNull();
+ assertThat(q.rejected).isNull();
+ assertThat(q.blocking).isNull();
+ }
+
+ @Test
public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception {
saveLabelConfig();
PushOneCommit.Result r = createChange();
@@ -122,6 +149,7 @@
private void saveLabelConfig() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
cfg.getLabelSections().put(label.getName(), label);
+ cfg.getLabelSections().put(P.getName(), P);
saveProjectConfig(project, 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 5d31c77..363a7e4 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
@@ -26,22 +26,14 @@
import com.google.gerrit.extensions.common.LabelInfo;
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.Util;
-import com.google.gerrit.testutil.ConfigSuite;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class LabelTypeIT extends AbstractDaemonTest {
- @ConfigSuite.Config
- public static Config noteDbEnabled() {
- return NotesMigration.allEnabledConfig();
- }
-
private LabelType codeReview;
@Before
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 d067b34..0729b68 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
@@ -1,6 +1,7 @@
include_defs('//gerrit-acceptance-tests/tests.defs')
acceptance_tests(
+ group = 'ssh',
srcs = glob(['*IT.java']),
deps = ['//lib/commons:compress'],
labels = ['ssh'],
diff --git a/gerrit-acceptance-tests/tests.defs b/gerrit-acceptance-tests/tests.defs
index 7bd2430..940b1cc 100644
--- a/gerrit-acceptance-tests/tests.defs
+++ b/gerrit-acceptance-tests/tests.defs
@@ -5,6 +5,7 @@
]
def acceptance_tests(
+ group,
srcs,
deps = [],
labels = [],
@@ -16,19 +17,18 @@
if path.exists('/dev/urandom'):
vm_args = vm_args + ['-Djava.security.egd=file:/dev/./urandom']
- for j in srcs:
- java_test(
- name = j[:-len('.java')],
- srcs = [j],
- deps = ['//gerrit-acceptance-tests:lib'] + deps,
- source_under_test = [
- '//gerrit-httpd:httpd',
- '//gerrit-sshd:sshd',
- '//gerrit-server:server',
- ] + source_under_test,
- labels = labels + [
- 'acceptance',
- 'slow',
- ],
- vm_args = vm_args,
- )
+ java_test(
+ name = group,
+ srcs = srcs,
+ deps = ['//gerrit-acceptance-tests:lib'] + deps,
+ source_under_test = [
+ '//gerrit-httpd:httpd',
+ '//gerrit-sshd:sshd',
+ '//gerrit-server:server',
+ ] + source_under_test,
+ labels = labels + [
+ 'acceptance',
+ 'slow',
+ ],
+ vm_args = vm_args,
+ )
diff --git a/gerrit-antlr/BUCK b/gerrit-antlr/BUCK
index 03c3c1e..e858a72 100644
--- a/gerrit-antlr/BUCK
+++ b/gerrit-antlr/BUCK
@@ -25,7 +25,6 @@
genrule(
name = 'query_link',
cmd = 'ln -s $(location :lib) $OUT',
- deps = [':lib'],
out = 'query_parser.jar',
)
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/FormatUtil.java
similarity index 62%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-common/src/main/java/com/google/gerrit/common/FormatUtil.java
index cd01186..0f6b37a 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/FormatUtil.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.common;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+public class FormatUtil {
+ public static String elide(String s, int max) {
+ if (s == null || s.length() <= max) {
+ return s;
+ }
+ int len = (max - 3) / 2;
+ return s.substring(0, len) + "..." + s.substring(s.length() - len);
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
index 9a30696..9bc2ea5 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
@@ -26,6 +26,7 @@
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Set;
public final class IoUtil {
@@ -86,8 +87,8 @@
}
}
- public static void loadJARs(Path... jars) {
- loadJARs(Arrays.asList(jars));
+ public static void loadJARs(Path jar) {
+ loadJARs(Collections.singleton(jar));
}
private static UnsupportedOperationException noAddURL(String m, Throwable why) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index ff2121d..28e0d24 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -25,6 +25,8 @@
public class PageLinks {
public static final String SETTINGS = "/settings/";
public static final String SETTINGS_PREFERENCES = "/settings/preferences";
+ public static final String SETTINGS_DIFF_PREFERENCES = "/settings/diff-preferences";
+ public static final String SETTINGS_EDIT_PREFERENCES = "/settings/edit-preferences";
public static final String SETTINGS_SSHKEYS = "/settings/ssh-keys";
public static final String SETTINGS_GPGKEYS = "/settings/gpg-keys";
public static final String SETTINGS_HTTP_PASSWORD = "/settings/http-password";
@@ -89,6 +91,10 @@
return "/admin/projects/" + p.get() + ",branches";
}
+ public static String toProjectTags(Project.NameKey p) {
+ return "/admin/projects/" + p.get() + ",tags";
+ }
+
public static String toAccountQuery(String fullname, Status status) {
return toChangeQuery(op("owner", fullname) + " " + status(status));
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java
index 9f4da74..39f5cb0 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java
@@ -19,7 +19,6 @@
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
-import java.util.Collections;
import java.util.List;
public class PatchSetDetail {
@@ -27,7 +26,6 @@
protected PatchSetInfo info;
protected List<Patch> patches;
protected Project.NameKey project;
- protected List<UiCommandDetail> commands;
public PatchSetDetail() {
}
@@ -63,15 +61,4 @@
public void setProject(final Project.NameKey p) {
project = p;
}
-
- public List<UiCommandDetail> getCommands() {
- if (commands != null) {
- return commands;
- }
- return Collections.emptyList();
- }
-
- public void setCommands(List<UiCommandDetail> cmds) {
- commands = cmds.isEmpty() ? null : cmds;
- }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java
index bf84ccb0..c007161 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/HashtagsInput.java
@@ -22,4 +22,11 @@
@DefaultInput
public Set<String> add;
public Set<String> remove;
+
+ public HashtagsInput(){
+ }
+
+ public HashtagsInput(Set<String> add) {
+ this.add = add;
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java
index ab09e5f..b909f31 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java
@@ -63,6 +63,7 @@
private int limit;
private int start;
private String substring;
+ private String suggest;
public List<GroupInfo> get() throws RestApiException {
Map<String, GroupInfo> map = getAsMap();
@@ -128,6 +129,11 @@
return this;
}
+ public ListRequest withSuggest(String suggest) {
+ this.suggest = suggest;
+ return this;
+ }
+
public EnumSet<ListGroupsOption> getOptions() {
return options;
}
@@ -163,5 +169,9 @@
public String getSubstring() {
return substring;
}
+
+ public String getSuggest() {
+ return suggest;
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInfo.java
index b973806..77513a2 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInfo.java
@@ -20,9 +20,7 @@
import java.util.List;
import java.util.Map;
-public class BranchInfo {
- public String ref;
- public String revision;
+public class BranchInfo extends RefInfo {
public Boolean canDelete;
public Map<String, ActionInfo> actions;
public List<WebLinkInfo> webLinks;
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 102b1ce..e3eb4be 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
@@ -28,32 +28,33 @@
String description() throws RestApiException;
void description(PutDescriptionInput in) throws RestApiException;
- ListBranchesRequest branches();
+ ListRefsRequest<BranchInfo> branches();
+ ListRefsRequest<TagInfo> tags();
- public abstract class ListBranchesRequest {
- private int limit;
- private int start;
- private String substring;
- private String regex;
+ public abstract class ListRefsRequest<T extends RefInfo> {
+ protected int limit;
+ protected int start;
+ protected String substring;
+ protected String regex;
- public abstract List<BranchInfo> get() throws RestApiException;
+ public abstract List<T> get() throws RestApiException;
- public ListBranchesRequest withLimit(int limit) {
+ public ListRefsRequest<T> withLimit(int limit) {
this.limit = limit;
return this;
}
- public ListBranchesRequest withStart(int start) {
+ public ListRefsRequest<T> withStart(int start) {
this.start = start;
return this;
}
- public ListBranchesRequest withSubstring(String substring) {
+ public ListRefsRequest<T> withSubstring(String substring) {
this.substring = substring;
return this;
}
- public ListBranchesRequest withRegex(String regex) {
+ public ListRefsRequest<T> withRegex(String regex) {
this.regex = regex;
return this;
}
@@ -73,7 +74,6 @@
public String getRegex() {
return regex;
}
-
}
List<ProjectInfo> children() throws RestApiException;
@@ -96,6 +96,15 @@
BranchApi branch(String ref) throws RestApiException;
/**
+ * Look up a tag by refname.
+ * <p>
+ * @param ref tag name, with or without "refs/tags/" prefix.
+ * @throws RestApiException if a problem occurred reading the project.
+ * @return API for accessing the tag.
+ */
+ TagApi tag(String ref) throws RestApiException;
+
+ /**
* A default implementation which allows source compatibility
* when adding new methods to the interface.
**/
@@ -127,7 +136,12 @@
}
@Override
- public ListBranchesRequest branches() {
+ public ListRefsRequest<BranchInfo> branches() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public ListRefsRequest<TagInfo> tags() {
throw new NotImplementedException();
}
@@ -150,5 +164,10 @@
public BranchApi branch(String ref) throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public TagApi tag(String ref) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/RefInfo.java
similarity index 65%
rename from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/RefInfo.java
index cd01186..1844a76 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/RefInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.extensions.api.projects;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+public class RefInfo {
+ public String ref;
+ public String revision;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagApi.java
new file mode 100644
index 0000000..6cc1ba4
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagApi.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface TagApi {
+ TagInfo get() throws RestApiException;
+
+ /**
+ * A default implementation which allows source compatibility
+ * when adding new methods to the interface.
+ **/
+ public class NotImplemented implements TagApi {
+ @Override
+ public TagInfo get() throws RestApiException {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java
similarity index 87%
rename from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java
index 3e3d8db..b531d67 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/TagInfo.java
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.extensions.common;
+package com.google.gerrit.extensions.api.projects;
-public class TagInfo {
- public String ref;
- public String revision;
+import com.google.gerrit.extensions.common.GitPerson;
+
+public class TagInfo extends RefInfo {
public String object;
public String message;
public GitPerson tagger;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
new file mode 100644
index 0000000..3e45523
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
@@ -0,0 +1,48 @@
+// 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.client;
+
+/* This class is stored in Git config file. */
+public class EditPreferencesInfo {
+ public Integer tabSize;
+ public Integer lineLength;
+ public Integer cursorBlinkRate;
+ public Boolean hideTopMenu;
+ public Boolean showTabs;
+ public Boolean showWhitespaceErrors;
+ public Boolean syntaxHighlighting;
+ public Boolean hideLineNumbers;
+ public Boolean matchBrackets;
+ public Boolean autoCloseBrackets;
+ public Theme theme;
+ public KeyMapType keyMapType;
+
+ public static EditPreferencesInfo defaults() {
+ EditPreferencesInfo i = new EditPreferencesInfo();
+ i.tabSize = 8;
+ i.lineLength = 100;
+ i.cursorBlinkRate = 0;
+ i.hideTopMenu = false;
+ i.showTabs = true;
+ i.showWhitespaceErrors = false;
+ i.syntaxHighlighting = true;
+ i.hideLineNumbers = false;
+ i.matchBrackets = true;
+ i.autoCloseBrackets = false;
+ i.theme = Theme.DEFAULT;
+ i.keyMapType = KeyMapType.DEFAULT;
+ return i;
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java
similarity index 65%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java
index cd01186..261168d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.extensions.client;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
-}
+public enum KeyMapType {
+ DEFAULT,
+ EMACS,
+ VIM
+}
\ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
index c1edd6a..7e99d1a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -59,7 +59,10 @@
CHANGE_ACTIONS(16),
/** Include a copy of commit messages including review footers. */
- COMMIT_FOOTERS(17);
+ COMMIT_FOOTERS(17),
+
+ /** Include push certificate information along with any patch sets. */
+ PUSH_CERTIFICATES(18);
private final int value;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java
index 58f5494..00d0c18 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java
@@ -20,4 +20,5 @@
public String oldPath;
public Integer linesInserted;
public Integer linesDeleted;
+ public long sizeDelta;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GpgKeyInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GpgKeyInfo.java
index 443ef07..33adbea 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GpgKeyInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GpgKeyInfo.java
@@ -17,8 +17,33 @@
import java.util.List;
public class GpgKeyInfo {
+ /**
+ * Status of checking an object like a key or signature.
+ * <p>
+ * Order of values in this enum is significant: OK is "better" than BAD, etc.
+ */
+ public enum Status {
+ /** Something is wrong with this key. */
+ BAD,
+
+ /**
+ * Inspecting only this key found no problems, but the system does not fully
+ * trust the key's origin.
+ */
+ OK,
+
+ /**
+ * This key is valid, and the system knows enough about the key and its
+ * origin to trust it.
+ */
+ TRUSTED;
+ }
+
public String id;
public String fingerprint;
public List<String> userIds;
public String key;
+
+ public Status status;
+ public List<String> problems;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PushCertificateInfo.java
similarity index 65%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PushCertificateInfo.java
index cd01186..9eed808 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PushCertificateInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.extensions.common;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+public class PushCertificateInfo {
+ public String certificate;
+ public GpgKeyInfo key;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
index bc0fa6d..025c623 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -29,4 +29,5 @@
public Map<String, FileInfo> files;
public Map<String, ActionInfo> actions;
public String commitWithFooters;
+ public PushCertificateInfo pushCertificate;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
index 92fefed..a21c2d4 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.restapi;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
@@ -23,7 +25,6 @@
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
-import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
/**
@@ -58,7 +59,7 @@
}
private String contentType = OCTET_STREAM;
- private String characterEncoding;
+ private Charset characterEncoding;
private long contentLength = -1;
private boolean gzip = true;
private boolean base64 = false;
@@ -66,9 +67,9 @@
/** @return the MIME type of the result, for HTTP clients. */
public String getContentType() {
- String enc = getCharacterEncoding();
+ Charset enc = getCharacterEncoding();
if (enc != null) {
- return contentType + "; charset=" + enc;
+ return contentType + "; charset=" + enc.name();
}
return contentType;
}
@@ -80,12 +81,18 @@
}
/** Get the character encoding; null if not known. */
- public String getCharacterEncoding() {
+ public Charset getCharacterEncoding() {
return characterEncoding;
}
/** Set the character set used to encode text data and return {@code this}. */
+ @Deprecated
public BinaryResult setCharacterEncoding(String encoding) {
+ return setCharacterEncoding(Charset.forName(encoding));
+ }
+
+ /** Set the character set used to encode text data and return {@code this}. */
+ public BinaryResult setCharacterEncoding(Charset encoding) {
characterEncoding = encoding;
return this;
}
@@ -183,11 +190,11 @@
getContentType());
}
- private static String decode(byte[] data, String enc) {
+ private static String decode(byte[] data, Charset enc) {
try {
Charset cs = enc != null
- ? Charset.forName(enc)
- : StandardCharsets.UTF_8;
+ ? enc
+ : UTF_8;
return cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
@@ -226,9 +233,9 @@
private final String str;
StringResult(String str) {
- super(str.getBytes(StandardCharsets.UTF_8));
+ super(str.getBytes(UTF_8));
setContentType("text/plain");
- setCharacterEncoding("UTF-8");
+ setCharacterEncoding(UTF_8.name());
this.str = str;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Url.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Url.java
index 8f4d909..debfa20 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Url.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Url.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.restapi;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
@@ -40,7 +42,7 @@
public static String encode(String component) {
if (component != null) {
try {
- return URLEncoder.encode(component, "UTF-8");
+ return URLEncoder.encode(component, UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("JVM must support UTF-8", e);
}
@@ -52,7 +54,7 @@
public static String decode(String str) {
if (str != null) {
try {
- return URLDecoder.decode(str, "UTF-8");
+ return URLDecoder.decode(str, UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("JVM must support UTF-8", e);
}
diff --git a/gerrit-extension-api/src/test/java/com/google/gerrit/extensions/registration/DynamicSetTest.java b/gerrit-extension-api/src/test/java/com/google/gerrit/extensions/registration/DynamicSetTest.java
index dc71b12..299b9b0 100644
--- a/gerrit-extension-api/src/test/java/com/google/gerrit/extensions/registration/DynamicSetTest.java
+++ b/gerrit-extension-api/src/test/java/com/google/gerrit/extensions/registration/DynamicSetTest.java
@@ -16,7 +16,6 @@
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.inject.Key;
import com.google.inject.util.Providers;
diff --git a/gerrit-gpg/BUCK b/gerrit-gpg/BUCK
index 592a5190..2b258a9 100644
--- a/gerrit-gpg/BUCK
+++ b/gerrit-gpg/BUCK
@@ -1,39 +1,36 @@
+DEPS = [
+ '//gerrit-common:server',
+ '//gerrit-extension-api:api',
+ '//gerrit-reviewdb:server',
+ '//gerrit-server:server',
+ '//lib:guava',
+ '//lib:gwtorm',
+ '//lib/guice:guice',
+ '//lib/guice:guice-assistedinject',
+ '//lib/guice:guice-servlet',
+ '//lib/jgit:jgit',
+ '//lib/log:api',
+]
+
java_library(
name = 'gpg',
srcs = glob(['src/main/java/**/*.java']),
- deps = [
- '//gerrit-common:server',
- '//gerrit-extension-api:api',
- '//gerrit-reviewdb:server',
- '//gerrit-server:server',
- '//lib:guava',
- '//lib:gwtorm',
- '//lib/guice:guice',
- '//lib/guice:guice-assistedinject',
- '//lib/guice:guice-servlet',
- '//lib/jgit:jgit',
- '//lib/log:api',
- ],
- provided_deps = [
- '//lib/bouncycastle:bcprov',
+ provided_deps = DEPS + [
'//lib/bouncycastle:bcpg',
+ '//lib/bouncycastle:bcprov',
],
visibility = ['PUBLIC'],
)
-TESTUTIL_SRCS = [
- 'src/test/java/com/google/gerrit/gpg/testutil/TestKey.java',
-]
+TESTUTIL_SRCS = glob(['src/test/**/testutil/**/*.java'])
java_library(
name = 'testutil',
srcs = TESTUTIL_SRCS,
- deps = [
+ deps = DEPS + [
':gpg',
- '//lib:guava',
'//lib/bouncycastle:bcpg',
'//lib/bouncycastle:bcprov',
- '//lib/jgit:jgit',
],
visibility = ['PUBLIC'],
)
@@ -44,20 +41,15 @@
['src/test/java/**/*.java'],
excludes = TESTUTIL_SRCS,
),
- deps = [
+ deps = DEPS + [
':gpg',
':testutil',
- '//gerrit-extension-api:api',
- '//gerrit-reviewdb:server',
- '//gerrit-server:server',
+ '//gerrit-cache-h2:cache-h2',
+ '//gerrit-lucene:lucene',
'//gerrit-server:testutil',
- '//lib:guava',
- '//lib:gwtorm',
'//lib:truth',
'//lib/bouncycastle:bcpg',
'//lib/bouncycastle:bcprov',
- '//lib/guice:guice',
- '//lib/jgit:jgit',
'//lib/jgit:junit',
],
source_under_test = [':gpg'],
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/CheckResult.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/CheckResult.java
index c41ecbe..74184bd 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/CheckResult.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/CheckResult.java
@@ -14,6 +14,8 @@
package com.google.gerrit.gpg;
+import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -21,22 +23,60 @@
/** Result of checking an object like a key or signature. */
public class CheckResult {
+ static CheckResult ok(String... problems) {
+ return create(Status.OK, problems);
+ }
+
+ static CheckResult bad(String... problems) {
+ return create(Status.BAD, problems);
+ }
+
+ static CheckResult trusted() {
+ return new CheckResult(Status.TRUSTED, Collections.<String> emptyList());
+ }
+
+ static CheckResult create(Status status, String... problems) {
+ List<String> problemList = problems.length > 0
+ ? Collections.unmodifiableList(Arrays.asList(problems))
+ : Collections.<String> emptyList();
+ return new CheckResult(status, problemList);
+ }
+
+ static CheckResult create(Status status, List<String> problems) {
+ return new CheckResult(status,
+ Collections.unmodifiableList(new ArrayList<>(problems)));
+ }
+
+ static CheckResult create(List<String> problems) {
+ return new CheckResult(
+ problems.isEmpty() ? Status.OK : Status.BAD,
+ Collections.unmodifiableList(problems));
+ }
+
+ private final Status status;
private final List<String> problems;
- CheckResult(String... problems) {
- this(Arrays.asList(problems));
+ private CheckResult(Status status, List<String> problems) {
+ if (status == null) {
+ throw new IllegalArgumentException("status must not be null");
+ }
+ this.status = status;
+ this.problems = problems;
}
- CheckResult(List<String> problems) {
- this.problems = Collections.unmodifiableList(new ArrayList<>(problems));
- }
-
- /**
- * @return whether the result is entirely ok, i.e. has passed any verification
- * or validation checks.
- */
+ /** @return whether the result has status {@link Status#OK} or better. */
public boolean isOk() {
- return problems.isEmpty();
+ return status.compareTo(Status.OK) >= 0;
+ }
+
+ /** @return whether the result has status {@link Status#TRUSTED} or better. */
+ public boolean isTrusted() {
+ return status.compareTo(Status.TRUSTED) >= 0;
+ }
+
+ /** @return the status enum value associated with the object. */
+ public Status getStatus() {
+ return status;
}
/** @return any problems encountered during checking. */
@@ -47,12 +87,9 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getSimpleName())
- .append('[');
+ .append('[').append(status);
for (int i = 0; i < problems.size(); i++) {
- if (i > 0) {
- sb.append(", ");
- }
- sb.append(problems.get(i));
+ sb.append(i == 0 ? ": " : ", ").append(problems.get(i));
}
return sb.append(']').toString();
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/Fingerprint.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/Fingerprint.java
index 6fd8bac..fa78f01 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/Fingerprint.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/Fingerprint.java
@@ -19,6 +19,9 @@
import org.eclipse.jgit.util.NB;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
public class Fingerprint {
private final byte[] fp;
@@ -33,6 +36,18 @@
NB.decodeUInt16(fp, 16), NB.decodeUInt16(fp, 18));
}
+ public static long getId(byte[] fp) {
+ return NB.decodeInt64(fp, 12);
+ }
+
+ public static Map<Long, Fingerprint> byId(Iterable<Fingerprint> fps) {
+ Map<Long, Fingerprint> result = new HashMap<>();
+ for (Fingerprint fp : fps) {
+ result.put(fp.getId(), fp);
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
private static byte[] checkLength(byte[] fp) {
checkArgument(fp.length == 20,
"fingerprint must be 20 bytes, got %s", fp.length);
@@ -54,6 +69,23 @@
this.fp = checkLength(fp);
}
+ /**
+ * Wrap a portion of a fingerprint byte array.
+ * <p>
+ * Unlike {@link #Fingerprint(byte[])}, creates a new copy of the byte array.
+ *
+ * @param buf byte array to wrap; must have at least {@code off + 20} bytes.
+ * @param off offset in buf.
+ */
+ public Fingerprint(byte[] buf, int off) {
+ int expected = 20 + off;
+ checkArgument(buf.length >= expected,
+ "fingerprint buffer must have at least %s bytes, got %s",
+ expected, buf.length);
+ this.fp = new byte[20];
+ System.arraycopy(buf, off, fp, 0, 20);
+ }
+
public byte[] get() {
return fp;
}
@@ -79,6 +111,6 @@
}
public long getId() {
- return NB.decodeInt64(fp, 12);
+ return getId(fp);
}
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index 52cddf2..c3c886f 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -17,13 +17,20 @@
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GPGKEY;
+import com.google.common.base.CharMatcher;
import com.google.common.base.MoreObjects;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
+import com.google.common.io.BaseEncoding;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -32,6 +39,7 @@
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.transport.PushCertificateIdent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +47,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -48,53 +56,148 @@
* For Gerrit, keys must contain a self-signed user ID certification matching a
* trusted external ID in the database, or an email address thereof.
*/
-@Singleton
public class GerritPublicKeyChecker extends PublicKeyChecker {
private static final Logger log =
LoggerFactory.getLogger(GerritPublicKeyChecker.class);
- private final String webUrl;
- private final Provider<IdentifiedUser> userProvider;
+ @Singleton
+ public static class Factory {
+ private final Provider<ReviewDb> db;
+ private final String webUrl;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final int maxTrustDepth;
+ private final ImmutableMap<Long, Fingerprint> trusted;
- @Inject
- GerritPublicKeyChecker(
- @CanonicalWebUrl String webUrl,
- Provider<IdentifiedUser> userProvider) {
- this.webUrl = webUrl;
- this.userProvider = userProvider;
+ @Inject
+ Factory(@GerritServerConfig Config cfg,
+ Provider<ReviewDb> db,
+ IdentifiedUser.GenericFactory userFactory,
+ @CanonicalWebUrl String webUrl) {
+ this.db = db;
+ this.webUrl = webUrl;
+ this.userFactory = userFactory;
+ this.maxTrustDepth = cfg.getInt("receive", null, "maxTrustDepth", 0);
+
+ String[] strs = cfg.getStringList("receive", null, "trustedKey");
+ if (strs.length != 0) {
+ Map<Long, Fingerprint> fps =
+ Maps.newHashMapWithExpectedSize(strs.length);
+ for (String str : strs) {
+ str = CharMatcher.WHITESPACE.removeFrom(str).toUpperCase();
+ Fingerprint fp = new Fingerprint(BaseEncoding.base16().decode(str));
+ fps.put(fp.getId(), fp);
+ }
+ trusted = ImmutableMap.copyOf(fps);
+ } else {
+ trusted = null;
+ }
+ }
+
+ public GerritPublicKeyChecker create() {
+ return new GerritPublicKeyChecker(this);
+ }
+
+ public GerritPublicKeyChecker create(IdentifiedUser expectedUser,
+ PublicKeyStore store) {
+ GerritPublicKeyChecker checker = new GerritPublicKeyChecker(this);
+ checker.setExpectedUser(expectedUser);
+ checker.setStore(store);
+ return checker;
+ }
+ }
+
+ private final Provider<ReviewDb> db;
+ private final String webUrl;
+ private final IdentifiedUser.GenericFactory userFactory;
+
+ private IdentifiedUser expectedUser;
+
+ private GerritPublicKeyChecker(Factory factory) {
+ this.db = factory.db;
+ this.webUrl = factory.webUrl;
+ this.userFactory = factory.userFactory;
+ if (factory.trusted != null) {
+ enableTrust(factory.maxTrustDepth, factory.trusted);
+ }
+ }
+
+ /**
+ * Set the expected user for this checker.
+ * <p>
+ * If set, the top-level key passed to {@link #check(PGPPublicKey)} must
+ * belong to the given user. (Other keys checked in the course of verifying
+ * the web of trust are checked against the set of identities in the database
+ * belonging to the same user as the key.)
+ */
+ public GerritPublicKeyChecker setExpectedUser(IdentifiedUser expectedUser) {
+ this.expectedUser = expectedUser;
+ return this;
}
@Override
- public void checkCustom(PGPPublicKey key, long expectedKeyId,
- List<String> problems) {
+ public CheckResult checkCustom(PGPPublicKey key, int depth) {
try {
- Set<String> allowedUserIds = getAllowedUserIds();
- if (allowedUserIds.isEmpty()) {
- problems.add("No identities found for user; check "
- + webUrl + "#" + PageLinks.SETTINGS_WEBIDENT);
- return;
+ if (depth == 0 && expectedUser != null) {
+ return checkIdsForExpectedUser(key);
+ } else {
+ return checkIdsForArbitraryUser(key);
}
+ } catch (PGPException | OrmException e) {
+ String msg = "Error checking user IDs for key";
+ log.warn(msg + " " + keyIdToString(key.getKeyID()), e);
+ return CheckResult.bad(msg);
+ }
+ }
- @SuppressWarnings("unchecked")
- Iterator<String> userIds = key.getUserIDs();
- while (userIds.hasNext()) {
- String userId = userIds.next();
- if (isAllowed(userId, allowedUserIds)) {
- Iterator<PGPSignature> sigs = getSignaturesForId(key, userId);
- while (sigs.hasNext()) {
- if (isValidCertification(key, sigs.next(), userId)) {
- return;
- }
+ private CheckResult checkIdsForExpectedUser(PGPPublicKey key)
+ throws PGPException {
+ Set<String> allowedUserIds = getAllowedUserIds(expectedUser);
+ if (allowedUserIds.isEmpty()) {
+ return CheckResult.bad("No identities found for user; check "
+ + webUrl + "#" + PageLinks.SETTINGS_WEBIDENT);
+ }
+ if (hasAllowedUserId(key, allowedUserIds)) {
+ return CheckResult.trusted();
+ }
+ return CheckResult.bad(missingUserIds(allowedUserIds));
+ }
+
+ private CheckResult checkIdsForArbitraryUser(PGPPublicKey key)
+ throws PGPException, OrmException {
+ AccountExternalId extId = db.get().accountExternalIds().get(
+ toExtIdKey(key));
+ if (extId == null) {
+ return CheckResult.bad("Key is not associated with any users");
+ }
+ IdentifiedUser user = userFactory.create(db, extId.getAccountId());
+ Set<String> allowedUserIds = getAllowedUserIds(user);
+ if (allowedUserIds.isEmpty()) {
+ return CheckResult.bad("No identities found for user");
+ }
+ if (hasAllowedUserId(key, allowedUserIds)) {
+ return CheckResult.trusted();
+ }
+ return CheckResult.bad(
+ "Key does not contain any valid certifications for user's identities");
+ }
+
+ private boolean hasAllowedUserId(PGPPublicKey key, Set<String> allowedUserIds)
+ throws PGPException {
+ @SuppressWarnings("unchecked")
+ Iterator<String> userIds = key.getUserIDs();
+ while (userIds.hasNext()) {
+ String userId = userIds.next();
+ if (isAllowed(userId, allowedUserIds)) {
+ Iterator<PGPSignature> sigs = getSignaturesForId(key, userId);
+ while (sigs.hasNext()) {
+ if (isValidCertification(key, sigs.next(), userId)) {
+ return true;
}
}
}
-
- problems.add(missingUserIds(allowedUserIds));
- } catch (PGPException e) {
- String msg = "Error checking user IDs for key";
- log.warn(msg + " " + keyIdToString(key.getKeyID()), e);
- problems.add(msg);
}
+
+ return false;
}
@SuppressWarnings("unchecked")
@@ -105,8 +208,7 @@
Collections.emptyIterator());
}
- private Set<String> getAllowedUserIds() {
- IdentifiedUser user = userProvider.get();
+ private Set<String> getAllowedUserIds(IdentifiedUser user) {
Set<String> result = new HashSet<>();
result.addAll(user.getEmailAddresses());
for (AccountExternalId extId : user.state().getExternalIds()) {
@@ -156,4 +258,10 @@
}
return sb.toString();
}
+
+ static AccountExternalId.Key toExtIdKey(PGPPublicKey key) {
+ return new AccountExternalId.Key(
+ SCHEME_GPGKEY,
+ BaseEncoding.base16().encode(key.getFingerprint()));
+ }
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPushCertificateChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPushCertificateChecker.java
new file mode 100644
index 0000000..30983ac
--- /dev/null
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPushCertificateChecker.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2015 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.gpg;
+
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+
+public class GerritPushCertificateChecker extends PushCertificateChecker {
+ public interface Factory {
+ GerritPushCertificateChecker create(IdentifiedUser expectedUser);
+ }
+
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+
+ @AssistedInject
+ GerritPushCertificateChecker(
+ GerritPublicKeyChecker.Factory keyCheckerFactory,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers,
+ @Assisted IdentifiedUser expectedUser) {
+ super(keyCheckerFactory.create().setExpectedUser(expectedUser));
+ this.repoManager = repoManager;
+ this.allUsers = allUsers;
+ }
+
+ @Override
+ protected Repository getRepository() throws IOException {
+ return repoManager.openRepository(allUsers);
+ }
+
+ @Override
+ protected boolean shouldClose(Repository repo) {
+ return true;
+ }
+}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GpgModule.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GpgModule.java
index fc1953a..bbf61b8 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GpgModule.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GpgModule.java
@@ -14,32 +14,15 @@
package com.google.gerrit.gpg;
-import static com.google.gerrit.gpg.server.GpgKey.GPG_KEY_KIND;
-import static com.google.gerrit.server.account.AccountResource.ACCOUNT_KIND;
-
-import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
-import com.google.gerrit.extensions.common.GpgKeyInfo;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.NotImplementedException;
-import com.google.gerrit.extensions.restapi.RestApiModule;
-import com.google.gerrit.gpg.api.GpgApiAdapterImpl;
-import com.google.gerrit.gpg.api.GpgKeyApiImpl;
-import com.google.gerrit.gpg.server.DeleteGpgKey;
-import com.google.gerrit.gpg.server.GpgKeys;
-import com.google.gerrit.gpg.server.PostGpgKeys;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.gpg.api.GpgApiModule;
import com.google.gerrit.server.EnableSignedPush;
-import com.google.gerrit.server.account.AccountResource;
-import com.google.gerrit.server.api.accounts.GpgApiAdapter;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.List;
-import java.util.Map;
-
-public class GpgModule extends RestApiModule {
+public class GpgModule extends FactoryModule {
private static final Logger log = LoggerFactory.getLogger(GpgModule.class);
private final Config cfg;
@@ -52,6 +35,8 @@
protected void configure() {
boolean configEnableSignedPush =
cfg.getBoolean("receive", null, "enableSignedPush", false);
+ boolean configEditGpgKeys =
+ cfg.getBoolean("gerrit", null, "editGpgKeys", true);
boolean havePgp = BouncyCastleUtil.havePGP();
boolean enableSignedPush = configEnableSignedPush && havePgp;
bindConstant().annotatedWith(EnableSignedPush.class).to(enableSignedPush);
@@ -60,40 +45,10 @@
log.info("Bouncy Castle PGP not installed; signed push verification is"
+ " disabled");
}
- if (!enableSignedPush) {
- bind(GpgApiAdapter.class).to(NoGpgApi.class);
- return;
+ if (enableSignedPush) {
+ install(new SignedPushModule());
+ factory(GerritPushCertificateChecker.Factory.class);
}
-
- install(new SignedPushModule());
- bind(GpgApiAdapter.class).to(GpgApiAdapterImpl.class);
- factory(GpgKeyApiImpl.Factory.class);
-
- DynamicMap.mapOf(binder(), GPG_KEY_KIND);
-
- child(ACCOUNT_KIND, "gpgkeys").to(GpgKeys.class);
- post(ACCOUNT_KIND, "gpgkeys").to(PostGpgKeys.class);
- get(GPG_KEY_KIND).to(GpgKeys.Get.class);
- delete(GPG_KEY_KIND).to(DeleteGpgKey.class);
- }
-
- private static class NoGpgApi implements GpgApiAdapter {
- private static final String MSG = "GPG key APIs disabled";
-
- @Override
- public Map<String, GpgKeyInfo> listGpgKeys(AccountResource account) {
- throw new NotImplementedException(MSG);
- }
-
- @Override
- public Map<String, GpgKeyInfo> putGpgKeys(AccountResource account,
- List<String> add, List<String> delete) {
- throw new NotImplementedException(MSG);
- }
-
- @Override
- public GpgKeyApi gpgKey(AccountResource account, IdString idStr) {
- throw new NotImplementedException(MSG);
- }
+ install(new GpgApiModule(enableSignedPush && configEditGpgKeys));
}
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java
index 7b7aabb..e4c81df 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyChecker.java
@@ -14,65 +14,476 @@
package com.google.gerrit.gpg;
+import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.BAD;
+import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.OK;
+import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.TRUSTED;
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
+import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY;
+import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_REASON;
+import static org.bouncycastle.bcpg.sig.RevocationReasonTags.KEY_COMPROMISED;
+import static org.bouncycastle.bcpg.sig.RevocationReasonTags.KEY_RETIRED;
+import static org.bouncycastle.bcpg.sig.RevocationReasonTags.KEY_SUPERSEDED;
+import static org.bouncycastle.bcpg.sig.RevocationReasonTags.NO_REASON;
+import static org.bouncycastle.openpgp.PGPSignature.DIRECT_KEY;
+import static org.bouncycastle.openpgp.PGPSignature.KEY_REVOCATION;
+import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.bcpg.sig.RevocationKey;
+import org.bouncycastle.bcpg.sig.RevocationReason;
+import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/** Checker for GPG public keys for use in a push certificate. */
public class PublicKeyChecker {
+ private static final Logger log =
+ LoggerFactory.getLogger(PublicKeyChecker.class);
+
+ // https://tools.ietf.org/html/rfc4880#section-5.2.3.13
+ private static final int COMPLETE_TRUST = 120;
+
+ private PublicKeyStore store;
+ private Map<Long, Fingerprint> trusted;
+ private int maxTrustDepth;
+ private Date effectiveTime = new Date();
+
/**
- * Check a public key.
+ * Enable web-of-trust checks.
+ * <p>
+ * If enabled, a store must be set with {@link #setStore(PublicKeyStore)}.
+ * (These methods are separate since the store is a closeable resource that
+ * may not be available when reading trusted keys from a config.)
*
- * @param key the public key.
+ * @param maxTrustDepth maximum depth to search while looking for a trusted
+ * key.
+ * @param trusted ultimately trusted key fingerprints, keyed by fingerprint;
+ * may not be empty. To construct a map, see {@link
+ * Fingerprint#byId(Iterable)}.
+ * @return a reference to this object.
*/
- public final CheckResult check(PGPPublicKey key) {
- return check(key, key.getKeyID());
+ public PublicKeyChecker enableTrust(int maxTrustDepth,
+ Map<Long, Fingerprint> trusted) {
+ if (maxTrustDepth <= 0) {
+ throw new IllegalArgumentException(
+ "maxTrustDepth must be positive, got: " + maxTrustDepth);
+ }
+ if (trusted == null || trusted.isEmpty()) {
+ throw new IllegalArgumentException(
+ "at least one trusted key is required");
+ }
+ this.maxTrustDepth = maxTrustDepth;
+ this.trusted = trusted;
+ return this;
+ }
+
+ /** Disable web-of-trust checks. */
+ public PublicKeyChecker disableTrust() {
+ trusted = null;
+ return this;
+ }
+
+ /** Set the public key store for reading keys referenced in signatures. */
+ public PublicKeyChecker setStore(PublicKeyStore store) {
+ if (store == null) {
+ throw new IllegalArgumentException("PublicKeyStore is required");
+ }
+ this.store = store;
+ return this;
+ }
+
+ /**
+ * Set the effective time for checking the key.
+ * <p>
+ * If set, check whether the key should be considered valid (e.g. unexpired)
+ * as of this time.
+ *
+ * @param effectiveTime effective time.
+ * @return a reference to this object.
+ */
+ public PublicKeyChecker setEffectiveTime(Date effectiveTime) {
+ this.effectiveTime = effectiveTime;
+ return this;
+ }
+
+ protected Date getEffectiveTime() {
+ return effectiveTime;
}
/**
* Check a public key.
*
* @param key the public key.
- * @param expectedKeyId the key ID that the caller expects.
+ * @return the result of the check.
*/
- public final CheckResult check(PGPPublicKey key, long expectedKeyId) {
- List<String> problems = new ArrayList<>();
- if (key.getKeyID() != expectedKeyId) {
- problems.add(
- "Public key does not match ID " + keyIdToString(expectedKeyId));
+ public final CheckResult check(PGPPublicKey key) {
+ if (store == null) {
+ throw new IllegalStateException("PublicKeyStore is required");
}
- if (key.isRevoked()) {
- // TODO(dborowitz): isRevoked is overeager:
- // http://www.bouncycastle.org/jira/browse/BJB-45
- problems.add("Key is revoked");
- }
-
- long validSecs = key.getValidSeconds();
- if (validSecs != 0) {
- long createdSecs = key.getCreationTime().getTime() / 1000;
- long nowSecs = System.currentTimeMillis() / 1000;
- if (nowSecs - createdSecs > validSecs) {
- problems.add("Key is expired");
- }
- }
- checkCustom(key, expectedKeyId, problems);
- return new CheckResult(problems);
+ return check(key, 0, true,
+ trusted != null ? new HashSet<Fingerprint>() : null);
}
/**
* Perform custom checks.
* <p>
- * Default implementation does nothing, but may be overridden by subclasses.
+ * Default implementation reports no problems, but may be overridden by
+ * subclasses.
*
* @param key the public key.
- * @param expectedKeyId the key ID that the caller expects.
- * @param problems list to which any problems should be added.
+ * @param depth the depth from the initial key passed to {@link #check(
+ * PGPPublicKey)}: 0 if this was the initial key, up to a maximum of
+ * {@code maxTrustDepth}.
+ * @return the result of the custom check.
*/
- public void checkCustom(PGPPublicKey key, long expectedKeyId,
+ public CheckResult checkCustom(PGPPublicKey key, int depth) {
+ return CheckResult.ok();
+ }
+
+ private CheckResult check(PGPPublicKey key, int depth, boolean expand,
+ Set<Fingerprint> seen) {
+ CheckResult basicResult = checkBasic(key, effectiveTime);
+ CheckResult customResult = checkCustom(key, depth);
+ CheckResult trustResult = checkWebOfTrust(key, store, depth, seen);
+ if (!expand && !trustResult.isTrusted()) {
+ trustResult = CheckResult.create(trustResult.getStatus(),
+ "Key is not trusted");
+ }
+
+ List<String> problems = new ArrayList<>(
+ basicResult.getProblems().size()
+ + customResult.getProblems().size()
+ + trustResult.getProblems().size());
+ problems.addAll(basicResult.getProblems());
+ problems.addAll(customResult.getProblems());
+ problems.addAll(trustResult.getProblems());
+
+ Status status;
+ if (basicResult.getStatus() == BAD
+ || customResult.getStatus() == BAD
+ || trustResult.getStatus() == BAD) {
+ // Any BAD result and the final result is BAD.
+ status = BAD;
+ } else if (trustResult.getStatus() == TRUSTED) {
+ // basicResult is BAD or OK, whereas trustResult is BAD or TRUSTED. If
+ // TRUSTED, we trust the final result.
+ status = TRUSTED;
+ } else {
+ // All results were OK or better, but trustResult was not TRUSTED. Don't
+ // let subclasses bypass checkWebOfTrust by returning TRUSTED; just return
+ // OK here.
+ status = OK;
+ }
+ return CheckResult.create(status, problems);
+ }
+
+ private CheckResult checkBasic(PGPPublicKey key, Date now) {
+ List<String> problems = new ArrayList<>(2);
+ gatherRevocationProblems(key, now, problems);
+
+ long validMs = key.getValidSeconds() * 1000;
+ if (validMs != 0) {
+ long msSinceCreation = now.getTime() - key.getCreationTime().getTime();
+ if (msSinceCreation > validMs) {
+ problems.add("Key is expired");
+ }
+ }
+ return CheckResult.create(problems);
+ }
+
+ private void gatherRevocationProblems(PGPPublicKey key, Date now,
List<String> problems) {
- // Default implementation does nothing.
+ try {
+ List<PGPSignature> revocations = new ArrayList<>();
+ Map<Long, RevocationKey> revokers = new HashMap<>();
+ PGPSignature selfRevocation =
+ scanRevocations(key, now, revocations, revokers);
+ if (selfRevocation != null) {
+ RevocationReason reason = getRevocationReason(selfRevocation);
+ if (isRevocationValid(selfRevocation, reason, now)) {
+ problems.add(reasonToString(reason));
+ }
+ } else {
+ checkRevocations(key, revocations, revokers, problems);
+ }
+ } catch (PGPException | IOException e) {
+ problems.add("Error checking key revocation");
+ }
+ }
+
+ private static boolean isRevocationValid(PGPSignature revocation,
+ RevocationReason reason, Date now) {
+ // RFC4880 states:
+ // "If a key has been revoked because of a compromise, all signatures
+ // created by that key are suspect. However, if it was merely superseded or
+ // retired, old signatures are still valid."
+ //
+ // Note that GnuPG does not implement this correctly, as it does not
+ // consider the revocation reason and timestamp when checking whether a
+ // signature (data or certification) is valid.
+ return reason.getRevocationReason() == KEY_COMPROMISED
+ || revocation.getCreationTime().before(now);
+ }
+
+ private PGPSignature scanRevocations(PGPPublicKey key, Date now,
+ List<PGPSignature> revocations, Map<Long, RevocationKey> revokers)
+ throws PGPException {
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> allSigs = key.getSignatures();
+ while (allSigs.hasNext()) {
+ PGPSignature sig = allSigs.next();
+ switch (sig.getSignatureType()) {
+ case KEY_REVOCATION:
+ if (sig.getKeyID() == key.getKeyID()) {
+ sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+ if (sig.verifyCertification(key)) {
+ return sig;
+ }
+ } else {
+ RevocationReason reason = getRevocationReason(sig);
+ if (reason != null && isRevocationValid(sig, reason, now)) {
+ revocations.add(sig);
+ }
+ }
+ break;
+ case DIRECT_KEY:
+ RevocationKey r = getRevocationKey(key, sig);
+ if (r != null) {
+ revokers.put(Fingerprint.getId(r.getFingerprint()), r);
+ }
+ break;
+ }
+ }
+ return null;
+ }
+
+ private RevocationKey getRevocationKey(PGPPublicKey key, PGPSignature sig)
+ throws PGPException {
+ if (sig.getKeyID() != key.getKeyID()) {
+ return null;
+ }
+ SignatureSubpacket sub =
+ sig.getHashedSubPackets().getSubpacket(REVOCATION_KEY);
+ if (sub == null) {
+ return null;
+ }
+ sig.init(new BcPGPContentVerifierBuilderProvider(), key);
+ if (!sig.verifyCertification(key)) {
+ return null;
+ }
+
+ return new RevocationKey(sub.isCritical(), sub.getData());
+ }
+
+ private void checkRevocations(PGPPublicKey key,
+ List<PGPSignature> revocations, Map<Long, RevocationKey> revokers,
+ List<String> problems)
+ throws PGPException, IOException {
+ for (PGPSignature revocation : revocations) {
+ RevocationKey revoker = revokers.get(revocation.getKeyID());
+ if (revoker == null) {
+ continue; // Not a designated revoker.
+ }
+ byte[] rfp = revoker.getFingerprint();
+ PGPPublicKeyRing revokerKeyRing = store.get(rfp);
+ if (revokerKeyRing == null) {
+ // Revoker is authorized and there is a revocation signature by this
+ // revoker, but the key is not in the store so we can't verify the
+ // signature.
+ log.info("Key " + Fingerprint.toString(key.getFingerprint())
+ + " is revoked by " + Fingerprint.toString(rfp)
+ + ", which is not in the store. Assuming revocation is valid.");
+ problems.add(reasonToString(getRevocationReason(revocation)));
+ continue;
+ }
+ PGPPublicKey rk = revokerKeyRing.getPublicKey();
+ if (rk.getAlgorithm() != revoker.getAlgorithm()) {
+ continue;
+ }
+ if (!checkBasic(rk, revocation.getCreationTime()).isOk()) {
+ // Revoker's key was expired or revoked at time of revocation, so the
+ // revocation is invalid.
+ continue;
+ }
+ revocation.init(new BcPGPContentVerifierBuilderProvider(), rk);
+ if (revocation.verifyCertification(key)) {
+ problems.add(reasonToString(getRevocationReason(revocation)));
+ }
+ }
+ }
+
+ private static RevocationReason getRevocationReason(PGPSignature sig) {
+ if (sig.getSignatureType() != KEY_REVOCATION) {
+ throw new IllegalArgumentException(
+ "Expected KEY_REVOCATION signature, got " + sig.getSignatureType());
+ }
+ SignatureSubpacket sub =
+ sig.getHashedSubPackets().getSubpacket(REVOCATION_REASON);
+ if (sub == null) {
+ return null;
+ }
+ return new RevocationReason(sub.isCritical(), sub.getData());
+ }
+
+ private static String reasonToString(RevocationReason reason) {
+ StringBuilder r = new StringBuilder("Key is revoked (");
+ if (reason == null) {
+ return r.append("no reason provided)").toString();
+ }
+ switch (reason.getRevocationReason()) {
+ case NO_REASON:
+ r.append("no reason code specified");
+ break;
+ case KEY_SUPERSEDED:
+ r.append("superseded");
+ break;
+ case KEY_COMPROMISED:
+ r.append("key material has been compromised");
+ break;
+ case KEY_RETIRED:
+ r.append("retired and no longer valid");
+ break;
+ default:
+ r.append("reason code ")
+ .append(Integer.toString(reason.getRevocationReason()))
+ .append(')');
+ break;
+ }
+ r.append(')');
+ String desc = reason.getRevocationDescription();
+ if (!desc.isEmpty()) {
+ r.append(": ").append(desc);
+ }
+ return r.toString();
+ }
+
+ private CheckResult checkWebOfTrust(PGPPublicKey key, PublicKeyStore store,
+ int depth, Set<Fingerprint> seen) {
+ if (trusted == null) {
+ // Trust checking not configured, server trusts all OK keys.
+ return CheckResult.trusted();
+ }
+ Fingerprint fp = new Fingerprint(key.getFingerprint());
+ if (seen.contains(fp)) {
+ return CheckResult.ok("Key is trusted in a cycle");
+ }
+ seen.add(fp);
+
+ Fingerprint trustedFp = trusted.get(key.getKeyID());
+ if (trustedFp != null && trustedFp.equals(fp)) {
+ return CheckResult.trusted(); // Directly trusted.
+ } else if (depth >= maxTrustDepth) {
+ return CheckResult.ok(
+ "No path of depth <= " + maxTrustDepth + " to a trusted key");
+ }
+
+ List<CheckResult> signerResults = new ArrayList<>();
+ @SuppressWarnings("unchecked")
+ Iterator<String> userIds = key.getUserIDs();
+ while (userIds.hasNext()) {
+ String userId = userIds.next();
+
+ // Don't check the timestamp of these certifications. This allows admins
+ // to correct untrusted keys by signing them with a trusted key, such that
+ // older signatures created by those keys retroactively appear valid.
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> sigs = key.getSignaturesForID(userId);
+
+ while (sigs.hasNext()) {
+ PGPSignature sig = sigs.next();
+ // TODO(dborowitz): Handle CERTIFICATION_REVOCATION.
+ if (sig.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION
+ && sig.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) {
+ continue; // Not a certification.
+ }
+
+ PGPPublicKey signer = getSigner(store, sig, userId, key, signerResults);
+ // TODO(dborowitz): Require self certification.
+ if (signer == null
+ || Arrays.equals(signer.getFingerprint(), key.getFingerprint())) {
+ continue;
+ }
+ String subpacketProblem = checkTrustSubpacket(sig, depth);
+ if (subpacketProblem == null) {
+ CheckResult signerResult = check(signer, depth + 1, false, seen);
+ if (signerResult.isTrusted()) {
+ return CheckResult.trusted();
+ }
+ }
+ signerResults.add(CheckResult.ok(
+ "Certification by " + keyToString(signer)
+ + " is valid, but key is not trusted"));
+ }
+ }
+
+ List<String> problems = new ArrayList<>();
+ problems.add("No path to a trusted key");
+ for (CheckResult signerResult : signerResults) {
+ problems.addAll(signerResult.getProblems());
+ }
+ return CheckResult.create(OK, problems);
+ }
+
+ private static PGPPublicKey getSigner(PublicKeyStore store, PGPSignature sig,
+ String userId, PGPPublicKey key, List<CheckResult> results) {
+ try {
+ PGPPublicKeyRingCollection signers = store.get(sig.getKeyID());
+ if (!signers.getKeyRings().hasNext()) {
+ results.add(CheckResult.ok(
+ "Key " + keyIdToString(sig.getKeyID())
+ + " used for certification is not in store"));
+ return null;
+ }
+ PGPPublicKey signer = PublicKeyStore.getSigner(signers, sig, userId, key);
+ if (signer == null) {
+ results.add(CheckResult.ok(
+ "Certification by " + keyIdToString(sig.getKeyID())
+ + " is not valid"));
+ return null;
+ }
+ return signer;
+ } catch (PGPException | IOException e) {
+ results.add(CheckResult.ok(
+ "Error checking certification by " + keyIdToString(sig.getKeyID())));
+ return null;
+ }
+ }
+
+ private String checkTrustSubpacket(PGPSignature sig, int depth) {
+ SignatureSubpacket trustSub = sig.getHashedSubPackets().getSubpacket(
+ SignatureSubpacketTags.TRUST_SIG);
+ if (trustSub == null || trustSub.getData().length != 2) {
+ return "Certification is missing trust information";
+ }
+ byte amount = trustSub.getData()[1];
+ if (amount < COMPLETE_TRUST) {
+ return "Certification does not fully trust key";
+ }
+ byte level = trustSub.getData()[0];
+ int required = depth + 1;
+ if (level < required) {
+ return "Certification trusts to depth " + level
+ + ", but depth " + required + " is required";
+ }
+ return null;
}
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
index a36052e..3d939a1 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
@@ -23,7 +23,9 @@
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -74,6 +76,52 @@
/** Ref where GPG public keys are stored. */
public static final String REFS_GPG_KEYS = "refs/meta/gpg-keys";
+ /**
+ * Choose the public key that produced a signature.
+ * <p>
+ * @param keyRings candidate keys.
+ * @param sig signature object.
+ * @param data signed payload.
+ * @return the key chosen from {@code keyRings} that was able to verify the
+ * signature, or {@code null} if none was found.
+ * @throws PGPException if an error occurred verifying the signature.
+ */
+ public static PGPPublicKey getSigner(Iterable<PGPPublicKeyRing> keyRings,
+ PGPSignature sig, byte[] data) throws PGPException {
+ for (PGPPublicKeyRing kr : keyRings) {
+ PGPPublicKey k = kr.getPublicKey();
+ sig.init(new BcPGPContentVerifierBuilderProvider(), k);
+ sig.update(data);
+ if (sig.verify()) {
+ return k;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Choose the public key that produced a certification.
+ * <p>
+ * @param keyRings candidate keys.
+ * @param sig signature object.
+ * @param userId user ID being certified.
+ * @param key key being certified.
+ * @return the key chosen from {@code keyRings} that was able to verify the
+ * certification, or {@code null} if none was found.
+ * @throws PGPException if an error occurred verifying the certification.
+ */
+ public static PGPPublicKey getSigner(Iterable<PGPPublicKeyRing> keyRings,
+ PGPSignature sig, String userId, PGPPublicKey key) throws PGPException {
+ for (PGPPublicKeyRing kr : keyRings) {
+ PGPPublicKey k = kr.getPublicKey();
+ sig.init(new BcPGPContentVerifierBuilderProvider(), k);
+ if (sig.verifyCertification(userId, key)) {
+ return k;
+ }
+ }
+ return null;
+ }
+
private final Repository repo;
private ObjectReader reader;
private RevCommit tip;
@@ -130,15 +178,39 @@
*/
public PGPPublicKeyRingCollection get(long keyId)
throws PGPException, IOException {
+ return new PGPPublicKeyRingCollection(get(keyId, null));
+ }
+
+ /**
+ * Read public key with the given fingerprint.
+ * <p>
+ * Keys should not be trusted unless checked with {@link PublicKeyChecker}.
+ * <p>
+ * Multiple calls to this method use the same state of the key ref; to reread
+ * the ref, call {@link #close()} first.
+ *
+ * @param fingerprint key fingerprint.
+ * @return the key if found, or {@code null}.
+ * @throws PGPException if an error occurred parsing the key data.
+ * @throws IOException if an error occurred reading the repository data.
+ */
+ public PGPPublicKeyRing get(byte[] fingerprint)
+ throws PGPException, IOException {
+ List<PGPPublicKeyRing> keyRings =
+ get(Fingerprint.getId(fingerprint), fingerprint);
+ return !keyRings.isEmpty() ? keyRings.get(0) : null;
+ }
+
+ private List<PGPPublicKeyRing> get(long keyId, byte[] fp) throws IOException {
if (reader == null) {
load();
}
if (notes == null) {
- return empty();
+ return Collections.emptyList();
}
Note note = notes.getNote(keyObjectId(keyId));
if (note == null) {
- return empty();
+ return Collections.emptyList();
}
List<PGPPublicKeyRing> keys = new ArrayList<>();
@@ -152,12 +224,16 @@
}
Object obj = it.next();
if (obj instanceof PGPPublicKeyRing) {
- keys.add((PGPPublicKeyRing) obj);
+ PGPPublicKeyRing kr = (PGPPublicKeyRing) obj;
+ if (fp == null
+ || Arrays.equals(fp, kr.getPublicKey().getFingerprint())) {
+ keys.add(kr);
+ }
}
checkState(!it.hasNext(),
"expected one PGP object per ArmoredInputStream");
}
- return new PGPPublicKeyRingCollection(keys);
+ return keys;
}
}
@@ -303,7 +379,7 @@
}
if (toWrite.size() == existing.size()) {
return;
- } else if (toWrite.size() > 0) {
+ } else if (!toWrite.isEmpty()) {
notes.set(keyObjectId(keyId),
ins.insert(OBJ_BLOB, keysToArmored(toWrite)));
} else {
@@ -327,12 +403,6 @@
return out.toByteArray();
}
- private static PGPPublicKeyRingCollection empty()
- throws PGPException, IOException {
- return new PGPPublicKeyRingCollection(
- Collections.<PGPPublicKeyRing> emptyList());
- }
-
public static String keyToString(PGPPublicKey key) {
@SuppressWarnings("unchecked")
Iterator<String> it = key.getUserIDs();
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java
index 86a33ab..0a0fff7 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java
@@ -14,19 +14,23 @@
package com.google.gerrit.gpg;
+import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.BAD;
+import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.OK;
+import static com.google.gerrit.extensions.common.GpgKeyInfo.Status.TRUSTED;
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import com.google.common.base.Joiner;
+import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
+
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
-import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushCertificate;
@@ -44,10 +48,37 @@
private static final Logger log =
LoggerFactory.getLogger(PushCertificateChecker.class);
+ public static class Result {
+ private final PGPPublicKey key;
+ private final CheckResult checkResult;
+
+ private Result(PGPPublicKey key, CheckResult checkResult) {
+ this.key = key;
+ this.checkResult = checkResult;
+ }
+
+ public PGPPublicKey getPublicKey() {
+ return key;
+ }
+
+ public CheckResult getCheckResult() {
+ return checkResult;
+ }
+ }
+
private final PublicKeyChecker publicKeyChecker;
+ private boolean checkNonce;
+
protected PushCertificateChecker(PublicKeyChecker publicKeyChecker) {
this.publicKeyChecker = publicKeyChecker;
+ checkNonce = true;
+ }
+
+ /** Set whether to check the status of the nonce; defaults to true. */
+ public PushCertificateChecker setCheckNonce(boolean checkNonce) {
+ this.checkNonce = checkNonce;
+ return this;
}
/**
@@ -55,33 +86,65 @@
*
* @return result of the check.
*/
- public final CheckResult check(PushCertificate cert) {
- if (cert.getNonceStatus() != NonceStatus.OK) {
- return new CheckResult("Invalid nonce");
+ public final Result check(PushCertificate cert) {
+ if (checkNonce && cert.getNonceStatus() != NonceStatus.OK) {
+ return new Result(null, CheckResult.bad("Invalid nonce"));
}
- List<String> problems = new ArrayList<>();
+ List<CheckResult> results = new ArrayList<>(2);
+ Result sigResult = null;
try {
PGPSignature sig = readSignature(cert);
if (sig != null) {
@SuppressWarnings("resource")
Repository repo = getRepository();
try (PublicKeyStore store = new PublicKeyStore(repo)) {
- checkSignature(sig, cert, store.get(sig.getKeyID()), problems);
- checkCustom(repo, problems);
+ sigResult = checkSignature(sig, cert, store);
+ results.add(checkCustom(repo));
} finally {
if (shouldClose(repo)) {
repo.close();
}
}
} else {
- problems.add("Invalid signature format");
+ results.add(CheckResult.bad("Invalid signature format"));
}
} catch (PGPException | IOException e) {
String msg = "Internal error checking push certificate";
log.error(msg, e);
- problems.add(msg);
+ results.add(CheckResult.bad(msg));
}
- return new CheckResult(problems);
+
+ return combine(sigResult, results);
+ }
+
+ private static Result combine(Result sigResult, List<CheckResult> results) {
+ // Combine results:
+ // - If any input result is BAD, the final result is bad.
+ // - If sigResult is TRUSTED and no other result is BAD, the final result
+ // is TRUSTED.
+ // - Otherwise, the result is OK.
+ List<String> problems = new ArrayList<>();
+ boolean bad = false;
+ for (CheckResult result : results) {
+ problems.addAll(result.getProblems());
+ bad |= result.getStatus() == BAD;
+ }
+ Status status = bad ? BAD : OK;
+
+ PGPPublicKey key;
+ if (sigResult != null) {
+ key = sigResult.getPublicKey();
+ CheckResult cr = sigResult.getCheckResult();
+ problems.addAll(cr.getProblems());
+ if (cr.getStatus() == BAD) {
+ status = BAD;
+ } else if (!bad && cr.getStatus() == TRUSTED) {
+ status = TRUSTED;
+ }
+ } else {
+ key = null;
+ }
+ return new Result(key, CheckResult.create(status, problems));
}
/**
@@ -104,13 +167,14 @@
/**
* Perform custom checks.
* <p>
- * Default implementation does nothing, but may be overridden by subclasses.
+ * Default implementation reports no problems, but may be overridden by
+ * subclasses.
*
* @param repo a repository previously returned by {@link #getRepository()}.
- * @param problems list to which any problems should be added.
+ * @return the result of the custom check.
*/
- protected void checkCustom(Repository repo, List<String> problems) {
- // Default implementation does nothing.
+ protected CheckResult checkCustom(Repository repo) {
+ return CheckResult.ok();
}
private PGPSignature readSignature(PushCertificate cert) throws IOException {
@@ -129,47 +193,33 @@
return null;
}
- private void checkSignature(PGPSignature sig,
- PushCertificate cert, PGPPublicKeyRingCollection keys,
- List<String> problems) {
- List<String> deferredProblems = new ArrayList<>();
- boolean anyKeys = false;
- for (PGPPublicKeyRing kr : keys) {
- PGPPublicKey k = kr.getPublicKey();
- anyKeys = true;
- try {
- sig.init(new BcPGPContentVerifierBuilderProvider(), k);
- sig.update(Constants.encode(cert.toText()));
- if (!sig.verify()) {
- // TODO(dborowitz): Privacy issues with exposing fingerprint/user ID
- // of keys having the same ID as the pusher's key?
- deferredProblems.add(
- "Signature not valid with public key: " + keyToString(k));
- continue;
- }
- CheckResult result = publicKeyChecker.check(k, sig.getKeyID());
- if (result.isOk()) {
- return;
- }
- StringBuilder err = new StringBuilder("Invalid public key ")
- .append(keyToString(k))
- .append(":");
- for (int i = 0; i < result.getProblems().size(); i++) {
- err.append('\n').append(" ").append(result.getProblems().get(i));
- }
- problems.add(err.toString());
- return;
- } catch (PGPException e) {
- deferredProblems.add(
- "Error checking signature with public key " + keyToString(k)
- + ": " + e.getMessage());
- }
+ private Result checkSignature(PGPSignature sig, PushCertificate cert,
+ PublicKeyStore store) throws PGPException, IOException {
+ PGPPublicKeyRingCollection keys = store.get(sig.getKeyID());
+ if (!keys.getKeyRings().hasNext()) {
+ return new Result(null,
+ CheckResult.bad("No public keys found for key ID "
+ + keyIdToString(sig.getKeyID())));
}
- if (!anyKeys) {
- problems.add(
- "No public keys found for key ID " + keyIdToString(sig.getKeyID()));
- } else {
- problems.addAll(deferredProblems);
+ PGPPublicKey signer =
+ PublicKeyStore.getSigner(keys, sig, Constants.encode(cert.toText()));
+ if (signer == null) {
+ return new Result(null,
+ CheckResult.bad("Signature by " + keyIdToString(sig.getKeyID())
+ + " is not valid"));
}
+ CheckResult result = publicKeyChecker
+ .setStore(store)
+ .setEffectiveTime(sig.getCreationTime())
+ .check(signer);
+ if (!result.getProblems().isEmpty()) {
+ StringBuilder err = new StringBuilder("Invalid public key ")
+ .append(keyToString(signer))
+ .append(":\n ")
+ .append(Joiner.on("\n ").join(result.getProblems()));
+ return new Result(
+ signer, CheckResult.create(result.getStatus(), err.toString()));
+ }
+ return new Result(signer, result);
}
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushModule.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushModule.java
index 7508806..bc027cd 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushModule.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushModule.java
@@ -15,7 +15,6 @@
package com.google.gerrit.gpg;
import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.EnableSignedPush;
@@ -33,6 +32,7 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.PreReceiveHookChain;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.SignedPushConfig;
@@ -42,6 +42,8 @@
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
class SignedPushModule extends AbstractModule {
@@ -53,7 +55,6 @@
if (!BouncyCastleUtil.havePGP()) {
throw new ProvisionException("Bouncy Castle PGP not installed");
}
- bind(PublicKeyChecker.class).to(GerritPublicKeyChecker.class);
bind(PublicKeyStore.class).toProvider(StoreProvider.class);
DynamicSet.bind(binder(), ReceivePackInitializer.class)
.to(Initializer.class);
@@ -93,15 +94,22 @@
if (!ps.isEnableSignedPush()) {
rp.setSignedPushConfig(null);
return;
- }
- if (signedPushConfig == null) {
+ } else if (signedPushConfig == null) {
log.error("receive.enableSignedPush is true for project {} but"
+ " false in gerrit.config, so signed push verification is"
+ " disabled", project.get());
+ rp.setSignedPushConfig(null);
+ return;
}
rp.setSignedPushConfig(signedPushConfig);
- rp.setPreReceiveHook(PreReceiveHookChain.newChain(Lists.newArrayList(
- hook, rp.getPreReceiveHook())));
+
+ List<PreReceiveHook> hooks = new ArrayList<>(3);
+ if (ps.isRequireSignedPush()) {
+ hooks.add(SignedPushPreReceiveHook.Required.INSTANCE);
+ }
+ hooks.add(hook);
+ hooks.add(rp.getPreReceiveHook());
+ rp.setPreReceiveHook(PreReceiveHookChain.newChain(hooks));
}
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java
index b2dca8b..cdc3c62 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java
@@ -14,18 +14,17 @@
package com.google.gerrit.gpg;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
-import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.PushCertificate;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceivePack;
-import java.io.IOException;
import java.util.Collection;
/**
@@ -37,18 +36,30 @@
*/
@Singleton
public class SignedPushPreReceiveHook implements PreReceiveHook {
- private final GitRepositoryManager repoManager;
- private final AllUsersName allUsers;
- private final PublicKeyChecker keyChecker;
+ public static class Required implements PreReceiveHook {
+ public static final Required INSTANCE = new Required();
+
+ @Override
+ public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
+ if (rp.getPushCertificate() == null) {
+ rp.sendMessage("ERROR: Signed push is required");
+ reject(commands, "push cert error");
+ }
+ }
+
+ private Required() {
+ }
+ }
+
+ private final Provider<IdentifiedUser> user;
+ private final GerritPushCertificateChecker.Factory checkerFactory;
@Inject
public SignedPushPreReceiveHook(
- GitRepositoryManager repoManager,
- AllUsersName allUsers,
- PublicKeyChecker keyChecker) {
- this.repoManager = repoManager;
- this.allUsers = allUsers;
- this.keyChecker = keyChecker;
+ Provider<IdentifiedUser> user,
+ GerritPushCertificateChecker.Factory checkerFactory) {
+ this.user = user;
+ this.checkerFactory = checkerFactory;
}
@Override
@@ -58,19 +69,11 @@
if (cert == null) {
return;
}
- PushCertificateChecker checker = new PushCertificateChecker(keyChecker) {
- @Override
- protected Repository getRepository() throws IOException {
- return repoManager.openRepository(allUsers);
- }
-
- @Override
- protected boolean shouldClose(Repository repo) {
- return true;
- }
- };
- CheckResult result = checker.check(cert);
- if (!result.isOk()) {
+ CheckResult result = checkerFactory.create(user.get())
+ .setCheckNonce(true)
+ .check(cert)
+ .getCheckResult();
+ if (!isAllowed(result, commands)) {
for (String problem : result.getProblems()) {
rp.sendMessage(problem);
}
@@ -78,6 +81,28 @@
}
}
+ private static boolean isAllowed(CheckResult result,
+ Collection<ReceiveCommand> commands) {
+ if (onlyMagicBranches(commands)) {
+ // Only pushing magic branches: allow a valid push certificate even if the
+ // key is not ultimately trusted. Assume anyone with Submit permission to
+ // the branch is able to verify during review that the code is legitimate.
+ return result.isOk();
+ } else {
+ // Directly updating one or more refs: require a trusted key.
+ return result.isTrusted();
+ }
+ }
+
+ private static boolean onlyMagicBranches(Iterable<ReceiveCommand> commands) {
+ for (ReceiveCommand c : commands) {
+ if (!MagicBranch.isMagicBranch(c.getRefName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private static void reject(Collection<ReceiveCommand> commands,
String reason) {
for (ReceiveCommand cmd : commands) {
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
index 64b9e85..e6720db 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
@@ -16,17 +16,23 @@
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
import com.google.gerrit.extensions.common.GpgKeyInfo;
+import com.google.gerrit.extensions.common.PushCertificateInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.gpg.GerritPushCertificateChecker;
+import com.google.gerrit.gpg.PushCertificateChecker;
import com.google.gerrit.gpg.server.GpgKeys;
import com.google.gerrit.gpg.server.PostGpgKeys;
import com.google.gerrit.server.GpgException;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.api.accounts.GpgApiAdapter;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import org.bouncycastle.openpgp.PGPException;
+import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.PushCertificateParser;
import java.io.IOException;
import java.util.List;
@@ -36,15 +42,18 @@
private final PostGpgKeys postGpgKeys;
private final GpgKeys gpgKeys;
private final GpgKeyApiImpl.Factory gpgKeyApiFactory;
+ private final GerritPushCertificateChecker.Factory pushCertCheckerFactory;
@Inject
GpgApiAdapterImpl(
PostGpgKeys postGpgKeys,
GpgKeys gpgKeys,
- GpgKeyApiImpl.Factory gpgKeyApiFactory) {
+ GpgKeyApiImpl.Factory gpgKeyApiFactory,
+ GerritPushCertificateChecker.Factory pushCertCheckerFactory) {
this.postGpgKeys = postGpgKeys;
this.gpgKeys = gpgKeys;
this.gpgKeyApiFactory = gpgKeyApiFactory;
+ this.pushCertCheckerFactory = pushCertCheckerFactory;
}
@Override
@@ -80,4 +89,23 @@
throw new GpgException(e);
}
}
+
+ @Override
+ public PushCertificateInfo checkPushCertificate(String certStr,
+ IdentifiedUser expectedUser) throws GpgException {
+ try {
+ PushCertificate cert = PushCertificateParser.fromString(certStr);
+ PushCertificateChecker.Result result = pushCertCheckerFactory
+ .create(expectedUser)
+ .setCheckNonce(false)
+ .check(cert);
+ PushCertificateInfo info = new PushCertificateInfo();
+ info.certificate = certStr;
+ info.key = GpgKeys.toJson(result.getPublicKey(), result.getCheckResult());
+ return info;
+ } catch (IOException e) {
+ throw new GpgException(e);
+ }
+ }
+
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiModule.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiModule.java
new file mode 100644
index 0000000..932f439
--- /dev/null
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiModule.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2015 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.gpg.api;
+
+import static com.google.gerrit.gpg.server.GpgKey.GPG_KEY_KIND;
+import static com.google.gerrit.server.account.AccountResource.ACCOUNT_KIND;
+
+import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
+import com.google.gerrit.extensions.common.GpgKeyInfo;
+import com.google.gerrit.extensions.common.PushCertificateInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.gpg.server.DeleteGpgKey;
+import com.google.gerrit.gpg.server.GpgKeys;
+import com.google.gerrit.gpg.server.PostGpgKeys;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.api.accounts.GpgApiAdapter;
+
+import java.util.List;
+import java.util.Map;
+
+public class GpgApiModule extends RestApiModule {
+ private final boolean enabled;
+
+ public GpgApiModule(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ protected void configure() {
+ if (!enabled) {
+ bind(GpgApiAdapter.class).to(NoGpgApi.class);
+ return;
+ }
+ bind(GpgApiAdapter.class).to(GpgApiAdapterImpl.class);
+ factory(GpgKeyApiImpl.Factory.class);
+
+ DynamicMap.mapOf(binder(), GPG_KEY_KIND);
+
+ child(ACCOUNT_KIND, "gpgkeys").to(GpgKeys.class);
+ post(ACCOUNT_KIND, "gpgkeys").to(PostGpgKeys.class);
+ get(GPG_KEY_KIND).to(GpgKeys.Get.class);
+ delete(GPG_KEY_KIND).to(DeleteGpgKey.class);
+ }
+
+ private static class NoGpgApi implements GpgApiAdapter {
+ private static final String MSG = "GPG key APIs disabled";
+
+ @Override
+ public Map<String, GpgKeyInfo> listGpgKeys(AccountResource account) {
+ throw new NotImplementedException(MSG);
+ }
+
+ @Override
+ public Map<String, GpgKeyInfo> putGpgKeys(AccountResource account,
+ List<String> add, List<String> delete) {
+ throw new NotImplementedException(MSG);
+ }
+
+ @Override
+ public GpgKeyApi gpgKey(AccountResource account, IdString idStr) {
+ throw new NotImplementedException(MSG);
+ }
+
+ @Override
+ public PushCertificateInfo checkPushCertificate(String certStr,
+ IdentifiedUser expectedUser) {
+ throw new NotImplementedException(MSG);
+ }
+ }
+}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
index b22ca0e..a136007 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -32,7 +32,10 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.gpg.BouncyCastleUtil;
+import com.google.gerrit.gpg.CheckResult;
import com.google.gerrit.gpg.Fingerprint;
+import com.google.gerrit.gpg.GerritPublicKeyChecker;
+import com.google.gerrit.gpg.PublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -68,14 +71,17 @@
private final DynamicMap<RestView<GpgKey>> views;
private final Provider<ReviewDb> db;
private final Provider<PublicKeyStore> storeProvider;
+ private final GerritPublicKeyChecker.Factory checkerFactory;
@Inject
GpgKeys(DynamicMap<RestView<GpgKey>> views,
Provider<ReviewDb> db,
- Provider<PublicKeyStore> storeProvider) {
+ Provider<PublicKeyStore> storeProvider,
+ GerritPublicKeyChecker.Factory checkerFactory) {
this.views = views;
this.db = db;
this.storeProvider = storeProvider;
+ this.checkerFactory = checkerFactory;
}
@Override
@@ -155,7 +161,10 @@
for (PGPPublicKeyRing keyRing : store.get(keyId(fp))) {
if (Arrays.equals(keyRing.getPublicKey().getFingerprint(), fp)) {
found = true;
- GpgKeyInfo info = toJson(keyRing);
+ GpgKeyInfo info = toJson(
+ keyRing.getPublicKey(),
+ checkerFactory.create(rsrc.getUser(), store),
+ store);
keys.put(info.id, info);
info.id = null;
break;
@@ -173,9 +182,24 @@
@Singleton
public static class Get implements RestReadView<GpgKey> {
+ private final Provider<PublicKeyStore> storeProvider;
+ private final GerritPublicKeyChecker.Factory checkerFactory;
+
+ @Inject
+ Get(Provider<PublicKeyStore> storeProvider,
+ GerritPublicKeyChecker.Factory checkerFactory) {
+ this.storeProvider = storeProvider;
+ this.checkerFactory = checkerFactory;
+ }
+
@Override
public GpgKeyInfo apply(GpgKey rsrc) throws IOException {
- return toJson(rsrc.getKeyRing());
+ try (PublicKeyStore store = storeProvider.get()) {
+ return toJson(
+ rsrc.getKeyRing().getPublicKey(),
+ checkerFactory.create().setExpectedUser(rsrc.getUser()),
+ store);
+ }
}
}
@@ -207,23 +231,41 @@
}
}
- static GpgKeyInfo toJson(PGPPublicKeyRing keyRing) throws IOException {
- PGPPublicKey key = keyRing.getPublicKey();
+ public static GpgKeyInfo toJson(PGPPublicKey key, CheckResult checkResult)
+ throws IOException {
GpgKeyInfo info = new GpgKeyInfo();
- info.id = PublicKeyStore.keyIdToString(key.getKeyID());
- info.fingerprint = Fingerprint.toString(key.getFingerprint());
- @SuppressWarnings("unchecked")
- Iterator<String> userIds = key.getUserIDs();
- info.userIds = ImmutableList.copyOf(userIds);
- try (ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
- ArmoredOutputStream aout = new ArmoredOutputStream(out)) {
- // This is not exactly the key stored in the store, but is equivalent. In
- // particular, it will have a Bouncy Castle version string. The armored
- // stream reader in PublicKeyStore doesn't give us an easy way to extract
- // the original ASCII armor.
- key.encode(aout);
- info.key = new String(out.toByteArray(), UTF_8);
+
+ if (key != null) {
+ info.id = PublicKeyStore.keyIdToString(key.getKeyID());
+ info.fingerprint = Fingerprint.toString(key.getFingerprint());
+ @SuppressWarnings("unchecked")
+ Iterator<String> userIds = key.getUserIDs();
+ info.userIds = ImmutableList.copyOf(userIds);
+
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
+ ArmoredOutputStream aout = new ArmoredOutputStream(out)) {
+ // This is not exactly the key stored in the store, but is equivalent. In
+ // particular, it will have a Bouncy Castle version string. The armored
+ // stream reader in PublicKeyStore doesn't give us an easy way to extract
+ // the original ASCII armor.
+ key.encode(aout);
+ info.key = new String(out.toByteArray(), UTF_8);
+ }
}
+
+ info.status = checkResult.getStatus();
+ info.problems = checkResult.getProblems();
+
return info;
}
+
+ static GpgKeyInfo toJson(PGPPublicKey key, PublicKeyChecker checker,
+ PublicKeyStore store) throws IOException {
+ return toJson(key, checker.setStore(store).check(key));
+ }
+
+ public static void toJson(GpgKeyInfo info, CheckResult checkResult) {
+ info.status = checkResult.getStatus();
+ info.problems = checkResult.getProblems();
+ }
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 80e3500..91c4494 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -35,12 +35,14 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.gpg.CheckResult;
import com.google.gerrit.gpg.Fingerprint;
+import com.google.gerrit.gpg.GerritPublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.gpg.server.PostGpgKeys.Input;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.mail.AddKeySender;
import com.google.gwtorm.server.OrmException;
@@ -79,19 +81,19 @@
private final Provider<PersonIdent> serverIdent;
private final Provider<ReviewDb> db;
private final Provider<PublicKeyStore> storeProvider;
- private final PublicKeyChecker checker;
+ private final GerritPublicKeyChecker.Factory checkerFactory;
private final AddKeySender.Factory addKeyFactory;
@Inject
PostGpgKeys(@GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<ReviewDb> db,
Provider<PublicKeyStore> storeProvider,
- PublicKeyChecker checker,
+ GerritPublicKeyChecker.Factory checkerFactory,
AddKeySender.Factory addKeyFactory) {
this.serverIdent = serverIdent;
this.db = db;
this.storeProvider = storeProvider;
- this.checker = checker;
+ this.checkerFactory = checkerFactory;
this.addKeyFactory = addKeyFactory;
}
@@ -135,7 +137,7 @@
return toExtIdKey(fp.get());
}
}));
- return toJson(newKeys, toRemove);
+ return toJson(newKeys, toRemove, store, rsrc.getUser());
}
}
@@ -191,7 +193,10 @@
List<String> addedKeys = new ArrayList<>();
for (PGPPublicKeyRing keyRing : keyRings) {
PGPPublicKey key = keyRing.getPublicKey();
- CheckResult result = checker.check(key);
+ // Don't check web of trust; admins can fill in certifications later.
+ CheckResult result = checkerFactory.create(rsrc.getUser(), store)
+ .disableTrust()
+ .check(key);
if (!result.isOk()) {
throw new BadRequestException(String.format(
"Problems with public key %s:\n%s",
@@ -237,13 +242,19 @@
BaseEncoding.base16().encode(fp));
}
- private static Map<String, GpgKeyInfo> toJson(
+ private Map<String, GpgKeyInfo> toJson(
Collection<PGPPublicKeyRing> keys,
- Set<Fingerprint> deleted) throws IOException {
+ Set<Fingerprint> deleted, PublicKeyStore store, IdentifiedUser user)
+ throws IOException {
+ // Unlike when storing keys, include web-of-trust checks when producing
+ // result JSON, so the user at least knows of any issues.
+ PublicKeyChecker checker = checkerFactory.create(user, store);
Map<String, GpgKeyInfo> infos =
Maps.newHashMapWithExpectedSize(keys.size() + deleted.size());
for (PGPPublicKeyRing keyRing : keys) {
- GpgKeyInfo info = GpgKeys.toJson(keyRing);
+ PGPPublicKey key = keyRing.getPublicKey();
+ CheckResult result = checker.check(key);
+ GpgKeyInfo info = GpgKeys.toJson(key, result);
infos.put(info.id, info);
info.id = null;
}
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index e65ba00..4df9d37 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -14,10 +14,23 @@
package com.google.gerrit.gpg;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.gpg.GerritPublicKeyChecker.toExtIdKey;
+import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithSecondUserId;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyA;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyB;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyC;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyD;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyE;
+import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_MAILTO;
+import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
+import static org.eclipse.jgit.lib.RefUpdate.Result.FORCED;
+import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
@@ -39,11 +52,23 @@
import com.google.inject.Provider;
import com.google.inject.util.Providers;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushCertificateIdent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
/** Unit tests for {@link GerritPublicKeyChecker}. */
public class GerritPublicKeyCheckerTest {
@@ -54,7 +79,7 @@
private AccountManager accountManager;
@Inject
- private GerritPublicKeyChecker checker;
+ private GerritPublicKeyChecker.Factory checkerFactory;
@Inject
private IdentifiedUser.GenericFactory userFactory;
@@ -72,10 +97,18 @@
private ReviewDb db;
private Account.Id userId;
private IdentifiedUser user;
+ private Repository storeRepo;
+ private PublicKeyStore store;
@Before
public void setUpInjector() throws Exception {
- Injector injector = Guice.createInjector(new InMemoryModule());
+ Config cfg = InMemoryModule.newDefaultConfig();
+ cfg.setInt("receive", null, "maxTrustDepth", 2);
+ cfg.setStringList("receive", null, "trustedKey", ImmutableList.of(
+ Fingerprint.toString(keyB().getPublicKey().getFingerprint()),
+ Fingerprint.toString(keyD().getPublicKey().getFingerprint())));
+ Injector injector = Guice.createInjector(new InMemoryModule(cfg));
+
lifecycle = new LifecycleManager();
lifecycle.add(injector);
injector.injectMembers(this);
@@ -86,14 +119,14 @@
userId =
accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
Account userAccount = db.accounts().get(userId);
- // Note: does not match any key in TestKey.
+ // Note: does not match any key in TestKeys.
userAccount.setPreferredEmail("user@example.com");
db.accounts().update(ImmutableList.of(userAccount));
user = reloadUser();
requestContext.setContext(new RequestContext() {
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
@@ -102,6 +135,21 @@
return Providers.of(db);
}
});
+
+ storeRepo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
+ store = new PublicKeyStore(storeRepo);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ store.close();
+ storeRepo.close();
+ }
+
+ private IdentifiedUser addUser(String name) throws Exception {
+ AuthRequest req = AuthRequest.forUser(name);
+ Account.Id id = accountManager.authenticate(req).getAccountId();
+ return userFactory.create(Providers.of(db), id);
}
private IdentifiedUser reloadUser() {
@@ -123,23 +171,29 @@
@Test
public void defaultGpgCertificationMatchesEmail() throws Exception {
- TestKey key = TestKey.key5();
+ TestKey key = validKeyWithSecondUserId();
+ PublicKeyChecker checker = checkerFactory.create(user, store)
+ .disableTrust();
assertProblems(
- TestKey.key5(),
+ checker.check(key.getPublicKey()), Status.BAD,
"Key must contain a valid certification for one of the following "
+ "identities:\n"
+ " gerrit:user\n"
+ " username:user");
addExternalId("test", "test", "test5@example.com");
- assertNoProblems(key);
+ checker = checkerFactory.create(user, store)
+ .disableTrust();
+ assertNoProblems(checker.check(key.getPublicKey()));
}
@Test
public void defaultGpgCertificationDoesNotMatchEmail() throws Exception {
addExternalId("test", "test", "nobody@example.com");
+ PublicKeyChecker checker = checkerFactory.create(user, store)
+ .disableTrust();
assertProblems(
- TestKey.key5(),
+ checker.check(validKeyWithSecondUserId().getPublicKey()), Status.BAD,
"Key must contain a valid certification for one of the following "
+ "identities:\n"
+ " gerrit:user\n"
@@ -151,14 +205,18 @@
@Test
public void manualCertificationMatchesExternalId() throws Exception {
addExternalId("foo", "myId", null);
- assertNoProblems(TestKey.key5());
+ PublicKeyChecker checker = checkerFactory.create(user, store)
+ .disableTrust();
+ assertNoProblems(checker.check(validKeyWithSecondUserId().getPublicKey()));
}
@Test
- public void manualCertificationDoesNotExternalId() throws Exception {
+ public void manualCertificationDoesNotMatchExternalId() throws Exception {
addExternalId("foo", "otherId", null);
+ PublicKeyChecker checker = checkerFactory.create(user, store)
+ .disableTrust();
assertProblems(
- TestKey.key5(),
+ checker.check(validKeyWithSecondUserId().getPublicKey()), Status.BAD,
"Key must contain a valid certification for one of the following "
+ "identities:\n"
+ " foo:otherId\n"
@@ -171,24 +229,225 @@
db.accountExternalIds().delete(
db.accountExternalIds().byAccount(user.getAccountId()));
reloadUser();
+
+ TestKey key = validKeyWithSecondUserId();
+ PublicKeyChecker checker = checkerFactory.create(user, store)
+ .disableTrust();
assertProblems(
- TestKey.key5(),
+ checker.check(key.getPublicKey()), Status.BAD,
"No identities found for user; check"
+ " http://test/#/settings/web-identities");
+
+ checker = checkerFactory.create()
+ .setStore(store)
+ .disableTrust();
+ assertProblems(
+ checker.check(key.getPublicKey()), Status.BAD,
+ "Key is not associated with any users");
+
+ db.accountExternalIds().insert(Collections.singleton(
+ new AccountExternalId(
+ user.getAccountId(), toExtIdKey(key.getPublicKey()))));
+ reloadUser();
+ assertProblems(
+ checker.check(key.getPublicKey()), Status.BAD,
+ "No identities found for user");
}
- private void assertNoProblems(TestKey key) throws Exception {
- assertThat(checker.check(key.getPublicKey()).getProblems()).isEmpty();
+ @Test
+ public void checkValidTrustChainAndCorrectExternalIds() throws Exception {
+ // A---Bx
+ // \
+ // \---C---D
+ // \
+ // \---Ex
+ //
+ // The server ultimately trusts B and D.
+ // D and E trust C to be a valid introducer of depth 2.
+ IdentifiedUser userB = addUser("userB");
+ TestKey keyA = add(keyA(), user);
+ TestKey keyB = add(keyB(), userB);
+ add(keyC(), addUser("userC"));
+ add(keyD(), addUser("userD"));
+ add(keyE(), addUser("userE"));
+
+ // Checker for A, checking A.
+ PublicKeyChecker checkerA = checkerFactory.create(user, store);
+ assertNoProblems(checkerA.check(keyA.getPublicKey()));
+
+ // Checker for B, checking B. Trust chain and IDs are correct, so the only
+ // problem is with the key itself.
+ PublicKeyChecker checkerB = checkerFactory.create(userB, store);
+ assertProblems(
+ checkerB.check(keyB.getPublicKey()), Status.BAD,
+ "Key is expired");
}
- private void assertProblems(TestKey key, String... expected)
+ @Test
+ public void checkWithValidKeyButWrongExpectedUserInChecker()
throws Exception {
- checkArgument(expected.length > 0);
- assertThat(checker.check(key.getPublicKey()).getProblems())
- .containsExactly((Object[]) expected)
+ // A---Bx
+ // \
+ // \---C---D
+ // \
+ // \---Ex
+ //
+ // The server ultimately trusts B and D.
+ // D and E trust C to be a valid introducer of depth 2.
+ IdentifiedUser userB = addUser("userB");
+ TestKey keyA = add(keyA(), user);
+ TestKey keyB = add(keyB(), userB);
+ add(keyC(), addUser("userC"));
+ add(keyD(), addUser("userD"));
+ add(keyE(), addUser("userE"));
+
+ // Checker for A, checking B.
+ PublicKeyChecker checkerA = checkerFactory.create(user, store);
+ assertProblems(
+ checkerA.check(keyB.getPublicKey()), Status.BAD,
+ "Key is expired",
+ "Key must contain a valid certification for one of the following"
+ + " identities:\n"
+ + " gerrit:user\n"
+ + " mailto:testa@example.com\n"
+ + " testa@example.com\n"
+ + " username:user");
+
+ // Checker for B, checking A.
+ PublicKeyChecker checkerB = checkerFactory.create(userB, store);
+ assertProblems(
+ checkerB.check(keyA.getPublicKey()), Status.BAD,
+ "Key must contain a valid certification for one of the following"
+ + " identities:\n"
+ + " gerrit:userB\n"
+ + " mailto:testb@example.com\n"
+ + " testb@example.com\n"
+ + " username:userB");
+ }
+
+ @Test
+ public void checkTrustChainWithExpiredKey() throws Exception {
+ // A---Bx
+ //
+ // The server ultimately trusts B.
+ TestKey keyA = add(keyA(), user);
+ TestKey keyB = add(keyB(), addUser("userB"));
+
+ PublicKeyChecker checker = checkerFactory.create(user, store);
+ assertProblems(
+ checker.check(keyA.getPublicKey()), Status.OK,
+ "No path to a trusted key",
+ "Certification by " + keyToString(keyB.getPublicKey())
+ + " is valid, but key is not trusted",
+ "Key D24FE467 used for certification is not in store");
+ }
+
+ @Test
+ public void checkTrustChainUsingCheckerWithoutExpectedKey() throws Exception {
+ // A---Bx
+ // \
+ // \---C---D
+ // \
+ // \---Ex
+ //
+ // The server ultimately trusts B and D.
+ // D and E trust C to be a valid introducer of depth 2.
+ TestKey keyA = add(keyA(), user);
+ TestKey keyB = add(keyB(), addUser("userB"));
+ TestKey keyC = add(keyC(), addUser("userC"));
+ TestKey keyD = add(keyD(), addUser("userD"));
+ TestKey keyE = add(keyE(), addUser("userE"));
+
+ // This checker can check any key, so the only problems come from issues
+ // with the keys themselves, not having invalid user IDs.
+ PublicKeyChecker checker = checkerFactory.create()
+ .setStore(store);
+ assertNoProblems(checker.check(keyA.getPublicKey()));
+ assertProblems(
+ checker.check(keyB.getPublicKey()), Status.BAD,
+ "Key is expired");
+ assertNoProblems(checker.check(keyC.getPublicKey()));
+ assertNoProblems(checker.check(keyD.getPublicKey()));
+ assertProblems(
+ checker.check(keyE.getPublicKey()), Status.BAD,
+ "Key is expired",
+ "No path to a trusted key");
+ }
+
+ @Test
+ public void keyLaterInTrustChainMissingUserId() throws Exception {
+ // A---Bx
+ // \
+ // \---C
+ //
+ // The server ultimately trusts B.
+ // C signed A's key but is not in the store.
+ TestKey keyA = add(keyA(), user);
+
+ PGPPublicKeyRing keyRingB = keyB().getPublicKeyRing();
+ PGPPublicKey keyB = keyRingB.getPublicKey();
+ keyB = PGPPublicKey.removeCertification(
+ keyB, (String) keyB.getUserIDs().next());
+ keyRingB = PGPPublicKeyRing.insertPublicKey(keyRingB, keyB);
+ add(keyRingB, addUser("userB"));
+
+ PublicKeyChecker checkerA = checkerFactory.create(user, store);
+ assertProblems(checkerA.check(keyA.getPublicKey()), Status.OK,
+ "No path to a trusted key",
+ "Certification by " + keyToString(keyB)
+ + " is valid, but key is not trusted",
+ "Key D24FE467 used for certification is not in store");
+ }
+
+ private void add(PGPPublicKeyRing kr, IdentifiedUser user) throws Exception {
+ Account.Id id = user.getAccountId();
+ List<AccountExternalId> newExtIds = new ArrayList<>(2);
+ newExtIds.add(new AccountExternalId(id, toExtIdKey(kr.getPublicKey())));
+
+ @SuppressWarnings("unchecked")
+ String userId = (String) Iterators.getOnlyElement(
+ kr.getPublicKey().getUserIDs(), null);
+ if (userId != null) {
+ String email = PushCertificateIdent.parse(userId).getEmailAddress();
+ assertThat(email).contains("@");
+ AccountExternalId mailto = new AccountExternalId(
+ id, new AccountExternalId.Key(SCHEME_MAILTO, email));
+ mailto.setEmailAddress(email);
+ newExtIds.add(mailto);
+ }
+
+ store.add(kr);
+ PersonIdent ident = new PersonIdent("A U Thor", "author@example.com");
+ CommitBuilder cb = new CommitBuilder();
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ assertThat(store.save(cb)).isAnyOf(NEW, FAST_FORWARD, FORCED);
+
+ db.accountExternalIds().insert(newExtIds);
+ accountCache.evict(user.getAccountId());
+ }
+
+ private TestKey add(TestKey k, IdentifiedUser user) throws Exception {
+ add(k.getPublicKeyRing(), user);
+ return k;
+ }
+
+ private void assertProblems(CheckResult result, Status expectedStatus,
+ String first, String... rest) throws Exception {
+ List<String> expectedProblems = new ArrayList<>();
+ expectedProblems.add(first);
+ expectedProblems.addAll(Arrays.asList(rest));
+ assertThat(result.getStatus()).isEqualTo(expectedStatus);
+ assertThat(result.getProblems())
+ .containsExactlyElementsIn(expectedProblems)
.inOrder();
}
+ private void assertNoProblems(CheckResult result) {
+ assertThat(result.getStatus()).isEqualTo(Status.TRUSTED);
+ assertThat(result.getProblems()).isEmpty();
+ }
+
private void addExternalId(String scheme, String id, String email)
throws Exception {
AccountExternalId extId = new AccountExternalId(user.getAccountId(),
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyCheckerTest.java
index ebc3e58..742bf1a 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyCheckerTest.java
@@ -14,55 +14,384 @@
package com.google.gerrit.gpg;
+import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import static com.google.gerrit.gpg.testutil.TestKeys.expiredKey;
+import static com.google.gerrit.gpg.testutil.TestKeys.keyRevokedByExpiredKeyAfterExpiration;
+import static com.google.gerrit.gpg.testutil.TestKeys.keyRevokedByExpiredKeyBeforeExpiration;
+import static com.google.gerrit.gpg.testutil.TestKeys.revokedCompromisedKey;
+import static com.google.gerrit.gpg.testutil.TestKeys.revokedNoLongerUsedKey;
+import static com.google.gerrit.gpg.testutil.TestKeys.selfRevokedKey;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyA;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyB;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyC;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyD;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyE;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyF;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyG;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyH;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyI;
+import static com.google.gerrit.gpg.testutil.TestTrustKeys.keyJ;
+import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY;
+import static org.bouncycastle.openpgp.PGPSignature.DIRECT_KEY;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.gpg.testutil.TestKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
public class PublicKeyCheckerTest {
- private PublicKeyChecker checker;
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private InMemoryRepository repo;
+ private PublicKeyStore store;
@Before
public void setUp() {
- checker = new PublicKeyChecker();
+ repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
+ store = new PublicKeyStore(repo);
+ }
+
+ @After
+ public void tearDown() {
+ if (store != null) {
+ store.close();
+ store = null;
+ }
+ if (repo != null) {
+ repo.close();
+ repo = null;
+ }
}
@Test
public void validKey() throws Exception {
- assertProblems(TestKey.key1());
- }
-
- @Test
- public void wrongKeyId() throws Exception {
- TestKey k = TestKey.key1();
- long badId = k.getKeyId() + 1;
- CheckResult result = checker.check(k.getPublicKey(), badId);
- assertEquals(
- Arrays.asList("Public key does not match ID 46328A8D"),
- result.getProblems());
+ assertNoProblems(validKeyWithoutExpiration());
}
@Test
public void keyExpiringInFuture() throws Exception {
- assertProblems(TestKey.key2());
+ TestKey k = validKeyWithExpiration();
+
+ PublicKeyChecker checker = new PublicKeyChecker()
+ .setStore(store);
+ assertNoProblems(checker, k);
+
+ checker.setEffectiveTime(parseDate("2015-07-10 12:00:00 -0400"));
+ assertNoProblems(checker, k);
+
+ checker.setEffectiveTime(parseDate("2075-07-10 12:00:00 -0400"));
+ assertProblems(checker, k, "Key is expired");
}
@Test
- public void expiredKey() throws Exception {
- assertProblems(TestKey.key3(), "Key is expired");
+ public void expiredKeyIsExpired() throws Exception {
+ assertProblems(expiredKey(), "Key is expired");
}
@Test
- public void selfRevokedKey() throws Exception {
- assertProblems(TestKey.key4(), "Key is revoked");
+ public void selfRevokedKeyIsRevoked() throws Exception {
+ assertProblems(selfRevokedKey(),
+ "Key is revoked (key material has been compromised)");
}
- private void assertProblems(TestKey tk, String... expected) throws Exception {
- CheckResult result = checker.check(tk.getPublicKey(), tk.getKeyId());
- assertEquals(Arrays.asList(expected), result.getProblems());
+ // Test keys specific to this test are at the bottom of this class. Each test
+ // has a diagram of the trust network, where:
+ // - The notation M---N indicates N trusts M.
+ // - An 'x' indicates the key is expired.
+
+ @Test
+ public void trustValidPathLength2() throws Exception {
+ // A---Bx
+ // \
+ // \---C---D
+ // \
+ // \---Ex
+ //
+ // D and E trust C to be a valid introducer of depth 2.
+ TestKey ka = add(keyA());
+ TestKey kb = add(keyB());
+ TestKey kc = add(keyC());
+ TestKey kd = add(keyD());
+ TestKey ke = add(keyE());
+ save();
+
+ PublicKeyChecker checker = newChecker(2, kb, kd);
+ assertNoProblems(checker, ka);
+ assertProblems(checker, kb, "Key is expired");
+ assertNoProblems(checker, kc);
+ assertNoProblems(checker, kd);
+ assertProblems(checker, ke, "Key is expired", "No path to a trusted key");
+ }
+
+ @Test
+ public void trustValidPathLength1() throws Exception {
+ // A---Bx
+ // \
+ // \---C---D
+ // \
+ // \---Ex
+ //
+ // D and E trust C to be a valid introducer of depth 2.
+ TestKey ka = add(keyA());
+ TestKey kb = add(keyB());
+ TestKey kc = add(keyC());
+ TestKey kd = add(keyD());
+ add(keyE());
+ save();
+
+ PublicKeyChecker checker = newChecker(1, kd);
+ assertProblems(checker, ka,
+ "No path to a trusted key", notTrusted(kb), notTrusted(kc));
+ }
+
+ @Test
+ public void trustCycle() throws Exception {
+ // F---G---F, in a cycle.
+ TestKey kf = add(keyF());
+ TestKey kg = add(keyG());
+ save();
+
+ PublicKeyChecker checker = newChecker(10, keyA());
+ assertProblems(checker, kf,
+ "No path to a trusted key", notTrusted(kg));
+ assertProblems(checker, kg,
+ "No path to a trusted key", notTrusted(kf));
+ }
+
+ @Test
+ public void trustInsufficientDepthInSignature() throws Exception {
+ // H---I---J, but J is only trusted to length 1.
+ TestKey kh = add(keyH());
+ TestKey ki = add(keyI());
+ add(keyJ());
+ save();
+
+ PublicKeyChecker checker = newChecker(10, keyJ());
+
+ // J trusts I to a depth of 1, so I itself is valid, but I's certification
+ // of K is not valid.
+ assertNoProblems(checker, ki);
+ assertProblems(checker, kh,
+ "No path to a trusted key", notTrusted(ki));
+ }
+
+ @Test
+ public void revokedKeyDueToCompromise() throws Exception {
+ TestKey k = add(revokedCompromisedKey());
+ add(validKeyWithoutExpiration());
+ save();
+
+ assertProblems(k,
+ "Key is revoked (key material has been compromised):"
+ + " test6 compromised");
+
+ PGPPublicKeyRing kr = removeRevokers(k.getPublicKeyRing());
+ store.add(kr);
+ save();
+
+ // Key no longer specified as revoker.
+ assertNoProblems(kr.getPublicKey());
+ }
+
+ @Test
+ public void revokedKeyDueToCompromiseRevokesKeyRetroactively()
+ throws Exception {
+ TestKey k = add(revokedCompromisedKey());
+ add(validKeyWithoutExpiration());
+ save();
+
+ String problem =
+ "Key is revoked (key material has been compromised): test6 compromised";
+ assertProblems(k, problem);
+
+ SimpleDateFormat df = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
+ PublicKeyChecker checker = new PublicKeyChecker()
+ .setStore(store)
+ .setEffectiveTime(df.parse("2010-01-01 12:00:00"));
+ assertProblems(checker, k, problem);
+ }
+
+ @Test
+ public void revokedByKeyNotPresentInStore() throws Exception {
+ TestKey k = add(revokedCompromisedKey());
+ save();
+
+ assertProblems(k,
+ "Key is revoked (key material has been compromised):"
+ + " test6 compromised");
+ }
+
+ @Test
+ public void revokedKeyDueToNoLongerBeingUsed() throws Exception {
+ TestKey k = add(revokedNoLongerUsedKey());
+ add(validKeyWithoutExpiration());
+ save();
+
+ assertProblems(k,
+ "Key is revoked (retired and no longer valid): test7 not used");
+ }
+
+ @Test
+ public void revokedKeyDueToNoLongerBeingUsedDoesNotRevokeKeyRetroactively()
+ throws Exception {
+ TestKey k = add(revokedNoLongerUsedKey());
+ add(validKeyWithoutExpiration());
+ save();
+
+ assertProblems(k,
+ "Key is revoked (retired and no longer valid): test7 not used");
+
+ PublicKeyChecker checker = new PublicKeyChecker()
+ .setStore(store)
+ .setEffectiveTime(parseDate("2010-01-01 12:00:00 -0400"));
+ assertNoProblems(checker, k);
+ }
+
+ @Test
+ public void keyRevokedByExpiredKeyAfterExpirationIsNotRevoked()
+ throws Exception {
+ TestKey k = add(keyRevokedByExpiredKeyAfterExpiration());
+ add(expiredKey());
+ save();
+
+ PublicKeyChecker checker = new PublicKeyChecker().setStore(store);
+ assertNoProblems(checker, k);
+ }
+
+ @Test
+ public void keyRevokedByExpiredKeyBeforeExpirationIsRevoked()
+ throws Exception {
+ TestKey k = add(keyRevokedByExpiredKeyBeforeExpiration());
+ add(expiredKey());
+ save();
+
+ PublicKeyChecker checker = new PublicKeyChecker().setStore(store);
+ assertProblems(checker, k,
+ "Key is revoked (retired and no longer valid): test9 not used");
+
+ // Set time between key creation and revocation.
+ checker.setEffectiveTime(parseDate("2005-08-01 13:00:00 -0400"));
+ assertNoProblems(checker, k);
+ }
+
+ private PGPPublicKeyRing removeRevokers(PGPPublicKeyRing kr) {
+ PGPPublicKey k = kr.getPublicKey();
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> sigs = k.getSignaturesOfType(DIRECT_KEY);
+ while (sigs.hasNext()) {
+ PGPSignature sig = sigs.next();
+ if (sig.getHashedSubPackets().hasSubpacket(REVOCATION_KEY)) {
+ k = PGPPublicKey.removeCertification(k, sig);
+ }
+ }
+ return PGPPublicKeyRing.insertPublicKey(kr, k);
+ }
+
+ private PublicKeyChecker newChecker(int maxTrustDepth, TestKey... trusted) {
+ Map<Long, Fingerprint> fps = new HashMap<>();
+ for (TestKey k : trusted) {
+ Fingerprint fp = new Fingerprint(k.getPublicKey().getFingerprint());
+ fps.put(fp.getId(), fp);
+ }
+ return new PublicKeyChecker()
+ .enableTrust(maxTrustDepth, fps)
+ .setStore(store);
+ }
+
+ private TestKey add(TestKey k) {
+ store.add(k.getPublicKeyRing());
+ return k;
+ }
+
+ private void save() throws Exception {
+ PersonIdent ident = new PersonIdent("A U Thor", "author@example.com");
+ CommitBuilder cb = new CommitBuilder();
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ RefUpdate.Result result = store.save(cb);
+ switch (result) {
+ case NEW:
+ case FAST_FORWARD:
+ case FORCED:
+ break;
+ default:
+ throw new AssertionError(result);
+ }
+ }
+
+ private void assertProblems(PublicKeyChecker checker, TestKey k,
+ String first, String... rest) {
+ CheckResult result = checker.setStore(store)
+ .check(k.getPublicKey());
+ assertEquals(list(first, rest), result.getProblems());
+ }
+
+ private void assertNoProblems(PublicKeyChecker checker, TestKey k) {
+ CheckResult result = checker.setStore(store)
+ .check(k.getPublicKey());
+ assertEquals(Collections.emptyList(), result.getProblems());
+ }
+
+ private void assertProblems(TestKey tk, String first, String... rest) {
+ assertProblems(tk.getPublicKey(), first, rest);
+ }
+
+ private void assertNoProblems(TestKey tk) {
+ assertNoProblems(tk.getPublicKey());
+ }
+
+ private void assertProblems(PGPPublicKey k, String first, String... rest) {
+ CheckResult result = new PublicKeyChecker()
+ .setStore(store)
+ .check(k);
+ assertEquals(list(first, rest), result.getProblems());
+ }
+
+ private void assertNoProblems(PGPPublicKey k) {
+ CheckResult result = new PublicKeyChecker()
+ .setStore(store)
+ .check(k);
+ assertEquals(Collections.emptyList(), result.getProblems());
+ }
+
+ private static String notTrusted(TestKey k) {
+ return "Certification by " + keyToString(k.getPublicKey())
+ + " is valid, but key is not trusted";
+ }
+
+ private static Date parseDate(String str) throws Exception {
+ return new SimpleDateFormat("YYYY-MM-dd HH:mm:ss Z").parse(str);
+ }
+
+ private static List<String> list(String first, String[] rest) {
+ List<String> all = new ArrayList<>();
+ all.add(first);
+ all.addAll(Arrays.asList(rest));
+ return all;
}
}
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java
index d936a31..9c0a908 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PublicKeyStoreTest.java
@@ -18,6 +18,9 @@
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.gpg.PublicKeyStore.keyObjectId;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithSecondUserId;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,13 +63,13 @@
@Test
public void testKeyIdToString() throws Exception {
- PGPPublicKey key = TestKey.key1().getPublicKey();
+ PGPPublicKey key = validKeyWithoutExpiration().getPublicKey();
assertEquals("46328A8C", keyIdToString(key.getKeyID()));
}
@Test
public void testKeyToString() throws Exception {
- PGPPublicKey key = TestKey.key1().getPublicKey();
+ PGPPublicKey key = validKeyWithoutExpiration().getPublicKey();
assertEquals("46328A8C Testuser One <test1@example.com>"
+ " (04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C)",
keyToString(key));
@@ -74,7 +77,7 @@
@Test
public void testKeyObjectId() throws Exception {
- PGPPublicKey key = TestKey.key1().getPublicKey();
+ PGPPublicKey key = validKeyWithoutExpiration().getPublicKey();
String objId = keyObjectId(key.getKeyID()).name();
assertEquals("ed0625dc46328a8c000000000000000000000000", objId);
assertEquals(keyIdToString(key.getKeyID()).toLowerCase(),
@@ -83,13 +86,13 @@
@Test
public void testGet() throws Exception {
- TestKey key1 = TestKey.key1();
+ TestKey key1 = validKeyWithoutExpiration();
tr.branch(REFS_GPG_KEYS)
.commit()
.add(keyObjectId(key1.getKeyId()).name(),
key1.getPublicKeyArmored())
.create();
- TestKey key2 = TestKey.key2();
+ TestKey key2 = validKeyWithExpiration();
tr.branch(REFS_GPG_KEYS)
.commit()
.add(keyObjectId(key2.getKeyId()).name(),
@@ -102,8 +105,8 @@
@Test
public void testGetMultiple() throws Exception {
- TestKey key1 = TestKey.key1();
- TestKey key2 = TestKey.key2();
+ TestKey key1 = validKeyWithoutExpiration();
+ TestKey key2 = validKeyWithExpiration();
tr.branch(REFS_GPG_KEYS)
.commit()
.add(keyObjectId(key1.getKeyId()).name(),
@@ -116,8 +119,8 @@
@Test
public void save() throws Exception {
- TestKey key1 = TestKey.key1();
- TestKey key2 = TestKey.key2();
+ TestKey key1 = validKeyWithoutExpiration();
+ TestKey key2 = validKeyWithExpiration();
store.add(key1.getPublicKeyRing());
store.add(key2.getPublicKeyRing());
@@ -129,8 +132,8 @@
@Test
public void saveAppendsToExistingList() throws Exception {
- TestKey key1 = TestKey.key1();
- TestKey key2 = TestKey.key2();
+ TestKey key1 = validKeyWithoutExpiration();
+ TestKey key2 = validKeyWithExpiration();
tr.branch(REFS_GPG_KEYS)
.commit()
// Mismatched for this key ID, but we can still read it out.
@@ -160,7 +163,7 @@
@Test
public void updateExisting() throws Exception {
- TestKey key5 = TestKey.key5();
+ TestKey key5 = validKeyWithSecondUserId();
PGPPublicKeyRing keyRing = key5.getPublicKeyRing();
PGPPublicKey key = keyRing.getPublicKey();
store.add(keyRing);
@@ -184,7 +187,7 @@
@Test
public void remove() throws Exception {
- TestKey key1 = TestKey.key1();
+ TestKey key1 = validKeyWithoutExpiration();
store.add(key1.getPublicKeyRing());
assertEquals(RefUpdate.Result.NEW, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId(), key1);
@@ -196,11 +199,11 @@
@Test
public void removeNonexisting() throws Exception {
- TestKey key1 = TestKey.key1();
+ TestKey key1 = validKeyWithoutExpiration();
store.add(key1.getPublicKeyRing());
assertEquals(RefUpdate.Result.NEW, store.save(newCommitBuilder()));
- TestKey key2 = TestKey.key2();
+ TestKey key2 = validKeyWithExpiration();
store.remove(key2.getPublicKey().getFingerprint());
assertEquals(RefUpdate.Result.NO_CHANGE, store.save(newCommitBuilder()));
assertKeys(key1.getKeyId(), key1);
@@ -208,7 +211,7 @@
@Test
public void addThenRemove() throws Exception {
- TestKey key1 = TestKey.key1();
+ TestKey key1 = validKeyWithoutExpiration();
store.add(key1.getPublicKeyRing());
store.remove(key1.getPublicKey().getFingerprint());
assertEquals(RefUpdate.Result.NO_CHANGE, store.save(newCommitBuilder()));
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PushCertificateCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PushCertificateCheckerTest.java
index 8a633ae..ee07d55 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PushCertificateCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/PushCertificateCheckerTest.java
@@ -14,9 +14,11 @@
package com.google.gerrit.gpg;
-import static com.google.gerrit.gpg.PublicKeyStore.REFS_GPG_KEYS;
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
+import static com.google.gerrit.gpg.testutil.TestKeys.expiredKey;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithExpiration;
+import static com.google.gerrit.gpg.testutil.TestKeys.validKeyWithoutExpiration;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
@@ -26,11 +28,14 @@
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
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.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushCertificate;
import org.eclipse.jgit.transport.PushCertificateIdent;
@@ -43,57 +48,80 @@
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
public class PushCertificateCheckerTest {
- private TestRepository<?> tr;
+ private InMemoryRepository repo;
+ private PublicKeyStore store;
private SignedPushConfig signedPushConfig;
private PushCertificateChecker checker;
@Before
public void setUp() throws Exception {
- TestKey key1 = TestKey.key1();
- TestKey key3 = TestKey.key3();
- tr = new TestRepository<>(new InMemoryRepository(
- new DfsRepositoryDescription("repo")));
- tr.branch(REFS_GPG_KEYS).commit()
- .add(PublicKeyStore.keyObjectId(key1.getPublicKey().getKeyID()).name(),
- key1.getPublicKeyArmored())
- .add(PublicKeyStore.keyObjectId(key3.getPublicKey().getKeyID()).name(),
- key3.getPublicKeyArmored())
- .create();
+ TestKey key1 = validKeyWithoutExpiration();
+ TestKey key3 = expiredKey();
+ repo = new InMemoryRepository(new DfsRepositoryDescription("repo"));
+ store = new PublicKeyStore(repo);
+ store.add(key1.getPublicKeyRing());
+ store.add(key3.getPublicKeyRing());
+
+ PersonIdent ident = new PersonIdent("A U Thor", "author@example.com");
+ CommitBuilder cb = new CommitBuilder();
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ assertEquals(RefUpdate.Result.NEW, store.save(cb));
+
signedPushConfig = new SignedPushConfig();
signedPushConfig.setCertNonceSeed("sekret");
signedPushConfig.setCertNonceSlopLimit(60 * 24);
+ checker = newChecker(true);
+ }
- checker = new PushCertificateChecker(new PublicKeyChecker()) {
+ private PushCertificateChecker newChecker(boolean checkNonce) {
+ PublicKeyChecker keyChecker = new PublicKeyChecker().setStore(store);
+ return new PushCertificateChecker(keyChecker) {
@Override
protected Repository getRepository() {
- return tr.getRepository();
+ return repo;
}
@Override
protected boolean shouldClose(Repository repo) {
return false;
}
- };
+ }.setCheckNonce(checkNonce);
}
@Test
public void validCert() throws Exception {
- PushCertificate cert = newSignedCert(validNonce(), TestKey.key1());
- assertProblems(cert);
+ PushCertificate cert =
+ newSignedCert(validNonce(), validKeyWithoutExpiration());
+ assertNoProblems(cert);
}
@Test
public void invalidNonce() throws Exception {
- PushCertificate cert = newSignedCert("invalid-nonce", TestKey.key1());
+ PushCertificate cert =
+ newSignedCert("invalid-nonce", validKeyWithoutExpiration());
assertProblems(cert, "Invalid nonce");
}
@Test
+ public void invalidNonceNotChecked() throws Exception {
+ checker = newChecker(false);
+ PushCertificate cert =
+ newSignedCert("invalid-nonce", validKeyWithoutExpiration());
+ assertNoProblems(cert);
+ }
+
+ @Test
public void missingKey() throws Exception {
- TestKey key2 = TestKey.key2();
+ TestKey key2 = validKeyWithExpiration();
PushCertificate cert = newSignedCert(validNonce(), key2);
assertProblems(cert,
"No public keys found for key ID " + keyIdToString(key2.getKeyId()));
@@ -101,20 +129,34 @@
@Test
public void invalidKey() throws Exception {
- TestKey key3 = TestKey.key3();
+ TestKey key3 = expiredKey();
PushCertificate cert = newSignedCert(validNonce(), key3);
assertProblems(cert,
"Invalid public key " + keyToString(key3.getPublicKey())
+ ":\n Key is expired");
}
+ @Test
+ public void signatureByExpiredKeyBeforeExpiration() throws Exception {
+ TestKey key3 = expiredKey();
+ Date now = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss Z")
+ .parse("2005-07-10 12:00:00 -0400");
+ PushCertificate cert = newSignedCert(validNonce(), key3, now);
+ assertNoProblems(cert);
+ }
+
private String validNonce() {
return signedPushConfig.getNonceGenerator()
- .createNonce(tr.getRepository(), System.currentTimeMillis() / 1000);
+ .createNonce(repo, System.currentTimeMillis() / 1000);
}
private PushCertificate newSignedCert(String nonce, TestKey signingKey)
throws Exception {
+ return newSignedCert(nonce, signingKey, null);
+ }
+
+ private PushCertificate newSignedCert(String nonce, TestKey signingKey,
+ Date now) throws Exception {
PushCertificateIdent ident = new PushCertificateIdent(
signingKey.getFirstUserId(), System.currentTimeMillis(), -7 * 60);
String payload = "certificate version 0.1\n"
@@ -128,6 +170,14 @@
PGPSignatureGenerator gen = new PGPSignatureGenerator(
new BcPGPContentSignerBuilder(
signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1));
+
+ if (now != null) {
+ PGPSignatureSubpacketGenerator subGen =
+ new PGPSignatureSubpacketGenerator();
+ subGen.setSignatureCreationTime(false, now);
+ gen.setHashedSubpackets(subGen.generate());
+ }
+
gen.init(PGPSignature.BINARY_DOCUMENT, signingKey.getPrivateKey());
gen.update(payload.getBytes(UTF_8));
PGPSignature sig = gen.generate();
@@ -142,13 +192,21 @@
Reader reader =
new InputStreamReader(new ByteArrayInputStream(cert.getBytes(UTF_8)));
PushCertificateParser parser =
- new PushCertificateParser(tr.getRepository(), signedPushConfig);
+ new PushCertificateParser(repo, signedPushConfig);
return parser.parse(reader);
}
- private void assertProblems(PushCertificate cert, String... expected)
- throws Exception {
- CheckResult result = checker.check(cert);
- assertEquals(Arrays.asList(expected), result.getProblems());
+ private void assertProblems(PushCertificate cert, String first,
+ String... rest) throws Exception {
+ List<String> expected = new ArrayList<>();
+ expected.add(first);
+ expected.addAll(Arrays.asList(rest));
+ CheckResult result = checker.check(cert).getCheckResult();
+ assertEquals(expected, result.getProblems());
+ }
+
+ private void assertNoProblems(PushCertificate cert) {
+ CheckResult result = checker.check(cert).getCheckResult();
+ assertEquals(Collections.emptyList(), result.getProblems());
}
}
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java
index 614818e..494cb2d 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKey.java
@@ -16,8 +16,6 @@
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
-import com.google.common.collect.ImmutableList;
-
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
@@ -34,536 +32,12 @@
import java.io.IOException;
public class TestKey {
- public static ImmutableList<TestKey> allValidKeys() {
- return ImmutableList.of(key1(), key2(), key5());
- }
-
- /**
- * A valid key with no expiration.
- *
- * <pre>
- * pub 2048R/46328A8C 2015-07-08
- * Key fingerprint = 04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C
- * uid Testuser One <test1@example.com>
- * sub 2048R/F0AF69C0 2015-07-08
- * </pre>
- */
- public static TestKey key1() {
- return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "mQENBFWdTIkBCADOaygDKjLuRX6LXAvBAYB91cmTf1MSlmEy+qsG3c9ijjQixPkr\n"
- + "atdYkocrrT2S0R9UGjksTOI2WN5S0lQfLA1RSk63KURQE+OF+IfNqdD6nQdLBs1w\n"
- + "va+GDj/uvuI05I0oXf/M7POdFphutrS4EUDBnFPj6ns/0C2sTRTxliD+Y9Y9a84V\n"
- + "DfVVUbJB6wc3LP3L6ImT+cSM7dLq3hZHya+9FNeYPmPYnBrkJyqf2NDd38Sddsro\n"
- + "7smw/GgCZHnnuVNS4C7NsHr6900VKC+JDtdx+fqptixcAEJWiGoQfWqU+hYmia3p\n"
- + "9+Xw02+3FcjOT6ONUCmHX+xlz0pXW4iIYlPpABEBAAG0IFRlc3R1c2VyIE9uZSA8\n"
- + "dGVzdDFAZXhhbXBsZS5jb20+iQE4BBMBAgAiBQJVnUyJAhsDBgsJCAcDAgYVCAIJ\n"
- + "CgsEFgIDAQIeAQIXgAAKCRDtBiXcRjKKjHblB/9RaFO5+GTDIphAL/aVj2u+d8Lq\n"
- + "yUpBrDp3P06QDGpKGFMAovBuh+NLH76VKNIzQLQC8rdTj651fLcLMuJ1enQ3Rblg\n"
- + "RKr1oc+wqqtFHr4QyOQjE/N3C9GQjEzfqn4qnp5KtZxYFnlvU5NGehid7M1HTZMx\n"
- + "jRcHbM9KQnsE5Z4fh4wmN5ynG+5nbaF4O9otPOpFzYRvIhxFmHscWyOgRaMZiYEX\n"
- + "7Qkzze+scAlc9E/EWRJQIFcxnxV/SYIT4qCTT1g2aKA8OCBO/ZTOleH8SzvTODjy\n"
- + "W0lGHnh/ZqH6XGVcGUaJZZ2uHTck1+czuVVShNcXPW1W20T6E9UqzHbJHN0guQEN\n"
- + "BFWdTIkBCACoLVdPr3gpQwzI+2NGXjdtoyqYoPlgfeyI2M1XQD/7+rLZTbi14ZjN\n"
- + "vYkS/+/oGtVEmiYOiAVTwmkjCYkKGDgNcCiJVekiPAN6JryVv488wRc999b5LpFE\n"
- + "fhLGwI0YxjcS4KFFnpMC3wSb6tJUnHRLVoE5d8icdiaOpgYdp7uqWkSx2oxqHgIb\n"
- + "nuyrk3ydEcS4ZeGD+w+taIxMc9F1DS9kiXALD7xWgUkmqZLEQoNgF6KlwCHXRd3m\n"
- + "rBCo97sE95yKcq98ZMIWuQtTcEccZsN/6jlsei+9RI0tqs+FbZnIFm/go9zk11Vl\n"
- + "IQ9QFSj6ruqoKrYvNZuDDLD1lHvZPD4/ABEBAAGJAR8EGAECAAkFAlWdTIkCGwwA\n"
- + "CgkQ7QYl3EYyiox+HAf/Z/OCQO3jxALAcn3oUb1g/IlHm6qZv7RJOFUsj/16fGiF\n"
- + "rRTP15zMXzyqV+L/LGV/owvOsdD/o7boZz4C/U98COx0Nl1jOrmPATOl+xqsgpEj\n"
- + "Fhk+eAR7exO2XxW+u2g4cYoSMosIOX5w1GrdsxQeaZDwiSJMEOR2cVLs3YI19Ci/\n"
- + "FuzActZ0wJNk0nlNF6l8CAbzwN6pM9OIc/iBIwDjz92KUco0NF8XKZnxqhH4wfHB\n"
- + "PGkTx8RwOvELUTDMtvYnG5R0QtND0RbOnmp4ZVZmeOjKSLo1mZliUZB1H2PPSxrA\n"
- + "0oLr8+wLntz1SU7uS4ddvhSQW+j2M/0pa352KUwmrw==\n"
- + "=o/aU\n"
- + "-----END PGP PUBLIC KEY BLOCK-----\n",
- "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "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\n"
- + "=MuAn\n"
- + "-----END PGP PRIVATE KEY BLOCK-----\n");
- }
-
- /**
- * A valid key expiring in 2065.
- *
- * <pre>
- * pub 2048R/378A0AED 2015-07-08 [expires: 2065-06-25]
- * Key fingerprint = C378 369A CBCD 34CC 138D 90B1 4531 1A6F 378A 0AED
- * uid Testuser Two <test2@example.com>
- * sub 2048R/46D4F204 2015-07-08 [expires: 2065-06-25]
- * </pre>
- */
- public static final TestKey key2() {
- return new TestKey(
- "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "mQENBFWdTP8BCADRxNpasIv0jtNXTK6VYIS2VJ2Xk0ZD6gtxeoXCpjQ+TsB9fxh3\n"
- + "vAMPt2Zu5LqoGwygKOJj1zquG8xk7GUCCHJk3+qG8xxB1xGtSz2vLyfRm7fOZmHj\n"
- + "3W/C/25lynSPDrfvcwvwA4PN8iP5EWbWU10L6WOZGMwwwtVDUSEouSOw2LEepxLV\n"
- + "rkKuZcyHaivheDbUlZliwe9rGXd4hh1h4qyNQWG3q+ytlL28sVkOzUh6IMBTvqhe\n"
- + "IRsvxvaVSLV8jRVKfUTqw0g57ft4ZD2/L46yUTXzr9aUCBjTNxvWLlyboqql/D8P\n"
- + "inp51h3cvAg7NW5RdG1GEYmylH8SygT5utPxABEBAAG0IFRlc3R1c2VyIFR3byA8\n"
- + "dGVzdDJAZXhhbXBsZS5jb20+iQE+BBMBAgAoBQJVnUz/AhsDBQld/A8ABgsJCAcD\n"
- + "AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBFMRpvN4oK7UZqCACWwQL/YvBK4b0m+R0d\n"
- + "UdvAXeBx7DwOAnAodis9ZVqChb7RxcZQxF1Ti9mtCBPPQGuEs5wE2Ocrrq+L13r6\n"
- + "bgW+1WOB1tZSDVxwL1PnZFw/SyADRIDCZrOHiAkp82UnZwWAkk39GzNJtt1wTYDZ\n"
- + "FMTFUr2SPscXk1k7muS+ZfEFwNPD4tODo/poJKDYEJ80Z5UXXFQLDtsfdeIXMFIT\n"
- + "449CYoq8XBMBfvyWl/LLpw0r3JI6pV/YdH3Oeuz8XkkEVzRxaxB6Zmeo5jSwjR/T\n"
- + "8TKDGwwiuwiiT3SfkFSVdcjKulRuXSRNs1Ouf7/UC3cq4bG2WXWa85X1+HQRm7iu\n"
- + "RHSOuQENBFWdTP8BCADhhGxAA0pX5yBHwIgM1j0gw2h5nSsopDrO6t/sbRUcNxnR\n"
- + "tBScgKZnP0sjRTYEUIwmZuseHMBohtVCuMaDt06qyZDvDk/98j3AeE5t2dgFnOIe\n"
- + "qCrm/6aejbFcQOpxe6U29KJRCAxuwNtB15X1VH1Kj7B0gRSTu13n/5sUsi2lunoZ\n"
- + "oIvpIe9tZH4aXitCY2MCQH+hTyCyNBzlEa44kWz6LxUsPdo7I6rXkTr6Ot7wQh+9\n"
- + "7HCe042GIq65h0apgujyjhJidjch5ur1mngaSNSEyvbji2MGC+cd3wAIstG5a7xP\n"
- + "d9MncY5Q/eH+hn96694k5bckottSyGm/3f2Ihfj1ABEBAAGJASUEGAECAA8FAlWd\n"
- + "TP8CGwwFCV38DwAACgkQRTEabzeKCu1FNwgAif4eK2v7R3QubL2S6wmb1nsgRMgV\n"
- + "YoxGBeUk2EK6WZ5IPor93ySd0ixRVNMRmJ8BLH3EMjZQTzkDG+BH6zFyxo6lLHw9\n"
- + "NxQjI06tqQWgyyK0mEweVwB/zqtxiB4lNUpsNbqOZWnBJ3d6o1SsnD2Q3uwvP5fb\n"
- + "fSIgdmUk3c0VMdgA+KzWjPD/PJIPujE+ckHhjn5cbDNw35/FuyhkLJfqlOG7SPvM\n"
- + "NmCdJ1Pcqju9t7sf6b0BGPDOCL4gpuWKK7HJz9WxngNb3FSziLbyPLk13ynADO+v\n"
- + "EOR44LPyXE9kVxPusazsXlt9ayTOhELhwzw7sGFFu8E17Cpn7GnVj3tN9A==\n"
- + "=1e/A\n"
- + "-----END PGP PUBLIC KEY BLOCK-----\n",
- "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "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\n"
- + "9A==\n"
- + "=qbV3\n"
- + "-----END PGP PRIVATE KEY BLOCK-----\n");
- }
-
- /**
- * A key that expired in 2006.
- *
- * <pre>
- * pub 2048R/17DE1ACD 2005-07-08 [expired: 2006-07-08]
- * Key fingerprint = 1D9E EB79 DD38 B049 939D 9CAF 3CEC 781B 17DE 1ACD
- * uid Testuser Three <test3@example.com>
- * </pre>
- */
- public static final TestKey key3() {
- return new TestKey(
- "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "mQENBELOp0UBCACxholOPWuKhK+TYb88nvLUSCMvTLIFEpb5u3Eavr0wiluEzq6H\n"
- + "55nswAD3dQm8DWxA7yUlEYjPr5btpw7V9441bb1+qtgZMJ10RTdEb/WjyctdGA99\n"
- + "uOKBEarWbt8W+w6lyJ9NXy5bS/x5EwHHfoTFp4ff6ffHI5hbx1a00K8oxmitgd0X\n"
- + "Mx86UmauFNJYupZOZG9gEcP4RbRp7e2pm4Jy1WLEOeg9Fdgm5e5Hj2nMkCSZ9BKV\n"
- + "cxuOllSVzM/Zp0/4+RS9R57jKo3/V74Whwh9yQNgL9UxdNk7L0eGqvaT3EjXxjOc\n"
- + "RCeJiucGN/0W2iq+V01/QGspp4SKtAogWBozABEBAAG0IlRlc3R1c2VyIFRocmVl\n"
- + "IDx0ZXN0M0BleGFtcGxlLmNvbT6JAT4EEwECACgFAkLOp0UCGwMFCQHhM4AGCwkI\n"
- + "BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEDzseBsX3hrNYg0H/2CMm5/JDQNSuRFC\n"
- + "ECWLrcOeimuvwbmkonNzOkvKbGXl73GStISAksRWAHBQED1rEPC0NkFCDeVZO7df\n"
- + "SYLlsqKwV6uSh05Ra0F5XeniC12YpAyzoQyCGRS2wLaS822j0zUPXA8XLaO2blCu\n"
- + "R+8sNu/oecMRcFK4S9NaApi3vdqBNhLiN1/Lpqn1LfB8uIO+eaUf4PmCWbaPgzSk\n"
- + "qcPfKZmocNXdgLV5Q80n3hc2y2nrl+vDW2M+eVZuDHAok2BOD9uGKFfLAbaXLbX5\n"
- + "btBW2L0UHtoEyiqkRfD6lX2laSLQmA6+eup7e4GS+s0vXBuVh8XEYddV6Yjt8H7/\n"
- + "2thO41K5AQ0EQs6nRQEIAM/833UHK1DuFlOm7/n18dRMvs7BkXvg+hPquKWMG3be\n"
- + "eE4sh1NG5DbRCdo6iacZLarWr3FDz7J9+wswRhtHCh3pGHEuaJk52vRjQxlkNh5F\n"
- + "p5u2R4WF546bWqX45xPdLfHVTPyWB9q7aVxE+6Q+MHa6lMoyTVnTVCOy3nshiihw\n"
- + "dxLsxaga+QmaL0bAR+dRcO6ucj7TDQXz1AJAVp26c0LXV9iErhFuuybUZKT0a9Aj\n"
- + "FoumMZ6l+k30sSdjSjpBMsNvPos0dTPPRXUMu77o5sj+pHa4o8WctgB3o7BHQELp\n"
- + "KgujZ2sKC9Nm395u6Q4cqUWihzb/Y7rIRuNHJarI7vUAEQEAAYkBJQQYAQIADwUC\n"
- + "Qs6nRQIbDAUJAeEzgAAKCRA87HgbF94azRiBB/4vAyOOjUjK3lDWjHGs7mvEWJI/\n"
- + "1MeLlGPswCSInJBa+HMiMI4tzq+hu5ejGThojNbmnL96GdzfDkMlP4Feyxb2rjtb\n"
- + "NrD/R5tlXHmjX/QLzep4LCeMziP80fu8qUeiOej/Ecdny0w365PlMdt10RaYR8VE\n"
- + "ZX/DAie6JfElnfQcG5q8TIOH3i71qxV+kIoPqKWfQ0MXrNEJ3BYFfDGdUt8U1Kq9\n"
- + "OuIHVRgGS7mMSyjgNqqp7MBeMY+PFFZaZel5yoYVjb9d3L8XvVv2eoa/jPj5FUEU\n"
- + "kE9uxNmwaD1PiV8DvBTYI+eQL4qzfu+3NTG2SfgQYtj5oiGHw8aL3U6QHDJb\n"
- + "=d/Xp\n"
- + "-----END PGP PUBLIC KEY BLOCK-----\n",
- "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "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\n"
- + "HDJb\n"
- + "=RrXv\n"
- + "-----END PGP PRIVATE KEY BLOCK-----\n");
- }
-
- /**
- * A self-revoked key with no expiration.
- *
- * <pre>
- * pub 2048R/7CA87821 2015-07-08 [revoked: 2015-07-08]
- * Key fingerprint = E328 CAB1 1F7E B1BC 1451 ABA5 0855 2A17 7CA8 7821
- * uid Testuser Four <test4@example.com>
- * </pre>
- */
- public static final TestKey key4() {
- return new TestKey(
- "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "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\n"
- + "=477N\n"
- + "-----END PGP PUBLIC KEY BLOCK-----\n",
- "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "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\n"
- + "=5aNq\n"
- + "-----END PGP PRIVATE KEY BLOCK-----\n");
- }
-
- /**
- * A key with an additional user ID.
- *
- * <pre>
- * pub 2048R/98C51DBF 2015-07-30
- * Key fingerprint = 42B3 294D 1924 D7EB AF4A A99F 5024 BB44 98C5 1DBF
- * uid foo:myId
- * uid Testuser Five <test5@example.com>
- * sub 2048R/C781A9E3 2015-07-30
- * </pre>
- */
- public static TestKey key5() {
- return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "mQENBFW6jd4BCACrf+BZS3lntuWq2DWPOG07/BUWhx3RSoiS3JBuKEDmlsjswKcp\n"
- + "JHT+p2tqH52XbujMlzNjAQcZjJwfOMt6Fg7zd3F8cwYQdCE/W5dpMs/mqdeEz6GL\n"
- + "VJDZ0Y5wwz54ZQHp91Xq6uejxt5qffeTQk5cToQZ0RVx3iwBc+2P3iYJqMFmJzj8\n"
- + "djabEoF4D50iI5tY8moE83VcXJ5Y4xn+5Z5AThmlfrMP6gIdG0b4lEe1tsnJC6AG\n"
- + "GUU6VkzK6E1Tp93Y0brtWpJKi9Gt6eUqvWhZtPEdFVCFbLTpezUdRFEuaFbGg5pn\n"
- + "9K/DceahFmquDJOHVgawt6erlq/ie7QEEld/ABEBAAG0IVRlc3R1c2VyIEZpdmUg\n"
- + "PHRlc3Q1QGV4YW1wbGUuY29tPokBOAQTAQIAIgUCVbqN3gIbAwYLCQgHAwIGFQgC\n"
- + "CQoLBBYCAwECHgECF4AACgkQUCS7RJjFHb+/MAf9FKZatGcuOIoYqwGQQneyc63v\n"
- + "3H/PyhvYF1nuKNftmhqIiUHec9RaUHQkgam6LRoonkDfIpNlQVRv2XBV2VOAOFVO\n"
- + "RyQ/Tv7/xtpqGZqivV0yn2ZXbCceA627Vz7gP4gkO0ZJ0JsYJTc/5wO+nVG5Lohu\n"
- + "/zdUofEbFAvcXs+Z1uXnUDdeGn47Lf1xZ2XOHOI0aQW4DdNaFoAd+AOTe0W3iB6W\n"
- + "paCIGno69CyNHNnWjJCSD33oLVaXyvbgw5UoyITvSqRnPyLGIc6dsqDLT59ok0Fk\n"
- + "t4jtiGu9aze4n59GbtSjmWQgzbLCQWhK9K7UCcSLYNKXVyMha2WapBO156V027QI\n"
- + "Zm9vOm15SWSJATgEEwECACIFAlW6jwYCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B\n"
- + "AheAAAoJEFAku0SYxR2/zZUH/1BwPsResHLDSmo6UdQyQGxvV0NcwBqGAPSLHr+S\n"
- + "PHEaHEIYvOywNfWXquYrECa/5iIrXuTQmCH0q8WRcz1UapDCeD8Ui82r+3O8m6gk\n"
- + "hIR5VAeza+x/fGWhG342PvtpDU7JycDA3KMCTWtcAM89tFhffzuEQ3f5p5cMTtZk\n"
- + "/23iegXbHd61vojYO17QYEj+qp9l0VNiyFymPL3qr5bVj/xn/mXFj+asj0L2ypIj\n"
- + "zC36FkhzW5EX2xgV9Cl9zu7kLMTm+yM+jxbMLskYkG8z/D+xBQsoX8tEIPlxHLhB\n"
- + "miEmVuZrp91ArRMWa3B7PYz7hQzs+M/bxKXcmWxacggTOvy5AQ0EVbqN3gEIAOlq\n"
- + "mwdiXW0BQP/iQvIweP1taNypAvdjI2fpnXkUfBT5X/+E/RjYOHQEAzy8nEkS+Y0l\n"
- + "MLwKt3S0IVRvdeXxlpL6Tl+P8DkcD5H+uvACrg9rtgbbNSoQtc9/3bknG9hea6xi\n"
- + "6SBH1k9Y2RInIrwWslfKmuNkyZVhxPKypasBsvyhOWLlpCngGiCa74KJ1th1WKa2\n"
- + "aaDqcbieBTc1mtsXR6kBhJZqK+JYBoHriUQMs7nyXxn2qyv6Lehs/tHlrBZ7j16S\n"
- + "faQzYoBi1edVrpFr/CuGk6RNKxG9vi/uAA9q2cLCMjjyfMH4g0G2l0HuDPQLA9wi\n"
- + "BfusEC+OceaeFKtS9ykAEQEAAYkBHwQYAQIACQUCVbqN3gIbDAAKCRBQJLtEmMUd\n"
- + "vw/DB/9Qx9m1eSdddqz/fk16wJf7Ncr2teVvdQOjRf/qo43KDKxEzeepjgypG1br\n"
- + "St7U4/MlPygJLBDB4pXp0kaKt+S/aqLpEGSGzQ1FysM8oY6K0e1Kbf6nMaQS8ATG\n"
- + "aD377FrUJ42NV4JS+NGlwaM9PhpRVm5n8iCzRs9HtlTyfCBkNGDjGOSdWcah2m6T\n"
- + "fEQdD+XVDN1ZC8zAnc8FW28YOTeTjX079okP6ZCjLJ16VZ7eiHFkrNbS9Dl4SPNK\n"
- + "eElvsZLBaf8t4RQXFFKwRq4BW+zS8zm9E2H6bZ9yGrmgIREzyRPpwU98g8yrabu0\n"
- + "54w16Vp/SVViJs7nTMSug0WREyd2\n"
- + "=ldwB\n"
- + "-----END PGP PUBLIC KEY BLOCK-----\n",
- "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "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\n"
- + "=uND5\n"
- + "-----END PGP PRIVATE KEY BLOCK-----\n");
- }
-
- // TODO(dborowitz): Figure out how to get gpg to revoke a key for someone
- // else.
-
private final String pubArmored;
private final String secArmored;
private final PGPPublicKeyRing pubRing;
private final PGPSecretKeyRing secRing;
- private TestKey(String pubArmored, String secArmored) {
+ public TestKey(String pubArmored, String secArmored) {
this.pubArmored = pubArmored;
this.secArmored = secArmored;
BcKeyFingerprintCalculator fc = new BcKeyFingerprintCalculator();
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKeys.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKeys.java
new file mode 100644
index 0000000..ad944c5
--- /dev/null
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestKeys.java
@@ -0,0 +1,1028 @@
+// Copyright (C) 2015 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.gpg.testutil;
+
+import com.google.common.collect.ImmutableList;
+
+/** Common test keys used by a variety of tests. */
+public class TestKeys {
+ public static ImmutableList<TestKey> allValidKeys() {
+ return ImmutableList.of(
+ validKeyWithoutExpiration(),
+ validKeyWithExpiration(),
+ validKeyWithSecondUserId());
+ }
+
+ /**
+ * A valid key with no expiration.
+ *
+ * <pre>
+ * pub 2048R/46328A8C 2015-07-08
+ * Key fingerprint = 04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C
+ * uid Testuser One <test1@example.com>
+ * sub 2048R/F0AF69C0 2015-07-08
+ * </pre>
+ */
+ public static TestKey validKeyWithoutExpiration() {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBFWdTIkBCADOaygDKjLuRX6LXAvBAYB91cmTf1MSlmEy+qsG3c9ijjQixPkr\n"
+ + "atdYkocrrT2S0R9UGjksTOI2WN5S0lQfLA1RSk63KURQE+OF+IfNqdD6nQdLBs1w\n"
+ + "va+GDj/uvuI05I0oXf/M7POdFphutrS4EUDBnFPj6ns/0C2sTRTxliD+Y9Y9a84V\n"
+ + "DfVVUbJB6wc3LP3L6ImT+cSM7dLq3hZHya+9FNeYPmPYnBrkJyqf2NDd38Sddsro\n"
+ + "7smw/GgCZHnnuVNS4C7NsHr6900VKC+JDtdx+fqptixcAEJWiGoQfWqU+hYmia3p\n"
+ + "9+Xw02+3FcjOT6ONUCmHX+xlz0pXW4iIYlPpABEBAAG0IFRlc3R1c2VyIE9uZSA8\n"
+ + "dGVzdDFAZXhhbXBsZS5jb20+iQE4BBMBAgAiBQJVnUyJAhsDBgsJCAcDAgYVCAIJ\n"
+ + "CgsEFgIDAQIeAQIXgAAKCRDtBiXcRjKKjHblB/9RaFO5+GTDIphAL/aVj2u+d8Lq\n"
+ + "yUpBrDp3P06QDGpKGFMAovBuh+NLH76VKNIzQLQC8rdTj651fLcLMuJ1enQ3Rblg\n"
+ + "RKr1oc+wqqtFHr4QyOQjE/N3C9GQjEzfqn4qnp5KtZxYFnlvU5NGehid7M1HTZMx\n"
+ + "jRcHbM9KQnsE5Z4fh4wmN5ynG+5nbaF4O9otPOpFzYRvIhxFmHscWyOgRaMZiYEX\n"
+ + "7Qkzze+scAlc9E/EWRJQIFcxnxV/SYIT4qCTT1g2aKA8OCBO/ZTOleH8SzvTODjy\n"
+ + "W0lGHnh/ZqH6XGVcGUaJZZ2uHTck1+czuVVShNcXPW1W20T6E9UqzHbJHN0guQEN\n"
+ + "BFWdTIkBCACoLVdPr3gpQwzI+2NGXjdtoyqYoPlgfeyI2M1XQD/7+rLZTbi14ZjN\n"
+ + "vYkS/+/oGtVEmiYOiAVTwmkjCYkKGDgNcCiJVekiPAN6JryVv488wRc999b5LpFE\n"
+ + "fhLGwI0YxjcS4KFFnpMC3wSb6tJUnHRLVoE5d8icdiaOpgYdp7uqWkSx2oxqHgIb\n"
+ + "nuyrk3ydEcS4ZeGD+w+taIxMc9F1DS9kiXALD7xWgUkmqZLEQoNgF6KlwCHXRd3m\n"
+ + "rBCo97sE95yKcq98ZMIWuQtTcEccZsN/6jlsei+9RI0tqs+FbZnIFm/go9zk11Vl\n"
+ + "IQ9QFSj6ruqoKrYvNZuDDLD1lHvZPD4/ABEBAAGJAR8EGAECAAkFAlWdTIkCGwwA\n"
+ + "CgkQ7QYl3EYyiox+HAf/Z/OCQO3jxALAcn3oUb1g/IlHm6qZv7RJOFUsj/16fGiF\n"
+ + "rRTP15zMXzyqV+L/LGV/owvOsdD/o7boZz4C/U98COx0Nl1jOrmPATOl+xqsgpEj\n"
+ + "Fhk+eAR7exO2XxW+u2g4cYoSMosIOX5w1GrdsxQeaZDwiSJMEOR2cVLs3YI19Ci/\n"
+ + "FuzActZ0wJNk0nlNF6l8CAbzwN6pM9OIc/iBIwDjz92KUco0NF8XKZnxqhH4wfHB\n"
+ + "PGkTx8RwOvELUTDMtvYnG5R0QtND0RbOnmp4ZVZmeOjKSLo1mZliUZB1H2PPSxrA\n"
+ + "0oLr8+wLntz1SU7uS4ddvhSQW+j2M/0pa352KUwmrw==\n"
+ + "=o/aU\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=MuAn\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A valid key expiring in 2065.
+ *
+ * <pre>
+ * pub 2048R/378A0AED 2015-07-08 [expires: 2065-06-25]
+ * Key fingerprint = C378 369A CBCD 34CC 138D 90B1 4531 1A6F 378A 0AED
+ * uid Testuser Two <test2@example.com>
+ * sub 2048R/46D4F204 2015-07-08 [expires: 2065-06-25]
+ * </pre>
+ */
+ public static final TestKey validKeyWithExpiration() {
+ return new TestKey(
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBFWdTP8BCADRxNpasIv0jtNXTK6VYIS2VJ2Xk0ZD6gtxeoXCpjQ+TsB9fxh3\n"
+ + "vAMPt2Zu5LqoGwygKOJj1zquG8xk7GUCCHJk3+qG8xxB1xGtSz2vLyfRm7fOZmHj\n"
+ + "3W/C/25lynSPDrfvcwvwA4PN8iP5EWbWU10L6WOZGMwwwtVDUSEouSOw2LEepxLV\n"
+ + "rkKuZcyHaivheDbUlZliwe9rGXd4hh1h4qyNQWG3q+ytlL28sVkOzUh6IMBTvqhe\n"
+ + "IRsvxvaVSLV8jRVKfUTqw0g57ft4ZD2/L46yUTXzr9aUCBjTNxvWLlyboqql/D8P\n"
+ + "inp51h3cvAg7NW5RdG1GEYmylH8SygT5utPxABEBAAG0IFRlc3R1c2VyIFR3byA8\n"
+ + "dGVzdDJAZXhhbXBsZS5jb20+iQE+BBMBAgAoBQJVnUz/AhsDBQld/A8ABgsJCAcD\n"
+ + "AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBFMRpvN4oK7UZqCACWwQL/YvBK4b0m+R0d\n"
+ + "UdvAXeBx7DwOAnAodis9ZVqChb7RxcZQxF1Ti9mtCBPPQGuEs5wE2Ocrrq+L13r6\n"
+ + "bgW+1WOB1tZSDVxwL1PnZFw/SyADRIDCZrOHiAkp82UnZwWAkk39GzNJtt1wTYDZ\n"
+ + "FMTFUr2SPscXk1k7muS+ZfEFwNPD4tODo/poJKDYEJ80Z5UXXFQLDtsfdeIXMFIT\n"
+ + "449CYoq8XBMBfvyWl/LLpw0r3JI6pV/YdH3Oeuz8XkkEVzRxaxB6Zmeo5jSwjR/T\n"
+ + "8TKDGwwiuwiiT3SfkFSVdcjKulRuXSRNs1Ouf7/UC3cq4bG2WXWa85X1+HQRm7iu\n"
+ + "RHSOuQENBFWdTP8BCADhhGxAA0pX5yBHwIgM1j0gw2h5nSsopDrO6t/sbRUcNxnR\n"
+ + "tBScgKZnP0sjRTYEUIwmZuseHMBohtVCuMaDt06qyZDvDk/98j3AeE5t2dgFnOIe\n"
+ + "qCrm/6aejbFcQOpxe6U29KJRCAxuwNtB15X1VH1Kj7B0gRSTu13n/5sUsi2lunoZ\n"
+ + "oIvpIe9tZH4aXitCY2MCQH+hTyCyNBzlEa44kWz6LxUsPdo7I6rXkTr6Ot7wQh+9\n"
+ + "7HCe042GIq65h0apgujyjhJidjch5ur1mngaSNSEyvbji2MGC+cd3wAIstG5a7xP\n"
+ + "d9MncY5Q/eH+hn96694k5bckottSyGm/3f2Ihfj1ABEBAAGJASUEGAECAA8FAlWd\n"
+ + "TP8CGwwFCV38DwAACgkQRTEabzeKCu1FNwgAif4eK2v7R3QubL2S6wmb1nsgRMgV\n"
+ + "YoxGBeUk2EK6WZ5IPor93ySd0ixRVNMRmJ8BLH3EMjZQTzkDG+BH6zFyxo6lLHw9\n"
+ + "NxQjI06tqQWgyyK0mEweVwB/zqtxiB4lNUpsNbqOZWnBJ3d6o1SsnD2Q3uwvP5fb\n"
+ + "fSIgdmUk3c0VMdgA+KzWjPD/PJIPujE+ckHhjn5cbDNw35/FuyhkLJfqlOG7SPvM\n"
+ + "NmCdJ1Pcqju9t7sf6b0BGPDOCL4gpuWKK7HJz9WxngNb3FSziLbyPLk13ynADO+v\n"
+ + "EOR44LPyXE9kVxPusazsXlt9ayTOhELhwzw7sGFFu8E17Cpn7GnVj3tN9A==\n"
+ + "=1e/A\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "9A==\n"
+ + "=qbV3\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A key that expired in 2006.
+ *
+ * <pre>
+ * pub 2048R/17DE1ACD 2005-07-08 [expired: 2006-07-08]
+ * Key fingerprint = 1D9E EB79 DD38 B049 939D 9CAF 3CEC 781B 17DE 1ACD
+ * uid Testuser Three <test3@example.com>
+ * </pre>
+ */
+ public static final TestKey expiredKey() {
+ return new TestKey(
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBELOp0UBCACxholOPWuKhK+TYb88nvLUSCMvTLIFEpb5u3Eavr0wiluEzq6H\n"
+ + "55nswAD3dQm8DWxA7yUlEYjPr5btpw7V9441bb1+qtgZMJ10RTdEb/WjyctdGA99\n"
+ + "uOKBEarWbt8W+w6lyJ9NXy5bS/x5EwHHfoTFp4ff6ffHI5hbx1a00K8oxmitgd0X\n"
+ + "Mx86UmauFNJYupZOZG9gEcP4RbRp7e2pm4Jy1WLEOeg9Fdgm5e5Hj2nMkCSZ9BKV\n"
+ + "cxuOllSVzM/Zp0/4+RS9R57jKo3/V74Whwh9yQNgL9UxdNk7L0eGqvaT3EjXxjOc\n"
+ + "RCeJiucGN/0W2iq+V01/QGspp4SKtAogWBozABEBAAG0IlRlc3R1c2VyIFRocmVl\n"
+ + "IDx0ZXN0M0BleGFtcGxlLmNvbT6JAT4EEwECACgFAkLOp0UCGwMFCQHhM4AGCwkI\n"
+ + "BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEDzseBsX3hrNYg0H/2CMm5/JDQNSuRFC\n"
+ + "ECWLrcOeimuvwbmkonNzOkvKbGXl73GStISAksRWAHBQED1rEPC0NkFCDeVZO7df\n"
+ + "SYLlsqKwV6uSh05Ra0F5XeniC12YpAyzoQyCGRS2wLaS822j0zUPXA8XLaO2blCu\n"
+ + "R+8sNu/oecMRcFK4S9NaApi3vdqBNhLiN1/Lpqn1LfB8uIO+eaUf4PmCWbaPgzSk\n"
+ + "qcPfKZmocNXdgLV5Q80n3hc2y2nrl+vDW2M+eVZuDHAok2BOD9uGKFfLAbaXLbX5\n"
+ + "btBW2L0UHtoEyiqkRfD6lX2laSLQmA6+eup7e4GS+s0vXBuVh8XEYddV6Yjt8H7/\n"
+ + "2thO41K5AQ0EQs6nRQEIAM/833UHK1DuFlOm7/n18dRMvs7BkXvg+hPquKWMG3be\n"
+ + "eE4sh1NG5DbRCdo6iacZLarWr3FDz7J9+wswRhtHCh3pGHEuaJk52vRjQxlkNh5F\n"
+ + "p5u2R4WF546bWqX45xPdLfHVTPyWB9q7aVxE+6Q+MHa6lMoyTVnTVCOy3nshiihw\n"
+ + "dxLsxaga+QmaL0bAR+dRcO6ucj7TDQXz1AJAVp26c0LXV9iErhFuuybUZKT0a9Aj\n"
+ + "FoumMZ6l+k30sSdjSjpBMsNvPos0dTPPRXUMu77o5sj+pHa4o8WctgB3o7BHQELp\n"
+ + "KgujZ2sKC9Nm395u6Q4cqUWihzb/Y7rIRuNHJarI7vUAEQEAAYkBJQQYAQIADwUC\n"
+ + "Qs6nRQIbDAUJAeEzgAAKCRA87HgbF94azRiBB/4vAyOOjUjK3lDWjHGs7mvEWJI/\n"
+ + "1MeLlGPswCSInJBa+HMiMI4tzq+hu5ejGThojNbmnL96GdzfDkMlP4Feyxb2rjtb\n"
+ + "NrD/R5tlXHmjX/QLzep4LCeMziP80fu8qUeiOej/Ecdny0w365PlMdt10RaYR8VE\n"
+ + "ZX/DAie6JfElnfQcG5q8TIOH3i71qxV+kIoPqKWfQ0MXrNEJ3BYFfDGdUt8U1Kq9\n"
+ + "OuIHVRgGS7mMSyjgNqqp7MBeMY+PFFZaZel5yoYVjb9d3L8XvVv2eoa/jPj5FUEU\n"
+ + "kE9uxNmwaD1PiV8DvBTYI+eQL4qzfu+3NTG2SfgQYtj5oiGHw8aL3U6QHDJb\n"
+ + "=d/Xp\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "lQOYBELOp0UBCACxholOPWuKhK+TYb88nvLUSCMvTLIFEpb5u3Eavr0wiluEzq6H\n"
+ + "55nswAD3dQm8DWxA7yUlEYjPr5btpw7V9441bb1+qtgZMJ10RTdEb/WjyctdGA99\n"
+ + "uOKBEarWbt8W+w6lyJ9NXy5bS/x5EwHHfoTFp4ff6ffHI5hbx1a00K8oxmitgd0X\n"
+ + "Mx86UmauFNJYupZOZG9gEcP4RbRp7e2pm4Jy1WLEOeg9Fdgm5e5Hj2nMkCSZ9BKV\n"
+ + "cxuOllSVzM/Zp0/4+RS9R57jKo3/V74Whwh9yQNgL9UxdNk7L0eGqvaT3EjXxjOc\n"
+ + "RCeJiucGN/0W2iq+V01/QGspp4SKtAogWBozABEBAAEAB/4hGI3ckkLMTjRVa7G1\n"
+ + "YYSv4sr8dHXz0CVpZXKOo+Stef3Z4pZTK/BcXOdROvaXooD+EheAs6Yn4fpnT+/K\n"
+ + "IB7ZAx6C0OL8vz17gbPuBFltMZ/COUwaCi/gFCUfWQgqRp/SdHaOfCIuTxpAkDSS\n"
+ + "tpmWJ8eDDSFudMpgweb+SrF9DkCwp+FgUbzDRzO1aqzuu8PGihCHQt/pkhNHQ63/\n"
+ + "srDDqk6lIxxZHhv9+ucr3plDuijkvAa5/QDudQlucKDLtTPSD40UcqYnpg/V/RJU\n"
+ + "eBK0ZXmCIHpG9beHW/xdlwrK3eY4Z2sVDMm9TeeHmRYOCr5wQCyeLpMdAt0Ijk6a\n"
+ + "nINhBADI2lRodgnLvUKbOvVocz8WQjG1IXlL8iXSNuuHONijPXZiWh7XdkNxr9fm\n"
+ + "jRqzvZzYsWGT6MnirX2eXaEWJsWJHxTxJuiuOk0V/iGnV/d+jFduoKXNmB5k/ZB3\n"
+ + "6zySi7+STKNyIvnMATVsRoI/cNUwfmx53m6trFg581CnSiA82QQA4kSPw9OXmTKj\n"
+ + "ctlHrWsapWu+66pDVZw62lW6lvrd7t+m8liNb6VJuTnwIKVXJOQtUo1+GSMs0+YK\n"
+ + "wnd9FGq4jT8l0qBO4K/8B1HxppLC2S0ntC+CusxWMUDbdC2xg+G2W3oLwq3iamgz\n"
+ + "LvPTy1Pzs9PqDd6FXIdzieFy6J8W1+sEAKS3vjh7Z/PIVULZhdaohAd5Igd67S/Z\n"
+ + "BMWYNbBuJTnnb7DiOllLZSd2lR7IAKPKsUd6UY8uskOxI81hI116zNx17mIGFIIq\n"
+ + "DdDgRbvzMNEgNlOxg/BD01kXOS4fhnT2F6ca3VGTgUtOdcdF3M9MtePWQLBzEDPz\n"
+ + "8nx3O20HDupuQmG0IlRlc3R1c2VyIFRocmVlIDx0ZXN0M0BleGFtcGxlLmNvbT6J\n"
+ + "AT4EEwECACgFAkLOp0UCGwMFCQHhM4AGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA\n"
+ + "AAoJEDzseBsX3hrNYg0H/2CMm5/JDQNSuRFCECWLrcOeimuvwbmkonNzOkvKbGXl\n"
+ + "73GStISAksRWAHBQED1rEPC0NkFCDeVZO7dfSYLlsqKwV6uSh05Ra0F5XeniC12Y\n"
+ + "pAyzoQyCGRS2wLaS822j0zUPXA8XLaO2blCuR+8sNu/oecMRcFK4S9NaApi3vdqB\n"
+ + "NhLiN1/Lpqn1LfB8uIO+eaUf4PmCWbaPgzSkqcPfKZmocNXdgLV5Q80n3hc2y2nr\n"
+ + "l+vDW2M+eVZuDHAok2BOD9uGKFfLAbaXLbX5btBW2L0UHtoEyiqkRfD6lX2laSLQ\n"
+ + "mA6+eup7e4GS+s0vXBuVh8XEYddV6Yjt8H7/2thO41KdA5gEQs6nRQEIAM/833UH\n"
+ + "K1DuFlOm7/n18dRMvs7BkXvg+hPquKWMG3beeE4sh1NG5DbRCdo6iacZLarWr3FD\n"
+ + "z7J9+wswRhtHCh3pGHEuaJk52vRjQxlkNh5Fp5u2R4WF546bWqX45xPdLfHVTPyW\n"
+ + "B9q7aVxE+6Q+MHa6lMoyTVnTVCOy3nshiihwdxLsxaga+QmaL0bAR+dRcO6ucj7T\n"
+ + "DQXz1AJAVp26c0LXV9iErhFuuybUZKT0a9AjFoumMZ6l+k30sSdjSjpBMsNvPos0\n"
+ + "dTPPRXUMu77o5sj+pHa4o8WctgB3o7BHQELpKgujZ2sKC9Nm395u6Q4cqUWihzb/\n"
+ + "Y7rIRuNHJarI7vUAEQEAAQAH+gNBKDf7FDzwdM37Sz8Ej7OsPcIbekzPcOpV3mzM\n"
+ + "u/NIuOY0QSvW7KRE8hwFlXjVZocJU/Z4Qqw+12pN55LusiRUrOq8eKuJIbl4QikI\n"
+ + "Dea8XUqM+CKJPV3YZXs6YVdIuzrRBSLgsB/Glff5JlzkEjsRYVmmnto8edETL/MK\n"
+ + "S9ClJqQiFKE4b01+Eh9oB/DfxzsiEf/a+rdRnWRh/jtpEwgeXcfmjhf+0zrzChu2\n"
+ + "ylQQ5QOuwQNKJP6DvRu/W5pOaKH9tPDR31SccDJDdnDUzBD7oSsXl06DcfMNEa8q\n"
+ + "PaNHLDDRNnqTEhwYSJ4r2emDFMxg7Kky+aatUNjAYk9vkgMEANnvumgr6/KCLWKc\n"
+ + "D3fZE09N7BveGBBDQBYNGPFtx60WbKrSY3e2RSfgWbyEXkzwm1VlB2869T1we0rL\n"
+ + "z6eV/TK5rrJQxJFHZ/anMxbQY0sCiOgqi6PKT03RTpA2N803hTym+oypy+5T6BFM\n"
+ + "rtjXvwIZN/BgAE2JjA70crTAd1mvBAD0UFNAU9oE7K7sgDbni4EhxmDyaviBHfxV\n"
+ + "PJP1ICUXAcEzAsz2T/L5TqZUD+LfYIkbf8wk2/mPZFfrCrQgCrzWn7KV1SHXkhf4\n"
+ + "4Sg6Y6p0g0Jl3mWRPiQ6ALlOVQIkp5V8z4b0hTF2c4oct1Pzaeq+ZkahyvrhW06P\n"
+ + "iaucRZb+mwP/aVTpkd4n/FyKCcbf9/KniYJ+Ou1OunsBQr/jzN+r0PKCb8l/ksig\n"
+ + "i/M0NGetemq9CxYsJDAyJs1aO4SWgx5LbfcMmyXDuJ3sL0ztFLOES31Mih3ZJebg\n"
+ + "xPpj2bB/67i2zeYRcjxQ116y23gOa2TWM8EE4TW7F/mQjw4fIPJ93ClBMIkBJQQY\n"
+ + "AQIADwUCQs6nRQIbDAUJAeEzgAAKCRA87HgbF94azRiBB/4vAyOOjUjK3lDWjHGs\n"
+ + "7mvEWJI/1MeLlGPswCSInJBa+HMiMI4tzq+hu5ejGThojNbmnL96GdzfDkMlP4Fe\n"
+ + "yxb2rjtbNrD/R5tlXHmjX/QLzep4LCeMziP80fu8qUeiOej/Ecdny0w365PlMdt1\n"
+ + "0RaYR8VEZX/DAie6JfElnfQcG5q8TIOH3i71qxV+kIoPqKWfQ0MXrNEJ3BYFfDGd\n"
+ + "Ut8U1Kq9OuIHVRgGS7mMSyjgNqqp7MBeMY+PFFZaZel5yoYVjb9d3L8XvVv2eoa/\n"
+ + "jPj5FUEUkE9uxNmwaD1PiV8DvBTYI+eQL4qzfu+3NTG2SfgQYtj5oiGHw8aL3U6Q\n"
+ + "HDJb\n"
+ + "=RrXv\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A self-revoked key with no expiration.
+ *
+ * <pre>
+ * pub 2048R/7CA87821 2015-07-08 [revoked: 2015-07-08]
+ * Key fingerprint = E328 CAB1 1F7E B1BC 1451 ABA5 0855 2A17 7CA8 7821
+ * uid Testuser Four <test4@example.com>
+ * </pre>
+ */
+ public static final TestKey selfRevokedKey() {
+ return new TestKey(
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=477N\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "lQOYBFWdTZwBCAC1jukp5mlitfq2sAmdtx1s1VbWh+buDbBY2kWcxbbssczozFUP\n"
+ + "Ii67wPwjRbn3GM5+jY3GMsqKIrdyDlxeTxGWoU/qa2YkCQzgFGD/XJBqkVpP6osm\n"
+ + "qFYSP0xST1iBkatkMHq5KMjrX2q2bGVLlchLF9eHrWSefMcfff1Vs/Y8F2RCo38y\n"
+ + "gH88mbcvgyC+zq6Q2T3h5RiLK2IaZDNsn3uUoIMYHxI6oYtXXMSXRJlLJvamXVrB\n"
+ + "7QAq8L8cNikJjZMz+bHtLtGDyVVp9tqo4yvMrHe6BYmBUte3tPYQlDVdEo7UqepR\n"
+ + "uT7JbBOGBoD+9ngDrDggPUBAoa0h3VNOTyoDABEBAAEAB/4jqeZoOiACaV/Nygeh\n"
+ + "iOpJSiDsNDbrFRpKYdnhwT69APIQ2q5sshi+/dopbZVpkeBiIJk0UR7TAp3JVEPV\n"
+ + "rK92SMqjcCRYuMRkMeyZzMt7e4DjiN17ov6BSBjMZFSs4vnpTNKWk4ngHlaebe15\n"
+ + "6vq0sYK/XpKQxU7yAzQjxR190P/F+QEL98zVG/9uqM8PupfdSm4Smp2cIpfta+JD\n"
+ + "mO23HC6jAEm2RFwklovzgK3rbIjyiMuowIkAKx5xxRvpxMHf1l566b9zJrRi0xau\n"
+ + "vp4J/lnBJtTMzCbsaaFxhrj23xvTXaWR+UkaGPCv7wheXQ9K7NAHwmH8YrR+cZx7\n"
+ + "KbDlBADUTHZ+OhNslx/rkjRWrFuK9p49x7qxQc26kcqlGPbW6KOAMdUpwneQbhCG\n"
+ + "a36E/GAZgsgQ4SUqn37EVCtd2Y9Dp0inPAujcZXSwgDHev6ea7fzbxT9KLtEgvQN\n"
+ + "0vrFJDCPIt0wzGqNDw4wgFjF2rAafBO//Wu5K5QLW4hfzSguRQQA2u6DpVja/FYY\n"
+ + "UHVh2HLiB8th4T+qogOsBe5mKEsGRPXtAh7QzJu36C4PJyHeNlmlMx+15cCFnovj\n"
+ + "6cLpGn6ZP4okLyq2+VsW7wh/Vir+UZHoAO/cZRlOc1PsaQconcxxq30SsbaRQrAd\n"
+ + "YargKlXU7HMFiK34nkidBV6vVW0+P6cD/jYRInM983KXqX5bYvqsM1Zyvvlu6otD\n"
+ + "nG0F/nQYT7oaKKR46quDa+xHMxK8/Vu1+TabzY8XapnoYFaFvrl/d2rUBEZSoury\n"
+ + "z2yfTyeomft9MGGQsCGAJ95bVDT+jBoohnYwfwdC7HG3qk0aK/TxFyUqvMOX7SFe\n"
+ + "YT55n3HlD9InST+0IVRlc3R1c2VyIEZvdXIgPHRlc3Q0QGV4YW1wbGUuY29tPokB\n"
+ + "OAQTAQIAIgUCVZ1NnAIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQCFUq\n"
+ + "F3yoeCF7BwgAkpvUSHccaomwGsYfp5usOReWuh47uXHZpJPWpWw6EGIdnjPBOTXQ\n"
+ + "6vq9Kkkntvrow2kOAveQ2TSUBEryHBxx2/QBAqwX0vGJPOq1/IcVXmEHMzJAfS3Q\n"
+ + "R8Ypk0qDq/C/3gMQRP1Vw8LrLehAHukNQGt7ZU07wn3JsOD4qb7T+xelMxWqvkFO\n"
+ + "n7wEaQQEznL1XNiVOCUDq8bjRENjyq0rYu+UuZp1VptKUXmzPhKK1gbZQNHjgHiz\n"
+ + "agqfO1MC3b1Mjk0WU+AyedXk8X6xQA16yPf3o7N/2MQj2MWj4nAJXJXXbrmo1P7P\n"
+ + "vYn61J9DaYNiC5fJkJDc6nnq5QXnnFFeBJ0DmARVnU2cAQgAxDrCvphUP9rnbPc5\n"
+ + "5JgKq5NuMGlQacjxq0017V19HSobUALIekJGRpIy05ysRLGvbMqlXJMPtSbQoeOE\n"
+ + "j9YI6g1KEoaQc+cCLpLGe6veh8fhdE1Rv540uhq1r8CoS9i2/YWH3Za2YmLTjotI\n"
+ + "ojs7nduR/zLNeZAvLbkJuzVrQDSSReEsaAcaK+i/xeM7pxJEy9C/f3aeKla8Nt3y\n"
+ + "VShN/HEncYwdLtakg8y1f7tECapWMSQIhHIsEDdrlZ02Nf10qaLjMui722FOwdrl\n"
+ + "P1qITZh90Yjf30OoyZuMfnwmfPs6lzImZxtnz5MJscSvXJeuGJzRnE8LFbegTGwh\n"
+ + "5PYx8QARAQABAAf8CeTumd6jbN7USXXDyQdzjkguR6mfwN29dcY8YF4U52oOm3+w\n"
+ + "bR23XmqTvoDJXONatZEYOm093wP4hBktP3Vq2KZX5Ew9r2JoBUIoWOcHHvCQqSUW\n"
+ + "6KMJBJNBMv3zXnOscmcPvTgStS5HfYn/XRLAhEqkd2ov2x/OiS8p0vM0F7YYSOdu\n"
+ + "X6/nHeBCM5QSJl00kgcaeQYdIGL0bPv9DnoeAC2/yITEvtvs+MHZ7FjH8A45QjWn\n"
+ + "DwfVoLg7WOc3wJtqJ55/r/2pylrWz0YYM8s6I3gbDilCF+Wb8tEIOaWJEwY73J1/\n"
+ + "KQG5qlO3/hBlO80DtzNmi3ylRUuzGhTxQfvemwQA3EuZ+E48LJ3dwtdJhh5mFlWI\n"
+ + "Ket21e5v1mqMxuLhf5/2CYcifM08u3EsEUdIr7egF25Sea8otqmCYcG8FuB37VY/\n"
+ + "Hd4G/+YVVaaAB8EU6u64YfSswhzr0R2qWVLtkJr0EAephzdPdoUEtKDSdTxnXiDV\n"
+ + "3vSqLWtZekScLa979uMEAOQIodJwxSvveKQWILjK67ZJr56X8YQZWA6rFsr1xMY0\n"
+ + "N0GH+5k0k+tr4wT3H9uk9ZM1Z11G3c01mhzCNg5roFoKtftKUZRKxmbfjjDmAofl\n"
+ + "bA6EZ0WHLdOwDLLTuXK09IsjjSHq0YHOxIlgFzIreuoxtz27bEEGhVFQg7xb0Lgb\n"
+ + "A/9LP8i32L7/CHsuN0q4YjhJkkaB6JWUQMFqWwoAXALG3rnw/CGRYHmHpiAuSeHR\n"
+ + "dSlZzndVi5poNC/d27msTx7ZuWlN7nOyywHBCTWV/nstm2I9rDhrHK7Axgq0Vv0y\n"
+ + "bWAurUmEgDJHU3ZpsNVt4e30FooXIDLR4cnpRM7tILv39D4giQEfBBgBAgAJBQJV\n"
+ + "nU2cAhsMAAoJEAhVKhd8qHghjncH/05qMR96RQZvzA4WOQreRTtCr7IEJNmYA2uv\n"
+ + "B8kPI0GItRVArcg47MX+Sk38r86c3E6Uu3MjsHCTbU4+lIscAE5HIr72FmKDl4IQ\n"
+ + "LE6OH9fo/8ehAqtfp2BOjaXADe1USaWDJNlDtqpYRwnrEzxs7B3IS+5sImHYRq3Y\n"
+ + "lhk6XSiPVnzmA2WfWUsP0CDSVMk0caw52rjr1W4WwF3O43SgpEUEWabDZRf/9UrY\n"
+ + "Op7LYxSe0q41XbDCTZYoU+nxubo5q7ctf+m9VQi5CEH1uXFr80c0wRcrau4uD1So\n"
+ + "d7uzs7bfZX/LOdM7drgRPwCjJMhOiCS7AVqST5O4hSpBxg8dOOE=\n"
+ + "=5aNq\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A key with an additional user ID.
+ *
+ * <pre>
+ * pub 2048R/98C51DBF 2015-07-30
+ * Key fingerprint = 42B3 294D 1924 D7EB AF4A A99F 5024 BB44 98C5 1DBF
+ * uid foo:myId
+ * uid Testuser Five <test5@example.com>
+ * sub 2048R/C781A9E3 2015-07-30
+ * </pre>
+ */
+ public static TestKey validKeyWithSecondUserId() {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBFW6jd4BCACrf+BZS3lntuWq2DWPOG07/BUWhx3RSoiS3JBuKEDmlsjswKcp\n"
+ + "JHT+p2tqH52XbujMlzNjAQcZjJwfOMt6Fg7zd3F8cwYQdCE/W5dpMs/mqdeEz6GL\n"
+ + "VJDZ0Y5wwz54ZQHp91Xq6uejxt5qffeTQk5cToQZ0RVx3iwBc+2P3iYJqMFmJzj8\n"
+ + "djabEoF4D50iI5tY8moE83VcXJ5Y4xn+5Z5AThmlfrMP6gIdG0b4lEe1tsnJC6AG\n"
+ + "GUU6VkzK6E1Tp93Y0brtWpJKi9Gt6eUqvWhZtPEdFVCFbLTpezUdRFEuaFbGg5pn\n"
+ + "9K/DceahFmquDJOHVgawt6erlq/ie7QEEld/ABEBAAG0IVRlc3R1c2VyIEZpdmUg\n"
+ + "PHRlc3Q1QGV4YW1wbGUuY29tPokBOAQTAQIAIgUCVbqN3gIbAwYLCQgHAwIGFQgC\n"
+ + "CQoLBBYCAwECHgECF4AACgkQUCS7RJjFHb+/MAf9FKZatGcuOIoYqwGQQneyc63v\n"
+ + "3H/PyhvYF1nuKNftmhqIiUHec9RaUHQkgam6LRoonkDfIpNlQVRv2XBV2VOAOFVO\n"
+ + "RyQ/Tv7/xtpqGZqivV0yn2ZXbCceA627Vz7gP4gkO0ZJ0JsYJTc/5wO+nVG5Lohu\n"
+ + "/zdUofEbFAvcXs+Z1uXnUDdeGn47Lf1xZ2XOHOI0aQW4DdNaFoAd+AOTe0W3iB6W\n"
+ + "paCIGno69CyNHNnWjJCSD33oLVaXyvbgw5UoyITvSqRnPyLGIc6dsqDLT59ok0Fk\n"
+ + "t4jtiGu9aze4n59GbtSjmWQgzbLCQWhK9K7UCcSLYNKXVyMha2WapBO156V027QI\n"
+ + "Zm9vOm15SWSJATgEEwECACIFAlW6jwYCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B\n"
+ + "AheAAAoJEFAku0SYxR2/zZUH/1BwPsResHLDSmo6UdQyQGxvV0NcwBqGAPSLHr+S\n"
+ + "PHEaHEIYvOywNfWXquYrECa/5iIrXuTQmCH0q8WRcz1UapDCeD8Ui82r+3O8m6gk\n"
+ + "hIR5VAeza+x/fGWhG342PvtpDU7JycDA3KMCTWtcAM89tFhffzuEQ3f5p5cMTtZk\n"
+ + "/23iegXbHd61vojYO17QYEj+qp9l0VNiyFymPL3qr5bVj/xn/mXFj+asj0L2ypIj\n"
+ + "zC36FkhzW5EX2xgV9Cl9zu7kLMTm+yM+jxbMLskYkG8z/D+xBQsoX8tEIPlxHLhB\n"
+ + "miEmVuZrp91ArRMWa3B7PYz7hQzs+M/bxKXcmWxacggTOvy5AQ0EVbqN3gEIAOlq\n"
+ + "mwdiXW0BQP/iQvIweP1taNypAvdjI2fpnXkUfBT5X/+E/RjYOHQEAzy8nEkS+Y0l\n"
+ + "MLwKt3S0IVRvdeXxlpL6Tl+P8DkcD5H+uvACrg9rtgbbNSoQtc9/3bknG9hea6xi\n"
+ + "6SBH1k9Y2RInIrwWslfKmuNkyZVhxPKypasBsvyhOWLlpCngGiCa74KJ1th1WKa2\n"
+ + "aaDqcbieBTc1mtsXR6kBhJZqK+JYBoHriUQMs7nyXxn2qyv6Lehs/tHlrBZ7j16S\n"
+ + "faQzYoBi1edVrpFr/CuGk6RNKxG9vi/uAA9q2cLCMjjyfMH4g0G2l0HuDPQLA9wi\n"
+ + "BfusEC+OceaeFKtS9ykAEQEAAYkBHwQYAQIACQUCVbqN3gIbDAAKCRBQJLtEmMUd\n"
+ + "vw/DB/9Qx9m1eSdddqz/fk16wJf7Ncr2teVvdQOjRf/qo43KDKxEzeepjgypG1br\n"
+ + "St7U4/MlPygJLBDB4pXp0kaKt+S/aqLpEGSGzQ1FysM8oY6K0e1Kbf6nMaQS8ATG\n"
+ + "aD377FrUJ42NV4JS+NGlwaM9PhpRVm5n8iCzRs9HtlTyfCBkNGDjGOSdWcah2m6T\n"
+ + "fEQdD+XVDN1ZC8zAnc8FW28YOTeTjX079okP6ZCjLJ16VZ7eiHFkrNbS9Dl4SPNK\n"
+ + "eElvsZLBaf8t4RQXFFKwRq4BW+zS8zm9E2H6bZ9yGrmgIREzyRPpwU98g8yrabu0\n"
+ + "54w16Vp/SVViJs7nTMSug0WREyd2\n"
+ + "=ldwB\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=uND5\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A key revoked by a valid key, due to key compromise.
+ * <p>
+ * Revoked by {@link #validKeyWithoutExpiration()}.
+ *
+ * <pre>
+ * pub 2048R/3434B39F 2015-10-20 [revoked: 2015-10-20]
+ * Key fingerprint = 931F 047D 7D01 DDEF 367A 8D90 8C4F D28E 3434 B39F
+ * uid Testuser Six <test6@example.com>
+ * </pre>
+ */
+ public static TestKey revokedCompromisedKey() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "tULvIEIjNNP8Qizfc4u2O8h7HP2b3yYSrp9MMQ==\n"
+ + "=Dxr7\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "i7Y7yHsc/ZvfJhKun0wx\n"
+ + "=M/kw\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A key revoked by a valid key, due to no longer being used.
+ * <p>
+ * Revoked by {@link #validKeyWithoutExpiration()}.
+ *
+ * <pre>
+ * pub 2048R/3D6C52D0 2015-10-20 [revoked: 2015-10-20]
+ * Key fingerprint = 32DB 6C31 2ED7 A98D 11B2 43EA FAD2 ABE2 3D6C 52D0
+ * uid Testuser Seven <test7@example.com>
+ * </pre>
+ */
+ public static TestKey revokedNoLongerUsedKey() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBFYmq3EBCAC9ssY6QhFsnZqKEPlQrx8Zomblj8qV93/B448isOT2L6OVY7UC\n"
+ + "kKPj6afW5UDkYeyZSmLZfTrpePcbAB8FB3uvd/AS9mHC+6zuBlwlkl9xIXlwUXQP\n"
+ + "KER4LKYNTP21AM+/vTJm4+u26tlZECIZlez31KEeqM30EAm+/pO8VkEp8+1ImfLv\n"
+ + "otndIjMoq9gxJvn6KZeexJT2eKCsSa20vVsmAhuFjLZitU3lEjIROfDiyHUZ2cZ+\n"
+ + "qynfppJCKlHJRu/T9L/yxDFVUFDFSajNzSfjG1g3FEveDITyAhRetVfZbhyJptnV\n"
+ + "jfiHSQkLamPsBmMoKfP+aO5SfsTHTJvxgLUdABEBAAGJAS0EIAECABcFAlYmq8AQ\n"
+ + "HQN0ZXN0NyBub3QgdXNlZAAKCRDtBiXcRjKKjPKqB/sF+ypJZaZ5M4jFdoH/YA3s\n"
+ + "4+VkA/NbLKcrlMI0lbnIrax02jdyTo7rBUJfTwuBs5QeQ25+VfaBcz9fWSv4Z8Bk\n"
+ + "9+w61bQZLQkExZ9W7hnhaapyR0aT0rY48KGtHOPNoMQu9Si+RnRiI024jMUUjrau\n"
+ + "w/exgCteY261VtCPRgyZOlpbX43rsBhF8ott0ZzSfLwaNTHhsjFsD1uH6TSFO8La\n"
+ + "/H1nO31sORlY3+rCGiQVuYIJD1qI7bEjDHYO0nq/f7JjfYKmVBg9grwLsX3h1qZ2\n"
+ + "L3Yz+0eCi7/6T/Sm7PavQ+EGL7+WBXX3qJpwc+EFNHs6VxQp86k6csba0c5mNcaQ\n"
+ + "iQE3BB8BAgAhBQJWJqusFwyAAQSup+0vghEz5bEo0e0GJdxGMoqMAgcAAAoJEPrS\n"
+ + "q+I9bFLQ2BYH/jm+t7pZuv8WqZdb8FiBa9CFfhcSKjYarMHjBw7GxWZJMd5VR4DC\n"
+ + "r4T/ZSAGRKBRKQ2uXrkm9H0NPDp0c/UKCHtQMFDnqTk7B63mwSR1d7W0qaRPXYQ1\n"
+ + "bbatnzkEDOj0e+rX6aiqVRMo/q6uMNUFl6UMrUZPSNB5PVRQWPnQ7K11mw3vg0e5\n"
+ + "ycqJbyFvER6EtyDUXGBo8a5/4bK8VBNBMTAIy6GeGpeSM5b7cpQk7/j4dXugCJAV\n"
+ + "fhFNUOgLduoIKM4u+VcFjk3Km/YxOtGi1dLqCbTX/0LiCRA9mgQpyNVyA+Sm48LM\n"
+ + "LUkbcrN/F3SHX1ao/5lm19r8Biu1ziQnLgC0IlRlc3R1c2VyIFNldmVuIDx0ZXN0\n"
+ + "N0BleGFtcGxlLmNvbT6JATgEEwECACIFAlYmq3ECGwMGCwkIBwMCBhUIAgkKCwQW\n"
+ + "AgMBAh4BAheAAAoJEPrSq+I9bFLQvjQH/0K7aBsGU2U/rm4I+u+uPa6BnFTYQJqg\n"
+ + "034pwdD0WfM3M/XgVh7ERjnR9ZViCMVej+K3kW5d2DNaXu5vVpcD6L6jjWwiJHBw\n"
+ + "LIcmpqQrL0TdoCr4F4FKQnBbcH1fNvP8A/hLDHB3k3ERPvEFIo1AkVuK4s/v7yZY\n"
+ + "HAowX0r4ok4ndu/wAc0HI1FkApkAfh18JDTuui53dkKhnkDp7Xnfm/ElAZYjB7Se\n"
+ + "ivxOD9vdhViWSx1VhttPZo5hSyJrEYaJ5u9hsXNUN85DxgLqCmS1v8n3pN1lVY/Q\n"
+ + "TYXtgocakQgHGEG0Tl6a3xpNkn9ihnyCr80mHCxXTyUUBGfygccelB+5AQ0EViar\n"
+ + "cQEIAKxwXb6HGV9QjepADyWW7GMxc2JVZ7pZM2sdf8wrgnQqV2G1rc9gAgwTX4jt\n"
+ + "OY0vSKT1vBq09ZXS3qpYHi/Wwft0KkaX/a7e6vKabDSfhilxC2LuGz2+56f6UOzj\n"
+ + "ggwf5k4LFTQvkDUZumwPjoeC2hqQO3Q/9PW39C6GnvsCr5L0MRdO3PbVJM7lJaOk\n"
+ + "MbGwgysErWgiZXKlxMpIvffIsLC4BAxnjXaCy6zHuBcPMPaRMs7sDRBzeuTV2wnX\n"
+ + "Sd+IXZgdpd1hF7VkuXenzwOqvBGS66C3ILW0ZTFaOtgrloIkTvtYEcJFWvxqWl2F\n"
+ + "+JQ5V6eu2aJ3HIGyr9L1R8MUA6EAEQEAAYkBHwQYAQIACQUCViarcQIbDAAKCRD6\n"
+ + "0qviPWxS0M0PB/9Rbk4/pNW67+uE1lwtaIG7uFiMbJqu8jK6MkD8GdayflroWEZA\n"
+ + "x0Xow9HL8UaRfeRPTZMrDRpjl+fJIXT5qnlB0FPmzSXAKr3piC8migBcbp5m6hWh\n"
+ + "c3ScAqWOeMt9j0TTWHh4hKS8Q+lK392ht65cI/kpFhxm9EEaXmajplNL/2G3PVrl\n"
+ + "fFUgCdOn2DYdVSgJsfBhkcoiy17G3vqtb+We6ulhziae4SIrkUSqdYmRjiFyvqZz\n"
+ + "tmMEoF6CQNCUb1NK0TsSDeIdDacYjUwyq0Qj6TaXrWcbC3kW0GtWoFTNIiX4q9bN\n"
+ + "+B6paw/s8P7XCWznTBRdlFWWgrhcpzQ8fefC\n"
+ + "=CHer\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "bOdMFF2UVZaCuFynNDx958I=\n"
+ + "=aoJv\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * Key revoked by an expired key, after that key's expiration.
+ * <p>
+ * Revoked by {@link #expiredKey()}.
+ *
+ * <pre>
+ * pub 2048R/78BF7D7E 2005-08-01 [revoked: 2015-10-20]
+ * Key fingerprint = 916F AB22 5BE7 7585 F59A 994C 001A DF8B 78BF 7D7E
+ * uid Testuser Eight <test8@example.com>
+ * </pre>
+ */
+ public static TestKey keyRevokedByExpiredKeyAfterExpiration() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "0d0PyzZT3676mQwmFoebMR9vACAeHjvDxD4F\n"
+ + "=ihWb\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "DCYWh5sxH28AIB4eO8PEPgU=\n"
+ + "=cSfw\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * Key revoked by an expired key, before that key's expiration.
+ * <p>
+ * Revoked by {@link #expiredKey()}.
+ *
+ * <pre>
+ * pub 2048R/C43BF2E1 2005-08-01 [revoked: 2005-08-01]
+ * Key fingerprint = 916D 6AD6 36A5 CBA6 B5A6 7274 6040 8661 C43B F2E1
+ * uid Testuser Nine <test9@example.com>
+ * </pre>
+ */
+ public static TestKey keyRevokedByExpiredKeyBeforeExpiration() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "REW68wsnWjwTtrPG8bqo6cCsOzqGYVt81eU=\n"
+ + "=FnZg\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "s8bxuqjpwKw7OoZhW3zV5Q==\n"
+ + "=JxsF\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+}
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestTrustKeys.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestTrustKeys.java
new file mode 100644
index 0000000..55bb9c2
--- /dev/null
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/testutil/TestTrustKeys.java
@@ -0,0 +1,1047 @@
+// Copyright (C) 2015 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.gpg.testutil;
+
+/**
+ * Test keys specific to web-of-trust checks.
+ * <p>
+ * In the following diagrams, the notation <code>M---N</code> indicates N trusts
+ * M, and an 'x' indicates the key is expired.
+ * <p>
+ *
+ * <pre>
+ * A---Bx
+ * \
+ * \---C---D
+ * \
+ * \---Ex
+ *
+ * D and E trust C to be a valid introducer of depth 2.
+ *
+ * F---G---F, in a cycle.
+ *
+ * H---I---J, but J is only trusted to length 1.
+ * </pre>
+ */
+public class TestTrustKeys {
+ /**
+ * pub 2048R/9FD0D396 2010-08-29
+ * Key fingerprint = E401 17FC 4BF4 17BD 8F93 DEB1 D25A D07A 9FD0 D396
+ * uid Testuser A <testa@example.com>
+ * sub 2048R/F5C099DB 2010-08-29
+ */
+ public static TestKey keyA() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=DAMW\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=FLdD\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/B007D490 2010-08-29 [expired: 2011-08-29]
+ * Key fingerprint = 355D 5B98 FECE 6199 83CD C91D 5760 6987 B007 D490
+ * uid Testuser B <testb@example.com>
+ */
+ public static TestKey keyB() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBEx6ntMBCADG7j/nuI+VvvNbPY9nnfLrAc3KTj0Z+DMxxUMYoZNLjTw1szQ0\n"
+ + "PuKKACiiSA9Oyj4R0aIhWdIR9iYxp6gQdje3yewzoqMwE+t5onYDpdX9QDFXyEzF\n"
+ + "UPWCjA7OSji1G6fyWakiYxKseqyRXOdHXI5TqMikBalmSpwwvmik0cfRGO+l6qvM\n"
+ + "mVJlcn6mkZB0d8WOPV8j8rFxmVSPn9SVP9L8HaFWv1uI9EY3zXbfNeDNgNeTWIMY\n"
+ + "75saINBA2LALBQ54u52GoSbaR8ukZYAjjkif3WIFI8B9xREwjUBLFy3E357aGyLZ\n"
+ + "jE8nsmPk4MDxDaeDNoSHJjcxtDWQJBub3u1zABEBAAG0HlRlc3R1c2VyIEIgPHRl\n"
+ + "c3RiQGV4YW1wbGUuY29tPokBPgQTAQIAKAUCTHqe0wIbAwUJAeEzgAYLCQgHAwIG\n"
+ + "FQgCCQoLBBYCAwECHgECF4AACgkQV2Bph7AH1JD0nQf/Vm+/Mvl99/y3Qw10S6et\n"
+ + "H6NYWDUeAKXe9mfXBJ39HdtlF50jZ5NzSwksAOSQtQZJ3tQQeElXB29cZDvAscva\n"
+ + "RiTtt+KUxDZSYbEHrC0EO7w0Wi5ltwaWdXnoitMOgPZ/grL7UpUbL8rB1evfLbhm\n"
+ + "AqC/6kgHuXeY/7EAzwU3o0wKbmfx1sh8AyQSi4unUwIDCV1RIAP0+ZfJSg0WwGoS\n"
+ + "JB5+lKajtIE6kMn9m8CWM66/zxSCY3XLcoXvjVxCYPwwgYSyje8dDxxOI+x7uj2I\n"
+ + "IjM5RHQ9hTsR7NQ9JUTFmpKZlcdah93NZLKJAFLUtOPjMa5d5t2O0ZOxZ5ftlhHp\n"
+ + "Q7kBDQRMep7TAQgAwOuLBXnACIsd879ld/vLcn8umpKV8MIUjrqOMjR0rNKpCUDw\n"
+ + "LxL4uVh3q/ksESHnQPPqxFYkgeA66SYrx4jwZjbZ5vv9BW99LHe8lSahqrJA9A9g\n"
+ + "5iw5hH+2ZWrGlu3P65UdQUJW+JaDx1IIBt3BbmdGDuKF/ESsy9qxEKq7tKqHI2JL\n"
+ + "Ed+6OIwWblU7ZogfiNpgZJ0lapxTe84mGsD0TowGTu5re/8wIJf1f2q4PuG+L9OZ\n"
+ + "0ZD5i9s1MAxdw4OD+705owPCQnqsr18nH9aUBHWJn9NCXb3jL7QGaId84Yq8SRlK\n"
+ + "wHSRtHLLJoowJ5fXw5UbZcUtRUergxFRwae87wARAQABiQElBBgBAgAPBQJMep7T\n"
+ + "AhsMBQkB4TOAAAoJEFdgaYewB9SQMbsH/iu1HY7OMJxd8EkfxairRNec/v9uEvYQ\n"
+ + "XqfEPw/Hihdef1TY8vB69ymAPd89e1PRDj1m+0/RivO045qFP7lbWMkjKeR9dXXe\n"
+ + "UzIEsTUJ1CNnA7C3fo11NBVg59E0d84bMKQx7n4AZkljgKFKghUb6OJZiWRdh+8W\n"
+ + "0I95JI2R7nMYw3L8/sSGxt+Vjhs9acB1DldbyYbJitYA4fhVZQH9zgeuhQqdCULQ\n"
+ + "ZexpkQqvG0o4iJKO4yeJNHdeM+NwH38wXfzydtEv6Dxz/YZSTwt08p97l6DQ//H7\n"
+ + "wek1LcqeX47YFa9Ftns8Y8fjh4S8Kyi1F6BhZKbsdDqg2hA+0AFv7LA=\n"
+ + "=tmW1\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=uFLT\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/D24FE467 2010-08-29
+ * Key fingerprint = 6C21 10AC F4FC 1C7B F270 C00E 641F 1193 D24F E467
+ * uid Testuser C <testc@example.com>
+ * sub 2048R/DBECD4FA 2010-08-29
+ */
+ public static TestKey keyC() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=LtMR\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=5pIh\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/0FDD3677 2010-08-29
+ * Key fingerprint = C96C 5E9D 669C 448A D1B9 BEB5 A991 E2D5 0FDD 3677
+ * uid Testuser D <testd@example.com>
+ * sub 2048R/CAB81AE0 2010-08-29
+ */
+ public static TestKey keyD() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBEx6nwkBCADuztv2tGhjPljwW46qEhth7ZnkdhYXuctZ6lNQuy5LMaEECE3C\n"
+ + "jvVKY+nBrgsLY2Trts+q+mdooBWvxy/qe5PAQTcPR83KjVS4fYwNMBgeRxBEZAZg\n"
+ + "DFwRRCsRrHost+cMgtzLocQ+vL3+9yTRAIe/WmYwbEDXg/c9JSC7kQbZqaAaOshO\n"
+ + "cIOyeB8/QoYee0fEnBzHMmcd0SB1YpwIvRG6v61lXmgpQ9CbovvXO6ZZyEyCX784\n"
+ + "9xprzqP1y03DPrbhuhBAY8EMf3KGJA1dEcU4+lbGEgmlOe2YSbWoLs7mRLFcq5xx\n"
+ + "JroYMtvXF04k4ZHNZAnT3IZc+lJyCqOp4vXpABEBAAG0HlRlc3R1c2VyIEQgPHRl\n"
+ + "c3RkQGV4YW1wbGUuY29tPokBOAQTAQIAIgUCTHqfCQIbAwYLCQgHAwIGFQgCCQoL\n"
+ + "BBYCAwECHgECF4AACgkQqZHi1Q/dNne/0wgApuPzh4J8p2quCK1ScsJHlgGRojGq\n"
+ + "IDPhZFtPn0p2IAkqr5sAhvZAjd3u9A2DqQ7pwOX7gnGRE7dSrK69IAjfbRMc5k16\n"
+ + "aBK2ADq2YgPEmTToots1A0Tj+LaCFOXYUtEkgAC+RfFIkCdt8z86GIr0kg19Q/vY\n"
+ + "I/LtvThAk28D8yIfDnW49Mc4GGq+qvrOytBaGu3dzW0mjYWGEyl0fdSjNqtKyWN7\n"
+ + "Qw70Kqysaoy1KiPRAgwiPQfMCEx6pVaXuAfgRKaJ18kCNOldpajLgQv6yeY7mhgu\n"
+ + "Q3Qe7xQlAtVObxskcTH2CWggl2dPqSMNieLK0g/ER8PIReGDCBXNSJ4qYbkBDQRM\n"
+ + "ep8JAQgAw/o1nhJPLGlIfEMzOGU0Jjj+DwEyB3QIEEc+WKRvgtGsJ4cbZdaGWBJq\n"
+ + "jSo7e9XC9jA2ih0+Gld0vWV7S0LZ84xXxQeadC+AZBFR+b9ga4aUFIji8Tdi2dWX\n"
+ + "QmY76hHIaF8rs6aJB7lRig735VRLxVHOb194t9KLUzZiEKqd71BvLQyuLqAfTEsT\n"
+ + "GRHgmydaxZbGXz+Z57jbQgm11CQEHX1dtS8uqWb64xrV5GAeuEhRj4R6Yiy7OPNi\n"
+ + "xXHxryH2Jd34pA0cGHYVcTgVjXuZ9FFP2SnXuxABONGAIaJuqg7ozYBa2kOdr0DN\n"
+ + "5Pxy5ocR7R2ZoN0pYD5+Cc7oGHjuCQARAQABiQEfBBgBAgAJBQJMep8JAhsMAAoJ\n"
+ + "EKmR4tUP3TZ369QIAKPlfX2TUfhP3otYiaa24zBJ/cvGljGiSfX0KrausBHH161j\n"
+ + "lraJfLzpe7vSOZhwZwgIY/eKoErAkJwVnX1+dLuOcHaqRDi5gnLqa6Yg9a2LWb4z\n"
+ + "rvgsvbiNUs1o9htOcvcpv7e3UUUcRa8lO+aNkO+VoI6DI8RJ3wIfJayboePRXdfr\n"
+ + "8g9of0jSdIOzlaaBPxA2wYSWXm4kv7QXzZooxuGqhn0+JKuq2+oO9y5QUig+c3oG\n"
+ + "a5mpVblmv5ZL6Gc36kCbeEC8j6JkNT4wnceQwpNUNYtPU186cjy3rAD4C58w0Uvp\n"
+ + "HZZSTc0syLOShQr//We39LUNaX6WF3NmyF8K/OM=\n"
+ + "=YDhQ\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=e1xT\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/4B4387EE 2010-08-29 [expired: 2011-08-29]
+ * Key fingerprint = F01D 677C 8BDB 854E 1054 406E 3B09 B97F 4B43 87EE
+ * uid Testuser E <teste@example.com>
+ */
+ public static TestKey keyE() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBEx6nxoBCADjYOWOFa7ZBJpRuNspRoXBTK0LiK5zqN894b87LgIYEgUM6q5J\n"
+ + "yLNo43x7V+ow1/7BEq0JUAMSQ3uRn2jqXiJskSXvwlFYcTVFb0gY09CSD0ptHvda\n"
+ + "zqYOuM/MU1l9jqmlM+pDw/z0pLTKYmAHi6pKJ64pqccMHPUZHpLywyzSNX+JM86I\n"
+ + "K5KAsyGArtgpT9vfci3idNeXjhMR8rfLPDFbdGvGFOZrYv0cfgTbBpVEWeHjs2FR\n"
+ + "4vHG133AdjdZcvA9Y9VW34ZLeiyBEeFix7+HPVS82rko2kQxZu1UZRu340maKDAo\n"
+ + "+UVirgo0FQ8nNUR+c9oNKgiZtO39IAPJv/WZABEBAAG0HlRlc3R1c2VyIEUgPHRl\n"
+ + "c3RlQGV4YW1wbGUuY29tPokBPgQTAQIAKAUCTHqfGgIbAwUJAeEzgAYLCQgHAwIG\n"
+ + "FQgCCQoLBBYCAwECHgECF4AACgkQOwm5f0tDh+6Fowf9FZgntlW4qc7BHe8zYJ0q\n"
+ + "zoLZrHwCFcaeO3kz53y5Lz3+plMuqVDjoQDOt8DxsPHrXWKiu0qBTjZ28ztN3ef6\n"
+ + "f0MpguTGclvFroevUct0xiyox5r1DfMT8JRvqsojE1XPscR2zJzIgEg3OCPuksT9\n"
+ + "EsHsF+/3RBbsXbQgDpW38g0GzIJI4AiQ/yvG2ON9awN2kzIWoBkthVCGy54lCTGj\n"
+ + "yPhatE7Zu2ABNcerIDstupWww2Psec6pGbPPci8ojc90fzalk3UMXcXHD7m8cTJS\n"
+ + "kgHScOzTElIQqOA1+w6uiHy2oAn+qW7534j6p9Tj+DrSIzUXBedGjXZevaKaurVy\n"
+ + "KLkBDQRMep8aAQgAn5r6toYnEzwDeig8r+t89vqOFtohYcmtyeLeTiTTdAz/xWBW\n"
+ + "HUlqV8sglQ9aINpGtBf37v13RhtU3WkUv8cZMQoRM8P2H3cKDNwkucFO6uKSEQO5\n"
+ + "FdzTm4C4WaoE7QiTRbiekwh7O54mz4Wup6LHuEFQEcSpdRUp8w/qaJIHG9EJad1q\n"
+ + "UEsKNnITW+mWHY3+ccK1hgqPwOPqO3/8QtaipekKOYAtOb+57c1jtDFBZnYIkant\n"
+ + "oKs+kRw0DykXFTyFOMYqaleBMcVG+u7ljwAq18L8Ev+qVIpBIZ5eQ5+6p1w9B69h\n"
+ + "RH0Ebn50ebpoqKOXhN4/bu/wq596y0o4xDB0GQARAQABiQElBBgBAgAPBQJMep8a\n"
+ + "AhsMBQkB4TOAAAoJEDsJuX9LQ4fu0/wH/35/22xina8ktbvGV/kB0pH2LBqeXN/b\n"
+ + "CLdA+CDzfwMDzqG0kU39EJ3Fbux7fj4uMaeiYfbO9U85+NOuDmeH41B2dM9S1AzE\n"
+ + "H+/OiCp/Zf1fdd1qXhsA4Xe5vc/VD9oso9OrZK5CM5u0TPmYFijfVDPNgag6mPnD\n"
+ + "zd8JCsuEj4VEy6NF1KcoCc8edQ8AZ4L6ZQ6qiV24gxLnh8xImVr5YjBKDUCdrl79\n"
+ + "0u4wekfgapSx9Sw9Ycz5dFOL07OOHPiKZwUG0f8td6oJX4Ddxset5JAm1pPcLQHR\n"
+ + "6PRx0hI/Tz7rsAI6O37/BEM15+MVGIgOSLL/SRIpOa0L8qmuUhhS6Bg=\n"
+ + "=uA5x\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "lQOYBEx6nxoBCADjYOWOFa7ZBJpRuNspRoXBTK0LiK5zqN894b87LgIYEgUM6q5J\n"
+ + "yLNo43x7V+ow1/7BEq0JUAMSQ3uRn2jqXiJskSXvwlFYcTVFb0gY09CSD0ptHvda\n"
+ + "zqYOuM/MU1l9jqmlM+pDw/z0pLTKYmAHi6pKJ64pqccMHPUZHpLywyzSNX+JM86I\n"
+ + "K5KAsyGArtgpT9vfci3idNeXjhMR8rfLPDFbdGvGFOZrYv0cfgTbBpVEWeHjs2FR\n"
+ + "4vHG133AdjdZcvA9Y9VW34ZLeiyBEeFix7+HPVS82rko2kQxZu1UZRu340maKDAo\n"
+ + "+UVirgo0FQ8nNUR+c9oNKgiZtO39IAPJv/WZABEBAAEAB/4xKKzYqDVyM/2NN5Mi\n"
+ + "fF3EqegruzRESzlgrqLij5LiU1sGLOLbjunC/pPWMu6t+rTYV0pT3hmb5D0eAcH0\n"
+ + "EcANiuAR0wg1P9yNk36Z54mLWoTzzKMb3dunCSvb+BU8AREKZ4v5dLEGz2lK7DPo\n"
+ + "zbhWaffMiClBpC0VbjfFBo91LrVUVnhRglBYKdPLQm/Lhw5cNCYOw194ZturO+cC\n"
+ + "iQZhGSy52HMoMs4Wr470CeFZvvWaiDCirVLcj4UhMsVANFKsahMARm9c+QrGrkRP\n"
+ + "+654f8M9ptapcQYpGOMmaeZVnpocONXOTkiJd7Hhr4PRUY+QS8C8F0LbmL2ERQbL\n"
+ + "F65RBADkIelztY/8Xy2S0jsW7+xF2ziz9riOR87G6b0wrXDdFz4GHPzLvwsdXOeN\n"
+ + "cODic14d9bf5jtXr9hgbAzx55ANDjOl3jK5qil8Z9qwsrNK9Mz0wT1acQXBwf/5D\n"
+ + "hI/whBK1FsH7Y+wdX64XA3EXmclxB8GZf1JsGXF3jNH30vyS7QQA/ydoMMw8ja9L\n"
+ + "j6MxHtVHcE4A4j6tFljLDuf8icOwwNUfb7SsHTDjUI2+30ZJOv+qISrthsASCSj3\n"
+ + "AN87CGdVR62Xe923DNdW8/moKKDILNaESyOi27qhI5qWrVRgNB5QwbQcSoClUxbj\n"
+ + "V7YZSfrZkiI+GE1gh1QPMOVyCUmqu90D+wc0x0wUj8emX/4xbbujOa5RAvNcNvnD\n"
+ + "mOB2CfPWD10TEeOOlHBhuoy2/GdIl76W0szJaxnzcV82VArllSciCBzpSfkExDZ6\n"
+ + "08hA8GpOsuOmAAPwXWZsb8YZbJeM0ULMgUCGHgvUj1/pGsCVA6c7sPAdkCfAFlmO\n"
+ + "smC9bvpS2VHZPuG0HlRlc3R1c2VyIEUgPHRlc3RlQGV4YW1wbGUuY29tPokBPgQT\n"
+ + "AQIAKAUCTHqfGgIbAwUJAeEzgAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ\n"
+ + "Owm5f0tDh+6Fowf9FZgntlW4qc7BHe8zYJ0qzoLZrHwCFcaeO3kz53y5Lz3+plMu\n"
+ + "qVDjoQDOt8DxsPHrXWKiu0qBTjZ28ztN3ef6f0MpguTGclvFroevUct0xiyox5r1\n"
+ + "DfMT8JRvqsojE1XPscR2zJzIgEg3OCPuksT9EsHsF+/3RBbsXbQgDpW38g0GzIJI\n"
+ + "4AiQ/yvG2ON9awN2kzIWoBkthVCGy54lCTGjyPhatE7Zu2ABNcerIDstupWww2Ps\n"
+ + "ec6pGbPPci8ojc90fzalk3UMXcXHD7m8cTJSkgHScOzTElIQqOA1+w6uiHy2oAn+\n"
+ + "qW7534j6p9Tj+DrSIzUXBedGjXZevaKaurVyKJ0DmARMep8aAQgAn5r6toYnEzwD\n"
+ + "eig8r+t89vqOFtohYcmtyeLeTiTTdAz/xWBWHUlqV8sglQ9aINpGtBf37v13RhtU\n"
+ + "3WkUv8cZMQoRM8P2H3cKDNwkucFO6uKSEQO5FdzTm4C4WaoE7QiTRbiekwh7O54m\n"
+ + "z4Wup6LHuEFQEcSpdRUp8w/qaJIHG9EJad1qUEsKNnITW+mWHY3+ccK1hgqPwOPq\n"
+ + "O3/8QtaipekKOYAtOb+57c1jtDFBZnYIkantoKs+kRw0DykXFTyFOMYqaleBMcVG\n"
+ + "+u7ljwAq18L8Ev+qVIpBIZ5eQ5+6p1w9B69hRH0Ebn50ebpoqKOXhN4/bu/wq596\n"
+ + "y0o4xDB0GQARAQABAAf7Bk9bQCIXo2QJAyhaFd5qh10qhu7CyRnvG/8zKMW98mWd\n"
+ + "KxF+9hNz99qZBCuiNZBLoU0dST6OG6By/3nrDxXxAgZS3cgOj/nl1NJTRWDGHPUu\n"
+ + "LywFgj7Dwu8Y2rqlDTX8lJIS+t8n+BhtkmDHoesGmFtErh8nT/CxQuHLM60qSMgv\n"
+ + "6mSmtOkM+2KfiA5z2o1fDWXjDieW+hdgDPxkaB835wfuDn/Dsn1ch1XHON0xSyTo\n"
+ + "+c35nFXoK1pAXaoalAxZNxcXCAM3NhU37Ih4GejM0K7sSgK72HmgxtNYF77DrTIM\n"
+ + "m5+3960ri1JUuEaJ7ZcqbpKxy/GDldNCYBTx07QMzQQAyYQ+ujT9Pj8zfp1jMLRs\n"
+ + "Xn9GsvYawjo+AIZuHeUmmIXfEoyNmsEUoGHnz9ROLnJzanW5XEStiTys8tHJPIkz\n"
+ + "zL0Ce0oUF93ln0z/jQBIKaSzYB7PMmYCd7ueF94aKqAOrQ/QBb+6JsVjGAtLUoTv\n"
+ + "ey09hGYMogiBV1r0MB2Rsa8EAMrB5VKVQF6+q0XuP6ljFQRaumi4lH7PoQ65E7UD\n"
+ + "6YpyQpLBOE7dV+fHizdUuwsD/wyAOu0EskV1ZLXvXzyk10r3PRoFdpHOvijwZBGt\n"
+ + "jiOiVvK1vkQKDMBczOe74+DaknKn6HzgCsXmLgfk+P8BtLOJnCYsbS9IbnImy2vi\n"
+ + "aJC3A/9wOOK+po8C7JPHVIEfxbe7nwHOoi/h7T4uPrlq/gcQRquqGhQ16nDGYZvX\n"
+ + "ny9aPQ3NcvDR69RM2AaXav03bHVxfhVEyGjP5jLZz7956e4LlnKrsuEhDLfiv30i\n"
+ + "qCC7zNHNA99s5u25vt8AuPVVHfSQ++jifabfv5lU4FHqmK8/4EAoiQElBBgBAgAP\n"
+ + "BQJMep8aAhsMBQkB4TOAAAoJEDsJuX9LQ4fu0/wH/35/22xina8ktbvGV/kB0pH2\n"
+ + "LBqeXN/bCLdA+CDzfwMDzqG0kU39EJ3Fbux7fj4uMaeiYfbO9U85+NOuDmeH41B2\n"
+ + "dM9S1AzEH+/OiCp/Zf1fdd1qXhsA4Xe5vc/VD9oso9OrZK5CM5u0TPmYFijfVDPN\n"
+ + "gag6mPnDzd8JCsuEj4VEy6NF1KcoCc8edQ8AZ4L6ZQ6qiV24gxLnh8xImVr5YjBK\n"
+ + "DUCdrl790u4wekfgapSx9Sw9Ycz5dFOL07OOHPiKZwUG0f8td6oJX4Ddxset5JAm\n"
+ + "1pPcLQHR6PRx0hI/Tz7rsAI6O37/BEM15+MVGIgOSLL/SRIpOa0L8qmuUhhS6Bg=\n"
+ + "=HTKj\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/31FA48C4 2010-09-01
+ * Key fingerprint = 85CE F045 8113 42DA 14A4 42AA 4A9F AC70 31FA 48C4
+ * uid Testuser F <testf@example.com>
+ * sub 2048R/50FF7D5C 2010-09-01
+ */
+ public static TestKey keyF() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBEx+aQkBCADycHmQ8wC4GzaDIvw4uv6/HiTWOUmuLcT06PpsvNBdR2sQ6Vyy\n"
+ + "w1SAnaPmskdgxE7TXpDrwIWPmIkg8KzSfPAap6qZy5zyE1ABQa9yD9v6wsew+lM5\n"
+ + "3UdBO6HQodpWSJMbeR48mUQ96z72B7Lb2GvhFLxvcn5od9jQhbQXfb2k67l33hgR\n"
+ + "D427lxXa+qnmL9pMRGhRw6QATFX+icHxsPfpKnzuk0aY3feJm4+jr4RgHP4djH3i\n"
+ + "NZbv3ibZ24Dj3CK07PwbhqUhZwMqueWbo3ChYjLkRGT/UosNTN0EbHjqBMl4N9OT\n"
+ + "Pl2CM6kuzuaLz3ZeAf48B29GX4rAXfuJTKBbABEBAAG0HlRlc3R1c2VyIEYgPHRl\n"
+ + "c3RmQGV4YW1wbGUuY29tPokBOAQTAQIAIgUCTH5pCQIbAwYLCQgHAwIGFQgCCQoL\n"
+ + "BBYCAwECHgECF4AACgkQSp+scDH6SMRfqggAh//U/l4JuwFgWx14mo0SB9YWU81L\n"
+ + "EgUYUd2MUzvX4p/HIFQa0c7stj68Za40+O0tG/J0RCMNb7piM9JFii+MQZzOVuza\n"
+ + "4bbO59D9qboc7Anvx9hGlfIdinT+n5rwX9kZvD2D7GMskm8ZgovkvNwNKcW+5W/4\n"
+ + "ciWqCJKE/Fp9XsooJgN94pJfgDQ2WBL5KDx1aGt4wZXhH2Atl6a6oVZJIH4SaizD\n"
+ + "jau7F4vc7hBfbcDhxFcrVX1QMpzpl352cIx6KVw4FRWvQ8VKkga4JiQwosfvCT2Z\n"
+ + "pdMwy3cARynv8BWLc4Uexf88QIeClP9ZhoVeMqvHMfUb3d6Q5362VdZqI4kBIAQQ\n"
+ + "AQIACgUCTH5xcgMFCngACgkQiptSk+LTK6UqsAgAlsEmzC3Xxv4o5ui95AFbWZGi\n"
+ + "es5rI9WoW2P+6OqVUy1E8+5HdlJ8wUbU1H7JAdFTjY9rH3vKXCXsTetF4z0cupER\n"
+ + "Rkx06M9/jl5OSw8i9bPNNJFobHwiiNO00ctC1tT5oUVXVsfPQHlEbMofv8jehfgC\n"
+ + "gMqH/ve/aafKFfYCZkNHugRgLzxeDpXp3IdyXoSAFGiULnGvMDN7n61QOvEYOw2Z\n"
+ + "i63ql+bL2oj4G+/bNOkdYkuIBN4F/P45P7xy80MSOvkMH7IG/aFTKMNQGWSykKwI\n"
+ + "FRkC+y+F5Oqf/WD30GvbSA7q013sb6nHYvsaHS/48cgIJ5TSVd0LTlrF9uv43bkB\n"
+ + "DQRMfmkJAQgAzc1uAF4x16Cx4GtHI0Hvm+v7bUEUtBw2XzyOKu883XC5JmGcY18y\n"
+ + "YItRpchAtmacDpu0/2925/mWF7aS9RMgSYI/1D9LaTeimISM3iGFY35kt78NGZwJ\n"
+ + "DeCPJPI1sbOU0njfrCPTbOQuRDJ6evaBNX9HYArSEp0ygruJdOUYgnepCt4A7W95\n"
+ + "EKp9KPo7XV1K8y86vrKbgpJ+NnEi7dzMqVxnhO4wAWqb6HYcKLrEc2gVnLtzHkBl\n"
+ + "Y/6dOP15jgQKql1/yQIXae/WGT24n/VeaKqrbSmDNkhW5eW5o1Bkgy/M98oNHXd0\n"
+ + "nVrT8Lyf6un5TwMy+vk0l5AjMMtIZKS0GQARAQABiQEfBBgBAgAJBQJMfmkJAhsM\n"
+ + "AAoJEEqfrHAx+kjEvDAH/iO6BHQfFa+kqjfYD3NE+FNosXv3jiXOU7SCD2MG3AwD\n"
+ + "YqM+v1n4UvvMLLdEbtboht1Btys1vuyNM3RAmR45oh9Dfuc4SKtVzSCkKs85jNvH\n"
+ + "7Ik8gxZ9ARzJbawNzTLFyLwDdcdX42Umuvh49Pn7Nc7FDYcZLffEcTh9sZ7KyxLY\n"
+ + "qcjtnblx5oOQnYnpBbM61GvgNXC8Z+g9fg0oHRouKXKE/HDKbsN0siEf9XJFJTKd\n"
+ + "Eg1NgoyKWdaV4+pU/fTzZUvvDqOSRx8he5w64dvW9o7WdARq/3vPvHgy0O8fMTSI\n"
+ + "tmcHxCU8l0jptJz181N36Uhmjyc9oC4dn9ceSn6VDbg=\n"
+ + "=WDx2\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=ZLpl\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/E2D32BA5 2010-09-01
+ * Key fingerprint = CB2B 665B 88DA D56A 7009 C15D 8A9B 5293 E2D3 2BA5
+ * uid Testuser G <testg@example.com>
+ * sub 2048R/829DAE8D 2010-09-01
+ */
+ public static TestKey keyG() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=34GE\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=T1WV\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/080E5723 2010-09-01
+ * Key fingerprint = 2957 ABE4 937D A84A 2E5D 31DB 65C4 33C4 080E 5723
+ * uid Testuser H <testh@example.com>
+ * sub 2048R/68C7C262 2010-09-01
+ */
+ public static TestKey keyH() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=lddL\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=voB9\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/5E9AEDD0 2010-09-01
+ * Key fingerprint = 818D 5D0B 4AE2 A4FE A4C3 C44D D022 DA3A 5E9A EDD0
+ * uid Testuser I <testi@example.com>
+ * sub 2048R/0884E452 2010-09-01
+ */
+ public static TestKey keyI() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=SiG3\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=RcWw\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * pub 2048R/C2E6A198 2010-09-01
+ * Key fingerprint = 83AB CE4D 6845 D6DA F7FB AA47 A139 3C46 C2E6 A198
+ * uid Testuser J <testj@example.com>
+ * sub 2048R/863E8ABF 2010-09-01
+ */
+ public static TestKey keyJ() throws Exception {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBEx+aUgBCADczNio9UWnUggUZkdqJye57497oD9vNo9rmtR+i1TkMpVeWaMH\n"
+ + "UWrm1twzIPV9D4lAWLJoG2cYF6nXG1JxsKc9mOIZ6O1WfsopMUU0p+EfU8H/cvdM\n"
+ + "/iccYS6OnNL4/xR1R7hlA4+b/jaOZfzdS3i5jwOf+TtCk7c5qOuFhraiVQ9H1G86\n"
+ + "+LsiWVeXEFc/FXxnESRmbaZFNJrAdJl23eKXRC6az0S5FwMVBvUhRpLwIGDbVT/0\n"
+ + "/QwaNUOq3bYwudPREFLg/1HtBuxNhRdV6mCrit+tsPan9o0/WtsHuq8n4/pqOKBc\n"
+ + "RRmOIQR9SEohE2TuVT3XVFpMXa4a4CBuNXjTABEBAAG0HlRlc3R1c2VyIEogPHRl\n"
+ + "c3RqQGV4YW1wbGUuY29tPokBOAQTAQIAIgUCTH5pSAIbAwYLCQgHAwIGFQgCCQoL\n"
+ + "BBYCAwECHgECF4AACgkQoTk8RsLmoZi0BggAlnbCwmwaLwcpU9YcOE9/8KF56dIs\n"
+ + "XhdxzqdP91UmhVT0df1OBhgTqFkKprBLCT+B9yBClsnyXMatkvuhQG6C7lw9toMO\n"
+ + "TITRPZoFJe3Ezi+HRRPqAPubIcSgeILuilvFhkoUOgoC1ubmVPgcGBLb8tdvI3bA\n"
+ + "svq+n2jaYUlgL5N6ZNRNakc07e8vH5SeKiD8ZntJlTU49fkxzlawtDaI3+GhyUiB\n"
+ + "0Ah8pl143DFNAq8CfvQCPKwX4WFPkEflh0LlgaEPJUZ/H6KxKXXF8SC9cD2VIii8\n"
+ + "Yrue8y9T+j5y699A0GCptb1IKrgxbfhgD//3g3l1eXsEwn2cwFNCt7pZFLkBDQRM\n"
+ + "fmlIAQgA3E2pM6oDJGgfxbqSfykuRtTbiAi7JEd1DNvEe6gJ7qkBLM4ipILBD0qR\n"
+ + "qCwL37E4/3nMsZjA7GIFLQj2DrFW3aEEKwR/zdh7R67lo9CunCY+FPWTuOkCG8Sh\n"
+ + "3RLpbAV6I61NG/wDznW30vmKNJDgPpkzYj8u0T4MtpywEgxTxCqWZKCufWDRfNAy\n"
+ + "IBLt+piG+bcYKfw9pS8PvXPQMNIi4U2pu3hb/BHC3Y1A8FVpEe4CFV7rWb/K2Ydx\n"
+ + "eBxwwxm9sBxF+vhlI+ZEeb9JxGH6jYlc6twD4e6p3KqusAKLKiLsS5uLQnpMGGZ8\n"
+ + "vcpTSfyHjG2QHc3qG9S/yDCZjhhe2QARAQABiQEfBBgBAgAJBQJMfmlIAhsMAAoJ\n"
+ + "EKE5PEbC5qGYClMIANTdZ+/g/FPl1Lm0tO1CSnHVHekeGNA9n3L6SGiSZQJjEDo0\n"
+ + "gsye5xgxh5JGKf7CqbEFfeLC9Jx5W5EN4dVFudncIlC/SutfRzdt5W8CLXMl0c41\n"
+ + "5FmtpWNStk3MglkcjE5PrRRrSiRc45S0e2kIPb8eiVKg98/rCToC9+Qn3pMi/fcM\n"
+ + "LVpYE+dhvB5EhOSwBWWgvWXzeLhv5CnBKxH0ItHhNwvt8qPOHgQAJKPc6dV888xn\n"
+ + "Sew62LFefHPnGTHLP8RRgVIvZBG5IoovxSz89QGHQZiC4xv00I7zNwmtr6eEcl+y\n"
+ + "BkUK9QWITEBHUDqR+cbVa2dRr3fUHwRP7G2G+ow=\n"
+ + "=ucAX\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=NiQI\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ private TestTrustKeys() {
+ }
+}
diff --git a/gerrit-gwtdebug/BUCK b/gerrit-gwtdebug/BUCK
index bf05af0..3670916 100644
--- a/gerrit-gwtdebug/BUCK
+++ b/gerrit-gwtdebug/BUCK
@@ -2,6 +2,7 @@
name = 'gwtdebug',
srcs = glob(['src/main/java/**/*.java']),
deps = [
+ '//gerrit-pgm:daemon',
'//gerrit-pgm:pgm',
'//gerrit-pgm:util',
'//gerrit-util-cli:cli',
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
index 5da8b1e..4b6e7e4 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
@@ -274,6 +274,7 @@
try {
t.setText(getText());
content.add(t);
+ t.setFocus(true);
t.selectAll();
boolean ok = execCommand("copy");
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
index 2a79db4..ef4de82 100644
--- a/gerrit-gwtui-common/BUCK
+++ b/gerrit-gwtui-common/BUCK
@@ -64,6 +64,7 @@
deps = [
':client',
'//lib:junit',
+ '//lib/gwt:user',
'//lib/jgit:jgit',
],
source_under_test = [':client'],
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
index 0db7ea4..a5a02cd 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
@@ -104,4 +104,7 @@
@Source("warning.png")
public ImageResource warning();
+
+ @Source("question.png")
+ public ImageResource question();
}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
index 695c126..dd88b04 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
@@ -296,6 +296,7 @@
public final native int _number() /*-{ return this._number; }-*/;
public final native String name() /*-{ return this.name; }-*/;
public final native boolean draft() /*-{ return this.draft || false; }-*/;
+ public final native AccountInfo uploader() /*-{ return this.uploader; }-*/;
public final native boolean isEdit() /*-{ return this._number == 0; }-*/;
public final native CommitInfo commit() /*-{ return this.commit; }-*/;
public final native void setCommit(CommitInfo c) /*-{ this.commit = c; }-*/;
@@ -310,6 +311,9 @@
public final native boolean hasFetch() /*-{ return this.hasOwnProperty('fetch') }-*/;
public final native NativeMap<FetchInfo> fetch() /*-{ return this.fetch; }-*/;
+ public final native boolean hasPushCertificate() /*-{ return this.hasOwnProperty('push_certificate'); }-*/;
+ public final native PushCertificateInfo pushCertificate() /*-{ return this.push_certificate; }-*/;
+
public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
final int editParent = findEditParent(list);
Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
index b21078e..d95f9ef 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
@@ -30,6 +30,15 @@
public final native boolean binary() /*-{ return this.binary || false; }-*/;
public final native String status() /*-{ return this.status; }-*/;
+
+ // JSNI methods cannot have 'long' as a parameter type or a return type and
+ // it's suggested to use double in this case:
+ // http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html#important
+ public final long sizeDelta() {
+ return (long)_sizeDelta();
+ }
+ private final native double _sizeDelta() /*-{ return this.size_delta || 0; }-*/;
+
public final native int _row() /*-{ return this._row }-*/;
public final native void _row(int r) /*-{ this._row = r }-*/;
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
index f0f3b66..55ef892 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
@@ -37,6 +37,7 @@
public final native String allProjects() /*-{ return this.all_projects; }-*/;
public final native String allUsers() /*-{ return this.all_users; }-*/;
public final native String docUrl() /*-{ return this.doc_url; }-*/;
+ public final native boolean editGpgKeys() /*-{ return this.edit_gpg_keys || false; }-*/;
public final native String reportBugUrl() /*-{ return this.report_bug_url; }-*/;
public final native String reportBugText() /*-{ return this.report_bug_text; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/GpgKeyInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GpgKeyInfo.java
similarity index 68%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/account/GpgKeyInfo.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GpgKeyInfo.java
index d1bb426..f7477a1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/GpgKeyInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GpgKeyInfo.java
@@ -12,17 +12,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.client.account;
+package com.google.gerrit.client.info;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
public class GpgKeyInfo extends JavaScriptObject {
+ public enum Status {
+ BAD, OK, TRUSTED;
+ }
+
public final native String id() /*-{ return this.id; }-*/;
public final native String fingerprint() /*-{ return this.fingerprint; }-*/;
public final native JsArrayString userIds() /*-{ return this.user_ids; }-*/;
public final native String key() /*-{ return this.key; }-*/;
+ private final native String statusRaw() /*-{ return this.status; }-*/;
+ public final Status status() {
+ String s = statusRaw();
+ if (s == null) {
+ return null;
+ }
+ return Status.valueOf(s);
+ }
+
+ public final native boolean hasProblems()
+ /*-{ return this.hasOwnProperty('problems'); }-*/;
+ public final native JsArrayString problems() /*-{ return this.problems; }-*/;
+
protected GpgKeyInfo() {
}
}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/PushCertificateInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/PushCertificateInfo.java
new file mode 100644
index 0000000..ebfec1a
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/PushCertificateInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.info;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PushCertificateInfo extends JavaScriptObject {
+ public final native String certificate() /*-{ return this.certificate; }-*/;
+ public final native GpgKeyInfo key() /*-{ return this.key; }-*/;
+
+ protected PushCertificateInfo() {
+ }
+}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/ui/HighlightSuggestion.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/ui/HighlightSuggestion.java
new file mode 100644
index 0000000..10e20bf
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/ui/HighlightSuggestion.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+
+/** A {@code Suggestion} with highlights. */
+public class HighlightSuggestion implements Suggestion {
+
+ private final String keyword;
+ private final String value;
+
+ public HighlightSuggestion(String keyword, String value) {
+ this.keyword = keyword;
+ this.value = value;
+ }
+
+ @Override
+ public String getDisplayString() {
+ int start = 0;
+ int keyLen = keyword.length();
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+ for (;;) {
+ int index = value.indexOf(keyword, start);
+ if (index == -1) {
+ builder.appendEscaped(value.substring(start));
+ break;
+ }
+ builder.appendEscaped(value.substring(start, index));
+ builder.appendHtmlConstant("<strong>");
+ start = index + keyLen;
+ builder.appendEscaped(value.substring(index, start));
+ builder.appendHtmlConstant("</strong>");
+ }
+ return builder.toSafeHtml().asString();
+ }
+
+ @Override
+ public String getReplacementString() {
+ return value;
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java
similarity index 100%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/ui/RemoteSuggestOracle.java
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/question.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/question.png
new file mode 100644
index 0000000..f25fc3f
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/question.png
Binary files differ
diff --git a/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/ui/HighlightSuggestionTest.java b/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/ui/HighlightSuggestionTest.java
new file mode 100644
index 0000000..44ed50b
--- /dev/null
+++ b/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/ui/HighlightSuggestionTest.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class HighlightSuggestionTest {
+
+ @Test
+ public void singleHighlight() throws Exception {
+ String keyword = "key";
+ String value = "somethingkeysomething";
+ HighlightSuggestion suggestion = new HighlightSuggestion(keyword, value);
+ assertEquals(
+ "something<strong>key</strong>something",
+ suggestion.getDisplayString());
+ assertEquals(value, suggestion.getReplacementString());
+ }
+
+ @Test
+ public void noHighlight() throws Exception {
+ String keyword = "key";
+ String value = "something";
+ HighlightSuggestion suggestion = new HighlightSuggestion(keyword, value);
+ assertEquals(value, suggestion.getDisplayString());
+ assertEquals(value, suggestion.getReplacementString());
+ }
+
+ @Test
+ public void doubleHighlight() throws Exception {
+ String keyword = "key";
+ String value = "somethingkeysomethingkeysomething";
+ HighlightSuggestion suggestion = new HighlightSuggestion(keyword, value);
+ assertEquals(
+ "something<strong>key</strong>something<strong>key</strong>something",
+ suggestion.getDisplayString());
+ assertEquals(value, suggestion.getReplacementString());
+ }
+}
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 90fdcab..ead19f4 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -1,7 +1,7 @@
include_defs('//gerrit-gwtui/gwt.defs')
include_defs('//tools/gwt-constants.defs')
-DEPS = [
+DEPS = GWT_TRANSITIVE_DEPS + [
'//gerrit-gwtexpui:CSS',
'//lib:gwtjsonrpc',
'//lib/gwt:dev',
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 946888d..ba3cc4c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -28,6 +28,8 @@
import static com.google.gerrit.common.PageLinks.SETTINGS;
import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
import static com.google.gerrit.common.PageLinks.SETTINGS_CONTACT;
+import static com.google.gerrit.common.PageLinks.SETTINGS_DIFF_PREFERENCES;
+import static com.google.gerrit.common.PageLinks.SETTINGS_EDIT_PREFERENCES;
import static com.google.gerrit.common.PageLinks.SETTINGS_EXTENSION;
import static com.google.gerrit.common.PageLinks.SETTINGS_GPGKEYS;
import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
@@ -41,6 +43,8 @@
import com.google.gerrit.client.account.MyAgreementsScreen;
import com.google.gerrit.client.account.MyContactInformationScreen;
+import com.google.gerrit.client.account.MyDiffPreferencesScreen;
+import com.google.gerrit.client.account.MyEditPreferencesScreen;
import com.google.gerrit.client.account.MyGpgKeysScreen;
import com.google.gerrit.client.account.MyGroupsScreen;
import com.google.gerrit.client.account.MyIdentitiesScreen;
@@ -66,6 +70,7 @@
import com.google.gerrit.client.admin.ProjectInfoScreen;
import com.google.gerrit.client.admin.ProjectListScreen;
import com.google.gerrit.client.admin.ProjectScreen;
+import com.google.gerrit.client.admin.ProjectTagsScreen;
import com.google.gerrit.client.api.ExtensionScreen;
import com.google.gerrit.client.api.ExtensionSettingsScreen;
import com.google.gerrit.client.change.ChangeScreen;
@@ -526,6 +531,14 @@
return new MyPreferencesScreen();
}
+ if (matchExact(SETTINGS_DIFF_PREFERENCES, token)) {
+ return new MyDiffPreferencesScreen();
+ }
+
+ if (matchExact(SETTINGS_EDIT_PREFERENCES, token)) {
+ return new MyEditPreferencesScreen();
+ }
+
if (matchExact(SETTINGS_PROJECTS, token)) {
return new MyWatchedProjectsScreen();
}
@@ -538,7 +551,8 @@
return new MySshKeysScreen();
}
- if (matchExact(SETTINGS_GPGKEYS, token)) {
+ if (matchExact(SETTINGS_GPGKEYS, token)
+ && Gerrit.info().gerrit().editGpgKeys()) {
return new MyGpgKeysScreen();
}
@@ -733,11 +747,16 @@
return new ProjectInfoScreen(k);
}
- if (ProjectScreen.BRANCH.equals(panel)
- || matchPrefix(ProjectScreen.BRANCH, panel)) {
+ if (ProjectScreen.BRANCHES.equals(panel)
+ || matchPrefix(ProjectScreen.BRANCHES, panel)) {
return new ProjectBranchesScreen(k);
}
+ if (ProjectScreen.TAGS.equals(panel)
+ || matchPrefix(ProjectScreen.TAGS, panel)) {
+ return new ProjectTagsScreen(k);
+ }
+
if (ProjectScreen.ACCESS.equals(panel)) {
return new ProjectAccessScreen(k);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 80aa9cc..c77b71f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -17,6 +17,7 @@
import com.google.gerrit.client.info.AccountInfo;
import com.google.gerrit.client.info.AccountPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gwt.i18n.client.NumberFormat;
import java.util.Date;
@@ -107,4 +108,30 @@
private static AccountFormatter createAccountFormatter() {
return new AccountFormatter(Gerrit.info().user().anonymousCowardName());
}
+
+ /** The returned format string doesn't contain any +/- sign. */
+ public static String formatAbsBytes(long bytes) {
+ return formatBytes(bytes, true);
+ }
+
+ public static String formatBytes(long bytes) {
+ return formatBytes(bytes, false);
+ }
+
+ private static String formatBytes(long bytes, boolean abs) {
+ bytes = abs ? Math.abs(bytes) : bytes;
+
+ if (bytes == 0) {
+ return abs ? "0 B" : "+/- 0 B";
+ }
+
+ if (Math.abs(bytes) < 1024) {
+ return (bytes > 0 && !abs ? "+" : "") + bytes + " B";
+ }
+
+ int exp = (int) (Math.log(Math.abs(bytes)) / Math.log(1024));
+ return (bytes > 0 && !abs ? "+" : "")
+ + NumberFormat.getFormat("#.0").format(bytes / Math.pow(1024, exp))
+ + " " + "KMGTPE".charAt(exp - 1) + "iB";
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index d52f2fc..cd53b8e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -20,6 +20,7 @@
import com.google.gerrit.client.account.AccountApi;
import com.google.gerrit.client.account.AccountCapabilities;
+import com.google.gerrit.client.account.EditPreferences;
import com.google.gerrit.client.admin.ProjectScreen;
import com.google.gerrit.client.api.ApiGlue;
import com.google.gerrit.client.api.PluginLoader;
@@ -46,6 +47,7 @@
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.common.data.SystemInfoService;
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GerritTopMenu;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.Project;
@@ -118,6 +120,7 @@
private static HostPageData.Theme myTheme;
private static String defaultScreenToken;
private static AccountDiffPreference myAccountDiffPref;
+ private static EditPreferencesInfo editPrefs;
private static String xGerritAuth;
private static boolean isNoteDbEnabled;
@@ -327,6 +330,15 @@
myAccountDiffPref = accountDiffPref;
}
+ /** @return the edit preferences of the current user, null if not signed-in */
+ public static EditPreferencesInfo getEditPreferences() {
+ return editPrefs;
+ }
+
+ public static void setEditPreferences(EditPreferencesInfo p) {
+ editPrefs = p;
+ }
+
/** @return true if the user is currently authenticated */
public static boolean isSignedIn() {
return xGerritAuth != null;
@@ -390,6 +402,7 @@
static void deleteSessionCookie() {
myAccount = AccountInfo.create(0, null, null, null);
myAccountDiffPref = null;
+ editPrefs = null;
myPrefs = AccountPreferencesInfo.createDefault();
urlAliasMatcher.clearUserAliases();
xGerritAuth = null;
@@ -478,16 +491,26 @@
}
}));
AccountApi.self().view("preferences")
- .get(cbg.addFinal(new GerritCallback<AccountPreferencesInfo>() {
+ .get(cbg.add(new GerritCallback<AccountPreferencesInfo>() {
@Override
public void onSuccess(AccountPreferencesInfo prefs) {
myPrefs = prefs;
onModuleLoad2(result);
}
}));
+ AccountApi.getEditPreferences(
+ cbg.addFinal(new GerritCallback<EditPreferences>() {
+ @Override
+ public void onSuccess(EditPreferences prefs) {
+ EditPreferencesInfo prefsInfo = new EditPreferencesInfo();
+ prefs.copyTo(prefsInfo);
+ editPrefs = prefsInfo;
+ }
+ }));
} else {
myAccount = AccountInfo.create(0, null, null, null);
myPrefs = AccountPreferencesInfo.createDefault();
+ editPrefs = null;
onModuleLoad2(result);
}
}
@@ -697,7 +720,8 @@
menuBars.put(GerritTopMenu.PROJECTS.menuName, projectsBar);
addLink(projectsBar, C.menuProjectsList(), PageLinks.ADMIN_PROJECTS);
projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsInfo(), ProjectScreen.INFO));
- projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsBranches(), ProjectScreen.BRANCH));
+ projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsBranches(), ProjectScreen.BRANCHES));
+ projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsTags(), ProjectScreen.TAGS));
projectsBar.addItem(new ProjectLinkMenuItem(C.menuProjectsAccess(), ProjectScreen.ACCESS));
final LinkMenuItem dashboardsMenuItem =
new ProjectLinkMenuItem(C.menuProjectsDashboards(),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 6bbc8f1..269999c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -77,6 +77,7 @@
String menuProjectsList();
String menuProjectsInfo();
String menuProjectsBranches();
+ String menuProjectsTags();
String menuProjectsAccess();
String menuProjectsDashboards();
String menuProjectsCreate();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 05de983..fb74506 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -60,6 +60,7 @@
menuProjectsList = List
menuProjectsInfo = General
menuProjectsBranches = Branches
+menuProjectsTags = Tags
menuProjectsAccess = Access
menuProjectsDashboards = Dashboards
menuProjectsCreate = Create New Project
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index f010879..d7f878f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -29,6 +29,7 @@
String addWatchPanel();
String avatarInfoPanel();
String bottomheader();
+ String branchTableDeleteButton();
String branchTablePrevNextLinks();
String cAPPROVAL();
String cLastUpdate();
@@ -141,6 +142,7 @@
String needsReview();
String negscore();
String noborder();
+ String pagingLink();
String patchBrowserPopup();
String patchBrowserPopupBody();
String patchCellReverseDiff();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index 85ebeb7..a1bcfe8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -16,6 +16,7 @@
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.info.AccountInfo;
+import com.google.gerrit.client.info.GpgKeyInfo;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
@@ -37,6 +38,17 @@
return new RestApi("/accounts/").view("self");
}
+ /** Retrieve the account edit preferences */
+ public static void getEditPreferences(AsyncCallback<EditPreferences> cb) {
+ self().view("preferences.edit").get(cb);
+ }
+
+ /** Put the account edit preferences */
+ public static void putEditPreferences(EditPreferences in,
+ AsyncCallback<VoidResult> cb) {
+ self().view("preferences.edit").put(in, cb);
+ }
+
public static void suggest(String query, int limit,
AsyncCallback<JsArray<AccountInfo>> cb) {
new RestApi("/accounts/")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index 1bf4034..94884fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -52,6 +52,8 @@
String tabAccountSummary();
String tabAgreements();
String tabContactInformation();
+ String tabDiffPreferences();
+ String tabEditPreferences();
String tabGpgKeys();
String tabHttpAccess();
String tabMyGroups();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index 6d91807..4580aea 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -38,6 +38,8 @@
tabAccountSummary = Profile
tabAgreements = Agreements
tabContactInformation = Contact Information
+tabDiffPreferences = Diff Preferences
+tabEditPreferences = Edit Preferences
tabGpgKeys = GPG Public Keys
tabHttpAccess = HTTP Password
tabMyGroups = Groups
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java
new file mode 100644
index 0000000..6e4b1c7
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java
@@ -0,0 +1,111 @@
+// 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.client.account;
+
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
+import com.google.gerrit.extensions.client.KeyMapType;
+import com.google.gerrit.extensions.client.Theme;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class EditPreferences extends JavaScriptObject {
+ public static EditPreferences create(EditPreferencesInfo in) {
+ EditPreferences p = createObject().cast();
+ p.tabSize(in.tabSize);
+ p.lineLength(in.lineLength);
+ p.cursorBlinkRate(in.cursorBlinkRate);
+ p.hideTopMenu(in.hideTopMenu);
+ p.showTabs(in.showTabs);
+ p.showWhitespaceErrors(in.showWhitespaceErrors);
+ p.syntaxHighlighting(in.syntaxHighlighting);
+ p.hideLineNumbers(in.hideLineNumbers);
+ p.matchBrackets(in.matchBrackets);
+ p.autoCloseBrackets(in.autoCloseBrackets);
+ p.theme(in.theme);
+ p.keyMapType(in.keyMapType);
+ return p;
+ }
+
+ public final void copyTo(EditPreferencesInfo p) {
+ p.tabSize = tabSize();
+ p.lineLength = lineLength();
+ p.cursorBlinkRate = cursorBlinkRate();
+ p.hideTopMenu = hideTopMenu();
+ p.showTabs = showTabs();
+ p.showWhitespaceErrors = showWhitespaceErrors();
+ p.syntaxHighlighting = syntaxHighlighting();
+ p.hideLineNumbers = hideLineNumbers();
+ p.matchBrackets = matchBrackets();
+ p.autoCloseBrackets = autoCloseBrackets();
+ p.theme = theme();
+ p.keyMapType = keyMapType();
+ }
+
+ public final void theme(Theme i) {
+ setThemeRaw(i != null ? i.toString() : Theme.DEFAULT.toString());
+ }
+ private final native void setThemeRaw(String i) /*-{ this.theme = i }-*/;
+
+ public final void keyMapType(KeyMapType i) {
+ setkeyMapTypeRaw(i != null ? i.toString() : KeyMapType.DEFAULT.toString());
+ }
+ private final native void setkeyMapTypeRaw(String i) /*-{ this.key_map_type = i }-*/;
+
+ public final native void tabSize(int t) /*-{ this.tab_size = t }-*/;
+ public final native void lineLength(int c) /*-{ this.line_length = c }-*/;
+ public final native void cursorBlinkRate(int r) /*-{ this.cursor_blink_rate = r }-*/;
+ public final native void hideTopMenu(boolean s) /*-{ this.hide_top_menu = s }-*/;
+ public final native void showTabs(boolean s) /*-{ this.show_tabs = s }-*/;
+ public final native void showWhitespaceErrors(boolean s) /*-{ this.show_whitespace_errors = s }-*/;
+ public final native void syntaxHighlighting(boolean s) /*-{ this.syntax_highlighting = s }-*/;
+ public final native void hideLineNumbers(boolean s) /*-{ this.hide_line_numbers = s }-*/;
+ public final native void matchBrackets(boolean m) /*-{ this.match_brackets = m }-*/;
+ public final native void autoCloseBrackets(boolean c) /*-{ this.auto_close_brackets = c }-*/;
+
+ public final Theme theme() {
+ String s = themeRaw();
+ return s != null ? Theme.valueOf(s) : Theme.DEFAULT;
+ }
+ private final native String themeRaw() /*-{ return this.theme }-*/;
+
+ public final KeyMapType keyMapType() {
+ String s = keyMapTypeRaw();
+ return s != null ? KeyMapType.valueOf(s) : KeyMapType.DEFAULT;
+ }
+ private final native String keyMapTypeRaw() /*-{ return this.key_map_type }-*/;
+
+ public final int tabSize() {
+ return get("tab_size", 8);
+ }
+
+ public final int lineLength() {
+ return get("line_length", 100);
+ }
+
+ public final int cursorBlinkRate() {
+ return get("cursor_blink_rate", 0);
+ }
+
+ public final native boolean hideTopMenu() /*-{ return this.hide_top_menu || false }-*/;
+ public final native boolean showTabs() /*-{ return this.show_tabs || false }-*/;
+ public final native boolean showWhitespaceErrors() /*-{ return this.show_whitespace_errors || false }-*/;
+ public final native boolean syntaxHighlighting() /*-{ return this.syntax_highlighting || false }-*/;
+ public final native boolean hideLineNumbers() /*-{ return this.hide_line_numbers || false }-*/;
+ public final native boolean matchBrackets() /*-{ return this.match_brackets || false }-*/;
+ public final native boolean autoCloseBrackets() /*-{ return this.auto_close_brackets || false }-*/;
+ private final native int get(String n, int d) /*-{ return this.hasOwnProperty(n) ? this[n] : d }-*/;
+
+ protected EditPreferences() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyDiffPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyDiffPreferencesScreen.java
new file mode 100644
index 0000000..73d4ca0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyDiffPreferencesScreen.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.account;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.diff.PreferencesBox;
+import com.google.gwt.user.client.ui.FlowPanel;
+
+public class MyDiffPreferencesScreen extends SettingsScreen {
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ PreferencesBox pb = new PreferencesBox(null);
+ pb.set(DiffPreferences.create(Gerrit.getAccountDiffPreference()));
+ FlowPanel p = new FlowPanel();
+ p.setStyleName(pb.getStyle().dialog());
+ p.add(pb);
+ add(p);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ display();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyEditPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyEditPreferencesScreen.java
new file mode 100644
index 0000000..424b5d5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyEditPreferencesScreen.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.account;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.editor.EditPreferencesBox;
+import com.google.gwt.user.client.ui.FlowPanel;
+
+public class MyEditPreferencesScreen extends SettingsScreen {
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+
+ EditPreferencesBox pb = new EditPreferencesBox(null);
+ pb.set(EditPreferences.create(Gerrit.getEditPreferences()));
+ FlowPanel p = new FlowPanel();
+ p.setStyleName(pb.getStyle().dialog());
+ p.add(pb);
+ add(p);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ display();
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
index dc35b1e..99d791b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
@@ -15,6 +15,7 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.info.GpgKeyInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
index 2f3a819..e8c58ef 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -37,6 +37,8 @@
linkByGerrit(Util.C.tabAccountSummary(), PageLinks.SETTINGS);
linkByGerrit(Util.C.tabPreferences(), PageLinks.SETTINGS_PREFERENCES);
+ linkByGerrit(Util.C.tabDiffPreferences(), PageLinks.SETTINGS_DIFF_PREFERENCES);
+ linkByGerrit(Util.C.tabEditPreferences(), PageLinks.SETTINGS_EDIT_PREFERENCES);
linkByGerrit(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
linkByGerrit(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
if (Gerrit.info().hasSshd()) {
@@ -45,7 +47,7 @@
if (Gerrit.info().auth().isHttpPasswordSettingsEnabled()) {
linkByGerrit(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
}
- if (Gerrit.info().receive().enableSignedPush()) {
+ if (Gerrit.info().gerrit().editGpgKeys()) {
linkByGerrit(Util.C.tabGpgKeys(), PageLinks.SETTINGS_GPGKEYS);
}
linkByGerrit(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
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 66a64b4..511be5f 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
@@ -43,6 +43,7 @@
String useSignedOffBy();
String createNewChangeForAllNotInTarget();
String enableSignedPush();
+ String requireSignedPush();
String requireChangeID();
String headingMaxObjectSizeLimit();
String headingGroupOptions();
@@ -104,6 +105,7 @@
String buttonDeleteBranch();
String saveHeadButton();
String cancelHeadButton();
+ String columnTagName();
String groupItemHelp();
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 952ea5f..7a8888c 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
@@ -25,6 +25,7 @@
useSignedOffBy = Require <code>Signed-off-by</code> in commit message
createNewChangeForAllNotInTarget = Create a new change for every commit not in the target branch
enableSignedPush = Enable signed push
+requireSignedPush = Require signed push
requireChangeID = Require <code>Change-Id</code> in commit message
headingMaxObjectSizeLimit = Maximum Git object size limit
headingGroupOptions = Group Options
@@ -83,6 +84,7 @@
buttonDeleteBranch = Delete
saveHeadButton = Save
cancelHeadButton = Cancel
+columnTagName = Tag Name
groupItemHelp = group
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 134f869..bfe7787 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
@@ -20,6 +20,7 @@
import com.google.gerrit.client.groups.GroupMap;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.ui.PagingHyperlink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -97,10 +98,10 @@
setPageTitle(Util.C.groupListTitle());
initPageHeader();
- prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
+ prev = PagingHyperlink.createPrev();
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedListNext(), true, "");
+ next = PagingHyperlink.createNext();
next.setVisible(false);
groups = new GroupTable(PageLinks.ADMIN_GROUPS);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PaginatedProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PaginatedProjectScreen.java
new file mode 100644
index 0000000..6349803
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PaginatedProjectScreen.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.admin;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.http.client.URL;
+
+abstract class PaginatedProjectScreen extends ProjectScreen {
+ protected int pageSize;
+ protected String match;
+ protected int start;
+
+ PaginatedProjectScreen(Project.NameKey toShow) {
+ super(toShow);
+ pageSize = Gerrit.getUserPreferences().changesPerPage();
+ }
+
+ protected void parseToken(String token) {
+ for (String kvPair : token.split("[,;&/?]")) {
+ String[] kv = kvPair.split("=", 2);
+ if (kv.length != 2 || kv[0].isEmpty()) {
+ continue;
+ }
+
+ if ("filter".equals(kv[0])) {
+ match = URL.decodeQueryString(kv[1]);
+ }
+
+ if ("skip".equals(kv[0])
+ && URL.decodeQueryString(kv[1]).matches("^[\\d]+")) {
+ start = Integer.parseInt(URL.decodeQueryString(kv[1]));
+ }
+ }
+ }
+
+ protected void parseToken() {
+ parseToken(getToken());
+ }
+
+ protected String getTokenForScreen(String filter, int skip) {
+ String token = getScreenToken();
+ if (filter != null && !filter.isEmpty()) {
+ token += "?filter=" + URL.encodeQueryString(filter);
+ }
+ if (skip > 0) {
+ if (token.contains("?filter=")) {
+ token += ",";
+ } else {
+ token += "?";
+ }
+ token += "skip=" + skip;
+ }
+ return token;
+ }
+
+ protected abstract String getScreenToken();
+
+ protected void setupNavigationLink(Hyperlink link, String filter, int skip) {
+ link.setTargetHistoryToken(getTokenForScreen(filter, skip));
+ link.setVisible(true);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 9a13158..1a28685 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -37,6 +37,7 @@
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.OnEditEnabler;
+import com.google.gerrit.client.ui.PagingHyperlink;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
@@ -53,7 +54,6 @@
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
@@ -74,7 +74,7 @@
import java.util.List;
import java.util.Set;
-public class ProjectBranchesScreen extends ProjectScreen {
+public class ProjectBranchesScreen extends PaginatedProjectScreen {
private Hyperlink prev;
private Hyperlink next;
private BranchesTable branchTable;
@@ -83,56 +83,16 @@
private HintTextBox nameTxtBox;
private HintTextBox irevTxtBox;
private FlowPanel addPanel;
- private int pageSize;
- private int start;
private NpTextBox filterTxt;
- private String match;
private Query query;
public ProjectBranchesScreen(final Project.NameKey toShow) {
super(toShow);
- pageSize = Gerrit.getUserPreferences().changesPerPage();
}
- private void parseToken() {
- String token = getToken();
-
- for (String kvPair : token.split("[,;&/?]")) {
- String[] kv = kvPair.split("=", 2);
- if (kv.length != 2 || kv[0].isEmpty()) {
- continue;
- }
-
- if ("filter".equals(kv[0])) {
- match = URL.decodeQueryString(kv[1]);
- }
-
- if ("skip".equals(kv[0])
- && URL.decodeQueryString(kv[1]).matches("^[\\d]+")) {
- start = Integer.parseInt(URL.decodeQueryString(kv[1]));
- }
- }
- }
-
- private void setupNavigationLink(Hyperlink link, String filter, int skip) {
- link.setTargetHistoryToken(getTokenForScreen(filter, skip));
- link.setVisible(true);
- }
-
- private String getTokenForScreen(String filter, int skip) {
- String token = PageLinks.toProjectBranches(getProjectKey());
- if (filter != null && !filter.isEmpty()) {
- token += "?filter=" + URL.encodeQueryString(filter);
- }
- if (skip > 0) {
- if (token.contains("?filter=")) {
- token += ",";
- } else {
- token += "?";
- }
- token += "skip=" + skip;
- }
- return token;
+ @Override
+ public String getScreenToken() {
+ return PageLinks.toProjectBranches(getProjectKey());
}
@Override
@@ -147,7 +107,7 @@
}
});
query = new Query(match).start(start).run();
- savedPanel = BRANCH;
+ savedPanel = BRANCHES;
}
private void updateForm() {
@@ -162,10 +122,10 @@
super.onInitUI();
initPageHeader();
- prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
+ prev = PagingHyperlink.createPrev();
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedListNext(), true, "");
+ next = PagingHyperlink.createNext();
next.setVisible(false);
addPanel = new FlowPanel();
@@ -215,6 +175,7 @@
branchTable = new BranchesTable();
delBranch = new Button(Util.C.buttonDeleteBranch());
+ delBranch.setStyleName(Gerrit.RESOURCES.css().branchTableDeleteButton());
delBranch.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index eefd199..c6bd1d1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -84,6 +84,7 @@
private ListBox contentMerge;
private ListBox newChangeForAllNotInTarget;
private ListBox enableSignedPush;
+ private ListBox requireSignedPush;
private NpTextBox maxObjectSizeLimit;
private Label effectiveMaxObjectSizeLimit;
private Map<String, Map<String, HasEnabled>> pluginConfigWidgets;
@@ -247,6 +248,9 @@
enableSignedPush = newInheritedBooleanBox();
saveEnabler.listenTo(enableSignedPush);
grid.add(Util.C.enableSignedPush(), enableSignedPush);
+ requireSignedPush = newInheritedBooleanBox();
+ saveEnabler.listenTo(requireSignedPush);
+ grid.add(Util.C.requireSignedPush(), requireSignedPush);
}
maxObjectSizeLimit = new NpTextBox();
@@ -326,6 +330,9 @@
}
private void setBool(ListBox box, InheritedBooleanInfo inheritedBoolean) {
+ if (box == null) {
+ return;
+ }
int inheritedIndex = -1;
for (int i = 0; i < box.getItemCount(); i++) {
if (box.getValue(i).startsWith(InheritableBoolean.INHERIT.name())) {
@@ -372,8 +379,9 @@
setBool(contentMerge, result.useContentMerge());
setBool(newChangeForAllNotInTarget, result.createNewChangeForAllNotInTarget());
setBool(requireChangeID, result.requireChangeId());
- if (enableSignedPush != null) {
+ if (Gerrit.info().receive().enableSignedPush()) {
setBool(enableSignedPush, result.enableSignedPush());
+ setBool(requireSignedPush, result.requireSignedPush());
}
setSubmitType(result.submitType());
setState(result.state());
@@ -644,12 +652,14 @@
private void doSave() {
enableForm(false);
saveProject.setEnabled(false);
- InheritableBoolean sp = enableSignedPush != null
+ InheritableBoolean esp = enableSignedPush != null
? getBool(enableSignedPush) : null;
+ InheritableBoolean rsp = requireSignedPush != null
+ ? getBool(requireSignedPush) : null;
ProjectApi.setConfig(getProjectKey(), descTxt.getText().trim(),
getBool(contributorAgreements), getBool(contentMerge),
getBool(signedOffBy), getBool(newChangeForAllNotInTarget), getBool(requireChangeID),
- sp,
+ esp, rsp,
maxObjectSizeLimit.getText().trim(),
SubmitType.valueOf(submitType.getValue(submitType.getSelectedIndex())),
ProjectState.valueOf(state.getValue(state.getSelectedIndex())),
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 f9904cd..4503265 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
@@ -26,14 +26,13 @@
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.HighlightingInlineHyperlink;
import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.ui.PagingHyperlink;
import com.google.gerrit.client.ui.ProjectSearchLink;
import com.google.gerrit.client.ui.ProjectsTable;
-import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
@@ -44,37 +43,21 @@
import java.util.List;
-public class ProjectListScreen extends Screen {
+public class ProjectListScreen extends PaginatedProjectScreen {
private Hyperlink prev;
private Hyperlink next;
private ProjectsTable projects;
private NpTextBox filterTxt;
- private int pageSize;
- private String match = "";
- private int start;
private Query query;
public ProjectListScreen() {
- pageSize = Gerrit.getUserPreferences().changesPerPage();
+ super(null);
}
public ProjectListScreen(String params) {
this();
- for (String kvPair : params.split("[,;&]")) {
- String[] kv = kvPair.split("=", 2);
- if (kv.length != 2 || kv[0].isEmpty()) {
- continue;
- }
-
- if ("filter".equals(kv[0])) {
- match = URL.decodeQueryString(kv[1]);
- }
-
- if ("skip".equals(kv[0]) && URL.decodeQueryString(kv[1]).matches("^[\\d]+")) {
- start = Integer.parseInt(URL.decodeQueryString(kv[1]));
- }
- }
+ parseToken(params);
}
@Override
@@ -83,25 +66,9 @@
query = new Query(match).start(start).run();
}
- private void setupNavigationLink(Hyperlink link, String filter, int skip) {
- link.setTargetHistoryToken(getTokenForScreen(filter, skip));
- link.setVisible(true);
- }
-
- private String getTokenForScreen(String filter, int skip) {
- String token = ADMIN_PROJECTS;
- if (filter != null && !filter.isEmpty()) {
- token += "?filter=" + URL.encodeQueryString(filter);
- }
- if (skip > 0) {
- if (token.contains("?filter=")) {
- token += ",";
- } else {
- token += "?";
- }
- token += "skip=" + skip;
- }
- return token;
+ @Override
+ public String getScreenToken() {
+ return ADMIN_PROJECTS;
}
@Override
@@ -110,10 +77,10 @@
setPageTitle(Util.C.projectListTitle());
initPageHeader();
- prev = new Hyperlink(Util.C.pagedListPrev(), true, "");
+ prev = PagingHyperlink.createPrev();
prev.setVisible(false);
- next = new Hyperlink(Util.C.pagedListNext(), true, "");
+ next = PagingHyperlink.createNext();
next.setVisible(false);
projects = new ProjectsTable() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
index fdf3ab8..a63dae4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectScreen.java
@@ -19,9 +19,10 @@
public abstract class ProjectScreen extends Screen {
public static final String INFO = "info";
- public static final String BRANCH = "branches";
+ public static final String BRANCHES = "branches";
public static final String ACCESS = "access";
public static final String DASHBOARDS = "dashboards";
+ public static final String TAGS = "tags";
protected static String savedPanel;
protected static Project.NameKey savedKey;
@@ -47,7 +48,9 @@
@Override
protected void onInitUI() {
super.onInitUI();
- setPageTitle(Util.M.project(name.get()));
+ if (name != null) {
+ setPageTitle(Util.M.project(name.get()));
+ }
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
new file mode 100644
index 0000000..1b5d2e8
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
@@ -0,0 +1,233 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.admin;
+
+import static com.google.gerrit.client.ui.Util.highlight;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.projects.ProjectApi;
+import com.google.gerrit.client.projects.TagInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.client.ui.PagingHyperlink;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.InlineHTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+import java.util.List;
+
+public class ProjectTagsScreen extends PaginatedProjectScreen {
+ private NpTextBox filterTxt;
+ private Query query;
+ private Hyperlink prev;
+ private Hyperlink next;
+ private TagsTable tagsTable;
+
+ public ProjectTagsScreen(Project.NameKey toShow) {
+ super(toShow);
+ }
+
+ @Override
+ public String getScreenToken() {
+ return PageLinks.toProjectTags(getProjectKey());
+ }
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ initPageHeader();
+ prev = PagingHyperlink.createPrev();
+ prev.setVisible(false);
+
+ next = PagingHyperlink.createNext();
+ next.setVisible(false);
+
+ tagsTable = new TagsTable();
+
+ HorizontalPanel buttons = new HorizontalPanel();
+ buttons.setStyleName(Gerrit.RESOURCES.css().branchTablePrevNextLinks());
+ buttons.add(prev);
+ buttons.add(next);
+ add(tagsTable);
+ add(buttons);
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ query = new Query(match).start(start).run();
+ savedPanel = TAGS;
+ }
+
+ private void initPageHeader() {
+ parseToken();
+ HorizontalPanel hp = new HorizontalPanel();
+ hp.setStyleName(Gerrit.RESOURCES.css().projectFilterPanel());
+ Label filterLabel = new Label(Util.C.projectFilter());
+ filterLabel.setStyleName(Gerrit.RESOURCES.css().projectFilterLabel());
+ hp.add(filterLabel);
+ filterTxt = new NpTextBox();
+ filterTxt.setValue(match);
+ filterTxt.addKeyUpHandler(new KeyUpHandler() {
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ Query q = new Query(filterTxt.getValue());
+ if (match.equals(q.qMatch)) {
+ q.start(start);
+ } else if (query == null) {
+ q.run();
+ query = q;
+ }
+ }
+ });
+ hp.add(filterTxt);
+ add(hp);
+ }
+
+ private class TagsTable extends NavigationTable<TagInfo> {
+
+ TagsTable() {
+ table.setWidth("");
+ table.setText(0, 0, Util.C.columnTagName());
+ table.setText(0, 1, Util.C.columnBranchRevision());
+
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
+ }
+
+ void display(List<TagInfo> tags) {
+ displaySubset(tags, 0, tags.size());
+ }
+
+ void displaySubset(List<TagInfo> tags, int fromIndex, int toIndex) {
+ while (1 < table.getRowCount()) {
+ table.removeRow(table.getRowCount() - 1);
+ }
+
+ for (TagInfo k : tags.subList(fromIndex, toIndex)) {
+ int row = table.getRowCount();
+ table.insertRow(row);
+ applyDataRowStyle(row);
+ populate(row, k);
+ }
+ }
+
+ void populate(int row, TagInfo k) {
+ table.setWidget(row, 0, new InlineHTML(highlight(k.getShortName(), match)));
+
+ if (k.revision() != null) {
+ table.setText(row, 1, k.revision());
+ } else {
+ table.setText(row, 1, "");
+ }
+
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ String dataCellStyle = Gerrit.RESOURCES.css().dataCell();
+ fmt.addStyleName(row, 0, dataCellStyle);
+ fmt.addStyleName(row, 1, dataCellStyle);
+
+ setRowItem(row, k);
+ }
+
+ @Override
+ protected void onOpenRow(int row) {
+ if (row > 0) {
+ movePointerTo(row);
+ }
+ }
+
+ @Override
+ protected Object getRowItemKey(TagInfo item) {
+ return item.ref();
+ }
+ }
+
+ @Override
+ public void onShowView() {
+ super.onShowView();
+ if (match != null) {
+ filterTxt.setCursorPos(match.length());
+ }
+ filterTxt.setFocus(true);
+ }
+
+ private class Query {
+ private String qMatch;
+ private int qStart;
+
+ Query(String match) {
+ this.qMatch = match;
+ }
+
+ Query start(int start) {
+ this.qStart = start;
+ return this;
+ }
+
+ Query run() {
+ // Retrieve one more tag than page size to determine if there are more
+ // tags to display
+ ProjectApi.getTags(getProjectKey(), pageSize + 1, qStart, qMatch,
+ new ScreenLoadCallback<JsArray<TagInfo>>(ProjectTagsScreen.this) {
+ @Override
+ public void preDisplay(JsArray<TagInfo> result) {
+ if (!isAttached()) {
+ // View has been disposed.
+ } else if (query == Query.this) {
+ query = null;
+ showList(result);
+ } else {
+ query.run();
+ }
+ }
+ });
+ return this;
+ }
+
+ void showList(JsArray<TagInfo> result) {
+ setToken(getTokenForScreen(qMatch, qStart));
+ ProjectTagsScreen.this.match = qMatch;
+ ProjectTagsScreen.this.start = qStart;
+
+ if (result.length() <= pageSize) {
+ tagsTable.display(Natives.asList(result));
+ next.setVisible(false);
+ } else {
+ tagsTable.displaySubset(Natives.asList(result), 0,
+ result.length() - 1);
+ setupNavigationLink(next, qMatch, qStart + pageSize);
+ }
+ if (qStart > 0) {
+ setupNavigationLink(prev, qMatch, qStart - pageSize);
+ } else {
+ prev.setVisible(false);
+ }
+
+ if (!isCurrentView()) {
+ display();
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index b9ea8e0..eabf0e5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -28,6 +28,7 @@
import com.google.gerrit.client.changes.StarredChanges;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.diff.DiffApi;
+import com.google.gerrit.client.info.AccountInfo;
import com.google.gerrit.client.info.AccountInfo.AvatarInfo;
import com.google.gerrit.client.info.ActionInfo;
import com.google.gerrit.client.info.ChangeInfo;
@@ -37,6 +38,8 @@
import com.google.gerrit.client.info.ChangeInfo.MessageInfo;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.info.FileInfo;
+import com.google.gerrit.client.info.GpgKeyInfo;
+import com.google.gerrit.client.info.PushCertificateInfo;
import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.projects.ConfigInfoCache.Entry;
import com.google.gerrit.client.rpc.CallbackGroup;
@@ -66,6 +69,7 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -86,6 +90,7 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SimplePanel;
@@ -109,17 +114,18 @@
private static final Binder uiBinder = GWT.create(Binder.class);
interface Style extends CssResource {
- String labelName();
String avatar();
- String label_user();
- String label_ok();
- String label_reject();
+ String hashtagName();
+ String highlight();
+ String labelName();
String label_may();
String label_need();
+ String label_ok();
+ String label_reject();
+ String label_user();
+ String pushCertStatus();
String replyBox();
String selected();
- String highlight();
- String hashtagName();
}
static ChangeScreen get(NativeEvent in) {
@@ -161,8 +167,14 @@
@UiField Reviewers reviewers;
@UiField Hashtags hashtags;
@UiField Element hashtagTableRow;
+
@UiField FlowPanel ownerPanel;
@UiField InlineHyperlink ownerLink;
+
+ @UiField Element uploaderRow;
+ @UiField FlowPanel uploaderPanel;
+ @UiField InlineLabel uploaderName;
+
@UiField Element statusText;
@UiField Image projectSettings;
@UiField AnchorElement projectSettingsLink;
@@ -286,11 +298,19 @@
p.add(extensionPanel);
}
+ private boolean enableSignedPush() {
+ return Gerrit.info().receive().enableSignedPush();
+ }
+
void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) {
RestApi call = ChangeApi.detail(changeId.get());
- ChangeList.addOptions(call, EnumSet.of(
- ListChangesOption.CHANGE_ACTIONS,
- ListChangesOption.ALL_REVISIONS));
+ EnumSet<ListChangesOption> opts = EnumSet.of(
+ ListChangesOption.ALL_REVISIONS,
+ ListChangesOption.CHANGE_ACTIONS);
+ if (enableSignedPush()) {
+ opts.add(ListChangesOption.PUSH_CERTIFICATES);
+ }
+ ChangeList.addOptions(call, opts);
if (!fg) {
call.background();
}
@@ -1140,12 +1160,14 @@
}
private void renderChangeInfo(ChangeInfo info) {
+ RevisionInfo revisionInfo = info.revision(revision);
changeInfo = info;
lastDisplayedUpdate = info.updated();
labels.set(info);
renderOwner(info);
+ renderUploader(info, revisionInfo);
renderActionTextDate(info);
renderDiffBaseListBox(info);
initReplyButton(info, revision);
@@ -1182,7 +1204,7 @@
// render it faster.
if (!info.status().isOpen()
|| !revision.equals(info.currentRevision())
- || info.revision(revision).isEdit()) {
+ || revisionInfo.isEdit()) {
setVisible(strategy, false);
}
@@ -1193,7 +1215,6 @@
quickApprove.setVisible(false);
actions.reloadRevisionActions(emptyMap);
- RevisionInfo revisionInfo = info.revision(revision);
boolean current = revision.equals(info.currentRevision())
&& !revisionInfo.isEdit();
@@ -1231,17 +1252,12 @@
private void renderOwner(ChangeInfo info) {
// TODO info card hover
- String name = info.owner().name() != null
- ? info.owner().name()
- : Gerrit.info().user().anonymousCowardName();
-
+ String name = name(info.owner());
if (info.owner().avatar(AvatarInfo.DEFAULT_SIZE) != null) {
ownerPanel.insert(new AvatarImage(info.owner()), 0);
}
ownerLink.setText(name);
- ownerLink.setTitle(info.owner().email() != null
- ? info.owner().email()
- : name);
+ ownerLink.setTitle(email(info.owner(), name));
ownerLink.setTargetHistoryToken(PageLinks.toAccountQuery(
info.owner().name() != null
? info.owner().name()
@@ -1250,6 +1266,81 @@
: String.valueOf(info.owner()._accountId()), Change.Status.NEW));
}
+ private void renderUploader(ChangeInfo changeInfo, RevisionInfo revInfo) {
+ AccountInfo uploader = revInfo.uploader();
+ boolean isOwner = uploader == null
+ || uploader._accountId() == changeInfo.owner()._accountId();
+ renderPushCertificate(revInfo, isOwner ? ownerPanel : uploaderPanel);
+ if (isOwner) {
+ uploaderRow.getStyle().setDisplay(Display.NONE);
+ return;
+ }
+ uploaderRow.getStyle().setDisplay(Display.TABLE_ROW);
+
+ if (uploader.avatar(AvatarInfo.DEFAULT_SIZE) != null) {
+ uploaderPanel.insert(new AvatarImage(uploader), 0);
+ }
+ String name = name(uploader);
+ uploaderName.setText(name);
+ uploaderName.setTitle(email(uploader, name));
+ }
+
+ private void renderPushCertificate(RevisionInfo revInfo, FlowPanel panel) {
+ if (!enableSignedPush()) {
+ return;
+ }
+ Image status = new Image();
+ panel.add(status);
+ status.setStyleName(style.pushCertStatus());
+ if (!revInfo.hasPushCertificate()
+ || revInfo.pushCertificate().key() == null) {
+ status.setResource(Gerrit.RESOURCES.question());
+ status.setTitle(Util.C.pushCertMissing());
+ return;
+ }
+ PushCertificateInfo certInfo = revInfo.pushCertificate();
+ GpgKeyInfo.Status s = certInfo.key().status();
+ switch (s) {
+ case BAD:
+ status.setResource(Gerrit.RESOURCES.redNot());
+ status.setTitle(problems(Util.C.pushCertBad(), certInfo));
+ break;
+ case OK:
+ status.setResource(Gerrit.RESOURCES.warning());
+ status.setTitle(problems(Util.C.pushCertOk(), certInfo));
+ break;
+ case TRUSTED:
+ status.setResource(Gerrit.RESOURCES.greenCheck());
+ status.setTitle(Util.C.pushCertTrusted());
+ break;
+ }
+ }
+
+ private static String name(AccountInfo info) {
+ return info.name() != null
+ ? info.name()
+ : Gerrit.info().user().anonymousCowardName();
+ }
+
+ private static String email(AccountInfo info, String name) {
+ return info.email() != null ? info.email() : name;
+ }
+
+ private static String problems(String msg, PushCertificateInfo info) {
+ if (info.key() == null
+ || !info.key().hasProblems()
+ || info.key().problems().length() == 0) {
+ return msg;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(msg).append(':');
+ for (String problem : Natives.asList(info.key().problems())) {
+ sb.append('\n').append(problem);
+ }
+ return sb.toString();
+ }
+
private void renderSubmitType(String action) {
try {
SubmitType type = SubmitType.valueOf(action);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
index 06ae4ca..c643072 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
@@ -315,7 +315,7 @@
background-color: trimColor;
}
- .ownerPanel img {
+ .ownerPanel img, .uploaderPanel img {
margin: 0 2px 0 0;
width: 16px;
height: 16px !important;
@@ -334,6 +334,10 @@
.changeExtension {
padding-top: 5px;
}
+
+ .pushCertStatus {
+ padding-left: 5px;
+ }
</ui:style>
<g:HTMLPanel styleName='{style.cs2}'>
@@ -421,6 +425,14 @@
</g:FlowPanel>
</td>
</tr>
+ <tr ui:field='uploaderRow'>
+ <th><ui:msg>Uploader</ui:msg></th>
+ <td>
+ <g:FlowPanel ui:field='uploaderPanel' styleName='{style.uploaderPanel}'>
+ <g:InlineLabel ui:field='uploaderName'/>
+ </g:FlowPanel>
+ </td>
+ </tr>
<tr>
<th><ui:msg>Reviewers</ui:msg></th>
<td>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index ad61446..d1ca517 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -14,6 +14,9 @@
package com.google.gerrit.client.change;
+import static com.google.gerrit.client.FormatUtil.formatAbsBytes;
+import static com.google.gerrit.client.FormatUtil.formatBytes;
+
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
@@ -457,8 +460,12 @@
private ProgressBar meter;
private String lastPath = "";
+ private boolean hasBinaryFile;
+ private boolean hasNonBinaryFile;
private int inserted;
private int deleted;
+ private long bytesInserted;
+ private long bytesDeleted;
private DisplayCommand(NativeMap<FileInfo> map,
JsArray<FileInfo> list,
@@ -513,11 +520,23 @@
private void computeInsertedDeleted() {
inserted = 0;
deleted = 0;
+ bytesInserted = 0;
+ bytesDeleted = 0;
for (int i = 0; i < list.length(); i++) {
FileInfo info = list.get(i);
- if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()) {
- inserted += info.linesInserted();
- deleted += info.linesDeleted();
+ if (!Patch.COMMIT_MSG.equals(info.path())) {
+ if (!info.binary()) {
+ hasNonBinaryFile = true;
+ inserted += info.linesInserted();
+ deleted += info.linesDeleted();
+ } else {
+ hasBinaryFile = true;
+ if (info.sizeDelta() >= 0) {
+ bytesInserted += info.sizeDelta();
+ } else {
+ bytesDeleted += info.sizeDelta();
+ }
+ }
}
}
}
@@ -750,6 +769,8 @@
.append(info.linesDeleted());
}
}
+ } else if (info.binary()) {
+ sb.append(formatBytes(info.sizeDelta()));
}
sb.closeTd();
}
@@ -798,9 +819,18 @@
sb.openTd().setAttribute("colspan", 3).closeTd(); // comments
// delta1
- sb.openTh().setStyleName(R.css().deltaColumn1())
- .append(Util.M.patchTableSize_Modify(inserted, deleted))
- .closeTh();
+ sb.openTh().setStyleName(R.css().deltaColumn1());
+ if (hasNonBinaryFile) {
+ sb.append(Util.M.patchTableSize_Modify(inserted, deleted));
+ }
+ if (hasBinaryFile) {
+ if (hasNonBinaryFile) {
+ sb.br();
+ }
+ sb.append(Util.M.patchTableSize_ModifyBinaryFiles(
+ formatAbsBytes(bytesInserted), formatAbsBytes(bytesDeleted)));
+ }
+ sb.closeTh();
// delta2
sb.openTh().setStyleName(R.css().deltaColumn2());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
index 9bb3a76..8b559e3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
@@ -138,15 +138,9 @@
}
private void setName(boolean open) {
- name.setInnerText(open ? authorName(info) : elide(authorName(info), 20));
- }
-
- private static String elide(final String s, final int len) {
- if (s == null || s.length() <= len || len <= 10) {
- return s;
- }
- int i = (len - 3) / 2;
- return s.substring(0, i) + "..." + s.substring(s.length() - i);
+ name.setInnerText(open
+ ? authorName(info)
+ : com.google.gerrit.common.FormatUtil.elide(authorName(info), 20));
}
void autoOpen() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index ae1a608..9612f71 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -228,11 +228,9 @@
new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
} else {
// TODO(sbeller): show only on latest revision
- if (info.status().isOpen()) {
- ChangeApi.change(info.legacyId().get()).view("submitted_together")
- .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
- info.project(), revision));
- }
+ ChangeApi.change(info.legacyId().get()).view("submitted_together")
+ .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
+ info.project(), revision));
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
index f0101cb..bde9755 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -75,7 +75,7 @@
.deltaColumn1 {
white-space: nowrap;
- text-align: right;
+ text-align: right !important;
}
.deltaColumn2 {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index ca4c633..1fb997f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -202,4 +202,9 @@
String diffAllUnified();
String votable();
+
+ String pushCertMissing();
+ String pushCertBad();
+ String pushCertOk();
+ String pushCertTrusted();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index e348161..a5fa7b4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -184,3 +184,8 @@
diffAllUnified = All Unified
votable = Votable:
+
+pushCertMissing = This patch set was created without a push certificate
+pushCertBad = Push certificate is invalid
+pushCertOk = Push certificate is valid, but key is not trusted
+pushCertTrusted = Push certificate is valid and key is trusted
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index 2d3644e..ef74a65 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -34,6 +34,8 @@
String patchTableComments(@PluralCount int count);
String patchTableDrafts(@PluralCount int count);
String patchTableSize_Modify(int insertions, int deletions);
+ String patchTableSize_ModifyBinaryFiles(String bytesInserted,
+ String bytesDeleted);
String patchTableSize_LongModify(int insertions, int deletions);
String patchTableSize_Lines(@PluralCount int insertions);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index c109794..67ef2c3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -17,6 +17,7 @@
patchTableComments = {0} comments
patchTableDrafts = {0} drafts
patchTableSize_Modify = +{0}, -{1}
+patchTableSize_ModifyBinaryFiles = +{0}, -{1}
patchTableSize_LongModify = {0} inserted, {1} deleted
patchTableSize_Lines = {0} lines
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
index 6e65ccd..28812ac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
@@ -52,7 +52,7 @@
}
private static class EmailConfirmationInput extends JavaScriptObject {
- final native void setToken(String t) /*-{ this.t = t; }-*/;
+ final native void setToken(String token) /*-{ this.token = token; }-*/;
static EmailConfirmationInput create() {
return createObject().cast();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
index 4265203..3364490 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -35,6 +35,8 @@
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyDownEvent;
@@ -54,6 +56,7 @@
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ToggleButton;
+import com.google.gwt.user.client.ui.UIObject;
import net.codemirror.mode.ModeInfo;
import net.codemirror.mode.ModeInjector;
@@ -62,11 +65,11 @@
import java.util.Objects;
/** Displays current diff preferences. */
-class PreferencesBox extends Composite {
+public class PreferencesBox extends Composite {
interface Binder extends UiBinder<HTMLPanel, PreferencesBox> {}
private static final Binder uiBinder = GWT.create(Binder.class);
- interface Style extends CssResource {
+ public interface Style extends CssResource {
String dialog();
}
@@ -76,6 +79,7 @@
private Timer updateContextTimer;
@UiField Style style;
+ @UiField Element header;
@UiField Anchor close;
@UiField ListBox ignoreWhitespace;
@UiField NpIntTextBox tabWidth;
@@ -87,6 +91,7 @@
@UiField ToggleButton whitespaceErrors;
@UiField ToggleButton showTabs;
@UiField ToggleButton lineNumbers;
+ @UiField Element leftSideLabel;
@UiField ToggleButton leftSide;
@UiField ToggleButton emptyPane;
@UiField ToggleButton topMenu;
@@ -95,17 +100,24 @@
@UiField ToggleButton expandAllComments;
@UiField ToggleButton renderEntireFile;
@UiField ListBox theme;
+ @UiField Element modeLabel;
@UiField ListBox mode;
@UiField Button apply;
@UiField Button save;
- PreferencesBox(SideBySide view) {
+ public PreferencesBox(SideBySide view) {
this.view = view;
initWidget(uiBinder.createAndBindUi(this));
initIgnoreWhitespace();
initTheme();
- initMode();
+
+ if (view != null) {
+ initMode();
+ } else {
+ UIObject.setVisible(header, false);
+ apply.getElement().getStyle().setVisibility(Visibility.HIDDEN);
+ }
}
@Override
@@ -113,40 +125,47 @@
super.onLoad();
save.setVisible(Gerrit.isSignedIn());
- addDomHandler(new KeyDownHandler() {
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (event.getNativeKeyCode() == KEY_ESCAPE
- || event.getNativeKeyCode() == ',') {
- close();
- }
- }
- }, KeyDownEvent.getType());
- updateContextTimer = new Timer() {
- @Override
- public void run() {
- if (prefs.context() == WHOLE_FILE_CONTEXT) {
- contextEntireFile.setValue(true);
+ if (view != null) {
+ addDomHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (event.getNativeKeyCode() == KEY_ESCAPE
+ || event.getNativeKeyCode() == ',') {
+ close();
+ }
}
- if (view.canRenderEntireFile(prefs)) {
- renderEntireFile.setEnabled(true);
- renderEntireFile.setValue(prefs.renderEntireFile());
- } else {
- renderEntireFile.setValue(false);
- renderEntireFile.setEnabled(false);
+ }, KeyDownEvent.getType());
+
+ updateContextTimer = new Timer() {
+ @Override
+ public void run() {
+ if (prefs.context() == WHOLE_FILE_CONTEXT) {
+ contextEntireFile.setValue(true);
+ }
+ if (view.canRenderEntireFile(prefs)) {
+ renderEntireFile.setEnabled(true);
+ renderEntireFile.setValue(prefs.renderEntireFile());
+ } else {
+ renderEntireFile.setValue(false);
+ renderEntireFile.setEnabled(false);
+ }
+ view.setContext(prefs.context());
}
- view.setContext(prefs.context());
- }
- };
+ };
+ }
}
- void set(DiffPreferences prefs) {
+ public Style getStyle() {
+ return style;
+ }
+
+ public void set(DiffPreferences prefs) {
this.prefs = prefs;
setIgnoreWhitespace(prefs.ignoreWhitespace());
tabWidth.setIntValue(prefs.tabSize());
- if (Patch.COMMIT_MSG.equals(view.getPath())) {
+ if (view != null && Patch.COMMIT_MSG.equals(view.getPath())) {
lineLength.setEnabled(false);
lineLength.setIntValue(72);
} else {
@@ -157,17 +176,22 @@
whitespaceErrors.setValue(prefs.showWhitespaceErrors());
showTabs.setValue(prefs.showTabs());
lineNumbers.setValue(prefs.showLineNumbers());
- leftSide.setValue(view.diffTable.isVisibleA());
emptyPane.setValue(!prefs.hideEmptyPane());
- leftSide.setEnabled(!(prefs.hideEmptyPane()
- && view.diffTable.getChangeType() == ChangeType.ADDED));
+ if (view != null) {
+ leftSide.setValue(view.diffTable.isVisibleA());
+ leftSide.setEnabled(!(prefs.hideEmptyPane()
+ && view.diffTable.getChangeType() == ChangeType.ADDED));
+ } else {
+ UIObject.setVisible(leftSideLabel, false);
+ leftSide.setVisible(false);
+ }
topMenu.setValue(!prefs.hideTopMenu());
autoHideDiffTableHeader.setValue(!prefs.autoHideDiffTableHeader());
manualReview.setValue(prefs.manualReview());
expandAllComments.setValue(prefs.expandAllComments());
setTheme(prefs.theme());
- if (view.canRenderEntireFile(prefs)) {
+ if (view == null || view.canRenderEntireFile(prefs)) {
renderEntireFile.setValue(prefs.renderEntireFile());
renderEntireFile.setEnabled(true);
} else {
@@ -175,22 +199,31 @@
renderEntireFile.setEnabled(false);
}
- mode.setEnabled(prefs.syntaxHighlighting());
- if (prefs.syntaxHighlighting()) {
- setMode(view.getCmFromSide(DisplaySide.B).getStringOption("mode"));
+ if (view != null) {
+ mode.setEnabled(prefs.syntaxHighlighting());
+ if (prefs.syntaxHighlighting()) {
+ setMode(view.getCmFromSide(DisplaySide.B).getStringOption("mode"));
+ }
+ } else {
+ UIObject.setVisible(modeLabel, false);
+ mode.setVisible(false);
}
- switch (view.getIntraLineStatus()) {
- case OFF:
- case OK:
- intralineDifference.setValue(prefs.intralineDifference());
- break;
+ if (view != null) {
+ switch (view.getIntraLineStatus()) {
+ case OFF:
+ case OK:
+ intralineDifference.setValue(prefs.intralineDifference());
+ break;
- case TIMEOUT:
- case FAILURE:
- intralineDifference.setValue(false);
- intralineDifference.setEnabled(false);
- break;
+ case TIMEOUT:
+ case FAILURE:
+ intralineDifference.setValue(false);
+ intralineDifference.setEnabled(false);
+ break;
+ }
+ } else {
+ intralineDifference.setValue(prefs.intralineDifference());
}
if (prefs.context() == WHOLE_FILE_CONTEXT) {
@@ -207,13 +240,17 @@
void onIgnoreWhitespace(@SuppressWarnings("unused") ChangeEvent e) {
prefs.ignoreWhitespace(Whitespace.valueOf(
ignoreWhitespace.getValue(ignoreWhitespace.getSelectedIndex())));
- view.reloadDiffInfo();
+ if (view != null) {
+ view.reloadDiffInfo();
+ }
}
@UiHandler("intralineDifference")
void onIntralineDifference(ValueChangeEvent<Boolean> e) {
prefs.intralineDifference(e.getValue());
- view.setShowIntraline(prefs.intralineDifference());
+ if (view != null) {
+ view.setShowIntraline(prefs.intralineDifference());
+ }
}
@UiHandler("context")
@@ -239,7 +276,9 @@
return;
}
prefs.context(c);
- updateContextTimer.schedule(200);
+ if (view != null) {
+ updateContextTimer.schedule(200);
+ }
}
@UiHandler("contextEntireFile")
@@ -257,7 +296,9 @@
context.setFocus(true);
context.setSelectionRange(0, context.getText().length());
}
- updateContextTimer.schedule(200);
+ if (view != null) {
+ updateContextTimer.schedule(200);
+ }
}
@UiHandler("tabWidth")
@@ -265,14 +306,16 @@
String v = e.getValue();
if (v != null && v.length() > 0) {
prefs.tabSize(Math.max(1, Integer.parseInt(v)));
- view.operation(new Runnable() {
- @Override
- public void run() {
- int v = prefs.tabSize();
- view.getCmFromSide(DisplaySide.A).setOption("tabSize", v);
- view.getCmFromSide(DisplaySide.B).setOption("tabSize", v);
- }
- });
+ if (view != null) {
+ view.operation(new Runnable() {
+ @Override
+ public void run() {
+ int v = prefs.tabSize();
+ view.getCmFromSide(DisplaySide.A).setOption("tabSize", v);
+ view.getCmFromSide(DisplaySide.B).setOption("tabSize", v);
+ }
+ });
+ }
}
}
@@ -281,30 +324,38 @@
String v = e.getValue();
if (v != null && v.length() > 0) {
prefs.lineLength(Math.max(1, Integer.parseInt(v)));
- view.operation(new Runnable() {
- @Override
- public void run() {
- view.setLineLength(prefs.lineLength());
- }
- });
+ if (view != null) {
+ view.operation(new Runnable() {
+ @Override
+ public void run() {
+ view.setLineLength(prefs.lineLength());
+ }
+ });
+ }
}
}
@UiHandler("expandAllComments")
void onExpandAllComments(ValueChangeEvent<Boolean> e) {
prefs.expandAllComments(e.getValue());
- view.getCommentManager().setExpandAllComments(prefs.expandAllComments());
+ if (view != null) {
+ view.getCommentManager().setExpandAllComments(prefs.expandAllComments());
+ }
}
@UiHandler("showTabs")
void onShowTabs(ValueChangeEvent<Boolean> e) {
prefs.showTabs(e.getValue());
- view.setShowTabs(prefs.showTabs());
+ if (view != null) {
+ view.setShowTabs(prefs.showTabs());
+ }
}
@UiHandler("lineNumbers")
void onLineNumbers(ValueChangeEvent<Boolean> e) {
prefs.showLineNumbers(e.getValue());
- view.setShowLineNumbers(prefs.showLineNumbers());
+ if (view != null) {
+ view.setShowLineNumbers(prefs.showLineNumbers());
+ }
}
@UiHandler("leftSide")
@@ -315,29 +366,35 @@
@UiHandler("emptyPane")
void onHideEmptyPane(ValueChangeEvent<Boolean> e) {
prefs.hideEmptyPane(!e.getValue());
- view.diffTable.setHideEmptyPane(prefs.hideEmptyPane());
- if (prefs.hideEmptyPane()) {
- if (view.diffTable.getChangeType() == ChangeType.ADDED) {
- leftSide.setValue(false);
- leftSide.setEnabled(false);
+ if (view != null) {
+ view.diffTable.setHideEmptyPane(prefs.hideEmptyPane());
+ if (prefs.hideEmptyPane()) {
+ if (view.diffTable.getChangeType() == ChangeType.ADDED) {
+ leftSide.setValue(false);
+ leftSide.setEnabled(false);
+ }
+ } else {
+ leftSide.setValue(view.diffTable.isVisibleA());
+ leftSide.setEnabled(true);
}
- } else {
- leftSide.setValue(view.diffTable.isVisibleA());
- leftSide.setEnabled(true);
}
}
@UiHandler("topMenu")
void onTopMenu(ValueChangeEvent<Boolean> e) {
prefs.hideTopMenu(!e.getValue());
- Gerrit.setHeaderVisible(!prefs.hideTopMenu());
- view.resizeCodeMirror();
+ if (view != null) {
+ Gerrit.setHeaderVisible(!prefs.hideTopMenu());
+ view.resizeCodeMirror();
+ }
}
@UiHandler("autoHideDiffTableHeader")
void onAutoHideDiffTableHeader(ValueChangeEvent<Boolean> e) {
prefs.autoHideDiffTableHeader(!e.getValue());
- view.setAutoHideDiffHeader(!e.getValue());
+ if (view != null) {
+ view.setAutoHideDiffHeader(!e.getValue());
+ }
}
@UiHandler("manualReview")
@@ -348,11 +405,13 @@
@UiHandler("syntaxHighlighting")
void onSyntaxHighlighting(ValueChangeEvent<Boolean> e) {
prefs.syntaxHighlighting(e.getValue());
- mode.setEnabled(prefs.syntaxHighlighting());
- if (prefs.syntaxHighlighting()) {
- setMode(view.getContentType());
+ if (view != null) {
+ mode.setEnabled(prefs.syntaxHighlighting());
+ if (prefs.syntaxHighlighting()) {
+ setMode(view.getContentType());
+ }
+ view.setSyntaxHighlighting(prefs.syntaxHighlighting());
}
- view.setSyntaxHighlighting(prefs.syntaxHighlighting());
}
@UiHandler("mode")
@@ -386,42 +445,48 @@
@UiHandler("whitespaceErrors")
void onWhitespaceErrors(ValueChangeEvent<Boolean> e) {
prefs.showWhitespaceErrors(e.getValue());
- view.operation(new Runnable() {
- @Override
- public void run() {
- boolean s = prefs.showWhitespaceErrors();
- view.getCmFromSide(DisplaySide.A).setOption("showTrailingSpace", s);
- view.getCmFromSide(DisplaySide.B).setOption("showTrailingSpace", s);
- }
- });
+ if (view != null) {
+ view.operation(new Runnable() {
+ @Override
+ public void run() {
+ boolean s = prefs.showWhitespaceErrors();
+ view.getCmFromSide(DisplaySide.A).setOption("showTrailingSpace", s);
+ view.getCmFromSide(DisplaySide.B).setOption("showTrailingSpace", s);
+ }
+ });
+ }
}
@UiHandler("renderEntireFile")
void onRenderEntireFile(ValueChangeEvent<Boolean> e) {
prefs.renderEntireFile(e.getValue());
- view.updateRenderEntireFile();
+ if (view != null) {
+ view.updateRenderEntireFile();
+ }
}
@UiHandler("theme")
void onTheme(@SuppressWarnings("unused") ChangeEvent e) {
final Theme newTheme = getSelectedTheme();
prefs.theme(newTheme);
- ThemeLoader.loadTheme(newTheme, new GerritCallback<Void>() {
- @Override
- public void onSuccess(Void result) {
- view.operation(new Runnable() {
- @Override
- public void run() {
- if (getSelectedTheme() == newTheme && isAttached()) {
- String t = newTheme.name().toLowerCase();
- view.getCmFromSide(DisplaySide.A).setOption("theme", t);
- view.getCmFromSide(DisplaySide.B).setOption("theme", t);
- view.setThemeStyles(newTheme.isDark());
+ if (view != null) {
+ ThemeLoader.loadTheme(newTheme, new GerritCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ view.operation(new Runnable() {
+ @Override
+ public void run() {
+ if (getSelectedTheme() == newTheme && isAttached()) {
+ String t = newTheme.name().toLowerCase();
+ view.getCmFromSide(DisplaySide.A).setOption("theme", t);
+ view.getCmFromSide(DisplaySide.B).setOption("theme", t);
+ view.setThemeStyles(newTheme.isDark());
+ }
}
- }
- });
- }
- });
+ });
+ }
+ });
+ }
}
private Theme getSelectedTheme() {
@@ -446,7 +511,9 @@
Gerrit.setAccountDiffPreference(p);
}
});
- close();
+ if (view != null) {
+ close();
+ }
}
@UiHandler("close")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
index e011091..da744c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
@@ -155,15 +155,17 @@
</ui:style>
<g:HTMLPanel styleName='{style.box}'>
- <table style='width: 100%'>
- <tr>
- <td><ui:msg>Diff Preferences</ui:msg></td>
- <td style='text-align: right'>
- <g:Anchor ui:field='close' href='javascript:;'><ui:msg>Close</ui:msg></g:Anchor>
- </td>
- </tr>
- </table>
- <hr/>
+ <div ui:field='header'>
+ <table style='width: 100%'>
+ <tr>
+ <td><ui:msg>Diff Preferences</ui:msg></td>
+ <td style='text-align: right'>
+ <g:Anchor ui:field='close' href='javascript:;'><ui:msg>Close</ui:msg></g:Anchor>
+ </td>
+ </tr>
+ </table>
+ <hr/>
+ </div>
<table class='{style.table}'>
<tr>
<th><ui:msg>Theme</ui:msg></th>
@@ -208,7 +210,7 @@
</g:ToggleButton></td>
</tr>
<tr>
- <th><ui:msg>Language</ui:msg></th>
+ <th><div ui:field='modeLabel'><ui:msg>Language</ui:msg></div></th>
<td><g:ListBox ui:field='mode'/></td>
</tr>
<tr>
@@ -240,7 +242,7 @@
</g:ToggleButton></td>
</tr>
<tr>
- <th><ui:msg>Left Side</ui:msg></th>
+ <th><div ui:field='leftSideLabel'><ui:msg>Left Side</ui:msg></div></th>
<td><g:ToggleButton ui:field='leftSide'>
<g:upFace><ui:msg>Hide</ui:msg></g:upFace>
<g:downFace><ui:msg>Show</ui:msg></g:downFace>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesAction.java
new file mode 100644
index 0000000..d297f4d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesAction.java
@@ -0,0 +1,69 @@
+// 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.client.editor;
+
+import com.google.gerrit.client.account.EditPreferences;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+
+class EditPreferencesAction {
+ private final EditScreen view;
+ private final EditPreferences prefs;
+ private PopupPanel popup;
+ private EditPreferencesBox current;
+
+ EditPreferencesAction(EditScreen view, EditPreferences prefs) {
+ this.view = view;
+ this.prefs = prefs;
+ }
+
+ void show() {
+ if (popup != null) {
+ hide();
+ return;
+ }
+
+ current = new EditPreferencesBox(view);
+ current.set(prefs);
+
+ popup = new PopupPanel(true, false);
+ popup.setStyleName(current.style.dialog());
+ popup.add(current);
+ popup.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ view.getEditor().focus();
+ popup = null;
+ current = null;
+ }
+ });
+ popup.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight) {
+ popup.setPopupPosition(300, 120);
+ }
+ });
+ }
+
+ void hide() {
+ if (popup != null) {
+ popup.hide();
+ popup = null;
+ current = null;
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java
new file mode 100644
index 0000000..dadf5b2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java
@@ -0,0 +1,314 @@
+// 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.client.editor;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.client.account.EditPreferences;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.NpIntTextBox;
+import com.google.gerrit.extensions.client.KeyMapType;
+import com.google.gerrit.extensions.client.Theme;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.ToggleButton;
+import com.google.gwt.user.client.ui.UIObject;
+
+import net.codemirror.theme.ThemeLoader;
+
+/** Displays current edit preferences. */
+public class EditPreferencesBox extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, EditPreferencesBox> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ public interface Style extends CssResource {
+ String dialog();
+ }
+
+ private final EditScreen view;
+ private EditPreferences prefs;
+
+ @UiField Style style;
+ @UiField Element header;
+ @UiField Anchor close;
+ @UiField NpIntTextBox tabWidth;
+ @UiField NpIntTextBox lineLength;
+ @UiField NpIntTextBox cursorBlinkRate;
+ @UiField ToggleButton topMenu;
+ @UiField ToggleButton syntaxHighlighting;
+ @UiField ToggleButton showTabs;
+ @UiField ToggleButton whitespaceErrors;
+ @UiField ToggleButton lineNumbers;
+ @UiField ToggleButton matchBrackets;
+ @UiField ToggleButton autoCloseBrackets;
+ @UiField ListBox theme;
+ @UiField ListBox keyMap;
+ @UiField Button apply;
+ @UiField Button save;
+
+ public EditPreferencesBox(EditScreen view) {
+ this.view = view;
+ initWidget(uiBinder.createAndBindUi(this));
+ initTheme();
+ initKeyMapType();
+
+ if (view == null) {
+ UIObject.setVisible(header, false);
+ apply.getElement().getStyle().setVisibility(Visibility.HIDDEN);
+ }
+ }
+
+ public Style getStyle() {
+ return style;
+ }
+
+ public void set(EditPreferences prefs) {
+ this.prefs = prefs;
+
+ tabWidth.setIntValue(prefs.tabSize());
+ lineLength.setIntValue(prefs.lineLength());
+ cursorBlinkRate.setIntValue(prefs.cursorBlinkRate());
+ topMenu.setValue(!prefs.hideTopMenu());
+ syntaxHighlighting.setValue(prefs.syntaxHighlighting());
+ showTabs.setValue(prefs.showTabs());
+ whitespaceErrors.setValue(prefs.showWhitespaceErrors());
+ lineNumbers.setValue(prefs.hideLineNumbers());
+ matchBrackets.setValue(prefs.matchBrackets());
+ autoCloseBrackets.setValue(prefs.autoCloseBrackets());
+ setTheme(prefs.theme());
+ setKeyMapType(prefs.keyMapType());
+ }
+
+ @UiHandler("tabWidth")
+ void onTabWidth(ValueChangeEvent<String> e) {
+ String v = e.getValue();
+ if (v != null && v.length() > 0) {
+ prefs.tabSize(Math.max(1, Integer.parseInt(v)));
+ if (view != null) {
+ view.getEditor().setOption("tabSize", v);
+ }
+ }
+ }
+
+ @UiHandler("lineLength")
+ void onLineLength(ValueChangeEvent<String> e) {
+ String v = e.getValue();
+ if (v != null && v.length() > 0) {
+ prefs.lineLength(Math.max(1, Integer.parseInt(v)));
+ if (view != null) {
+ view.setLineLength(prefs.lineLength());
+ }
+ }
+ }
+
+ @UiHandler("cursorBlinkRate")
+ void onCursoBlinkRate(ValueChangeEvent<String> e) {
+ String v = e.getValue();
+ if (v != null && v.length() > 0) {
+ // A negative value hides the cursor entirely:
+ // don't let user shoot himself in the foot.
+ prefs.cursorBlinkRate(Math.max(0, Integer.parseInt(v)));
+ if (view != null) {
+ view.getEditor().setOption("cursorBlinkRate", prefs.cursorBlinkRate());
+ }
+ }
+ }
+
+ @UiHandler("topMenu")
+ void onTopMenu(ValueChangeEvent<Boolean> e) {
+ prefs.hideTopMenu(!e.getValue());
+ if (view != null) {
+ Gerrit.setHeaderVisible(!prefs.hideTopMenu());
+ view.resizeCodeMirror();
+ }
+ }
+
+ @UiHandler("showTabs")
+ void onShowTabs(ValueChangeEvent<Boolean> e) {
+ prefs.showTabs(e.getValue());
+ if (view != null) {
+ view.setShowTabs(prefs.showTabs());
+ }
+ }
+
+ @UiHandler("whitespaceErrors")
+ void onshowTrailingSpace(ValueChangeEvent<Boolean> e) {
+ prefs.showWhitespaceErrors(e.getValue());
+ if (view != null) {
+ view.setShowWhitespaceErrors(prefs.showWhitespaceErrors());
+ }
+ }
+
+ @UiHandler("lineNumbers")
+ void onLineNumbers(ValueChangeEvent<Boolean> e) {
+ prefs.hideLineNumbers(e.getValue());
+ if (view != null) {
+ view.setShowLineNumbers(prefs.hideLineNumbers());
+ }
+ }
+
+ @UiHandler("syntaxHighlighting")
+ void onSyntaxHighlighting(ValueChangeEvent<Boolean> e) {
+ prefs.syntaxHighlighting(e.getValue());
+ if (view != null) {
+ view.setSyntaxHighlighting(prefs.syntaxHighlighting());
+ }
+ }
+
+ @UiHandler("matchBrackets")
+ void onMatchBrackets(ValueChangeEvent<Boolean> e) {
+ prefs.matchBrackets(e.getValue());
+ if (view != null) {
+ view.getEditor().setOption("matchBrackets", prefs.matchBrackets());
+ }
+ }
+
+ @UiHandler("autoCloseBrackets")
+ void onCloseBrackets(ValueChangeEvent<Boolean> e) {
+ prefs.autoCloseBrackets(e.getValue());
+ if (view != null) {
+ view.getEditor().setOption("autoCloseBrackets", prefs.autoCloseBrackets());
+ }
+ }
+
+ @UiHandler("theme")
+ void onTheme(@SuppressWarnings("unused") ChangeEvent e) {
+ final Theme newTheme = Theme.valueOf(theme.getValue(theme.getSelectedIndex()));
+ prefs.theme(newTheme);
+ if (view != null) {
+ ThemeLoader.loadTheme(newTheme, new GerritCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ view.getEditor().operation(new Runnable() {
+ @Override
+ public void run() {
+ String t = newTheme.name().toLowerCase();
+ view.getEditor().setOption("theme", t);
+ }
+ });
+ }
+ });
+ }
+ }
+
+ @UiHandler("keyMap")
+ void onKeyMap(@SuppressWarnings("unused") ChangeEvent e) {
+ KeyMapType keyMapType = KeyMapType.valueOf(
+ keyMap.getValue(keyMap.getSelectedIndex()));
+ prefs.keyMapType(keyMapType);
+ if (view != null) {
+ view.getEditor().setOption("keyMap", keyMapType.name().toLowerCase());
+ }
+ }
+
+ @UiHandler("apply")
+ void onApply(@SuppressWarnings("unused") ClickEvent e) {
+ close();
+ }
+
+ @UiHandler("save")
+ void onSave(@SuppressWarnings("unused") ClickEvent e) {
+ AccountApi.putEditPreferences(prefs, new GerritCallback<VoidResult>() {
+ @Override
+ public void onSuccess(VoidResult n) {
+ prefs.copyTo(Gerrit.getEditPreferences());
+ }
+ });
+ close();
+ }
+
+ @UiHandler("close")
+ void onClose(ClickEvent e) {
+ e.preventDefault();
+ close();
+ }
+
+ private void close() {
+ ((PopupPanel) getParent()).hide();
+ }
+
+ private void setTheme(Theme v) {
+ String name = v != null ? v.name() : Theme.DEFAULT.name();
+ for (int i = 0; i < theme.getItemCount(); i++) {
+ if (theme.getValue(i).equals(name)) {
+ theme.setSelectedIndex(i);
+ return;
+ }
+ }
+ theme.setSelectedIndex(0);
+ }
+
+ private void initTheme() {
+ theme.addItem(
+ Theme.DEFAULT.name().toLowerCase(),
+ Theme.DEFAULT.name());
+ theme.addItem(
+ Theme.ECLIPSE.name().toLowerCase(),
+ Theme.ECLIPSE.name());
+ theme.addItem(
+ Theme.ELEGANT.name().toLowerCase(),
+ Theme.ELEGANT.name());
+ theme.addItem(
+ Theme.NEAT.name().toLowerCase(),
+ Theme.NEAT.name());
+ theme.addItem(
+ Theme.MIDNIGHT.name().toLowerCase(),
+ Theme.MIDNIGHT.name());
+ theme.addItem(
+ Theme.NIGHT.name().toLowerCase(),
+ Theme.NIGHT.name());
+ theme.addItem(
+ Theme.TWILIGHT.name().toLowerCase(),
+ Theme.TWILIGHT.name());
+ }
+
+ private void setKeyMapType(KeyMapType v) {
+ String name = v != null ? v.name() : KeyMapType.DEFAULT.name();
+ for (int i = 0; i < keyMap.getItemCount(); i++) {
+ if (keyMap.getValue(i).equals(name)) {
+ keyMap.setSelectedIndex(i);
+ return;
+ }
+ }
+ keyMap.setSelectedIndex(0);
+ }
+
+ private void initKeyMapType() {
+ keyMap.addItem(
+ KeyMapType.DEFAULT.name().toLowerCase(),
+ KeyMapType.DEFAULT.name());
+ keyMap.addItem(
+ KeyMapType.EMACS.name().toLowerCase(),
+ KeyMapType.EMACS.name());
+ keyMap.addItem(
+ KeyMapType.VIM.name().toLowerCase(),
+ KeyMapType.VIM.name());
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml
new file mode 100644
index 0000000..ec8ad39
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:x='urn:import:com.google.gerrit.client.ui'>
+ <ui:style type='com.google.gerrit.client.editor.EditPreferencesBox.Style'>
+ @external .gwt-TextBox;
+ @external .gwt-ToggleButton .html-face;
+ @external .gwt-ToggleButton-up;
+ @external .gwt-ToggleButton-up-hovering;
+ @external .gwt-ToggleButton-up-disabled;
+ @external .gwt-ToggleButton-down;
+ @external .gwt-ToggleButton-down-hovering;
+ @external .gwt-ToggleButton-down-disabled;
+
+ .dialog {
+ background: rgba(0, 0, 0, 0.85) none repeat scroll 0 50%;
+ color: #ffffff;
+ font-family: arial,sans-serif;
+ font-weight: bold;
+ overflow: hidden;
+ text-align: left;
+ text-shadow: 1px 1px 7px #000000;
+ min-width: 300px;
+ z-index: 200;
+ border-radius: 10px;
+ }
+
+ @if user.agent safari {
+ .dialog {
+ \-webkit-border-radius: 10px;
+ }
+ }
+
+ @if user.agent gecko1_8 {
+ .dialog {
+ \-moz-border-radius: 10px;
+ }
+ }
+
+ .box { margin: 10px; }
+ .box .gwt-TextBox { padding: 0; }
+ .context { vertical-align: bottom; }
+
+ .table tr { min-height: 23px; }
+ .table th,
+ .table td {
+ white-space: nowrap;
+ color: #ffffff;
+ }
+ .table th {
+ padding-right: 8px;
+ text-align: right;
+ }
+
+ .box a,
+ .box a:visited,
+ .box a:hover {
+ color: #dddd00;
+ }
+
+ .box .gwt-ToggleButton {
+ position: relative;
+ height: 19px;
+ width: 140px;
+ background: #fff;
+ color: #000;
+ text-shadow: none;
+ }
+ .box .gwt-ToggleButton .html-face {
+ position: absolute;
+ top: 0;
+ width: 68px;
+ height: 17px;
+ line-height: 17px;
+ text-align: center;
+ border-width: 1px;
+ }
+
+ .box .gwt-ToggleButton-up,
+ .box .gwt-ToggleButton-up-hovering,
+ .box .gwt-ToggleButton-up-disabled,
+ .box .gwt-ToggleButton-down,
+ .box .gwt-ToggleButton-down-hovering,
+ .box .gwt-ToggleButton-down-disabled {
+ padding: 0;
+ border: 0;
+ }
+ .box .gwt-ToggleButton-up .html-face,
+ .box .gwt-ToggleButton-up-hovering .html-face {
+ left: 0;
+ background: #cacaca;
+ border-style: outset;
+ }
+ .box .gwt-ToggleButton-down .html-face,
+ .box .gwt-ToggleButton-down-hovering .html-face {
+ right: 0;
+ background: #bcf;
+ border-style: inset;
+ }
+
+ .box button {
+ margin: 6px 3px 0 0;
+ border-color: rgba(0, 0, 0, 0.1);
+ text-align: center;
+ font-size: 8pt;
+ font-weight: bold;
+ border: 1px solid;
+ cursor: pointer;
+ color: #444;
+ background-color: #f5f5f5;
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+ -webkit-border-radius: 2px;
+ -webkit-box-sizing: content-box;
+ }
+ .box button div {
+ color: #444;
+ height: 10px;
+ min-width: 54px;
+ line-height: 10px;
+ white-space: nowrap;
+ }
+
+ button.apply {
+ background-color: #4d90fe;
+ background-image: -webkit-linear-gradient(top, #4d90fe, #4d90fe);
+ }
+ button.apply div { color: #fff; }
+
+ button.save {
+ margin-left: 10px;
+ color: #d14836;
+ background-color: #d14836;
+ background-image: -webkit-linear-gradient(top, #d14836, #d14836);
+ }
+ button.save div { color: #fff; }
+ </ui:style>
+
+ <g:HTMLPanel styleName='{style.box}'>
+ <div ui:field='header'>
+ <table style='width: 100%'>
+ <tr>
+ <td><ui:msg>Edit Preferences</ui:msg></td>
+ <td style='text-align: right'>
+ <g:Anchor ui:field='close' href='javascript:;'><ui:msg>Close</ui:msg></g:Anchor>
+ </td>
+ </tr>
+ </table>
+ <hr/>
+ </div>
+ <table class='{style.table}'>
+ <tr>
+ <th><ui:msg>Theme</ui:msg></th>
+ <td><g:ListBox ui:field='theme'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Key Map</ui:msg></th>
+ <td><g:ListBox ui:field='keyMap'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Tab Width</ui:msg></th>
+ <td><x:NpIntTextBox ui:field='tabWidth'
+ visibleLength='4'
+ alignment='RIGHT'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Columns</ui:msg></th>
+ <td><x:NpIntTextBox ui:field='lineLength'
+ visibleLength='4'
+ alignment='RIGHT'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Cursor Blink Rate</ui:msg></th>
+ <td><x:NpIntTextBox ui:field='cursorBlinkRate'
+ visibleLength='4'
+ alignment='RIGHT'/></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Top Menu</ui:msg></th>
+ <td><g:ToggleButton ui:field='topMenu'>
+ <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+ <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Syntax Highlighting</ui:msg></th>
+ <td><g:ToggleButton ui:field='syntaxHighlighting'>
+ <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+ <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Show Tabs</ui:msg></th>
+ <td><g:ToggleButton ui:field='showTabs'>
+ <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+ <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Whitespace Errors</ui:msg></th>
+ <td><g:ToggleButton ui:field='whitespaceErrors'>
+ <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+ <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Line Numbers</ui:msg></th>
+ <td><g:ToggleButton ui:field='lineNumbers'>
+ <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+ <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Match Brackets</ui:msg></th>
+ <td><g:ToggleButton ui:field='matchBrackets'>
+ <g:upFace><ui:msg>Off</ui:msg></g:upFace>
+ <g:downFace><ui:msg>On</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <th><ui:msg>Auto Close Brackets</ui:msg></th>
+ <td><g:ToggleButton ui:field='autoCloseBrackets'>
+ <g:upFace><ui:msg>Off</ui:msg></g:upFace>
+ <g:downFace><ui:msg>On</ui:msg></g:downFace>
+ </g:ToggleButton></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>
+ <g:Button ui:field='apply' styleName='{style.apply}'>
+ <div><ui:msg>Apply</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='save' styleName='{style.save}'>
+ <div><ui:msg>Save</ui:msg></div>
+ </g:Button>
+ </td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index e5a5b53..a546c62 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -22,7 +22,7 @@
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.JumpKeys;
import com.google.gerrit.client.VoidResult;
-import com.google.gerrit.client.account.DiffPreferences;
+import com.google.gerrit.client.account.EditPreferences;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeEditApi;
import com.google.gerrit.client.diff.DiffApi;
@@ -42,6 +42,7 @@
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.client.KeyMapType;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
@@ -87,7 +88,8 @@
private final PatchSet.Id revision;
private final String path;
private final int startLine;
- private DiffPreferences prefs;
+ private EditPreferences prefs;
+ private EditPreferencesAction editPrefsAction;
private CodeMirror cm;
private HttpResponse<NativeString> content;
private EditFileInfo editFileInfo;
@@ -113,7 +115,7 @@
this.revision = patch.getParentKey();
this.path = patch.get();
this.startLine = startLine - 1;
- prefs = DiffPreferences.create(Gerrit.getAccountDiffPreference());
+ setRequiresSignIn(true);
add(uiBinder.createAndBindUi(this));
addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
}
@@ -129,6 +131,8 @@
protected void onLoad() {
super.onLoad();
+ prefs = EditPreferences.create(Gerrit.getEditPreferences());
+
CallbackGroup group1 = new CallbackGroup();
final CallbackGroup group2 = new CallbackGroup();
final CallbackGroup group3 = new CallbackGroup();
@@ -163,7 +167,6 @@
}
}));
-
if (revision.get() == 0) {
ChangeEditApi.getMeta(revision, path,
group1.add(new AsyncCallback<EditFileInfo>() {
@@ -224,7 +227,6 @@
@Override
protected void preDisplay(Void result) {
initEditor(content);
- content = null;
renderLinks(editFileInfo, diffLinks);
editFileInfo = null;
@@ -237,11 +239,18 @@
@Override
public void registerKeys() {
super.registerKeys();
- cm.addKeyMap(KeyMap.create()
+ KeyMap localKeyMap = KeyMap.create();
+ localKeyMap
.on("Ctrl-L", gotoLine())
.on("Cmd-L", gotoLine())
- .on("Cmd-S", save())
- .on("Ctrl-S", save()));
+ .on("Cmd-S", save());
+
+ // TODO(davido): Find a better way to prevent key maps collisions
+ if (prefs.keyMapType() != KeyMapType.EMACS) {
+ localKeyMap.on("Ctrl-S", save());
+ }
+
+ cm.addKeyMap(localKeyMap);
}
private Runnable gotoLine() {
@@ -300,9 +309,8 @@
cm.adjustHeight(header.getOffsetHeight());
cm.on("cursorActivity", updateCursorPosition());
- cm.extras().showTabs(prefs.showTabs());
- cm.extras().lineLength(
- Patch.COMMIT_MSG.equals(path) ? 72 : prefs.lineLength());
+ setShowTabs(prefs.showTabs());
+ setLineLength(prefs.lineLength());
cm.refresh();
cm.focus();
@@ -310,6 +318,7 @@
cm.scrollToLine(startLine);
}
updateActiveLine();
+ editPrefsAction = new EditPreferencesAction(this, prefs);
}
@Override
@@ -329,6 +338,15 @@
JumpKeys.enable(true);
}
+ CodeMirror getEditor() {
+ return cm;
+ }
+
+ @UiHandler("editSettings")
+ void onEditSetting(@SuppressWarnings("unused") ClickEvent e) {
+ editPrefsAction.show();
+ }
+
@UiHandler("save")
void onSave(@SuppressWarnings("unused") ClickEvent e) {
save().run();
@@ -342,6 +360,52 @@
}
}
+ void setLineLength(int length) {
+ cm.extras().lineLength(
+ Patch.COMMIT_MSG.equals(path) ? 72 : length);
+ }
+
+ void setShowLineNumbers(boolean show) {
+ cm.setOption("lineNumbers", show);
+ }
+
+ void setShowWhitespaceErrors(final boolean show) {
+ cm.operation(new Runnable() {
+ @Override
+ public void run() {
+ cm.setOption("showTrailingSpace", show);
+ }
+ });
+ }
+
+ void setShowTabs(boolean show) {
+ cm.extras().showTabs(show);
+ }
+
+ void resizeCodeMirror() {
+ cm.adjustHeight(header.getOffsetHeight());
+ }
+
+ void setSyntaxHighlighting(boolean b) {
+ ModeInfo modeInfo = ModeInfo.findMode(content.getContentType(), path);
+ final String mode = modeInfo != null ? modeInfo.mode() : null;
+ if (b && mode != null && !mode.isEmpty()) {
+ injectMode(mode, new AsyncCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ cm.setOption("mode", mode);
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ prefs.syntaxHighlighting(false);
+ }
+ });
+ } else {
+ cm.setOption("mode", (String) null);
+ }
+ }
+
private void upToChange() {
Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey()));
}
@@ -358,15 +422,17 @@
cm = CodeMirror.create(editor, Configuration.create()
.set("value", content)
.set("readOnly", false)
- .set("cursorBlinkRate", 0)
+ .set("cursorBlinkRate", prefs.cursorBlinkRate())
.set("cursorHeight", 0.85)
- .set("lineNumbers", true)
+ .set("lineNumbers", prefs.hideLineNumbers())
.set("tabSize", prefs.tabSize())
.set("lineWrapping", false)
+ .set("matchBrackets", prefs.matchBrackets())
+ .set("autoCloseBrackets", prefs.autoCloseBrackets())
.set("scrollbarStyle", "overlay")
.set("styleSelectedText", true)
- .set("showTrailingSpace", true)
- .set("keyMap", "default")
+ .set("showTrailingSpace", prefs.showWhitespaceErrors())
+ .set("keyMap", prefs.keyMapType().name().toLowerCase())
.set("theme", prefs.theme().name().toLowerCase())
.set("mode", mode != null ? mode.mode() : null));
}
@@ -452,6 +518,13 @@
if (!cm.isClean(generation)) {
close.setEnabled(false);
String text = cm.getValue();
+ if (Patch.COMMIT_MSG.equals(path)) {
+ String trimmed = text.trim() + "\r";
+ if (!trimmed.equals(text)) {
+ text = trimmed;
+ cm.setValue(text);
+ }
+ }
final int g = cm.changeGeneration(false);
ChangeEditApi.put(revision.getParentKey().get(), path, text,
new GerritCallback<VoidResult>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
index a82dc49..88af398 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
@@ -16,6 +16,7 @@
-->
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
<ui:style gss='false'>
@external .CodeMirror, .CodeMirror-cursor;
@@ -115,6 +116,13 @@
padding-top: 2px;
padding-right: 3px;
}
+
+ .preferences {
+ position: relative;
+ top: 2px;
+ cursor: pointer;
+ outline: none;
+ }
</ui:style>
<g:HTMLPanel styleName='{style.header}'>
<div class='{style.headerLine}' ui:field='header'>
@@ -135,6 +143,13 @@
<span class='{style.path}'><span ui:field='project'/> / <span ui:field='filePath'/></span>
<div class='{style.navigation}'>
<g:FlowPanel ui:field='linkPanel' styleName='{style.linkPanel}'/>
+ <g:Image
+ ui:field='editSettings'
+ styleName='{style.preferences}'
+ resource='{ico.gear}'
+ title='Edit screen preferences'>
+ <ui:attribute name='title'/>
+ </g:Image>
</div>
</div>
<div ui:field='editor' />
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 3ec3d28..0987e83 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -1258,6 +1258,10 @@
cursor: pointer;
}
+.branchTableDeleteButton {
+ margin-top: 5px;
+}
+
.branchTablePrevNextLinks {
position: relative;
}
@@ -1310,3 +1314,10 @@
font-size: 7pt;
padding: 1px;
}
+
+/* List Screens */
+.pagingLink {
+ font-size: 18px;
+ margin-top: 5px;
+ margin-bottom: 15px;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
index b94f5d5..8d166a1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
@@ -17,17 +17,9 @@
import com.google.gerrit.client.info.ActionInfo;
import com.google.gerrit.client.info.WebLinkInfo;
import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
-public class BranchInfo extends JavaScriptObject {
- public final String getShortName() {
- return RefNames.shortName(ref());
- }
-
- public final native String ref() /*-{ return this.ref; }-*/;
- public final native String revision() /*-{ return this.revision; }-*/;
+public class BranchInfo extends RefInfo {
public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
public final native NativeMap<ActionInfo> actions() /*-{ return this.actions }-*/;
public final native JsArray<WebLinkInfo> webLinks() /*-{ return this.web_links; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 3f3875a..322a354 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -53,6 +53,9 @@
public final native InheritedBooleanInfo enableSignedPush()
/*-{ return this.enable_signed_push; }-*/;
+ public final native InheritedBooleanInfo requireSignedPush()
+ /*-{ return this.require_signed_push; }-*/;
+
public final SubmitType submitType() {
return SubmitType.valueOf(submitTypeRaw());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 53eba42..fffdd3f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -44,6 +44,32 @@
.put(input, cb);
}
+ private static RestApi getRestApi(Project.NameKey name, String viewName,
+ int limit, int start, String match) {
+ RestApi call = project(name).view(viewName);
+ call.addParameter("n", limit);
+ call.addParameter("s", start);
+ if (match != null) {
+ if (match.startsWith("^")) {
+ call.addParameter("r", match);
+ } else {
+ call.addParameter("m", match);
+ }
+ }
+ return call;
+ }
+
+ /** Retrieve all visible tags of the project */
+ public static void getTags(Project.NameKey name,
+ AsyncCallback<JsArray<TagInfo>> cb) {
+ project(name).view("tags").get(cb);
+ }
+
+ public static void getTags(Project.NameKey name, int limit, int start,
+ String match, AsyncCallback<JsArray<TagInfo>> cb) {
+ getRestApi(name, "tags", limit, start, match).get(cb);
+ }
+
/** Create a new branch */
public static void createBranch(Project.NameKey name, String ref,
String revision, AsyncCallback<BranchInfo> cb) {
@@ -60,17 +86,7 @@
public static void getBranches(Project.NameKey name, int limit, int start,
String match, AsyncCallback<JsArray<BranchInfo>> cb) {
- RestApi call = project(name).view("branches");
- call.addParameter("n", limit);
- call.addParameter("s", start);
- if (match != null) {
- if (match.startsWith("^")) {
- call.addParameter("r", match);
- } else {
- call.addParameter("m", match);
- }
- }
- call.get(cb);
+ getRestApi(name, "branches", limit, start, match).get(cb);
}
/**
@@ -101,6 +117,7 @@
InheritableBoolean createNewChangeForAllNotInTarget,
InheritableBoolean requireChangeId,
InheritableBoolean enableSignedPush,
+ InheritableBoolean requireSignedPush,
String maxObjectSizeLimit,
SubmitType submitType, ProjectState state,
Map<String, Map<String, ConfigParameterValue>> pluginConfigValues,
@@ -115,6 +132,9 @@
if (enableSignedPush != null) {
in.setEnableSignedPush(enableSignedPush);
}
+ if (requireSignedPush != null) {
+ in.setRequireSignedPush(requireSignedPush);
+ }
in.setMaxObjectSizeLimit(maxObjectSizeLimit);
in.setSubmitType(submitType);
in.setState(state);
@@ -241,6 +261,12 @@
private final native void setEnableSignedPushRaw(String v)
/*-{ if(v)this.enable_signed_push=v; }-*/;
+ final void setRequireSignedPush(InheritableBoolean v) {
+ setRequireSignedPushRaw(v.name());
+ }
+ private final native void setRequireSignedPushRaw(String v)
+ /*-{ if(v)this.require_signed_push=v; }-*/;
+
final native void setMaxObjectSizeLimit(String l)
/*-{ if(l)this.max_object_size_limit=l; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java
new file mode 100644
index 0000000..053dbd3
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.projects;
+
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RefInfo extends JavaScriptObject {
+ public final String getShortName() {
+ return RefNames.shortName(ref());
+ }
+
+ public final native String ref() /*-{ return this.ref; }-*/;
+ public final native String revision() /*-{ return this.revision; }-*/;
+
+ protected RefInfo() {
+ }
+}
\ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
similarity index 65%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
index cd01186..ee1d1af 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.client.projects;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+public class TagInfo extends RefInfo {
+
+ // TODO(dpursehouse) add extra tag-related fields (message, tagger, etc)
+ protected TagInfo() {
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
index e48477f..771423e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
@@ -72,7 +72,8 @@
}
return sce.getStatusCode() == Response.SC_FORBIDDEN
&& (sce.getEncodedResponse().equals("Authentication required")
- || sce.getEncodedResponse().startsWith("Must be signed-in"));
+ || sce.getEncodedResponse().startsWith("Must be signed-in")
+ || sce.getEncodedResponse().startsWith("Invalid authentication"));
}
return false;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java
new file mode 100644
index 0000000..e4ad903
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/PagingHyperlink.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.admin.Util;
+
+public class PagingHyperlink extends Hyperlink {
+
+ public static PagingHyperlink createPrev() {
+ return new PagingHyperlink(Util.C.pagedListPrev());
+ }
+
+ public static PagingHyperlink createNext() {
+ return new PagingHyperlink(Util.C.pagedListNext());
+ }
+
+ private PagingHyperlink(String text) {
+ super(text, true, "");
+ setStyleName(Gerrit.RESOURCES.css().pagingLink());
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
index 782500d..85b350d 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
@@ -75,6 +75,7 @@
Modes.I.stex(),
Modes.I.velocity(),
Modes.I.verilog(),
+ Modes.I.vhdl(),
Modes.I.xml(),
Modes.I.yaml(),
});
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
index 8170c2b..42990b8 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
@@ -61,6 +61,7 @@
@Source("tcl.js") @DoNotEmbed DataResource tcl();
@Source("velocity.js") @DoNotEmbed DataResource velocity();
@Source("verilog.js") @DoNotEmbed DataResource verilog();
+ @Source("vhdl.js") @DoNotEmbed DataResource vhdl();
@Source("xml.js") @DoNotEmbed DataResource xml();
@Source("yaml.js") @DoNotEmbed DataResource yaml();
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java
new file mode 100644
index 0000000..0f659c9
--- /dev/null
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client;
+
+import static com.google.gerrit.client.FormatUtil.formatBytes;
+import static org.junit.Assert.assertEquals;
+
+import com.googlecode.gwt.test.GwtModule;
+import com.googlecode.gwt.test.GwtTest;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+@GwtModule("com.google.gerrit.GerritGwtUI")
+@Ignore
+public class FormatUtilTest extends GwtTest {
+ @Test
+ public void testFormatBytes() {
+ assertEquals("+/- 0 B", formatBytes(0));
+ assertEquals("+27 B", formatBytes(27));
+ assertEquals("+999 B", formatBytes(999));
+ assertEquals("+1000 B", formatBytes(1000));
+ assertEquals("+1023 B", formatBytes(1023));
+ assertEquals("+1.0 KiB", formatBytes(1024));
+ assertEquals("+1.7 KiB", formatBytes(1728));
+ assertEquals("+108.0 KiB", formatBytes(110592));
+ assertEquals("+6.8 MiB", formatBytes(7077888));
+ assertEquals("+432.0 MiB", formatBytes(452984832));
+ assertEquals("+27.0 GiB", formatBytes(28991029248L));
+ assertEquals("+1.7 TiB", formatBytes(1855425871872L));
+ assertEquals("+8.0 EiB", formatBytes(9223372036854775807L));
+
+ assertEquals("-27 B", formatBytes(-27));
+ assertEquals("-1.7 MiB", formatBytes(-1728));
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index d315fa3..a6e4b44 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -136,7 +136,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
if (user == null) {
if (isSignedIn()) {
user = identified.create(val.getAccountId());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
index 94b8f29..019efb4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
@@ -72,7 +72,7 @@
throws IOException, ServletException {
CurrentUser user = userProvider.get();
if (user != null && user.isIdentifiedUser()) {
- IdentifiedUser who = (IdentifiedUser) user;
+ IdentifiedUser who = user.asIdentifiedUser();
if (who.getUserName() != null && !who.getUserName().isEmpty()) {
req.setAttribute(REQ_ATTR_KEY, who.getUserName());
} else {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 43cd741..89d62dd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -63,8 +63,6 @@
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -158,13 +156,6 @@
public Repository open(HttpServletRequest req, String projectName)
throws RepositoryNotFoundException, ServiceNotAuthorizedException,
ServiceNotEnabledException {
- try {
- // TODO: remove this code when Guice fixes its issue 745
- projectName = URLDecoder.decode(projectName, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // leave it encoded
- }
-
while (projectName.endsWith("/")) {
projectName = projectName.substring(0, projectName.length() - 1);
}
@@ -186,7 +177,7 @@
throw new RepositoryNotFoundException(projectName);
}
- CurrentUser user = pc.getCurrentUser();
+ CurrentUser user = pc.getUser();
user.setAccessPath(AccessPath.GIT);
if (!pc.isVisible()) {
@@ -301,12 +292,12 @@
throws ServiceNotAuthorizedException {
final ProjectControl pc = (ProjectControl) req.getAttribute(ATT_CONTROL);
- if (!(pc.getCurrentUser().isIdentifiedUser())) {
+ if (!(pc.getUser().isIdentifiedUser())) {
// Anonymous users are not permitted to push.
throw new ServiceNotAuthorizedException();
}
- final IdentifiedUser user = (IdentifiedUser) pc.getCurrentUser();
+ final IdentifiedUser user = pc.getUser().asIdentifiedUser();
final ReceiveCommits rc = factory.create(pc, db).getReceiveCommits();
ReceivePack rp = rc.getReceivePack();
rp.setRefLogIdent(user.newRefLogIdent());
@@ -376,14 +367,13 @@
return;
}
- if (!(pc.getCurrentUser().isIdentifiedUser())) {
+ if (!(pc.getUser().isIdentifiedUser())) {
chain.doFilter(request, response);
return;
}
AdvertisedObjectsCacheKey cacheKey = AdvertisedObjectsCacheKey.create(
- ((IdentifiedUser) pc.getCurrentUser()).getAccountId(),
- projectName);
+ pc.getUser().getAccountId(), projectName);
if (isGet) {
cache.invalidate(cacheKey);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
index 1eb88b1..95f4536 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.io.ByteStreams;
import org.w3c.dom.Document;
@@ -27,7 +29,6 @@
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -50,7 +51,7 @@
/** Utility functions to deal with HTML using W3C DOM operations. */
public class HtmlDomUtil {
/** Standard character encoding we prefer (UTF-8). */
- public static final Charset ENC = StandardCharsets.UTF_8;
+ public static final Charset ENC = UTF_8;
/** DOCTYPE for a standards mode HTML document. */
public static final String HTML_STRICT =
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
index ada3ebf..88584eb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
@@ -78,7 +78,7 @@
final HttpServletResponse rsp) throws IOException {
final String sid = webSession.get().getSessionId();
- final CurrentUser currentUser = webSession.get().getCurrentUser();
+ final CurrentUser currentUser = webSession.get().getUser();
final String what = "sign out";
final long when = TimeUtil.nowMs();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
index 47593aa..adad03f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
@@ -34,8 +34,8 @@
}
@Override
- public CurrentUser getCurrentUser() {
- return session.get().getCurrentUser();
+ public CurrentUser getUser() {
+ return session.get().getUser();
}
@Override
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 6c17c87..177ff04 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
@@ -17,7 +17,6 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.extensions.restapi.Url;
import javax.servlet.http.HttpServletRequest;
@@ -25,11 +24,11 @@
private static final String DEFAULT_TOKEN = '#' + PageLinks.MINE;
public static String getToken(final HttpServletRequest req){
- String encodedToken = req.getPathInfo();
- if (Strings.isNullOrEmpty(encodedToken)) {
+ String token = req.getPathInfo();
+ if (Strings.isNullOrEmpty(token)) {
return DEFAULT_TOKEN;
} else {
- return CharMatcher.is('/').trimLeadingFrom(Url.decode(encodedToken));
+ return CharMatcher.is('/').trimLeadingFrom(token);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index 5d896df..6e6324e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.MoreObjects;
@@ -188,7 +189,7 @@
}
private String encoding(HttpServletRequest req) {
- return MoreObjects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
+ return MoreObjects.firstNonNull(req.getCharacterEncoding(), UTF_8.name());
}
static class Response extends HttpServletResponseWrapper {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index 33b9fed..38dd118 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
@@ -36,7 +37,6 @@
import org.eclipse.jgit.lib.Config;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
@@ -189,25 +189,17 @@
}
private static String H(String data) {
- try {
- MessageDigest md = newMD5();
- md.update(data.getBytes("UTF-8"));
- return LHEX(md.digest());
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("UTF-8 encoding not available", e);
- }
+ MessageDigest md = newMD5();
+ md.update(data.getBytes(UTF_8));
+ return LHEX(md.digest());
}
private static String KD(String secret, String data) {
- try {
- MessageDigest md = newMD5();
- md.update(secret.getBytes("UTF-8"));
- md.update((byte) ':');
- md.update(data.getBytes("UTF-8"));
- return LHEX(md.digest());
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("UTF-8 encoding not available", e);
- }
+ MessageDigest md = newMD5();
+ md.update(secret.getBytes(UTF_8));
+ md.update((byte) ':');
+ md.update(data.getBytes(UTF_8));
+ return LHEX(md.digest());
}
private static MessageDigest newMD5() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
index 614184a..f6b79e5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -84,7 +84,7 @@
return;
}
- CurrentUser self = session.get().getCurrentUser();
+ CurrentUser self = session.get().getUser();
if (!self.getCapabilities().canRunAs()) {
replyError(req, res,
SC_FORBIDDEN,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
index 3349cc1..b2d32fc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
@@ -25,7 +25,7 @@
public String getXGerritAuth();
public boolean isValidXGerritAuth(String keyIn);
public AccountExternalId.Key getLastLoginExternalId();
- public CurrentUser getCurrentUser();
+ public CurrentUser getUser();
public void login(AuthResult res, boolean rememberMe);
/** Set the user account for this current request only. */
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index ccc945f..bfbf1ff 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.auth.container;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_EXTERNAL;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -102,7 +103,7 @@
final byte[] bin = HtmlDomUtil.toUTF8(doc);
rsp.setStatus(HttpServletResponse.SC_FORBIDDEN);
rsp.setContentType("text/html");
- rsp.setCharacterEncoding("UTF-8");
+ rsp.setCharacterEncoding(UTF_8.name());
rsp.setContentLength(bin.length);
try (ServletOutputStream out = rsp.getOutputStream()) {
out.write(bin);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
index 5388048..a5b4f7a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.auth.ldap;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
@@ -90,7 +92,7 @@
byte[] bin = HtmlDomUtil.toUTF8(doc);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("text/html");
- res.setCharacterEncoding("UTF-8");
+ res.setCharacterEncoding(UTF_8.name());
res.setContentLength(bin.length);
try (ServletOutputStream out = res.getOutputStream()) {
out.write(bin);
@@ -106,6 +108,7 @@
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
+ req.setCharacterEncoding(UTF_8.name());
String username = Strings.nullToEmpty(req.getParameter("username")).trim();
String password = Strings.nullToEmpty(req.getParameter("password"));
String remember = Strings.nullToEmpty(req.getParameter("rememberme"));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java
index 75e468e..abcc4fe 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.gitweb;
import static com.google.gerrit.common.FileUtil.lastModified;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.config.GitwebCgiConfig;
@@ -51,8 +52,6 @@
}
}
- private static final String ENC = "UTF-8";
-
private final long modified;
private final byte[] raw_css;
private final byte[] gz_css;
@@ -65,7 +64,7 @@
final String raw = HtmlDomUtil.readFile(dir, name);
if (raw != null) {
modified = lastModified(src);
- raw_css = raw.getBytes(ENC);
+ raw_css = raw.getBytes(UTF_8);
gz_css = HtmlDomUtil.compress(raw_css);
} else {
modified = -1L;
@@ -89,7 +88,7 @@
final HttpServletResponse rsp) throws IOException {
if (raw_css != null) {
rsp.setContentType("text/css");
- rsp.setCharacterEncoding(ENC);
+ rsp.setCharacterEncoding(UTF_8.name());
final byte[] toSend;
if (RPCServletUtils.acceptsGzipEncoding(req)) {
rsp.setHeader("Content-Encoding", "gzip");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebModule.java
index e73cb11..21a4d65 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebModule.java
@@ -19,7 +19,7 @@
public class GitwebModule extends ServletModule {
@Override
protected void configureServlets() {
- serve("/gitweb").with(GitwebServlet.class);
+ serveRegex("^/(?:a/)?gitweb").with(GitwebServlet.class);
serve("/gitweb-logo.png").with(GitLogoServlet.class);
serve("/gitweb.js").with(GitwebJavaScriptServlet.class);
serve("/gitweb-default.css").with(GitwebCssServlet.Default.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index e5af965..9a85562 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -29,6 +29,7 @@
package com.google.gerrit.httpd.gitweb;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.PageLinks;
@@ -550,8 +551,8 @@
}
String remoteUser = null;
- if (project.getCurrentUser().isIdentifiedUser()) {
- final IdentifiedUser u = (IdentifiedUser) project.getCurrentUser();
+ if (project.getUser().isIdentifiedUser()) {
+ final IdentifiedUser u = project.getUser().asIdentifiedUser();
final String user = u.getUserName();
env.set("GERRIT_USER_NAME", user);
if (user != null && !user.isEmpty()) {
@@ -636,7 +637,7 @@
@Override
public void run() {
try (BufferedReader br =
- new BufferedReader(new InputStreamReader(in, "ISO-8859-1"))) {
+ new BufferedReader(new InputStreamReader(in, ISO_8859_1.name()))) {
String line;
while ((line = br.readLine()) != null) {
log.error("CGI: " + line);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 0ac2c4f..d5259bd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.common.FileUtil.lastModified;
import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CHARACTER_ENCODING;
import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CONTENT_TYPE;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.CharMatcher;
import com.google.common.base.Optional;
@@ -468,13 +469,13 @@
m.appendTail(sb);
byte[] html = new MarkdownFormatter()
- .markdownToDocHtml(sb.toString(), "UTF-8");
+ .markdownToDocHtml(sb.toString(), UTF_8.name());
resourceCache.put(cacheKey, new SmallResource(html)
.setContentType("text/html")
- .setCharacterEncoding("UTF-8")
+ .setCharacterEncoding(UTF_8.name())
.setLastModified(lastModifiedTime));
res.setContentType("text/html");
- res.setCharacterEncoding("UTF-8");
+ res.setCharacterEncoding(UTF_8.name());
res.setContentLength(html.length);
res.setDateHeader("Last-Modified", lastModifiedTime);
res.getOutputStream().write(html);
@@ -526,7 +527,7 @@
charEnc = Strings.emptyToNull(atts.get(ATTR_CHARACTER_ENCODING));
}
if (charEnc == null) {
- charEnc = "UTF-8";
+ charEnc = UTF_8.name();
}
return new MarkdownFormatter().extractTitleFromMarkdown(
readWholeEntry(scanner, entry),
@@ -553,7 +554,7 @@
}
String txtmd = RawParseUtils.decode(
- Charset.forName(encoding != null ? encoding : "UTF-8"),
+ Charset.forName(encoding != null ? encoding : UTF_8.name()),
rawmd);
long time = entry.getTime();
if (0 < time) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
index 3cbb68b..ce696a1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -15,48 +15,23 @@
package com.google.gerrit.httpd.raw;
import com.google.common.base.Optional;
-import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Url;
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.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtexpui.server.CacheHeaders;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import eu.medsea.mimeutil.MimeType;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectReader;
-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 org.eclipse.jgit.util.NB;
-
-import java.io.FilterOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -74,26 +49,17 @@
@SuppressWarnings("serial")
@Singleton
public class CatServlet extends HttpServlet {
- private static final MimeType ZIP = new MimeType("application/zip");
private final Provider<ReviewDb> requestDb;
- private final GitRepositoryManager repoManager;
- private final SecureRandom rng;
- private final FileTypeRegistry registry;
private final Provider<CurrentUser> userProvider;
private final ChangeControl.GenericFactory changeControl;
private final ChangeEditUtil changeEditUtil;
@Inject
- CatServlet(GitRepositoryManager grm,
- Provider<ReviewDb> sf,
- FileTypeRegistry ftr,
+ CatServlet(Provider<ReviewDb> sf,
ChangeControl.GenericFactory ccf,
Provider<CurrentUser> usrprv,
ChangeEditUtil ceu) {
requestDb = sf;
- repoManager = grm;
- rng = new SecureRandom();
- registry = ftr;
changeControl = ccf;
userProvider = usrprv;
changeEditUtil = ceu;
@@ -130,7 +96,6 @@
if (c < 0) {
side = 0;
-
} else {
try {
side = Integer.parseInt(keyStr.substring(c + 1));
@@ -150,15 +115,11 @@
}
final Change.Id changeId = patchKey.getParentKey().getParentKey();
- final Project project;
- final String revision;
+ String revision;
try {
final ReviewDb db = requestDb.get();
final ChangeControl control = changeControl.validateFor(changeId,
userProvider.get());
-
- project = control.getProject();
-
if (patchKey.getParentKey().get() == 0) {
// change edit
try {
@@ -190,184 +151,9 @@
return;
}
- ObjectLoader blobLoader;
- RevCommit fromCommit;
- String suffix;
String path = patchKey.getFileName();
- try (Repository repo = repoManager.openRepository(project.getNameKey())) {
- try (ObjectReader reader = repo.newObjectReader();
- RevWalk rw = new RevWalk(reader)) {
- RevCommit c;
-
- c = rw.parseCommit(ObjectId.fromString(revision));
- if (side == 0) {
- fromCommit = c;
- suffix = "new";
-
- } else if (1 <= side && side - 1 < c.getParentCount()) {
- fromCommit = rw.parseCommit(c.getParent(side - 1));
- if (c.getParentCount() == 1) {
- suffix = "old";
- } else {
- suffix = "old" + side;
- }
-
- } else {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
-
- try (TreeWalk tw = TreeWalk.forPath(reader, path, fromCommit.getTree())) {
- if (tw == null) {
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
-
- if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) {
- blobLoader = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
-
- } else {
- rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- return;
- }
- }
- }
- } catch (RepositoryNotFoundException e) {
- getServletContext().log("Cannot open repository", e);
- rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- return;
- } catch (IOException | RuntimeException e) {
- getServletContext().log("Cannot read repository", e);
- rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- return;
- }
-
- final byte[] raw =
- blobLoader.isLarge() ? null : blobLoader.getCachedBytes();
- final long when = fromCommit.getCommitTime() * 1000L;
-
- rsp.setDateHeader("Last-Modified", when);
- CacheHeaders.setNotCacheable(rsp);
-
- try (OutputStream out = openOutputStream(
- req, rsp, blobLoader, fromCommit, when, path, suffix, raw)) {
- if (raw != null) {
- out.write(raw);
- } else {
- blobLoader.copyTo(out);
- }
- }
+ String restUrl = String.format("%s/changes/%d/revisions/%s/files/%s/download?parent=%d",
+ req.getContextPath(), changeId.get(), revision, Url.encode(path), side);
+ rsp.sendRedirect(restUrl);
}
-
- private OutputStream openOutputStream(HttpServletRequest req,
- HttpServletResponse rsp, ObjectLoader blobLoader,
- RevCommit fromCommit, long when, String path, String suffix, byte[] raw)
- throws IOException {
- MimeType contentType = registry.getMimeType(path, raw);
- if (!registry.isSafeInline(contentType)) {
- // The content may not be safe to transmit inline, as a browser might
- // interpret it as HTML or JavaScript hosted by this site. Such code
- // might then run in the site's security domain, and may be able to use
- // the user's cookies to perform unauthorized actions.
- //
- // Usually, wrapping the content into a ZIP file forces the browser to
- // save the content to the local system instead.
- //
-
- rsp.setContentType(ZIP.toString());
- rsp.setHeader("Content-Disposition", "attachment; filename=\""
- + safeFileName(path, suffix) + ".zip" + "\"");
-
- final ZipOutputStream zo = new ZipOutputStream(rsp.getOutputStream());
-
- ZipEntry e = new ZipEntry(safeFileName(path, rand(req, suffix)));
- e.setComment(fromCommit.name() + ":" + path);
- e.setSize(blobLoader.getSize());
- e.setTime(when);
- zo.putNextEntry(e);
- return new FilterOutputStream(zo) {
- @Override
- public void close() throws IOException {
- try {
- zo.closeEntry();
- } finally {
- super.close();
- }
- }
- };
-
- } else {
- rsp.setContentType(contentType.toString());
- rsp.setHeader("Content-Length", "" + blobLoader.getSize());
-
- return rsp.getOutputStream();
- }
- }
-
- private static String safeFileName(String fileName, final String suffix) {
- // Convert a file path (e.g. "src/Init.c") to a safe file name with
- // no meta-characters that might be unsafe on any given platform.
- //
- final int slash = fileName.lastIndexOf('/');
- if (slash >= 0) {
- fileName = fileName.substring(slash + 1);
- }
-
- final StringBuilder r = new StringBuilder(fileName.length());
- for (int i = 0; i < fileName.length(); i++) {
- final char c = fileName.charAt(i);
- if (c == '_' || c == '-' || c == '.' || c == '@') {
- r.append(c);
-
- } else if ('0' <= c && c <= '9') {
- r.append(c);
-
- } else if ('A' <= c && c <= 'Z') {
- r.append(c);
-
- } else if ('a' <= c && c <= 'z') {
- r.append(c);
-
- } else if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
- r.append('-');
-
- } else {
- r.append('_');
- }
- }
- fileName = r.toString();
-
- final int ext = fileName.lastIndexOf('.');
- if (ext <= 0) {
- return fileName + "_" + suffix;
-
- } else {
- return fileName.substring(0, ext) + "_" + suffix
- + fileName.substring(ext);
- }
- }
-
- private String rand(final HttpServletRequest req, final String suffix)
- throws UnsupportedEncodingException {
- // Produce a random suffix that is difficult (or nearly impossible)
- // for an attacker to guess in advance. This reduces the risk that
- // an attacker could upload a *.class file and have us send a ZIP
- // that can be invoked through an applet tag in the victim's browser.
- //
- final MessageDigest md = Constants.newMessageDigest();
- final byte[] buf = new byte[8];
-
- NB.encodeInt32(buf, 0, req.getRemotePort());
- md.update(req.getRemoteAddr().getBytes("UTF-8"));
- md.update(buf, 0, 4);
-
- NB.encodeInt64(buf, 0, TimeUtil.nowMs());
- md.update(buf, 0, 8);
-
- rng.nextBytes(buf);
- md.update(buf, 0, 8);
-
- return suffix + "-" + ObjectId.fromRaw(md.digest()).name();
- }
-
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index e499109..c1a113f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.raw;
import static com.google.gerrit.common.FileUtil.lastModified;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
@@ -30,7 +31,6 @@
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
@@ -187,7 +187,7 @@
w.write(";");
w.write(HPD_ID + ".accountDiffPref=");
- json(((IdentifiedUser) user).getAccountDiffPreference(), w);
+ json(user.asIdentifiedUser().getAccountDiffPreference(), w);
w.write(";");
w.write(HPD_ID + ".theme=");
@@ -201,7 +201,7 @@
plugins(w);
messages(w);
- final byte[] hpd = w.toString().getBytes("UTF-8");
+ final byte[] hpd = w.toString().getBytes(UTF_8);
final byte[] raw = Bytes.concat(page.part1, hpd, page.part2);
final byte[] tosend;
if (RPCServletUtils.acceptsGzipEncoding(req)) {
@@ -353,8 +353,8 @@
if (p < 0) {
throw new IOException("No tag in transformed host page HTML");
}
- part1 = raw.substring(0, p).getBytes("UTF-8");
- part2 = raw.substring(raw.indexOf('>', p) + 1).getBytes("UTF-8");
+ part1 = raw.substring(0, p).getBytes(UTF_8);
+ part2 = raw.substring(raw.indexOf('>', p) + 1).getBytes(UTF_8);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
index 5f526dd..f570cb6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.raw;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
@@ -88,7 +90,7 @@
}
CacheHeaders.setNotCacheable(rsp);
- rsp.setCharacterEncoding("UTF-8");
+ rsp.setCharacterEncoding(UTF_8.name());
rsp.setContentType("text/plain");
try (PrintWriter w = rsp.getWriter()) {
w.write(out);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java
index 179b268..5c6480a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ToolServlet.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.httpd.HtmlDomUtil.compress;
import static com.google.gerrit.httpd.HtmlDomUtil.newDocument;
import static com.google.gerrit.httpd.HtmlDomUtil.toUTF8;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static org.eclipse.jgit.util.HttpSupport.HDR_CACHE_CONTROL;
import static org.eclipse.jgit.util.HttpSupport.HDR_EXPIRES;
@@ -143,7 +144,7 @@
rsp.setHeader(HDR_PRAGMA, "no-cache");
rsp.setHeader(HDR_CACHE_CONTROL, "no-cache, must-revalidate");
rsp.setContentType("text/html");
- rsp.setCharacterEncoding("UTF-8");
+ rsp.setCharacterEncoding(UTF_8.name());
rsp.setContentLength(tosend.length);
try (OutputStream out = rsp.getOutputStream()) {
out.write(tosend);
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 2f2575d..7347cd9 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
@@ -669,7 +669,7 @@
w.flush();
replyBinaryResult(req, res, asBinaryResult(buf)
.setContentType(JSON_TYPE)
- .setCharacterEncoding(UTF_8.name()));
+ .setCharacterEncoding(UTF_8));
}
private static Gson newGson(Multimap<String, String> config,
@@ -791,7 +791,7 @@
res.setHeader("X-FYI-Content-Type", src.getContentType());
return asBinaryResult(buf)
.setContentType(JSON_TYPE)
- .setCharacterEncoding(UTF_8.name());
+ .setCharacterEncoding(UTF_8);
}
private static BinaryResult stackBase64(HttpServletResponse res,
@@ -819,7 +819,7 @@
}
res.setHeader("X-FYI-Content-Encoding", "base64");
res.setHeader("X-FYI-Content-Type", src.getContentType());
- return b64.setContentType("text/plain").setCharacterEncoding("ISO-8859-1");
+ return b64.setContentType("text/plain").setCharacterEncoding(ISO_8859_1);
}
private static BinaryResult stackGzip(HttpServletResponse res,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
index 73b546b..547bf45 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
@@ -21,7 +21,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -42,13 +41,10 @@
protected Account.Id getAccountId() {
CurrentUser u = currentUser.get();
- if (u.isIdentifiedUser()) {
- return ((IdentifiedUser) u).getAccountId();
- }
- return null;
+ return u.isIdentifiedUser() ? u.getAccountId() : null;
}
- protected CurrentUser getCurrentUser() {
+ protected CurrentUser getUser() {
return currentUser.get();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
index 01f2df3..3b064e2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
@@ -134,7 +134,7 @@
Audit note = method.getAnnotation(Audit.class);
if (note != null) {
final String sid = call.getWebSession().getSessionId();
- final CurrentUser username = call.getWebSession().getCurrentUser();
+ final CurrentUser username = call.getWebSession().getUser();
final Multimap<String, ?> args =
extractParams(note, call);
final String what = extractWhat(note, call);
@@ -166,14 +166,18 @@
}
private String extractWhat(final Audit note, final GerritCall call) {
- String methodClass = call.getMethodClass().getName();
- methodClass = methodClass.substring(methodClass.lastIndexOf(".")+1);
+ Class<?> methodClass = call.getMethodClass();
+ String methodClassName = methodClass != null
+ ? methodClass.getName()
+ : "<UNKNOWN_CLASS>";
+ methodClassName =
+ methodClassName.substring(methodClassName.lastIndexOf(".") + 1);
String what = note.action();
if (what.length() == 0) {
what = call.getMethod().getName();
}
- return methodClass + "." + what;
+ return methodClassName + "." + what;
}
static class GerritCall extends ActiveCall {
@@ -275,7 +279,7 @@
} else if (session.isSignedIn() && session.isValidXGerritAuth(keyIn)) {
// The session must exist, and must be using this token.
//
- session.getCurrentUser().setAccessPath(AccessPath.JSON_RPC);
+ session.getUser().setAccessPath(AccessPath.JSON_RPC);
return true;
}
return false;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index 34066f1..968029c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -141,7 +141,7 @@
AccountProjectWatch watch =
new AccountProjectWatch(new AccountProjectWatch.Key(
- ((IdentifiedUser) ctl.getCurrentUser()).getAccountId(),
+ ctl.getUser().getAccountId(),
nameKey, filter));
try {
db.accountProjectWatches().insert(Collections.singleton(watch));
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 3237873..2bf5fe4 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
@@ -14,16 +14,11 @@
package com.google.gerrit.httpd.rpc.changedetail;
-import com.google.common.base.Function;
import com.google.common.base.Optional;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.common.data.UiCommandDetail;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
@@ -36,14 +31,9 @@
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.PatchLineCommentsUtil;
-import com.google.gerrit.server.change.ChangesCollection;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.change.Revisions;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
-import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
@@ -57,7 +47,6 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
-import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
@@ -86,8 +75,6 @@
private final PatchListCache patchListCache;
private final Provider<CurrentUser> userProvider;
private final ChangeControl.GenericFactory changeControlFactory;
- private final ChangesCollection changes;
- private final Revisions revisions;
private final PatchLineCommentsUtil plcUtil;
private final ChangeEditUtil editUtil;
@@ -107,8 +94,6 @@
final PatchListCache patchListCache,
final Provider<CurrentUser> userProvider,
final ChangeControl.GenericFactory changeControlFactory,
- final ChangesCollection changes,
- final Revisions revisions,
final PatchLineCommentsUtil plcUtil,
ChangeEditUtil editUtil,
@Assisted("psIdBase") @Nullable final PatchSet.Id psIdBase,
@@ -119,8 +104,6 @@
this.patchListCache = patchListCache;
this.userProvider = userProvider;
this.changeControlFactory = changeControlFactory;
- this.changes = changes;
- this.revisions = revisions;
this.plcUtil = plcUtil;
this.editUtil = editUtil;
@@ -193,13 +176,13 @@
detail.setInfo(infoFactory.get(db, patchSet.getId()));
detail.setPatches(patches);
- final CurrentUser user = control.getCurrentUser();
+ final CurrentUser user = control.getUser();
if (user.isIdentifiedUser() && edit == null) {
// If we are signed in, compute the number of draft comments by the
// current user on each of these patch files. This way they can more
// quickly locate where they have pending drafts, and review them.
//
- final Account.Id me = ((IdentifiedUser) user).getAccountId();
+ final Account.Id me = user.getAccountId();
for (PatchLineComment c
: plcUtil.draftByPatchSetAuthor(db, psIdNew, me, notes)) {
final Patch p = byKey.get(c.getKey().getParentKey());
@@ -216,23 +199,6 @@
}
}
- detail.setCommands(Lists.newArrayList(Iterables.transform(
- UiActions.sorted(UiActions.plugins(UiActions.from(
- revisions,
- new RevisionResource(changes.parse(control), patchSet),
- Providers.of(user)))),
- new Function<UiAction.Description, UiCommandDetail>() {
- @Override
- public UiCommandDetail apply(UiAction.Description in) {
- UiCommandDetail r = new UiCommandDetail();
- r.method = in.getMethod();
- r.id = in.getId();
- r.label = in.getLabel();
- r.title = in.getTitle();
- r.enabled = in.isEnabled();
- return r;
- }
- })));
return detail;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index 9257271..7ff3782 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -60,7 +60,7 @@
public PatchScript call() throws Exception {
ChangeControl control = changeControlFactory.validateFor(
patchKey.getParentKey().getParentKey(),
- getCurrentUser());
+ getUser());
return patchScriptFactoryFactory.create(
control, patchKey.getFileName(), psa, psb, dp).call();
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index dff2cd0..5b3b064 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -20,7 +20,6 @@
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -37,8 +36,11 @@
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
@@ -49,7 +51,9 @@
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.List;
@@ -70,6 +74,7 @@
private final ProjectCache projectCache;
private final ChangesCollection changes;
private final ChangeInserter.Factory changeInserterFactory;
+ private final BatchUpdate.Factory updateFactory;
@Inject
ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
@@ -81,6 +86,7 @@
AllProjectsNameProvider allProjects,
ChangesCollection changes,
ChangeInserter.Factory changeInserterFactory,
+ BatchUpdate.Factory updateFactory,
Provider<SetParent> setParent,
@Assisted("projectName") Project.NameKey projectName,
@@ -97,6 +103,7 @@
this.projectCache = projectCache;
this.changes = changes;
this.changeInserterFactory = changeInserterFactory;
+ this.updateFactory = updateFactory;
}
@Override
@@ -120,9 +127,21 @@
config.getProject().getNameKey(),
RefNames.REFS_CONFIG),
TimeUtil.nowTs());
- ChangeInserter ins =
- changeInserterFactory.create(ctl, change, commit);
- ins.insert();
+ try (RevWalk rw = new RevWalk(md.getRepository());
+ ObjectInserter objInserter = md.getRepository().newObjectInserter();
+ BatchUpdate bu = updateFactory.create(
+ db, change.getProject(), ctl.getUser(),
+ change.getCreatedOn())) {
+ bu.setRepository(md.getRepository(), rw, objInserter);
+ bu.insertChange(
+ changeInserterFactory.create(
+ ctl.controlForRef(change.getDest().get()), change, commit)
+ .setValidatePolicy(CommitValidators.Policy.NONE)
+ .setUpdateRef(false)); // Created by commitToNewRef.
+ bu.execute();
+ } catch (UpdateException | RestApiException e) {
+ throw new IOException(e);
+ }
ChangeResource rsrc;
try {
@@ -152,7 +171,7 @@
AddReviewerInput input = new AddReviewerInput();
input.reviewer = projectOwners;
reviewersProvider.get().apply(rsrc, input);
- } catch (IOException | OrmException | RestApiException | EmailException e) {
+ } catch (IOException | OrmException | RestApiException e) {
// one of the owner groups is not visible to the user and this it why it
// can't be added as reviewer
}
@@ -168,7 +187,7 @@
AddReviewerInput input = new AddReviewerInput();
input.reviewer = r.getGroup().getUUID().get();
reviewersProvider.get().apply(rsrc, input);
- } catch (IOException | OrmException | RestApiException | EmailException e) {
+ } catch (IOException | OrmException | RestApiException e) {
// ignore
}
}
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index 60404a3..89c8ec6 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -29,6 +29,8 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -64,14 +66,15 @@
System.err.println("usage: java -jar " + jar + " command [ARG ...]");
System.err.println();
System.err.println("The most commonly used commands are:");
- System.err.println(" init Initialize a Gerrit installation");
- System.err.println(" reindex Rebuild the secondary index");
- System.err.println(" daemon Run the Gerrit network daemons");
- System.err.println(" gsql Run the interactive query console");
- System.err.println(" version Display the build version number");
+ System.err.println(" init Initialize a Gerrit installation");
+ System.err.println(" rebuild-notedb Rebuild the review notes database");
+ System.err.println(" reindex Rebuild the secondary index");
+ System.err.println(" daemon Run the Gerrit network daemons");
+ System.err.println(" gsql Run the interactive query console");
+ System.err.println(" version Display the build version number");
System.err.println();
- System.err.println(" ls List files available for cat");
- System.err.println(" cat FILE Display a file from the archive");
+ System.err.println(" ls List files available for cat");
+ System.err.println(" cat FILE Display a file from the archive");
System.err.println();
return 1;
}
@@ -540,7 +543,7 @@
*
* @throws FileNotFoundException if the directory cannot be found.
*/
- public static File getDeveloperBuckOut() throws FileNotFoundException {
+ public static Path getDeveloperBuckOut() throws FileNotFoundException {
// Find ourselves in the CLASSPATH, we should be a loose class file.
Class<GerritLauncher> self = GerritLauncher.class;
URL u = self.getResource(self.getSimpleName() + ".class");
@@ -551,39 +554,43 @@
}
// Pop up to the top level classes folder that contains us.
- File dir = new File(u.getPath());
+ Path dir = Paths.get(u.getPath());
String myName = self.getName();
for (;;) {
int dot = myName.lastIndexOf('.');
if (dot < 0) {
- dir = dir.getParentFile();
+ dir = dir.getParent();
break;
}
myName = myName.substring(0, dot);
- dir = dir.getParentFile();
+ dir = dir.getParent();
}
dir = popdir(u, dir, "classes");
dir = popdir(u, dir, "eclipse");
- if ("buck-out".equals(dir.getName())) {
+ if (last(dir).equals("buck-out")) {
return dir;
}
throw new FileNotFoundException("Cannot find buck-out from " + u);
}
- private static File popdir(URL u, File dir, String name)
+ private static String last(Path dir) {
+ return dir.getName(dir.getNameCount() - 1).toString();
+ }
+
+ private static Path popdir(URL u, Path dir, String name)
throws FileNotFoundException {
- if (dir.getName().equals(name)) {
- return dir.getParentFile();
+ if (last(dir).equals(name)) {
+ return dir.getParent();
}
throw new FileNotFoundException("Cannot find buck-out from " + u);
}
private static ClassLoader useDevClasspath()
throws MalformedURLException, FileNotFoundException {
- File out = getDeveloperBuckOut();
+ Path out = getDeveloperBuckOut();
List<URL> dirs = new ArrayList<>();
- dirs.add(new File(new File(out, "eclipse"), "classes").toURI().toURL());
+ dirs.add(out.resolve("eclipse").resolve("classes").toUri().toURL());
ClassLoader cl = GerritLauncher.class.getClassLoader();
for (URL u : ((URLClassLoader) cl).getURLs()) {
if (includeJar(u)) {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java
index 27ded17..4e47bca 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AutoCommitWriter.java
@@ -25,13 +25,28 @@
import java.io.IOException;
/** Writer that optionally flushes/commits after every write. */
-class AutoCommitWriter extends IndexWriter {
+public class AutoCommitWriter extends IndexWriter {
private boolean autoCommit;
+ AutoCommitWriter(Directory dir, IndexWriterConfig config)
+ throws IOException {
+ this(dir, config, false);
+ }
+
AutoCommitWriter(Directory dir, IndexWriterConfig config, boolean autoCommit)
throws IOException {
super(dir, config);
- this.autoCommit = autoCommit;
+ setAutoCommit(autoCommit);
+ }
+
+ /**
+ * This method will override Gerrit configuration index.name.commitWithin
+ * until next Gerrit restart (or reconfiguration through this method).
+ *
+ * @param enable auto commit
+ */
+ public void setAutoCommit(boolean enable) {
+ this.autoCommit = enable;
}
@Override
@@ -99,7 +114,7 @@
}
}
- private void autoFlush() throws IOException {
+ public void autoFlush() throws IOException {
if (autoCommit) {
manualFlush();
}
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 55ed22d..99003ee 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
@@ -386,6 +386,14 @@
}
}
+ public SubIndex getOpenChangesIndex() {
+ return openIndex;
+ }
+
+ public SubIndex getClosedChangesIndex() {
+ return closedIndex;
+ }
+
private class QuerySource implements ChangeDataSource {
private final List<SubIndex> indexes;
private final Query query;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index 7fd98aa..939c1fb 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -82,11 +82,11 @@
private Query or(Predicate<ChangeData> p)
throws QueryParseException {
try {
- BooleanQuery q = new BooleanQuery();
+ BooleanQuery.Builder q = new BooleanQuery.Builder();
for (int i = 0; i < p.getChildCount(); i++) {
q.add(toQuery(p.getChild(i)), SHOULD);
}
- return q;
+ return q.build();
} catch (BooleanQuery.TooManyClauses e) {
throw new QueryParseException("cannot create query for index: " + p, e);
}
@@ -95,7 +95,7 @@
private Query and(Predicate<ChangeData> p)
throws QueryParseException {
try {
- BooleanQuery b = new BooleanQuery();
+ BooleanQuery.Builder b = new BooleanQuery.Builder();
List<Query> not = Lists.newArrayListWithCapacity(p.getChildCount());
for (int i = 0; i < p.getChildCount(); i++) {
Predicate<ChangeData> c = p.getChild(i);
@@ -113,7 +113,7 @@
for (Query q : not) {
b.add(q, MUST_NOT);
}
- return b;
+ return b.build();
} catch (BooleanQuery.TooManyClauses e) {
throw new QueryParseException("cannot create query for index: " + p, e);
}
@@ -127,10 +127,10 @@
}
// Lucene does not support negation, start with all and subtract.
- BooleanQuery q = new BooleanQuery();
- q.add(new MatchAllDocsQuery(), MUST);
- q.add(toQuery(n), MUST_NOT);
- return q;
+ return new BooleanQuery.Builder()
+ .add(new MatchAllDocsQuery(), MUST)
+ .add(toQuery(n), MUST_NOT)
+ .build();
}
private Query fieldQuery(IndexPredicate<ChangeData> p)
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
index 5778008..bb69533bf 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
@@ -47,7 +47,7 @@
import java.util.concurrent.TimeoutException;
/** Piece of the change index that is implemented as a separate Lucene index. */
-class SubIndex {
+public class SubIndex {
private static final Logger log = LoggerFactory.getLogger(SubIndex.class);
private final Directory dir;
@@ -70,13 +70,13 @@
long commitPeriod = writerConfig.getCommitWithinMs();
if (commitPeriod < 0) {
- delegateWriter = new IndexWriter(dir, writerConfig.getLuceneConfig());
+ delegateWriter = new AutoCommitWriter(dir, writerConfig.getLuceneConfig());
} else if (commitPeriod == 0) {
delegateWriter =
new AutoCommitWriter(dir, writerConfig.getLuceneConfig(), true);
} else {
final AutoCommitWriter autoCommitWriter =
- new AutoCommitWriter(dir, writerConfig.getLuceneConfig(), false);
+ new AutoCommitWriter(dir, writerConfig.getLuceneConfig());
delegateWriter = autoCommitWriter;
new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
@@ -191,6 +191,10 @@
writer.deleteAll();
}
+ public TrackingIndexWriter getWriter() {
+ return writer;
+ }
+
IndexSearcher acquire() throws IOException {
return searcherManager.acquire();
}
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
index 2d73634..333af15 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.auth.oauth;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
@@ -32,7 +34,6 @@
import org.w3c.dom.Element;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
@@ -183,7 +184,7 @@
byte[] bin = HtmlDomUtil.toUTF8(doc);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("text/html");
- res.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ res.setCharacterEncoding(UTF_8.name());
res.setContentLength(bin.length);
try (ServletOutputStream out = res.getOutputStream()) {
out.write(bin);
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index bd7558b..8b05c72 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.auth.openid;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
@@ -164,11 +166,14 @@
mode = SignInMode.SIGN_IN;
}
+ log.debug("mode \"{}\"", mode);
OAuthServiceProvider oauthProvider = lookupOAuthServiceProvider(id);
if (oauthProvider == null) {
+ log.debug("OpenId provider \"{}\"", id);
discover(req, res, link, id, remember, token, mode);
} else {
+ log.debug("OAuth provider \"{}\"", id);
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
if (!currentUserProvider.get().isIdentifiedUser()
&& oauthSession.isLoggedIn()) {
@@ -317,7 +322,7 @@
byte[] bin = HtmlDomUtil.toUTF8(doc);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("text/html");
- res.setCharacterEncoding("UTF-8");
+ res.setCharacterEncoding(UTF_8.name());
res.setContentLength(bin.length);
try (ServletOutputStream out = res.getOutputStream()) {
out.write(bin);
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
index 6d129bf..8d5d4b9 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
@@ -127,10 +127,12 @@
Account.Id actualId = accountManager.lookup(user.getExternalId());
// Use case 1: claimed identity was provided during handshake phase
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
+ log.debug("Claimed identity is set");
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
if (claimedId != null && actualId != null) {
if (claimedId.equals(actualId)) {
// Both link to the same account, that's what we expected.
+ log.debug("Both link to the same account. All is fine.");
} else {
// This is (for now) a fatal error. There are two records
// for what might be the same user.
@@ -144,7 +146,7 @@
}
} else if (claimedId != null && actualId == null) {
// Claimed account already exists: link to it.
- //
+ log.debug("Claimed account already exists: link to it.");
try {
accountManager.link(claimedId, areq);
} catch (OrmException e) {
@@ -157,11 +159,14 @@
}
} else if (linkMode) {
// Use case 2: link mode activated from the UI
+ Account.Id accountId = identifiedUser.get().getAccountId();
try {
- accountManager.link(identifiedUser.get().getAccountId(), areq);
+ log.debug("Linking \"{}\" to \"{}\"", user.getExternalId(),
+ accountId);
+ accountManager.link(accountId, areq);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
- + " to user identity: " + identifiedUser.get().getAccountId());
+ + " to user identity: " + accountId);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
} finally {
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java
index d6ada97..b48d3ed 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.auth.openid;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -29,7 +31,6 @@
@Singleton
class XrdsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
- private static final String ENC = "UTF-8";
static final String LOCATION = "OpenID.XRDS";
private final Provider<String> url;
@@ -43,7 +44,8 @@
protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
throws IOException {
final StringBuilder r = new StringBuilder();
- r.append("<?xml version=\"1.0\" encoding=\"").append(ENC).append("\"?>");
+ r.append("<?xml version=\"1.0\" encoding=\"")
+ .append(UTF_8.name()).append("\"?>");
r.append("<xrds:XRDS");
r.append(" xmlns:xrds=\"xri://$xrds\"");
r.append(" xmlns:openid=\"http://openid.net/xmlns/1.0\"");
@@ -58,10 +60,10 @@
r.append("</xrds:XRDS>");
r.append("\n");
- final byte[] raw = r.toString().getBytes(ENC);
+ final byte[] raw = r.toString().getBytes(UTF_8);
rsp.setContentLength(raw.length);
rsp.setContentType("application/xrds+xml");
- rsp.setCharacterEncoding(ENC);
+ rsp.setCharacterEncoding(UTF_8.name());
try (ServletOutputStream out = rsp.getOutputStream()) {
out.write(raw);
diff --git a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
index eb4ebab..c5a9d0c 100644
--- a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
+++ b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
@@ -14,6 +14,8 @@
package org.apache.commons.net.smtp;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
import org.apache.commons.codec.binary.Base64;
@@ -35,8 +37,6 @@
import javax.net.ssl.SSLSocketFactory;
public class AuthSMTPClient extends SMTPClient {
- private static final String UTF_8 = "UTF-8";
-
private String authTypes;
public AuthSMTPClient(final String charset) {
@@ -65,11 +65,9 @@
_input_ = _socket_.getInputStream();
_output_ = _socket_.getOutputStream();
_reader =
- new BufferedReader(new InputStreamReader(_input_,
- UTF_8));
+ new BufferedReader(new InputStreamReader(_input_, UTF_8));
_writer =
- new BufferedWriter(new OutputStreamWriter(_output_,
- UTF_8));
+ new BufferedWriter(new OutputStreamWriter(_output_, UTF_8));
return true;
}
@@ -190,7 +188,7 @@
return SMTPReply.isPositiveCompletion(sendCommand("AUTH", cmd));
}
- private static String encodeBase64(final byte[] data) throws UnsupportedEncodingException {
+ private static String encodeBase64(final byte[] data) {
return new String(Base64.encodeBase64(data), UTF_8);
}
}
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 80c37f7..c57ec52 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -45,6 +45,7 @@
'//gerrit-common:annotations',
'//gerrit-lucene:lucene',
'//lib:args4j',
+ '//lib:derby',
'//lib:gwtjsonrpc',
'//lib:gwtorm',
'//lib:h2',
@@ -53,21 +54,24 @@
],
provided_deps = ['//gerrit-launcher:launcher'],
visibility = [
+ '//gerrit-acceptance-framework/...',
'//gerrit-acceptance-tests/...',
'//gerrit-war:',
],
)
+REST_UTIL_DEPS = [
+ '//gerrit-cache-h2:cache-h2',
+ '//gerrit-util-cli:cli',
+ '//lib:args4j',
+ '//lib:gwtorm',
+ '//lib/commons:dbcp',
+]
+
java_library(
name = 'util',
- srcs = glob([SRCS + 'util/*.java']),
- deps = DEPS + [
- '//gerrit-cache-h2:cache-h2',
- '//gerrit-util-cli:cli',
- '//lib:args4j',
- '//lib:gwtorm',
- '//lib/commons:dbcp',
- ],
+ deps = DEPS + REST_UTIL_DEPS,
+ exported_deps = [':util-nodep'],
visibility = [
'//gerrit-acceptance-tests/...',
'//gerrit-gwtdebug:gwtdebug',
@@ -76,6 +80,15 @@
)
java_library(
+ name = 'util-nodep',
+ srcs = glob([SRCS + 'util/*.java']),
+ provided_deps = DEPS + REST_UTIL_DEPS,
+ visibility = [
+ '//gerrit-acceptance-framework/...',
+ ],
+)
+
+java_library(
name = 'http',
srcs = glob([SRCS + 'http/**/*.java']),
deps = DEPS + [
@@ -90,30 +103,32 @@
visibility = ['//gerrit-war:'],
)
+REST_PGM_DEPS = [
+ ':http',
+ ':init',
+ ':init-api',
+ ':util',
+ '//gerrit-cache-h2:cache-h2',
+ '//gerrit-gpg:gpg',
+ '//gerrit-lucene:lucene',
+ '//gerrit-oauth:oauth',
+ '//gerrit-openid:openid',
+ '//lib:args4j',
+ '//lib:gwtorm',
+ '//lib:protobuf',
+ '//lib:servlet-api-3_1',
+ '//lib/auto:auto-value',
+ '//lib/prolog:cafeteria',
+ '//lib/prolog:compiler',
+ '//lib/prolog:runtime',
+]
+
java_library(
name = 'pgm',
- srcs = glob([SRCS + '*.java', SRCS + 'rules/*.java']),
resources = glob([RSRCS + '*']),
- deps = DEPS + [
- ':http',
- ':init',
- ':init-api',
- ':util',
- '//gerrit-cache-h2:cache-h2',
- '//gerrit-gpg:gpg',
- '//gerrit-lucene:lucene',
- '//gerrit-oauth:oauth',
- '//gerrit-openid:openid',
- '//lib:args4j',
- '//lib:gwtorm',
- '//lib:protobuf',
- '//lib:servlet-api-3_1',
- '//lib/auto:auto-value',
- '//lib/prolog:cafeteria',
- '//lib/prolog:compiler',
- '//lib/prolog:runtime',
+ deps = DEPS + REST_PGM_DEPS + [
+ ':daemon',
],
- provided_deps = ['//gerrit-launcher:launcher'],
visibility = [
'//:',
'//gerrit-acceptance-tests/...',
@@ -123,6 +138,21 @@
],
)
+# no transitive deps, used for gerrit-acceptance-framework
+java_library(
+ name = 'daemon',
+ srcs = glob([SRCS + '*.java', SRCS + 'rules/*.java']),
+ resources = glob([RSRCS + '*']),
+ deps = ['//lib/auto:auto-value'],
+ provided_deps = DEPS + REST_PGM_DEPS + [
+ '//gerrit-launcher:launcher',
+ ],
+ visibility = [
+ '//gerrit-acceptance-framework/...',
+ '//gerrit-gwtdebug:gwtdebug',
+ ],
+)
+
java_test(
name = 'pgm_tests',
srcs = glob(['src/test/java/**/*.java']),
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index da87a20..39a5cbd 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -80,6 +80,7 @@
import com.google.gerrit.sshd.SshKeyCacheImpl;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.DefaultCommandModule;
+import com.google.gerrit.sshd.commands.IndexCommandsModule;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -155,6 +156,7 @@
private Module emailModule;
private Runnable serverStarted;
+ private IndexType indexType;
public Daemon() {
}
@@ -276,6 +278,7 @@
cfgInjector = createCfgInjector();
config = cfgInjector.getInstance(
Key.get(Config.class, GerritServerConfig.class));
+ initIndexType();
sysInjector = createSysInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class)
.setDbCfgInjector(dbInjector, cfgInjector);
@@ -379,7 +382,6 @@
if (slave) {
return new DummyIndexModule();
}
- IndexType indexType = IndexModule.getIndexType(cfgInjector);
switch (indexType) {
case LUCENE:
return luceneModule != null ? luceneModule : new LuceneIndexModule();
@@ -388,6 +390,16 @@
}
}
+ private void initIndexType() {
+ indexType = IndexModule.getIndexType(cfgInjector);
+ switch (indexType) {
+ case LUCENE:
+ break;
+ default:
+ throw new IllegalStateException("unsupported index.type = " + indexType);
+ }
+ }
+
private void initSshd() {
sshInjector = createSshInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class)
@@ -403,6 +415,9 @@
}
modules.add(new DefaultCommandModule(slave,
sysInjector.getInstance(DownloadConfig.class)));
+ if (indexType == IndexType.LUCENE) {
+ modules.add(new IndexCommandsModule());
+ }
return sysInjector.createChildInjector(modules);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
index a77cc8c..84741d4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.pgm.util.AbstractProgram;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.schema.java.JavaSchemaModel;
@@ -45,13 +47,13 @@
JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
try (OutputStream o = lock.getOutputStream();
PrintWriter out = new PrintWriter(
- new BufferedWriter(new OutputStreamWriter(o, "UTF-8")))) {
+ new BufferedWriter(new OutputStreamWriter(o, UTF_8)))) {
String header;
try (InputStream in = getClass().getResourceAsStream("ProtoGenHeader.txt")) {
ByteBuffer buf = IO.readWholeStream(in, 1024);
int ptr = buf.arrayOffset() + buf.position();
int len = buf.remaining();
- header = new String(buf.array(), ptr, len, "UTF-8");
+ header = new String(buf.array(), ptr, len, UTF_8);
}
String version = com.google.gerrit.common.Version.getVersion();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
index 8b90e54..dd5fe0c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
@@ -25,6 +25,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.common.FormatUtil;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleManager;
@@ -76,7 +77,8 @@
private static final Logger log =
LoggerFactory.getLogger(RebuildNotedb.class);
- @Option(name = "--threads", usage = "Number of threads to use for indexing")
+ @Option(name = "--threads",
+ usage = "Number of threads to use for rebuilding NoteDb")
private int threads = Runtime.getRuntime().availableProcessors();
private Injector dbInjector;
@@ -123,10 +125,10 @@
allUsersRepo.getRefDatabase().newBatchUpdate();
List<ListenableFuture<?>> futures = Lists.newArrayList();
- // Here, we truncate the project name to 50 characters to ensure that
+ // Here, we elide 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()));
+ FormatUtil.elide(project.get(), 50));
final Task doneTask =
mpm.beginSubTask("done", changesByProject.get(project).size());
final Task failedTask =
@@ -166,17 +168,6 @@
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 {
try (RevWalk rw = new RevWalk(repo)) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
index ba1aea3..1b663ae 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
@@ -14,7 +14,8 @@
package com.google.gerrit.pgm.http.jetty;
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
import com.google.common.base.Strings;
import com.google.gwtexpui.server.CacheHeaders;
@@ -68,7 +69,7 @@
msg = HttpStatus.getMessage(conn.getHttpChannel()
.getResponse().getStatus());
}
- return msg.getBytes(Charsets.ISO_8859_1);
+ return msg.getBytes(ISO_8859_1);
}
private static void log(HttpServletRequest req) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 9f94dbf..25b351e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -14,10 +14,10 @@
package com.google.gerrit.pgm.http.jetty;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
-import com.google.common.base.Charsets;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.escape.Escaper;
@@ -83,6 +83,7 @@
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Enumeration;
@@ -562,9 +563,9 @@
private Resource useDeveloperBuild(ServletContextHandler app)
throws IOException {
- final File dir = GerritLauncher.getDeveloperBuckOut();
- final File gen = new File(dir, "gen");
- final File root = dir.getParentFile();
+ final Path dir = GerritLauncher.getDeveloperBuckOut();
+ final Path gen = dir.resolve("gen");
+ final Path root = dir.getParent();
final File dstwar = makeWarTempDir();
File ui = new File(dstwar, "gerrit_ui");
File p = new File(ui, "permutations");
@@ -593,7 +594,7 @@
// $ buck targets --show_output //gerrit-gwtui:ui_safari \
// | awk '{print $2}'
String child = String.format("%s/__gwt_binary_%s__", pkg, target);
- File zip = new File(new File(gen, child), target + ".zip");
+ File zip = gen.resolve(child).resolve(target + ".zip").toFile();
synchronized (this) {
try {
@@ -618,7 +619,7 @@
throws IOException {
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
res.setContentType("text/html");
- res.setCharacterEncoding(Charsets.UTF_8.name());
+ res.setCharacterEncoding(UTF_8.name());
CacheHeaders.setNotCacheable(res);
Escaper html = HtmlEscapers.htmlEscaper();
@@ -643,13 +644,13 @@
return Resource.newResource(dstwar.toURI());
}
- private static void build(File root, File gen, String target)
+ private static void build(Path root, Path gen, String target)
throws IOException, BuildFailureException {
log.info("buck build " + target);
Properties properties = loadBuckProperties(gen);
String buck = MoreObjects.firstNonNull(properties.getProperty("buck"), "buck");
ProcessBuilder proc = new ProcessBuilder(buck, "build", target)
- .directory(root)
+ .directory(root.toFile())
.redirectErrorStream(true);
if (properties.containsKey("PATH")) {
proc.environment().put("PATH", properties.getProperty("PATH"));
@@ -677,11 +678,11 @@
log.info(String.format("UPDATED %s in %.3fs", target, time / 1000.0));
}
- private static Properties loadBuckProperties(File gen)
+ private static Properties loadBuckProperties(Path gen)
throws FileNotFoundException, IOException {
Properties properties = new Properties();
try (InputStream in = new FileInputStream(
- new File(new File(gen, "tools"), "buck.properties"))) {
+ gen.resolve(Paths.get("tools/buck/buck.properties")).toFile())) {
properties.load(in);
}
return properties;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index 7730fa5..0f75a09 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -20,7 +20,6 @@
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
@@ -84,17 +83,17 @@
}
}
- private final Provider<CurrentUser> userProvider;
+ private final Provider<CurrentUser> user;
private final QueueProvider queue;
private final ServletContext context;
private final long maxWait;
@Inject
- ProjectQoSFilter(final Provider<CurrentUser> userProvider,
+ ProjectQoSFilter(final Provider<CurrentUser> user,
QueueProvider queue, final ServletContext context,
@GerritServerConfig final Config cfg) {
- this.userProvider = userProvider;
+ this.user = user;
this.queue = queue;
this.context = context;
this.maxWait = MINUTES.toMillis(getTimeUnit(cfg, "httpd", null, "maxwait", 5, MINUTES));
@@ -142,7 +141,7 @@
}
private WorkQueue.Executor getExecutor() {
- return queue.getQueue(userProvider.get().getCapabilities().getQueueType());
+ return queue.getQueue(user.get().getCapabilities().getQueueType());
}
@Override
@@ -226,9 +225,9 @@
private String generateName(HttpServletRequest req) {
String userName = "";
- CurrentUser who = userProvider.get();
+ CurrentUser who = user.get();
if (who.isIdentifiedUser()) {
- String name = ((IdentifiedUser) who).getUserName();
+ String name = who.asIdentifiedUser().getUserName();
if (name != null && !name.isEmpty()) {
userName = " (" + name + ")";
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index 1c70468..cd1c7a3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -28,6 +28,7 @@
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.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -222,6 +223,7 @@
throw die(err);
}
+ m.add(new GerritServerConfigModule());
m.add(new InitModule(standalone, initDb));
m.add(new AbstractModule() {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
index a0c24a6..a11f56f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
@@ -32,6 +32,8 @@
bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("db2")).to(DB2Initializer.class);
bind(DatabaseConfigInitializer.class).annotatedWith(
+ Names.named("derby")).to(DerbyInitializer.class);
+ bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("h2")).to(H2Initializer.class);
bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("jdbc")).to(JDBCInitializer.class);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DerbyInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DerbyInitializer.java
new file mode 100644
index 0000000..4d710f1
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DerbyInitializer.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2015 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.init;
+
+import static com.google.gerrit.pgm.init.api.InitUtil.die;
+
+import com.google.gerrit.common.FileUtil;
+import com.google.gerrit.pgm.init.api.Section;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+
+import java.nio.file.Path;
+
+class DerbyInitializer implements DatabaseConfigInitializer {
+
+ private final SitePaths site;
+
+ @Inject
+ DerbyInitializer(final SitePaths site) {
+ this.site = site;
+ }
+
+ @Override
+ public void initConfig(Section databaseSection) {
+ String path = databaseSection.get("database");
+ Path db;
+ if (path == null) {
+ db = site.resolve("db").resolve("ReviewDB");
+ databaseSection.set("database", db.toString());
+ } else {
+ db = site.resolve(path);
+ }
+ if (db == null) {
+ throw die("database.database must be supplied for Derby");
+ }
+ db = db.getParent();
+ FileUtil.mkdirsOrDie(db, "cannot create database.database");
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
index b670d39..a09dcd5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm.init;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.pgm.init.api.ConsoleUI;
@@ -32,7 +34,6 @@
import org.apache.commons.validator.routines.EmailValidator;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -153,7 +154,7 @@
throw new IOException(String.format(
"Cannot add public SSH key: %s is not a file", keyFile));
}
- String content = new String(Files.readAllBytes(p), StandardCharsets.UTF_8);
+ String content = new String(Files.readAllBytes(p), UTF_8);
return new AccountSshKey(new AccountSshKey.Id(id, 0), content);
}
}
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 a3a3f27..69f2c15 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
@@ -61,9 +61,6 @@
if (auth.getSecure("registerEmailPrivateKey") == null) {
auth.setSecure("registerEmailPrivateKey", SignedToken.generateRandomKey());
}
- if (auth.getSecure("restTokenPrivateKey") == null) {
- auth.setSecure("restTokenPrivateKey", SignedToken.generateRandomKey());
- }
initSignedPush();
}
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 dfd6171..4659ee3 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
@@ -33,7 +33,9 @@
private void guessDriver(Section database) {
String url = Strings.emptyToNull(database.get("url"));
if (url != null && Strings.isNullOrEmpty(database.get("driver"))) {
- if (url.startsWith("jdbc:h2:")) {
+ if (url.startsWith("jdbc:derby:")) {
+ database.set("driver", "org.apache.derby.jdbc.EmbeddedDriver");
+ } else if (url.startsWith("jdbc:h2:")) {
database.set("driver", "org.h2.Driver");
} else if (url.startsWith("jdbc:mysql:")) {
database.set("driver", "com.mysql.jdbc.Driver");
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 8ae712d..7cc8f10 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
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm.init;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -120,7 +122,7 @@
if (in == null) {
throw new FileNotFoundException("Cannot load resource " + p);
}
- try (Reader r = new InputStreamReader(in, "UTF-8")) {
+ try (Reader r = new InputStreamReader(in, UTF_8)) {
final StringBuilder buf = new StringBuilder();
final char[] tmp = new char[512];
int n;
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 9c9843e..e4cc305 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
@@ -228,7 +228,7 @@
for (Path p : paths) {
String old = p.getFileName().toString();
String bak = "." + old + ".backup";
- ui.message("Renaming %s to %s", old, bak);
+ ui.message("Renaming %s to %s\n", old, bak);
try {
Files.move(p, p.resolveSibling(bak));
} catch (IOException e) {
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 21cd3c8..3c91241 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
@@ -16,6 +16,7 @@
import static com.google.gerrit.pgm.init.api.InitUtil.die;
import static com.google.gerrit.pgm.init.api.InitUtil.savePublic;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
@@ -172,8 +173,8 @@
return false;
}
- String n = URLDecoder.decode(pair.substring(0, eq), "UTF-8");
- String v = URLDecoder.decode(pair.substring(eq + 1), "UTF-8");
+ String n = URLDecoder.decode(pair.substring(0, eq), UTF_8.name());
+ String v = URLDecoder.decode(pair.substring(eq + 1), UTF_8.name());
if ("user".equals(n) || "username".equals(n)) {
username = v;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
index 02969d9..e210d5b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
@@ -14,8 +14,6 @@
package com.google.gerrit.pgm.init.api;
-import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
-
import com.google.gerrit.common.Die;
import java.io.Console;
@@ -218,7 +216,7 @@
return def;
}
for (final T e : options) {
- if (equalsIgnoreCase(e.toString(), r)) {
+ if (e.toString().equalsIgnoreCase(r)) {
return e;
}
}
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
index 5d09305..b6539f1 100644
--- 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
@@ -36,12 +36,14 @@
import com.google.gerrit.server.change.ChangeKindCacheImpl;
import com.google.gerrit.server.change.MergeabilityCacheImpl;
import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.change.RebaseChangeOp;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.DisableReverseDnsLookup;
import com.google.gerrit.server.config.DisableReverseDnsLookupProvider;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.group.GroupModule;
@@ -106,8 +108,10 @@
bind(ReplacePatchSetSender.Factory.class).toProvider(
Providers.<ReplacePatchSetSender.Factory>of(null));
bind(CurrentUser.class).to(IdentifiedUser.class);
+ factory(BatchUpdate.Factory.class);
factory(MergeUtil.Factory.class);
factory(PatchSetInserter.Factory.class);
+ factory(RebaseChangeOp.Factory.class);
bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
.annotatedWith(GitUploadPackGroups.class)
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
index 8a0d9a1..95caf25 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
@@ -81,7 +81,7 @@
if (text) {
root.addAppender(SystemLog.createAppender(logdir, LOG_NAME,
- new PatternLayout("[%d] %-5p %c %x: %m%n")));
+ new PatternLayout("[%d] [%t] %-5p %c %x: %m%n")));
}
if (json) {
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index ed11e0f..abcacf9 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -20,7 +20,6 @@
java_library(
name = 'lib',
exported_deps = PLUGIN_API + [
- '//gerrit-acceptance-tests:lib',
'//gerrit-antlr:query_exception',
'//gerrit-antlr:query_parser',
'//gerrit-common:annotations',
@@ -34,11 +33,13 @@
'//lib:jsch',
'//lib:mime-util',
'//lib:servlet-api-3_1',
+ '//lib:velocity',
'//lib/commons:lang',
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet',
'//lib/jgit:jgit',
+ '//lib/joda:joda-time',
'//lib/log:api',
'//lib/mina:sshd',
],
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK
index ec5903a..2ee0e19 100644
--- a/gerrit-plugin-gwtui/BUCK
+++ b/gerrit-plugin-gwtui/BUCK
@@ -55,7 +55,7 @@
deps = DEPS + [
'//lib:gwtjsonrpc',
'//lib:gwtorm_client',
- '//lib/gwt:dev__jar',
+ '//lib/gwt:dev',
'//gerrit-gwtui-common:client-lib',
'//gerrit-common:client',
'//gerrit-reviewdb:client',
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/ui/GroupSuggestOracle.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/ui/GroupSuggestOracle.java
new file mode 100644
index 0000000..528b07a
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/ui/GroupSuggestOracle.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2015 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.plugin.client.ui;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.ui.HighlightSuggestion;
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.SuggestOracle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** A {@code SuggestOracle} for groups. */
+public class GroupSuggestOracle extends SuggestOracle {
+
+ private final int chars;
+
+ /**
+ * @param chars minimum chars to start suggesting.
+ */
+ public GroupSuggestOracle(int chars) {
+ this.chars = chars;
+ }
+
+ @Override
+ public boolean isDisplayStringHTML() {
+ return true;
+ }
+
+ @Override
+ public void requestSuggestions(final Request req, final Callback done) {
+ if (req.getQuery().length() < chars) {
+ responseEmptySuggestion(req, done);
+ return;
+ }
+ RestApi rest = new RestApi("/groups/").addParameter("suggest", req.getQuery());
+ if (req.getLimit() > 0) {
+ rest.addParameter("n", req.getLimit());
+ }
+ rest.get(new AsyncCallback<NativeMap<JavaScriptObject>>() {
+ @Override
+ public void onSuccess(NativeMap<JavaScriptObject> result) {
+ List<String> keys = result.sortedKeys();
+ List<Suggestion> suggestions = new ArrayList<>(keys.size());
+ for (String g : keys) {
+ suggestions.add(new HighlightSuggestion(req.getQuery(), g));
+ }
+ done.onSuggestionsReady(req, new Response(suggestions));
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ responseEmptySuggestion(req, done);
+ }
+ });
+ }
+
+ private static void responseEmptySuggestion(Request req, Callback done) {
+ List<Suggestion> empty = Collections.emptyList();
+ done.onSuggestionsReady(req, new Response(empty));
+ }
+}
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 0701771..26d31da 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
@@ -125,6 +125,23 @@
id = newValue;
}
+ public String toRefPrefix() {
+ return refPrefixBuilder().toString();
+ }
+
+ StringBuilder refPrefixBuilder() {
+ StringBuilder r = new StringBuilder(32)
+ .append(REFS_CHANGES);
+ int m = id % 100;
+ if (m < 10) {
+ r.append('0');
+ }
+ return r.append(m)
+ .append('/')
+ .append(id)
+ .append('/');
+ }
+
/** Parse a Change.Id out of a string representation. */
public static Id parse(final String str) {
final Id r = new Id();
@@ -453,6 +470,13 @@
@Column(id = 17, notNull = false)
protected String originalSubject;
+ /**
+ * Unique id for the changes submitted together assigned during merging.
+ * Only set if the status is MERGED.
+ */
+ @Column(id = 18, notNull = false)
+ protected String submissionId;
+
protected Change() {
}
@@ -479,6 +503,7 @@
currentPatchSetId = other.currentPatchSetId;
subject = other.subject;
originalSubject = other.originalSubject;
+ submissionId = other.submissionId;
topic = other.topic;
}
@@ -562,6 +587,14 @@
}
}
+ public String getSubmissionId() {
+ return submissionId;
+ }
+
+ public void setSubmissionId(String id) {
+ this.submissionId = id;
+ }
+
public Status getStatus() {
return Status.forCode(status);
}
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 4ec957e..ae1b75b 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
@@ -14,8 +14,6 @@
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;
@@ -26,10 +24,20 @@
/** A single revision of a {@link Change}. */
public final class PatchSet {
/** Is the reference name a change reference? */
- public static boolean isRef(String name) {
+ public static boolean isChangeRef(String name) {
return Id.fromRef(name) != null;
}
+ /**
+ * Is the reference name a change reference?
+ *
+ * @deprecated use isChangeRef instead.
+ **/
+ @Deprecated
+ public static boolean isRef(String name) {
+ return isChangeRef(name);
+ }
+
public static String joinGroups(Iterable<String> groups) {
if (groups == null) {
return null;
@@ -99,19 +107,9 @@
}
public String toRefName() {
- StringBuilder r = new StringBuilder();
- r.append(REFS_CHANGES);
- int change = changeId.get();
- int m = change % 100;
- if (m < 10) {
- r.append('0');
- }
- r.append(m);
- r.append('/');
- r.append(change);
- r.append('/');
- r.append(patchSetId);
- return r.toString();
+ return changeId.refPrefixBuilder()
+ .append(patchSetId)
+ .toString();
}
/** Parse a PatchSet.Id out of a string representation. */
@@ -189,6 +187,10 @@
@Column(id = 6, notNull = false)
protected String groups;
+ /** Certificate sent with a push that created this patch set. */
+ @Column(id = 7, notNull = false)
+ protected String pushCertficate;
+
protected PatchSet() {
}
@@ -248,6 +250,14 @@
return id.toRefName();
}
+ public String getPushCertificate() {
+ return pushCertficate;
+ }
+
+ public void setPushCertificate(String cert) {
+ pushCertficate = cert;
+ }
+
@Override
public String toString() {
return "[PatchSet " + getId().toString() + "]";
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index ce1b27f..af9e75c 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -97,6 +97,7 @@
protected InheritableBoolean createNewChangeForAllNotInTarget;
protected InheritableBoolean enableSignedPush;
+ protected InheritableBoolean requireSignedPush;
protected Project() {
}
@@ -111,6 +112,7 @@
useContentMerge = InheritableBoolean.INHERIT;
createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
enableSignedPush = InheritableBoolean.INHERIT;
+ requireSignedPush = InheritableBoolean.INHERIT;
}
public Project.NameKey getNameKey() {
@@ -182,6 +184,14 @@
enableSignedPush = enable;
}
+ public InheritableBoolean getRequireSignedPush() {
+ return requireSignedPush;
+ }
+
+ public void setRequireSignedPush(InheritableBoolean require) {
+ requireSignedPush = require;
+ }
+
public void setMaxObjectSizeLimit(final String limit) {
maxObjectSizeLimit = limit;
}
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 da66929..5d2a1fd 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
@@ -21,6 +21,8 @@
public static final String REFS_HEADS = "refs/heads/";
+ public static final String REFS_TAGS = "refs/tags/";
+
public static final String REFS_CHANGES = "refs/changes/";
/** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
@@ -62,9 +64,12 @@
}
public static final String shortName(String ref) {
- return ref.startsWith(REFS_HEADS)
- ? ref.substring(REFS_HEADS.length())
- : ref;
+ if (ref.startsWith(REFS_HEADS)) {
+ return ref.substring(REFS_HEADS.length());
+ } else if (ref.startsWith(REFS_TAGS)) {
+ return ref.substring(REFS_TAGS.length());
+ }
+ return ref;
}
public static String refsUsers(Account.Id accountId) {
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
index 218d04f..47f409a 100644
--- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/ChangeTest.java
@@ -14,8 +14,7 @@
package com.google.gerrit.reviewdb.client;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static com.google.common.truth.Truth.assertThat;
import org.junit.Test;
@@ -72,11 +71,19 @@
assertNotRef("refs/changes/01/1/1/meta");
}
+ @Test
+ public void toRefPrefix() {
+ assertThat(new Change.Id(1).toRefPrefix())
+ .isEqualTo("refs/changes/01/1/");
+ assertThat(new Change.Id(1234).toRefPrefix())
+ .isEqualTo("refs/changes/34/1234/");
+ }
+
private static void assertRef(int changeId, String refName) {
- assertEquals(new Change.Id(changeId), Change.Id.fromRef(refName));
+ assertThat(Change.Id.fromRef(refName)).isEqualTo(new Change.Id(changeId));
}
private static void assertNotRef(String refName) {
- assertNull(Change.Id.fromRef(refName));
+ assertThat(Change.Id.fromRef(refName)).isNull();
}
}
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
index 87e5b88..7a6be87 100644
--- 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
@@ -83,14 +83,22 @@
assertThat(joinGroups(ImmutableList.of("", "cd"))).isEqualTo(",cd");
}
+ @Test
+ public void testToRefName() {
+ assertThat(new PatchSet.Id(new Change.Id(1), 23).toRefName())
+ .isEqualTo("refs/changes/01/1/23");
+ assertThat(new PatchSet.Id(new Change.Id(1234), 5).toRefName())
+ .isEqualTo("refs/changes/34/1234/5");
+ }
+
private static void assertRef(int changeId, int psId, String refName) {
- assertThat(PatchSet.isRef(refName)).isTrue();
+ assertThat(PatchSet.isChangeRef(refName)).isTrue();
assertThat(PatchSet.Id.fromRef(refName))
.isEqualTo(new PatchSet.Id(new Change.Id(changeId), psId));
}
private static void assertNotRef(String refName) {
- assertThat(PatchSet.isRef(refName)).isFalse();
+ assertThat(PatchSet.isChangeRef(refName)).isFalse();
assertThat(PatchSet.Id.fromRef(refName)).isNull();
}
}
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 31a9b49..c264779 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -82,6 +82,26 @@
visibility = ['PUBLIC'],
)
+TESTUTIL_DEPS = [
+ ':server',
+ '//gerrit-common:server',
+ '//gerrit-cache-h2:cache-h2',
+ '//gerrit-extension-api:api',
+ '//gerrit-gpg:gpg',
+ '//gerrit-lucene:lucene',
+ '//gerrit-reviewdb:server',
+ '//lib:gwtorm',
+ '//lib:h2',
+ '//lib:truth',
+ '//lib/guice:guice',
+ '//lib/guice:guice-servlet',
+ '//lib/jgit:jgit',
+ '//lib/jgit:junit',
+ '//lib/log:api',
+ '//lib/log:impl_log4j',
+ '//lib/log:log4j',
+]
+
TESTUTIL = glob([
'src/test/java/com/google/gerrit/testutil/**/*.java',
'src/test/java/com/google/gerrit/server/project/Util.java',
@@ -90,25 +110,9 @@
name = 'testutil',
srcs = TESTUTIL,
deps = [
- ':server',
- '//gerrit-common:server',
- '//gerrit-cache-h2:cache-h2',
- '//gerrit-extension-api:api',
- '//gerrit-gpg:gpg',
- '//gerrit-lucene:lucene',
- '//gerrit-reviewdb:server',
- '//lib:gwtorm',
- '//lib:h2',
- '//lib:truth',
'//lib/auto:auto-value',
- '//lib/guice:guice',
- '//lib/guice:guice-servlet',
- '//lib/jgit:jgit',
- '//lib/jgit:junit',
- '//lib/log:api',
- '//lib/log:impl_log4j',
- '//lib/log:log4j',
],
+ provided_deps = TESTUTIL_DEPS,
exported_deps = [
'//lib/easymock:easymock',
'//lib/powermock:powermock-api-easymock',
@@ -147,19 +151,10 @@
name = 'prolog_tests',
srcs = PROLOG_TESTS,
resources = glob(['src/test/resources/com/google/gerrit/rules/**/*']),
- deps = [
+ deps = TESTUTIL_DEPS + [
':prolog_test_case',
- ':server',
':testutil',
- '//gerrit-common:server',
- '//gerrit-reviewdb:server',
'//gerrit-server/src/main/prolog:common',
- '//lib:guava',
- '//lib:gwtorm',
- '//lib:junit',
- '//lib:truth',
- '//lib/jgit:jgit',
- '//lib/guice:guice',
'//lib/prolog:runtime',
],
)
@@ -171,22 +166,13 @@
java_test(
name = 'query_tests',
srcs = QUERY_TESTS,
- deps = [
- ':server',
+ deps = TESTUTIL_DEPS + [
':testutil',
'//gerrit-antlr:query_exception',
'//gerrit-antlr:query_parser',
'//gerrit-common:annotations',
- '//gerrit-common:server',
- '//gerrit-extension-api:api',
- '//gerrit-reviewdb:server',
'//gerrit-server/src/main/prolog:common',
- '//lib:gwtorm',
- '//lib:truth',
'//lib/antlr:java_runtime',
- '//lib/guice:guice',
- '//lib/jgit:jgit',
- '//lib/jgit:junit',
'//lib/joda:joda-time',
],
source_under_test = [':server'],
@@ -199,25 +185,15 @@
['src/test/java/**/*.java'],
excludes = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE + QUERY_TESTS
),
- deps = [
- ':server',
+ deps = TESTUTIL_DEPS + [
':testutil',
'//gerrit-antlr:query_exception',
'//gerrit-common:annotations',
- '//gerrit-common:server',
- '//gerrit-extension-api:api',
- '//gerrit-gpg:gpg',
- '//gerrit-reviewdb:server',
'//gerrit-server/src/main/prolog:common',
'//lib:args4j',
'//lib:grappa',
'//lib:guava',
- '//lib:gwtorm',
- '//lib:truth',
- '//lib/guice:guice',
'//lib/guice:guice-assistedinject',
- '//lib/jgit:jgit',
- '//lib/jgit:junit',
'//lib/joda:joda-time',
'//lib/prolog:runtime',
],
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
index 641ba03..57a2946 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
@@ -14,6 +14,8 @@
package com.google.gerrit.common;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +41,7 @@
if (in == null) {
return "(dev)";
}
- try (BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(in, UTF_8))) {
String vs = r.readLine();
if (vs != null && vs.startsWith("v")) {
vs = vs.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index b3cf660..1d1f571 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.change.ChangeKind.NO_CHANGE;
import static com.google.gerrit.server.change.ChangeKind.NO_CODE_CHANGE;
import static com.google.gerrit.server.change.ChangeKind.TRIVIAL_REBASE;
@@ -45,6 +46,7 @@
import java.io.IOException;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;
@@ -86,16 +88,22 @@
Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
ChangeControl ctl, PatchSet.Id psId) throws OrmException {
- return getForPatchSet(db, ctl, db.patchSets().get(psId));
+ PatchSet ps = db.patchSets().get(psId);
+ if (ps == null) {
+ return Collections.emptyList();
+ }
+ return getForPatchSet(db, ctl, ps);
}
private Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
ChangeControl ctl, PatchSet ps) throws OrmException {
+ checkNotNull(ps, "ps should not be null");
ChangeData cd = changeDataFactory.create(db, ctl);
try {
ProjectState project =
projectCache.checkedGet(cd.change().getDest().getParentKey());
ListMultimap<PatchSet.Id, PatchSetApproval> all = cd.approvals();
+ checkNotNull(all, "all should not be null");
Table<String, Account.Id, PatchSetApproval> byUser =
HashBasedTable.create();
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 7265196..31058bc 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
@@ -222,9 +222,8 @@
}
public void addApprovals(ReviewDb db, ChangeUpdate update,
- LabelTypes labelTypes, PatchSet ps, PatchSetInfo info,
- ChangeControl changeCtl, Map<String, Short> approvals)
- throws OrmException {
+ LabelTypes labelTypes, PatchSet ps, ChangeControl changeCtl,
+ Map<String, Short> approvals) throws OrmException {
if (!approvals.isEmpty()) {
checkApprovals(approvals, changeCtl);
List<PatchSetApproval> cells = new ArrayList<>(approvals.size());
@@ -233,7 +232,7 @@
LabelType lt = labelTypes.byLabel(vote.getKey());
cells.add(new PatchSetApproval(new PatchSetApproval.Key(
ps.getId(),
- info.getCommitter().getAccount(),
+ ps.getUploader(),
lt.getLabelId()),
vote.getValue(),
ts));
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 590d065..0de7e38 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
@@ -22,8 +22,8 @@
import com.google.common.collect.Ordering;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.client.Project;
@@ -32,21 +32,18 @@
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeMessages;
import com.google.gerrit.server.change.ChangeTriplet;
-import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.RevertedSender;
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.project.RefControl;
import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.IdGenerator;
-import com.google.gerrit.server.util.MagicBranch;
import com.google.gwtorm.server.OrmConcurrencyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -193,8 +190,7 @@
return subject;
}
- private final Provider<CurrentUser> userProvider;
- private final CommitValidators.Factory commitValidatorsFactory;
+ private final Provider<IdentifiedUser> user;
private final Provider<ReviewDb> db;
private final Provider<InternalChangeQuery> queryProvider;
private final RevertedSender.Factory revertedSenderFactory;
@@ -202,19 +198,19 @@
private final GitRepositoryManager gitManager;
private final GitReferenceUpdated gitRefUpdated;
private final ChangeIndexer indexer;
+ private final BatchUpdate.Factory updateFactory;
@Inject
- ChangeUtil(Provider<CurrentUser> userProvider,
- CommitValidators.Factory commitValidatorsFactory,
+ ChangeUtil(Provider<IdentifiedUser> user,
Provider<ReviewDb> db,
Provider<InternalChangeQuery> queryProvider,
RevertedSender.Factory revertedSenderFactory,
ChangeInserter.Factory changeInserterFactory,
GitRepositoryManager gitManager,
GitReferenceUpdated gitRefUpdated,
- ChangeIndexer indexer) {
- this.userProvider = userProvider;
- this.commitValidatorsFactory = commitValidatorsFactory;
+ ChangeIndexer indexer,
+ BatchUpdate.Factory updateFactory) {
+ this.user = user;
this.db = db;
this.queryProvider = queryProvider;
this.revertedSenderFactory = revertedSenderFactory;
@@ -222,13 +218,14 @@
this.gitManager = gitManager;
this.gitRefUpdated = gitRefUpdated;
this.indexer = indexer;
+ this.updateFactory = updateFactory;
}
public Change.Id revert(ChangeControl ctl, PatchSet.Id patchSetId,
- String message, PersonIdent myIdent, SshInfo sshInfo)
+ String message, PersonIdent myIdent)
throws NoSuchChangeException, OrmException,
MissingObjectException, IncorrectObjectTypeException, IOException,
- InvalidChangeOperationException {
+ RestApiException, UpdateException {
Change.Id changeId = patchSetId.getParentKey();
PatchSet patch = db.get().patchSets().get(patchSetId);
if (patch == null) {
@@ -242,8 +239,8 @@
RevCommit commitToRevert =
revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
- PersonIdent authorIdent =
- user().newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
+ PersonIdent authorIdent = user.get()
+ .newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
revWalk.parseHeaders(parentToCommitToRevert);
@@ -267,73 +264,49 @@
ChangeIdUtil.insertId(message, computedChangeId, true));
RevCommit revertCommit;
+ ChangeInserter ins;
try (ObjectInserter oi = git.newObjectInserter()) {
ObjectId id = oi.insert(revertCommitBuilder);
oi.flush();
revertCommit = revWalk.parseCommit(id);
+
+ RefControl refControl = ctl.getRefControl();
+ Change change = new Change(
+ new Change.Key("I" + computedChangeId.name()),
+ new Change.Id(db.get().nextChangeId()),
+ user.get().getAccountId(),
+ changeToRevert.getDest(),
+ TimeUtil.nowTs());
+ change.setTopic(changeToRevert.getTopic());
+ ins = changeInserterFactory.create(
+ refControl, change, revertCommit)
+ .setValidatePolicy(CommitValidators.Policy.GERRIT);
+ StringBuilder msgBuf = new StringBuilder();
+ msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
+ msgBuf.append("\n\n");
+ msgBuf.append("This patchset was reverted in change: ")
+ .append(change.getKey().get());
+ ins.setMessage(msgBuf.toString());
+ try (BatchUpdate bu = updateFactory.create(
+ db.get(), change.getProject(), refControl.getUser(),
+ change.getCreatedOn())) {
+ bu.setRepository(git, revWalk, oi);
+ bu.insertChange(ins);
+ bu.execute();
+ }
}
- RefControl refControl = ctl.getRefControl();
- Change change = new Change(
- new Change.Key("I" + computedChangeId.name()),
- new Change.Id(db.get().nextChangeId()),
- user().getAccountId(),
- changeToRevert.getDest(),
- TimeUtil.nowTs());
- change.setTopic(changeToRevert.getTopic());
- ChangeInserter ins =
- changeInserterFactory.create(refControl.getProjectControl(),
- change, revertCommit);
- PatchSet ps = ins.getPatchSet();
-
- String ref = refControl.getRefName();
- String cmdRef = MagicBranch.NEW_PUBLISH_CHANGE
- + ref.substring(ref.lastIndexOf('/') + 1);
- CommitReceivedEvent commitReceivedEvent = new CommitReceivedEvent(
- new ReceiveCommand(ObjectId.zeroId(), revertCommit.getId(), cmdRef),
- refControl.getProjectControl().getProject(),
- refControl.getRefName(), revertCommit, user());
-
+ Change.Id id = ins.getChange().getId();
try {
- commitValidatorsFactory.create(refControl, sshInfo, git)
- .validateForGerritCommits(commitReceivedEvent);
- } catch (CommitValidationException e) {
- throw new InvalidChangeOperationException(e.getMessage());
- }
-
- RefUpdate ru = git.updateRef(ps.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(revertCommit);
- ru.disableRefLog();
- if (ru.update(revWalk) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", ps.getRefName(),
- change.getDest().getParentKey().get(), ru.getResult()));
- }
-
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(changeId, messageUUID(db.get())),
- user().getAccountId(), TimeUtil.nowTs(), patchSetId);
- StringBuilder msgBuf = new StringBuilder();
- msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
- msgBuf.append("\n\n");
- msgBuf.append("This patchset was reverted in change: ")
- .append(change.getKey().get());
- cmsg.setMessage(msgBuf.toString());
-
- ins.setMessage(cmsg).insert();
-
- try {
- RevertedSender cm = revertedSenderFactory.create(change.getId());
- cm.setFrom(user().getAccountId());
- cm.setChangeMessage(cmsg);
+ RevertedSender cm = revertedSenderFactory.create(id);
+ cm.setFrom(user.get().getAccountId());
+ cm.setChangeMessage(ins.getChangeMessage());
cm.send();
} catch (Exception err) {
- log.error("Cannot send email for revert change " + change.getId(),
- err);
+ log.error("Cannot send email for revert change " + id, err);
}
- return change.getId();
+ return id;
} catch (RepositoryNotFoundException e) {
throw new NoSuchChangeException(changeId, e);
}
@@ -476,10 +449,6 @@
throw new ResourceNotFoundException(id);
}
- private IdentifiedUser user() {
- return (IdentifiedUser) userProvider.get();
- }
-
private static void deleteOnlyDraftPatchSetPreserveRef(ReviewDb db,
PatchSet patch) throws NoSuchChangeException, OrmException {
PatchSet.Id patchSetId = patch.getId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 4f2c6b9..6a8600f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.CapabilityControl;
@@ -100,4 +101,21 @@
public boolean isIdentifiedUser() {
return false;
}
+
+ /** Cast to IdentifiedUser if possible. */
+ public IdentifiedUser asIdentifiedUser() {
+ throw new UnsupportedOperationException(
+ getClass().getSimpleName() + " is not an IdentifiedUser");
+ }
+
+ /** Return account ID if {@link #isIdentifiedUser} is true. */
+ public Account.Id getAccountId() {
+ throw new UnsupportedOperationException(
+ getClass().getSimpleName() + " is not an IdentifiedUser");
+ }
+
+ /** Check if the CurrentUser is an InternalUser. */
+ public boolean isInternalUser() {
+ return false;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index baba4bb..df25042 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -39,7 +39,6 @@
import com.google.gerrit.server.config.DisableReverseDnsLookup;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.OutOfScopeException;
@@ -255,7 +254,12 @@
return state;
}
- /** The account identity for the user. */
+ @Override
+ public IdentifiedUser asIdentifiedUser() {
+ return this;
+ }
+
+ @Override
public Account.Id getAccountId() {
return accountId;
}
@@ -334,8 +338,11 @@
try {
starredChanges = starredChangeIds(
starredQuery != null ? starredQuery : starredQuery());
- } catch (OrmException | OrmRuntimeException e) {
+ } catch (OrmException | RuntimeException e) {
log.warn("Cannot query starred changes", e);
+ starredChanges = Collections.emptySet();
+ } finally {
+ starredQuery = null;
}
}
return starredChanges;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
index ef28ed8..d0c2dc0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java
@@ -62,6 +62,11 @@
}
@Override
+ public boolean isInternalUser() {
+ return true;
+ }
+
+ @Override
public String toString() {
return "InternalUser";
}
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 15519cc..7b182b1 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
@@ -68,7 +68,7 @@
*/
@Singleton
public class PatchLineCommentsUtil {
- public static Ordering<PatchLineComment> PLC_ORDER =
+ public static final Ordering<PatchLineComment> PLC_ORDER =
new Ordering<PatchLineComment>() {
@Override
public int compare(PatchLineComment c1, PatchLineComment c2) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
index 825bd3b..aad427b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
@@ -61,19 +61,19 @@
private final AccountsSection accountsSection;
private final GroupControl.Factory groupControlFactory;
- private final CurrentUser currentUser;
+ private final CurrentUser user;
private final IdentifiedUser.GenericFactory userFactory;
private final AccountVisibility accountVisibility;
AccountControl(final ProjectCache projectCache,
final GroupControl.Factory groupControlFactory,
- final CurrentUser currentUser,
+ final CurrentUser user,
final IdentifiedUser.GenericFactory userFactory,
final AccountVisibility accountVisibility) {
this.accountsSection =
projectCache.getAllProjects().getConfig().getAccountsSection();
this.groupControlFactory = groupControlFactory;
- this.currentUser = currentUser;
+ this.user = user;
this.userFactory = userFactory;
this.accountVisibility = accountVisibility;
}
@@ -100,11 +100,10 @@
*/
public boolean canSee(final Account.Id otherUser) {
// Special case: I can always see myself.
- if (currentUser.isIdentifiedUser()
- && ((IdentifiedUser) currentUser).getAccountId().equals(otherUser)) {
+ if (user.isIdentifiedUser() && user.getAccountId().equals(otherUser)) {
return true;
}
- if (currentUser.getCapabilities().canViewAllAccounts()) {
+ if (user.getCapabilities().canViewAllAccounts()) {
return true;
}
@@ -119,7 +118,7 @@
}
}
- if (currentUser.getEffectiveGroups().containsAnyOf(usersGroups)) {
+ if (user.getEffectiveGroups().containsAnyOf(usersGroups)) {
return true;
}
break;
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 66c7570..7de3d64 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
@@ -106,7 +106,7 @@
try {
try (ReviewDb db = schema.open()) {
AccountExternalId.Key key = id(who);
- AccountExternalId id = db.accountExternalIds().get(key);
+ AccountExternalId id = getAccountExternalId(db, key);
if (id == null) {
// New account, automatically create and return.
//
@@ -130,6 +130,29 @@
}
}
+ private AccountExternalId getAccountExternalId(ReviewDb db,
+ AccountExternalId.Key key) throws OrmException {
+ String keyValue = key.get();
+ String keyScheme = keyValue.substring(0, keyValue.indexOf(':') + 1);
+
+ // We don't have at the moment an account_by_external_id cache
+ // but by using the accounts cache we get the list of external_ids
+ // without having to query the DB every time
+ if (keyScheme.equals(AccountExternalId.SCHEME_GERRIT)
+ || keyScheme.equals(AccountExternalId.SCHEME_USERNAME)) {
+ AccountState state = byIdCache.getByUsername(
+ keyValue.substring(keyScheme.length()));
+ if (state != null) {
+ for (AccountExternalId accountExternalId : state.getExternalIds()) {
+ if (accountExternalId.getKey().equals(key)) {
+ return accountExternalId;
+ }
+ }
+ }
+ }
+ return db.accountExternalIds().get(key);
+ }
+
private void update(ReviewDb db, AuthRequest who, AccountExternalId extId)
throws OrmException, NameAlreadyUsedException, InvalidUserNameException {
IdentifiedUser user = userFactory.create(extId.getAccountId());
@@ -152,12 +175,14 @@
}
if (!realm.allowsEdit(Account.FieldName.FULL_NAME)
+ && who.getDisplayName() != null
&& !eq(user.getAccount().getFullName(), who.getDisplayName())) {
toUpdate = load(toUpdate, user.getAccountId(), db);
toUpdate.setFullName(who.getDisplayName());
}
if (!realm.allowsEdit(Account.FieldName.USER_NAME)
+ && who.getUserName() != null
&& !eq(user.getUserName(), who.getUserName())) {
changeUserNameFactory.create(db, user, who.getUserName()).call();
}
@@ -322,7 +347,7 @@
who = realm.link(db, to, who);
AccountExternalId.Key key = id(who);
- AccountExternalId extId = db.accountExternalIds().get(key);
+ AccountExternalId extId = getAccountExternalId(db, key);
if (extId != null) {
if (!extId.getAccountId().equals(to)) {
throw new AccountException("Identity in use by another account");
@@ -413,7 +438,7 @@
who = realm.unlink(db, from, who);
AccountExternalId.Key key = id(who);
- AccountExternalId extId = db.accountExternalIds().get(key);
+ AccountExternalId extId = getAccountExternalId(db, key);
if (extId != null) {
if (!extId.getAccountId().equals(from)) {
throw new AccountException("Identity in use by another account");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index 80a451a..80ea907 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -114,7 +114,7 @@
if (id.equals("self")) {
CurrentUser user = self.get();
if (user.isIdentifiedUser()) {
- return (IdentifiedUser) user;
+ return user.asIdentifiedUser();
} else if (user instanceof AnonymousUser) {
throw new AuthException("Authentication required");
} else {
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 a8eec10..7bb00c5 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
@@ -58,7 +58,7 @@
}
/** Identity of the user the control will compute for. */
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
new file mode 100644
index 0000000..e6e7644
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
@@ -0,0 +1,67 @@
+// 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.account;
+
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+
+@Singleton
+public class GetEditPreferences implements RestReadView<AccountResource> {
+ private final Provider<CurrentUser> self;
+ private final AllUsersName allUsersName;
+ private final GitRepositoryManager gitMgr;
+
+ @Inject
+ GetEditPreferences(Provider<CurrentUser> self,
+ AllUsersName allUsersName,
+ GitRepositoryManager gitMgr) {
+ this.self = self;
+ this.allUsersName = allUsersName;
+ this.gitMgr = gitMgr;
+ }
+
+ @Override
+ public EditPreferencesInfo apply(AccountResource rsrc) throws AuthException,
+ IOException, ConfigInvalidException {
+ if (self.get() != rsrc.getUser()
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("restricted to members of Modify Accounts");
+ }
+
+ try (Repository git = gitMgr.openRepository(allUsersName)) {
+ VersionedAccountPreferences p =
+ VersionedAccountPreferences.forUser(rsrc.getUser().getAccountId());
+ p.load(git);
+
+ return loadSection(p.getConfig(), UserConfigSections.EDIT, null,
+ new EditPreferencesInfo(), EditPreferencesInfo.defaults());
+ }
+ }
+}
\ No newline at end of file
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 3df6837..08bf83e 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
@@ -30,6 +30,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UserConfigSections;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -51,7 +52,6 @@
public class GetPreferences implements RestReadView<AccountResource> {
private static final Logger log = LoggerFactory.getLogger(GetPreferences.class);
- public static final String MY = "my";
public static final String KEY_URL = "url";
public static final String KEY_TARGET = "target";
public static final String KEY_ID = "id";
@@ -165,7 +165,7 @@
private List<TopMenu.MenuItem> my(VersionedAccountPreferences v) {
List<TopMenu.MenuItem> my = new ArrayList<>();
Config cfg = v.getConfig();
- for (String subsection : cfg.getSubsections(MY)) {
+ for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
String url = my(cfg, subsection, KEY_URL, "#/");
String target = my(cfg, subsection, KEY_TARGET,
url.startsWith("#") ? null : "_blank");
@@ -178,7 +178,7 @@
private static String my(Config cfg, String subsection, String key,
String defaultValue) {
- String val = cfg.getString(MY, subsection, key);
+ String val = cfg.getString(UserConfigSections.MY, subsection, key);
return !Strings.isNullOrEmpty(val) ? val : defaultValue;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
index 084cfe8..2e03913 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
@@ -20,8 +20,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.InternalUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -122,7 +120,7 @@
return group;
}
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
@@ -132,7 +130,7 @@
* for visibility of all groups that are not an internal group to
* server administrators.
*/
- return user instanceof InternalUser
+ return user.isInternalUser()
|| groupBackend.isVisibleToAll(group.getGroupUUID())
|| user.getEffectiveGroups().contains(group.getGroupUUID())
|| user.getCapabilities().canAdministrateServer()
@@ -145,8 +143,8 @@
isOwner = false;
} else if (isOwner == null) {
AccountGroup.UUID ownerUUID = accountGroup.getOwnerGroupUUID();
- isOwner = getCurrentUser().getEffectiveGroups().contains(ownerUUID)
- || getCurrentUser().getCapabilities().canAdministrateServer();
+ isOwner = getUser().getEffectiveGroups().contains(ownerUUID)
+ || getUser().getCapabilities().canAdministrateServer();
}
return isOwner;
}
@@ -160,8 +158,7 @@
}
public boolean canSeeMember(Account.Id id) {
- if (user.isIdentifiedUser()
- && ((IdentifiedUser) user).getAccountId().equals(id)) {
+ if (user.isIdentifiedUser() && user.getAccountId().equals(id)) {
return true;
}
return canSeeMembers();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 7cf1e37..54d4cc0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -70,6 +70,8 @@
put(ACCOUNT_KIND, "preferences").to(SetPreferences.class);
get(ACCOUNT_KIND, "preferences.diff").to(GetDiffPreferences.class);
put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
+ get(ACCOUNT_KIND, "preferences.edit").to(GetEditPreferences.class);
+ put(ACCOUNT_KIND, "preferences.edit").to(SetEditPreferences.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
child(ACCOUNT_KIND, "starred.changes").to(StarredChanges.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
new file mode 100644
index 0000000..2df1a77
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.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.account;
+
+import static com.google.gerrit.server.config.ConfigUtil.storeSection;
+
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+
+import java.io.IOException;
+
+@Singleton
+public class SetEditPreferences implements
+ RestModifyView<AccountResource, EditPreferencesInfo> {
+
+ private final Provider<CurrentUser> self;
+ private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
+ private final AllUsersName allUsersName;
+
+ @Inject
+ SetEditPreferences(Provider<CurrentUser> self,
+ Provider<MetaDataUpdate.User> metaDataUpdateFactory,
+ AllUsersName allUsersName) {
+ this.self = self;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.allUsersName = allUsersName;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource rsrc, EditPreferencesInfo in)
+ throws AuthException, BadRequestException, RepositoryNotFoundException,
+ IOException, ConfigInvalidException {
+ if (self.get() != rsrc.getUser()
+ && !self.get().getCapabilities().canModifyAccount()) {
+ throw new AuthException("restricted to members of Modify Accounts");
+ }
+
+ if (in == null) {
+ throw new BadRequestException("input must be provided");
+ }
+
+ Account.Id accountId = rsrc.getUser().getAccountId();
+ MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName);
+
+ VersionedAccountPreferences prefs;
+ try {
+ prefs = VersionedAccountPreferences.forUser(accountId);
+ prefs.load(md);
+ storeSection(prefs.getConfig(), UserConfigSections.EDIT, null, in,
+ EditPreferencesInfo.defaults());
+ prefs.commit(md);
+ } finally {
+ md.close();
+ }
+
+ return Response.none();
+ }
+}
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 c7725c5..d355dae 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
@@ -19,7 +19,6 @@
import static com.google.gerrit.server.account.GetPreferences.KEY_TARGET;
import static com.google.gerrit.server.account.GetPreferences.KEY_TOKEN;
import static com.google.gerrit.server.account.GetPreferences.KEY_URL;
-import static com.google.gerrit.server.account.GetPreferences.MY;
import static com.google.gerrit.server.account.GetPreferences.URL_ALIAS;
import com.google.common.base.Strings;
@@ -42,6 +41,7 @@
import com.google.gerrit.server.account.SetPreferences.Input;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.UserConfigSections;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -192,7 +192,7 @@
List<TopMenu.MenuItem> my) {
Config cfg = prefs.getConfig();
if (my != null) {
- unsetSection(cfg, MY);
+ unsetSection(cfg, UserConfigSections.MY);
for (TopMenu.MenuItem item : my) {
set(cfg, item.name, KEY_URL, item.url);
set(cfg, item.name, KEY_TARGET, item.target);
@@ -203,9 +203,9 @@
private static void set(Config cfg, String section, String key, String val) {
if (Strings.isNullOrEmpty(val)) {
- cfg.unset(MY, section, key);
+ cfg.unset(UserConfigSections.MY, section, key);
} else {
- cfg.setString(MY, section, key, val);
+ cfg.setString(UserConfigSections.MY, section, key, val);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
index 5e65acd..426c6f6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
@@ -24,7 +24,6 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.FileMode;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
index 3f578ae..7be8299 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.SuggestAccounts;
@@ -66,7 +65,7 @@
if (!self.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
- return api.create(new AccountResource((IdentifiedUser)self.get()));
+ return api.create(new AccountResource(self.get().asIdentifiedUser()));
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/GpgApiAdapter.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/GpgApiAdapter.java
index 9faa418..7d65ce9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/GpgApiAdapter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/GpgApiAdapter.java
@@ -16,9 +16,11 @@
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
import com.google.gerrit.extensions.common.GpgKeyInfo;
+import com.google.gerrit.extensions.common.PushCertificateInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.GpgException;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import java.util.List;
@@ -27,8 +29,13 @@
public interface GpgApiAdapter {
Map<String, GpgKeyInfo> listGpgKeys(AccountResource account)
throws RestApiException, GpgException;
+
Map<String, GpgKeyInfo> putGpgKeys(AccountResource account, List<String> add,
List<String> delete) throws RestApiException, GpgException;
+
GpgKeyApi gpgKey(AccountResource account, IdString idStr)
throws RestApiException, GpgException;
+
+ PushCertificateInfo checkPushCertificate(String certStr,
+ IdentifiedUser expectedUser) throws GpgException;
}
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 868094e..b55642c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.api.changes;
-import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ChangeApi;
@@ -33,7 +32,6 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeEdits;
import com.google.gerrit.server.change.ChangeJson;
@@ -51,7 +49,7 @@
import com.google.gerrit.server.change.Revisions;
import com.google.gerrit.server.change.SubmittedTogether;
import com.google.gerrit.server.change.SuggestReviewers;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -166,7 +164,7 @@
public void abandon(AbandonInput in) throws RestApiException {
try {
abandon.apply(change, in);
- } catch (OrmException | IOException e) {
+ } catch (OrmException | UpdateException e) {
throw new RestApiException("Cannot abandon change", e);
}
}
@@ -180,7 +178,7 @@
public void restore(RestoreInput in) throws RestApiException {
try {
restore.apply(change, in);
- } catch (OrmException | IOException e) {
+ } catch (OrmException | UpdateException e) {
throw new RestApiException("Cannot restore change", e);
}
}
@@ -194,7 +192,7 @@
public ChangeApi revert(RevertInput in) throws RestApiException {
try {
return changeApi.id(revert.apply(change, in)._number);
- } catch (OrmException | EmailException | IOException e) {
+ } catch (OrmException | IOException | UpdateException e) {
throw new RestApiException("Cannot revert change", e);
}
}
@@ -219,7 +217,7 @@
in.topic = topic;
try {
putTopic.apply(change, in);
- } catch (OrmException | IOException e) {
+ } catch (UpdateException e) {
throw new RestApiException("Cannot set topic", e);
}
}
@@ -235,7 +233,7 @@
public void addReviewer(AddReviewerInput in) throws RestApiException {
try {
postReviewers.apply(change, in);
- } catch (OrmException | EmailException | IOException e) {
+ } catch (OrmException | IOException e) {
throw new RestApiException("Cannot add change reviewer", e);
}
}
@@ -274,7 +272,7 @@
try {
CurrentUser u = user.get();
if (u.isIdentifiedUser()) {
- ((IdentifiedUser) u).clearStarredChanges();
+ u.asIdentifiedUser().clearStarredChanges();
}
return changeJson.create(s).format(change);
} catch (OrmException e) {
@@ -292,7 +290,7 @@
try {
Response<EditInfo> edit = editDetail.apply(change);
return edit.isNone() ? null : edit.value();
- } catch (IOException | OrmException | InvalidChangeOperationException e) {
+ } catch (IOException | OrmException e) {
throw new RestApiException("Cannot retrieve change edit", e);
}
}
@@ -306,7 +304,7 @@
public void setHashtags(HashtagsInput input) throws RestApiException {
try {
postHashtags.apply(change, input);
- } catch (IOException | OrmException e) {
+ } catch (RestApiException | UpdateException e) {
throw new RestApiException("Cannot post hashtags", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index f7705a7..acda1ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -29,9 +29,9 @@
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.CreateChange;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.query.change.QueryChanges;
import com.google.gwtorm.server.OrmException;
@@ -95,7 +95,8 @@
TopLevelResource.INSTANCE, in).value();
return api.create(changes.parse(TopLevelResource.INSTANCE,
IdString.fromUrl(out.changeId)));
- } catch (OrmException | IOException | InvalidChangeOperationException e) {
+ } catch (OrmException | IOException | InvalidChangeOperationException
+ | UpdateException e) {
throw new RestApiException("Cannot create change", e);
}
}
@@ -129,7 +130,7 @@
try {
CurrentUser u = user.get();
if (u.isIdentifiedUser()) {
- ((IdentifiedUser) u).clearStarredChanges();
+ u.asIdentifiedUser().clearStarredChanges();
}
List<?> result = qc.apply(TopLevelResource.INSTANCE);
if (result.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 0569e5f..0926142 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -50,10 +50,11 @@
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.PublishDraftPatchSet;
import com.google.gerrit.server.change.Rebase;
-import com.google.gerrit.server.change.RebaseChange;
+import com.google.gerrit.server.change.RebaseUtil;
import com.google.gerrit.server.change.Reviewed;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -73,7 +74,7 @@
private final CherryPick cherryPick;
private final DeleteDraftPatchSet deleteDraft;
private final Rebase rebase;
- private final RebaseChange rebaseChange;
+ private final RebaseUtil rebaseUtil;
private final Submit submit;
private final PublishDraftPatchSet publish;
private final Reviewed.PutReviewed putReviewed;
@@ -99,7 +100,7 @@
CherryPick cherryPick,
DeleteDraftPatchSet deleteDraft,
Rebase rebase,
- RebaseChange rebaseChange,
+ RebaseUtil rebaseUtil,
Submit submit,
PublishDraftPatchSet publish,
Reviewed.PutReviewed putReviewed,
@@ -123,7 +124,7 @@
this.cherryPick = cherryPick;
this.deleteDraft = deleteDraft;
this.rebase = rebase;
- this.rebaseChange = rebaseChange;
+ this.rebaseUtil = rebaseUtil;
this.review = review;
this.submit = submit;
this.publish = publish;
@@ -149,7 +150,7 @@
public void review(ReviewInput in) throws RestApiException {
try {
review.get().apply(revision, in);
- } catch (OrmException | IOException e) {
+ } catch (OrmException | UpdateException e) {
throw new RestApiException("Cannot post review", e);
}
}
@@ -173,7 +174,7 @@
public void publish() throws RestApiException {
try {
publish.apply(revision, new PublishDraftPatchSet.Input());
- } catch (OrmException | IOException e) {
+ } catch (UpdateException e) {
throw new RestApiException("Cannot publish draft patch set", e);
}
}
@@ -197,21 +198,21 @@
public ChangeApi rebase(RebaseInput in) throws RestApiException {
try {
return changes.id(rebase.apply(revision, in)._number);
- } catch (OrmException | EmailException | IOException e) {
+ } catch (OrmException | EmailException | UpdateException | IOException e) {
throw new RestApiException("Cannot rebase ps", e);
}
}
@Override
public boolean canRebase() {
- return rebaseChange.canRebase(revision);
+ return rebaseUtil.canRebase(revision);
}
@Override
public ChangeApi cherryPick(CherryPickInput in) throws RestApiException {
try {
return changes.id(cherryPick.apply(revision, in)._number);
- } catch (OrmException | EmailException | IOException e) {
+ } catch (OrmException | IOException | UpdateException e) {
throw new RestApiException("Cannot cherry pick", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
index 97ba5d3..3d2c960 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
@@ -138,6 +138,7 @@
list.setLimit(req.getLimit());
list.setStart(req.getStart());
list.setMatchSubstring(req.getSubstring());
+ list.setSuggest(req.getSuggest());
try {
return list.apply(tlr);
} catch (OrmException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
index 3113d07..975e6c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
@@ -23,6 +23,7 @@
bind(Projects.class).to(ProjectsImpl.class);
factory(BranchApiImpl.Factory.class);
+ factory(TagApiImpl.Factory.class);
factory(ProjectApiImpl.Factory.class);
factory(ChildProjectApiImpl.Factory.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 84b219b5..dbd246c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -22,6 +22,8 @@
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.api.projects.PutDescriptionInput;
+import com.google.gerrit.extensions.api.projects.TagApi;
+import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
@@ -35,6 +37,7 @@
import com.google.gerrit.server.project.GetDescription;
import com.google.gerrit.server.project.ListBranches;
import com.google.gerrit.server.project.ListChildProjects;
+import com.google.gerrit.server.project.ListTags;
import com.google.gerrit.server.project.ProjectJson;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectsCollection;
@@ -66,7 +69,9 @@
private final ProjectJson projectJson;
private final String name;
private final BranchApiImpl.Factory branchApi;
+ private final TagApiImpl.Factory tagApi;
private final Provider<ListBranches> listBranchesProvider;
+ private final Provider<ListTags> listTagsProvider;
@AssistedInject
ProjectApiImpl(Provider<CurrentUser> user,
@@ -79,11 +84,13 @@
ChildProjectsCollection children,
ProjectJson projectJson,
BranchApiImpl.Factory branchApiFactory,
+ TagApiImpl.Factory tagApiFactory,
Provider<ListBranches> listBranchesProvider,
+ Provider<ListTags> listTagsProvider,
@Assisted ProjectResource project) {
this(user, createProjectFactory, projectApi, projects, getDescription,
putDescription, childApi, children, projectJson, branchApiFactory,
- listBranchesProvider, project, null);
+ tagApiFactory, listBranchesProvider, listTagsProvider, project, null);
}
@AssistedInject
@@ -97,11 +104,13 @@
ChildProjectsCollection children,
ProjectJson projectJson,
BranchApiImpl.Factory branchApiFactory,
+ TagApiImpl.Factory tagApiFactory,
Provider<ListBranches> listBranchesProvider,
+ Provider<ListTags> listTagsProvider,
@Assisted String name) {
this(user, createProjectFactory, projectApi, projects, getDescription,
putDescription, childApi, children, projectJson, branchApiFactory,
- listBranchesProvider, null, name);
+ tagApiFactory, listBranchesProvider, listTagsProvider, null, name);
}
private ProjectApiImpl(Provider<CurrentUser> user,
@@ -114,7 +123,9 @@
ChildProjectsCollection children,
ProjectJson projectJson,
BranchApiImpl.Factory branchApiFactory,
+ TagApiImpl.Factory tagApiFactory,
Provider<ListBranches> listBranchesProvider,
+ Provider<ListTags> listTagsProvider,
ProjectResource project,
String name) {
this.user = user;
@@ -129,7 +140,9 @@
this.project = project;
this.name = name;
this.branchApi = branchApiFactory;
+ this.tagApi = tagApiFactory;
this.listBranchesProvider = listBranchesProvider;
+ this.listTagsProvider = listTagsProvider;
}
@Override
@@ -179,8 +192,8 @@
}
@Override
- public ListBranchesRequest branches() {
- return new ListBranchesRequest() {
+ public ListRefsRequest<BranchInfo> branches() {
+ return new ListRefsRequest<BranchInfo>() {
@Override
public List<BranchInfo> get() throws RestApiException {
return listBranches(this);
@@ -188,7 +201,7 @@
};
}
- private List<BranchInfo> listBranches(ListBranchesRequest request)
+ private List<BranchInfo> listBranches(ListRefsRequest<BranchInfo> request)
throws RestApiException {
ListBranches list = listBranchesProvider.get();
list.setLimit(request.getLimit());
@@ -203,6 +216,30 @@
}
@Override
+ public ListRefsRequest<TagInfo> tags() {
+ return new ListRefsRequest<TagInfo>() {
+ @Override
+ public List<TagInfo> get() throws RestApiException {
+ return listTags(this);
+ }
+ };
+ }
+
+ private List<TagInfo> listTags(ListRefsRequest<TagInfo> request)
+ throws RestApiException {
+ ListTags list = listTagsProvider.get();
+ list.setLimit(request.getLimit());
+ list.setStart(request.getStart());
+ list.setMatchSubstring(request.getSubstring());
+ list.setMatchRegex(request.getRegex());
+ try {
+ return list.apply(checkExists());
+ } catch (IOException e) {
+ throw new RestApiException("Cannot list tags", e);
+ }
+ }
+
+ @Override
public List<ProjectInfo> children() throws RestApiException {
return children(false);
}
@@ -229,6 +266,11 @@
return branchApi.create(checkExists(), ref);
}
+ @Override
+ public TagApi tag(String ref) throws ResourceNotFoundException {
+ return tagApi.create(checkExists(), ref);
+ }
+
private ProjectResource checkExists() throws ResourceNotFoundException {
if (project == null) {
throw new ResourceNotFoundException(name);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java
new file mode 100644
index 0000000..086447d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2015 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.api.projects;
+
+import com.google.gerrit.extensions.api.projects.TagApi;
+import com.google.gerrit.extensions.api.projects.TagInfo;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.project.ListTags;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+
+public class TagApiImpl implements TagApi {
+ interface Factory {
+ TagApiImpl create(ProjectResource project, String ref);
+ }
+
+ private final ListTags listTags;
+ private final String ref;
+ private final ProjectResource project;
+
+ @Inject
+ TagApiImpl(ListTags listTags,
+ @Assisted ProjectResource project,
+ @Assisted String ref) {
+ this.listTags = listTags;
+ this.project = project;
+ this.ref = ref;
+ }
+
+ @Override
+ public TagInfo get() throws RestApiException {
+ try {
+ return listTags.get(project, IdString.fromDecoded(ref));
+ } catch (IOException e) {
+ throw new RestApiException(e.getMessage());
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index 0c09155..bd4a3b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -126,7 +126,7 @@
String groupDn = uuid.get().substring(LDAP_UUID.length());
CurrentUser user = userProvider.get();
if (!(user.isIdentifiedUser())
- || !membershipsOf((IdentifiedUser) user).contains(uuid)) {
+ || !membershipsOf(user.asIdentifiedUser()).contains(uuid)) {
try {
if (!existsCache.get(groupDn)) {
return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 27779f2..9d7f7b7 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
@@ -163,6 +163,7 @@
return null;
} else {
+ checkBackendCompliance(n, v[0], Strings.isNullOrEmpty(d));
return v[0];
}
}
@@ -186,6 +187,16 @@
}
}
+ private static void checkBackendCompliance(String configOption,
+ String suppliedValue, boolean disabledByBackend) {
+ if (disabledByBackend && !Strings.isNullOrEmpty(suppliedValue)) {
+ String msg = String.format("LDAP backend doesn't support: ldap.%s",
+ configOption);
+ log.error(msg);
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
@Override
public boolean allowsEdit(final Account.FieldName field) {
return !readOnlyAccountFields.contains(field);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index 6723db8..f2d40c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -16,25 +16,29 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
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.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.ReplyToChangeSender;
-import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -43,7 +47,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
+import java.util.Collections;
@Singleton
public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
@@ -54,100 +58,118 @@
private final AbandonedSender.Factory abandonedSenderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson.Factory json;
- private final ChangeIndexer indexer;
- private final ChangeUpdate.Factory updateFactory;
private final ChangeMessagesUtil cmUtil;
+ private final BatchUpdate.Factory batchUpdateFactory;
@Inject
Abandon(ChangeHooks hooks,
AbandonedSender.Factory abandonedSenderFactory,
Provider<ReviewDb> dbProvider,
ChangeJson.Factory json,
- ChangeIndexer indexer,
- ChangeUpdate.Factory updateFactory,
- ChangeMessagesUtil cmUtil) {
+ ChangeMessagesUtil cmUtil,
+ BatchUpdate.Factory batchUpdateFactory) {
this.hooks = hooks;
this.abandonedSenderFactory = abandonedSenderFactory;
this.dbProvider = dbProvider;
this.json = json;
- this.indexer = indexer;
- this.updateFactory = updateFactory;
this.cmUtil = cmUtil;
+ this.batchUpdateFactory = batchUpdateFactory;
}
@Override
- public ChangeInfo apply(ChangeResource req, AbandonInput input)
- throws AuthException, ResourceConflictException, OrmException,
- IOException {
+ public ChangeInfo apply(ChangeResource req,
+ final AbandonInput input)
+ throws RestApiException, UpdateException, OrmException {
ChangeControl control = req.getControl();
- IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
- Change change = req.getChange();
+ IdentifiedUser caller = control.getUser().asIdentifiedUser();
if (!control.canAbandon()) {
throw new AuthException("abandon not permitted");
- } else if (!change.getStatus().isOpen()) {
- throw new ResourceConflictException("change is " + status(change));
- } else if (change.getStatus() == Change.Status.DRAFT) {
- throw new ResourceConflictException("draft changes cannot be abandoned");
}
- change = abandon(control, input.message, caller.getAccount());
+ Change change = abandon(control, input.message, caller.getAccount());
return json.create(ChangeJson.NO_OPTIONS).format(change);
}
public Change abandon(ChangeControl control,
- String msgTxt, Account acc) throws ResourceConflictException,
- OrmException, IOException {
- Change change;
- ChangeMessage message;
- ChangeUpdate update;
- Change.Id changeId = control.getChange().getId();
- ReviewDb db = dbProvider.get();
- db.changes().beginTransaction(changeId);
- try {
- change = db.changes().atomicUpdate(
- changeId,
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isOpen()) {
- change.setStatus(Change.Status.ABANDONED);
- ChangeUtil.updated(change);
- return change;
- }
- return null;
- }
- });
- if (change == null) {
- throw new ResourceConflictException("change is "
- + status(db.changes().get(changeId)));
+ final String msgTxt, final Account account)
+ throws RestApiException, UpdateException {
+ Op op = new Op(msgTxt, account);
+ Change c = control.getChange();
+ try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
+ c.getProject(), control.getUser(), TimeUtil.nowTs())) {
+ u.addOp(c.getId(), op).execute();
+ }
+ return op.change;
+ }
+
+ private class Op extends BatchUpdate.Op {
+ private final Account account;
+ private final String msgTxt;
+
+ private Change change;
+ private PatchSet patchSet;
+ private ChangeMessage message;
+
+ private Op(String msgTxt, Account account) {
+ this.account = account;
+ this.msgTxt = msgTxt;
+ }
+
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException,
+ ResourceConflictException {
+ change = ctx.getChange();
+ if (change == null || !change.getStatus().isOpen()) {
+ throw new ResourceConflictException("change is " + status(change));
+ } else if (change.getStatus() == Change.Status.DRAFT) {
+ throw new ResourceConflictException(
+ "draft changes cannot be abandoned");
+ }
+ patchSet = ctx.getDb().patchSets().get(change.currentPatchSetId());
+ change.setStatus(Change.Status.ABANDONED);
+ change.setLastUpdatedOn(ctx.getWhen());
+ ctx.getDb().changes().update(Collections.singleton(change));
+
+ message = newMessage(ctx.getDb());
+ cmUtil.addChangeMessage(ctx.getDb(), ctx.getChangeUpdate(), message);
+ }
+
+ private ChangeMessage newMessage(ReviewDb db) throws OrmException {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Abandoned");
+ if (!Strings.nullToEmpty(msgTxt).trim().isEmpty()) {
+ msg.append("\n\n");
+ msg.append(msgTxt.trim());
}
- //TODO(yyonas): atomic update was not propagated
- update = updateFactory.create(control, change.getLastUpdatedOn());
- message = newMessage(msgTxt, acc != null ? acc.getId() : null, change);
- cmUtil.addChangeMessage(db, update, message);
- db.commit();
- } finally {
- db.rollback();
+ ChangeMessage message = new ChangeMessage(
+ new ChangeMessage.Key(
+ change.getId(),
+ ChangeUtil.messageUUID(db)),
+ account != null ? account.getId() : null,
+ change.getLastUpdatedOn(),
+ change.currentPatchSetId());
+ message.setMessage(msg.toString());
+ return message;
}
- update.commit();
- indexer.index(db, change);
- try {
- ReplyToChangeSender cm = abandonedSenderFactory.create(change.getId());
- if (acc != null) {
- cm.setFrom(acc.getId());
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ try {
+ ReplyToChangeSender cm = abandonedSenderFactory.create(change.getId());
+ if (account != null) {
+ cm.setFrom(account.getId());
+ }
+ cm.setChangeMessage(message);
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot email update for change " + change.getId(), e);
}
- cm.setChangeMessage(message);
- cm.send();
- } catch (Exception e) {
- log.error("Cannot email update for change " + change.getChangeId(), e);
+ hooks.doChangeAbandonedHook(change,
+ account,
+ patchSet,
+ Strings.emptyToNull(msgTxt),
+ ctx.getDb());
}
- hooks.doChangeAbandonedHook(change,
- acc,
- db.patchSets().get(change.currentPatchSetId()),
- Strings.emptyToNull(msgTxt),
- db);
- return change;
}
@Override
@@ -160,26 +182,6 @@
&& resource.getControl().canAbandon());
}
- private ChangeMessage newMessage(String msgTxt, Account.Id accId,
- Change change) throws OrmException {
- StringBuilder msg = new StringBuilder();
- msg.append("Abandoned");
- if (!Strings.nullToEmpty(msgTxt).trim().isEmpty()) {
- msg.append("\n\n");
- msg.append(msgTxt.trim());
- }
-
- ChangeMessage message = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(dbProvider.get())),
- accId,
- change.getLastUpdatedOn(),
- change.currentPatchSetId());
- message.setMessage(msg.toString());
- return message;
- }
-
private static String status(Change change) {
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index fd20868..d8574f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -62,11 +62,11 @@
private Map<String, ActionInfo> toActionMap(ChangeControl ctl) {
Map<String, ActionInfo> out = new LinkedHashMap<>();
- if (!ctl.getCurrentUser().isIdentifiedUser()) {
+ if (!ctl.getUser().isIdentifiedUser()) {
return out;
}
- Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser());
+ Provider<CurrentUser> userProvider = Providers.of(ctl.getUser());
for (UiAction.Description d : UiActions.from(
changeViews,
new ChangeResource(ctl),
@@ -89,9 +89,9 @@
private Map<String, ActionInfo> toActionMap(RevisionResource rsrc) {
Map<String, ActionInfo> out = new LinkedHashMap<>();
- if (rsrc.getControl().getCurrentUser().isIdentifiedUser()) {
+ if (rsrc.getControl().getUser().isIdentifiedUser()) {
Provider<CurrentUser> userProvider = Providers.of(
- rsrc.getControl().getCurrentUser());
+ rsrc.getControl().getUser());
for (UiAction.Description d : UiActions.from(
revisions, rsrc, userProvider)) {
out.put(d.getId(), new ActionInfo(d));
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
index 73f9cab..cb3729b 100644
--- 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
@@ -272,8 +272,7 @@
@Override
public Response<EditInfo> apply(ChangeResource rsrc) throws AuthException,
- IOException, InvalidChangeOperationException,
- ResourceNotFoundException, OrmException {
+ IOException, ResourceNotFoundException, OrmException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (!edit.isPresent()) {
return Response.none();
@@ -382,7 +381,7 @@
@Override
public Response<?> apply(ChangeEditResource rsrc, Input input)
- throws AuthException, ResourceConflictException, IOException {
+ throws AuthException, ResourceConflictException {
String path = rsrc.getPath();
if (Strings.isNullOrEmpty(path) || path.charAt(0) == '/') {
throw new ResourceConflictException("Invalid path: " + path);
@@ -442,7 +441,7 @@
@Override
public Response<?> apply(ChangeEditResource rsrc)
- throws ResourceNotFoundException, IOException {
+ throws IOException {
try {
return Response.ok(fileContentUtil.getContent(
rsrc.getControl().getProjectControl().getProjectState(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index a9153d6..344eda1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.change;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
-import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.extensions.api.changes.HashtagsInput;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -31,25 +31,33 @@
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.BanCommit;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.GroupCollector;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,93 +66,104 @@
import java.util.Map;
import java.util.Set;
-public class ChangeInserter {
+public class ChangeInserter extends BatchUpdate.InsertChangeOp {
public static interface Factory {
- ChangeInserter create(ProjectControl ctl, Change c, RevCommit rc);
+ ChangeInserter create(RefControl ctl, Change c, RevCommit rc);
}
private static final Logger log =
LoggerFactory.getLogger(ChangeInserter.class);
- private final Provider<ReviewDb> dbProvider;
- private final ChangeUpdate.Factory updateFactory;
- private final GitReferenceUpdated gitRefUpdated;
+ private final PatchSetInfoFactory patchSetInfoFactory;
private final ChangeHooks hooks;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
- private final ChangeIndexer indexer;
private final CreateChangeSender.Factory createChangeSenderFactory;
- private final HashtagsUtil hashtagsUtil;
- private final AccountCache accountCache;
private final WorkQueue workQueue;
+ private final CommitValidators.Factory commitValidatorsFactory;
- private final ProjectControl projectControl;
+ private final RefControl refControl;
+ private final IdentifiedUser user;
private final Change change;
private final PatchSet patchSet;
private final RevCommit commit;
- private final PatchSetInfo patchSetInfo;
- private ChangeMessage changeMessage;
+ // Fields exposed as setters.
+ private String message;
+ private CommitValidators.Policy validatePolicy =
+ CommitValidators.Policy.GERRIT;
private Set<Account.Id> reviewers;
private Set<Account.Id> extraCC;
private Map<String, Short> approvals;
- private Set<String> hashtags;
private RequestScopePropagator requestScopePropagator;
private boolean runHooks;
private boolean sendMail;
+ private boolean updateRef;
+
+ // Fields set during the insertion process.
+ private ChangeMessage changeMessage;
+ private PatchSetInfo patchSetInfo;
@Inject
- ChangeInserter(Provider<ReviewDb> dbProvider,
- ChangeUpdate.Factory updateFactory,
- PatchSetInfoFactory patchSetInfoFactory,
- GitReferenceUpdated gitRefUpdated,
+ ChangeInserter(PatchSetInfoFactory patchSetInfoFactory,
ChangeHooks hooks,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
- ChangeIndexer indexer,
CreateChangeSender.Factory createChangeSenderFactory,
- HashtagsUtil hashtagsUtil,
- AccountCache accountCache,
WorkQueue workQueue,
- @Assisted ProjectControl projectControl,
+ CommitValidators.Factory commitValidatorsFactory,
+ @Assisted RefControl refControl,
@Assisted Change change,
@Assisted RevCommit commit) {
- this.dbProvider = dbProvider;
- this.updateFactory = updateFactory;
- this.gitRefUpdated = gitRefUpdated;
+ String projectName = refControl.getProjectControl().getProject().getName();
+ String refName = refControl.getRefName();
+ checkArgument(projectName.equals(change.getProject().get())
+ && refName.equals(change.getDest().get()),
+ "RefControl for %s,%s does not match change destination %s",
+ projectName, refName, change.getDest());
+
+ this.patchSetInfoFactory = patchSetInfoFactory;
this.hooks = hooks;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
- this.indexer = indexer;
this.createChangeSenderFactory = createChangeSenderFactory;
- this.hashtagsUtil = hashtagsUtil;
- this.accountCache = accountCache;
this.workQueue = workQueue;
- this.projectControl = projectControl;
+ this.commitValidatorsFactory = commitValidatorsFactory;
+
+ this.refControl = refControl;
this.change = change;
this.commit = commit;
this.reviewers = Collections.emptySet();
this.extraCC = Collections.emptySet();
this.approvals = Collections.emptyMap();
- this.hashtags = Collections.emptySet();
this.runHooks = true;
this.sendMail = true;
+ this.updateRef = true;
+ user = refControl.getUser().asIdentifiedUser();
patchSet =
new PatchSet(new PatchSet.Id(change.getId(), INITIAL_PATCH_SET_ID));
patchSet.setCreatedOn(change.getCreatedOn());
patchSet.setUploader(change.getOwner());
patchSet.setRevision(new RevId(commit.name()));
- patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
- change.setCurrentPatchSet(patchSetInfo);
}
+ @Override
public Change getChange() {
return change;
}
- public ChangeInserter setMessage(ChangeMessage changeMessage) {
- this.changeMessage = changeMessage;
+ public IdentifiedUser getUser() {
+ return user;
+ }
+
+ public ChangeInserter setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public ChangeInserter setValidatePolicy(CommitValidators.Policy validate) {
+ this.validatePolicy = checkNotNull(validate);
return this;
}
@@ -169,11 +188,6 @@
return this;
}
- public ChangeInserter setHashtags(Set<String> hashtags) {
- this.hashtags = hashtags;
- return this;
- }
-
public ChangeInserter setRunHooks(boolean runHooks) {
this.runHooks = runHooks;
return this;
@@ -198,55 +212,62 @@
return this;
}
- public PatchSetInfo getPatchSetInfo() {
- return patchSetInfo;
+ public ChangeInserter setUpdateRef(boolean updateRef) {
+ this.updateRef = updateRef;
+ return this;
}
- public Change insert() throws OrmException, IOException {
- ReviewDb db = dbProvider.get();
- ChangeControl ctl = projectControl.controlFor(change);
- ChangeUpdate update = updateFactory.create(
- ctl,
- change.getCreatedOn());
- db.changes().beginTransaction(change.getId());
- try {
- ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
- if (patchSet.getGroups() == null) {
- patchSet.setGroups(GroupCollector.getDefaultGroups(patchSet));
- }
- db.patchSets().insert(Collections.singleton(patchSet));
- db.changes().insert(Collections.singleton(change));
- LabelTypes labelTypes = projectControl.getLabelTypes();
- approvalsUtil.addReviewers(db, update, labelTypes, change,
- patchSet, patchSetInfo, reviewers, Collections.<Account.Id> emptySet());
- approvalsUtil.addApprovals(db, update, labelTypes, patchSet, patchSetInfo,
- ctl, approvals);
- if (messageIsForChange()) {
- cmUtil.addChangeMessage(db, update, changeMessage);
- }
- db.commit();
- } finally {
- db.rollback();
+ public ChangeMessage getChangeMessage() {
+ if (message == null) {
+ return null;
}
+ checkState(changeMessage != null,
+ "getChangeMessage() only valid after inserting change");
+ return changeMessage;
+ }
- update.commit();
-
- if (hashtags != null && hashtags.size() > 0) {
- try {
- HashtagsInput input = new HashtagsInput();
- input.add = hashtags;
- hashtagsUtil.setHashtags(ctl, input, false, false);
- } catch (ValidationException | AuthException e) {
- log.error("Cannot add hashtags to change " + change.getId(), e);
- }
+ @Override
+ public void updateRepo(RepoContext ctx)
+ throws InvalidChangeOperationException, IOException {
+ validate(ctx);
+ patchSetInfo = patchSetInfoFactory.get(
+ ctx.getRevWalk(), commit, patchSet.getId());
+ change.setCurrentPatchSet(patchSetInfo);
+ if (!updateRef) {
+ return;
}
+ ctx.addRefUpdate(
+ new ReceiveCommand(ObjectId.zeroId(), commit, patchSet.getRefName()));
+ }
- CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
- if (!messageIsForChange()) {
- commitMessageNotForChange();
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException, IOException {
+ ReviewDb db = ctx.getDb();
+ ChangeControl ctl = ctx.getChangeControl();
+ ChangeUpdate update = ctx.getChangeUpdate();
+ ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
+ if (patchSet.getGroups() == null) {
+ patchSet.setGroups(GroupCollector.getDefaultGroups(patchSet));
}
- f.checkedGet();
+ db.patchSets().insert(Collections.singleton(patchSet));
+ db.changes().insert(Collections.singleton(change));
+ LabelTypes labelTypes = ctl.getProjectControl().getLabelTypes();
+ approvalsUtil.addReviewers(db, update, labelTypes, change,
+ patchSet, patchSetInfo, reviewers, Collections.<Account.Id> emptySet());
+ approvalsUtil.addApprovals(db, update, labelTypes, patchSet,
+ ctx.getChangeControl(), approvals);
+ if (message != null) {
+ changeMessage =
+ new ChangeMessage(new ChangeMessage.Key(change.getId(),
+ ChangeUtil.messageUUID(db)), user.getAccountId(),
+ patchSet.getCreatedOn(), patchSet.getId());
+ changeMessage.setMessage(message);
+ cmUtil.addChangeMessage(db, update, changeMessage);
+ }
+ }
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
if (sendMail) {
Runnable sender = new Runnable() {
@Override
@@ -276,43 +297,50 @@
}
}
- gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
- ObjectId.zeroId(), commit);
-
if (runHooks) {
+ ReviewDb db = ctx.getDb();
hooks.doPatchsetCreatedHook(change, patchSet, db);
- if (hashtags != null && hashtags.size() > 0) {
- hooks.doHashtagsChangedHook(change,
- accountCache.get(change.getOwner()).getAccount(),
- hashtags, null, hashtags, db);
+ if (approvals != null && !approvals.isEmpty()) {
+ hooks.doCommentAddedHook(
+ change, user.getAccount(), patchSet, null, approvals, db);
}
}
-
- return change;
}
- private void commitMessageNotForChange() throws OrmException,
- IOException {
- ReviewDb db = dbProvider.get();
- if (changeMessage != null) {
- Change otherChange =
- db.changes().get(changeMessage.getPatchSetId().getParentKey());
- ChangeUtil.bumpRowVersionNotLastUpdatedOn(
- changeMessage.getKey().getParentKey(), db);
- ChangeControl otherControl = projectControl.controlFor(otherChange);
- ChangeUpdate updateForOtherChange =
- updateFactory.create(otherControl, change.getLastUpdatedOn());
- cmUtil.addChangeMessage(db, updateForOtherChange, changeMessage);
- updateForOtherChange.commit();
+ private void validate(RepoContext ctx)
+ throws IOException, InvalidChangeOperationException {
+ if (validatePolicy == CommitValidators.Policy.NONE) {
+ return;
}
- }
+ CommitValidators cv = commitValidatorsFactory.create(
+ refControl, new NoSshInfo(), ctx.getRepository());
- private boolean messageIsForChange() {
- if (changeMessage == null) {
- return false;
+ String refName = patchSet.getId().toRefName();
+ CommitReceivedEvent event = new CommitReceivedEvent(
+ new ReceiveCommand(
+ ObjectId.zeroId(),
+ commit.getId(),
+ refName),
+ refControl.getProjectControl().getProject(),
+ change.getDest().get(),
+ commit,
+ user);
+
+ try {
+ switch (validatePolicy) {
+ case RECEIVE_COMMITS:
+ NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(
+ ctx.getRepository(), ctx.getRevWalk());
+ cv.validateForReceiveCommits(event, rejectCommits);
+ break;
+ case GERRIT:
+ cv.validateForGerritCommits(event);
+ break;
+ case NONE:
+ break;
+ }
+ } catch (CommitValidationException e) {
+ throw new InvalidChangeOperationException(e.getMessage());
}
- Change.Id id = change.getId();
- Change.Id msgId = changeMessage.getKey().getParentKey();
- return msgId.equals(id);
}
}
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 acdf004..7d126e3 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
@@ -29,6 +29,7 @@
import static com.google.gerrit.extensions.client.ListChangesOption.DOWNLOAD_COMMANDS;
import static com.google.gerrit.extensions.client.ListChangesOption.LABELS;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
+import static com.google.gerrit.extensions.client.ListChangesOption.PUSH_CERTIFICATES;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
import static com.google.gerrit.server.CommonConverters.toGitPerson;
@@ -67,6 +68,7 @@
import com.google.gerrit.extensions.common.FetchInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.ProblemInfo;
+import com.google.gerrit.extensions.common.PushCertificateInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.config.DownloadCommand;
@@ -84,9 +86,11 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.api.accounts.GpgApiAdapter;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.git.MergeUtil;
@@ -149,6 +153,7 @@
private final ChangeMessagesUtil cmUtil;
private final Provider<ConsistencyChecker> checkerProvider;
private final ActionJson actionJson;
+ private final GpgApiAdapter gpgApi;
private AccountLoader accountLoader;
private FixInput fix;
@@ -173,6 +178,7 @@
ChangeMessagesUtil cmUtil,
Provider<ConsistencyChecker> checkerProvider,
ActionJson actionJson,
+ GpgApiAdapter gpgApi,
@Assisted Set<ListChangesOption> options) {
this.db = db;
this.labelNormalizer = ln;
@@ -192,6 +198,7 @@
this.cmUtil = cmUtil;
this.checkerProvider = checkerProvider;
this.actionJson = actionJson;
+ this.gpgApi = gpgApi;
this.options = options.isEmpty()
? EnumSet.noneOf(ListChangesOption.class)
: EnumSet.copyOf(options);
@@ -254,8 +261,8 @@
} else {
return toChangeInfo(cd, limitToPsId);
}
- } catch (PatchListNotAvailableException | OrmException | IOException
- | RuntimeException e) {
+ } catch (PatchListNotAvailableException | GpgException | OrmException
+ | IOException | RuntimeException e) {
if (!has(CHECK)) {
Throwables.propagateIfPossible(e, OrmException.class);
throw new OrmException(e);
@@ -272,13 +279,40 @@
public List<List<ChangeInfo>> formatQueryResults(List<QueryResult> in)
throws OrmException {
accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
- Iterable<ChangeData> all = FluentIterable.from(in)
+ ensureLoaded(FluentIterable.from(in)
.transformAndConcat(new Function<QueryResult, List<ChangeData>>() {
@Override
public List<ChangeData> apply(QueryResult in) {
return in.changes();
}
- });
+ }));
+
+ List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
+ Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
+ for (QueryResult r : in) {
+ List<ChangeInfo> infos = toChangeInfo(out, r.changes());
+ if (!infos.isEmpty() && r.moreChanges()) {
+ infos.get(infos.size() - 1)._moreChanges = true;
+ }
+ res.add(infos);
+ }
+ accountLoader.fill();
+ return res;
+ }
+
+ public List<ChangeInfo> formatChangeDatas(Collection<ChangeData> in)
+ throws OrmException {
+ accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
+ ensureLoaded(in);
+ List<ChangeInfo> out = new ArrayList<>(in.size());
+ for (ChangeData cd : in) {
+ out.add(format(cd));
+ }
+ accountLoader.fill();
+ return out;
+ }
+
+ private void ensureLoaded(Iterable<ChangeData> all) throws OrmException {
ChangeData.ensureChangeLoaded(all);
if (has(ALL_REVISIONS)) {
ChangeData.ensureAllPatchSetsLoaded(all);
@@ -289,18 +323,6 @@
ChangeData.ensureReviewedByLoadedForOpenChanges(all);
}
ChangeData.ensureCurrentApprovalsLoaded(all);
-
- List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
- Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
- for (QueryResult r : in) {
- List<ChangeInfo> infos = toChangeInfo(out, r.changes());
- if (r.moreChanges()) {
- infos.get(infos.size() - 1)._moreChanges = true;
- }
- res.add(infos);
- }
- accountLoader.fill();
- return res;
}
private boolean has(ListChangesOption option) {
@@ -315,8 +337,8 @@
if (i == null) {
try {
i = toChangeInfo(cd, Optional.<PatchSet.Id> absent());
- } catch (PatchListNotAvailableException | OrmException | IOException
- | RuntimeException e) {
+ } catch (PatchListNotAvailableException | GpgException | OrmException
+ | IOException | RuntimeException e) {
if (has(CHECK)) {
i = checkOnly(cd);
} else {
@@ -359,8 +381,8 @@
}
private ChangeInfo toChangeInfo(ChangeData cd,
- Optional<PatchSet.Id> limitToPsId)
- throws PatchListNotAvailableException, OrmException, IOException {
+ Optional<PatchSet.Id> limitToPsId) throws PatchListNotAvailableException,
+ GpgException, OrmException, IOException {
ChangeInfo out = new ChangeInfo();
if (has(CHECK)) {
@@ -402,7 +424,7 @@
? true
: null;
if (in.getStatus().isOpen() && has(REVIEWED) && user.isIdentifiedUser()) {
- Account.Id accountId = ((IdentifiedUser) user).getAccountId();
+ Account.Id accountId = user.getAccountId();
out.reviewed = cd.reviewedBy().contains(accountId) ? true : null;
}
@@ -816,8 +838,8 @@
}
private Map<String, RevisionInfo> revisions(ChangeControl ctl,
- Map<PatchSet.Id, PatchSet> map)
- throws PatchListNotAvailableException, OrmException, IOException {
+ Map<PatchSet.Id, PatchSet> map) throws PatchListNotAvailableException,
+ GpgException, OrmException, IOException {
Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
for (PatchSet in : map.values()) {
if ((has(ALL_REVISIONS)
@@ -858,7 +880,8 @@
}
private RevisionInfo toRevisionInfo(ChangeControl ctl, PatchSet in)
- throws PatchListNotAvailableException, OrmException, IOException {
+ throws PatchListNotAvailableException, GpgException, OrmException,
+ IOException {
Change c = ctl.getChange();
RevisionInfo out = new RevisionInfo();
out.isCurrent = in.getId().equals(c.currentPatchSetId());
@@ -903,6 +926,16 @@
new RevisionResource(new ChangeResource(ctl), in));
}
+ if (has(PUSH_CERTIFICATES)) {
+ if (in.getPushCertificate() != null) {
+ out.pushCertificate = gpgApi.checkPushCertificate(
+ in.getPushCertificate(),
+ userFactory.create(db, in.getUploader()));
+ } else {
+ out.pushCertificate = new PushCertificateInfo();
+ }
+ }
+
return out;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 84658b2..03d189f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -20,9 +20,9 @@
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestResource.HasETag;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectState;
@@ -62,9 +62,13 @@
public void prepareETag(Hasher h, CurrentUser user) {
h.putLong(getChange().getLastUpdatedOn().getTime())
.putInt(getChange().getRowVersion())
- .putInt(user.isIdentifiedUser()
- ? ((IdentifiedUser) user).getAccountId().get()
- : 0);
+ .putInt(user.isIdentifiedUser() ? user.getAccountId().get() : 0);
+
+ if (user.isIdentifiedUser()) {
+ for (AccountGroup.UUID uuid : user.getEffectiveGroups().getKnownGroups()) {
+ h.putBytes(uuid.get().getBytes());
+ }
+ }
byte[] buf = new byte[20];
ObjectId noteId;
@@ -84,7 +88,7 @@
@Override
public String getETag() {
- CurrentUser user = control.getCurrentUser();
+ CurrentUser user = control.getUser();
Hasher h = Hashing.md5().newHasher()
.putBoolean(user.getStarredChanges().contains(getChange().getId()));
prepareETag(h, user);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
index f4b2f4e..d7fe43b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -47,7 +47,7 @@
ChangeControl ctl = rsrc.getControl();
if (!ctl.isOwner()
&& !ctl.getProjectControl().isOwner()
- && !ctl.getCurrentUser().getCapabilities().canMaintainServer()) {
+ && !ctl.getUser().getCapabilities().canMaintainServer()) {
throw new AuthException("Cannot fix change");
}
return Response.withMustRevalidate(newChangeJson().fix(input).format(rsrc));
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 ad5940e..b0f14af 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
@@ -14,19 +14,19 @@
package com.google.gerrit.server.change;
-import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -56,8 +56,7 @@
@Override
public ChangeInfo apply(RevisionResource revision, CherryPickInput input)
- throws AuthException, BadRequestException, ResourceConflictException,
- ResourceNotFoundException, OrmException, IOException, EmailException {
+ throws OrmException, IOException, UpdateException, RestApiException {
final ChangeControl control = revision.getControl();
if (input.message == null || input.message.trim().isEmpty()) {
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 34a7070..1045cff 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
@@ -17,6 +17,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -26,16 +27,17 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
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.UpdateException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
@@ -45,7 +47,6 @@
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -58,11 +59,8 @@
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
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 org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.ChangeIdUtil;
import java.io.IOException;
@@ -76,37 +74,37 @@
private final Provider<InternalChangeQuery> queryProvider;
private final GitRepositoryManager gitManager;
private final TimeZone serverTimeZone;
- private final Provider<CurrentUser> currentUser;
- private final CommitValidators.Factory commitValidatorsFactory;
+ private final Provider<IdentifiedUser> user;
private final ChangeInserter.Factory changeInserterFactory;
private final PatchSetInserter.Factory patchSetInserterFactory;
private final MergeUtil.Factory mergeUtilFactory;
private final ChangeMessagesUtil changeMessagesUtil;
private final ChangeUpdate.Factory updateFactory;
+ private final BatchUpdate.Factory batchUpdateFactory;
@Inject
CherryPickChange(Provider<ReviewDb> db,
Provider<InternalChangeQuery> queryProvider,
@GerritPersonIdent PersonIdent myIdent,
GitRepositoryManager gitManager,
- Provider<CurrentUser> currentUser,
- CommitValidators.Factory commitValidatorsFactory,
+ Provider<IdentifiedUser> user,
ChangeInserter.Factory changeInserterFactory,
PatchSetInserter.Factory patchSetInserterFactory,
MergeUtil.Factory mergeUtilFactory,
ChangeMessagesUtil changeMessagesUtil,
- ChangeUpdate.Factory updateFactory) {
+ ChangeUpdate.Factory updateFactory,
+ BatchUpdate.Factory batchUpdateFactory) {
this.db = db;
this.queryProvider = queryProvider;
this.gitManager = gitManager;
this.serverTimeZone = myIdent.getTimeZone();
- this.currentUser = currentUser;
- this.commitValidatorsFactory = commitValidatorsFactory;
+ this.user = user;
this.changeInserterFactory = changeInserterFactory;
this.patchSetInserterFactory = patchSetInserterFactory;
this.mergeUtilFactory = mergeUtilFactory;
this.changeMessagesUtil = changeMessagesUtil;
this.updateFactory = updateFactory;
+ this.batchUpdateFactory = batchUpdateFactory;
}
public Change.Id cherryPick(Change change, PatchSet patch,
@@ -114,7 +112,8 @@
final RefControl refControl) throws NoSuchChangeException,
OrmException, MissingObjectException,
IncorrectObjectTypeException, IOException,
- InvalidChangeOperationException, MergeException {
+ InvalidChangeOperationException, MergeException, UpdateException,
+ RestApiException {
if (Strings.isNullOrEmpty(ref)) {
throw new InvalidChangeOperationException(
@@ -123,18 +122,18 @@
Project.NameKey project = change.getProject();
String destinationBranch = RefNames.shortName(ref);
- IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get();
+ IdentifiedUser identifiedUser = user.get();
try (Repository git = gitManager.openRepository(project);
- RevWalk revWalk = new RevWalk(git)) {
+ CodeReviewRevWalk revWalk = CodeReviewCommit.newRevWalk(git)) {
Ref destRef = git.getRefDatabase().exactRef(ref);
if (destRef == null) {
throw new InvalidChangeOperationException(String.format(
"Branch %s does not exist.", destinationBranch));
}
- final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
+ CodeReviewCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
- RevCommit commitToCherryPick =
+ CodeReviewCommit commitToCherryPick =
revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
PersonIdent committerIdent =
@@ -148,131 +147,115 @@
String commitMessage =
ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
- RevCommit cherryPickCommit;
+ CodeReviewCommit cherryPickCommit;
try (ObjectInserter oi = git.newObjectInserter()) {
ProjectState projectState = refControl.getProjectControl().getProjectState();
cherryPickCommit =
mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
commitToCherryPick, committerIdent, commitMessage, revWalk);
+
+ Change.Key changeKey;
+ final List<String> idList = cherryPickCommit.getFooterLines(
+ FooterConstants.CHANGE_ID);
+ if (!idList.isEmpty()) {
+ final String idStr = idList.get(idList.size() - 1).trim();
+ changeKey = new Change.Key(idStr);
+ } else {
+ changeKey = new Change.Key("I" + computedChangeId.name());
+ }
+
+ Branch.NameKey newDest =
+ new Branch.NameKey(change.getProject(), destRef.getName());
+ List<ChangeData> destChanges = queryProvider.get()
+ .setLimit(2)
+ .byBranchKey(newDest, changeKey);
+ if (destChanges.size() > 1) {
+ throw new InvalidChangeOperationException("Several changes with key "
+ + changeKey + " reside on the same branch. "
+ + "Cannot create a new patch set.");
+ } else if (destChanges.size() == 1) {
+ // The change key exists on the destination branch. The cherry pick
+ // will be added as a new patch set.
+ return insertPatchSet(git, revWalk, oi, destChanges.get(0).change(),
+ cherryPickCommit, refControl, identifiedUser);
+ } else {
+ // Change key not found on destination branch. We can create a new
+ // change.
+ String newTopic = null;
+ if (!Strings.isNullOrEmpty(change.getTopic())) {
+ newTopic = change.getTopic() + "-" + newDest.getShortName();
+ }
+ Change newChange = createNewChange(git, revWalk, oi, changeKey,
+ project, destRef, cherryPickCommit, refControl, identifiedUser,
+ newTopic, change.getDest());
+
+ addMessageToSourceChange(change, patch.getId(), destinationBranch,
+ cherryPickCommit, identifiedUser, refControl);
+
+ return newChange.getId();
+ }
} catch (MergeIdenticalTreeException | MergeConflictException e) {
throw new MergeException("Cherry pick failed: " + e.getMessage());
}
-
- Change.Key changeKey;
- final List<String> idList = cherryPickCommit.getFooterLines(
- FooterConstants.CHANGE_ID);
- if (!idList.isEmpty()) {
- final String idStr = idList.get(idList.size() - 1).trim();
- changeKey = new Change.Key(idStr);
- } else {
- changeKey = new Change.Key("I" + computedChangeId.name());
- }
-
- Branch.NameKey newDest =
- new Branch.NameKey(change.getProject(), destRef.getName());
- List<ChangeData> destChanges = queryProvider.get()
- .setLimit(2)
- .byBranchKey(newDest, changeKey);
- if (destChanges.size() > 1) {
- throw new InvalidChangeOperationException("Several changes with key "
- + changeKey + " reside on the same branch. "
- + "Cannot create a new patch set.");
- } else if (destChanges.size() == 1) {
- // The change key exists on the destination branch. The cherry pick
- // will be added as a new patch set.
- return insertPatchSet(git, revWalk, destChanges.get(0).change(),
- cherryPickCommit, refControl, identifiedUser);
- } else {
- // Change key not found on destination branch. We can create a new
- // change.
- String newTopic = null;
- if (!Strings.isNullOrEmpty(change.getTopic())) {
- newTopic = change.getTopic() + "-" + newDest.getShortName();
- }
- Change newChange = createNewChange(git, revWalk, changeKey, project,
- destRef, cherryPickCommit, refControl,
- identifiedUser, newTopic);
-
- addMessageToSourceChange(change, patch.getId(), destinationBranch,
- cherryPickCommit, identifiedUser, refControl);
-
- addMessageToDestinationChange(newChange, change.getDest().getShortName(),
- identifiedUser, refControl);
-
- return newChange.getId();
- }
} catch (RepositoryNotFoundException e) {
throw new NoSuchChangeException(change.getId(), e);
}
}
- private Change.Id insertPatchSet(Repository git, RevWalk revWalk, Change change,
- RevCommit cherryPickCommit, RefControl refControl,
- IdentifiedUser identifiedUser)
- throws InvalidChangeOperationException, IOException, OrmException,
- NoSuchChangeException {
- final ChangeControl changeControl =
- refControl.getProjectControl().controlFor(change);
- final PatchSetInserter inserter = patchSetInserterFactory
- .create(git, revWalk, changeControl, cherryPickCommit);
- final PatchSet.Id newPatchSetId = inserter.getPatchSetId();
+ private Change.Id insertPatchSet(Repository git, RevWalk revWalk,
+ ObjectInserter oi, Change change, CodeReviewCommit cherryPickCommit,
+ RefControl refControl, IdentifiedUser identifiedUser)
+ throws IOException, OrmException, UpdateException, RestApiException {
+ PatchSet.Id psId =
+ ChangeUtil.nextPatchSetId(git, change.currentPatchSetId());
+ PatchSetInserter inserter = patchSetInserterFactory
+ .create(refControl, psId, cherryPickCommit);
+ PatchSet.Id newPatchSetId = inserter.getPatchSetId();
PatchSet current = db.get().patchSets().get(change.currentPatchSetId());
- inserter
- .setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
- .setDraft(current.isDraft())
- .setUploader(identifiedUser.getAccountId())
- .setSendMail(false)
- .insert();
+
+ try (BatchUpdate bu = batchUpdateFactory.create(
+ db.get(), change.getDest().getParentKey(), identifiedUser,
+ TimeUtil.nowTs())) {
+ bu.setRepository(git, revWalk, oi);
+ bu.addOp(change.getId(), inserter
+ .setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
+ .setDraft(current.isDraft())
+ .setUploader(identifiedUser.getAccountId())
+ .setSendMail(false));
+ bu.execute();
+ }
return change.getId();
}
private Change createNewChange(Repository git, RevWalk revWalk,
- Change.Key changeKey, Project.NameKey project,
- Ref destRef, RevCommit cherryPickCommit, RefControl refControl,
- IdentifiedUser identifiedUser, String topic)
- throws OrmException, InvalidChangeOperationException, IOException {
+ ObjectInserter oi, Change.Key changeKey, Project.NameKey project,
+ Ref destRef, CodeReviewCommit cherryPickCommit, RefControl refControl,
+ IdentifiedUser identifiedUser, String topic, Branch.NameKey sourceBranch)
+ throws RestApiException, UpdateException, OrmException {
Change change =
new Change(changeKey, new Change.Id(db.get().nextChangeId()),
identifiedUser.getAccountId(), new Branch.NameKey(project,
destRef.getName()), TimeUtil.nowTs());
change.setTopic(topic);
- ChangeInserter ins =
- changeInserterFactory.create(refControl.getProjectControl(), change,
- cherryPickCommit);
- PatchSet newPatchSet = ins.getPatchSet();
+ ChangeInserter ins = changeInserterFactory.create(
+ refControl, change, cherryPickCommit)
+ .setValidatePolicy(CommitValidators.Policy.GERRIT);
- CommitValidators commitValidators =
- commitValidatorsFactory.create(refControl, new NoSshInfo(), git);
- CommitReceivedEvent commitReceivedEvent =
- new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
- cherryPickCommit.getId(), newPatchSet.getRefName()), refControl
- .getProjectControl().getProject(), refControl.getRefName(),
- cherryPickCommit, identifiedUser);
-
- try {
- commitValidators.validateForGerritCommits(commitReceivedEvent);
- } catch (CommitValidationException e) {
- throw new InvalidChangeOperationException(e.getMessage());
+ ins.setMessage(
+ messageForDestinationChange(ins.getPatchSet().getId(), sourceBranch));
+ try (BatchUpdate bu = batchUpdateFactory.create(
+ db.get(), change.getProject(), identifiedUser, TimeUtil.nowTs())) {
+ bu.setRepository(git, revWalk, oi);
+ bu.insertChange(ins);
+ bu.execute();
}
-
- final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(cherryPickCommit);
- ru.disableRefLog();
- if (ru.update(revWalk) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
- change.getDest().getParentKey().get(), ru.getResult()));
- }
-
- ins.insert();
-
- return change;
+ return ins.getChange();
}
private void addMessageToSourceChange(Change change, PatchSet.Id patchSetId,
- String destinationBranch, RevCommit cherryPickCommit,
- IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+ String destinationBranch, CodeReviewCommit cherryPickCommit,
+ IdentifiedUser identifiedUser, RefControl refControl)
+ throws OrmException, IOException {
ChangeMessage changeMessage = new ChangeMessage(
new ChangeMessage.Key(
patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
@@ -288,28 +271,18 @@
changeMessage.setMessage(sb.toString());
ChangeControl ctl = refControl.getProjectControl().controlFor(change);
- ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+ ChangeUpdate update = updateFactory.create(ctl, TimeUtil.nowTs());
changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
+ update.commit();
}
- private void addMessageToDestinationChange(Change change, String sourceBranch,
- IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
- PatchSet.Id patchSetId =
- db.get().patchSets().get(change.currentPatchSetId()).getId();
- ChangeMessage changeMessage = new ChangeMessage(
- new ChangeMessage.Key(
- patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
- identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
-
- StringBuilder sb = new StringBuilder("Patch Set ")
+ private String messageForDestinationChange(PatchSet.Id patchSetId,
+ Branch.NameKey sourceBranch) {
+ return new StringBuilder("Patch Set ")
.append(patchSetId.get())
.append(": Cherry Picked from branch ")
- .append(sourceBranch)
- .append(".");
- changeMessage.setMessage(sb.toString());
-
- ChangeControl ctl = refControl.getProjectControl().controlFor(change);
- ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
- changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
+ .append(sourceBranch.getShortName())
+ .append(".")
+ .toString();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 7240f44..8a0a47e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -28,9 +28,11 @@
import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.extensions.common.ProblemInfo.Status;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
@@ -39,14 +41,15 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
@@ -57,6 +60,7 @@
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
@@ -106,9 +110,10 @@
private final GitRepositoryManager repoManager;
private final Provider<CurrentUser> user;
private final Provider<PersonIdent> serverIdent;
- private final ChangeControl.GenericFactory changeControlFactory;
+ private final ProjectControl.GenericFactory projectControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetInserter.Factory patchSetInserterFactory;
+ private final BatchUpdate.Factory updateFactory;
private FixInput fix;
private Change change;
@@ -127,16 +132,18 @@
GitRepositoryManager repoManager,
Provider<CurrentUser> user,
@GerritPersonIdent Provider<PersonIdent> serverIdent,
- ChangeControl.GenericFactory changeControlFactory,
+ ProjectControl.GenericFactory projectControlFactory,
PatchSetInfoFactory patchSetInfoFactory,
- PatchSetInserter.Factory patchSetInserterFactory) {
+ PatchSetInserter.Factory patchSetInserterFactory,
+ BatchUpdate.Factory updateFactory) {
this.db = db;
this.repoManager = repoManager;
this.user = user;
this.serverIdent = serverIdent;
- this.changeControlFactory = changeControlFactory;
+ this.projectControlFactory = projectControlFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.patchSetInserterFactory = patchSetInserterFactory;
+ this.updateFactory = updateFactory;
reset();
}
@@ -343,17 +350,17 @@
+ " is merged");
return;
}
- checkMergedBitMatchesStatus(currPs, currPsCommit, merged);
+ checkMergedBitMatchesStatus(currPs.getId(), currPsCommit, merged);
}
}
- private void checkMergedBitMatchesStatus(PatchSet ps, RevCommit commit,
+ private void checkMergedBitMatchesStatus(PatchSet.Id psId, RevCommit commit,
boolean merged) {
String refName = change.getDest().get();
if (merged && change.getStatus() != Change.Status.MERGED) {
ProblemInfo p = problem(String.format(
"Patch set %d (%s) is merged into destination ref %s (%s), but change"
- + " status is %s", ps.getId().get(), commit.name(),
+ + " status is %s", psId.get(), commit.name(),
refName, tip.name(), change.getStatus()));
if (fix != null) {
fixMerged(p);
@@ -414,9 +421,9 @@
commit.name(), changeId, change.getKey().get()));
return;
}
- PatchSet ps = insertPatchSet(commit);
- if (ps != null) {
- checkMergedBitMatchesStatus(ps, commit, true);
+ PatchSet.Id psId = insertPatchSet(commit);
+ if (psId != null) {
+ checkMergedBitMatchesStatus(psId, commit, true);
}
break;
@@ -445,7 +452,7 @@
}
}
- private PatchSet insertPatchSet(RevCommit commit) {
+ private PatchSet.Id insertPatchSet(RevCommit commit) {
ProblemInfo p =
problem("No patch set found for merged commit " + commit.name());
if (!user.get().isIdentifiedUser()) {
@@ -456,23 +463,33 @@
}
try {
- ChangeControl ctl = changeControlFactory.controlFor(change, user.get());
+ RefControl ctl = projectControlFactory
+ .controlFor(change.getProject(), user.get())
+ .controlForRef(change.getDest());
+ PatchSet.Id psId =
+ ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
PatchSetInserter inserter =
- patchSetInserterFactory.create(repo, rw, ctl, commit);
- change = inserter.setValidatePolicy(ValidatePolicy.NONE)
- .setRunHooks(false)
- .setSendMail(false)
- .setAllowClosed(true)
- .setUploader(((IdentifiedUser) user.get()).getAccountId())
- // TODO: fix setMessage to work without init()
- .setMessage(
- "Patch set for merged commit inserted by consistency checker")
- .insert();
+ patchSetInserterFactory.create(ctl, psId, commit);
+ try (BatchUpdate bu = updateFactory.create(
+ db.get(), change.getProject(), ctl.getUser(), TimeUtil.nowTs());
+ ObjectInserter oi = repo.newObjectInserter()) {
+ bu.setRepository(repo, rw, oi);
+ bu.addOp(change.getId(), inserter
+ .setValidatePolicy(CommitValidators.Policy.NONE)
+ .setRunHooks(false)
+ .setSendMail(false)
+ .setAllowClosed(true)
+ .setUploader(user.get().getAccountId())
+ .setMessage(
+ "Patch set for merged commit inserted by consistency checker"));
+ bu.execute();
+ }
+ change = inserter.getChange();
p.status = Status.FIXED;
- p.outcome = "Inserted as patch set " + change.currentPatchSetId().get();
- return inserter.getPatchSet();
- } catch (InvalidChangeOperationException | OrmException | IOException
- | NoSuchChangeException e) {
+ p.outcome = "Inserted as patch set " + psId.get();
+ return psId;
+ } catch (IOException | NoSuchProjectException | UpdateException
+ | RestApiException e) {
warn(e);
p.status = Status.FIX_FAILED;
p.outcome = "Error inserting new patch set";
@@ -591,7 +608,7 @@
private PersonIdent newRefLogIdent() {
CurrentUser u = user.get();
if (u.isIdentifiedUser()) {
- return ((IdentifiedUser) u).newRefLogIdent();
+ return u.asIdentifiedUser().newRefLogIdent();
} else {
return serverIdent.get();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index 6c0ee3b..3768738 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -24,15 +24,13 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -42,15 +40,14 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gerrit.server.project.RefControl;
-import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -62,11 +59,9 @@
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
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 org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.ChangeIdUtil;
import java.io.IOException;
@@ -82,43 +77,41 @@
private final Provider<ReviewDb> db;
private final GitRepositoryManager gitManager;
private final TimeZone serverTimeZone;
- private final Provider<CurrentUser> userProvider;
+ private final Provider<CurrentUser> user;
private final ProjectsCollection projectsCollection;
- private final CommitValidators.Factory commitValidatorsFactory;
private final ChangeInserter.Factory changeInserterFactory;
private final ChangeJson.Factory jsonFactory;
private final ChangeUtil changeUtil;
+ private final BatchUpdate.Factory updateFactory;
private final boolean allowDrafts;
@Inject
CreateChange(Provider<ReviewDb> db,
GitRepositoryManager gitManager,
@GerritPersonIdent PersonIdent myIdent,
- Provider<CurrentUser> userProvider,
+ Provider<CurrentUser> user,
ProjectsCollection projectsCollection,
- CommitValidators.Factory commitValidatorsFactory,
ChangeInserter.Factory changeInserterFactory,
ChangeJson.Factory json,
ChangeUtil changeUtil,
+ BatchUpdate.Factory updateFactory,
@GerritServerConfig Config config) {
this.db = db;
this.gitManager = gitManager;
this.serverTimeZone = myIdent.getTimeZone();
- this.userProvider = userProvider;
+ this.user = user;
this.projectsCollection = projectsCollection;
- this.commitValidatorsFactory = commitValidatorsFactory;
this.changeInserterFactory = changeInserterFactory;
this.jsonFactory = json;
this.changeUtil = changeUtil;
+ this.updateFactory = updateFactory;
this.allowDrafts = config.getBoolean("change", "allowDrafts", true);
}
@Override
- public Response<ChangeInfo> apply(TopLevelResource parent,
- ChangeInfo input) throws AuthException, OrmException,
- BadRequestException, UnprocessableEntityException, IOException,
- InvalidChangeOperationException, ResourceNotFoundException,
- MethodNotAllowedException, ResourceConflictException {
+ public Response<ChangeInfo> apply(TopLevelResource parent, ChangeInfo input)
+ throws OrmException, IOException, InvalidChangeOperationException,
+ RestApiException, UpdateException {
if (Strings.isNullOrEmpty(input.project)) {
throw new BadRequestException("project must be non-empty");
}
@@ -188,85 +181,45 @@
RevCommit mergeTip = rw.parseCommit(parentCommit);
Timestamp now = TimeUtil.nowTs();
- IdentifiedUser me = (IdentifiedUser) userProvider.get();
+ IdentifiedUser me = user.get().asIdentifiedUser();
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
ObjectId id = ChangeIdUtil.computeChangeId(mergeTip.getTree(),
mergeTip, author, author, input.subject);
String commitMessage = ChangeIdUtil.insertId(input.subject, id);
- RevCommit c = newCommit(git, rw, author, mergeTip, commitMessage);
+ try (ObjectInserter oi = git.newObjectInserter()) {
+ RevCommit c = newCommit(oi, rw, author, mergeTip, commitMessage);
- Change change = new Change(
- getChangeId(id, c),
- new Change.Id(db.get().nextChangeId()),
- me.getAccountId(),
- new Branch.NameKey(project, refName),
- now);
+ Change change = new Change(
+ getChangeId(id, c),
+ new Change.Id(db.get().nextChangeId()),
+ me.getAccountId(),
+ new Branch.NameKey(project, refName),
+ now);
- ChangeInserter ins =
- changeInserterFactory.create(refControl.getProjectControl(),
- change, c);
-
- ChangeMessage msg = new ChangeMessage(new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(db.get())),
- me.getAccountId(),
- ins.getPatchSet().getCreatedOn(),
- ins.getPatchSet().getId());
- msg.setMessage(String.format("Uploaded patch set %s.",
- ins.getPatchSet().getPatchSetId()));
-
- ins.setMessage(msg);
- validateCommit(git, refControl, c, me, ins);
- updateRef(git, rw, c, change, ins.getPatchSet());
-
- String topic = input.topic;
- if (topic != null) {
- topic = Strings.emptyToNull(topic.trim());
+ ChangeInserter ins = changeInserterFactory
+ .create(refControl, change, c)
+ .setValidatePolicy(CommitValidators.Policy.GERRIT);
+ ins.setMessage(String.format("Uploaded patch set %s.",
+ ins.getPatchSet().getPatchSetId()));
+ String topic = input.topic;
+ if (topic != null) {
+ topic = Strings.emptyToNull(topic.trim());
+ }
+ change.setTopic(topic);
+ ins.setDraft(input.status != null && input.status == ChangeStatus.DRAFT);
+ ins.setGroups(groups);
+ try (BatchUpdate bu = updateFactory.create(
+ db.get(), change.getProject(), me, now)) {
+ bu.setRepository(git, rw, oi);
+ bu.insertChange(ins);
+ bu.execute();
+ }
+ ChangeJson json = jsonFactory.create(ChangeJson.NO_OPTIONS);
+ return Response.created(json.format(change.getId()));
}
- change.setTopic(topic);
- ins.setDraft(input.status != null && input.status == ChangeStatus.DRAFT);
- ins.setGroups(groups);
- ins.insert();
- ChangeJson json = jsonFactory.create(ChangeJson.NO_OPTIONS);
- return Response.created(json.format(change.getId()));
- }
- }
-
- private void validateCommit(Repository git, RefControl refControl,
- RevCommit c, IdentifiedUser me, ChangeInserter ins)
- throws ResourceConflictException {
- PatchSet newPatchSet = ins.getPatchSet();
- CommitValidators commitValidators =
- commitValidatorsFactory.create(refControl, new NoSshInfo(), git);
- CommitReceivedEvent commitReceivedEvent =
- new CommitReceivedEvent(new ReceiveCommand(
- ObjectId.zeroId(),
- c.getId(),
- newPatchSet.getRefName()),
- refControl.getProjectControl().getProject(),
- refControl.getRefName(),
- c,
- me);
-
- try {
- commitValidators.validateForGerritCommits(commitReceivedEvent);
- } catch (CommitValidationException e) {
- throw new ResourceConflictException(e.getMessage());
- }
- }
-
- private static void updateRef(Repository git, RevWalk rw, RevCommit c,
- Change change, PatchSet newPatchSet) throws IOException {
- RefUpdate ru = git.updateRef(newPatchSet.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(c);
- ru.disableRefLog();
- if (ru.update(rw) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
- change.getDest().getParentKey().get(), ru.getResult()));
}
}
@@ -279,20 +232,16 @@
return changeKey;
}
- private static RevCommit newCommit(Repository git, RevWalk rw,
+ private static RevCommit newCommit(ObjectInserter oi, RevWalk rw,
PersonIdent authorIdent, RevCommit mergeTip, String commitMessage)
throws IOException {
- RevCommit emptyCommit;
- try (ObjectInserter oi = git.newObjectInserter()) {
- CommitBuilder commit = new CommitBuilder();
- commit.setTreeId(mergeTip.getTree().getId());
- commit.setParentId(mergeTip);
- commit.setAuthor(authorIdent);
- commit.setCommitter(authorIdent);
- commit.setMessage(commitMessage);
- emptyCommit = rw.parseCommit(insert(oi, commit));
- }
- return emptyCommit;
+ CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(mergeTip.getTree().getId());
+ commit.setParentId(mergeTip);
+ commit.setAuthor(authorIdent);
+ commit.setCommitter(authorIdent);
+ commit.setMessage(commitMessage);
+ return rw.parseCommit(insert(oi, commit));
}
private static ObjectId insert(ObjectInserter inserter,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
index 9bb1f02..495e695 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
@@ -22,7 +22,6 @@
import com.google.gerrit.server.change.DeleteChangeEdit.Input;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -42,8 +41,7 @@
@Override
public Response<?> apply(ChangeResource rsrc, Input input)
- throws AuthException, ResourceNotFoundException, IOException,
- InvalidChangeOperationException {
+ throws AuthException, ResourceNotFoundException, IOException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (edit.isPresent()) {
editUtil.delete(edit.get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index d9512eb..6c37252 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -111,7 +111,7 @@
ChangeMessage changeMessage =
new ChangeMessage(new ChangeMessage.Key(rsrc.getChange().getId(),
ChangeUtil.messageUUID(db)),
- ((IdentifiedUser) control.getCurrentUser()).getAccountId(),
+ control.getUser().getAccountId(),
TimeUtil.nowTs(), rsrc.getChange().currentPatchSetId());
changeMessage.setMessage(msg.toString());
cmUtil.addChangeMessage(db, update, changeMessage);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
index 733ff0b..24c2f0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
@@ -30,8 +30,8 @@
public class DownloadContent implements RestReadView<FileResource> {
private final FileContentUtil fileContentUtil;
- @Option(name = "--suffix")
- private String suffix;
+ @Option(name = "--parent")
+ private Integer parent;
@Inject
DownloadContent(FileContentUtil fileContentUtil) {
@@ -47,6 +47,6 @@
rsrc.getRevision().getControl().getProjectControl().getProjectState();
ObjectId revstr = ObjectId.fromString(
rsrc.getRevision().getPatchSet().getRevision().get());
- return fileContentUtil.downloadContent(projectState, revstr, path, suffix);
+ return fileContentUtil.downloadContent(projectState, revstr, path, parent);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
index 006b1ec..3dc0c78 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
@@ -20,7 +20,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.TypeLiteral;
@@ -57,6 +56,6 @@
}
Account.Id getAuthorId() {
- return ((IdentifiedUser) getControl().getCurrentUser()).getAccountId();
+ return getControl().getUser().getAccountId();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index 122156b..6122609 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -128,7 +128,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
throw new OutOfScopeException("No user on email thread");
}
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
index 86ce61e..d145ddf 100644
--- 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
@@ -16,7 +16,6 @@
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
@@ -126,14 +125,20 @@
}
public BinaryResult downloadContent(ProjectState project, ObjectId revstr,
- String path, @Nullable String suffix)
+ String path, @Nullable Integer parent)
throws ResourceNotFoundException, IOException {
- suffix = Strings.emptyToNull(CharMatcher.inRange('a', 'z')
- .retainFrom(Strings.nullToEmpty(suffix)));
-
try (Repository repo = openRepository(project);
RevWalk rw = new RevWalk(repo)) {
+ String suffix = "new";
RevCommit commit = rw.parseCommit(revstr);
+ if (parent != null && parent > 0) {
+ if (commit.getParentCount() == 1) {
+ suffix = "old";
+ } else {
+ suffix = "old" + parent;
+ }
+ commit = rw.parseCommit(commit.getParent(parent - 1));
+ }
ObjectReader reader = rw.getObjectReader();
TreeWalk tw = TreeWalk.forPath(reader, path, commit.getTree());
if (tw == null) {
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 9ddb0ed..84f8a04 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
@@ -63,6 +63,7 @@
d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
? e.getChangeType().getCode() : null;
d.oldPath = e.getOldName();
+ d.sizeDelta = e.getSizeDelta();
if (e.getPatchType() == Patch.PatchType.BINARY) {
d.binary = true;
} else {
@@ -76,6 +77,7 @@
// when the file was rewritten and too little content survived. Write
// a single record with data from both sides.
d.status = Patch.ChangeType.REWRITE.getCode();
+ d.sizeDelta = o.sizeDelta;
if (o.binary != null && o.binary) {
d.binary = true;
}
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 e19b6a9..e28d796 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
@@ -34,7 +34,6 @@
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.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
@@ -207,7 +206,7 @@
throw new AuthException("Authentication required");
}
- Account.Id userId = ((IdentifiedUser) user).getAccountId();
+ Account.Id userId = user.getAccountId();
List<String> r = scan(userId, resource.getPatchSet().getId());
if (r.isEmpty() && 1 < resource.getPatchSet().getPatchSetId()) {
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 4fbb6a8..77f7bda 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
@@ -24,9 +24,8 @@
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.ChangeUtil;
import com.google.gerrit.server.CommonConverters;
-import com.google.gerrit.server.change.WalkSorter.PatchSetData;
+import com.google.gerrit.server.change.PatchSetAncestorSorter.PatchSetData;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GroupCollector;
import com.google.gerrit.server.index.IndexCollection;
@@ -43,7 +42,6 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -54,7 +52,7 @@
private final Provider<ReviewDb> db;
private final GetRelatedByAncestors byAncestors;
private final Provider<InternalChangeQuery> queryProvider;
- private final Provider<WalkSorter> sorter;
+ private final PatchSetAncestorSorter sorter;
private final IndexCollection indexes;
private final boolean byAncestorsOnly;
@@ -63,7 +61,7 @@
@GerritServerConfig Config cfg,
GetRelatedByAncestors byAncestors,
Provider<InternalChangeQuery> queryProvider,
- Provider<WalkSorter> sorter,
+ PatchSetAncestorSorter sorter,
IndexCollection indexes) {
this.db = db;
this.byAncestors = byAncestors;
@@ -107,16 +105,14 @@
}
List<ChangeAndCommit> result = new ArrayList<>(cds.size());
- PatchSet.Id editBaseId = rsrc.getEdit().isPresent()
- ? rsrc.getEdit().get().getBasePatchSet().getId()
- : null;
- for (PatchSetData d : sorter.get()
- .includePatchSets(choosePatchSets(thisPatchSetGroups, cds))
- .setRetainBody(true)
- .sort(cds)) {
+ boolean isEdit = rsrc.getEdit().isPresent();
+ PatchSet basePs = isEdit
+ ? rsrc.getEdit().get().getBasePatchSet()
+ : rsrc.getPatchSet();
+ for (PatchSetData d : sorter.sort(cds, basePs)) {
PatchSet ps = d.patchSet();
RevCommit commit;
- if (ps.getId().equals(editBaseId)) {
+ if (isEdit && ps.getId().equals(basePs.getId())) {
// Replace base of an edit with the edit itself.
ps = rsrc.getPatchSet();
commit = rsrc.getEdit().get().getEditCommit();
@@ -147,40 +143,6 @@
return result;
}
- private static Set<PatchSet.Id> choosePatchSets(List<String> groups,
- List<ChangeData> cds) throws OrmException {
- // Prefer the latest patch set matching at least one group from this
- // revision; otherwise, just use the latest patch set overall.
- Set<PatchSet.Id> result = new HashSet<>();
- for (ChangeData cd : cds) {
- Collection<PatchSet> patchSets = cd.patchSets();
- List<PatchSet> sameGroup = new ArrayList<>(patchSets.size());
- for (PatchSet ps : patchSets) {
- if (hasAnyGroup(ps, groups)) {
- sameGroup.add(ps);
- }
- }
- result.add(ChangeUtil.PS_ID_ORDER.max(
- !sameGroup.isEmpty() ? sameGroup : patchSets).getId());
- }
- return result;
- }
-
- private static boolean hasAnyGroup(PatchSet ps, List<String> groups) {
- if (ps.getGroups() == null) {
- return false;
- }
- // Expected size of each list is 1, so nested linear search is fine.
- for (String g1 : ps.getGroups()) {
- for (String g2 : groups) {
- if (g1.equals(g2)) {
- return true;
- }
- }
- }
- return false;
- }
-
public static class RelatedInfo {
public List<ChangeAndCommit> changes;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index a502a7d..6b08469 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -63,7 +63,7 @@
@Override
public String getETag(RevisionResource rsrc) {
Hasher h = Hashing.md5().newHasher();
- CurrentUser user = rsrc.getControl().getCurrentUser();
+ CurrentUser user = rsrc.getControl().getUser();
try {
rsrc.getChangeResource().prepareETag(h, user);
h.putBoolean(Submit.wholeTopicEnabled(config));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
index 9f7eb93..eb260f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
@@ -16,56 +16,18 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.extensions.api.changes.HashtagsInput;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.validators.HashtagValidationListener;
-import com.google.gerrit.server.validators.ValidationException;
-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;
import java.util.HashSet;
import java.util.Set;
-import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-@Singleton
public class HashtagsUtil {
private static final CharMatcher LEADER =
CharMatcher.whitespace().or(CharMatcher.is('#'));
private static final String PATTERN = "(?:\\s|\\A)#[\\p{L}[0-9]-_]+";
- private final ChangeUpdate.Factory updateFactory;
- private final Provider<ReviewDb> dbProvider;
- private final ChangeIndexer indexer;
- private final ChangeHooks hooks;
- private final DynamicSet<HashtagValidationListener> hashtagValidationListeners;
-
- @Inject
- HashtagsUtil(ChangeUpdate.Factory updateFactory,
- Provider<ReviewDb> dbProvider,
- ChangeIndexer indexer,
- ChangeHooks hooks,
- DynamicSet<HashtagValidationListener> hashtagValidationListeners) {
- this.updateFactory = updateFactory;
- this.dbProvider = dbProvider;
- this.indexer = indexer;
- this.hooks = hooks;
- this.hashtagValidationListeners = hashtagValidationListeners;
- }
-
public static String cleanupHashtag(String hashtag) {
hashtag = LEADER.trimLeadingFrom(hashtag);
hashtag = CharMatcher.whitespace().trimTrailingFrom(hashtag);
@@ -83,7 +45,7 @@
return result;
}
- private Set<String> extractTags(Set<String> input)
+ static Set<String> extractTags(Set<String> input)
throws IllegalArgumentException {
if (input == null) {
return Collections.emptySet();
@@ -102,54 +64,6 @@
}
}
- public TreeSet<String> setHashtags(ChangeControl control,
- HashtagsInput input, boolean runHooks, boolean index)
- throws IllegalArgumentException, IOException,
- ValidationException, AuthException, OrmException {
- if (input == null
- || (input.add == null && input.remove == null)) {
- throw new IllegalArgumentException("Hashtags are required");
- }
-
- if (!control.canEditHashtags()) {
- throw new AuthException("Editing hashtags not permitted");
- }
- ChangeUpdate update = updateFactory.create(control);
- ChangeNotes notes = control.getNotes().load();
-
- Set<String> existingHashtags = notes.getHashtags();
- Set<String> updatedHashtags = new HashSet<>();
- Set<String> toAdd = new HashSet<>(extractTags(input.add));
- Set<String> toRemove = new HashSet<>(extractTags(input.remove));
-
- for (HashtagValidationListener validator : hashtagValidationListeners) {
- validator.validateHashtags(update.getChange(), toAdd, toRemove);
- }
-
- if (existingHashtags != null && !existingHashtags.isEmpty()) {
- updatedHashtags.addAll(existingHashtags);
- toAdd.removeAll(existingHashtags);
- toRemove.retainAll(existingHashtags);
- }
-
- if (toAdd.size() > 0 || toRemove.size() > 0) {
- updatedHashtags.addAll(toAdd);
- updatedHashtags.removeAll(toRemove);
- update.setHashtags(updatedHashtags);
- update.commit();
-
- if (index) {
- indexer.index(dbProvider.get(), update.getChange());
- }
-
- if (runHooks) {
- IdentifiedUser currentUser = ((IdentifiedUser) control.getCurrentUser());
- hooks.doHashtagsChangedHook(
- update.getChange(), currentUser.getAccount(),
- toAdd, toRemove, updatedHashtags,
- dbProvider.get());
- }
- }
- return new TreeSet<>(updatedHashtags);
+ private HashtagsUtil() {
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
index e63363e..a0be718 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
@@ -47,7 +47,7 @@
throws IOException, AuthException {
ChangeControl ctl = rsrc.getControl();
if (!ctl.isOwner()
- && !ctl.getCurrentUser().getCapabilities().canMaintainServer()) {
+ && !ctl.getUser().getCapabilities().canMaintainServer()) {
throw new AuthException(
"Only change owner or server maintainer can reindex");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java
index 2b5d7d9..561a040 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListChangeDrafts.java
@@ -19,7 +19,6 @@
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.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
@@ -51,13 +50,12 @@
@Override
public Map<String, List<CommentInfo>> apply(
ChangeResource rsrc) throws AuthException, OrmException {
- if (!rsrc.getControl().getCurrentUser().isIdentifiedUser()) {
+ if (!rsrc.getControl().getUser().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
- IdentifiedUser user = (IdentifiedUser) rsrc.getControl().getCurrentUser();
ChangeData cd = changeDataFactory.create(db.get(), rsrc.getControl());
- List<PatchLineComment> drafts =
- plcUtil.draftByChangeAuthor(db.get(), cd.notes(), user.getAccountId());
+ List<PatchLineComment> drafts = plcUtil.draftByChangeAuthor(
+ db.get(), cd.notes(), rsrc.getControl().getUser().getAccountId());
return commentJson.get()
.setFillAccounts(false)
.setFillPatchSet(true)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index e318162..8d92beb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -33,6 +33,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -206,11 +207,11 @@
Iterable<Ref> refs = Iterables.concat(
refDatabase.getRefs(Constants.R_HEADS).values(),
refDatabase.getRefs(Constants.R_TAGS).values());
- try (RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
+ try (CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
RevFlag canMerge = rw.newFlag("CAN_MERGE");
- CodeReviewCommit rev = parse(rw, key.commit);
+ CodeReviewCommit rev = rw.parseCommit(key.commit);
rev.add(canMerge);
- CodeReviewCommit tip = parse(rw, key.into);
+ CodeReviewCommit tip = rw.parseCommit(key.into);
Set<RevCommit> accepted = alreadyAccepted(rw, refs);
accepted.add(tip);
accepted.addAll(Arrays.asList(rev.getParents()));
@@ -239,12 +240,6 @@
}
return accepted;
}
-
- private CodeReviewCommit parse(RevWalk rw, ObjectId id)
- throws MissingObjectException, IncorrectObjectTypeException,
- IOException {
- return (CodeReviewCommit) rw.parseCommit(id);
- }
}
public static class MergeabilityWeigher
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 99f8ba6..9c588ed 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
@@ -119,12 +119,14 @@
get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
get(CHANGE_EDIT_KIND, "meta").to(ChangeEdits.GetMeta.class);
- factory(ReviewerResource.Factory.class);
factory(AccountLoader.Factory.class);
- factory(EmailReviewComments.Factory.class);
- factory(ChangeInserter.Factory.class);
- factory(PatchSetInserter.Factory.class);
factory(ChangeEdits.Create.Factory.class);
factory(ChangeEdits.DeleteFile.Factory.class);
+ factory(ChangeInserter.Factory.class);
+ factory(EmailReviewComments.Factory.class);
+ factory(PatchSetInserter.Factory.class);
+ factory(RebaseChangeOp.Factory.class);
+ factory(ReviewerResource.Factory.class);
+ factory(SetHashtagsOp.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetAncestorSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetAncestorSorter.java
new file mode 100644
index 0000000..8b0ceb2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetAncestorSorter.java
@@ -0,0 +1,250 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+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.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.ObjectId;
+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.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@Singleton
+class PatchSetAncestorSorter {
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ PatchSetAncestorSorter(GitRepositoryManager repoManager) {
+ this.repoManager = repoManager;
+ }
+
+ public List<PatchSetData> sort(List<ChangeData> in, PatchSet startPs)
+ throws OrmException, IOException {
+ checkArgument(!in.isEmpty(), "Input may not be empty");
+ // Map of all patch sets, keyed by commit SHA-1.
+ Map<String, PatchSetData> byId = collectById(in);
+ PatchSetData start = byId.get(startPs.getRevision().get());
+ checkArgument(start != null, "%s not found in %s", startPs, in);
+ ProjectControl ctl = start.data().changeControl().getProjectControl();
+
+ // Map of patch set -> immediate parent.
+ ListMultimap<PatchSetData, PatchSetData> parents =
+ ArrayListMultimap.create(in.size(), 3);
+ // Map of patch set -> immediate children.
+ ListMultimap<PatchSetData, PatchSetData> children =
+ ArrayListMultimap.create(in.size(), 3);
+ // All other patch sets of the same change as startPs.
+ List<PatchSetData> otherPatchSetsOfStart = new ArrayList<>();
+
+ for (ChangeData cd : in) {
+ for (PatchSet ps : cd.patchSets()) {
+ PatchSetData thisPsd = checkNotNull(byId.get(ps.getRevision().get()));
+ if (cd.getId().equals(start.id()) && !ps.getId().equals(start.psId())) {
+ otherPatchSetsOfStart.add(thisPsd);
+ }
+ for (RevCommit p : thisPsd.commit().getParents()) {
+ PatchSetData parentPsd = byId.get(p.name());
+ if (parentPsd != null) {
+ parents.put(thisPsd, parentPsd);
+ children.put(parentPsd, thisPsd);
+ }
+ }
+ }
+ }
+
+ List<PatchSetData> ancestors = walkAncestors(ctl, parents, start);
+ List<PatchSetData> descendants =
+ walkDescendants(ctl, children, start, otherPatchSetsOfStart, ancestors);
+ List<PatchSetData> result =
+ new ArrayList<>(ancestors.size() + descendants.size() - 1);
+ result.addAll(Lists.reverse(descendants));
+ result.addAll(ancestors);
+ return result;
+ }
+
+ private Map<String, PatchSetData> collectById(List<ChangeData> in)
+ throws OrmException, IOException {
+ Project.NameKey project = in.get(0).change().getProject();
+ Map<String, PatchSetData> result =
+ Maps.newHashMapWithExpectedSize(in.size() * 3);
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ rw.setRetainBody(true);
+ for (ChangeData cd : in) {
+ checkArgument(cd.change().getProject().equals(project),
+ "Expected change %s in project %s, found %s",
+ cd.getId(), project, cd.change().getProject());
+ for (PatchSet ps : cd.patchSets()) {
+ String id = ps.getRevision().get();
+ RevCommit c = rw.parseCommit(ObjectId.fromString(id));
+ PatchSetData psd = PatchSetData.create(cd, ps, c);
+ result.put(id, psd);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static List<PatchSetData> walkAncestors(ProjectControl ctl,
+ ListMultimap<PatchSetData, PatchSetData> parents, PatchSetData start)
+ throws OrmException {
+ List<PatchSetData> result = new ArrayList<>();
+ Deque<PatchSetData> pending = new ArrayDeque<>();
+ pending.add(start);
+ while (!pending.isEmpty()) {
+ PatchSetData psd = pending.remove();
+ if (!isVisible(psd, ctl)) {
+ continue;
+ }
+ result.add(psd);
+ pending.addAll(Lists.reverse(parents.get(psd)));
+ }
+ return result;
+ }
+
+ private static List<PatchSetData> walkDescendants(ProjectControl ctl,
+ ListMultimap<PatchSetData, PatchSetData> children,
+ PatchSetData start, List<PatchSetData> otherPatchSetsOfStart,
+ List<PatchSetData> ancestors)
+ throws OrmException {
+ Set<Change.Id> alreadyEmittedChanges = new HashSet<>();
+ addAllChangeIds(alreadyEmittedChanges, ancestors);
+
+ // Prefer descendants found by following the original patch set passed in.
+ List<PatchSetData> result = walkDescendentsImpl(
+ ctl, alreadyEmittedChanges, children, ImmutableList.of(start));
+ addAllChangeIds(alreadyEmittedChanges, result);
+
+ // Then, go back and add new indirect descendants found by following any
+ // other patch sets of start. These show up after all direct descendants,
+ // because we wouldn't know where in the walk to insert them.
+ result.addAll(walkDescendentsImpl(
+ ctl, alreadyEmittedChanges, children, otherPatchSetsOfStart));
+ return result;
+ }
+
+ private static void addAllChangeIds(Collection<Change.Id> changeIds,
+ Iterable<PatchSetData> psds) {
+ for (PatchSetData psd : psds) {
+ changeIds.add(psd.id());
+ }
+ }
+
+ private static List<PatchSetData> walkDescendentsImpl(ProjectControl ctl,
+ Set<Change.Id> alreadyEmittedChanges,
+ ListMultimap<PatchSetData, PatchSetData> children,
+ List<PatchSetData> start) throws OrmException {
+ if (start.isEmpty()) {
+ return ImmutableList.of();
+ }
+ Map<Change.Id, PatchSet.Id> maxPatchSetIds = new HashMap<>();
+ List<PatchSetData> allPatchSets = new ArrayList<>();
+ Deque<PatchSetData> pending = new ArrayDeque<>();
+ pending.addAll(start);
+ while (!pending.isEmpty()) {
+ PatchSetData psd = pending.remove();
+ if (!isVisible(psd, ctl)) {
+ continue;
+ }
+ if (!alreadyEmittedChanges.contains(psd.id())) {
+ // Don't emit anything for changes that were previously emitted, even
+ // though different patch sets might show up later. However, do
+ // continue walking through them for the purposes of finding indirect
+ // descendants.
+ PatchSet.Id oldMax = maxPatchSetIds.get(psd.id());
+ if (oldMax == null || psd.psId().get() > oldMax.get()) {
+ maxPatchSetIds.put(psd.id(), psd.psId());
+ }
+ allPatchSets.add(psd);
+ }
+ // Breadth-first search with oldest children first.
+ // TODO(dborowitz): After killing PatchSetAncestors, consider DFS to keep
+ // parallel history together.
+ pending.addAll(Lists.reverse(children.get(psd)));
+ }
+
+ // If we saw the same change multiple times, prefer the latest patch set.
+ List<PatchSetData> result = new ArrayList<>(allPatchSets.size());
+ for (PatchSetData psd : allPatchSets) {
+ if (checkNotNull(maxPatchSetIds.get(psd.id())).equals(psd.psId())) {
+ result.add(psd);
+ }
+ }
+ return result;
+ }
+
+ private static boolean isVisible(PatchSetData psd, ProjectControl ctl)
+ throws OrmException {
+ // Reuse existing project control rather than lazily creating a new one for
+ // each ChangeData.
+ return ctl.controlFor(psd.data().change())
+ .isPatchVisible(psd.patchSet(), psd.data());
+ }
+
+ @AutoValue
+ abstract static class PatchSetData {
+ @VisibleForTesting
+ static PatchSetData create(ChangeData cd, PatchSet ps, RevCommit commit) {
+ return new AutoValue_PatchSetAncestorSorter_PatchSetData(cd, ps, commit);
+ }
+
+ abstract ChangeData data();
+ abstract PatchSet patchSet();
+ abstract RevCommit commit();
+
+ PatchSet.Id psId() {
+ return patchSet().getId();
+ }
+
+ Change.Id id() {
+ return psId().getParentKey();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(patchSet().getId(), commit());
+ }
+ }
+}
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 7ff1c89e..68f1497 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
@@ -14,13 +14,12 @@
package com.google.gerrit.server.change;
-import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -32,35 +31,32 @@
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
-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.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.GroupCollector;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
-import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.ReviewerState;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ChangeModifiedException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.ssh.NoSshInfo;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,132 +64,81 @@
import java.io.IOException;
import java.util.Collections;
-public class PatchSetInserter {
+public class PatchSetInserter extends BatchUpdate.Op {
private static final Logger log =
LoggerFactory.getLogger(PatchSetInserter.class);
public static interface Factory {
- PatchSetInserter create(Repository git, RevWalk revWalk, ChangeControl ctl,
+ PatchSetInserter create(RefControl refControl, PatchSet.Id psId,
RevCommit commit);
}
- /**
- * Whether to use {@link CommitValidators#validateForGerritCommits},
- * {@link CommitValidators#validateForReceiveCommits}, or no commit
- * validation.
- */
- public static enum ValidatePolicy {
- GERRIT, RECEIVE_COMMITS, NONE
- }
-
+ // Injected fields.
private final ChangeHooks hooks;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ReviewDb db;
- private final ChangeUpdate.Factory updateFactory;
- private final ChangeControl.GenericFactory ctlFactory;
- private final GitReferenceUpdated gitRefUpdated;
private final CommitValidators.Factory commitValidatorsFactory;
- private final ChangeIndexer indexer;
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final ApprovalsUtil approvalsUtil;
private final ApprovalCopier approvalCopier;
private final ChangeMessagesUtil cmUtil;
- private final Repository git;
- private final RevWalk revWalk;
+ // Assisted-injected fields.
+ private final PatchSet.Id psId;
private final RevCommit commit;
- private final ChangeControl ctl;
- private final IdentifiedUser user;
+ private final RefControl refControl;
- private PatchSet patchSet;
- private ChangeMessage changeMessage;
+ // Fields exposed as setters.
private SshInfo sshInfo;
- private ValidatePolicy validatePolicy = ValidatePolicy.GERRIT;
+ private String message;
+ private CommitValidators.Policy validatePolicy =
+ CommitValidators.Policy.GERRIT;
private boolean draft;
private Iterable<String> groups;
- private boolean runHooks;
- private boolean sendMail;
+ private boolean runHooks = true;
+ private boolean sendMail = true;
private Account.Id uploader;
private boolean allowClosed;
- @Inject
+ // Fields set during some phase of BatchUpdate.Op.
+ private Change change;
+ private PatchSet patchSet;
+ private PatchSetInfo patchSetInfo;
+ private ChangeMessage changeMessage;
+ private SetMultimap<ReviewerState, Account.Id> oldReviewers;
+
+ @AssistedInject
public PatchSetInserter(ChangeHooks hooks,
ReviewDb db,
- ChangeUpdate.Factory updateFactory,
- ChangeControl.GenericFactory ctlFactory,
ApprovalsUtil approvalsUtil,
ApprovalCopier approvalCopier,
ChangeMessagesUtil cmUtil,
PatchSetInfoFactory patchSetInfoFactory,
- GitReferenceUpdated gitRefUpdated,
CommitValidators.Factory commitValidatorsFactory,
- ChangeIndexer indexer,
ReplacePatchSetSender.Factory replacePatchSetFactory,
- @Assisted Repository git,
- @Assisted RevWalk revWalk,
- @Assisted ChangeControl ctl,
+ @Assisted RefControl refControl,
+ @Assisted PatchSet.Id psId,
@Assisted RevCommit commit) {
- checkArgument(ctl.getCurrentUser().isIdentifiedUser(),
- "only IdentifiedUser may create patch set on change %s",
- ctl.getChange().getId());
this.hooks = hooks;
this.db = db;
- this.updateFactory = updateFactory;
- this.ctlFactory = ctlFactory;
this.approvalsUtil = approvalsUtil;
this.approvalCopier = approvalCopier;
this.cmUtil = cmUtil;
this.patchSetInfoFactory = patchSetInfoFactory;
- this.gitRefUpdated = gitRefUpdated;
this.commitValidatorsFactory = commitValidatorsFactory;
- this.indexer = indexer;
this.replacePatchSetFactory = replacePatchSetFactory;
- this.git = git;
- this.revWalk = revWalk;
+ this.refControl = refControl;
+ this.psId = psId;
this.commit = commit;
- this.ctl = ctl;
- this.user = (IdentifiedUser) ctl.getCurrentUser();
- this.runHooks = true;
- this.sendMail = true;
}
- public PatchSetInserter setPatchSet(PatchSet patchSet) {
- Change c = ctl.getChange();
- PatchSet.Id psid = patchSet.getId();
- checkArgument(psid.getParentKey().equals(c.getId()),
- "patch set %s not for change %s", psid, c.getId());
- checkArgument(psid.get() > c.currentPatchSetId().get(),
- "new patch set ID %s is not greater than current patch set ID %s",
- psid.get(), c.currentPatchSetId().get());
- this.patchSet = patchSet;
- return this;
+ public PatchSet.Id getPatchSetId() {
+ return psId;
}
- public PatchSet.Id getPatchSetId() throws IOException {
- init();
- return patchSet.getId();
- }
-
- public PatchSet getPatchSet() {
- checkState(patchSet != null,
- "getPatchSet() only valid after patch set is created");
- return patchSet;
- }
-
- public PatchSetInserter setMessage(String message)
- throws OrmException, IOException {
- init();
- changeMessage = new ChangeMessage(
- new ChangeMessage.Key(
- ctl.getChange().getId(), ChangeUtil.messageUUID(db)),
- user.getAccountId(), TimeUtil.nowTs(), patchSet.getId());
- changeMessage.setMessage(message);
- return this;
- }
-
- public PatchSetInserter setMessage(ChangeMessage changeMessage) {
- this.changeMessage = changeMessage;
+ public PatchSetInserter setMessage(String message) {
+ this.message = message;
return this;
}
@@ -202,7 +147,7 @@
return this;
}
- public PatchSetInserter setValidatePolicy(ValidatePolicy validate) {
+ public PatchSetInserter setValidatePolicy(CommitValidators.Policy validate) {
this.validatePolicy = checkNotNull(validate);
return this;
}
@@ -237,161 +182,143 @@
return this;
}
- public Change insert() throws InvalidChangeOperationException, OrmException,
- IOException, NoSuchChangeException {
+ public Change getChange() {
+ checkState(change != null, "getChange() only valid after executing update");
+ return change;
+ }
+
+ public PatchSet getPatchSet() {
+ checkState(patchSet != null, "getPatchSet() only valid after executing update");
+ return patchSet;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx)
+ throws InvalidChangeOperationException, IOException {
init();
- validate();
+ validate(ctx);
+ patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
+ ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(),
+ commit, getPatchSetId().toRefName(), ReceiveCommand.Type.CREATE));
+ }
- Change c = ctl.getChange();
- Change updatedChange;
- RefUpdate ru = git.updateRef(patchSet.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(commit);
- ru.disableRefLog();
- if (ru.update(revWalk) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", patchSet.getRefName(),
- c.getDest().getParentKey().get(), ru.getResult()));
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException,
+ InvalidChangeOperationException {
+ ChangeControl ctl = ctx.getChangeControl();
+
+ change = ctx.getChange();
+ Change.Id id = change.getId();
+ final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
+ if (!change.getStatus().isOpen() && !allowClosed) {
+ throw new InvalidChangeOperationException(String.format(
+ "Change %s is closed", change.getId()));
}
- gitRefUpdated.fire(c.getProject(), ru);
- final PatchSet.Id currentPatchSetId = c.currentPatchSetId();
+ patchSet = new PatchSet(psId);
+ patchSet.setCreatedOn(ctx.getWhen());
+ patchSet.setUploader(firstNonNull(uploader, ctl.getChange().getOwner()));
+ patchSet.setRevision(new RevId(commit.name()));
+ patchSet.setDraft(draft);
- ChangeUpdate update = updateFactory.create(ctl, patchSet.getCreatedOn());
+ ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
+ if (groups != null) {
+ patchSet.setGroups(groups);
+ } else {
+ patchSet.setGroups(GroupCollector.getCurrentGroups(db, change));
+ }
+ db.patchSets().insert(Collections.singleton(patchSet));
- db.changes().beginTransaction(c.getId());
- try {
- updatedChange = db.changes().get(c.getId());
- if (!updatedChange.getStatus().isOpen() && !allowClosed) {
- throw new InvalidChangeOperationException(String.format(
- "Change %s is closed", c.getId()));
- }
+ if (sendMail) {
+ oldReviewers = approvalsUtil.getReviewers(db, ctl.getNotes());
+ }
- ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
- if (groups != null) {
- patchSet.setGroups(groups);
- } else {
- patchSet.setGroups(GroupCollector.getCurrentGroups(db, c));
- }
- db.patchSets().insert(Collections.singleton(patchSet));
+ if (message != null) {
+ changeMessage = new ChangeMessage(
+ new ChangeMessage.Key(
+ ctl.getChange().getId(), ChangeUtil.messageUUID(db)),
+ ctx.getUser().getAccountId(), ctx.getWhen(), patchSet.getId());
+ changeMessage.setMessage(message);
+ }
- SetMultimap<ReviewerState, Account.Id> oldReviewers = sendMail
- ? approvalsUtil.getReviewers(db, ctl.getNotes())
- : null;
-
- updatedChange =
- db.changes().atomicUpdate(c.getId(), new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isClosed() && !allowClosed) {
- return null;
- }
- if (!change.currentPatchSetId().equals(currentPatchSetId)) {
- return null;
- }
- if (change.getStatus() != Change.Status.DRAFT && !allowClosed) {
- change.setStatus(Change.Status.NEW);
- }
- change.setCurrentPatchSet(patchSetInfoFactory.get(commit,
- patchSet.getId()));
- ChangeUtil.updated(change);
- return change;
- }
- });
- if (updatedChange == null) {
- throw new ChangeModifiedException(String.format(
- "Change %s was modified", c.getId()));
- }
-
- if (messageIsForChange()) {
- cmUtil.addChangeMessage(db, update, changeMessage);
- }
-
- approvalCopier.copy(db, ctl, patchSet);
- db.commit();
- if (messageIsForChange()) {
- update.commit();
- }
-
- if (!messageIsForChange()) {
- commitMessageNotForChange(updatedChange);
- }
-
- if (sendMail) {
- try {
- PatchSetInfo info = patchSetInfoFactory.get(commit, patchSet.getId());
- ReplacePatchSetSender cm =
- replacePatchSetFactory.create(c.getId());
- cm.setFrom(user.getAccountId());
- cm.setPatchSet(patchSet, info);
- cm.setChangeMessage(changeMessage);
- cm.addReviewers(oldReviewers.get(ReviewerState.REVIEWER));
- cm.addExtraCC(oldReviewers.get(ReviewerState.CC));
- cm.send();
- } catch (Exception err) {
- log.error("Cannot send email for new patch set on change "
- + updatedChange.getId(), err);
+ // TODO(dborowitz): Throw ResourceConflictException instead of using
+ // AtomicUpdate.
+ change = db.changes().atomicUpdate(id, new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus().isClosed() && !allowClosed) {
+ return null;
}
+ if (!change.currentPatchSetId().equals(currentPatchSetId)) {
+ return null;
+ }
+ if (change.getStatus() != Change.Status.DRAFT && !allowClosed) {
+ change.setStatus(Change.Status.NEW);
+ }
+ change.setCurrentPatchSet(patchSetInfo);
+ ChangeUtil.updated(change);
+ return change;
}
-
- } finally {
- db.rollback();
+ });
+ if (change == null) {
+ throw new ChangeModifiedException(String.format(
+ "Change %s was modified", id));
}
- indexer.index(db, updatedChange);
- if (runHooks) {
- hooks.doPatchsetCreatedHook(updatedChange, patchSet, db);
- }
- return updatedChange;
- }
- private void commitMessageNotForChange(Change updatedChange)
- throws OrmException, NoSuchChangeException, IOException {
+ approvalCopier.copy(db, ctl, patchSet);
if (changeMessage != null) {
- Change otherChange =
- db.changes().get(changeMessage.getPatchSetId().getParentKey());
- ChangeControl otherControl =
- ctlFactory.controlFor(otherChange, user);
- ChangeUpdate updateForOtherChange =
- updateFactory.create(otherControl, updatedChange.getLastUpdatedOn());
- cmUtil.addChangeMessage(db, updateForOtherChange, changeMessage);
- updateForOtherChange.commit();
+ cmUtil.addChangeMessage(db, ctx.getChangeUpdate(), changeMessage);
}
}
- private void init() throws IOException {
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ if (sendMail) {
+ try {
+ ReplacePatchSetSender cm = replacePatchSetFactory.create(
+ change.getId());
+ cm.setFrom(ctx.getUser().getAccountId());
+ cm.setPatchSet(patchSet, patchSetInfo);
+ cm.setChangeMessage(changeMessage);
+ cm.addReviewers(oldReviewers.get(ReviewerState.REVIEWER));
+ cm.addExtraCC(oldReviewers.get(ReviewerState.CC));
+ cm.send();
+ } catch (Exception err) {
+ log.error("Cannot send email for new patch set on change "
+ + change.getId(), err);
+ }
+ }
+
+ if (runHooks) {
+ hooks.doPatchsetCreatedHook(change, patchSet, ctx.getDb());
+ }
+ }
+
+ private void init() {
if (sshInfo == null) {
sshInfo = new NoSshInfo();
}
- if (patchSet == null) {
- patchSet = new PatchSet(
- ChangeUtil.nextPatchSetId(git, ctl.getChange().currentPatchSetId()));
- patchSet.setCreatedOn(TimeUtil.nowTs());
- patchSet.setUploader(ctl.getChange().getOwner());
- patchSet.setRevision(new RevId(commit.name()));
- }
- patchSet.setDraft(draft);
- if (uploader != null) {
- patchSet.setUploader(uploader);
- }
}
- private void validate() throws InvalidChangeOperationException, IOException {
- CommitValidators cv =
- commitValidatorsFactory.create(ctl.getRefControl(), sshInfo, git);
+ private void validate(RepoContext ctx)
+ throws InvalidChangeOperationException, IOException {
+ CommitValidators cv = commitValidatorsFactory.create(
+ refControl, sshInfo, ctx.getRepository());
- String refName = patchSet.getRefName();
+ String refName = getPatchSetId().toRefName();
CommitReceivedEvent event = new CommitReceivedEvent(
new ReceiveCommand(
ObjectId.zeroId(),
commit.getId(),
refName.substring(0, refName.lastIndexOf('/') + 1) + "new"),
- ctl.getProjectControl().getProject(), ctl.getRefControl().getRefName(),
- commit, user);
+ refControl.getProjectControl().getProject(), refControl.getRefName(),
+ commit, ctx.getUser().asIdentifiedUser());
try {
switch (validatePolicy) {
case RECEIVE_COMMITS:
- NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(git, revWalk);
+ NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(
+ ctx.getRepository(), ctx.getRevWalk());
cv.validateForReceiveCommits(event, rejectCommits);
break;
case GERRIT:
@@ -404,9 +331,4 @@
throw new InvalidChangeOperationException(e.getMessage());
}
}
-
- private boolean messageIsForChange() {
- return changeMessage != null && changeMessage.getKey().getParentKey()
- .equals(patchSet.getId().getParentKey());
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
index 62520f4..a3fc2e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
@@ -14,43 +14,47 @@
package com.google.gerrit.server.change;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.server.validators.ValidationException;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.UpdateException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.Set;
-
@Singleton
-public class PostHashtags implements RestModifyView<ChangeResource, HashtagsInput>,
- UiAction<ChangeResource>{
- private HashtagsUtil hashtagsUtil;
+public class PostHashtags
+ implements RestModifyView<ChangeResource, HashtagsInput>,
+ UiAction<ChangeResource> {
+ private final Provider<ReviewDb> db;
+ private final BatchUpdate.Factory batchUpdateFactory;
+ private final SetHashtagsOp.Factory hashtagsFactory;
@Inject
- PostHashtags(HashtagsUtil hashtagsUtil) {
- this.hashtagsUtil = hashtagsUtil;
+ PostHashtags(Provider<ReviewDb> db,
+ BatchUpdate.Factory batchUpdateFactory,
+ SetHashtagsOp.Factory hashtagsFactory) {
+ this.db = db;
+ this.batchUpdateFactory = batchUpdateFactory;
+ this.hashtagsFactory = hashtagsFactory;
}
@Override
- public Response<Set<String>> apply(ChangeResource req, HashtagsInput input)
- throws AuthException, OrmException, IOException, BadRequestException,
- ResourceConflictException {
-
- try {
- return Response.<Set<String>> ok(hashtagsUtil.setHashtags(
- req.getControl(), input, true, true));
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(e.getMessage());
- } catch (ValidationException e) {
- throw new ResourceConflictException(e.getMessage());
+ public Response<ImmutableSortedSet<String>> apply(ChangeResource req,
+ HashtagsInput input) throws RestApiException, UpdateException {
+ try (BatchUpdate bu = batchUpdateFactory.create(db.get(),
+ req.getChange().getProject(), req.getControl().getUser(),
+ TimeUtil.nowTs())) {
+ SetHashtagsOp op = hashtagsFactory.create(input);
+ bu.addOp(req.getChange().getId(), op);
+ bu.execute();
+ return Response.<ImmutableSortedSet<String>> ok(op.getUpdatedHashtags());
}
}
@@ -60,4 +64,4 @@
.setLabel("Edit Hashtags")
.setVisible(resource.getControl().canEditHashtags());
}
-}
\ No newline at end of file
+}
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 e10077a..7a24dbc 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
@@ -14,10 +14,10 @@
package com.google.gerrit.server.change;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -36,6 +36,7 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
@@ -44,6 +45,7 @@
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.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
@@ -52,7 +54,10 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountsCollection;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
@@ -61,18 +66,21 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
import java.sql.Timestamp;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+@Singleton
public class PostReview implements RestModifyView<RevisionResource, ReviewInput> {
private static final Logger log = LoggerFactory.getLogger(PostReview.class);
@@ -81,47 +89,37 @@
}
private final Provider<ReviewDb> db;
+ private final BatchUpdate.Factory batchUpdateFactory;
private final ChangesCollection changes;
private final ChangeData.Factory changeDataFactory;
- private final ChangeUpdate.Factory updateFactory;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
private final PatchLineCommentsUtil plcUtil;
private final PatchListCache patchListCache;
- private final ChangeIndexer indexer;
private final AccountsCollection accounts;
private final EmailReviewComments.Factory email;
- @Deprecated private final ChangeHooks hooks;
-
- private Change change;
- private ChangeMessage message;
- private Timestamp timestamp;
- private List<PatchLineComment> comments = Lists.newArrayList();
- private List<String> labelDelta = Lists.newArrayList();
- private Map<String, Short> categories = Maps.newHashMap();
+ private final ChangeHooks hooks;
@Inject
PostReview(Provider<ReviewDb> db,
+ BatchUpdate.Factory batchUpdateFactory,
ChangesCollection changes,
ChangeData.Factory changeDataFactory,
- ChangeUpdate.Factory updateFactory,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
PatchLineCommentsUtil plcUtil,
PatchListCache patchListCache,
- ChangeIndexer indexer,
AccountsCollection accounts,
EmailReviewComments.Factory email,
ChangeHooks hooks) {
this.db = db;
+ this.batchUpdateFactory = batchUpdateFactory;
this.changes = changes;
this.changeDataFactory = changeDataFactory;
- this.updateFactory = updateFactory;
this.plcUtil = plcUtil;
this.patchListCache = patchListCache;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
- this.indexer = indexer;
this.accounts = accounts;
this.email = email;
this.hooks = hooks;
@@ -129,16 +127,12 @@
@Override
public Output apply(RevisionResource revision, ReviewInput input)
- throws AuthException, BadRequestException, ResourceConflictException,
- UnprocessableEntityException, OrmException, IOException {
+ throws RestApiException, UpdateException, OrmException {
return apply(revision, input, TimeUtil.nowTs());
}
public Output apply(RevisionResource revision, ReviewInput input,
- Timestamp ts) throws AuthException, BadRequestException,
- ResourceConflictException, UnprocessableEntityException, OrmException,
- IOException {
- timestamp = ts;
+ Timestamp ts) throws RestApiException, UpdateException, OrmException {
if (revision.getEdit().isPresent()) {
throw new ResourceConflictException("cannot post review on edit");
}
@@ -156,46 +150,15 @@
input.notify = NotifyHandling.NONE;
}
- db.get().changes().beginTransaction(revision.getChange().getId());
- boolean dirty = false;
- try {
- change = db.get().changes().get(revision.getChange().getId());
- if (change.getLastUpdatedOn().before(timestamp)) {
- change.setLastUpdatedOn(timestamp);
- }
-
- ChangeUpdate update = updateFactory.create(revision.getControl(), timestamp);
- update.setPatchSetId(revision.getPatchSet().getId());
- dirty |= insertComments(revision, update, input.comments, input.drafts);
- dirty |= updateLabels(revision, update, input.labels);
- dirty |= insertMessage(revision, input.message, update);
- if (dirty) {
- db.get().changes().update(Collections.singleton(change));
- db.get().commit();
- }
- update.commit();
- } finally {
- db.get().rollback();
+ try (BatchUpdate bu = batchUpdateFactory.create(db.get(),
+ revision.getChange().getProject(), revision.getUser(), ts)) {
+ bu.addOp(
+ revision.getChange().getId(),
+ new Op(revision.getPatchSet().getId(), input));
+ bu.execute();
}
-
- if (dirty) {
- indexer.index(db.get(), change);
- }
- if (message != null && input.notify.compareTo(NotifyHandling.NONE) > 0) {
- email.create(
- input.notify,
- change,
- revision.getPatchSet(),
- revision.getAccountId(),
- message,
- comments).sendAsync();
- }
-
Output output = new Output();
output.labels = input.labels;
- if (message != null) {
- fireCommentAddedHook(revision);
- }
return output;
}
@@ -336,258 +299,302 @@
}
}
- private boolean insertComments(RevisionResource rsrc,
- ChangeUpdate update, Map<String, List<CommentInput>> in, DraftHandling draftsHandling)
- throws OrmException {
- if (in == null) {
- in = Collections.emptyMap();
+ private class Op extends BatchUpdate.Op {
+ private final PatchSet.Id psId;
+ private final ReviewInput in;
+
+ private IdentifiedUser user;
+ private Change change;
+ private PatchSet ps;
+ private ChangeMessage message;
+ private List<PatchLineComment> comments = new ArrayList<>();
+ private List<String> labelDelta = new ArrayList<>();
+ private Map<String, Short> categories = new HashMap<>();
+
+ private Op(PatchSet.Id psId, ReviewInput in) {
+ this.psId = psId;
+ this.in = in;
}
- Map<String, PatchLineComment> drafts = Collections.emptyMap();
- if (!in.isEmpty() || draftsHandling != DraftHandling.KEEP) {
- if (draftsHandling == DraftHandling.PUBLISH_ALL_REVISIONS) {
- drafts = changeDrafts(rsrc);
- } else {
- drafts = patchSetDrafts(rsrc);
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException {
+ user = ctx.getUser().asIdentifiedUser();
+ change = ctx.getChange();
+ if (change.getLastUpdatedOn().before(ctx.getWhen())) {
+ change.setLastUpdatedOn(ctx.getWhen());
+ }
+ ps = ctx.getDb().patchSets().get(psId);
+ ctx.getChangeUpdate().setPatchSetId(psId);
+ boolean dirty = false;
+ dirty |= insertComments(ctx);
+ dirty |= updateLabels(ctx);
+ dirty |= insertMessage(ctx);
+ if (dirty) {
+ ctx.getDb().changes().update(Collections.singleton(change));
}
}
- List<PatchLineComment> del = Lists.newArrayList();
- List<PatchLineComment> ups = Lists.newArrayList();
-
- for (Map.Entry<String, List<CommentInput>> ent : in.entrySet()) {
- String path = ent.getKey();
- for (CommentInput c : ent.getValue()) {
- String parent = Url.decode(c.inReplyTo);
- PatchLineComment e = drafts.remove(Url.decode(c.id));
- if (e == null) {
- e = new PatchLineComment(
- new PatchLineComment.Key(
- new Patch.Key(rsrc.getPatchSet().getId(), path),
- ChangeUtil.messageUUID(db.get())),
- c.line != null ? c.line : 0,
- rsrc.getAccountId(),
- parent, timestamp);
- } else if (parent != null) {
- e.setParentUuid(parent);
- }
- e.setStatus(PatchLineComment.Status.PUBLISHED);
- e.setWrittenOn(timestamp);
- e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
- setCommentRevId(e, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
- e.setMessage(c.message);
- if (c.range != null) {
- e.setRange(new CommentRange(
- c.range.startLine,
- c.range.startCharacter,
- c.range.endLine,
- c.range.endCharacter));
- e.setLine(c.range.endLine);
- }
- ups.add(e);
+ @Override
+ public void postUpdate(Context ctx) {
+ if (message == null) {
+ return;
+ }
+ if (in.notify.compareTo(NotifyHandling.NONE) > 0) {
+ email.create(
+ in.notify,
+ change,
+ ps,
+ user.getAccountId(),
+ message,
+ comments).sendAsync();
+ }
+ try {
+ hooks.doCommentAddedHook(change, user.getAccount(), ps,
+ message.getMessage(), categories, ctx.getDb());
+ } catch (OrmException e) {
+ log.warn("ChangeHook.doCommentAddedHook delivery failed", e);
}
}
- switch (MoreObjects.firstNonNull(draftsHandling, DraftHandling.DELETE)) {
- case KEEP:
- default:
- break;
- case DELETE:
- del.addAll(drafts.values());
- break;
- case PUBLISH:
- case PUBLISH_ALL_REVISIONS:
- for (PatchLineComment e : drafts.values()) {
+ private boolean insertComments(ChangeContext ctx) throws OrmException {
+ Map<String, List<CommentInput>> map = in.comments;
+ if (map == null) {
+ map = Collections.emptyMap();
+ }
+
+ Map<String, PatchLineComment> drafts = Collections.emptyMap();
+ if (!map.isEmpty() || in.drafts != DraftHandling.KEEP) {
+ if (in.drafts == DraftHandling.PUBLISH_ALL_REVISIONS) {
+ drafts = changeDrafts(ctx);
+ } else {
+ drafts = patchSetDrafts(ctx);
+ }
+ }
+
+ List<PatchLineComment> del = Lists.newArrayList();
+ List<PatchLineComment> ups = Lists.newArrayList();
+
+ for (Map.Entry<String, List<CommentInput>> ent : map.entrySet()) {
+ String path = ent.getKey();
+ for (CommentInput c : ent.getValue()) {
+ String parent = Url.decode(c.inReplyTo);
+ PatchLineComment e = drafts.remove(Url.decode(c.id));
+ if (e == null) {
+ e = new PatchLineComment(
+ new PatchLineComment.Key(
+ new Patch.Key(psId, path),
+ ChangeUtil.messageUUID(ctx.getDb())),
+ c.line != null ? c.line : 0,
+ user.getAccountId(),
+ parent, ctx.getWhen());
+ } else if (parent != null) {
+ e.setParentUuid(parent);
+ }
e.setStatus(PatchLineComment.Status.PUBLISHED);
- e.setWrittenOn(timestamp);
- setCommentRevId(e, patchListCache, rsrc.getChange(), rsrc.getPatchSet());
+ e.setWrittenOn(ctx.getWhen());
+ e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
+ setCommentRevId(e, patchListCache, ctx.getChange(), ps);
+ e.setMessage(c.message);
+ if (c.range != null) {
+ e.setRange(new CommentRange(
+ c.range.startLine,
+ c.range.startCharacter,
+ c.range.endLine,
+ c.range.endCharacter));
+ e.setLine(c.range.endLine);
+ }
ups.add(e);
}
- break;
- }
- plcUtil.deleteComments(db.get(), update, del);
- plcUtil.upsertComments(db.get(), update, ups);
- comments.addAll(ups);
- return !del.isEmpty() || !ups.isEmpty();
- }
-
- private Map<String, PatchLineComment> changeDrafts(RevisionResource rsrc)
- throws OrmException {
- Map<String, PatchLineComment> drafts = Maps.newHashMap();
- for (PatchLineComment c : plcUtil.draftByChangeAuthor(
- db.get(), rsrc.getNotes(), rsrc.getAccountId())) {
- drafts.put(c.getKey().get(), c);
- }
- return drafts;
- }
-
- private Map<String, PatchLineComment> patchSetDrafts(RevisionResource rsrc)
- throws OrmException {
- Map<String, PatchLineComment> drafts = Maps.newHashMap();
- for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(db.get(),
- rsrc.getPatchSet().getId(), rsrc.getAccountId(), rsrc.getNotes())) {
- drafts.put(c.getKey().get(), c);
- }
- return drafts;
- }
-
- private boolean updateLabels(RevisionResource rsrc, ChangeUpdate update,
- Map<String, Short> labels) throws OrmException {
- if (labels == null) {
- labels = Collections.emptyMap();
- }
-
- List<PatchSetApproval> del = Lists.newArrayList();
- List<PatchSetApproval> ups = Lists.newArrayList();
- Map<String, PatchSetApproval> current = scanLabels(rsrc, del);
-
- LabelTypes labelTypes = rsrc.getControl().getLabelTypes();
- for (Map.Entry<String, Short> ent : labels.entrySet()) {
- String name = ent.getKey();
- LabelType lt = checkNotNull(labelTypes.byLabel(name), name);
- if (change.getStatus().isClosed()) {
- // TODO Allow updating some labels even when closed.
- continue;
}
- PatchSetApproval c = current.remove(lt.getName());
- String normName = lt.getName();
- if (ent.getValue() == null || ent.getValue() == 0) {
- // User requested delete of this label.
- if (c != null) {
- if (c.getValue() != 0) {
- addLabelDelta(normName, (short) 0);
+ switch (firstNonNull(in.drafts, DraftHandling.DELETE)) {
+ case KEEP:
+ default:
+ break;
+ case DELETE:
+ del.addAll(drafts.values());
+ break;
+ case PUBLISH:
+ case PUBLISH_ALL_REVISIONS:
+ for (PatchLineComment e : drafts.values()) {
+ e.setStatus(PatchLineComment.Status.PUBLISHED);
+ e.setWrittenOn(ctx.getWhen());
+ setCommentRevId(e, patchListCache, ctx.getChange(), ps);
+ ups.add(e);
}
- del.add(c);
- update.putApproval(ent.getKey(), (short) 0);
+ break;
+ }
+ plcUtil.deleteComments(ctx.getDb(), ctx.getChangeUpdate(), del);
+ plcUtil.upsertComments(ctx.getDb(), ctx.getChangeUpdate(), ups);
+ comments.addAll(ups);
+ return !del.isEmpty() || !ups.isEmpty();
+ }
+
+ private Map<String, PatchLineComment> changeDrafts(ChangeContext ctx)
+ throws OrmException {
+ Map<String, PatchLineComment> drafts = Maps.newHashMap();
+ for (PatchLineComment c : plcUtil.draftByChangeAuthor(
+ ctx.getDb(), ctx.getChangeNotes(), user.getAccountId())) {
+ drafts.put(c.getKey().get(), c);
+ }
+ return drafts;
+ }
+
+ private Map<String, PatchLineComment> patchSetDrafts(ChangeContext ctx)
+ throws OrmException {
+ Map<String, PatchLineComment> drafts = Maps.newHashMap();
+ for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(ctx.getDb(),
+ psId, user.getAccountId(), ctx.getChangeNotes())) {
+ drafts.put(c.getKey().get(), c);
+ }
+ return drafts;
+ }
+
+ private boolean updateLabels(ChangeContext ctx) throws OrmException {
+ Map<String, Short> labels = in.labels;
+ if (labels == null) {
+ labels = Collections.emptyMap();
+ }
+
+ List<PatchSetApproval> del = Lists.newArrayList();
+ List<PatchSetApproval> ups = Lists.newArrayList();
+ Map<String, PatchSetApproval> current = scanLabels(ctx, del);
+
+ ChangeUpdate update = ctx.getChangeUpdate();
+ LabelTypes labelTypes = ctx.getChangeControl().getLabelTypes();
+ for (Map.Entry<String, Short> ent : labels.entrySet()) {
+ String name = ent.getKey();
+ LabelType lt = checkNotNull(labelTypes.byLabel(name), name);
+ if (ctx.getChange().getStatus().isClosed()) {
+ // TODO Allow updating some labels even when closed.
+ continue;
}
- } else if (c != null && c.getValue() != ent.getValue()) {
- c.setValue(ent.getValue());
- c.setGranted(timestamp);
- ups.add(c);
- addLabelDelta(normName, c.getValue());
- categories.put(normName, c.getValue());
- update.putApproval(ent.getKey(), ent.getValue());
- } else if (c != null && c.getValue() == ent.getValue()) {
- current.put(normName, c);
- } else if (c == null) {
- c = new PatchSetApproval(new PatchSetApproval.Key(
- rsrc.getPatchSet().getId(),
- rsrc.getAccountId(),
- lt.getLabelId()),
- ent.getValue(), TimeUtil.nowTs());
- c.setGranted(timestamp);
- ups.add(c);
- addLabelDelta(normName, c.getValue());
- categories.put(normName, c.getValue());
- update.putApproval(ent.getKey(), ent.getValue());
+
+ PatchSetApproval c = current.remove(lt.getName());
+ String normName = lt.getName();
+ if (ent.getValue() == null || ent.getValue() == 0) {
+ // User requested delete of this label.
+ if (c != null) {
+ if (c.getValue() != 0) {
+ addLabelDelta(normName, (short) 0);
+ }
+ del.add(c);
+ update.putApproval(ent.getKey(), (short) 0);
+ }
+ } else if (c != null && c.getValue() != ent.getValue()) {
+ c.setValue(ent.getValue());
+ c.setGranted(ctx.getWhen());
+ ups.add(c);
+ addLabelDelta(normName, c.getValue());
+ categories.put(normName, c.getValue());
+ update.putApproval(ent.getKey(), ent.getValue());
+ } else if (c != null && c.getValue() == ent.getValue()) {
+ current.put(normName, c);
+ } else if (c == null) {
+ c = new PatchSetApproval(new PatchSetApproval.Key(
+ psId,
+ user.getAccountId(),
+ lt.getLabelId()),
+ ent.getValue(), TimeUtil.nowTs());
+ c.setGranted(ctx.getWhen());
+ ups.add(c);
+ addLabelDelta(normName, c.getValue());
+ categories.put(normName, c.getValue());
+ update.putApproval(ent.getKey(), ent.getValue());
+ }
+ }
+
+ forceCallerAsReviewer(ctx, current, ups, del);
+ ctx.getDb().patchSetApprovals().delete(del);
+ ctx.getDb().patchSetApprovals().upsert(ups);
+ return !del.isEmpty() || !ups.isEmpty();
+ }
+
+ private void forceCallerAsReviewer(ChangeContext ctx,
+ Map<String, PatchSetApproval> current, List<PatchSetApproval> ups,
+ List<PatchSetApproval> del) {
+ if (current.isEmpty() && ups.isEmpty()) {
+ // TODO Find another way to link reviewers to changes.
+ if (del.isEmpty()) {
+ // If no existing label is being set to 0, hack in the caller
+ // as a reviewer by picking the first server-wide LabelType.
+ PatchSetApproval c = new PatchSetApproval(new PatchSetApproval.Key(
+ psId,
+ user.getAccountId(),
+ ctx.getChangeControl().getLabelTypes().getLabelTypes().get(0)
+ .getLabelId()),
+ (short) 0, TimeUtil.nowTs());
+ c.setGranted(ctx.getWhen());
+ ups.add(c);
+ } else {
+ // Pick a random label that is about to be deleted and keep it.
+ Iterator<PatchSetApproval> i = del.iterator();
+ PatchSetApproval c = i.next();
+ c.setValue((short) 0);
+ c.setGranted(ctx.getWhen());
+ i.remove();
+ ups.add(c);
+ }
}
}
- forceCallerAsReviewer(rsrc, current, ups, del);
- db.get().patchSetApprovals().delete(del);
- db.get().patchSetApprovals().upsert(ups);
- return !del.isEmpty() || !ups.isEmpty();
- }
+ private Map<String, PatchSetApproval> scanLabels(ChangeContext ctx,
+ List<PatchSetApproval> del) throws OrmException {
+ LabelTypes labelTypes = ctx.getChangeControl().getLabelTypes();
+ Map<String, PatchSetApproval> current = Maps.newHashMap();
- private void forceCallerAsReviewer(RevisionResource rsrc,
- Map<String, PatchSetApproval> current, List<PatchSetApproval> ups,
- List<PatchSetApproval> del) {
- if (current.isEmpty() && ups.isEmpty()) {
- // TODO Find another way to link reviewers to changes.
- if (del.isEmpty()) {
- // If no existing label is being set to 0, hack in the caller
- // as a reviewer by picking the first server-wide LabelType.
- PatchSetApproval c = new PatchSetApproval(new PatchSetApproval.Key(
- rsrc.getPatchSet().getId(),
- rsrc.getAccountId(),
- rsrc.getControl().getLabelTypes().getLabelTypes().get(0)
- .getLabelId()),
- (short) 0, TimeUtil.nowTs());
- c.setGranted(timestamp);
- ups.add(c);
- } else {
- // Pick a random label that is about to be deleted and keep it.
- Iterator<PatchSetApproval> i = del.iterator();
- PatchSetApproval c = i.next();
- c.setValue((short) 0);
- c.setGranted(timestamp);
- i.remove();
- ups.add(c);
+ for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
+ ctx.getDb(), ctx.getChangeControl(), psId, user.getAccountId())) {
+ if (a.isSubmit()) {
+ continue;
+ }
+
+ LabelType lt = labelTypes.byLabel(a.getLabelId());
+ if (lt != null) {
+ current.put(lt.getName(), a);
+ } else {
+ del.add(a);
+ }
}
+ return current;
}
- }
- private Map<String, PatchSetApproval> scanLabels(RevisionResource rsrc,
- List<PatchSetApproval> del) throws OrmException {
- LabelTypes labelTypes = rsrc.getControl().getLabelTypes();
- Map<String, PatchSetApproval> current = Maps.newHashMap();
+ private boolean insertMessage(ChangeContext ctx)
+ throws OrmException {
+ String msg = Strings.nullToEmpty(in.message).trim();
- for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
- db.get(), rsrc.getControl(), rsrc.getPatchSet().getId(),
- rsrc.getAccountId())) {
- if (a.isSubmit()) {
- continue;
+ StringBuilder buf = new StringBuilder();
+ for (String d : labelDelta) {
+ buf.append(" ").append(d);
+ }
+ if (comments.size() == 1) {
+ buf.append("\n\n(1 comment)");
+ } else if (comments.size() > 1) {
+ buf.append(String.format("\n\n(%d comments)", comments.size()));
+ }
+ if (!msg.isEmpty()) {
+ buf.append("\n\n").append(msg);
+ }
+ if (buf.length() == 0) {
+ return false;
}
- LabelType lt = labelTypes.byLabel(a.getLabelId());
- if (lt != null) {
- current.put(lt.getName(), a);
- } else {
- del.add(a);
- }
- }
- return current;
- }
-
- private void addLabelDelta(String name, short value) {
- labelDelta.add(LabelVote.create(name, value).format());
- }
-
- private boolean insertMessage(RevisionResource rsrc, String msg,
- ChangeUpdate update) throws OrmException {
- msg = Strings.nullToEmpty(msg).trim();
-
- StringBuilder buf = new StringBuilder();
- for (String d : labelDelta) {
- buf.append(" ").append(d);
- }
- if (comments.size() == 1) {
- buf.append("\n\n(1 comment)");
- } else if (comments.size() > 1) {
- buf.append(String.format("\n\n(%d comments)", comments.size()));
- }
- if (!msg.isEmpty()) {
- buf.append("\n\n").append(msg);
- }
- if (buf.length() == 0) {
- return false;
+ message = new ChangeMessage(
+ new ChangeMessage.Key(
+ psId.getParentKey(), ChangeUtil.messageUUID(ctx.getDb())),
+ user.getAccountId(),
+ ctx.getWhen(),
+ psId);
+ message.setMessage(String.format(
+ "Patch Set %d:%s",
+ psId.get(),
+ buf.toString()));
+ cmUtil.addChangeMessage(ctx.getDb(), ctx.getChangeUpdate(), message);
+ return true;
}
- message = new ChangeMessage(
- new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db.get())),
- rsrc.getAccountId(),
- timestamp,
- rsrc.getPatchSet().getId());
- message.setMessage(String.format(
- "Patch Set %d:%s",
- rsrc.getPatchSet().getPatchSetId(),
- buf.toString()));
- cmUtil.addChangeMessage(db.get(), update, message);
- return true;
- }
-
- @Deprecated
- private void fireCommentAddedHook(RevisionResource rsrc) {
- IdentifiedUser user = (IdentifiedUser) rsrc.getControl().getCurrentUser();
- try {
- hooks.doCommentAddedHook(change,
- user.getAccount(),
- rsrc.getPatchSet(),
- message.getMessage(),
- categories, db.get());
- } catch (OrmException e) {
- log.warn("ChangeHook.doCommentAddedHook delivery failed", e);
+ private void addLabelDelta(String name, short value) {
+ labelDelta.add(LabelVote.create(name, value).format());
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index fc27010..3ab84ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -21,7 +21,6 @@
import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -36,7 +35,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountLoader;
@@ -84,7 +82,7 @@
private final AccountLoader.Factory accountLoaderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeUpdate.Factory updateFactory;
- private final Provider<CurrentUser> currentUser;
+ private final Provider<IdentifiedUser> user;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final Config cfg;
private final ChangeHooks hooks;
@@ -102,7 +100,7 @@
AccountLoader.Factory accountLoaderFactory,
Provider<ReviewDb> db,
ChangeUpdate.Factory updateFactory,
- Provider<CurrentUser> currentUser,
+ Provider<IdentifiedUser> user,
IdentifiedUser.GenericFactory identifiedUserFactory,
@GerritServerConfig Config cfg,
ChangeHooks hooks,
@@ -118,7 +116,7 @@
this.accountLoaderFactory = accountLoaderFactory;
this.dbProvider = db;
this.updateFactory = updateFactory;
- this.currentUser = currentUser;
+ this.user = user;
this.identifiedUserFactory = identifiedUserFactory;
this.cfg = cfg;
this.hooks = hooks;
@@ -130,7 +128,7 @@
@Override
public PostResult apply(ChangeResource rsrc, AddReviewerInput input)
throws AuthException, BadRequestException, UnprocessableEntityException,
- OrmException, EmailException, IOException {
+ OrmException, IOException {
if (input.reviewer == null) {
throw new BadRequestException("missing reviewer field");
}
@@ -175,7 +173,7 @@
ChangeControl control = rsrc.getControl();
Set<Account> members;
try {
- members = groupMembersFactory.create(control.getCurrentUser()).listAccounts(
+ members = groupMembersFactory.create(control.getUser()).listAccounts(
group.getGroupUUID(), control.getProject().getNameKey());
} catch (NoSuchGroupException e) {
throw new UnprocessableEntityException(e.getMessage());
@@ -275,16 +273,16 @@
//
// The user knows they added themselves, don't bother emailing them.
List<Account.Id> toMail = Lists.newArrayListWithCapacity(added.size());
- IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get();
+ Account.Id userId = user.get().getAccountId();
for (PatchSetApproval psa : added) {
- if (!psa.getAccountId().equals(identifiedUser.getAccountId())) {
+ if (!psa.getAccountId().equals(userId)) {
toMail.add(psa.getAccountId());
}
}
if (!toMail.isEmpty()) {
try {
AddReviewerSender cm = addReviewerSenderFactory.create(change.getId());
- cm.setFrom(identifiedUser.getAccountId());
+ cm.setFrom(userId);
cm.addReviewers(toMail);
cm.send();
} catch (Exception err) {
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
index b88931e..e137ac4 100644
--- 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
@@ -29,7 +29,8 @@
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.NoSuchChangeException;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -83,8 +84,8 @@
@Override
public Response<?> apply(ChangeResource rsrc, Publish.Input in)
- throws AuthException, ResourceConflictException, NoSuchChangeException,
- IOException, OrmException {
+ throws NoSuchProjectException, IOException, OrmException,
+ RestApiException, UpdateException {
Capable r =
rsrc.getControl().getProjectControl().canPushToAtLeastOneRef();
if (r != Capable.OK) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index dd9a44d..622c99d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -14,112 +14,111 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.restapi.AuthException;
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.webui.UiAction;
+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.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.PublishDraftPatchSet.Input;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.mail.PatchSetNotificationSender;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
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.ObjectId;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
@Singleton
public class PublishDraftPatchSet implements RestModifyView<RevisionResource, Input>,
UiAction<RevisionResource> {
+ private static final Logger log =
+ LoggerFactory.getLogger(PublishDraftPatchSet.class);
+
public static class Input {
}
private final Provider<ReviewDb> dbProvider;
- private final ChangeUpdate.Factory updateFactory;
- private final PatchSetNotificationSender sender;
+ private final BatchUpdate.Factory updateFactory;
private final ChangeHooks hooks;
- private final ChangeIndexer indexer;
+ private final ApprovalsUtil approvalsUtil;
+ private final AccountResolver accountResolver;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+ private final CreateChangeSender.Factory createChangeSenderFactory;
+ private final ReplacePatchSetSender.Factory replacePatchSetFactory;
@Inject
- public PublishDraftPatchSet(Provider<ReviewDb> dbProvider,
- ChangeUpdate.Factory updateFactory,
- PatchSetNotificationSender sender,
+ public PublishDraftPatchSet(
+ Provider<ReviewDb> dbProvider,
+ BatchUpdate.Factory updateFactory,
ChangeHooks hooks,
- ChangeIndexer indexer) {
+ ApprovalsUtil approvalsUtil,
+ AccountResolver accountResolver,
+ PatchSetInfoFactory patchSetInfoFactory,
+ CreateChangeSender.Factory createChangeSenderFactory,
+ ReplacePatchSetSender.Factory replacePatchSetFactory) {
this.dbProvider = dbProvider;
this.updateFactory = updateFactory;
- this.sender = sender;
this.hooks = hooks;
- this.indexer = indexer;
+ this.approvalsUtil = approvalsUtil;
+ this.accountResolver = accountResolver;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.createChangeSenderFactory = createChangeSenderFactory;
+ this.replacePatchSetFactory = replacePatchSetFactory;
}
@Override
public Response<?> apply(RevisionResource rsrc, Input input)
- throws AuthException, ResourceNotFoundException,
- ResourceConflictException, OrmException, IOException {
- if (!rsrc.getPatchSet().isDraft()) {
- throw new ResourceConflictException("Patch set is not a draft");
+ throws RestApiException, UpdateException {
+ return apply(rsrc.getUser(), rsrc.getChange(), rsrc.getPatchSet().getId(),
+ rsrc.getPatchSet());
+ }
+
+ private Response<?> apply(CurrentUser u, Change c, PatchSet.Id psId,
+ PatchSet ps) throws RestApiException, UpdateException {
+ try (BatchUpdate bu = updateFactory.create(
+ dbProvider.get(), c.getProject(), u, TimeUtil.nowTs())) {
+ bu.addOp(c.getId(), new Op(psId, ps));
+ bu.execute();
}
-
- if (!rsrc.getControl().canPublish(dbProvider.get())) {
- throw new AuthException("Cannot publish this draft patch set");
- }
-
- PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
- Change updatedChange = updateDraftChange(rsrc);
- ChangeUpdate update = updateFactory.create(rsrc.getControl(),
- updatedChange.getLastUpdatedOn());
-
- if (!updatedPatchSet.isDraft()
- || updatedChange.getStatus() == Change.Status.NEW) {
- indexer.index(dbProvider.get(), updatedChange);
- sender.send(rsrc.getNotes(), update,
- rsrc.getChange().getStatus() == Change.Status.DRAFT,
- rsrc.getUser(), updatedChange, updatedPatchSet,
- rsrc.getControl().getLabelTypes());
- hooks.doDraftPublishedHook(updatedChange, updatedPatchSet,
- dbProvider.get());
- }
-
return Response.none();
}
- private Change updateDraftChange(RevisionResource rsrc) throws OrmException {
- return dbProvider.get().changes()
- .atomicUpdate(rsrc.getChange().getId(),
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus() == Change.Status.DRAFT) {
- change.setStatus(Change.Status.NEW);
- ChangeUtil.updated(change);
- }
- return change;
- }
- });
- }
-
- private PatchSet updateDraftPatchSet(RevisionResource rsrc) throws OrmException {
- return dbProvider.get().patchSets()
- .atomicUpdate(rsrc.getPatchSet().getId(),
- new AtomicUpdate<PatchSet>() {
- @Override
- public PatchSet update(PatchSet patchset) {
- patchset.setDraft(false);
- return patchset;
- }
- });
- }
-
@Override
public UiAction.Description getDescription(RevisionResource rsrc) {
try {
@@ -135,28 +134,140 @@
public static class CurrentRevision implements
RestModifyView<ChangeResource, Input> {
- private final Provider<ReviewDb> dbProvider;
private final PublishDraftPatchSet publish;
@Inject
- CurrentRevision(Provider<ReviewDb> dbProvider,
- PublishDraftPatchSet publish) {
- this.dbProvider = dbProvider;
+ CurrentRevision(PublishDraftPatchSet publish) {
this.publish = publish;
}
@Override
public Response<?> apply(ChangeResource rsrc, Input input)
- throws AuthException, ResourceConflictException,
- ResourceNotFoundException, IOException, OrmException {
- PatchSet ps = dbProvider.get().patchSets()
- .get(rsrc.getChange().currentPatchSetId());
+ throws RestApiException, UpdateException {
+ return publish.apply(rsrc.getControl().getUser(), rsrc.getChange(),
+ rsrc.getChange().currentPatchSetId(), null);
+ }
+ }
+
+ private class Op extends BatchUpdate.Op {
+ private final PatchSet.Id psId;
+
+ private PatchSet patchSet;
+ private Change change;
+ private boolean wasDraftChange;
+ private RevCommit commit;
+ private PatchSetInfo patchSetInfo;
+ private MailRecipients recipients;
+
+ private Op(PatchSet.Id psId, @Nullable PatchSet patchSet) {
+ this.psId = psId;
+ this.patchSet = patchSet;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx)
+ throws RestApiException, OrmException, IOException {
+ PatchSet ps = patchSet;
if (ps == null) {
- throw new ResourceConflictException("current revision is missing");
- } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
- throw new AuthException("current revision not accessible");
+ // Don't save in patchSet, since we're not in a transaction. Here we
+ // just need the revision, which is immutable.
+ ps = ctx.getDb().patchSets().get(psId);
+ if (ps == null) {
+ throw new ResourceNotFoundException(psId.toString());
+ }
}
- return publish.apply(new RevisionResource(rsrc, ps), input);
+ commit = ctx.getRevWalk().parseCommit(
+ ObjectId.fromString(ps.getRevision().get()));
+ patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
+ }
+
+ @Override
+ public void updateChange(ChangeContext ctx)
+ throws RestApiException, OrmException {
+ if (!ctx.getChangeControl().canPublish(ctx.getDb())) {
+ throw new AuthException("Cannot publish this draft patch set");
+ }
+ saveChange(ctx);
+ savePatchSet(ctx);
+ addReviewers(ctx);
+ }
+
+ private void saveChange(ChangeContext ctx) throws OrmException {
+ change = ctx.getChange();
+ wasDraftChange = change.getStatus() == Change.Status.DRAFT;
+ if (wasDraftChange) {
+ change.setStatus(Change.Status.NEW);
+ ChangeUtil.updated(change);
+ ctx.getDb().changes().update(Collections.singleton(change));
+ }
+ }
+
+ private void savePatchSet(ChangeContext ctx)
+ throws RestApiException, OrmException {
+ patchSet = ctx.getDb().patchSets().get(psId);
+ if (!patchSet.isDraft()) {
+ throw new ResourceConflictException("Patch set is not a draft");
+ }
+ patchSet.setDraft(false);
+ }
+
+ private void addReviewers(ChangeContext ctx) throws OrmException {
+ LabelTypes labelTypes = ctx.getChangeControl().getLabelTypes();
+ Collection<Account.Id> oldReviewers = approvalsUtil.getReviewers(
+ ctx.getDb(), ctx.getChangeNotes()).values();
+ List<FooterLine> footerLines = commit.getFooterLines();
+ recipients =
+ getRecipientsFromFooters(accountResolver, patchSet, footerLines);
+ recipients.remove(ctx.getUser().getAccountId());
+ approvalsUtil.addReviewers(ctx.getDb(), ctx.getChangeUpdate(), labelTypes,
+ change, patchSet, patchSetInfo, recipients.getReviewers(),
+ oldReviewers);
+ }
+
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ hooks.doDraftPublishedHook(change, patchSet, ctx.getDb());
+ if (patchSet.isDraft() && change.getStatus() == Change.Status.DRAFT) {
+ // Skip emails if the patch set is still a draft.
+ return;
+ }
+ try {
+ if (wasDraftChange) {
+ sendCreateChange(ctx);
+ } else {
+ sendReplacePatchSet(ctx);
+ }
+ } catch (EmailException | OrmException e) {
+ log.error("Cannot send email for publishing draft " + psId, e);
+ }
+ }
+
+ private void sendCreateChange(Context ctx) throws EmailException {
+ CreateChangeSender cm =
+ createChangeSenderFactory.create(change.getId());
+ cm.setFrom(ctx.getUser().getAccountId());
+ cm.setPatchSet(patchSet, patchSetInfo);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
+ }
+
+ private void sendReplacePatchSet(Context ctx)
+ throws EmailException, OrmException {
+ Account.Id accountId = ctx.getUser().getAccountId();
+ ChangeMessage msg =
+ new ChangeMessage(new ChangeMessage.Key(change.getId(),
+ ChangeUtil.messageUUID(ctx.getDb())), accountId,
+ ctx.getWhen(), psId);
+ msg.setMessage("Uploaded patch set " + psId.get() + ".");
+ ReplacePatchSetSender cm =
+ replacePatchSetFactory.create(change.getId());
+ cm.setFrom(accountId);
+ cm.setPatchSet(patchSet, patchSetInfo);
+ cm.setChangeMessage(msg);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
}
}
}
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 d171785..1061d4b 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
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
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.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
@@ -29,25 +30,25 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.PutTopic.Input;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gwtorm.server.AtomicUpdate;
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
public class PutTopic implements RestModifyView<ChangeResource, Input>,
UiAction<ChangeResource> {
private final Provider<ReviewDb> dbProvider;
- private final ChangeIndexer indexer;
private final ChangeHooks hooks;
- private final ChangeUpdate.Factory updateFactory;
private final ChangeMessagesUtil cmUtil;
+ private final BatchUpdate.Factory batchUpdateFactory;
public static class Input {
@DefaultInput
@@ -55,80 +56,86 @@
}
@Inject
- PutTopic(Provider<ReviewDb> dbProvider, ChangeIndexer indexer,
- ChangeHooks hooks, ChangeUpdate.Factory updateFactory,
- ChangeMessagesUtil cmUtil) {
+ PutTopic(Provider<ReviewDb> dbProvider,
+ ChangeHooks hooks,
+ ChangeMessagesUtil cmUtil,
+ BatchUpdate.Factory batchUpdateFactory) {
this.dbProvider = dbProvider;
- this.indexer = indexer;
this.hooks = hooks;
- this.updateFactory = updateFactory;
this.cmUtil = cmUtil;
+ this.batchUpdateFactory = batchUpdateFactory;
}
@Override
public Response<String> apply(ChangeResource req, Input input)
- throws AuthException, OrmException, IOException {
- if (input == null) {
- input = new Input();
- }
-
- ChangeControl control = req.getControl();
- Change change = req.getChange();
- if (!control.canEditTopicName()) {
+ throws UpdateException, RestApiException {
+ ChangeControl ctl = req.getControl();
+ if (!ctl.canEditTopicName()) {
throw new AuthException("changing topic not permitted");
}
- ReviewDb db = dbProvider.get();
- final String newTopicName = Strings.nullToEmpty(input.topic);
- String oldTopicName = Strings.nullToEmpty(change.getTopic());
- if (!oldTopicName.equals(newTopicName)) {
+ Op op = new Op(ctl, input != null ? input : new Input());
+ try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
+ req.getChange().getProject(), ctl.getUser(), TimeUtil.nowTs())) {
+ u.addOp(req.getChange().getId(), op);
+ u.execute();
+ }
+ return Strings.isNullOrEmpty(op.newTopicName)
+ ? Response.<String> none()
+ : Response.ok(op.newTopicName);
+ }
+
+ private class Op extends BatchUpdate.Op {
+ private final Input input;
+ private final IdentifiedUser caller;
+
+ private Change change;
+ private String oldTopicName;
+ private String newTopicName;
+
+ public Op(ChangeControl ctl, Input input) {
+ this.input = input;
+ this.caller = ctl.getUser().asIdentifiedUser();
+ }
+
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException {
+ change = ctx.getChange();
+ String newTopicName = Strings.nullToEmpty(input.topic);
+ String oldTopicName = Strings.nullToEmpty(change.getTopic());
+ if (oldTopicName.equals(newTopicName)) {
+ return;
+ }
String summary;
if (oldTopicName.isEmpty()) {
summary = "Topic set to " + newTopicName;
} else if (newTopicName.isEmpty()) {
summary = "Topic " + oldTopicName + " removed";
} else {
- summary = String.format(
- "Topic changed from %s to %s",
+ summary = String.format("Topic changed from %s to %s",
oldTopicName, newTopicName);
}
+ change.setTopic(Strings.emptyToNull(newTopicName));
+ ChangeUtil.updated(change);
+ ctx.getDb().changes().update(Collections.singleton(change));
- IdentifiedUser currentUser = ((IdentifiedUser) control.getCurrentUser());
ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db)),
- currentUser.getAccountId(), TimeUtil.nowTs(),
+ new ChangeMessage.Key(
+ change.getId(),
+ ChangeUtil.messageUUID(ctx.getDb())),
+ caller.getAccountId(), ctx.getWhen(),
change.currentPatchSetId());
cmsg.setMessage(summary);
- ChangeUpdate update;
-
- db.changes().beginTransaction(change.getId());
- try {
- change = db.changes().atomicUpdate(change.getId(),
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- change.setTopic(Strings.emptyToNull(newTopicName));
- ChangeUtil.updated(change);
- return change;
- }
- });
-
- //TODO(yyonas): atomic update was not propagated
- update = updateFactory.create(control);
- cmUtil.addChangeMessage(db, update, cmsg);
-
- db.commit();
- } finally {
- db.rollback();
- }
- update.commit();
- indexer.index(db, change);
- hooks.doTopicChangedHook(change, currentUser.getAccount(),
- oldTopicName, db);
+ cmUtil.addChangeMessage(ctx.getDb(), ctx.getChangeUpdate(), cmsg);
}
- return Strings.isNullOrEmpty(newTopicName)
- ? Response.<String>none()
- : Response.ok(newTopicName);
+
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ if (change != null) {
+ hooks.doTopicChangedHook(change, caller.getAccount(),
+ oldTopicName, ctx.getDb());
+ }
+ }
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index ece6752..60f285f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -15,31 +15,34 @@
package com.google.gerrit.server.change;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.project.ChangeControl;
-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.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -57,30 +60,36 @@
ListChangesOption.CURRENT_REVISION,
ListChangesOption.CURRENT_COMMIT);
+ private final BatchUpdate.Factory updateFactory;
private final GitRepositoryManager repoManager;
- private final Provider<RebaseChange> rebaseChange;
+ private final RebaseChangeOp.Factory rebaseFactory;
private final ChangeJson.Factory json;
private final Provider<ReviewDb> dbProvider;
@Inject
- public Rebase(GitRepositoryManager repoManager,
- Provider<RebaseChange> rebaseChange,
+ public Rebase(BatchUpdate.Factory updateFactory,
+ GitRepositoryManager repoManager,
+ RebaseChangeOp.Factory rebaseFactory,
ChangeJson.Factory json,
Provider<ReviewDb> dbProvider) {
+ this.updateFactory = updateFactory;
this.repoManager = repoManager;
- this.rebaseChange = rebaseChange;
+ this.rebaseFactory = rebaseFactory;
this.json = json;
this.dbProvider = dbProvider;
}
@Override
public ChangeInfo apply(RevisionResource rsrc, RebaseInput input)
- throws AuthException, ResourceNotFoundException,
- ResourceConflictException, EmailException, OrmException, IOException {
+ throws EmailException, OrmException, UpdateException, RestApiException,
+ IOException {
ChangeControl control = rsrc.getControl();
Change change = rsrc.getChange();
try (Repository repo = repoManager.openRepository(change.getProject());
- RevWalk rw = new RevWalk(repo)) {
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter oi = repo.newObjectInserter();
+ BatchUpdate bu = updateFactory.create(dbProvider.get(),
+ change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
if (!control.canRebase()) {
throw new AuthException("rebase not permitted");
} else if (!change.getStatus().isOpen()) {
@@ -90,13 +99,15 @@
throw new ResourceConflictException(
"cannot rebase merge commits or commit with no ancestor");
}
- rebaseChange.get().rebase(repo, rw, rsrc, findBaseRev(rw, rsrc, input));
- } catch (InvalidChangeOperationException e) {
- throw new ResourceConflictException(e.getMessage());
- } catch (NoSuchChangeException e) {
- throw new ResourceNotFoundException(change.getId().toString());
+ bu.setRepository(repo, rw, oi);
+ bu.addOp(change.getId(), rebaseFactory.create(
+ control, rsrc.getPatchSet(),
+ findBaseRev(rw, rsrc, input))
+ .setForceContentMerge(true)
+ .setRunHooks(true)
+ .setValidatePolicy(CommitValidators.Policy.GERRIT));
+ bu.execute();
}
-
return json.create(OPTIONS).format(change.getId());
}
@@ -196,29 +207,30 @@
@Override
public UiAction.Description getDescription(RevisionResource resource) {
- Project.NameKey project = resource.getChange().getProject();
+ PatchSet patchSet = resource.getPatchSet();
+ Branch.NameKey dest = resource.getChange().getDest();
boolean visible = resource.getChange().getStatus().isOpen()
&& resource.isCurrent()
&& resource.getControl().canRebase();
+ boolean enabled = true;
+
if (visible) {
- try (Repository repo = repoManager.openRepository(project);
+ try (Repository repo = repoManager.openRepository(dest.getParentKey());
RevWalk rw = new RevWalk(repo)) {
visible = hasOneParent(rw, resource.getPatchSet());
+ enabled =
+ RebaseUtil.canRebase(patchSet, dest, repo, rw, dbProvider.get());
} catch (IOException e) {
- log.error("Failed to get ancestors of patch set "
- + resource.getPatchSet().getId(), e);
+ log.error("Failed to check if patch set can be rebased: "
+ + resource.getPatchSet(), e);
visible = false;
}
}
UiAction.Description descr = new UiAction.Description()
.setLabel("Rebase")
.setTitle("Rebase onto tip of branch or parent change")
- .setVisible(visible);
- if (descr.isVisible()) {
- // Disable the rebase button in the RebaseDialog if
- // the change cannot be rebased.
- descr.setEnabled(rebaseChange.get().canRebase(resource));
- }
+ .setVisible(visible)
+ .setEnabled(enabled);
return descr;
}
@@ -233,8 +245,8 @@
@Override
public ChangeInfo apply(ChangeResource rsrc, RebaseInput input)
- throws AuthException, ResourceNotFoundException,
- ResourceConflictException, EmailException, OrmException, IOException {
+ throws EmailException, OrmException, UpdateException, RestApiException,
+ IOException {
PatchSet ps =
rebase.dbProvider.get().patchSets()
.get(rsrc.getChange().currentPatchSetId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
deleted file mode 100644
index 030aa92..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
+++ /dev/null
@@ -1,356 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.change;
-
-import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-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.ChangeUtil;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeConflictException;
-import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.project.ChangeControl;
-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.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.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.ThreeWayMerger;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.TimeZone;
-
-@Singleton
-public class RebaseChange {
- private static final Logger log = LoggerFactory.getLogger(RebaseChange.class);
-
- private final ChangeControl.GenericFactory changeControlFactory;
- private final Provider<ReviewDb> db;
- private final GitRepositoryManager gitManager;
- private final TimeZone serverTimeZone;
- private final MergeUtil.Factory mergeUtilFactory;
- private final PatchSetInserter.Factory patchSetInserterFactory;
-
- @Inject
- RebaseChange(ChangeControl.GenericFactory changeControlFactory,
- Provider<ReviewDb> db,
- @GerritPersonIdent PersonIdent myIdent,
- GitRepositoryManager gitManager,
- MergeUtil.Factory mergeUtilFactory,
- PatchSetInserter.Factory patchSetInserterFactory) {
- this.changeControlFactory = changeControlFactory;
- this.db = db;
- this.gitManager = gitManager;
- this.serverTimeZone = myIdent.getTimeZone();
- this.mergeUtilFactory = mergeUtilFactory;
- this.patchSetInserterFactory = patchSetInserterFactory;
- }
-
- /**
- * Rebase the change of the given patch set.
- * <p>
- * If the patch set has no dependency to an open change, then the change is
- * rebased on the tip of the destination branch.
- * <p>
- * If the patch set depends on an open change, it is rebased on the latest
- * patch set of this change.
- * <p>
- * The rebased commit is added as new patch set to the change.
- * <p>
- * E-mail notification and triggering of hooks happens for the creation of the
- * new patch set.
- *
- * @param git the repository.
- * @param rw the RevWalk.
- * @param rsrc revision to rebase.
- * @param newBaseRev the commit that should be the new base.
- * @throws NoSuchChangeException if the change to which the patch set belongs
- * does not exist or is not visible to the user.
- * @throws EmailException if sending the e-mail to notify about the new patch
- * set fails.
- * @throws OrmException if accessing the database fails.
- * @throws IOException if accessing the repository fails.
- * @throws InvalidChangeOperationException if rebase is not possible or not
- * allowed.
- */
- public void rebase(Repository git, RevWalk rw, RevisionResource rsrc,
- String newBaseRev) throws NoSuchChangeException, EmailException,
- OrmException, IOException, ResourceConflictException,
- InvalidChangeOperationException {
- Change change = rsrc.getChange();
- PatchSet patchSet = rsrc.getPatchSet();
- IdentifiedUser uploader = (IdentifiedUser) rsrc.getControl().getCurrentUser();
-
- try (ObjectInserter inserter = git.newObjectInserter()) {
- String baseRev = newBaseRev;
- if (baseRev == null) {
- baseRev = findBaseRevision(patchSet, change.getDest(), git, rw);
- }
-
- ObjectId baseObjectId = git.resolve(baseRev);
- if (baseObjectId == null) {
- throw new InvalidChangeOperationException(
- "Cannot rebase: Failed to resolve baseRev: " + baseRev);
- }
-
- RevCommit baseCommit = rw.parseCommit(baseObjectId);
- PersonIdent committerIdent =
- uploader.newCommitterIdent(TimeUtil.nowTs(), serverTimeZone);
-
- rebase(git, rw, inserter, change, patchSet.getId(),
- uploader, baseCommit, mergeUtilFactory.create(
- rsrc.getControl().getProjectControl().getProjectState(), true),
- committerIdent, true, ValidatePolicy.GERRIT);
- } catch (MergeConflictException e) {
- throw new ResourceConflictException(e.getMessage());
- }
- }
-
- /**
- * Find the commit onto which a patch set should be rebased.
- * <p>
- * This is defined as the latest patch set of the change corresponding to
- * this commit's parent, or the destination branch tip in the case where the
- * parent's change is merged.
- *
- * @param patchSet patch set for which the new base commit should be found.
- * @param destBranch the destination branch.
- * @param git the repository.
- * @param rw the RevWalk.
- * @return the commit onto which the patch set should be rebased.
- * @throws InvalidChangeOperationException if rebase is not possible or not
- * allowed.
- * @throws IOException if accessing the repository fails.
- * @throws OrmException if accessing the database fails.
- */
- private String findBaseRevision(PatchSet patchSet,
- Branch.NameKey destBranch, Repository git, RevWalk rw)
- throws InvalidChangeOperationException, IOException, OrmException {
- String baseRev = null;
- RevCommit commit = rw.parseCommit(
- ObjectId.fromString(patchSet.getRevision().get()));
-
- if (commit.getParentCount() > 1) {
- throw new InvalidChangeOperationException(
- "Cannot rebase a change with multiple parents.");
- } else if (commit.getParentCount() == 0) {
- throw new InvalidChangeOperationException(
- "Cannot rebase a change without any parents"
- + " (is this the initial commit?).");
- }
-
- RevId parentRev = new RevId(commit.getParent(0).name());
-
- for (PatchSet depPatchSet : db.get().patchSets().byRevision(parentRev)) {
- Change.Id depChangeId = depPatchSet.getId().getParentKey();
- Change depChange = db.get().changes().get(depChangeId);
- if (!depChange.getDest().equals(destBranch)) {
- continue;
- }
-
- if (depChange.getStatus() == Status.ABANDONED) {
- throw new InvalidChangeOperationException(
- "Cannot rebase a change with an abandoned parent: "
- + depChange.getKey());
- }
-
- if (depChange.getStatus().isOpen()) {
- if (depPatchSet.getId().equals(depChange.currentPatchSetId())) {
- throw new InvalidChangeOperationException(
- "Change is already based on the latest patch set of the"
- + " dependent change.");
- }
- PatchSet latestDepPatchSet =
- db.get().patchSets().get(depChange.currentPatchSetId());
- baseRev = latestDepPatchSet.getRevision().get();
- }
- break;
- }
-
- if (baseRev == null) {
- // We are dependent on a merged PatchSet or have no PatchSet
- // dependencies at all.
- Ref destRef = git.getRefDatabase().exactRef(destBranch.get());
- if (destRef == null) {
- throw new InvalidChangeOperationException(
- "The destination branch does not exist: " + destBranch.get());
- }
- baseRev = destRef.getObjectId().getName();
- if (baseRev.equals(parentRev.get())) {
- throw new InvalidChangeOperationException(
- "Change is already up to date.");
- }
- }
- return baseRev;
- }
-
- /**
- * Rebase the change of the given patch set on the given base commit.
- * <p>
- * The rebased commit is added as new patch set to the change.
- * <p>
- * E-mail notification and triggering of hooks is only done for the creation
- * of the new patch set if {@code sendEmail} and {@code runHooks} are true,
- * respectively.
- *
- * @param git the repository.
- * @param inserter the object inserter.
- * @param change the change to rebase.
- * @param patchSetId the patch set ID to rebase.
- * @param uploader the user that creates the rebased patch set.
- * @param baseCommit the commit that should be the new base.
- * @param mergeUtil merge utilities for the destination project.
- * @param committerIdent the committer's identity.
- * @param runHooks if hooks should be run for the new patch set.
- * @param validate if commit validation should be run for the new patch set.
- * @param rw the RevWalk.
- * @return the new patch set, which is based on the given base commit.
- * @throws NoSuchChangeException if the change to which the patch set belongs
- * does not exist or is not visible to the user.
- * @throws OrmException if accessing the database fails.
- * @throws IOException if rebase is not possible.
- * @throws InvalidChangeOperationException if rebase is not possible or not
- * allowed.
- */
- public PatchSet rebase(Repository git, RevWalk rw,
- ObjectInserter inserter, Change change, PatchSet.Id patchSetId,
- IdentifiedUser uploader, RevCommit baseCommit, MergeUtil mergeUtil,
- PersonIdent committerIdent, boolean runHooks, ValidatePolicy validate)
- throws NoSuchChangeException, OrmException, IOException,
- InvalidChangeOperationException, MergeConflictException {
- if (!change.currentPatchSetId().equals(patchSetId)) {
- throw new InvalidChangeOperationException("patch set is not current");
- }
- PatchSet originalPatchSet = db.get().patchSets().get(patchSetId);
-
- RevCommit rebasedCommit;
- ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
- ObjectId newId = rebaseCommit(git, inserter, rw.parseCommit(oldId),
- baseCommit, mergeUtil, committerIdent);
-
- rebasedCommit = rw.parseCommit(newId);
-
- ChangeControl changeControl =
- changeControlFactory.validateFor(change, uploader);
-
- PatchSetInserter patchSetInserter = patchSetInserterFactory
- .create(git, rw, changeControl, rebasedCommit)
- .setValidatePolicy(validate)
- .setDraft(originalPatchSet.isDraft())
- .setUploader(uploader.getAccountId())
- .setSendMail(false)
- .setRunHooks(runHooks);
-
- PatchSet.Id newPatchSetId = patchSetInserter.getPatchSetId();
- ChangeMessage cmsg = new ChangeMessage(
- new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(db.get())), uploader.getAccountId(),
- TimeUtil.nowTs(), patchSetId);
-
- cmsg.setMessage("Patch Set " + newPatchSetId.get()
- + ": Patch Set " + patchSetId.get() + " was rebased");
-
- Change newChange = patchSetInserter
- .setMessage(cmsg)
- .insert();
-
- return db.get().patchSets().get(newChange.currentPatchSetId());
- }
-
- /**
- * Rebase a commit.
- *
- * @param git repository to find commits in.
- * @param inserter inserter to handle new trees and blobs.
- * @param original the commit to rebase.
- * @param base base to rebase against.
- * @param mergeUtil merge utilities for the destination project.
- * @param committerIdent committer identity.
- * @return the id of the rebased commit.
- * @throws MergeConflictException the rebase failed due to a merge conflict.
- * @throws IOException the merge failed for another reason.
- */
- private ObjectId rebaseCommit(Repository git, ObjectInserter inserter,
- RevCommit original, RevCommit base, MergeUtil mergeUtil,
- PersonIdent committerIdent) throws MergeConflictException, IOException,
- InvalidChangeOperationException {
- RevCommit parentCommit = original.getParent(0);
-
- if (base.equals(parentCommit)) {
- throw new InvalidChangeOperationException(
- "Change is already up to date.");
- }
-
- ThreeWayMerger merger = mergeUtil.newThreeWayMerger(git, inserter);
- merger.setBase(parentCommit);
- merger.merge(original, base);
-
- if (merger.getResultTreeId() == null) {
- throw new MergeConflictException(
- "The change could not be rebased due to a conflict during merge.");
- }
-
- CommitBuilder cb = new CommitBuilder();
- cb.setTreeId(merger.getResultTreeId());
- cb.setParentId(base);
- cb.setAuthor(original.getAuthorIdent());
- cb.setMessage(original.getFullMessage());
- cb.setCommitter(committerIdent);
- ObjectId objectId = inserter.insert(cb);
- inserter.flush();
- return objectId;
- }
-
- public boolean canRebase(RevisionResource r) {
- return canRebase(r.getPatchSet(), r.getChange().getDest());
- }
-
- private boolean canRebase(PatchSet patchSet, Branch.NameKey dest) {
- try (Repository git = gitManager.openRepository(dest.getParentKey());
- RevWalk rw = new RevWalk(git)) {
- findBaseRevision(patchSet, dest, git, rw);
- return true;
- } catch (InvalidChangeOperationException e) {
- return false;
- } catch (OrmException | IOException e) {
- log.warn(String.format(
- "Error checking if patch set %s on %s can be rebased",
- patchSet.getId(), dest), e);
- return false;
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
new file mode 100644
index 0000000..6769a5c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -0,0 +1,208 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
+import com.google.gerrit.server.git.MergeConflictException;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+public class RebaseChangeOp extends BatchUpdate.Op {
+ public interface Factory {
+ RebaseChangeOp create(ChangeControl ctl, PatchSet originalPatchSet,
+ @Nullable String baseCommitish);
+ }
+
+ private final PatchSetInserter.Factory patchSetInserterFactory;
+ private final MergeUtil.Factory mergeUtilFactory;
+
+ private final ChangeControl ctl;
+ private final PatchSet originalPatchSet;
+
+ private String baseCommitish;
+ private PersonIdent committerIdent;
+ private boolean runHooks = true;
+ private CommitValidators.Policy validate;
+ private boolean forceContentMerge;
+
+ private RevCommit rebasedCommit;
+ private PatchSet.Id rebasedPatchSetId;
+ private PatchSetInserter patchSetInserter;
+ private PatchSet rebasedPatchSet;
+
+ @AssistedInject
+ RebaseChangeOp(
+ PatchSetInserter.Factory patchSetInserterFactory,
+ MergeUtil.Factory mergeUtilFactory,
+ @Assisted ChangeControl ctl,
+ @Assisted PatchSet originalPatchSet,
+ @Assisted @Nullable String baseCommitish) {
+ this.patchSetInserterFactory = patchSetInserterFactory;
+ this.mergeUtilFactory = mergeUtilFactory;
+ this.ctl = ctl;
+ this.originalPatchSet = originalPatchSet;
+ this.baseCommitish = baseCommitish;
+ }
+
+ public RebaseChangeOp setCommitterIdent(PersonIdent committerIdent) {
+ this.committerIdent = committerIdent;
+ return this;
+ }
+
+ public RebaseChangeOp setValidatePolicy(CommitValidators.Policy validate) {
+ this.validate = validate;
+ return this;
+ }
+
+ public RebaseChangeOp setRunHooks(boolean runHooks) {
+ this.runHooks = runHooks;
+ return this;
+ }
+
+ public RebaseChangeOp setForceContentMerge(boolean forceContentMerge) {
+ this.forceContentMerge = forceContentMerge;
+ return this;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx) throws MergeConflictException,
+ InvalidChangeOperationException, RestApiException, IOException,
+ OrmException {
+ // Ok that originalPatchSet was not read in a transaction, since we just
+ // need its revision.
+ RevId oldRev = originalPatchSet.getRevision();
+
+ RevWalk rw = ctx.getRevWalk();
+ RevCommit original = rw.parseCommit(ObjectId.fromString(oldRev.get()));
+ rw.parseBody(original);
+ RevCommit baseCommit;
+ if (baseCommitish != null) {
+ baseCommit = rw.parseCommit(ctx.getRepository().resolve(baseCommitish));
+ } else {
+ baseCommit = rw.parseCommit(RebaseUtil.findBaseRevision(
+ originalPatchSet, ctl.getChange().getDest(),
+ ctx.getRepository(), ctx.getRevWalk(), ctx.getDb()));
+ }
+
+ ObjectId newId = rebaseCommit(ctx, original, baseCommit);
+ rebasedCommit = rw.parseCommit(newId);
+
+ rebasedPatchSetId = ChangeUtil.nextPatchSetId(
+ ctx.getRepository(), ctl.getChange().currentPatchSetId());
+ patchSetInserter = patchSetInserterFactory
+ .create(ctl.getRefControl(), rebasedPatchSetId, rebasedCommit)
+ .setDraft(originalPatchSet.isDraft())
+ .setUploader(ctx.getUser().getAccountId())
+ .setSendMail(false)
+ .setRunHooks(runHooks)
+ .setMessage(
+ "Patch Set " + rebasedPatchSetId.get()
+ + ": Patch Set " + originalPatchSet.getId().get() + " was rebased");
+ if (validate != null) {
+ patchSetInserter.setValidatePolicy(validate);
+ }
+ patchSetInserter.updateRepo(ctx);
+ }
+
+ @Override
+ public void updateChange(ChangeContext ctx)
+ throws OrmException, InvalidChangeOperationException {
+ patchSetInserter.updateChange(ctx);
+ rebasedPatchSet = patchSetInserter.getPatchSet();
+ }
+
+ public PatchSet getPatchSet() {
+ checkState(rebasedPatchSet != null,
+ "getPatchSet() only valid after executing update");
+ return rebasedPatchSet;
+ }
+
+ private MergeUtil newMergeUtil() {
+ ProjectState project = ctl.getProjectControl().getProjectState();
+ return forceContentMerge
+ ? mergeUtilFactory.create(project, true)
+ : mergeUtilFactory.create(project);
+ }
+
+ /**
+ * Rebase a commit.
+ *
+ * @param ctx repo context.
+ * @param original the commit to rebase.
+ * @param base base to rebase against.
+ * @return the rebased commit.
+ * @throws MergeConflictException the rebase failed due to a merge conflict.
+ * @throws IOException the merge failed for another reason.
+ */
+ private RevCommit rebaseCommit(RepoContext ctx, RevCommit original,
+ ObjectId base) throws ResourceConflictException, MergeConflictException,
+ IOException {
+ RevCommit parentCommit = original.getParent(0);
+
+ if (base.equals(parentCommit)) {
+ throw new ResourceConflictException("Change is already up to date.");
+ }
+
+ ThreeWayMerger merger = newMergeUtil().newThreeWayMerger(
+ ctx.getRepository(), ctx.getInserter());
+ merger.setBase(parentCommit);
+ merger.merge(original, base);
+
+ if (merger.getResultTreeId() == null) {
+ throw new MergeConflictException(
+ "The change could not be rebased due to a conflict during merge.");
+ }
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(merger.getResultTreeId());
+ cb.setParentId(base);
+ cb.setAuthor(original.getAuthorIdent());
+ cb.setMessage(original.getFullMessage());
+ if (committerIdent != null) {
+ cb.setCommitter(committerIdent);
+ } else {
+ cb.setCommitter(ctx.getUser().asIdentifiedUser()
+ .newRefLogIdent(ctx.getWhen(), ctx.getTimeZone()));
+ }
+ ObjectId objectId = ctx.getInserter().insert(cb);
+ ctx.getInserter().flush();
+ return ctx.getRevWalk().parseCommit(objectId);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
new file mode 100644
index 0000000..aea49f1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -0,0 +1,160 @@
+// Copyright (C) 2015 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.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
+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.git.GitRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/** Utility methods related to rebasing changes. */
+public class RebaseUtil {
+ private static final Logger log = LoggerFactory.getLogger(RebaseUtil.class);
+
+ private final Provider<ReviewDb> db;
+ private final GitRepositoryManager gitManager;
+
+ @Inject
+ RebaseUtil(Provider<ReviewDb> db,
+ GitRepositoryManager gitManager) {
+ this.db = db;
+ this.gitManager = gitManager;
+ }
+
+ public boolean canRebase(RevisionResource r) {
+ PatchSet patchSet = r.getPatchSet();
+ Branch.NameKey dest = r.getChange().getDest();
+ try (Repository git = gitManager.openRepository(dest.getParentKey());
+ RevWalk rw = new RevWalk(git)) {
+ return canRebase(
+ r.getPatchSet(), dest, git, rw, db.get());
+ } catch (IOException e) {
+ log.warn(String.format(
+ "Error checking if patch set %s on %s can be rebased",
+ patchSet.getId(), dest), e);
+ return false;
+ }
+ }
+
+ public static boolean canRebase(PatchSet patchSet, Branch.NameKey dest,
+ Repository git, RevWalk rw, ReviewDb db) {
+ try {
+ findBaseRevision(patchSet, dest, git, rw, db);
+ return true;
+ } catch (RestApiException e) {
+ return false;
+ } catch (OrmException | IOException e) {
+ log.warn(String.format(
+ "Error checking if patch set %s on %s can be rebased",
+ patchSet.getId(), dest), e);
+ return false;
+ }
+ }
+
+ /**
+ * Find the commit onto which a patch set should be rebased.
+ * <p>
+ * This is defined as the latest patch set of the change corresponding to
+ * this commit's parent, or the destination branch tip in the case where the
+ * parent's change is merged.
+ *
+ * @param patchSet patch set for which the new base commit should be found.
+ * @param destBranch the destination branch.
+ * @param git the repository.
+ * @param rw the RevWalk.
+ * @return the commit onto which the patch set should be rebased.
+ * @throws RestApiException if rebase is not possible.
+ * @throws IOException if accessing the repository fails.
+ * @throws OrmException if accessing the database fails.
+ */
+ static ObjectId findBaseRevision(PatchSet patchSet,
+ Branch.NameKey destBranch, Repository git, RevWalk rw, ReviewDb db)
+ throws RestApiException, IOException, OrmException {
+ String baseRev = null;
+ RevCommit commit = rw.parseCommit(
+ ObjectId.fromString(patchSet.getRevision().get()));
+
+ if (commit.getParentCount() > 1) {
+ throw new UnprocessableEntityException(
+ "Cannot rebase a change with multiple parents.");
+ } else if (commit.getParentCount() == 0) {
+ throw new UnprocessableEntityException(
+ "Cannot rebase a change without any parents"
+ + " (is this the initial commit?).");
+ }
+
+ RevId parentRev = new RevId(commit.getParent(0).name());
+
+ for (PatchSet depPatchSet : db.patchSets().byRevision(parentRev)) {
+ Change.Id depChangeId = depPatchSet.getId().getParentKey();
+ Change depChange = db.changes().get(depChangeId);
+ if (!depChange.getDest().equals(destBranch)) {
+ continue;
+ }
+
+ if (depChange.getStatus() == Status.ABANDONED) {
+ throw new ResourceConflictException(
+ "Cannot rebase a change with an abandoned parent: "
+ + depChange.getKey());
+ }
+
+ if (depChange.getStatus().isOpen()) {
+ if (depPatchSet.getId().equals(depChange.currentPatchSetId())) {
+ throw new ResourceConflictException(
+ "Change is already based on the latest patch set of the"
+ + " dependent change.");
+ }
+ PatchSet latestDepPatchSet =
+ db.patchSets().get(depChange.currentPatchSetId());
+ baseRev = latestDepPatchSet.getRevision().get();
+ }
+ break;
+ }
+
+ if (baseRev == null) {
+ // We are dependent on a merged PatchSet or have no PatchSet
+ // dependencies at all.
+ Ref destRef = git.getRefDatabase().exactRef(destBranch.get());
+ if (destRef == null) {
+ throw new UnprocessableEntityException(
+ "The destination branch does not exist: " + destBranch.get());
+ }
+ baseRev = destRef.getObjectId().getName();
+ if (baseRev.equals(parentRev.get())) {
+ throw new ResourceConflictException("Change is already up to date.");
+ }
+ }
+ return ObjectId.fromString(baseRev);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index c09aa3b..5b0eb6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -16,25 +16,29 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.RestoreInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.mail.ReplyToChangeSender;
import com.google.gerrit.server.mail.RestoredSender;
-import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -43,7 +47,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
+import java.util.Collections;
@Singleton
public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
@@ -54,89 +58,104 @@
private final RestoredSender.Factory restoredSenderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson.Factory json;
- private final ChangeIndexer indexer;
private final ChangeMessagesUtil cmUtil;
- private final ChangeUpdate.Factory updateFactory;
+ private final BatchUpdate.Factory batchUpdateFactory;
@Inject
Restore(ChangeHooks hooks,
RestoredSender.Factory restoredSenderFactory,
Provider<ReviewDb> dbProvider,
ChangeJson.Factory json,
- ChangeIndexer indexer,
ChangeMessagesUtil cmUtil,
- ChangeUpdate.Factory updateFactory) {
+ BatchUpdate.Factory batchUpdateFactory) {
this.hooks = hooks;
this.restoredSenderFactory = restoredSenderFactory;
this.dbProvider = dbProvider;
this.json = json;
- this.indexer = indexer;
this.cmUtil = cmUtil;
- this.updateFactory = updateFactory;
+ this.batchUpdateFactory = batchUpdateFactory;
}
@Override
public ChangeInfo apply(ChangeResource req, RestoreInput input)
- throws AuthException, ResourceConflictException, OrmException,
- IOException {
- ChangeControl control = req.getControl();
- IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
- Change change = req.getChange();
- if (!control.canRestore()) {
+ throws RestApiException, UpdateException, OrmException {
+ ChangeControl ctl = req.getControl();
+ if (!ctl.canRestore()) {
throw new AuthException("restore not permitted");
- } else if (change.getStatus() != Status.ABANDONED) {
- throw new ResourceConflictException("change is " + status(change));
}
- ChangeMessage message;
- ChangeUpdate update;
- ReviewDb db = dbProvider.get();
- db.changes().beginTransaction(change.getId());
- try {
- change = db.changes().atomicUpdate(
- change.getId(),
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus() == Status.ABANDONED) {
- change.setStatus(Status.NEW);
- ChangeUtil.updated(change);
- return change;
- }
- return null;
- }
- });
- if (change == null) {
- throw new ResourceConflictException("change is "
- + status(db.changes().get(req.getChange().getId())));
+ Op op = new Op(input);
+ try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
+ req.getChange().getProject(), ctl.getUser(), TimeUtil.nowTs())) {
+ u.addOp(req.getChange().getId(), op).execute();
+ }
+ return json.create(ChangeJson.NO_OPTIONS).format(op.change);
+ }
+
+ private class Op extends BatchUpdate.Op {
+ private final RestoreInput input;
+
+ private Change change;
+ private PatchSet patchSet;
+ private ChangeMessage message;
+ private IdentifiedUser caller;
+
+ private Op(RestoreInput input) {
+ this.input = input;
+ }
+
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException,
+ ResourceConflictException {
+ caller = ctx.getUser().asIdentifiedUser();
+ change = ctx.getChange();
+ if (change == null || change.getStatus() != Status.ABANDONED) {
+ throw new ResourceConflictException("change is " + status(change));
+ }
+ patchSet = ctx.getDb().patchSets().get(change.currentPatchSetId());
+ change.setStatus(Status.NEW);
+ change.setLastUpdatedOn(ctx.getWhen());
+ ctx.getDb().changes().update(Collections.singleton(change));
+
+ message = newMessage(ctx.getDb());
+ cmUtil.addChangeMessage(ctx.getDb(), ctx.getChangeUpdate(), message);
+ }
+
+ private ChangeMessage newMessage(ReviewDb db) throws OrmException {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Restored");
+ if (!Strings.nullToEmpty(input.message).trim().isEmpty()) {
+ msg.append("\n\n");
+ msg.append(input.message.trim());
}
- //TODO(yyonas): atomic update was not propagated
- update = updateFactory.create(control);
- message = newMessage(input, caller, change);
- cmUtil.addChangeMessage(db, update, message);
- db.commit();
- } finally {
- db.rollback();
+ ChangeMessage message = new ChangeMessage(
+ new ChangeMessage.Key(
+ change.getId(),
+ ChangeUtil.messageUUID(db)),
+ caller.getAccountId(),
+ change.getLastUpdatedOn(),
+ change.currentPatchSetId());
+ message.setMessage(msg.toString());
+ return message;
}
- update.commit();
- indexer.index(db, change);
-
- try {
- ReplyToChangeSender cm = restoredSenderFactory.create(change.getId());
- cm.setFrom(caller.getAccountId());
- cm.setChangeMessage(message);
- cm.send();
- } catch (Exception e) {
- log.error("Cannot email update for change " + change.getChangeId(), e);
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ try {
+ ReplyToChangeSender cm = restoredSenderFactory.create(change.getId());
+ cm.setFrom(caller.getAccountId());
+ cm.setChangeMessage(message);
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot email update for change " + change.getId(), e);
+ }
+ hooks.doChangeRestoredHook(change,
+ caller.getAccount(),
+ patchSet,
+ Strings.emptyToNull(input.message),
+ ctx.getDb());
}
- hooks.doChangeRestoredHook(change,
- caller.getAccount(),
- db.patchSets().get(change.currentPatchSetId()),
- Strings.emptyToNull(input.message),
- dbProvider.get());
- return json.create(ChangeJson.NO_OPTIONS).format(change);
}
@Override
@@ -148,26 +167,6 @@
&& resource.getControl().canRestore());
}
- private ChangeMessage newMessage(RestoreInput input, IdentifiedUser caller,
- Change change) throws OrmException {
- StringBuilder msg = new StringBuilder();
- msg.append("Restored");
- if (!Strings.nullToEmpty(input.message).trim().isEmpty()) {
- msg.append("\n\n");
- msg.append(input.message.trim());
- }
-
- ChangeMessage message = new ChangeMessage(
- new ChangeMessage.Key(
- change.getId(),
- ChangeUtil.messageUUID(dbProvider.get())),
- caller.getAccountId(),
- change.getLastUpdatedOn(),
- change.currentPatchSetId());
- message.setMessage(msg.toString());
- return message;
- }
-
private static String status(Change change) {
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index aaae6f0..dc2ed5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -16,23 +16,21 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.git.UpdateException;
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.ssh.NoSshInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -59,8 +57,8 @@
@Override
public ChangeInfo apply(ChangeResource req, RevertInput input)
- throws AuthException, BadRequestException, ResourceConflictException,
- ResourceNotFoundException, IOException, OrmException, EmailException {
+ throws IOException, OrmException, RestApiException,
+ UpdateException {
ChangeControl control = req.getControl();
Change change = req.getChange();
if (!control.canAddPatchSet()) {
@@ -74,10 +72,7 @@
revertedChangeId = changeUtil.revert(control,
change.currentPatchSetId(),
Strings.emptyToNull(input.message),
- new PersonIdent(myIdent, TimeUtil.nowTs()),
- new NoSshInfo());
- } catch (InvalidChangeOperationException e) {
- throw new BadRequestException(e.getMessage());
+ new PersonIdent(myIdent, TimeUtil.nowTs()));
} catch (NoSuchChangeException e) {
throw new ResourceNotFoundException(e.getMessage());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
index 29fa0cf..5802c15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
@@ -110,16 +110,16 @@
List<String> segments = Splitter.on(' ').omitEmptyStrings().splitToList(
query.toLowerCase());
- BooleanQuery q = new BooleanQuery();
+ BooleanQuery.Builder q = new BooleanQuery.Builder();
for (String field : ALL) {
- BooleanQuery and = new BooleanQuery();
+ BooleanQuery.Builder and = new BooleanQuery.Builder();
for (String s : segments) {
and.add(new PrefixQuery(new Term(field, s)), Occur.MUST);
}
- q.add(and, Occur.SHOULD);
+ q.add(and.build(), Occur.SHOULD);
}
- TopDocs results = searcher.search(q, n);
+ TopDocs results = searcher.search(q.build(), n);
ScoreDoc[] hits = results.scoreDocs;
List<AccountInfo> result = new LinkedList<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
index 538f36b..6731dd9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
@@ -84,7 +84,7 @@
}
IdentifiedUser getUser() {
- return (IdentifiedUser) getControl().getCurrentUser();
+ return getControl().getUser().asIdentifiedUser();
}
RevisionResource doNotCache() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
new file mode 100644
index 0000000..61baeb2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
@@ -0,0 +1,140 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.change.HashtagsUtil.extractTags;
+
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.validators.HashtagValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SetHashtagsOp extends BatchUpdate.Op {
+ public interface Factory {
+ SetHashtagsOp create(HashtagsInput input);
+ }
+
+ private final ChangeHooks hooks;
+ private final DynamicSet<HashtagValidationListener> validationListeners;
+ private final HashtagsInput input;
+
+ private boolean runHooks = true;
+
+ private Change change;
+ private Set<String> toAdd;
+ private Set<String> toRemove;
+ private ImmutableSortedSet<String> updatedHashtags;
+
+ @AssistedInject
+ SetHashtagsOp(
+ ChangeHooks hooks,
+ DynamicSet<HashtagValidationListener> validationListeners,
+ @Assisted @Nullable HashtagsInput input) {
+ this.hooks = hooks;
+ this.validationListeners = validationListeners;
+ this.input = input;
+ }
+
+ public SetHashtagsOp setRunHooks(boolean runHooks) {
+ this.runHooks = runHooks;
+ return this;
+ }
+
+ @Override
+ public void updateChange(ChangeContext ctx)
+ throws AuthException, BadRequestException, OrmException, IOException {
+ if (input == null
+ || (input.add == null && input.remove == null)) {
+ updatedHashtags = ImmutableSortedSet.of();
+ return;
+ }
+ if (!ctx.getChangeControl().canEditHashtags()) {
+ throw new AuthException("Editing hashtags not permitted");
+ }
+ ChangeUpdate update = ctx.getChangeUpdate();
+ ChangeNotes notes = update.getChangeNotes().load();
+
+ Set<String> existingHashtags = notes.getHashtags();
+ Set<String> updated = new HashSet<>();
+ toAdd = new HashSet<>(extractTags(input.add));
+ toRemove = new HashSet<>(extractTags(input.remove));
+
+ try {
+ for (HashtagValidationListener validator : validationListeners) {
+ validator.validateHashtags(update.getChange(), toAdd, toRemove);
+ }
+ } catch (ValidationException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+
+ if (existingHashtags != null && !existingHashtags.isEmpty()) {
+ updated.addAll(existingHashtags);
+ toAdd.removeAll(existingHashtags);
+ toRemove.retainAll(existingHashtags);
+ }
+ if (updated()) {
+ updated.addAll(toAdd);
+ updated.removeAll(toRemove);
+ update.setHashtags(updated);
+ }
+
+ change = update.getChange();
+ updatedHashtags = ImmutableSortedSet.copyOf(updated);
+ }
+
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ if (updated() && runHooks) {
+ hooks.doHashtagsChangedHook(
+ change, ctx.getUser().asIdentifiedUser().getAccount(),
+ toAdd, toRemove, updatedHashtags,
+ ctx.getDb());
+ }
+ }
+
+ public ImmutableSortedSet<String> getUpdatedHashtags() {
+ checkState(updatedHashtags != null,
+ "getUpdatedHashtags() only valid after executing op");
+ return updatedHashtags;
+ }
+
+ private boolean updated() {
+ return !isNullOrEmpty(toAdd) || !isNullOrEmpty(toRemove);
+ }
+
+ private static boolean isNullOrEmpty(Collection<?> coll) {
+ return coll == null || coll.isEmpty();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index ec8d70d..1a92ec0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -165,7 +165,7 @@
rsrc = onBehalfOf(rsrc, input);
}
ChangeControl control = rsrc.getControl();
- IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
+ IdentifiedUser caller = control.getUser().asIdentifiedUser();
Change change = rsrc.getChange();
if (input.onBehalfOf == null && !control.canSubmit()) {
throw new AuthException("submit not permitted");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
index a4f9fef..7fe738d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
@@ -14,15 +14,20 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.WalkSorter.PatchSetData;
import com.google.gerrit.server.git.ChangeSet;
import com.google.gerrit.server.git.MergeSuperSet;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -32,6 +37,7 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -43,15 +49,21 @@
private final ChangeJson.Factory json;
private final Provider<ReviewDb> dbProvider;
+ private final Provider<InternalChangeQuery> queryProvider;
private final MergeSuperSet mergeSuperSet;
+ private final Provider<WalkSorter> sorter;
@Inject
SubmittedTogether(ChangeJson.Factory json,
Provider<ReviewDb> dbProvider,
- MergeSuperSet mergeSuperSet) {
+ Provider<InternalChangeQuery> queryProvider,
+ MergeSuperSet mergeSuperSet,
+ Provider<WalkSorter> sorter) {
this.json = json;
this.dbProvider = dbProvider;
+ this.queryProvider = queryProvider;
this.mergeSuperSet = mergeSuperSet;
+ this.sorter = sorter;
}
@Override
@@ -59,19 +71,54 @@
throws AuthException, BadRequestException,
ResourceConflictException, Exception {
try {
- ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(),
- resource.getChange());
- if (cs.size() > 1) {
- return json.create(EnumSet.of(
- ListChangesOption.CURRENT_REVISION,
- ListChangesOption.CURRENT_COMMIT))
- .format(cs.ids());
+ Change c = resource.getChange();
+ List<ChangeData> cds;
+ if (c.getStatus().isOpen()) {
+ cds = getForOpenChange(c);
+ } else if (c.getStatus().asChangeStatus() == ChangeStatus.MERGED) {
+ cds = getForMergedChange(c);
} else {
- return Collections.emptyList();
+ cds = getForAbandonedChange();
}
+
+ if (cds.size() <= 1) {
+ cds = Collections.emptyList();
+ } else {
+ // Skip sorting for singleton lists, to avoid WalkSorter opening the
+ // repo just to fill out the commit field in PatchSetData.
+ cds = sort(cds);
+ }
+
+ return json.create(EnumSet.of(
+ ListChangesOption.CURRENT_REVISION,
+ ListChangesOption.CURRENT_COMMIT))
+ .formatChangeDatas(cds);
} catch (OrmException | IOException e) {
log.error("Error on getting a ChangeSet", e);
throw e;
}
}
+
+ private List<ChangeData> getForOpenChange(Change c)
+ throws OrmException, IOException {
+ ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(), c);
+ return cs.changes().asList();
+ }
+
+ private List<ChangeData> getForMergedChange(Change c) throws OrmException {
+ return queryProvider.get().bySubmissionId(c.getSubmissionId());
+ }
+
+ private List<ChangeData> getForAbandonedChange() {
+ return Collections.emptyList();
+ }
+
+ private List<ChangeData> sort(List<ChangeData> cds)
+ throws OrmException, IOException {
+ List<ChangeData> sorted = new ArrayList<>(cds.size());
+ for (PatchSetData psd : sorter.get().sort(cds)) {
+ sorted.add(psd.data());
+ }
+ return sorted;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index 31e34cf..95a701e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -20,7 +20,6 @@
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -70,7 +69,7 @@
@Override
public List<Record> apply(RevisionResource rsrc, Input input)
- throws AuthException, BadRequestException, OrmException {
+ throws AuthException, OrmException {
if (input == null) {
input = new Input();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 163fb10..6e6ed97 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -60,7 +60,6 @@
private final String cookiePath;
private final boolean cookieSecure;
private final SignedToken emailReg;
- private final SignedToken restToken;
@Inject
AuthConfig(@GerritServerConfig final Config cfg)
@@ -103,15 +102,6 @@
} else {
emailReg = null;
}
-
- key = cfg.getString("auth", null, "restTokenPrivateKey");
- if (key != null && !key.isEmpty()) {
- int age = (int) ConfigUtil.getTimeUnit(cfg,
- "auth", null, "maxRestTokenAge", 60, TimeUnit.SECONDS);
- restToken = new SignedToken(age, key);
- } else {
- restToken = null;
- }
}
private static List<OpenIdProviderPattern> toPatterns(Config cfg, String name) {
@@ -196,10 +186,6 @@
return emailReg;
}
- public SignedToken getRestToken() {
- return restToken;
- }
-
/** OpenID identities which the server permits for authentication. */
public List<OpenIdProviderPattern> getAllowedOpenIDs() {
return allowedOpenIDs;
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 e4d841a..2b6ee41 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
@@ -14,11 +14,15 @@
package com.google.gerrit.server.config;
-import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -56,7 +60,7 @@
String n = valueString.replace(' ', '_').replace('-', '_');
for (final T e : all) {
- if (equalsIgnoreCase(e.name(), n)) {
+ if (e.name().equalsIgnoreCase(n)) {
return e;
}
}
@@ -251,9 +255,136 @@
return v;
}
+ /**
+ * Store section by inspecting Java class attributes.
+ * <p>
+ * Optimize the storage by unsetting a variable if it is
+ * being set to default value by the server.
+ * <p>
+ * Fields marked with final or transient modifiers are skipped.
+ *
+ * @param cfg config in which the values should be stored
+ * @param section section
+ * @param sub subsection
+ * @param s instance of class with config values
+ * @param defaults instance of class with default values
+ * @throws ConfigInvalidException
+ */
+ public static <T> void storeSection(Config cfg, String section, String sub,
+ T s, T defaults) throws ConfigInvalidException {
+ try {
+ for (Field f : s.getClass().getDeclaredFields()) {
+ if (skipField(f)) {
+ continue;
+ }
+ Class<?> t = f.getType();
+ String n = f.getName();
+ f.setAccessible(true);
+ Object c = f.get(s);
+ Object d = f.get(defaults);
+ Preconditions.checkNotNull(d, "Default cannot be null");
+ if (c == null || c.equals(d)) {
+ cfg.unset(section, sub, n);
+ } else {
+ if (isString(t)) {
+ cfg.setString(section, sub, n, (String) c);
+ } else if (isInteger(t)) {
+ cfg.setInt(section, sub, n, (Integer) c);
+ } else if (isLong(t)) {
+ cfg.setLong(section, sub, n, (Long) c);
+ } else if (isBoolean(t)) {
+ cfg.setBoolean(section, sub, n, (Boolean) c);
+ } else if (t.isEnum()) {
+ cfg.setEnum(section, sub, n, (Enum<?>) c);
+ } else {
+ throw new ConfigInvalidException("type is unknown: " + t.getName());
+ }
+ }
+ }
+ } catch (SecurityException | IllegalArgumentException
+ | IllegalAccessException e) {
+ throw new ConfigInvalidException("cannot save values", e);
+ }
+ }
+
+ /**
+ * Load section by inspecting Java class attributes.
+ * <p>
+ * Config values are stored optimized: no default values are stored.
+ * The loading is performed eagerly: all values are set.
+ * <p>
+ * Fields marked with final or transient modifiers are skipped.
+ * <p>
+ * Boolean fields are only set when their values are true.
+ *
+ * @param cfg config from which the values are loaded
+ * @param section section
+ * @param sub subsection
+ * @param s instance of class in which the values are set
+ * @param defaults instance of class with default values
+ * @return loaded instance
+ * @throws ConfigInvalidException
+ */
+ public static <T> T loadSection(Config cfg, String section, String sub,
+ T s, T defaults) throws ConfigInvalidException {
+ try {
+ for (Field f : s.getClass().getDeclaredFields()) {
+ if (skipField(f)) {
+ continue;
+ }
+ Class<?> t = f.getType();
+ String n = f.getName();
+ f.setAccessible(true);
+ Object d = f.get(defaults);
+ Preconditions.checkNotNull(d, "Default cannot be null");
+ if (isString(t)) {
+ f.set(s, MoreObjects.firstNonNull(cfg.getString(section, sub, n), d));
+ } else if (isInteger(t)) {
+ f.set(s, cfg.getInt(section, sub, n, (Integer) d));
+ } else if (isLong(t)) {
+ f.set(s, cfg.getLong(section, sub, n, (Long) d));
+ } else if (isBoolean(t)) {
+ boolean b = cfg.getBoolean(section, sub, n, (Boolean) d);
+ if (b) {
+ f.set(s, b);
+ }
+ } else if (t.isEnum()) {
+ f.set(s, cfg.getEnum(section, sub, n, (Enum<?>) d));
+ } else {
+ throw new ConfigInvalidException("type is unknown: " + t.getName());
+ }
+ }
+ } catch (SecurityException | IllegalArgumentException
+ | IllegalAccessException e) {
+ throw new ConfigInvalidException("cannot load values", e);
+ }
+ return s;
+ }
+
+ private static boolean skipField(Field field) {
+ int modifiers = field.getModifiers();
+ return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
+ }
+
+ private static boolean isString(Class<?> t) {
+ return String.class == t;
+ }
+
+ private static boolean isBoolean(Class<?> t) {
+ return Boolean.class == t || boolean.class == t;
+ }
+
+ private static boolean isLong(Class<?> t) {
+ return Long.class == t || long.class == t;
+ }
+
+ private static boolean isInteger(Class<?> t) {
+ return Integer.class == t || int.class == t;
+ }
+
private static boolean match(final String a, final String... cases) {
for (final String b : cases) {
- if (equalsIgnoreCase(a, b)) {
+ if (b != null && b.equalsIgnoreCase(a)) {
return true;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfirmEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfirmEmail.java
index 789af9d..24d28c7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfirmEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfirmEmail.java
@@ -21,7 +21,6 @@
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.ConfirmEmail.Input;
@@ -63,10 +62,13 @@
if (input == null) {
input = new Input();
}
+ if (input.token == null) {
+ throw new UnprocessableEntityException("missing token");
+ }
try {
EmailTokenVerifier.ParsedToken token = emailTokenVerifier.decode(input.token);
- Account.Id accId = ((IdentifiedUser)user).getAccountId();
+ Account.Id accId = user.getAccountId();
if (accId.equals(token.getAccountId())) {
accountManager.link(accId, token.toAuthRequest());
return Response.none();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 3964115..ca3afad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -76,6 +76,7 @@
import com.google.gerrit.server.change.MergeabilityCacheImpl;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.EmailMerge;
import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.MergeUtil;
@@ -94,8 +95,8 @@
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.group.GroupModule;
import com.google.gerrit.server.index.ReindexAfterUpdate;
-import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.AddKeySender;
+import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.FromAddressGenerator;
@@ -188,6 +189,7 @@
factory(AccountInfoCacheFactory.Factory.class);
factory(AddReviewerSender.Factory.class);
factory(AddKeySender.Factory.class);
+ factory(BatchUpdate.Factory.class);
factory(CapabilityControl.Factory.class);
factory(ChangeData.Factory.class);
factory(ChangeJson.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index b75d775..9eca842 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -238,6 +238,8 @@
info.reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
info.reportBugText = cfg.getString("gerrit", null, "reportBugText");
info.docUrl = getDocUrl(cfg);
+ info.editGpgKeys = toBoolean(enableSignedPush
+ && cfg.getBoolean("gerrit", null, "editGpgKeys", true));
return info;
}
@@ -367,6 +369,7 @@
public String docUrl;
public String reportBugUrl;
public String reportBugText;
+ public Boolean editGpgKeys;
}
public static class GitwebInfo {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java
index 65b0661..382c8fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java
@@ -78,7 +78,7 @@
return BinaryResult.create(Joiner.on('\n').join(cacheNames))
.base64()
.setContentType("text/plain")
- .setCharacterEncoding(UTF_8.name());
+ .setCharacterEncoding(UTF_8);
} else {
return cacheNames;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
index 44a70d4..5178210 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
@@ -68,9 +67,8 @@
}
@Override
- public Object apply(ConfigResource rsrc, Input input) throws AuthException,
- ResourceNotFoundException, BadRequestException,
- UnprocessableEntityException {
+ public Object apply(ConfigResource rsrc, Input input)
+ throws AuthException, BadRequestException, UnprocessableEntityException {
if (input == null || input.operation == null) {
throw new BadRequestException("operation must be specified");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
index c705a7d..1d9c795 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.documentation;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.pegdown.Extensions.ALL;
import static org.pegdown.Extensions.HARDWRAPS;
import static org.pegdown.Extensions.SUPPRESS_ALL_HTML;
@@ -159,7 +160,7 @@
try (InputStream in = url.openStream();
TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024)) {
tmp.copy(in);
- return new String(tmp.toByteArray(), "UTF-8");
+ return new String(tmp.toByteArray(), UTF_8);
}
}
}
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
index 67bdd0b..e44c810 100644
--- 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
@@ -123,7 +123,7 @@
throw new AuthException("Authentication required");
}
- IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ IdentifiedUser me = currentUser.get().asIdentifiedUser();
String refPrefix = RefNames.refsEditPrefix(me.getAccountId(), change.getId());
try (Repository repo = gitManager.openRepository(change.getProject())) {
@@ -162,7 +162,7 @@
}
Change change = edit.getChange();
- IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ IdentifiedUser me = currentUser.get().asIdentifiedUser();
String refName = RefNames.refsEdit(me.getAccountId(), change.getId(),
current.getId());
try (Repository repo = gitManager.openRepository(change.getProject());
@@ -227,6 +227,7 @@
public RefUpdate.Result modifyMessage(ChangeEdit edit, String msg)
throws AuthException, InvalidChangeOperationException, IOException,
UnchangedCommitMessageException {
+ msg = msg.trim() + "\n";
checkState(!Strings.isNullOrEmpty(msg), "message cannot be null");
if (!currentUser.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
@@ -237,7 +238,7 @@
throw new UnchangedCommitMessageException();
}
- IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ IdentifiedUser me = currentUser.get().asIdentifiedUser();
Project.NameKey project = edit.getChange().getProject();
try (Repository repo = gitManager.openRepository(project);
RevWalk rw = new RevWalk(repo);
@@ -323,7 +324,7 @@
if (!currentUser.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
- IdentifiedUser me = (IdentifiedUser) currentUser.get();
+ IdentifiedUser me = currentUser.get().asIdentifiedUser();
Project.NameKey project = edit.getChange().getProject();
try (Repository repo = gitManager.openRepository(project);
RevWalk rw = new RevWalk(repo);
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
index 7682b83..8ac0d55 100644
--- 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
@@ -20,11 +20,11 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.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.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
@@ -32,11 +32,15 @@
import com.google.gerrit.server.change.ChangeKind;
import com.google.gerrit.server.change.ChangeKindCache;
import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.index.ChangeIndexer;
-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.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -63,27 +67,33 @@
public class ChangeEditUtil {
private final GitRepositoryManager gitManager;
private final PatchSetInserter.Factory patchSetInserterFactory;
- private final ChangeControl.GenericFactory changeControlFactory;
+ private final ProjectControl.GenericFactory projectControlFactory;
private final ChangeIndexer indexer;
+ private final ProjectCache projectCache;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> user;
private final ChangeKindCache changeKindCache;
+ private final BatchUpdate.Factory updateFactory;
@Inject
ChangeEditUtil(GitRepositoryManager gitManager,
PatchSetInserter.Factory patchSetInserterFactory,
- ChangeControl.GenericFactory changeControlFactory,
+ ProjectControl.GenericFactory projectControlFactory,
ChangeIndexer indexer,
+ ProjectCache projectCache,
Provider<ReviewDb> db,
Provider<CurrentUser> user,
- ChangeKindCache changeKindCache) {
+ ChangeKindCache changeKindCache,
+ BatchUpdate.Factory updateFactory) {
this.gitManager = gitManager;
this.patchSetInserterFactory = patchSetInserterFactory;
- this.changeControlFactory = changeControlFactory;
+ this.projectControlFactory = projectControlFactory;
this.indexer = indexer;
+ this.projectCache = projectCache;
this.db = db;
this.user = user;
this.changeKindCache = changeKindCache;
+ this.updateFactory = updateFactory;
}
/**
@@ -101,7 +111,7 @@
if (!currentUser.isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
- return byChange(change, (IdentifiedUser)currentUser);
+ return byChange(change, currentUser.asIdentifiedUser());
}
/**
@@ -139,13 +149,14 @@
* its parent.
*
* @param edit change edit to publish
- * @throws NoSuchChangeException
+ * @throws NoSuchProjectException
* @throws IOException
* @throws OrmException
- * @throws ResourceConflictException
+ * @throws UpdateException
+ * @throws RestApiException
*/
- public void publish(ChangeEdit edit) throws NoSuchChangeException,
- IOException, OrmException, ResourceConflictException {
+ public void publish(ChangeEdit edit) throws NoSuchProjectException,
+ IOException, OrmException, RestApiException, UpdateException {
Change change = edit.getChange();
try (Repository repo = gitManager.openRepository(change.getProject());
RevWalk rw = new RevWalk(repo);
@@ -156,16 +167,12 @@
"only edit for current patch set can be published");
}
- try {
- Change updatedChange =
- insertPatchSet(edit, change, repo, rw, basePatchSet,
- squashEdit(rw, inserter, edit.getEditCommit(), basePatchSet));
- // TODO(davido): This should happen in the same BatchRefUpdate.
- deleteRef(repo, edit);
- indexer.index(db.get(), updatedChange);
- } catch (InvalidChangeOperationException e) {
- throw new ResourceConflictException(e.getMessage());
- }
+ Change updatedChange =
+ insertPatchSet(edit, change, repo, rw, inserter, basePatchSet,
+ squashEdit(rw, inserter, edit.getEditCommit(), basePatchSet));
+ // TODO(davido): This should happen in the same BatchRefUpdate.
+ deleteRef(repo, edit);
+ indexer.index(db.get(), updatedChange);
}
}
@@ -210,20 +217,25 @@
}
private Change 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());
+ Repository repo, RevWalk rw, ObjectInserter oi, PatchSet basePatchSet,
+ RevCommit squashed) throws NoSuchProjectException, RestApiException,
+ UpdateException, IOException {
+ RefControl ctl = projectControlFactory
+ .controlFor(change.getProject(), edit.getUser())
+ .controlForRef(change.getDest());
+ PatchSet.Id psId =
+ ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
+ PatchSetInserter inserter =
+ patchSetInserterFactory.create(ctl, psId, squashed);
StringBuilder message = new StringBuilder("Patch set ")
- .append(ps.getPatchSetId())
+ .append(inserter.getPatchSetId().get())
.append(": ");
- ChangeKind kind = changeKindCache.getChangeKind(db.get(), change, ps);
+ ProjectState project = projectCache.get(change.getDest().getParentKey());
+ // Previously checked that the base patch set is the current patch set.
+ ObjectId prior = ObjectId.fromString(basePatchSet.getRevision().get());
+ ChangeKind kind = changeKindCache.getChangeKind(project, repo, prior, squashed);
if (kind == ChangeKind.NO_CODE_CHANGE) {
message.append("Commit message was updated.");
} else {
@@ -232,15 +244,18 @@
.append(".");
}
- PatchSetInserter inserter =
- patchSetInserterFactory.create(repo, rw,
- changeControlFactory.controlFor(change, edit.getUser()),
- squashed);
- return inserter.setPatchSet(ps)
+ try (BatchUpdate bu = updateFactory.create(
+ db.get(), change.getProject(), ctl.getUser(),
+ TimeUtil.nowTs())) {
+ bu.setRepository(repo, rw, oi);
+ bu.addOp(change.getId(), inserter
.setDraft(change.getStatus() == Status.DRAFT ||
basePatchSet.isDraft())
- .setMessage(message.toString())
- .insert();
+ .setMessage(message.toString()));
+ bu.execute();
+ }
+
+ return inserter.getChange();
}
private static void deleteRef(Repository repo, ChangeEdit edit)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index 7dd8d47..601bcc6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -18,7 +18,6 @@
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -34,10 +33,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
public class UiActions {
private static final Logger log = LoggerFactory.getLogger(UiActions.class);
@@ -50,27 +45,6 @@
};
}
- public static List<UiAction.Description> sorted(Iterable<UiAction.Description> in) {
- List<UiAction.Description> s = Lists.newArrayList(in);
- Collections.sort(s, new Comparator<UiAction.Description>() {
- @Override
- public int compare(UiAction.Description a, UiAction.Description b) {
- return a.getId().compareTo(b.getId());
- }
- });
- return s;
- }
-
- public static Iterable<UiAction.Description> plugins(Iterable<UiAction.Description> in) {
- return Iterables.filter(in,
- new Predicate<UiAction.Description>() {
- @Override
- public boolean apply(UiAction.Description input) {
- return input.getId().indexOf('~') > 0;
- }
- });
- }
-
public static <R extends RestResource> Iterable<UiAction.Description> from(
RestCollection<?, R> collection,
R resource,
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 eab2785..be17776 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
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_REJECT_COMMITS;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.client.Project;
@@ -41,7 +42,6 @@
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
@@ -137,12 +137,12 @@
}
private ObjectId createNoteContent(String reason, ObjectInserter inserter)
- throws UnsupportedEncodingException, IOException {
+ throws IOException {
String noteContent = reason != null ? reason : "";
if (noteContent.length() > 0 && !noteContent.endsWith("\n")) {
noteContent = noteContent + "\n";
}
- return inserter.insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
+ return inserter.insert(Constants.OBJ_BLOB, noteContent.getBytes(UTF_8));
}
private PersonIdent createPersonIdent() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
new file mode 100644
index 0000000..c54fe26
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -0,0 +1,389 @@
+// Copyright (C) 2015 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.git;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.gerrit.extensions.restapi.RestApiException;
+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.GerritPersonIdent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Context for a set of updates that should be applied for a site.
+ * <p>
+ * An update operation can be divided into three phases:
+ * <ol>
+ * <li>Git reference updates</li>
+ * <li>Database updates</li>
+ * <li>Post-update steps<li>
+ * </ol>
+ * A single conceptual operation, such as a REST API call or a merge operation,
+ * may make multiple changes at each step, which all need to be serialized
+ * relative to each other. Moreover, for consistency, <em>all</em> git ref
+ * updates must be performed before <em>any</em> database updates, since
+ * database updates might refer to newly-created patch set refs. And all
+ * post-update steps, such as hooks, should run only after all storage
+ * mutations have completed.
+ * <p>
+ * Depending on the backend used, each step might support batching, for example
+ * in a {@code BatchRefUpdate} or one or more database transactions. All
+ * operations in one phase must complete successfully before proceeding to the
+ * next phase.
+ */
+public class BatchUpdate implements AutoCloseable {
+ public interface Factory {
+ public BatchUpdate create(ReviewDb db, Project.NameKey project,
+ CurrentUser user, Timestamp when);
+ }
+
+ public class Context {
+ public Project.NameKey getProject() {
+ return project;
+ }
+
+ public Timestamp getWhen() {
+ return when;
+ }
+
+ public ReviewDb getDb() {
+ return db;
+ }
+
+ public CurrentUser getUser() {
+ return user;
+ }
+ }
+
+ public class RepoContext extends Context {
+ public Repository getRepository() throws IOException {
+ initRepository();
+ return repo;
+ }
+
+ public RevWalk getRevWalk() throws IOException {
+ initRepository();
+ return revWalk;
+ }
+
+ public ObjectInserter getInserter() throws IOException {
+ initRepository();
+ return inserter;
+ }
+
+ public BatchRefUpdate getBatchRefUpdate() throws IOException {
+ initRepository();
+ if (batchRefUpdate == null) {
+ batchRefUpdate = repo.getRefDatabase().newBatchUpdate();
+ }
+ return batchRefUpdate;
+ }
+
+ public void addRefUpdate(ReceiveCommand cmd) throws IOException {
+ getBatchRefUpdate().addCommand(cmd);
+ }
+
+ public TimeZone getTimeZone() {
+ return tz;
+ }
+ }
+
+ public class ChangeContext extends Context {
+ private final ChangeControl ctl;
+ private final ChangeUpdate update;
+
+ private ChangeContext(ChangeControl ctl) {
+ this.ctl = ctl;
+ this.update = changeUpdateFactory.create(ctl, when);
+ }
+
+ public ChangeUpdate getChangeUpdate() {
+ return update;
+ }
+
+ public ChangeNotes getChangeNotes() {
+ return update.getChangeNotes();
+ }
+
+ public ChangeControl getChangeControl() {
+ return ctl;
+ }
+
+ public Change getChange() {
+ return update.getChange();
+ }
+ }
+
+ public static class Op {
+ @SuppressWarnings("unused")
+ public void updateRepo(RepoContext ctx) throws Exception {
+ }
+
+ @SuppressWarnings("unused")
+ public void updateChange(ChangeContext ctx) throws Exception {
+ }
+
+ // TODO(dborowitz): Support async operations?
+ @SuppressWarnings("unused")
+ public void postUpdate(Context ctx) throws Exception {
+ }
+ }
+
+ public abstract static class InsertChangeOp extends Op {
+ public abstract Change getChange();
+ }
+
+ private final ReviewDb db;
+ private final GitRepositoryManager repoManager;
+ private final ChangeIndexer indexer;
+ private final ChangeControl.GenericFactory changeControlFactory;
+ private final ChangeUpdate.Factory changeUpdateFactory;
+ private final GitReferenceUpdated gitRefUpdated;
+
+ private final Project.NameKey project;
+ private final CurrentUser user;
+ private final Timestamp when;
+ private final TimeZone tz;
+
+ private final ListMultimap<Change.Id, Op> ops = ArrayListMultimap.create();
+ private final Map<Change.Id, Change> newChanges = new HashMap<>();
+ private final List<CheckedFuture<?, IOException>> indexFutures =
+ new ArrayList<>();
+
+ private Repository repo;
+ private ObjectInserter inserter;
+ private RevWalk revWalk;
+ private BatchRefUpdate batchRefUpdate;
+ private boolean closeRepo;
+
+ @AssistedInject
+ BatchUpdate(GitRepositoryManager repoManager,
+ ChangeIndexer indexer,
+ ChangeControl.GenericFactory changeControlFactory,
+ ChangeUpdate.Factory changeUpdateFactory,
+ GitReferenceUpdated gitRefUpdated,
+ @GerritPersonIdent PersonIdent serverIdent,
+ @Assisted ReviewDb db,
+ @Assisted Project.NameKey project,
+ @Assisted CurrentUser user,
+ @Assisted Timestamp when) {
+ this.db = db;
+ this.repoManager = repoManager;
+ this.indexer = indexer;
+ this.changeControlFactory = changeControlFactory;
+ this.changeUpdateFactory = changeUpdateFactory;
+ this.gitRefUpdated = gitRefUpdated;
+ this.project = project;
+ this.user = user;
+ this.when = when;
+ tz = serverIdent.getTimeZone();
+ }
+
+ @Override
+ public void close() {
+ if (closeRepo) {
+ revWalk.close();
+ inserter.close();
+ repo.close();
+ }
+ }
+
+ public BatchUpdate setRepository(Repository repo, RevWalk revWalk,
+ ObjectInserter inserter) {
+ checkState(this.repo == null, "repo already set");
+ closeRepo = false;
+ this.repo = checkNotNull(repo, "repo");
+ this.revWalk = checkNotNull(revWalk, "revWalk");
+ this.inserter = checkNotNull(inserter, "inserter");
+ return this;
+ }
+
+ private void initRepository() throws IOException {
+ if (repo == null) {
+ this.repo = repoManager.openRepository(project);
+ closeRepo = true;
+ inserter = repo.newObjectInserter();
+ revWalk = new RevWalk(inserter.newReader());
+ }
+ }
+
+ public CurrentUser getUser() {
+ return user;
+ }
+
+ public Repository getRepository() throws IOException {
+ initRepository();
+ return repo;
+ }
+
+ public RevWalk getRevWalk() throws IOException {
+ initRepository();
+ return revWalk;
+ }
+
+ public ObjectInserter getObjectInserter() throws IOException {
+ initRepository();
+ return inserter;
+ }
+
+ public BatchUpdate addOp(Change.Id id, Op op) {
+ checkArgument(!(op instanceof InsertChangeOp), "use insertChange");
+ ops.put(id, op);
+ return this;
+ }
+
+ public BatchUpdate insertChange(InsertChangeOp op) {
+ Change c = op.getChange();
+ checkArgument(!newChanges.containsKey(c.getId()),
+ "only one op allowed to create change %s", c.getId());
+ newChanges.put(c.getId(), c);
+ ops.get(c.getId()).add(0, op);
+ return this;
+ }
+
+ public void execute() throws UpdateException, RestApiException {
+ try {
+ executeRefUpdates();
+ executeChangeOps();
+ reindexChanges();
+
+ if (batchRefUpdate != null) {
+ // Fire ref update events only after all mutations are finished, since
+ // callers may assume a patch set ref being created means the change was
+ // created, or a branch advancing meaning some changes were closed.
+ gitRefUpdated.fire(project, batchRefUpdate);
+ }
+
+ executePostOps();
+ } catch (UpdateException | RestApiException e) {
+ // Propagate REST API exceptions thrown by operations; they commonly throw
+ // exceptions like ResourceConflictException to indicate an atomic update
+ // failure.
+ throw e;
+ } catch (Exception e) {
+ Throwables.propagateIfPossible(e);
+ throw new UpdateException(e);
+ }
+ }
+
+ private void executeRefUpdates()
+ throws IOException, UpdateException, RestApiException {
+ try {
+ RepoContext ctx = new RepoContext();
+ for (Op op : ops.values()) {
+ op.updateRepo(ctx);
+ }
+ } catch (Exception e) {
+ Throwables.propagateIfPossible(e, RestApiException.class);
+ throw new UpdateException(e);
+ }
+
+ if (repo == null || batchRefUpdate == null
+ || batchRefUpdate.getCommands().isEmpty()) {
+ return;
+ }
+ inserter.flush();
+ batchRefUpdate.execute(revWalk, NullProgressMonitor.INSTANCE);
+ boolean ok = true;
+ for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ ok = false;
+ break;
+ }
+ }
+ if (!ok) {
+ throw new UpdateException("BatchRefUpdate failed: " + batchRefUpdate);
+ }
+ }
+
+ private void executeChangeOps() throws UpdateException, RestApiException {
+ try {
+ for (Map.Entry<Change.Id, Collection<Op>> e : ops.asMap().entrySet()) {
+ Change.Id id = e.getKey();
+ db.changes().beginTransaction(id);
+ ChangeContext ctx;
+ try {
+ ctx = newChangeContext(id);
+ for (Op op : e.getValue()) {
+ op.updateChange(ctx);
+ }
+ db.commit();
+ } finally {
+ db.rollback();
+ }
+ ctx.getChangeUpdate().commit();
+ indexFutures.add(indexer.indexAsync(id));
+ }
+ } catch (Exception e) {
+ Throwables.propagateIfPossible(e, RestApiException.class);
+ throw new UpdateException(e);
+ }
+ }
+
+ private ChangeContext newChangeContext(Change.Id id) throws Exception {
+ Change c = newChanges.get(id);
+ if (c == null) {
+ c = db.changes().get(id);
+ }
+ // Pass in preloaded change to controlFor, to avoid:
+ // - reading from a db that does not belong to this update
+ // - attempting to read a change that doesn't exist yet
+ return new ChangeContext(
+ changeControlFactory.controlFor(c, user));
+ }
+
+ private void reindexChanges() throws IOException {
+ ChangeIndexer.allAsList(indexFutures).checkedGet();
+ }
+
+ private void executePostOps() throws Exception {
+ Context ctx = new Context();
+ for (Op op : ops.values()) {
+ op.postUpdate(ctx);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
index d4b0c4b..fdf9b34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
@@ -91,12 +91,12 @@
return ret;
}
- public Multimap<Branch.NameKey, Change.Id> changesByBranch()
+ public Multimap<Branch.NameKey, ChangeData> changesByBranch()
throws OrmException {
- ListMultimap<Branch.NameKey, Change.Id> ret =
+ ListMultimap<Branch.NameKey, ChangeData> ret =
ArrayListMultimap.create();
for (ChangeData cd : changeData) {
- ret.put(cd.change().getDest(), cd.getId());
+ ret.put(cd.change().getDest(), cd);
}
return ret;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
index 1ff37b0..37a0886 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
import com.google.gerrit.reviewdb.client.Change;
@@ -21,6 +23,8 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
@@ -28,6 +32,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import java.io.IOException;
import java.util.List;
/** Extended commit entity with code review specific metadata. */
@@ -50,11 +55,11 @@
}
}).nullsFirst();
- public static RevWalk newRevWalk(Repository repo) {
+ public static CodeReviewRevWalk newRevWalk(Repository repo) {
return new CodeReviewRevWalk(repo);
}
- public static RevWalk newRevWalk(ObjectReader reader) {
+ public static CodeReviewRevWalk newRevWalk(ObjectReader reader) {
return new CodeReviewRevWalk(reader);
}
@@ -85,7 +90,7 @@
return r;
}
- private static class CodeReviewRevWalk extends RevWalk {
+ public static class CodeReviewRevWalk extends RevWalk {
private CodeReviewRevWalk(Repository repo) {
super(repo);
}
@@ -95,9 +100,42 @@
}
@Override
- protected RevCommit createCommit(AnyObjectId id) {
+ protected CodeReviewCommit createCommit(AnyObjectId id) {
return new CodeReviewCommit(id);
}
+
+ @Override
+ public CodeReviewCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ return (CodeReviewCommit) super.next();
+ }
+
+ @Override
+ public void markStart(RevCommit c) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ checkArgument(c instanceof CodeReviewCommit);
+ super.markStart(c);
+ }
+
+ @Override
+ public void markUninteresting(final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ checkArgument(c instanceof CodeReviewCommit);
+ super.markUninteresting(c);
+ }
+
+ @Override
+ public CodeReviewCommit lookupCommit(AnyObjectId id) {
+ return (CodeReviewCommit) super.lookupCommit(id);
+ }
+
+ @Override
+ public CodeReviewCommit parseCommit(AnyObjectId id)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return (CodeReviewCommit) super.parseCommit(id);
+ }
}
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
index 3d3d9b1..66417fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
@@ -16,7 +16,7 @@
public enum CommitMergeStatus {
/** */
- CLEAN_MERGE("Change has been successfully merged into the git repository"),
+ CLEAN_MERGE("Change has been successfully merged"),
/** */
CLEAN_PICK("Change has been successfully cherry-picked"),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
index 7b1161c..6535774 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
@@ -96,7 +96,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
throw new OutOfScopeException("No user on email thread");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/HackPushNegotiateHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/HackPushNegotiateHook.java
new file mode 100644
index 0000000..5cd7dc3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/HackPushNegotiateHook.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static org.eclipse.jgit.lib.RefDatabase.ALL;
+
+import com.google.common.collect.Sets;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.BaseReceivePack;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.UploadPack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Advertises part of history to git push clients.
+ * <p>
+ * This is a hack to work around the lack of negotiation in the
+ * send-pack/receive-pack wire protocol.
+ * <p>
+ * When the server is frequently advancing master by creating merge commits, the
+ * client may not be able to discover a common ancestor during push. Attempting
+ * to push will re-upload a very large amount of history. This hook hacks in a
+ * fake negotiation replacement by walking history and sending recent commits as
+ * {@code ".have"} lines in the wire protocol, allowing the client to find a
+ * common ancestor.
+ */
+public class HackPushNegotiateHook implements AdvertiseRefsHook {
+ private static final Logger log = LoggerFactory
+ .getLogger(HackPushNegotiateHook.class);
+
+ /** Size of an additional ".have" line. */
+ private static final int HAVE_LINE_LEN = 4
+ + Constants.OBJECT_ID_STRING_LENGTH
+ + 1 + 5 + 1;
+
+ /**
+ * Maximum number of bytes to "waste" in the advertisement with a peek at this
+ * repository's current reachable history.
+ */
+ private static final int MAX_EXTRA_BYTES = 8192;
+
+ /**
+ * Number of recent commits to advertise immediately, hoping to show a client
+ * a nearby merge base.
+ */
+ private static final int BASE_COMMITS = 64;
+
+ /** Number of commits to skip once base has already been shown. */
+ private static final int STEP_COMMITS = 16;
+
+ /** Total number of commits to extract from the history. */
+ private static final int MAX_HISTORY = MAX_EXTRA_BYTES / HAVE_LINE_LEN;
+
+ @Override
+ public void advertiseRefs(UploadPack us) {
+ throw new UnsupportedOperationException(
+ "HackPushNegotiateHook cannot be used for UploadPack");
+ }
+
+ @Override
+ public void advertiseRefs(BaseReceivePack rp)
+ throws ServiceMayNotContinueException {
+ Map<String, Ref> r = rp.getAdvertisedRefs();
+ if (r == null) {
+ try {
+ r = rp.getRepository().getRefDatabase().getRefs(ALL);
+ } catch (ServiceMayNotContinueException e) {
+ throw e;
+ } catch (IOException e) {
+ ServiceMayNotContinueException ex = new ServiceMayNotContinueException();
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+ rp.setAdvertisedRefs(r, history(r.values(), rp));
+ }
+
+ private Set<ObjectId> history(Collection<Ref> refs, BaseReceivePack rp) {
+ Set<ObjectId> alreadySending = rp.getAdvertisedObjects();
+ if (alreadySending.isEmpty()) {
+ alreadySending = idsOf(refs);
+ }
+
+ int max = MAX_HISTORY - Math.max(0, alreadySending.size() - refs.size());
+ if (max <= 0) {
+ return Collections.emptySet();
+ }
+
+ // Scan history until the advertisement is full.
+ RevWalk rw = rp.getRevWalk();
+ try {
+ for (Ref ref : refs) {
+ try {
+ if (ref.getObjectId() != null) {
+ rw.markStart(rw.parseCommit(ref.getObjectId()));
+ }
+ } catch (IOException badCommit) {
+ continue;
+ }
+ }
+
+ Set<ObjectId> history = Sets.newHashSetWithExpectedSize(max);
+ try {
+ int stepCnt = 0;
+ for (RevCommit c; history.size() < max && (c = rw.next()) != null;) {
+ if (c.getParentCount() <= 1
+ && !alreadySending.contains(c)
+ && (history.size() < BASE_COMMITS || (++stepCnt % STEP_COMMITS) == 0)) {
+ history.add(c);
+ }
+ }
+ } catch (IOException err) {
+ log.error("error trying to advertise history", err);
+ }
+ return history;
+ } finally {
+ rw.reset();
+ }
+ }
+
+ private static Set<ObjectId> idsOf(Collection<Ref> refs) {
+ Set<ObjectId> r = Sets.newHashSetWithExpectedSize(refs.size());
+ for (Ref ref : refs) {
+ if (ref.getObjectId() != null) {
+ r.add(ref.getObjectId());
+ }
+ }
+ return r;
+ }
+}
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 624e30c..eebae70 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
@@ -201,7 +201,7 @@
public Repository createRepository(Project.NameKey name)
throws RepositoryNotFoundException, RepositoryCaseMismatchException {
Repository repo = createRepository(basePath, name);
- if (noteDbPath != null) {
+ if (noteDbPath != null && !noteDbPath.equals(basePath)) {
createRepository(noteDbPath, name);
}
return repo;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 3bfa47e..776cee9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -27,6 +27,8 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.SubmitRecord;
@@ -50,6 +52,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
import com.google.gerrit.server.git.strategy.SubmitStrategy;
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
@@ -86,11 +89,12 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
@@ -143,12 +147,24 @@
private final Map<Change.Id, List<SubmitRecord>> records;
private final Map<Change.Id, CodeReviewCommit> commits;
- private String logPrefix;
+
+ private static final String MACHINE_ID;
+ static {
+ String id;
+ try {
+ id = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ id = "unknown";
+ }
+ MACHINE_ID = id;
+ }
+ private String staticSubmissionId;
+ private String submissionId;
private ProjectState destProject;
private ReviewDb db;
private Repository repo;
- private RevWalk rw;
+ private CodeReviewRevWalk rw;
private RevFlag canMergeFlag;
private ObjectInserter inserter;
private PersonIdent refLogIdent;
@@ -229,6 +245,10 @@
public static List<SubmitRecord> checkSubmitRule(ChangeData cd)
throws ResourceConflictException, OrmException {
PatchSet patchSet = cd.currentPatchSet();
+ if (patchSet == null) {
+ throw new ResourceConflictException(
+ "missing current patch set for change " + cd.getId());
+ }
List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
.setPatchSet(patchSet)
.evaluate();
@@ -332,10 +352,19 @@
}
}
+ private void updateSubmissionId(Change change) {
+ Hasher h = Hashing.sha1().newHasher();
+ h.putLong(Thread.currentThread().getId())
+ .putUnencodedChars(MACHINE_ID);
+ staticSubmissionId = h.hash().toString().substring(0, 8);
+ submissionId = change.getId().get() + "-" + TimeUtil.nowMs() +
+ "-" + staticSubmissionId;
+ }
+
public void merge(ReviewDb db, Change change, IdentifiedUser caller,
boolean checkSubmitRules) throws NoSuchChangeException,
OrmException, ResourceConflictException {
- logPrefix = String.format("[%s]: ", String.valueOf(change.hashCode()));
+ updateSubmissionId(change);
this.db = db;
logDebug("Beginning integration of {}", change);
try {
@@ -365,18 +394,14 @@
logDebug("Perform the merges");
try {
Multimap<Project.NameKey, Branch.NameKey> br = cs.branchesByProject();
- Multimap<Branch.NameKey, Change.Id> cbb = cs.changesByBranch();
+ Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
for (Project.NameKey project : br.keySet()) {
openRepository(project);
for (Branch.NameKey branch : br.get(project)) {
setDestProject(branch);
- List<ChangeData> cds = new ArrayList<>();
- for (Change.Id id : cbb.get(branch)) {
- cds.add(changeDataFactory.create(db, id));
- }
ListMultimap<SubmitType, ChangeData> submitting =
- validateChangeList(cds);
+ validateChangeList(cbb.get(branch));
toSubmit.put(branch, submitting);
Set<SubmitType> submitTypes = new HashSet<>(submitting.keySet());
@@ -473,6 +498,7 @@
rw = CodeReviewCommit.newRevWalk(repo);
rw.sort(RevSort.TOPO);
rw.sort(RevSort.COMMIT_TIME_DESC, true);
+ rw.setRetainBody(false);
canMergeFlag = rw.newFlag("CAN_MERGE");
inserter = repo.newObjectInserter();
@@ -503,8 +529,7 @@
RefUpdate branchUpdate = repo.updateRef(destBranch.get());
CodeReviewCommit branchTip;
if (branchUpdate.getOldObjectId() != null) {
- branchTip =
- (CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
+ branchTip = rw.parseCommit(branchUpdate.getOldObjectId());
} else if (Objects.equals(repo.getFullBranch(), destBranch.get())) {
branchTip = null;
branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
@@ -558,7 +583,7 @@
}
private ListMultimap<SubmitType, ChangeData> validateChangeList(
- List<ChangeData> submitted) throws MergeException {
+ Collection<ChangeData> submitted) throws MergeException {
logDebug("Validating {} changes", submitted.size());
ListMultimap<SubmitType, ChangeData> toSubmit = ArrayListMultimap.create();
@@ -637,7 +662,7 @@
CodeReviewCommit commit;
try {
- commit = (CodeReviewCommit) rw.parseCommit(id);
+ commit = rw.parseCommit(id);
} catch (IOException e) {
logError("Invalid commit " + idstr + " on patch set " + ps.getId(), e);
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
@@ -994,6 +1019,7 @@
@Override
public Change update(Change c) {
c.setStatus(Change.Status.MERGED);
+ c.setSubmissionId(submissionId);
if (!merged.equals(c.currentPatchSetId())) {
// Uncool; the patch set changed after we merged it.
// Go back to the patch set that was actually merged.
@@ -1250,28 +1276,28 @@
private void logDebug(String msg, Object... args) {
if (log.isDebugEnabled()) {
- log.debug(logPrefix + msg, args);
+ log.debug("[" + submissionId + "]" + msg, args);
}
}
private void logWarn(String msg, Throwable t) {
if (log.isWarnEnabled()) {
- log.warn(logPrefix + msg, t);
+ log.warn("[" + submissionId + "]" + msg, t);
}
}
private void logWarn(String msg) {
if (log.isWarnEnabled()) {
- log.warn(logPrefix + msg);
+ log.warn("[" + submissionId + "]" + msg);
}
}
private void logError(String msg, Throwable t) {
if (log.isErrorEnabled()) {
if (t != null) {
- log.error(logPrefix + msg, t);
+ log.error("[" + submissionId + "]" + msg, t);
} else {
- log.error(logPrefix + msg);
+ log.error("[" + submissionId + "]" + msg);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
index 4985390..aa55751 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
+
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevCommitList;
import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.ArrayList;
@@ -27,12 +28,12 @@
import java.util.Set;
public class MergeSorter {
- private final RevWalk rw;
+ private final CodeReviewRevWalk rw;
private final RevFlag canMergeFlag;
private final Set<RevCommit> accepted;
- public MergeSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
- final RevFlag canMergeFlag) {
+ public MergeSorter(CodeReviewRevWalk rw, Set<RevCommit> alreadyAccepted,
+ RevFlag canMergeFlag) {
this.rw = rw;
this.canMergeFlag = canMergeFlag;
this.accepted = alreadyAccepted;
@@ -51,8 +52,8 @@
rw.markUninteresting(c);
}
- RevCommit c;
- final RevCommitList<RevCommit> contents = new RevCommitList<>();
+ CodeReviewCommit c;
+ RevCommitList<RevCommit> contents = new RevCommitList<>();
while ((c = rw.next()) != null) {
if (!c.has(canMergeFlag) || !incoming.contains(c)) {
// We cannot merge n as it would bring something we
@@ -62,7 +63,7 @@
n.setStatusCode(CommitMergeStatus.MISSING_DEPENDENCY);
n.missing = new ArrayList<>();
}
- n.missing.add((CodeReviewCommit) c);
+ n.missing.add(c);
} else {
contents.add(c);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
index 3add138..c9a5c3e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
@@ -139,7 +139,8 @@
if (!hashes.isEmpty()) {
// Merged changes are ok to exclude
Iterable<ChangeData> destChanges = queryProvider.get()
- .byCommitsOnBranchNotMerged(cd.change().getDest(), hashes);
+ .byCommitsOnBranchNotMerged(
+ repo, db, cd.change().getDest(), hashes);
for (ChangeData chd : destChanges) {
ret.add(chd);
}
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 c5ead54..5fdefbe 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
@@ -34,6 +34,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
@@ -164,9 +165,10 @@
return result;
}
- public RevCommit createCherryPickFromCommit(Repository repo,
+ public CodeReviewCommit createCherryPickFromCommit(Repository repo,
ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
- PersonIdent cherryPickCommitterIdent, String commitMsg, RevWalk rw)
+ PersonIdent cherryPickCommitterIdent, String commitMsg,
+ CodeReviewRevWalk rw)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
MergeIdenticalTreeException, MergeConflictException {
@@ -361,9 +363,9 @@
}
}
- public boolean canFastForward(final MergeSorter mergeSorter,
- final CodeReviewCommit mergeTip, final RevWalk rw,
- final CodeReviewCommit toMerge) throws MergeException {
+ public boolean canFastForward(MergeSorter mergeSorter,
+ CodeReviewCommit mergeTip, CodeReviewRevWalk rw, CodeReviewCommit toMerge)
+ throws MergeException {
if (hasMissingDependencies(mergeSorter, toMerge)) {
return false;
}
@@ -375,9 +377,9 @@
}
}
- public boolean canCherryPick(final MergeSorter mergeSorter,
- final Repository repo, final CodeReviewCommit mergeTip, final RevWalk rw,
- final CodeReviewCommit toMerge) throws MergeException {
+ public boolean canCherryPick(MergeSorter mergeSorter, Repository repo,
+ CodeReviewCommit mergeTip, CodeReviewRevWalk rw, CodeReviewCommit toMerge)
+ throws MergeException {
if (mergeTip == null) {
// The branch is unborn. Fast-forward is possible.
//
@@ -445,7 +447,7 @@
}
public CodeReviewCommit mergeOneCommit(PersonIdent author,
- PersonIdent committer, Repository repo, RevWalk rw,
+ PersonIdent committer, Repository repo, CodeReviewRevWalk rw,
ObjectInserter inserter, RevFlag canMergeFlag, Branch.NameKey destBranch,
CodeReviewCommit mergeTip, CodeReviewCommit n) throws MergeException {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
@@ -481,22 +483,22 @@
}
}
- private static CodeReviewCommit failed(final RevWalk rw,
- final RevFlag canMergeFlag, final CodeReviewCommit mergeTip,
- final CodeReviewCommit n, final CommitMergeStatus failure)
+ private static CodeReviewCommit failed(CodeReviewRevWalk rw,
+ RevFlag canMergeFlag, CodeReviewCommit mergeTip, CodeReviewCommit n,
+ CommitMergeStatus failure)
throws MissingObjectException, IncorrectObjectTypeException, IOException {
rw.resetRetain(canMergeFlag);
rw.markStart(n);
rw.markUninteresting(mergeTip);
CodeReviewCommit failed;
- while ((failed = (CodeReviewCommit) rw.next()) != null) {
+ while ((failed = rw.next()) != null) {
failed.setStatusCode(failure);
}
return failed;
}
public CodeReviewCommit writeMergeCommit(PersonIdent author,
- PersonIdent committer, RevWalk rw, ObjectInserter inserter,
+ PersonIdent committer, CodeReviewRevWalk rw, ObjectInserter inserter,
RevFlag canMergeFlag, Branch.NameKey destBranch,
CodeReviewCommit mergeTip, ObjectId treeId, CodeReviewCommit n)
throws IOException, MissingObjectException,
@@ -505,8 +507,8 @@
rw.resetRetain(canMergeFlag);
rw.markStart(n);
rw.markUninteresting(mergeTip);
- for (final RevCommit c : rw) {
- final CodeReviewCommit crc = (CodeReviewCommit) c;
+ CodeReviewCommit crc;
+ while ((crc = rw.next()) != null) {
if (crc.getPatchsetId() != null) {
merged.add(crc);
}
@@ -536,7 +538,7 @@
mergeCommit.setMessage(msgbuf.toString());
CodeReviewCommit mergeResult =
- (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
+ rw.parseCommit(commit(inserter, mergeCommit));
mergeResult.setControl(n.getControl());
return mergeResult;
}
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 b868282..addbc1b 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
@@ -118,6 +118,7 @@
"requireContributorAgreement";
private static final String KEY_CHECK_RECEIVED_OBJECTS = "checkReceivedObjects";
private static final String KEY_ENABLE_SIGNED_PUSH = "enableSignedPush";
+ private static final String KEY_REQUIRE_SIGNED_PUSH = "requireSignedPush";
private static final String SUBMIT = "submit";
private static final String KEY_ACTION = "action";
@@ -420,6 +421,8 @@
p.setCreateNewChangeForAllNotInTarget(getEnum(rc, RECEIVE, null, KEY_USE_ALL_NOT_IN_TARGET, InheritableBoolean.INHERIT));
p.setEnableSignedPush(getEnum(rc, RECEIVE, null,
KEY_ENABLE_SIGNED_PUSH, InheritableBoolean.INHERIT));
+ p.setRequireSignedPush(getEnum(rc, RECEIVE, null,
+ KEY_REQUIRE_SIGNED_PUSH, InheritableBoolean.INHERIT));
p.setMaxObjectSizeLimit(rc.getString(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT));
p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
@@ -547,7 +550,7 @@
Config rc, Map<String, GroupReference> groupsByName) {
accessSections = new HashMap<>();
for (String refName : rc.getSubsections(ACCESS)) {
- if (RefConfigSection.isValid(refName)) {
+ if (RefConfigSection.isValid(refName) & isValidRegex(refName)) {
AccessSection as = getAccessSection(refName, true);
for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) {
@@ -580,6 +583,17 @@
}
}
+ private boolean isValidRegex(String refPattern) {
+ try {
+ Pattern.compile(refPattern.replace("${username}/", ""));
+ } catch (PatternSyntaxException e) {
+ error(new ValidationError(PROJECT_CONFIG, "Invalid ref name: "
+ + e.getMessage()));
+ return false;
+ }
+ return true;
+ }
+
private void loadBranchOrderSection(Config rc) {
if (rc.getSections().contains(BRANCH_ORDER)) {
branchOrderSection = new BranchOrderSection(
@@ -828,6 +842,8 @@
set(rc, RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
set(rc, RECEIVE, null, KEY_ENABLE_SIGNED_PUSH,
p.getEnableSignedPush(), InheritableBoolean.INHERIT);
+ set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_PUSH,
+ p.getRequireSignedPush(), InheritableBoolean.INHERIT);
set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.getUseContentMerge(), InheritableBoolean.INHERIT);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
index 8595582..3ee2a9b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -14,9 +14,10 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
+
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.ArrayList;
@@ -28,13 +29,12 @@
import java.util.Set;
public class RebaseSorter {
-
- private final RevWalk rw;
+ private final CodeReviewRevWalk rw;
private final RevFlag canMergeFlag;
private final Set<RevCommit> accepted;
- public RebaseSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
- final RevFlag canMergeFlag) {
+ public RebaseSorter(CodeReviewRevWalk rw, Set<RevCommit> alreadyAccepted,
+ RevFlag canMergeFlag) {
this.rw = rw;
this.canMergeFlag = canMergeFlag;
this.accepted = alreadyAccepted;
@@ -55,7 +55,7 @@
CodeReviewCommit c;
final List<CodeReviewCommit> contents = new ArrayList<>();
- while ((c = (CodeReviewCommit) rw.next()) != null) {
+ while ((c = rw.next()) != null) {
if (!c.has(canMergeFlag) || !incoming.contains(c)) {
// We cannot merge n as it would bring something we
// aren't permitted to merge at this time. Drop n.
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 9e5353a..c87ed00 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
@@ -62,6 +62,7 @@
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -71,6 +72,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -89,6 +91,7 @@
import com.google.gerrit.server.change.ChangeKindCache;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.change.SetHashtagsOp;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -136,6 +139,7 @@
import org.eclipse.jgit.lib.BatchRefUpdate;
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;
@@ -273,7 +277,7 @@
private Set<Account.Id> reviewersFromCommandLine = Sets.newLinkedHashSet();
private Set<Account.Id> ccFromCommandLine = Sets.newLinkedHashSet();
- private final IdentifiedUser currentUser;
+ private final IdentifiedUser user;
private final ReviewDb db;
private final Provider<InternalChangeQuery> queryProvider;
private final ChangeData.Factory changeDataFactory;
@@ -305,6 +309,8 @@
private final AllProjectsName allProjectsName;
private final ReceiveConfig receiveConfig;
private final ChangeKindCache changeKindCache;
+ private final BatchUpdate.Factory batchUpdateFactory;
+ private final SetHashtagsOp.Factory hashtagsFactory;
private final ProjectControl projectControl;
private final Project project;
@@ -381,8 +387,10 @@
final ChangeKindCache changeKindCache,
final DynamicMap<ProjectConfigEntry> pluginConfigEntries,
final NotesMigration notesMigration,
- final ChangeEditUtil editUtil) throws IOException {
- this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
+ final ChangeEditUtil editUtil,
+ final BatchUpdate.Factory batchUpdateFactory,
+ final SetHashtagsOp.Factory hashtagsFactory) throws IOException {
+ this.user = projectControl.getUser().asIdentifiedUser();
this.db = db;
this.queryProvider = queryProvider;
this.changeDataFactory = changeDataFactory;
@@ -414,6 +422,8 @@
this.allProjectsName = allProjectsName;
this.receiveConfig = config;
this.changeKindCache = changeKindCache;
+ this.batchUpdateFactory = batchUpdateFactory;
+ this.hashtagsFactory = hashtagsFactory;
this.projectControl = projectControl;
this.labelTypes = projectControl.getLabelTypes();
@@ -485,6 +495,7 @@
advHooks.add(rp.getAdvertiseRefsHook());
advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
db, queryProvider, projectControl.getProject().getNameKey()));
+ advHooks.add(new HackPushNegotiateHook());
rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
}
@@ -595,7 +606,7 @@
for (Error error : errors.keySet()) {
rp.sendMessage(buildError(error, errors.get(error)));
}
- rp.sendMessage(String.format("User: %s", displayName(currentUser)));
+ rp.sendMessage(String.format("User: %s", displayName(user)));
rp.sendMessage(COMMAND_REJECTION_MESSAGE_FOOTER);
}
@@ -651,7 +662,7 @@
new Branch.NameKey(project.getNameKey(), c.getRefName()),
c.getOldId(),
c.getNewId(),
- currentUser.getAccount());
+ user.getAccount());
}
}
}
@@ -870,7 +881,7 @@
}
HookResult result = hooks.doRefUpdateHook(project, cmd.getRefName(),
- currentUser.getAccount(), cmd.getOldId(),
+ user.getAccount(), cmd.getOldId(),
cmd.getNewId());
if (result != null) {
@@ -941,7 +952,7 @@
addError(" " + err.getMessage());
}
reject(cmd, "invalid project configuration");
- log.error("User " + currentUser.getUserName()
+ log.error("User " + user.getUserName()
+ " tried to push invalid project configuration "
+ cmd.getNewId().name() + " for " + project.getName());
continue;
@@ -956,7 +967,7 @@
}
} else {
if (!oldParent.equals(newParent)
- && !currentUser.getCapabilities().canAdministrateServer()) {
+ && !user.getCapabilities().canAdministrateServer()) {
reject(cmd, "invalid project configuration: only Gerrit admin can set parent");
continue;
}
@@ -1002,7 +1013,7 @@
}
} catch (Exception e) {
reject(cmd, "invalid project configuration");
- log.error("User " + currentUser.getUserName()
+ log.error("User " + user.getUserName()
+ " tried to push invalid project configuration "
+ cmd.getNewId().name() + " for " + project.getName(), e);
continue;
@@ -1518,7 +1529,7 @@
List<ChangeLookup> pending = Lists.newArrayList();
final Set<Change.Key> newChangeIds = new HashSet<>();
final int maxBatchChanges =
- receiveConfig.getEffectiveMaxBatchChangesLimit(currentUser);
+ receiveConfig.getEffectiveMaxBatchChangesLimit(user);
for (;;) {
final RevCommit c = rp.getRevWalk().next();
if (c == null) {
@@ -1712,12 +1723,14 @@
commit = c;
change = new Change(changeKey,
new Change.Id(db.nextChangeId()),
- currentUser.getAccountId(),
+ user.getAccountId(),
magicBranch.dest,
TimeUtil.nowTs());
change.setTopic(magicBranch.topic);
- ins = changeInserterFactory.create(ctl.getProjectControl(), change, c)
- .setDraft(magicBranch.draft);
+ ins = changeInserterFactory.create(ctl, change, c)
+ .setDraft(magicBranch.draft)
+ // Changes already validated in validateNewCommits.
+ .setValidatePolicy(CommitValidators.Policy.NONE);
cmd = new ReceiveCommand(ObjectId.zeroId(), c,
ins.getPatchSet().getRefName());
}
@@ -1729,13 +1742,13 @@
ListenableFuture<Void> future = changeUpdateExector.submit(
requestScopePropagator.wrap(new Callable<Void>() {
@Override
- public Void call() throws OrmException, IOException,
- ResourceConflictException {
+ public Void call()
+ throws OrmException, RestApiException, UpdateException {
if (caller == Thread.currentThread()) {
- insertChange(db);
+ insertChange(ReceiveCommits.this.db);
} else {
- try (ReviewDb db = schemaFactory.open()) {
- insertChange(db);
+ try (ReviewDb threadLocalDb = schemaFactory.open()) {
+ insertChange(threadLocalDb);
}
}
synchronized (newProgress) {
@@ -1747,34 +1760,41 @@
return Futures.makeChecked(future, INSERT_EXCEPTION);
}
- private void insertChange(ReviewDb db) throws OrmException, IOException,
- ResourceConflictException {
+ private void insertChange(ReviewDb threadLocalDb)
+ throws OrmException, RestApiException, UpdateException {
final PatchSet ps = ins.setGroups(groups).getPatchSet();
- final Account.Id me = currentUser.getAccountId();
+ final Account.Id me = user.getAccountId();
final List<FooterLine> footerLines = commit.getFooterLines();
final MailRecipients recipients = new MailRecipients();
Map<String, Short> approvals = new HashMap<>();
if (magicBranch != null) {
recipients.add(magicBranch.getMailRecipients());
approvals = magicBranch.labels;
- ins.setHashtags(magicBranch.hashtags);
}
recipients.add(getRecipientsFromFooters(accountResolver, ps, footerLines));
recipients.remove(me);
-
- ChangeMessage msg =
- new ChangeMessage(new ChangeMessage.Key(change.getId(),
- ChangeUtil.messageUUID(db)), me, ps.getCreatedOn(), ps.getId());
- msg.setMessage("Uploaded patch set " + ps.getPatchSetId() + ".");
-
- ins
- .setReviewers(recipients.getReviewers())
- .setExtraCC(recipients.getCcOnly())
- .setApprovals(approvals)
- .setMessage(msg)
- .setRequestScopePropagator(requestScopePropagator)
- .setSendMail(true)
- .insert();
+ String msg = renderMessageWithApprovals(ps.getPatchSetId(), null,
+ approvals, Collections.<String, PatchSetApproval> emptyMap());
+ try (ObjectInserter oi = repo.newObjectInserter();
+ BatchUpdate bu = batchUpdateFactory.create(threadLocalDb,
+ change.getProject(), user, change.getCreatedOn())) {
+ bu.setRepository(repo, rp.getRevWalk(), oi);
+ bu.insertChange(ins
+ .setReviewers(recipients.getReviewers())
+ .setExtraCC(recipients.getCcOnly())
+ .setApprovals(approvals)
+ .setMessage(msg)
+ .setRequestScopePropagator(requestScopePropagator)
+ .setSendMail(true)
+ .setUpdateRef(false));
+ if (magicBranch != null) {
+ bu.addOp(
+ ins.getChange().getId(),
+ hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
+ .setRunHooks(false));
+ }
+ bu.execute();
+ }
created = true;
if (magicBranch != null && magicBranch.submit) {
@@ -1789,7 +1809,7 @@
RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
try {
mergeOpProvider.get().merge(db, rsrc.getChange(),
- (IdentifiedUser) changeCtl.getCurrentUser(), false);
+ changeCtl.getUser().asIdentifiedUser(), false);
} catch (NoSuchChangeException e) {
throw new OrmException(e);
}
@@ -1881,6 +1901,32 @@
}
}
+ private String renderMessageWithApprovals(int patchSetId, String suffix,
+ Map<String, Short> n, Map<String, PatchSetApproval> c) {
+ StringBuilder msgs = new StringBuilder("Uploaded patch set " + patchSetId);
+ if (!n.isEmpty()) {
+ boolean first = true;
+ for (Map.Entry<String, Short> e : n.entrySet()) {
+ if (c.containsKey(e.getKey())
+ && c.get(e.getKey()).getValue() == e.getValue()) {
+ continue;
+ }
+ if (first) {
+ msgs.append(":");
+ first = false;
+ }
+ msgs.append(" ")
+ .append(LabelVote.create(e.getKey(), e.getValue()).format());
+ }
+ }
+
+ if (!Strings.isNullOrEmpty(suffix)) {
+ msgs.append(suffix);
+ }
+
+ return msgs.append('.').toString();
+ }
+
private class ReplaceRequest {
final Change.Id ontoChange;
final RevCommit newCommit;
@@ -2027,7 +2073,7 @@
Optional<ChangeEdit> edit = null;
try {
- edit = editUtil.byChange(change, currentUser);
+ edit = editUtil.byChange(change, user);
} catch (IOException e) {
log.error("Cannt retrieve edit", e);
return false;
@@ -2061,23 +2107,28 @@
ObjectId.zeroId(),
newCommit,
RefNames.refsEdit(
- currentUser.getAccountId(),
+ user.getAccountId(),
change.getId(),
newPatchSet.getId()));
}
- private void newPatchSet() {
+ private void newPatchSet() throws IOException {
PatchSet.Id id =
ChangeUtil.nextPatchSetId(allRefs, change.currentPatchSetId());
newPatchSet = new PatchSet(id);
newPatchSet.setCreatedOn(TimeUtil.nowTs());
- newPatchSet.setUploader(currentUser.getAccountId());
+ newPatchSet.setUploader(user.getAccountId());
newPatchSet.setRevision(toRevId(newCommit));
newPatchSet.setGroups(groups);
+ if (rp.getPushCertificate() != null) {
+ newPatchSet.setPushCertificate(
+ rp.getPushCertificate().toTextWithSignature());
+ }
if (magicBranch != null && magicBranch.draft) {
newPatchSet.setDraft(true);
}
- info = patchSetInfoFactory.get(newCommit, newPatchSet.getId());
+ info = patchSetInfoFactory.get(
+ rp.getRevWalk(), newCommit, newPatchSet.getId());
cmd = new ReceiveCommand(
ObjectId.zeroId(),
newCommit,
@@ -2114,27 +2165,52 @@
return Futures.makeChecked(future, INSERT_EXCEPTION);
}
- private ChangeMessage newChangeMessage(ReviewDb db, ChangeKind changeKind)
+ private ChangeMessage newChangeMessage(ReviewDb db, ChangeKind changeKind,
+ Map<String, Short> approvals)
throws OrmException {
msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
- .messageUUID(db)), currentUser.getAccountId(), newPatchSet.getCreatedOn(),
+ .messageUUID(db)), user.getAccountId(), newPatchSet.getCreatedOn(),
newPatchSet.getId());
- String message = "Uploaded patch set " + newPatchSet.getPatchSetId();
+
+ msg.setMessage(renderMessageWithApprovals(newPatchSet.getPatchSetId(),
+ changeKindMessage(changeKind), approvals, scanLabels(db, approvals)));
+
+ return msg;
+ }
+
+ private String changeKindMessage(ChangeKind changeKind) {
switch (changeKind) {
case TRIVIAL_REBASE:
case NO_CHANGE:
- message += ": Patch Set " + priorPatchSet.get() + " was rebased";
- break;
+ return ": Patch Set " + priorPatchSet.get() + " was rebased";
case NO_CODE_CHANGE:
- message += ": Commit message was updated";
- break;
+ return ": Commit message was updated";
case REWORK:
default:
- break;
+ return null;
}
- msg.setMessage(message + ".");
- return msg;
+ }
+
+ private Map<String, PatchSetApproval> scanLabels(ReviewDb db,
+ Map<String, Short> approvals)
+ throws OrmException {
+ Map<String, PatchSetApproval> current = new HashMap<>();
+ // We optimize here and only retrieve current when approvals provided
+ if (!approvals.isEmpty()) {
+ for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
+ db, changeCtl, priorPatchSet, user.getAccountId())) {
+ if (a.isSubmit()) {
+ continue;
+ }
+
+ LabelType lt = labelTypes.byLabel(a.getLabelId());
+ if (lt != null) {
+ current.put(lt.getName(), a);
+ }
+ }
+ }
+ return current;
}
PatchSet.Id upsertEdit() {
@@ -2146,7 +2222,7 @@
PatchSet.Id insertPatchSet(ReviewDb db) throws OrmException, IOException,
ResourceConflictException {
- final Account.Id me = currentUser.getAccountId();
+ final Account.Id me = user.getAccountId();
final List<FooterLine> footerLines = newCommit.getFooterLines();
final MailRecipients recipients = new MailRecipients();
Map<String, Short> approvals = new HashMap<>();
@@ -2193,7 +2269,7 @@
approvalCopier.copy(db, changeCtl, newPatchSet);
approvalsUtil.addReviewers(db, update, labelTypes, change, newPatchSet,
info, recipients.getReviewers(), oldRecipients.getAll());
- approvalsUtil.addApprovals(db, update, labelTypes, newPatchSet, info,
+ approvalsUtil.addApprovals(db, update, labelTypes, newPatchSet,
changeCtl, approvals);
recipients.add(oldRecipients);
@@ -2201,7 +2277,8 @@
changeKind = changeKindCache.getChangeKind(
projectControl.getProjectState(), repo, priorCommit, newCommit);
- cmUtil.addChangeMessage(db, update, newChangeMessage(db, changeKind));
+ cmUtil.addChangeMessage(db, update, newChangeMessage(db, changeKind,
+ approvals));
if (mergedIntoRef == null) {
// Change should be new, so it can go through review again.
@@ -2297,7 +2374,12 @@
hooks.doPatchsetCreatedHook(change, newPatchSet, db);
if (mergedIntoRef != null) {
hooks.doChangeMergedHook(
- change, currentUser.getAccount(), newPatchSet, db, newCommit.getName());
+ change, user.getAccount(), newPatchSet, db, newCommit.getName());
+ }
+
+ if (!approvals.isEmpty()) {
+ hooks.doCommentAddedHook(change, user.getAccount(), newPatchSet,
+ null, approvals, db);
}
if (magicBranch != null && magicBranch.submit) {
@@ -2473,7 +2555,7 @@
return;
}
- boolean defaultName = Strings.isNullOrEmpty(currentUser.getAccount().getFullName());
+ boolean defaultName = Strings.isNullOrEmpty(user.getAccount().getFullName());
final RevWalk walk = rp.getRevWalk();
walk.reset();
walk.sort(RevSort.NONE);
@@ -2492,14 +2574,14 @@
break;
}
- if (defaultName && currentUser.hasEmailAddress(
+ if (defaultName && user.hasEmailAddress(
c.getCommitterIdent().getEmailAddress())) {
try {
- Account a = db.accounts().get(currentUser.getAccountId());
+ Account a = db.accounts().get(user.getAccountId());
if (a != null && Strings.isNullOrEmpty(a.getFullName())) {
a.setFullName(c.getCommitterIdent().getName());
db.accounts().update(Collections.singleton(a));
- currentUser.getAccount().setFullName(a.getFullName());
+ user.getAccount().setFullName(a.getFullName());
accountCache.evict(a.getId());
}
} catch (OrmException e) {
@@ -2523,7 +2605,7 @@
}
CommitReceivedEvent receiveEvent =
- new CommitReceivedEvent(cmd, project, ctl.getRefName(), c, currentUser);
+ new CommitReceivedEvent(cmd, project, ctl.getRefName(), c, user);
CommitValidators commitValidators =
commitValidatorsFactory.create(ctl, sshInfo, repo);
@@ -2627,11 +2709,11 @@
result.change = change;
result.changeCtl = projectControl.controlFor(change);
result.newPatchSet = ps;
- result.info = patchSetInfoFactory.get(commit, psi);
+ result.info = patchSetInfoFactory.get(rp.getRevWalk(), commit, psi);
result.mergedIntoRef = refName;
markChangeMergedByPush(db, result, result.changeCtl);
hooks.doChangeMergedHook(
- change, currentUser.getAccount(), result.newPatchSet, db, commit.getName());
+ change, user.getAccount(), result.newPatchSet, db, commit.getName());
sendMergedEmail(result);
return change.getKey();
}
@@ -2680,7 +2762,7 @@
msgBuf.append(".");
ChangeMessage msg = new ChangeMessage(
new ChangeMessage.Key(id, ChangeUtil.messageUUID(db)),
- currentUser.getAccountId(), change.getLastUpdatedOn(),
+ user.getAccountId(), change.getLastUpdatedOn(),
result.info.getKey());
msg.setMessage(msgBuf.toString());
@@ -2703,7 +2785,7 @@
public void run() {
try {
final MergedSender cm = mergedSenderFactory.create(id);
- cm.setFrom(currentUser.getAccountId());
+ cm.setFrom(user.getAccountId());
cm.setPatchSet(result.newPatchSet, result.info);
cm.send();
} catch (Exception e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index b2d3632..43a4d4b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -28,11 +28,8 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
import org.eclipse.jgit.transport.BaseReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
@@ -41,6 +38,7 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -89,15 +87,11 @@
r.put(name, e.getValue());
}
}
- rp.setAdvertisedRefs(r, advertiseHistory(r.values(), rp));
+ rp.setAdvertisedRefs(r, advertiseOpenChanges());
}
- private Set<ObjectId> advertiseHistory(
- Iterable<Ref> sending,
- BaseReceivePack rp) {
- Set<ObjectId> toInclude = Sets.newHashSet();
-
- // Advertise some recent open changes, in case a commit is based one.
+ private Set<ObjectId> advertiseOpenChanges() {
+ // Advertise some recent open changes, in case a commit is based on one.
final int limit = 32;
try {
Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(limit);
@@ -110,68 +104,18 @@
toGet.add(id);
}
}
+
+ Set<ObjectId> r = Sets.newHashSetWithExpectedSize(toGet.size());
for (PatchSet ps : db.patchSets().get(toGet)) {
if (ps.getRevision() != null && ps.getRevision().get() != null) {
- toInclude.add(ObjectId.fromString(ps.getRevision().get()));
+ r.add(ObjectId.fromString(ps.getRevision().get()));
}
}
+ return r;
} catch (OrmException err) {
log.error("Cannot list open changes of " + projectName, err);
+ return Collections.emptySet();
}
-
- // Size of an additional ".have" line.
- final int haveLineLen = 4 + Constants.OBJECT_ID_STRING_LENGTH + 1 + 5 + 1;
-
- // Maximum number of bytes to "waste" in the advertisement with
- // a peek at this repository's current reachable history.
- final int maxExtraSize = 8192;
-
- // Number of recent commits to advertise immediately, hoping to
- // show a client a nearby merge base.
- final int base = 64;
-
- // Number of commits to skip once base has already been shown.
- final int step = 16;
-
- // Total number of commits to extract from the history.
- final int max = maxExtraSize / haveLineLen;
-
- // Scan history until the advertisement is full.
- Set<ObjectId> alreadySending = Sets.newHashSet();
- RevWalk rw = rp.getRevWalk();
- for (Ref ref : sending) {
- try {
- if (ref.getObjectId() != null) {
- alreadySending.add(ref.getObjectId());
- rw.markStart(rw.parseCommit(ref.getObjectId()));
- }
- } catch (IOException badCommit) {
- continue;
- }
- }
-
- int stepCnt = 0;
- RevCommit c;
- try {
- while ((c = rw.next()) != null && toInclude.size() < max) {
- if (alreadySending.contains(c)
- || toInclude.contains(c)
- || c.getParentCount() > 1) {
- // Do nothing
- } else if (toInclude.size() < base) {
- toInclude.add(c);
- } else {
- stepCnt = ++stepCnt % step;
- if (stepCnt == 0) {
- toInclude.add(c);
- }
- }
- }
- } catch (IOException err) {
- log.error("Error trying to advertise history on " + projectName, err);
- }
- rw.reset();
- return toInclude;
}
private static boolean skip(String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
index 61cafc0..3c7666e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
@@ -332,7 +332,7 @@
static boolean skip(Ref ref) {
return ref.isSymbolic() || ref.getObjectId() == null
- || PatchSet.isRef(ref.getName());
+ || PatchSet.isChangeRef(ref.getName());
}
private static boolean isTag(Ref ref) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/UpdateException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/UpdateException.java
new file mode 100644
index 0000000..087af6c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/UpdateException.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2015 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.git;
+
+/** Exception type thrown by {@link BatchUpdate} steps. */
+public class UpdateException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public UpdateException(String message) {
+ super(message);
+ }
+
+ public UpdateException(Throwable cause) {
+ super(cause);
+ }
+
+ public UpdateException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java
similarity index 62%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java
index cd01186..29b5373 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/UserConfigSections.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.server.git;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+public class UserConfigSections {
+
+ /** The my menu user preferences. */
+ public static final String MY = "my";
+
+ /** The edit user preferences. */
+ public static final String EDIT = "edit";
+
+ private UserConfigSections() {
+ }
}
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 dfde5d5..e7b98d8 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
@@ -50,8 +50,8 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
-import java.util.Objects;
import java.util.List;
+import java.util.Objects;
/**
* Support for metadata stored within a version controlled branch.
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 a4684a1..46638f0 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
@@ -79,8 +79,8 @@
Account.Id currAccountId;
boolean canViewMetadata;
- if (projectCtl.getCurrentUser().isIdentifiedUser()) {
- IdentifiedUser user = ((IdentifiedUser) projectCtl.getCurrentUser());
+ if (projectCtl.getUser().isIdentifiedUser()) {
+ IdentifiedUser user = projectCtl.getUser().asIdentifiedUser();
currAccountId = user.getAccountId();
canViewMetadata = user.getCapabilities().canAccessDatabase();
} else {
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 0e740f8..a2e9c03 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
@@ -16,14 +16,18 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.RestApiException;
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.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
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.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.git.GroupCollector;
@@ -31,14 +35,15 @@
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeIdenticalTreeException;
import com.google.gerrit.server.git.MergeTip;
+import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
import java.io.IOException;
import java.util.ArrayList;
@@ -50,16 +55,12 @@
public class CherryPick extends SubmitStrategy {
private final PatchSetInfoFactory patchSetInfoFactory;
- private final GitReferenceUpdated gitRefUpdated;
private final Map<Change.Id, CodeReviewCommit> newCommits;
CherryPick(SubmitStrategy.Arguments args,
- PatchSetInfoFactory patchSetInfoFactory,
- GitReferenceUpdated gitRefUpdated) {
+ PatchSetInfoFactory patchSetInfoFactory) {
super(args);
-
this.patchSetInfoFactory = patchSetInfoFactory;
- this.gitRefUpdated = gitRefUpdated;
this.newCommits = new HashMap<>();
}
@@ -68,148 +69,182 @@
Collection<CodeReviewCommit> toMerge) throws MergeException {
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
- while (!sorted.isEmpty()) {
- CodeReviewCommit n = sorted.remove(0);
- try {
- if (mergeTip.getCurrentTip() == null) {
- cherryPickUnbornRoot(n, mergeTip);
+ boolean first = true;
+ try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
+ while (!sorted.isEmpty()) {
+ CodeReviewCommit n = sorted.remove(0);
+ Change.Id cid = n.change().getId();
+ if (first && branchTip == null) {
+ u.addOp(cid, new CherryPickUnbornRootOp(mergeTip, n));
} else if (n.getParentCount() == 0) {
- cherryPickRootOntoBranch(n);
+ u.addOp(cid, new CherryPickRootOp(n));
} else if (n.getParentCount() == 1) {
- cherryPickOne(n, mergeTip);
+ u.addOp(cid, new CherryPickOneOp(mergeTip, n));
} else {
- cherryPickMultipleParents(n, mergeTip);
+ u.addOp(cid, new CherryPickMultipleParentsOp(mergeTip, n));
}
- } catch (NoSuchChangeException | IOException | OrmException e) {
- throw new MergeException("Cannot merge " + n.name(), e);
+ first = false;
}
+ u.execute();
+ } catch (UpdateException | RestApiException e) {
+ throw new MergeException("Cannot cherry-pick onto " + args.destBranch);
}
+ // TODO(dborowitz): When BatchUpdate is hoisted out of CherryPick,
+ // SubmitStrategy should probably no longer return MergeTip, instead just
+ // mutating a single shared MergeTip passed in from the caller.
return mergeTip;
}
- private void cherryPickUnbornRoot(CodeReviewCommit n, MergeTip mergeTip) {
- // The branch is unborn. Take fast-forward resolution to create the branch.
- mergeTip.moveTipTo(n, n);
- n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
- }
+ private static class CherryPickUnbornRootOp extends BatchUpdate.Op {
+ private final MergeTip mergeTip;
+ private final CodeReviewCommit toMerge;
- private void cherryPickRootOntoBranch(CodeReviewCommit n) {
- // Refuse to merge a root commit into an existing branch, we cannot obtain a
- // delta for the cherry-pick to apply.
- n.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
- }
+ private CherryPickUnbornRootOp(MergeTip mergeTip,
+ CodeReviewCommit toMerge) {
+ this.mergeTip = mergeTip;
+ this.toMerge = toMerge;
+ }
- private void cherryPickOne(CodeReviewCommit n, MergeTip mergeTip)
- throws NoSuchChangeException, OrmException, IOException {
- // If there is only one parent, a cherry-pick can be done by taking the
- // delta relative to that one parent and redoing that on the current merge
- // tip.
- //
- // Keep going in the case of a single merge failure; the goal is to
- // cherry-pick as many commits as possible.
- try {
- CodeReviewCommit merge =
- writeCherryPickCommit(mergeTip.getCurrentTip(), n);
- mergeTip.moveTipTo(merge, merge);
- newCommits.put(mergeTip.getCurrentTip().getPatchsetId()
- .getParentKey(), mergeTip.getCurrentTip());
- } catch (MergeConflictException mce) {
- n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
- } catch (MergeIdenticalTreeException mie) {
- n.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
+ @Override
+ public void updateRepo(RepoContext ctx) {
+ // The branch is unborn. Take fast-forward resolution to create the
+ // branch.
+ mergeTip.moveTipTo(toMerge, toMerge);
+ toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
}
}
- private void cherryPickMultipleParents(CodeReviewCommit n, MergeTip mergeTip)
- throws IOException, MergeException {
- // There are multiple parents, so this is a merge commit. We don't want
- // to cherry-pick the merge as clients can't easily rebase their history
- // with that merge present and replaced by an equivalent merge with a
- // different first parent. So instead behave as though MERGE_IF_NECESSARY
- // was configured.
- if (!args.mergeUtil.hasMissingDependencies(args.mergeSorter, n)) {
- if (args.rw.isMergedInto(mergeTip.getCurrentTip(), n)) {
- mergeTip.moveTipTo(n, n);
- } else {
- PersonIdent myIdent = args.serverIdent.get();
- CodeReviewCommit result = args.mergeUtil.mergeOneCommit(myIdent,
- myIdent, args.repo, args.rw, args.inserter,
- args.canMergeFlag, args.destBranch, mergeTip.getCurrentTip(), n);
- mergeTip.moveTipTo(result, n);
+ private static class CherryPickRootOp extends BatchUpdate.Op {
+ private final CodeReviewCommit toMerge;
+
+ private CherryPickRootOp(CodeReviewCommit toMerge) {
+ this.toMerge = toMerge;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx) {
+ // Refuse to merge a root commit into an existing branch, we cannot obtain
+ // a delta for the cherry-pick to apply.
+ toMerge.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
+ }
+ }
+
+ private class CherryPickOneOp extends BatchUpdate.Op {
+ private final MergeTip mergeTip;
+ private final CodeReviewCommit toMerge;
+
+ private PatchSet.Id psId;
+ private CodeReviewCommit newCommit;
+ private PatchSetInfo patchSetInfo;
+
+ private CherryPickOneOp(MergeTip mergeTip, CodeReviewCommit n) {
+ this.mergeTip = mergeTip;
+ this.toMerge = n;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx) throws IOException {
+ // If there is only one parent, a cherry-pick can be done by taking the
+ // delta relative to that one parent and redoing that on the current merge
+ // tip.
+ args.rw.parseBody(toMerge);
+ psId = ChangeUtil.nextPatchSetId(
+ args.repo, toMerge.change().currentPatchSetId());
+ String cherryPickCmtMsg =
+ args.mergeUtil.createCherryPickCommitMessage(toMerge);
+
+ PersonIdent committer = args.caller.newCommitterIdent(
+ ctx.getWhen(), args.serverIdent.get().getTimeZone());
+ try {
+ newCommit = args.mergeUtil.createCherryPickFromCommit(
+ args.repo, args.inserter, mergeTip.getCurrentTip(), toMerge,
+ committer, cherryPickCmtMsg, args.rw);
+ ctx.addRefUpdate(
+ new ReceiveCommand(ObjectId.zeroId(), newCommit, psId.toRefName()));
+ patchSetInfo =
+ patchSetInfoFactory.get(ctx.getRevWalk(), newCommit, psId);
+ } catch (MergeConflictException mce) {
+ // Keep going in the case of a single merge failure; the goal is to
+ // cherry-pick as many commits as possible.
+ toMerge.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
+ } catch (MergeIdenticalTreeException mie) {
+ toMerge.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
}
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
- mergeTip.getCurrentTip(), args.alreadyAccepted);
- setRefLogIdent();
- } else {
- // One or more dependencies were not met. The status was already marked on
- // the commit so we have nothing further to perform at this time.
}
- }
- private CodeReviewCommit writeCherryPickCommit(CodeReviewCommit mergeTip,
- CodeReviewCommit n) throws IOException, OrmException,
- NoSuchChangeException, MergeConflictException,
- MergeIdenticalTreeException {
+ @Override
+ public void updateChange(ChangeContext ctx) throws OrmException,
+ NoSuchChangeException {
+ if (newCommit == null) {
+ // Merge conflict; don't update change.
+ return;
+ }
+ PatchSet ps = new PatchSet(psId);
+ ps.setCreatedOn(ctx.getWhen());
+ ps.setUploader(args.caller.getAccountId());
+ ps.setRevision(new RevId(newCommit.getId().getName()));
- args.rw.parseBody(n);
-
- String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
-
- PersonIdent committer = args.caller.newCommitterIdent(
- TimeUtil.nowTs(), args.serverIdent.get().getTimeZone());
- CodeReviewCommit newCommit =
- (CodeReviewCommit) args.mergeUtil.createCherryPickFromCommit(args.repo,
- args.inserter, mergeTip, n, committer, cherryPickCmtMsg, args.rw);
-
- PatchSet.Id id =
- ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
- PatchSet ps = new PatchSet(id);
- ps.setCreatedOn(TimeUtil.nowTs());
- ps.setUploader(args.caller.getAccountId());
- ps.setRevision(new RevId(newCommit.getId().getName()));
-
- RefUpdate ru;
-
- args.db.changes().beginTransaction(n.change().getId());
- try {
- insertAncestors(args.db, ps.getId(), newCommit);
- ps.setGroups(GroupCollector.getCurrentGroups(args.db, n.change()));
+ Change c = toMerge.change();
+ ps.setGroups(GroupCollector.getCurrentGroups(args.db, c));
args.db.patchSets().insert(Collections.singleton(ps));
- n.change()
- .setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
- args.db.changes().update(Collections.singletonList(n.change()));
+ insertAncestors(args.db, ps.getId(), newCommit);
+ c.setCurrentPatchSet(patchSetInfo);
+ args.db.changes().update(Collections.singletonList(c));
List<PatchSetApproval> approvals = Lists.newArrayList();
for (PatchSetApproval a : args.approvalsUtil.byPatchSet(
- args.db, n.getControl(), n.getPatchsetId())) {
+ args.db, toMerge.getControl(), toMerge.getPatchsetId())) {
approvals.add(new PatchSetApproval(ps.getId(), a));
}
args.db.patchSetApprovals().insert(approvals);
- ru = args.repo.updateRef(ps.getRefName());
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(newCommit);
- ru.disableRefLog();
- if (ru.update(args.rw) != RefUpdate.Result.NEW) {
- throw new IOException(String.format(
- "Failed to create ref %s in %s: %s", ps.getRefName(), n.change()
- .getDest().getParentKey().get(), ru.getResult()));
- }
+ newCommit.copyFrom(toMerge);
+ newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
+ newCommit.setControl(
+ args.changeControlFactory.controlFor(toMerge.change(), args.caller));
+ mergeTip.moveTipTo(newCommit, newCommit);
+ newCommits.put(c.getId(), newCommit);
+ setRefLogIdent();
+ }
+ }
- args.db.commit();
- } finally {
- args.db.rollback();
+ private class CherryPickMultipleParentsOp extends BatchUpdate.Op {
+ private final MergeTip mergeTip;
+ private final CodeReviewCommit toMerge;
+
+ private CherryPickMultipleParentsOp(MergeTip mergeTip,
+ CodeReviewCommit toMerge) {
+ this.mergeTip = mergeTip;
+ this.toMerge = toMerge;
}
- gitRefUpdated.fire(n.change().getProject(), ru);
-
- newCommit.copyFrom(n);
- newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
- newCommit.setControl(
- args.changeControlFactory.controlFor(n.change(), args.caller));
- newCommits.put(newCommit.getPatchsetId().getParentKey(), newCommit);
- setRefLogIdent();
- return newCommit;
+ @Override
+ public void updateRepo(RepoContext ctx) throws MergeException, IOException {
+ if (args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)) {
+ // One or more dependencies were not met. The status was already marked
+ // on the commit so we have nothing further to perform at this time.
+ return;
+ }
+ // There are multiple parents, so this is a merge commit. We don't want
+ // to cherry-pick the merge as clients can't easily rebase their history
+ // with that merge present and replaced by an equivalent merge with a
+ // different first parent. So instead behave as though MERGE_IF_NECESSARY
+ // was configured.
+ if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
+ mergeTip.moveTipTo(toMerge, toMerge);
+ } else {
+ PersonIdent myIdent =
+ new PersonIdent(args.serverIdent.get(), ctx.getWhen());
+ CodeReviewCommit result = args.mergeUtil.mergeOneCommit(myIdent,
+ myIdent, args.repo, args.rw, args.inserter,
+ args.canMergeFlag, args.destBranch, mergeTip.getCurrentTip(),
+ toMerge);
+ mergeTip.moveTipTo(result, toMerge);
+ }
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ mergeTip.getCurrentTip(), args.alreadyAccepted);
+ setRefLogIdent();
+ }
}
private static void insertAncestors(ReviewDb db, PatchSet.Id id,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index f9102ec..5ef33a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -15,19 +15,22 @@
package com.google.gerrit.server.git.strategy;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
-import com.google.gerrit.server.change.RebaseChange;
+import com.google.gerrit.server.change.RebaseChangeOp;
+import com.google.gerrit.server.git.BatchUpdate;
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.MergeTip;
import com.google.gerrit.server.git.RebaseSorter;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
@@ -43,15 +46,15 @@
public class RebaseIfNecessary extends SubmitStrategy {
private final PatchSetInfoFactory patchSetInfoFactory;
- private final RebaseChange rebaseChange;
+ private final RebaseChangeOp.Factory rebaseFactory;
private final Map<Change.Id, CodeReviewCommit> newCommits;
RebaseIfNecessary(SubmitStrategy.Arguments args,
PatchSetInfoFactory patchSetInfoFactory,
- RebaseChange rebaseChange) {
+ RebaseChangeOp.Factory rebaseFactory) {
super(args);
this.patchSetInfoFactory = patchSetInfoFactory;
- this.rebaseChange = rebaseChange;
+ this.rebaseFactory = rebaseFactory;
this.newCommits = new HashMap<>();
}
@@ -84,11 +87,7 @@
} else {
try {
- PatchSet newPatchSet =
- rebaseChange.rebase(args.repo, args.rw, args.inserter,
- n.change(), n.getPatchsetId(), args.caller,
- mergeTip.getCurrentTip(), args.mergeUtil,
- args.serverIdent.get(), false, ValidatePolicy.NONE);
+ PatchSet newPatchSet = rebase(n, mergeTip);
List<PatchSetApproval> approvals = Lists.newArrayList();
for (PatchSetApproval a : args.approvalsUtil.byPatchSet(args.db,
n.getControl(), n.getPatchsetId())) {
@@ -97,11 +96,11 @@
// rebaseChange.rebase() may already have copied some approvals,
// use upsert, not insert, to avoid constraint violation on database
args.db.patchSetApprovals().upsert(approvals);
- CodeReviewCommit newTip = (CodeReviewCommit) args.rw.parseCommit(
+ CodeReviewCommit newTip = args.rw.parseCommit(
ObjectId.fromString(newPatchSet.getRevision().get()));
mergeTip.moveTipTo(newTip, newTip);
n.change().setCurrentPatchSet(
- patchSetInfoFactory.get(mergeTip.getCurrentTip(),
+ patchSetInfoFactory.get(args.rw, mergeTip.getCurrentTip(),
newPatchSet.getId()));
mergeTip.getCurrentTip().copyFrom(n);
mergeTip.getCurrentTip().setControl(
@@ -112,10 +111,15 @@
newCommits.put(newPatchSet.getId().getParentKey(),
mergeTip.getCurrentTip());
setRefLogIdent();
- } catch (MergeConflictException e) {
- n.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
+ } catch (UpdateException e) {
+ if (e.getCause() instanceof MergeConflictException) {
+ n.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
+ }
+ throw new MergeException("Cannot rebase " + n.name(), e);
} catch (NoSuchChangeException | OrmException | IOException
- | InvalidChangeOperationException e) {
+ | RestApiException e) {
+ // TODO(dborowitz): Allow Submit to unwrap ResourceConflictException
+ // so it can turn into a 409.
throw new MergeException("Cannot rebase " + n.name(), e);
}
}
@@ -163,6 +167,22 @@
}
}
+ private PatchSet rebase(CodeReviewCommit n, MergeTip mergeTip)
+ throws RestApiException, UpdateException, OrmException {
+ RebaseChangeOp op = rebaseFactory.create(
+ n.getControl(),
+ args.db.patchSets().get(n.getPatchsetId()),
+ mergeTip.getCurrentTip().name())
+ .setCommitterIdent(args.serverIdent.get())
+ .setRunHooks(false)
+ .setValidatePolicy(CommitValidators.Policy.NONE);
+ try (BatchUpdate bu = args.newBatchUpdate(TimeUtil.nowTs())) {
+ bu.addOp(n.change().getId(), op);
+ bu.execute();
+ }
+ return op.getPatchSet();
+ }
+
@Override
public Map<Change.Id, CodeReviewCommit> getNewCommits() {
return newCommits;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index 4034abd..e3247c7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -22,7 +22,9 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeSorter;
import com.google.gerrit.server.git.MergeTip;
@@ -37,8 +39,8 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevWalk;
+import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@@ -55,10 +57,11 @@
protected final IdentifiedUser.GenericFactory identifiedUserFactory;
protected final Provider<PersonIdent> serverIdent;
protected final ReviewDb db;
+ protected final BatchUpdate.Factory batchUpdateFactory;
protected final ChangeControl.GenericFactory changeControlFactory;
protected final Repository repo;
- protected final RevWalk rw;
+ protected final CodeReviewRevWalk rw;
protected final ObjectInserter inserter;
protected final RevFlag canMergeFlag;
protected final Set<RevCommit> alreadyAccepted;
@@ -71,14 +74,16 @@
Arguments(IdentifiedUser.GenericFactory identifiedUserFactory,
Provider<PersonIdent> serverIdent, ReviewDb db,
+ BatchUpdate.Factory batchUpdateFactory,
ChangeControl.GenericFactory changeControlFactory, Repository repo,
- RevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag,
+ CodeReviewRevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag,
Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch,
ApprovalsUtil approvalsUtil, MergeUtil mergeUtil,
ChangeIndexer indexer, IdentifiedUser caller) {
this.identifiedUserFactory = identifiedUserFactory;
this.serverIdent = serverIdent;
this.db = db;
+ this.batchUpdateFactory = batchUpdateFactory;
this.changeControlFactory = changeControlFactory;
this.repo = repo;
@@ -93,6 +98,12 @@
this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
this.caller = caller;
}
+
+ BatchUpdate newBatchUpdate(Timestamp when) {
+ return batchUpdateFactory
+ .create(db, destBranch.getParentKey(), caller, when)
+ .setRepository(repo, rw, inserter);
+ }
}
protected final Arguments args;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index b87499a..05aee25 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -20,8 +20,9 @@
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.RebaseChange;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.change.RebaseChangeOp;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.index.ChangeIndexer;
@@ -39,7 +40,6 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,10 +53,10 @@
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final Provider<PersonIdent> myIdent;
+ private final BatchUpdate.Factory batchUpdateFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
- private final GitReferenceUpdated gitRefUpdated;
- private final RebaseChange rebaseChange;
+ private final RebaseChangeOp.Factory rebaseFactory;
private final ProjectCache projectCache;
private final ApprovalsUtil approvalsUtil;
private final MergeUtil.Factory mergeUtilFactory;
@@ -66,39 +66,40 @@
SubmitStrategyFactory(
final IdentifiedUser.GenericFactory identifiedUserFactory,
@GerritPersonIdent Provider<PersonIdent> myIdent,
+ final BatchUpdate.Factory batchUpdateFactory,
final ChangeControl.GenericFactory changeControlFactory,
final PatchSetInfoFactory patchSetInfoFactory,
- final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
+ final RebaseChangeOp.Factory rebaseFactory,
final ProjectCache projectCache,
final ApprovalsUtil approvalsUtil,
final MergeUtil.Factory mergeUtilFactory,
final ChangeIndexer indexer) {
this.identifiedUserFactory = identifiedUserFactory;
this.myIdent = myIdent;
+ this.batchUpdateFactory = batchUpdateFactory;
this.changeControlFactory = changeControlFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
- this.gitRefUpdated = gitRefUpdated;
- this.rebaseChange = rebaseChange;
+ this.rebaseFactory = rebaseFactory;
this.projectCache = projectCache;
this.approvalsUtil = approvalsUtil;
this.mergeUtilFactory = mergeUtilFactory;
this.indexer = indexer;
}
- public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
- final Repository repo, final RevWalk rw, final ObjectInserter inserter,
- final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
- final Branch.NameKey destBranch, final IdentifiedUser caller)
+ public SubmitStrategy create(SubmitType submitType, ReviewDb db,
+ Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter,
+ RevFlag canMergeFlag, Set<RevCommit> alreadyAccepted,
+ Branch.NameKey destBranch, IdentifiedUser caller)
throws MergeException, NoSuchProjectException {
ProjectState project = getProject(destBranch);
- final SubmitStrategy.Arguments args =
- new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db,
- changeControlFactory, repo, rw, inserter, canMergeFlag,
- alreadyAccepted, destBranch,approvalsUtil,
- mergeUtilFactory.create(project), indexer, caller);
+ SubmitStrategy.Arguments args = new SubmitStrategy.Arguments(
+ identifiedUserFactory, myIdent, db, batchUpdateFactory,
+ changeControlFactory, repo, rw, inserter, canMergeFlag, alreadyAccepted,
+ destBranch,approvalsUtil, mergeUtilFactory.create(project), indexer,
+ caller);
switch (submitType) {
case CHERRY_PICK:
- return new CherryPick(args, patchSetInfoFactory, gitRefUpdated);
+ return new CherryPick(args, patchSetInfoFactory);
case FAST_FORWARD_ONLY:
return new FastForwardOnly(args);
case MERGE_ALWAYS:
@@ -106,7 +107,7 @@
case MERGE_IF_NECESSARY:
return new MergeIfNecessary(args);
case REBASE_IF_NECESSARY:
- return new RebaseIfNecessary(args, patchSetInfoFactory, rebaseChange);
+ return new RebaseIfNecessary(args, patchSetInfoFactory, rebaseFactory);
default:
final String errorMsg = "No submit strategy for: " + submitType;
log.error(errorMsg);
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 d8c3303..a48260e 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
@@ -14,12 +14,17 @@
package com.google.gerrit.server.git.validators;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+
import com.google.common.base.CharMatcher;
+import com.google.gerrit.common.ChangeHookRunner.HookResult;
+import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -39,6 +44,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
@@ -60,6 +66,17 @@
private static final Logger log = LoggerFactory
.getLogger(CommitValidators.class);
+ public static enum Policy {
+ /** Use {@link #validateForGerritCommits}. */
+ GERRIT,
+
+ /** Use {@link #validateForReceiveCommits}. */
+ RECEIVE_COMMITS,
+
+ /** Do not validate commits. */
+ NONE
+ }
+
public interface Factory {
CommitValidators create(RefControl refControl, SshInfo sshInfo,
Repository repo);
@@ -71,6 +88,7 @@
private final String installCommitMsgHookCommand;
private final SshInfo sshInfo;
private final Repository repo;
+ private final ChangeHooks hooks;
private final DynamicSet<CommitValidationListener> commitValidationListeners;
@Inject
@@ -78,6 +96,7 @@
@CanonicalWebUrl @Nullable final String canonicalWebUrl,
@GerritServerConfig final Config config,
final DynamicSet<CommitValidationListener> commitValidationListeners,
+ final ChangeHooks hooks,
@Assisted final SshInfo sshInfo,
@Assisted final Repository repo, @Assisted final RefControl refControl) {
this.gerritIdent = gerritIdent;
@@ -87,6 +106,7 @@
config.getString("gerrit", null, "installCommitMsgHookCommand");
this.sshInfo = sshInfo;
this.repo = repo;
+ this.hooks = hooks;
this.commitValidationListeners = commitValidationListeners;
}
@@ -144,6 +164,7 @@
}
validators.add(new ConfigValidator(refControl, repo));
validators.add(new PluginCommitValidationListener(commitValidationListeners));
+ validators.add(new ChangeHookValidator(refControl, hooks));
List<CommitValidationMessage> messages = new LinkedList<>();
@@ -172,7 +193,7 @@
this.canonicalWebUrl = canonicalWebUrl;
this.installCommitMsgHookCommand = installCommitMsgHookCommand;
this.sshInfo = sshInfo;
- this.user = (IdentifiedUser) projectControl.getCurrentUser();
+ this.user = projectControl.getUser().asIdentifiedUser();
}
@Override
@@ -295,9 +316,9 @@
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
- IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
- if (RefNames.REFS_CONFIG.equals(refControl.getRefName())) {
+ if (REFS_CONFIG.equals(refControl.getRefName())) {
List<CommitValidationMessage> messages = new LinkedList<>();
try {
@@ -381,7 +402,7 @@
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
- IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
final PersonIdent author = receiveEvent.commit.getAuthorIdent();
final ProjectControl projectControl = refControl.getProjectControl();
@@ -424,7 +445,7 @@
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
- IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
final PersonIdent author = receiveEvent.commit.getAuthorIdent();
if (!currentUser.hasEmailAddress(author.getEmailAddress())
@@ -454,7 +475,7 @@
@Override
public List<CommitValidationMessage> onCommitReceived(
CommitReceivedEvent receiveEvent) throws CommitValidationException {
- IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
+ IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
if (!currentUser.hasEmailAddress(committer.getEmailAddress())
&& !refControl.canForgeCommitter()) {
@@ -524,6 +545,48 @@
}
}
+ /** Reject commits that don't pass user-supplied ref-update hook. */
+ public static class ChangeHookValidator implements
+ CommitValidationListener {
+ private final RefControl refControl;
+ private final ChangeHooks hooks;
+
+ public ChangeHookValidator(RefControl refControl, ChangeHooks hooks) {
+ this.refControl = refControl;
+ this.hooks = hooks;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+
+ if (refControl.getUser().isIdentifiedUser()) {
+ IdentifiedUser user = refControl.getUser().asIdentifiedUser();
+
+ String refname = receiveEvent.refName;
+ ObjectId old = receiveEvent.commit.getParent(0);
+
+ if (receiveEvent.command.getRefName().startsWith(REFS_CHANGES)) {
+ /*
+ * If the ref-update hook tries to distinguish behavior between pushes to
+ * refs/heads/... and refs/for/..., make sure we send it the correct refname.
+ * Also, if this is targetting refs/for/, make sure we behave the same as
+ * what a push to refs/for/ would behave; in particular, setting oldrev to
+ * 0000000000000000000000000000000000000000.
+ */
+ refname = refname.replace(R_HEADS, "refs/for/refs/heads/");
+ old = ObjectId.zeroId();
+ }
+ HookResult result = hooks.doRefUpdateHook(receiveEvent.project, refname,
+ user.getAccount(), old, receiveEvent.commit);
+ if (result != null && result.getExitValue() != 0) {
+ throw new CommitValidationException(result.toString().trim());
+ }
+ }
+ return Collections.emptyList();
+ }
+ }
+
private static CommitValidationMessage getInvalidEmailError(RevCommit c, String type,
PersonIdent who, IdentifiedUser currentUser, String canonicalWebUrl) {
StringBuilder sb = new StringBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationListener.java
index fefe02a..2c032c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationListener.java
@@ -49,7 +49,7 @@
* These may be RevObject or RevCommit if the processor parsed them.
* Implementors should not rely on the values being parsed.
* @throws ValidationException to block the upload and send a message
- * back to the end-used over the client's protocol connection.
+ * back to the end-user over the client's protocol connection.
*/
public void onPreUpload(Repository repository, Project project,
String remoteHost, UploadPack up, Collection<? extends ObjectId> wants,
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 5155e25..c274a37 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
@@ -31,7 +31,6 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
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;
@@ -101,7 +100,7 @@
GroupControl control = resource.getControl();
Map<AccountGroup.UUID, AccountGroupById> newIncludedGroups = Maps.newHashMap();
List<GroupInfo> result = Lists.newLinkedList();
- Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId();
+ Account.Id me = control.getUser().getAccountId();
for (String includedGroup : input.groups) {
GroupDescription.Basic d = groupsCollection.parse(includedGroup);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
index 0986584..4e67c02 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
@@ -23,7 +23,6 @@
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -96,7 +95,7 @@
@Override
public GroupInfo apply(TopLevelResource resource, GroupInput input)
- throws AuthException, BadRequestException, UnprocessableEntityException,
+ throws BadRequestException, UnprocessableEntityException,
ResourceConflictException, OrmException {
if (input == null) {
input = new GroupInput();
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 3d99565..bde8fb7 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
@@ -29,7 +29,6 @@
import com.google.gerrit.reviewdb.client.AccountGroupById;
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;
@@ -110,7 +109,7 @@
}
private void writeAudits(final List<AccountGroupById> toRemoved) {
- final Account.Id me = ((IdentifiedUser) self.get()).getAccountId();
+ final Account.Id me = self.get().getAccountId();
auditService.dispatchDeleteGroupsFromGroup(me, toRemoved);
}
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 3047994..b14974b 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
@@ -27,7 +27,6 @@
import com.google.gerrit.reviewdb.client.AccountGroupMember;
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.AccountCache;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.GroupControl;
@@ -97,7 +96,7 @@
}
private void writeAudits(final List<AccountGroupMember> toRemove) {
- final Account.Id me = ((IdentifiedUser) self.get()).getAccountId();
+ final Account.Id me = self.get().getAccountId();
auditService.dispatchDeleteAccountsFromGroup(me, toRemove);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index bf5193f..f3a2ea2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -16,14 +16,17 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.client.ListGroupsOption;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
@@ -32,6 +35,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.GetGroups;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupComparator;
import com.google.gerrit.server.account.GroupControl;
@@ -64,6 +68,7 @@
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<GetGroups> accountGetGroups;
private final GroupJson json;
+ private final GroupBackend groupBackend;
private EnumSet<ListGroupsOption> options =
EnumSet.noneOf(ListGroupsOption.class);
@@ -73,6 +78,7 @@
private int limit;
private int start;
private String matchSubstring;
+ private String suggest;
@Option(name = "--project", aliases = {"-p"},
usage = "projects for which the groups should be listed")
@@ -121,6 +127,11 @@
this.matchSubstring = matchSubstring;
}
+ @Option(name = "--suggest", usage = "to get a suggestion of groups")
+ public void setSuggest(String suggest) {
+ this.suggest = suggest;
+ }
+
@Option(name = "-o", usage = "Output options per group")
void addOption(ListGroupsOption o) {
options.add(o);
@@ -137,7 +148,8 @@
final GroupControl.GenericFactory genericGroupControlFactory,
final Provider<IdentifiedUser> identifiedUser,
final IdentifiedUser.GenericFactory userFactory,
- final Provider<GetGroups> accountGetGroups, GroupJson json) {
+ final Provider<GetGroups> accountGetGroups, GroupJson json,
+ GroupBackend groupBackend) {
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.genericGroupControlFactory = genericGroupControlFactory;
@@ -145,6 +157,7 @@
this.userFactory = userFactory;
this.accountGetGroups = accountGetGroups;
this.json = json;
+ this.groupBackend = groupBackend;
}
public void setOptions(EnumSet<ListGroupsOption> options) {
@@ -161,7 +174,7 @@
@Override
public SortedMap<String, GroupInfo> apply(TopLevelResource resource)
- throws OrmException {
+ throws OrmException, BadRequestException {
SortedMap<String, GroupInfo> output = Maps.newTreeMap();
for (GroupInfo info : get()) {
output.put(MoreObjects.firstNonNull(
@@ -172,53 +185,107 @@
return output;
}
- public List<GroupInfo> get() throws OrmException {
- List<GroupInfo> groupInfos;
+ public List<GroupInfo> get() throws OrmException, BadRequestException {
+ if (!Strings.isNullOrEmpty(suggest)) {
+ return suggestGroups();
+ }
+
+ if (owned) {
+ return getGroupsOwnedBy(
+ user != null ? userFactory.create(user) : identifiedUser.get());
+ }
+
if (user != null) {
- if (owned) {
- groupInfos = getGroupsOwnedBy(userFactory.create(user));
- } else {
- groupInfos = accountGetGroups.get().apply(
- new AccountResource(userFactory.create(user)));
+ return accountGetGroups.get().apply(
+ new AccountResource(userFactory.create(user)));
+ }
+
+ return getAllGroups();
+ }
+
+ private List<GroupInfo> getAllGroups() throws OrmException {
+ List<GroupInfo> groupInfos;
+ List<AccountGroup> groupList;
+ if (!projects.isEmpty()) {
+ Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap();
+ for (final ProjectControl projectControl : projects) {
+ final Set<GroupReference> groupsRefs = projectControl.getAllGroups();
+ for (final GroupReference groupRef : groupsRefs) {
+ final AccountGroup group = groupCache.get(groupRef.getUUID());
+ if (group != null) {
+ groups.put(group.getGroupUUID(), group);
+ }
+ }
}
+ groupList = filterGroups(groups.values());
} else {
- if (owned) {
- groupInfos = getGroupsOwnedBy(identifiedUser.get());
- } else {
- List<AccountGroup> groupList;
- if (!projects.isEmpty()) {
- Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap();
- for (final ProjectControl projectControl : projects) {
- final Set<GroupReference> groupsRefs = projectControl.getAllGroups();
- for (final GroupReference groupRef : groupsRefs) {
- final AccountGroup group = groupCache.get(groupRef.getUUID());
- if (group != null) {
- groups.put(group.getGroupUUID(), group);
- }
- }
- }
- groupList = filterGroups(groups.values());
- } else {
- groupList = filterGroups(groupCache.all());
- }
- groupInfos = Lists.newArrayListWithCapacity(groupList.size());
- int found = 0;
- int foundIndex = 0;
- for (AccountGroup group : groupList) {
- if (foundIndex++ < start) {
- continue;
- }
- if (limit > 0 && ++found > limit) {
- break;
- }
- groupInfos.add(json.addOptions(options).format(
- GroupDescriptions.forAccountGroup(group)));
- }
+ groupList = filterGroups(groupCache.all());
+ }
+ groupInfos = Lists.newArrayListWithCapacity(groupList.size());
+ int found = 0;
+ int foundIndex = 0;
+ for (AccountGroup group : groupList) {
+ if (foundIndex++ < start) {
+ continue;
+ }
+ if (limit > 0 && ++found > limit) {
+ break;
+ }
+ groupInfos.add(json.addOptions(options).format(
+ GroupDescriptions.forAccountGroup(group)));
+ }
+ return groupInfos;
+ }
+
+ private List<GroupInfo> suggestGroups() throws OrmException, BadRequestException {
+ if (conflictingSuggestParameters()) {
+ throw new BadRequestException(
+ "You should only have no more than one --project and -n with --suggest");
+ }
+
+ List<GroupReference> groupRefs = Lists.newArrayList(Iterables.limit(
+ groupBackend.suggest(
+ suggest, Iterables.getFirst(projects, null)),
+ limit <= 0 ? 10 : Math.min(limit, 10)));
+
+ List<GroupInfo> groupInfos = Lists.newArrayListWithCapacity(groupRefs.size());
+ for (final GroupReference ref : groupRefs) {
+ GroupDescription.Basic desc = groupBackend.get(ref.getUUID());
+ if (desc != null) {
+ groupInfos.add(json.addOptions(options).format(desc));
}
}
return groupInfos;
}
+ private boolean conflictingSuggestParameters() {
+ if (Strings.isNullOrEmpty(suggest)) {
+ return false;
+ }
+ if (projects.size() > 1) {
+ return true;
+ }
+ if (visibleToAll) {
+ return true;
+ }
+ if (user != null) {
+ return true;
+ }
+ if (owned) {
+ return true;
+ }
+ if (start != 0) {
+ return true;
+ }
+ if (!groupsToInspect.isEmpty()) {
+ return true;
+ }
+ if (!Strings.isNullOrEmpty(matchSubstring)) {
+ return true;
+ }
+ return false;
+ }
+
private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user)
throws OrmException {
List<GroupInfo> groups = Lists.newArrayList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
index ba28cd1..8266162 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupName;
@@ -71,8 +70,7 @@
@Override
public String apply(GroupResource rsrc, Input input)
throws MethodNotAllowedException, AuthException, BadRequestException,
- ResourceNotFoundException, ResourceConflictException, OrmException,
- NoSuchGroupException {
+ ResourceConflictException, OrmException, NoSuchGroupException {
if (rsrc.toAccountGroup() == null) {
throw new MethodNotAllowedException();
} else if (!rsrc.getControl().isOwner()) {
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 e711306..4fa5cd3 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
@@ -207,6 +207,21 @@
}
};
+ /** Submission id assigned by MergeOp. */
+ public static final FieldDef<ChangeData, String> SUBMISSIONID =
+ new FieldDef.Single<ChangeData, String>(
+ "submissionid", FieldType.EXACT, false) {
+ @Override
+ public String get(ChangeData input, FillArgs args)
+ throws OrmException {
+ Change c = input.change();
+ if (c == null) {
+ return null;
+ }
+ return c.getSubmissionId();
+ }
+ };
+
/** Last update time since January 1, 1970. */
public static final FieldDef<ChangeData, Timestamp> UPDATED =
new FieldDef.Single<ChangeData, Timestamp>(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
index 5cb5c65..6c484d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
@@ -63,6 +63,15 @@
IndexCollection indexes);
}
+ public static CheckedFuture<?, IOException> allAsList(
+ List<? extends ListenableFuture<?>> futures) {
+ // allAsList propagates the first seen exception, wrapped in
+ // ExecutionException, so we can reuse the same mapper as for a single
+ // future. Assume the actual contents of the exception are not useful to
+ // callers. All exceptions are already logged by IndexTask.
+ return Futures.makeChecked(Futures.allAsList(futures), MAPPER);
+ }
+
private static final Function<Exception, IOException> MAPPER =
new Function<Exception, IOException>() {
@Override
@@ -136,11 +145,7 @@
for (Change.Id id : ids) {
futures.add(indexAsync(id));
}
- // allAsList propagates the first seen exception, wrapped in
- // ExecutionException, so we can reuse the same mapper as for a single
- // future. Assume the actual contents of the exception are not useful to
- // callers. All exceptions are already logged by IndexTask.
- return Futures.makeChecked(Futures.allAsList(futures), MAPPER);
+ return allAsList(futures);
}
/**
@@ -226,7 +231,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
throw new OutOfScopeException("No user during ChangeIndexer");
}
};
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index a8a97a8..4789a14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -377,6 +377,42 @@
ChangeField.AUTHOR,
ChangeField.COMMITTER);
+ static final Schema<ChangeData> V25 = schema(
+ ChangeField.LEGACY_ID2,
+ ChangeField.ID,
+ ChangeField.STATUS,
+ ChangeField.PROJECT,
+ ChangeField.PROJECTS,
+ ChangeField.REF,
+ ChangeField.EXACT_TOPIC,
+ ChangeField.FUZZY_TOPIC,
+ ChangeField.UPDATED,
+ ChangeField.FILE_PART,
+ ChangeField.PATH,
+ ChangeField.OWNER,
+ ChangeField.REVIEWER,
+ ChangeField.COMMIT,
+ ChangeField.TR,
+ ChangeField.LABEL,
+ ChangeField.COMMIT_MESSAGE,
+ ChangeField.COMMENT,
+ ChangeField.CHANGE,
+ ChangeField.APPROVAL,
+ ChangeField.MERGEABLE,
+ ChangeField.ADDED,
+ ChangeField.DELETED,
+ ChangeField.DELTA,
+ ChangeField.HASHTAG,
+ ChangeField.COMMENTBY,
+ ChangeField.PATCH_SET,
+ ChangeField.GROUP,
+ ChangeField.SUBMISSIONID,
+ ChangeField.EDITBY,
+ ChangeField.REVIEWEDBY,
+ ChangeField.EXACT_COMMIT,
+ ChangeField.AUTHOR,
+ ChangeField.COMMITTER);
+
private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
return new Schema<>(ImmutableList.copyOf(fields));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
index e79dc63..00aa081 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
@@ -29,24 +29,27 @@
*/
@AutoValue
public abstract class IndexConfig {
+ private static final int DEFAULT_MAX_TERMS = 500;
private static final int DEFAULT_MAX_PREFIX_TERMS = 100;
public static IndexConfig createDefault() {
- return create(0, 0, DEFAULT_MAX_PREFIX_TERMS);
+ return create(0, 0, DEFAULT_MAX_TERMS, DEFAULT_MAX_PREFIX_TERMS);
}
public static IndexConfig fromConfig(Config cfg) {
return create(
cfg.getInt("index", null, "maxLimit", 0),
cfg.getInt("index", null, "maxPages", 0),
+ cfg.getInt("index", null, "maxTerms", 0),
cfg.getInt("index", null, "maxPrefixTerms", DEFAULT_MAX_PREFIX_TERMS));
}
public static IndexConfig create(int maxLimit, int maxPages,
- int maxPrefixTerms) {
+ int maxTerms, int maxPrefixTerms) {
return new AutoValue_IndexConfig(
checkLimit(maxLimit, "maxLimit", Integer.MAX_VALUE),
checkLimit(maxPages, "maxPages", Integer.MAX_VALUE),
+ checkLimit(maxTerms, "maxTerms", Integer.MAX_VALUE),
checkLimit(maxPrefixTerms, "maxPrefixTerms", DEFAULT_MAX_PREFIX_TERMS));
}
@@ -71,6 +74,12 @@
public abstract int maxPages();
/**
+ * @return maximum number of total index query terms supported by the
+ * underlying index, or limited for performance reasons.
+ */
+ public abstract int maxTerms();
+
+ /**
* @return maximum number of prefix terms per query supported by the
* underlying index, or limited for performance reasons. Not enforced for
* general queries; only for specific cases where the query system can
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
index 27df8c2..8dd7aa3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
@@ -33,6 +33,8 @@
import com.google.gerrit.server.query.change.OrSource;
import com.google.inject.Inject;
+import org.eclipse.jgit.util.MutableInteger;
+
import java.util.BitSet;
import java.util.EnumSet;
import java.util.List;
@@ -117,10 +119,13 @@
}
private final IndexCollection indexes;
+ private final IndexConfig config;
@Inject
- IndexRewriteImpl(IndexCollection indexes) {
+ IndexRewriteImpl(IndexCollection indexes,
+ IndexConfig config) {
this.indexes = indexes;
+ this.config = config;
}
@Override
@@ -132,7 +137,8 @@
// skipped results would have been filtered out by the enclosing AndSource.
limit += start;
- Predicate<ChangeData> out = rewriteImpl(in, index, limit);
+ MutableInteger leafTerms = new MutableInteger();
+ Predicate<ChangeData> out = rewriteImpl(in, index, limit, leafTerms);
if (in == out || out instanceof IndexPredicate) {
return new IndexedChangeQuery(index, out, limit);
} else if (out == null /* cannot rewrite */) {
@@ -148,6 +154,7 @@
* @param in predicate to rewrite.
* @param index index whose schema determines which fields are indexed.
* @param limit maximum number of results to return.
+ * @param leafTerms number of leaf index query terms encountered so far.
* @return {@code null} if no part of this subtree can be queried in the
* index directly. {@code in} if this subtree and all its children can be
* queried directly in the index. Otherwise, a predicate that is
@@ -157,8 +164,12 @@
* support this predicate.
*/
private Predicate<ChangeData> rewriteImpl(Predicate<ChangeData> in,
- ChangeIndex index, int limit) throws QueryParseException {
+ ChangeIndex index, int limit, MutableInteger leafTerms)
+ throws QueryParseException {
if (isIndexPredicate(in, index)) {
+ if (++leafTerms.value > config.maxTerms()) {
+ throw new QueryParseException("too many terms in query");
+ }
return in;
} else if (in instanceof LimitPredicate) {
// Replace any limits with the limit provided by the caller.
@@ -174,7 +185,7 @@
List<Predicate<ChangeData>> newChildren = Lists.newArrayListWithCapacity(n);
for (int i = 0; i < n; i++) {
Predicate<ChangeData> c = in.getChild(i);
- Predicate<ChangeData> nc = rewriteImpl(c, index, limit);
+ Predicate<ChangeData> nc = rewriteImpl(c, index, limit, leafTerms);
if (nc == c) {
isIndexed.set(i);
newChildren.add(c);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/BasicSerialization.java b/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/BasicSerialization.java
index 4210363..8ea6653 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/BasicSerialization.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/BasicSerialization.java
@@ -29,6 +29,8 @@
package com.google.gerrit.server.ioutil;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.reviewdb.client.CodedEnum;
import org.eclipse.jgit.util.IO;
@@ -139,7 +141,7 @@
if (bin.length == 0) {
return null;
}
- return new String(bin, 0, bin.length, "UTF-8");
+ return new String(bin, 0, bin.length, UTF_8);
}
/** Write a UTF-8 string, prefixed by its byte length in a varint. */
@@ -148,7 +150,7 @@
if (s == null) {
writeVarInt32(output, 0);
} else {
- writeBytes(output, s.getBytes("UTF-8"));
+ writeBytes(output, s.getBytes(UTF_8));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
index 4e9ed2b..863cb82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.mail;
-import java.io.UnsupportedEncodingException;
-
public class Address {
public static Address parse(final String in) {
final int lt = in.indexOf('<');
@@ -69,14 +67,10 @@
@Override
public String toString() {
- try {
- return toHeaderString();
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Cannot encode address", e);
- }
+ return toHeaderString();
}
- public String toHeaderString() throws UnsupportedEncodingException {
+ public String toHeaderString() {
if (name != null) {
return quotedPhrase(name) + " <" + email + ">";
} else if (isSimple()) {
@@ -98,8 +92,7 @@
return true;
}
- private static String quotedPhrase(final String name)
- throws UnsupportedEncodingException {
+ private static String quotedPhrase(final String name) {
if (EmailHeader.needsQuotedPrintable(name)) {
return EmailHeader.quotedPrintable(name);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
index 592fbb0..30026bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.mail;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.MoreObjects;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -104,8 +105,7 @@
}
}
- static java.lang.String quotedPrintable(java.lang.String value)
- throws UnsupportedEncodingException {
+ static java.lang.String quotedPrintable(java.lang.String value) {
final StringBuilder r = new StringBuilder();
r.append("=?UTF-8?Q?");
@@ -115,7 +115,7 @@
r.append('_');
} else if (needsQuotedPrintableWithinPhrase(cp)) {
- byte[] buf = new java.lang.String(Character.toChars(cp)).getBytes("UTF-8");
+ byte[] buf = new java.lang.String(Character.toChars(cp)).getBytes(UTF_8);
for (byte b: buf) {
r.append('=');
r.append(Integer.toHexString((b >>> 4) & 0x0f).toUpperCase());
@@ -151,7 +151,7 @@
public void write(Writer w) throws IOException {
final SimpleDateFormat fmt;
// Mon, 1 Jun 2009 10:49:44 -0700
- fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
+ fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
w.write(fmt.format(value));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index 8501426..58bdac1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -26,13 +26,13 @@
* @param emailAddress the address to add.
* @return an unforgeable string to email to {@code emailAddress}. Presenting
* the string provides proof the user has the ability to read messages
- * sent to that address.
+ * sent to that address. Must not be null.
*/
public String encode(Account.Id accountId, String emailAddress);
/**
* Decode a token previously created.
- * @param tokenString the string created by encode.
+ * @param tokenString the string created by encode. Never null.
* @return a pair of account id and email address.
* @throws InvalidTokenException the token is invalid, expired, malformed, etc.
*/
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index a2f369b..7dd51fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.mail;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.collect.Sets;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
@@ -443,7 +445,7 @@
if (runtime.getLoaderNameForResource(name) == null) {
name = "com/google/gerrit/server/mail/" + name;
}
- Template template = runtime.getTemplate(name, "UTF-8");
+ Template template = runtime.getTemplate(name, UTF_8.name());
StringWriter w = new StringWriter();
template.merge(velocityContext, w);
return w.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
deleted file mode 100644
index 80a5d24..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.mail;
-
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
-
-import com.google.gerrit.common.data.LabelTypes;
-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.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mail.MailUtil.MailRecipients;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.FooterLine;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-public class PatchSetNotificationSender {
- private static final Logger log =
- LoggerFactory.getLogger(PatchSetNotificationSender.class);
-
- private final Provider<ReviewDb> db;
- private final GitRepositoryManager repoManager;
- private final PatchSetInfoFactory patchSetInfoFactory;
- private final ApprovalsUtil approvalsUtil;
- private final AccountResolver accountResolver;
- private final CreateChangeSender.Factory createChangeSenderFactory;
- private final ReplacePatchSetSender.Factory replacePatchSetFactory;
-
- @Inject
- public PatchSetNotificationSender(Provider<ReviewDb> db,
- GitRepositoryManager repoManager,
- PatchSetInfoFactory patchSetInfoFactory,
- ApprovalsUtil approvalsUtil,
- AccountResolver accountResolver,
- CreateChangeSender.Factory createChangeSenderFactory,
- ReplacePatchSetSender.Factory replacePatchSetFactory) {
- this.db = db;
- this.repoManager = repoManager;
- this.patchSetInfoFactory = patchSetInfoFactory;
- this.approvalsUtil = approvalsUtil;
- this.accountResolver = accountResolver;
- this.createChangeSenderFactory = createChangeSenderFactory;
- this.replacePatchSetFactory = replacePatchSetFactory;
- }
-
- public void send(final ChangeNotes notes, final ChangeUpdate update,
- final boolean newChange, final IdentifiedUser currentUser,
- final Change updatedChange, final PatchSet updatedPatchSet,
- final LabelTypes labelTypes)
- throws OrmException, IOException {
- try (Repository git = repoManager.openRepository(updatedChange.getProject())) {
- final RevCommit commit;
- try (RevWalk revWalk = new RevWalk(git)) {
- commit = revWalk.parseCommit(ObjectId.fromString(
- updatedPatchSet.getRevision().get()));
- }
- final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
- final List<FooterLine> footerLines = commit.getFooterLines();
- final Account.Id me = currentUser.getAccountId();
- final MailRecipients recipients =
- getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
- recipients.remove(me);
-
- if (newChange) {
- approvalsUtil.addReviewers(db.get(), update, labelTypes, updatedChange,
- updatedPatchSet, info, recipients.getReviewers(),
- Collections.<Account.Id> emptySet());
- try {
- CreateChangeSender cm =
- createChangeSenderFactory.create(updatedChange.getId());
- cm.setFrom(me);
- cm.setPatchSet(updatedPatchSet, info);
- cm.addReviewers(recipients.getReviewers());
- cm.addExtraCC(recipients.getCcOnly());
- cm.send();
- } catch (Exception e) {
- log.error("Cannot send email for new change " + updatedChange.getId(), e);
- }
- } else {
- approvalsUtil.addReviewers(db.get(), update, labelTypes, updatedChange,
- updatedPatchSet, info, recipients.getReviewers(),
- approvalsUtil.getReviewers(db.get(), notes).values());
- final ChangeMessage msg =
- new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
- ChangeUtil.messageUUID(db.get())), me,
- updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
- msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
- try {
- ReplacePatchSetSender cm =
- replacePatchSetFactory.create(updatedChange.getId());
- cm.setFrom(me);
- cm.setPatchSet(updatedPatchSet, info);
- cm.setChangeMessage(msg);
- cm.addReviewers(recipients.getReviewers());
- cm.addExtraCC(recipients.getCcOnly());
- cm.send();
- } catch (Exception e) {
- log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
- }
- }
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
index beada69..c4d374f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.mail;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
@@ -63,7 +65,8 @@
public String getEmailRegistrationToken() {
if (emailToken == null) {
- emailToken = tokenVerifier.encode(user.getAccountId(), addr);
+ emailToken = checkNotNull(
+ tokenVerifier.encode(user.getAccountId(), addr), "token");
}
return emailToken;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 29bce1c..f12859f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.mail;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtjsonrpc.server.SignedToken;
@@ -25,7 +27,6 @@
import org.eclipse.jgit.util.Base64;
-import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -50,10 +51,10 @@
public String encode(Account.Id accountId, String emailAddress) {
try {
String payload = String.format("%s:%s", accountId, emailAddress);
- byte[] utf8 = payload.getBytes("UTF-8");
+ byte[] utf8 = payload.getBytes(UTF_8);
String base64 = Base64.encodeBytes(utf8);
return emailRegistrationToken.newToken(base64);
- } catch (XsrfException | UnsupportedEncodingException e) {
+ } catch (XsrfException e) {
throw new IllegalArgumentException(e);
}
}
@@ -70,13 +71,7 @@
throw new InvalidTokenException();
}
- String payload;
- try {
- payload = new String(Base64.decode(token.getData()), "UTF-8");
- } catch (UnsupportedEncodingException err) {
- throw new InvalidTokenException(err);
- }
-
+ String payload = new String(Base64.decode(token.getData()), UTF_8);
Matcher matcher = Pattern.compile("^([0-9]+):(.+@.+)$").matcher(payload);
if (!matcher.matches()) {
throw new InvalidTokenException();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
index 785093a..8bd6633 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.mail;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.primitives.Ints;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.Version;
@@ -241,7 +243,7 @@
}
private SMTPClient open() throws EmailException {
- final AuthSMTPClient client = new AuthSMTPClient("UTF-8");
+ final AuthSMTPClient client = new AuthSMTPClient(UTF_8.name());
if (smtpEncryption == Encryption.SSL) {
client.enableSSL(sslVerify);
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 96322d2..fb41027 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
@@ -78,7 +78,7 @@
}
public IdentifiedUser getUser() {
- return (IdentifiedUser) ctl.getCurrentUser();
+ return ctl.getUser().asIdentifiedUser();
}
public PatchSet.Id getPatchSetId() {
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
index 47c1731..bd8f797 100644
--- 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
@@ -93,9 +93,9 @@
anonymousCowardName, when);
this.draftsProject = allUsers;
this.commentsUtil = commentsUtil;
- checkState(ctl.getCurrentUser().isIdentifiedUser(),
+ checkState(ctl.getUser().isIdentifiedUser(),
"Current user must be identified");
- IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+ IdentifiedUser user = ctl.getUser().asIdentifiedUser();
this.accountId = user.getAccountId();
this.changeNotes = getChangeNotes().load();
this.draftNotes = draftNotesFactory.create(ctl.getChange().getId(),
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 e6d9ff8..d63f972 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
@@ -84,11 +84,15 @@
public static Config allEnabledConfig() {
Config cfg = new Config();
+ setAllEnabledConfig(cfg);
+ return cfg;
+ }
+
+ public static void setAllEnabledConfig(Config cfg) {
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;
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 e7c56be..5de28f3 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
@@ -16,10 +16,12 @@
import static com.google.gerrit.server.ioutil.BasicSerialization.readBytes;
import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum;
+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;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
@@ -49,7 +51,7 @@
static PatchListEntry empty(final String fileName) {
return new PatchListEntry(ChangeType.MODIFIED, PatchType.UNIFIED, null,
- fileName, EMPTY_HEADER, Collections.<Edit> emptyList(), 0, 0);
+ fileName, EMPTY_HEADER, Collections.<Edit> emptyList(), 0, 0, 0);
}
private final ChangeType changeType;
@@ -60,8 +62,11 @@
private final List<Edit> edits;
private final int insertions;
private final int deletions;
+ private final long sizeDelta;
+ // Note: When adding new fields, the serialVersionUID in PatchListKey must be
+ // incremented so that entries from the cache are automatically invalidated.
- PatchListEntry(final FileHeader hdr, List<Edit> editList) {
+ PatchListEntry(FileHeader hdr, List<Edit> editList, long sizeDelta) {
changeType = toChangeType(hdr);
patchType = toPatchType(hdr);
@@ -106,12 +111,12 @@
}
insertions = ins;
deletions = del;
+ this.sizeDelta = sizeDelta;
}
- private PatchListEntry(final ChangeType changeType,
- final PatchType patchType, final String oldName, final String newName,
- final byte[] header, final List<Edit> edits, final int insertions,
- final int deletions) {
+ private PatchListEntry(ChangeType changeType, PatchType patchType,
+ String oldName, String newName, byte[] header, List<Edit> edits,
+ int insertions, int deletions, long sizeDelta) {
this.changeType = changeType;
this.patchType = patchType;
this.oldName = oldName;
@@ -120,6 +125,7 @@
this.edits = edits;
this.insertions = insertions;
this.deletions = deletions;
+ this.sizeDelta = sizeDelta;
}
int weigh() {
@@ -166,6 +172,10 @@
return deletions;
}
+ public long getSizeDelta() {
+ return sizeDelta;
+ }
+
public List<String> getHeaderLines() {
final IntList m = RawParseUtils.lineMap(header, 0, header.length);
final List<String> headerLines = new ArrayList<>(m.size() - 1);
@@ -190,7 +200,7 @@
return p;
}
- void writeTo(final OutputStream out) throws IOException {
+ void writeTo(OutputStream out) throws IOException {
writeEnum(out, changeType);
writeEnum(out, patchType);
writeString(out, oldName);
@@ -198,6 +208,7 @@
writeBytes(out, header);
writeVarInt32(out, insertions);
writeVarInt32(out, deletions);
+ writeFixInt64(out, sizeDelta);
writeVarInt32(out, edits.size());
for (final Edit e : edits) {
@@ -208,17 +219,18 @@
}
}
- static PatchListEntry readFrom(final InputStream in) throws IOException {
- final ChangeType changeType = readEnum(in, ChangeType.values());
- final PatchType patchType = readEnum(in, PatchType.values());
- final String oldName = readString(in);
- final String newName = readString(in);
- final byte[] hdr = readBytes(in);
- final int ins = readVarInt32(in);
- final int del = readVarInt32(in);
+ static PatchListEntry readFrom(InputStream in) throws IOException {
+ ChangeType changeType = readEnum(in, ChangeType.values());
+ PatchType patchType = readEnum(in, PatchType.values());
+ String oldName = readString(in);
+ String newName = readString(in);
+ byte[] hdr = readBytes(in);
+ int ins = readVarInt32(in);
+ int del = readVarInt32(in);
+ long sizeDelta = readFixInt64(in);
- final int editCount = readVarInt32(in);
- final Edit[] editArray = new Edit[editCount];
+ int editCount = readVarInt32(in);
+ Edit[] editArray = new Edit[editCount];
for (int i = 0; i < editCount; i++) {
int beginA = readVarInt32(in);
int endA = readVarInt32(in);
@@ -228,7 +240,7 @@
}
return new PatchListEntry(changeType, patchType, oldName, newName, hdr,
- toList(editArray), ins, del);
+ toList(editArray), ins, del, sizeDelta);
}
private static List<Edit> toList(Edit[] l) {
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 b645e6c..6f957da 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
@@ -33,7 +33,7 @@
import java.io.Serializable;
public class PatchListKey implements Serializable {
- static final long serialVersionUID = 17L;
+ static final long serialVersionUID = 18L;
private transient ObjectId oldId;
private transient ObjectId newId;
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 5cc8b87..0e421db 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
@@ -15,6 +15,9 @@
package com.google.gerrit.server.patch;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
@@ -59,6 +62,7 @@
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.slf4j.Logger;
@@ -93,6 +97,7 @@
private final PatchListKey key;
private final Project.NameKey project;
private final long timeoutMillis;
+ private final Object lock;
@AssistedInject
PatchListLoader(GitRepositoryManager mgr,
@@ -107,6 +112,7 @@
diffExecutor = de;
key = k;
project = p;
+ lock = new Object();
timeoutMillis =
ConfigUtil.getTimeUnit(cfg, "cache", PatchListCacheImpl.FILE_NAME,
"timeout", TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS),
@@ -191,11 +197,16 @@
entries.add(newCommitMessage(cmp, reader,
againstParent ? null : aCommit, b));
for (int i = 0; i < cnt; i++) {
- DiffEntry diffEntry = diffEntries.get(i);
- if (paths == null || paths.contains(diffEntry.getNewPath())
- || paths.contains(diffEntry.getOldPath())) {
- FileHeader fh = toFileHeader(key, df, diffEntry);
- entries.add(newEntry(aTree, fh));
+ DiffEntry e = diffEntries.get(i);
+ if (paths == null || paths.contains(e.getNewPath())
+ || paths.contains(e.getOldPath())) {
+
+ FileHeader fh = toFileHeader(key, df, e);
+ long oldSize =
+ getFileSize(repo, reader, e.getOldMode(), e.getOldPath(), aTree);
+ long newSize =
+ getFileSize(repo, reader, e.getNewMode(), e.getNewPath(), bTree);
+ entries.add(newEntry(aTree, fh, newSize - oldSize));
}
}
return new PatchList(a, b, againstParent,
@@ -203,6 +214,23 @@
}
}
+ private static long getFileSize(Repository repo, ObjectReader reader,
+ FileMode mode, String path, RevTree t) throws IOException {
+ if (!isBlob(mode)) {
+ return 0;
+ }
+ try (TreeWalk tw = TreeWalk.forPath(reader, path, t)) {
+ return tw != null
+ ? repo.open(tw.getObjectId(0), OBJ_BLOB).getSize()
+ : 0;
+ }
+ }
+
+ private static boolean isBlob(FileMode mode) {
+ int t = mode.getBits() & FileMode.TYPE_MASK;
+ return t == FileMode.TYPE_FILE || t == FileMode.TYPE_SYMLINK;
+ }
+
private FileHeader toFileHeader(PatchListKey key,
final DiffFormatter diffFormatter, final DiffEntry diffEntry)
throws IOException {
@@ -210,7 +238,9 @@
Future<FileHeader> result = diffExecutor.submit(new Callable<FileHeader>() {
@Override
public FileHeader call() throws IOException {
- return diffFormatter.toFileHeader(diffEntry);
+ synchronized (lock) {
+ return diffFormatter.toFileHeader(diffEntry);
+ }
}
});
@@ -224,7 +254,9 @@
+ " comparing " + diffEntry.getOldId().name()
+ ".." + diffEntry.getNewId().name());
result.cancel(true);
- return toFileHeaderWithoutMyersDiff(diffFormatter, diffEntry);
+ synchronized (lock) {
+ return toFileHeaderWithoutMyersDiff(diffFormatter, diffEntry);
+ }
} catch (ExecutionException e) {
// If there was an error computing the result, carry it
// up to the caller so the cache knows this key is invalid.
@@ -266,33 +298,40 @@
aCommit != null ? Text.forCommit(reader, aCommit) : Text.EMPTY;
Text bText = Text.forCommit(reader, bCommit);
- byte[] rawHdr = hdr.toString().getBytes("UTF-8");
- RawText aRawText = new RawText(aText.getContent());
- RawText bRawText = new RawText(bText.getContent());
+ byte[] rawHdr = hdr.toString().getBytes(UTF_8);
+ byte[] aContent = aText.getContent();
+ byte[] bContent = bText.getContent();
+ long sizeDelta = bContent.length - aContent.length;
+ RawText aRawText = new RawText(aContent);
+ RawText bRawText = new RawText(bContent);
EditList edits = new HistogramDiff().diff(cmp, aRawText, bRawText);
FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
- return new PatchListEntry(fh, edits);
+ return new PatchListEntry(fh, edits, sizeDelta);
}
- private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader) {
+ private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader,
+ long sizeDelta) {
final FileMode oldMode = fileHeader.getOldMode();
final FileMode newMode = fileHeader.getNewMode();
if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList(),
+ sizeDelta);
}
if (aTree == null // want combined diff
|| fileHeader.getPatchType() != PatchType.UNIFIED
|| fileHeader.getHunks().isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList(),
+ sizeDelta);
}
List<Edit> edits = fileHeader.toEditList();
if (edits.isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList(),
+ sizeDelta);
} else {
- return new PatchListEntry(fileHeader, edits);
+ return new PatchListEntry(fileHeader, edits, sizeDelta);
}
}
@@ -393,7 +432,7 @@
MergeResult<? extends Sequence> p = entry.getValue();
try (TemporaryBuffer buf =
new TemporaryBuffer.LocalFile(null, 10 * 1024 * 1024)) {
- fmt.formatMerge(buf, p, "BASE", oursName, theirsName, "UTF-8");
+ fmt.formatMerge(buf, p, "BASE", oursName, theirsName, UTF_8.name());
buf.close();
try (InputStream in = buf.openInputStream()) {
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 802a837..2169671 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
@@ -30,7 +30,6 @@
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.PatchLineCommentsUtil;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.edit.ChangeEdit;
@@ -314,9 +313,9 @@
break;
}
- final CurrentUser user = control.getCurrentUser();
+ final CurrentUser user = control.getUser();
if (user.isIdentifiedUser()) {
- final Account.Id me = ((IdentifiedUser) user).getAccountId();
+ final Account.Id me = user.getAccountId();
switch (changeType) {
case ADDED:
case MODIFIED:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 83856db..16598b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -56,7 +56,9 @@
this.byEmailCache = byEmailCache;
}
- public PatchSetInfo get(RevCommit src, PatchSet.Id psi) {
+ public PatchSetInfo get(RevWalk rw, RevCommit src, PatchSet.Id psi)
+ throws IOException {
+ rw.parseBody(src);
PatchSetInfo info = new PatchSetInfo(psi);
info.setSubject(src.getShortMessage());
info.setMessage(src.getFullMessage());
@@ -83,7 +85,7 @@
RevWalk rw = new RevWalk(repo)) {
final RevCommit src =
rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- PatchSetInfo info = get(src, patchSet.getId());
+ PatchSetInfo info = get(rw, src, patchSet.getId());
info.setParents(toParentInfos(src.getParents(), rw));
return info;
} catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
index 882e25f..7982479 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.patch;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -37,7 +40,6 @@
public class Text extends RawText {
private static final Logger log = LoggerFactory.getLogger(Text.class);
- private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int bigFileThreshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
public static final byte[] NO_BYTES = {};
@@ -81,7 +83,7 @@
appendPersonIdent(b, "Commit", c.getCommitterIdent());
b.append("\n");
b.append(c.getFullMessage());
- return new Text(b.toString().getBytes("UTF-8"));
+ return new Text(b.toString().getBytes(UTF_8));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index 6eb336d..d94df9c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -102,19 +102,19 @@
throw new InvalidPluginException("Cannot auto-register", err);
} catch (RuntimeException err) {
PluginLoader.log.warn(String.format(
- "Plugin %s has invaild class file %s inside of %s", pluginName,
+ "Plugin %s has invalid class file %s inside of %s", pluginName,
entry.getName(), jarFile.getName()), err);
continue;
}
- if (def.isConcrete()) {
- if (!Strings.isNullOrEmpty(def.annotationName)) {
- rawMap.put(def.annotationName, def);
+ if (!Strings.isNullOrEmpty(def.annotationName)) {
+ if (def.isConcrete()) {
+ rawMap.put(def.annotationName, def);
+ } else {
+ PluginLoader.log.warn(String.format(
+ "Plugin %s tries to @%s(\"%s\") abstract class %s", pluginName,
+ def.annotationName, def.annotationValue, def.className));
}
- } else {
- PluginLoader.log.warn(String.format(
- "Plugin %s tries to @%s(\"%s\") abstract class %s", pluginName,
- def.annotationName, def.annotationValue, def.className));
}
}
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 ef153ce..f0c2b78 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
@@ -60,7 +60,7 @@
@Override
public BanResultInfo apply(ProjectResource rsrc, Input input)
throws UnprocessableEntityException, AuthException,
- ResourceConflictException, IOException, InterruptedException {
+ ResourceConflictException, IOException {
BanResultInfo r = new BanResultInfo();
if (input != null && input.commits != null && !input.commits.isEmpty()) {
List<ObjectId> commitsToBan = new ArrayList<>(input.commits.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 83db726..3ab6ff5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.project;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
@@ -27,7 +29,6 @@
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.notedb.ChangeNotes;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
@@ -124,7 +125,7 @@
}
public ChangeControl forUser(final CurrentUser who) {
- if (getCurrentUser().equals(who)) {
+ if (getUser().equals(who)) {
return this;
}
return new ChangeControl(changeDataFactory,
@@ -135,8 +136,8 @@
return refControl;
}
- public CurrentUser getCurrentUser() {
- return getRefControl().getCurrentUser();
+ public CurrentUser getUser() {
+ return getRefControl().getUser();
}
public ProjectControl getProjectControl() {
@@ -177,12 +178,23 @@
return isVisible(db);
}
+ /** Can this user see the given patchset? */
+ public boolean isPatchVisible(PatchSet ps, ChangeData cd)
+ throws OrmException {
+ checkArgument(cd.getId().equals(ps.getId().getParentKey()),
+ "%s not for change %s", ps, cd.getId());
+ if (ps.isDraft() && !isDraftVisible(cd.db(), cd)) {
+ return false;
+ }
+ return isVisible(cd.db());
+ }
+
/** Can this user abandon this change? */
public boolean canAbandon() {
return isOwner() // owner (aka creator) of the change can abandon
|| getRefControl().isOwner() // branch owner can abandon
|| getProjectControl().isOwner() // project owner can abandon
- || getCurrentUser().getCapabilities().canAdministrateServer() // site administers are god
+ || getUser().getCapabilities().canAdministrateServer() // site administers are god
|| getRefControl().canAbandon() // user can abandon a specific ref
;
}
@@ -252,9 +264,9 @@
/** Is this user the owner of the change? */
public boolean isOwner() {
- if (getCurrentUser().isIdentifiedUser()) {
- final IdentifiedUser i = (IdentifiedUser) getCurrentUser();
- return i.getAccountId().equals(getChange().getOwner());
+ if (getUser().isIdentifiedUser()) {
+ Account.Id id = getUser().asIdentifiedUser().getAccountId();
+ return id.equals(getChange().getOwner());
}
return false;
}
@@ -267,10 +279,9 @@
/** Is this user a reviewer for the change? */
public boolean isReviewer(ReviewDb db, @Nullable ChangeData cd)
throws OrmException {
- if (getCurrentUser().isIdentifiedUser()) {
+ if (getUser().isIdentifiedUser()) {
Collection<Account.Id> results = changeData(db, cd).reviewers().values();
- IdentifiedUser user = (IdentifiedUser) getCurrentUser();
- return results.contains(user.getAccountId());
+ return results.contains(getUser().getAccountId());
}
return false;
}
@@ -284,9 +295,8 @@
if (getChange().getStatus().isOpen()) {
// A user can always remove themselves.
//
- if (getCurrentUser().isIdentifiedUser()) {
- final IdentifiedUser i = (IdentifiedUser) getCurrentUser();
- if (i.getAccountId().equals(reviewer)) {
+ if (getUser().isIdentifiedUser()) {
+ if (getUser().getAccountId().equals(reviewer)) {
return true; // can remove self
}
}
@@ -302,7 +312,7 @@
if (getRefControl().canRemoveReviewer() // has removal permissions
|| getRefControl().isOwner() // branch owner
|| getProjectControl().isOwner() // project owner
- || getCurrentUser().getCapabilities().canAdministrateServer()) {
+ || getUser().getCapabilities().canAdministrateServer()) {
return true;
}
}
@@ -316,7 +326,7 @@
return isOwner() // owner (aka creator) of the change can edit topic
|| getRefControl().isOwner() // branch owner can edit topic
|| getProjectControl().isOwner() // project owner can edit topic
- || getCurrentUser().getCapabilities().canAdministrateServer() // site administers are god
+ || getUser().getCapabilities().canAdministrateServer() // site administers are god
|| getRefControl().canEditTopicName() // user can edit topic on a specific ref
;
} else {
@@ -329,7 +339,7 @@
return isOwner() // owner (aka creator) of the change can edit hashtags
|| getRefControl().isOwner() // branch owner can edit hashtags
|| getProjectControl().isOwner() // project owner can edit hashtags
- || getCurrentUser().getCapabilities().canAdministrateServer() // site administers are god
+ || getUser().getCapabilities().canAdministrateServer() // site administers are god
|| getRefControl().canEditHashtags(); // user can edit hashtag on a specific ref
}
@@ -343,7 +353,7 @@
private boolean match(String destBranch, String refPattern) {
return RefPatternMatcher.getMatcher(refPattern).match(destBranch,
- this.getRefControl().getCurrentUser().getUserName());
+ getUser().getUserName());
}
private ChangeData changeData(ReviewDb db, @Nullable ChangeData cd) {
@@ -352,6 +362,7 @@
public boolean isDraftVisible(ReviewDb db, ChangeData cd)
throws OrmException {
- return isOwner() || isReviewer(db, cd) || getRefControl().canViewDrafts();
+ return isOwner() || isReviewer(db, cd) || getRefControl().canViewDrafts()
+ || getUser().isInternalUser();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index ae8fd53..6cbf7c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -46,6 +46,7 @@
public InheritedBooleanInfo createNewChangeForAllNotInTarget;
public InheritedBooleanInfo requireChangeId;
public InheritedBooleanInfo enableSignedPush;
+ public InheritedBooleanInfo requireSignedPush;
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
public SubmitType submitType;
public com.google.gerrit.extensions.client.ProjectState state;
@@ -74,6 +75,7 @@
InheritedBooleanInfo createNewChangeForAllNotInTarget =
new InheritedBooleanInfo();
InheritedBooleanInfo enableSignedPush = new InheritedBooleanInfo();
+ InheritedBooleanInfo requireSignedPush = new InheritedBooleanInfo();
useContributorAgreements.value = projectState.isUseContributorAgreements();
useSignedOffBy.value = projectState.isUseSignedOffBy();
@@ -90,6 +92,7 @@
createNewChangeForAllNotInTarget.configuredValue =
p.getCreateNewChangeForAllNotInTarget();
enableSignedPush.configuredValue = p.getEnableSignedPush();
+ requireSignedPush.configuredValue = p.getRequireSignedPush();
ProjectState parentState = Iterables.getFirst(projectState
.parents(), null);
@@ -102,6 +105,7 @@
createNewChangeForAllNotInTarget.inheritedValue =
parentState.isCreateNewChangeForAllNotInTarget();
enableSignedPush.inheritedValue = projectState.isEnableSignedPush();
+ requireSignedPush.inheritedValue = projectState.isRequireSignedPush();
}
this.useContributorAgreements = useContributorAgreements;
@@ -111,6 +115,7 @@
this.createNewChangeForAllNotInTarget = createNewChangeForAllNotInTarget;
if (serverEnableSignedPush) {
this.enableSignedPush = enableSignedPush;
+ this.requireSignedPush = requireSignedPush;
}
MaxObjectSizeLimitInfo maxObjectSizeLimit = new MaxObjectSizeLimitInfo();
@@ -138,7 +143,7 @@
actions = Maps.newTreeMap();
for (UiAction.Description d : UiActions.from(
views, new ProjectResource(control),
- Providers.of(control.getCurrentUser()))) {
+ Providers.of(control.getUser()))) {
actions.put(d.getId(), new ActionInfo(d));
}
this.theme = projectState.getTheme();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index 40bbb12..4646e3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -100,7 +100,7 @@
throw new ResourceNotFoundException(id);
}
- CurrentUser user = myCtl.getCurrentUser();
+ CurrentUser user = myCtl.getUser();
String ref = parts.get(0);
String path = parts.get(1);
for (ProjectState ps : myCtl.getProjectState().tree()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
index a5a96b1..03dc97c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
@@ -100,7 +100,7 @@
}
}
}.setContentType("text/plain")
- .setCharacterEncoding(UTF_8.name())
+ .setCharacterEncoding(UTF_8)
.disableGzip();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
index 8acc29e..09555b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
@@ -90,7 +90,7 @@
if ("default".equals(id)) {
throw new ResourceNotFoundException();
} else if (!Strings.isNullOrEmpty(id)) {
- ctl = ps.controlFor(ctl.getCurrentUser());
+ ctl = ps.controlFor(ctl.getUser());
return parse(ctl, id);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
index 5b78e08..a94d17e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index f7914e9..a50705d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Sets;
@@ -35,9 +33,6 @@
import com.google.inject.Inject;
import com.google.inject.util.Providers;
-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;
@@ -50,7 +45,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Locale;
import java.util.Set;
import java.util.TreeMap;
@@ -96,18 +90,15 @@
@Override
public List<BranchInfo> apply(ProjectResource rsrc)
throws ResourceNotFoundException, IOException, BadRequestException {
- FluentIterable<BranchInfo> branches = allBranches(rsrc);
- branches = filterBranches(branches);
- if (start > 0) {
- branches = branches.skip(start);
- }
- if (limit > 0) {
- branches = branches.limit(limit);
- }
- return branches.toList();
+ return new RefFilter<BranchInfo>(Constants.R_HEADS)
+ .subString(matchSubstring)
+ .regex(matchRegex)
+ .start(start)
+ .limit(limit)
+ .filter(allBranches(rsrc));
}
- private FluentIterable<BranchInfo> allBranches(ProjectResource rsrc)
+ private List<BranchInfo> allBranches(ProjectResource rsrc)
throws IOException, ResourceNotFoundException {
List<Ref> refs;
try (Repository db = repoManager.openRepository(rsrc.getNameKey())) {
@@ -162,7 +153,7 @@
}
}
Collections.sort(branches, new BranchComparator());
- return FluentIterable.from(branches);
+ return branches;
}
private static class BranchComparator implements Comparator<BranchInfo> {
@@ -184,61 +175,6 @@
}
}
- private FluentIterable<BranchInfo> filterBranches(
- FluentIterable<BranchInfo> branches) throws BadRequestException {
- if (!Strings.isNullOrEmpty(matchSubstring)) {
- branches = branches.filter(new SubstringPredicate(matchSubstring));
- } else if (!Strings.isNullOrEmpty(matchRegex)) {
- branches = branches.filter(new RegexPredicate(matchRegex));
- }
- return branches;
- }
-
- private static class SubstringPredicate implements Predicate<BranchInfo> {
- private final String substring;
-
- private SubstringPredicate(String substring) {
- this.substring = substring.toLowerCase(Locale.US);
- }
-
- @Override
- public boolean apply(BranchInfo in) {
- String ref = in.ref;
- if (ref.startsWith(Constants.R_HEADS)) {
- ref = ref.substring(Constants.R_HEADS.length());
- }
- ref = ref.toLowerCase(Locale.US);
- return ref.contains(substring);
- }
- }
-
- private static class RegexPredicate implements Predicate<BranchInfo> {
- private final RunAutomaton a;
-
- private RegexPredicate(String regex) throws BadRequestException {
- if (regex.startsWith("^")) {
- regex = regex.substring(1);
- if (regex.endsWith("$") && !regex.endsWith("\\$")) {
- regex = regex.substring(0, regex.length() - 1);
- }
- }
- try {
- a = new RunAutomaton(new RegExp(regex).toAutomaton());
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(e.getMessage());
- }
- }
-
- @Override
- public boolean apply(BranchInfo in) {
- if (!in.ref.startsWith(Constants.R_HEADS)){
- return a.run(in.ref);
- } else {
- return a.run(in.ref.substring(Constants.R_HEADS.length()));
- }
- }
- }
-
private BranchInfo createBranchInfo(Ref ref, RefControl refControl,
Set<String> targets) {
BranchInfo info = new BranchInfo();
@@ -249,7 +185,7 @@
for (UiAction.Description d : UiActions.from(
branchViews,
new BranchResource(refControl.getProjectControl(), info),
- Providers.of(refControl.getCurrentUser()))) {
+ Providers.of(refControl.getUser()))) {
if (info.actions == null) {
info.actions = new TreeMap<>();
}
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 8d1e95f..b2596be 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
@@ -58,7 +58,7 @@
public List<ProjectInfo> apply(ProjectResource rsrc) {
if (recursive) {
return getChildProjectsRecursively(rsrc.getNameKey(),
- rsrc.getControl().getCurrentUser());
+ rsrc.getControl().getUser());
} else {
return getDirectChildProjects(rsrc.getNameKey());
}
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 0208829..b4bd9a3 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
@@ -64,7 +64,7 @@
List<List<DashboardInfo>> all = Lists.newArrayList();
boolean setDefault = true;
for (ProjectState ps : ctl.getProjectState().tree()) {
- ctl = ps.controlFor(ctl.getCurrentUser());
+ ctl = ps.controlFor(ctl.getUser());
if (ctl.isVisible()) {
List<DashboardInfo> list = scan(ctl, project, setDefault);
for (DashboardInfo d : 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 04058f2..0704cb9 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
@@ -14,6 +14,8 @@
package com.google.gerrit.server.project;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
@@ -37,9 +39,9 @@
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.StringUtil;
import com.google.gerrit.server.WebLinks;
-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.group.GroupsCollection;
import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
@@ -59,7 +61,6 @@
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -109,7 +110,7 @@
private final CurrentUser currentUser;
private final ProjectCache projectCache;
- private final GroupCache groupCache;
+ private final GroupsCollection groupsCollection;
private final GroupControl.Factory groupControlFactory;
private final GitRepositoryManager repoManager;
private final ProjectNode.Factory projectNodeFactory;
@@ -191,13 +192,16 @@
private AccountGroup.UUID groupUuid;
@Inject
- protected ListProjects(CurrentUser currentUser, ProjectCache projectCache,
- GroupCache groupCache, GroupControl.Factory groupControlFactory,
- GitRepositoryManager repoManager, ProjectNode.Factory projectNodeFactory,
+ protected ListProjects(CurrentUser currentUser,
+ ProjectCache projectCache,
+ GroupsCollection groupsCollection,
+ GroupControl.Factory groupControlFactory,
+ GitRepositoryManager repoManager,
+ ProjectNode.Factory projectNodeFactory,
WebLinks webLinks) {
this.currentUser = currentUser;
this.projectCache = projectCache;
- this.groupCache = groupCache;
+ this.groupsCollection = groupsCollection;
this.groupControlFactory = groupControlFactory;
this.repoManager = repoManager;
this.projectNodeFactory = projectNodeFactory;
@@ -232,7 +236,7 @@
display(buf);
return BinaryResult.create(buf.toByteArray())
.setContentType("text/plain")
- .setCharacterEncoding("UTF-8");
+ .setCharacterEncoding(UTF_8);
}
return apply();
}
@@ -246,12 +250,8 @@
throws BadRequestException {
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);
- }
+ stdout = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(displayOutputStream, UTF_8)));
}
int foundIndex = 0;
@@ -280,7 +280,7 @@
break;
}
if (!pctl.getLocalGroups().contains(
- GroupReference.forGroup(groupCache.get(groupUuid)))) {
+ GroupReference.forGroup(groupsCollection.parseId(groupUuid.get())))) {
continue;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
index 1c140e3..4f157df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
@@ -16,7 +16,8 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.api.projects.TagInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -29,7 +30,6 @@
import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.Singleton;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -39,6 +39,7 @@
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.util.Collections;
@@ -46,13 +47,37 @@
import java.util.List;
import java.util.Map;
-@Singleton
public class ListTags implements RestReadView<ProjectResource> {
private final GitRepositoryManager repoManager;
private final Provider<ReviewDb> dbProvider;
private final TagCache tagCache;
private final ChangeCache changeCache;
+ @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of tags to list")
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+
+ @Option(name = "--start", aliases = {"-s"}, metaVar = "CNT", usage = "number of tags to skip")
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ @Option(name = "--match", aliases = {"-m"}, metaVar = "MATCH", usage = "match tags substring")
+ public void setMatchSubstring(String matchSubstring) {
+ this.matchSubstring = matchSubstring;
+ }
+
+ @Option(name = "--regex", aliases = {"-r"}, metaVar = "REGEX", usage = "match tags regex")
+ public void setMatchRegex(String matchRegex) {
+ this.matchRegex = matchRegex;
+ }
+
+ private int limit;
+ private int start;
+ private String matchSubstring;
+ private String matchRegex;
+
@Inject
public ListTags(GitRepositoryManager repoManager,
Provider<ReviewDb> dbProvider,
@@ -66,19 +91,15 @@
@Override
public List<TagInfo> apply(ProjectResource resource) throws IOException,
- ResourceNotFoundException {
+ ResourceNotFoundException, BadRequestException {
List<TagInfo> tags = Lists.newArrayList();
- try (Repository repo = getRepository(resource.getNameKey())) {
- RevWalk rw = new RevWalk(repo);
- try {
- Map<String, Ref> all = visibleTags(resource.getControl(), repo,
- repo.getRefDatabase().getRefs(Constants.R_TAGS));
- for (Ref ref : all.values()) {
- tags.add(createTagInfo(ref, rw));
- }
- } finally {
- rw.dispose();
+ try (Repository repo = getRepository(resource.getNameKey());
+ RevWalk rw = new RevWalk(repo)) {
+ Map<String, Ref> all = visibleTags(resource.getControl(), repo,
+ repo.getRefDatabase().getRefs(Constants.R_TAGS));
+ for (Ref ref : all.values()) {
+ tags.add(createTagInfo(ref, rw));
}
}
@@ -89,7 +110,12 @@
}
});
- return tags;
+ return new RefFilter<TagInfo>(Constants.R_TAGS)
+ .start(start)
+ .limit(limit)
+ .subString(matchSubstring)
+ .regex(matchRegex)
+ .filter(tags);
}
public TagInfo get(ProjectResource resource, IdString id)
@@ -131,7 +157,7 @@
RevTag tag = (RevTag)object;
// Annotated or signed tag
return new TagInfo(
- Constants.R_TAGS + tag.getTagName(),
+ ref.getName(),
tag.getName(),
tag.getObject().getName(),
tag.getFullMessage().trim(),
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 7f7dff6..9116dcc 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
@@ -33,7 +33,6 @@
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;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.change.IncludedInResolver;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -212,7 +211,7 @@
public List<String> get() {
List<String> r;
if (user.isIdentifiedUser()) {
- Set<String> emails = ((IdentifiedUser) user).getEmailAddresses();
+ Set<String> emails = user.asIdentifiedUser().getEmailAddresses();
r = new ArrayList<>(emails.size() + 1);
r.addAll(emails);
} else {
@@ -232,7 +231,7 @@
return ctl;
}
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
@@ -258,7 +257,7 @@
/** Can this user see this project exists? */
public boolean isVisible() {
- return (user instanceof InternalUser
+ return (user.isInternalUser()
|| canPerformOnAnyRef(Permission.READ)) && !isHidden();
}
@@ -287,7 +286,7 @@
}
public boolean allRefsAreVisible(Set<String> ignore) {
- return user instanceof InternalUser
+ return user.isInternalUser()
|| canPerformOnAllRefs(Permission.READ, ignore);
}
@@ -350,7 +349,7 @@
if (! (user.isIdentifiedUser())) {
return new Capable("Must be logged in to verify Contributor Agreement");
}
- final IdentifiedUser iUser = (IdentifiedUser) user;
+ final IdentifiedUser iUser = user.asIdentifiedUser();
List<AccountGroup.UUID> okGroupIds = Lists.newArrayList();
for (ContributorAgreement ca : contributorAgreements) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 455a42b..7094828 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -413,6 +413,15 @@
});
}
+ public boolean isRequireSignedPush() {
+ return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
+ @Override
+ public InheritableBoolean apply(Project input) {
+ return input.getRequireSignedPush();
+ }
+ });
+ }
+
public LabelTypes getLabelTypes() {
Map<String, LabelType> types = Maps.newLinkedHashMap();
for (ProjectState s : treeInOrder()) {
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 e529043..76ad2f0 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,7 +32,6 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.EnableSignedPush;
-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;
@@ -71,6 +70,7 @@
public InheritableBoolean createNewChangeForAllNotInTarget;
public InheritableBoolean requireChangeId;
public InheritableBoolean enableSignedPush;
+ public InheritableBoolean requireSignedPush;
public String maxObjectSizeLimit;
public SubmitType submitType;
public com.google.gerrit.extensions.client.ProjectState state;
@@ -87,7 +87,7 @@
private final PluginConfigFactory cfgFactory;
private final AllProjectsNameProvider allProjects;
private final DynamicMap<RestView<ProjectResource>> views;
- private final Provider<CurrentUser> currentUser;
+ private final Provider<CurrentUser> user;
private final ChangeHooks hooks;
@Inject
@@ -102,7 +102,7 @@
AllProjectsNameProvider allProjects,
DynamicMap<RestView<ProjectResource>> views,
ChangeHooks hooks,
- Provider<CurrentUser> currentUser) {
+ Provider<CurrentUser> user) {
this.serverEnableSignedPush = serverEnableSignedPush;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectCache = projectCache;
@@ -114,7 +114,7 @@
this.allProjects = allProjects;
this.views = views;
this.hooks = hooks;
- this.currentUser = currentUser;
+ this.user = user;
}
@Override
@@ -167,8 +167,13 @@
p.setRequireChangeID(input.requireChangeId);
}
- if (input.enableSignedPush != null) {
- p.setEnableSignedPush(input.enableSignedPush);
+ if (serverEnableSignedPush) {
+ if (input.enableSignedPush != null) {
+ p.setEnableSignedPush(input.enableSignedPush);
+ }
+ if (input.requireSignedPush != null) {
+ p.setRequireSignedPush(input.requireSignedPush);
+ }
}
if (input.maxObjectSizeLimit != null) {
@@ -194,10 +199,9 @@
ObjectId commitRev = projectConfig.commit(md);
// Only fire hook if project was actually changed.
if (!Objects.equals(baseRev, commitRev)) {
- IdentifiedUser user = (IdentifiedUser) currentUser.get();
hooks.doRefUpdatedHook(
new Branch.NameKey(projectName, RefNames.REFS_CONFIG),
- baseRev, commitRev, user.getAccount());
+ baseRev, commitRev, user.get().asIdentifiedUser().getAccount());
}
projectCache.evict(projectConfig.getProject());
gitMgr.setProjectDescription(projectName, p.getDescription());
@@ -214,7 +218,7 @@
ProjectState state = projectStateFactory.create(projectConfig);
return new ConfigInfo(serverEnableSignedPush,
- state.controlFor(currentUser.get()), config, pluginConfigEntries,
+ state.controlFor(user.get()), config, pluginConfigEntries,
cfgFactory, allProjects, views);
} catch (ConfigInvalidException err) {
throw new ResourceConflictException("Cannot read project " + projectName, err);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
index 1d7c724..d589865 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
@@ -67,7 +67,7 @@
}
ProjectControl ctl = resource.getControl();
- IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+ IdentifiedUser user = ctl.getUser().asIdentifiedUser();
if (!ctl.isOwner()) {
throw new AuthException("not project owner");
}
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 cfabf13..964554d 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
@@ -26,8 +26,6 @@
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;
import com.google.gerrit.server.group.SystemGroupBackend;
import dk.brics.automaton.RegExp;
@@ -50,6 +48,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
/** Manages access control for Git references (aka branches, tags). */
@@ -86,8 +86,8 @@
return projectControl;
}
- public CurrentUser getCurrentUser() {
- return projectControl.getCurrentUser();
+ public CurrentUser getUser() {
+ return projectControl.getUser();
}
public RefControl forUser(CurrentUser who) {
@@ -116,7 +116,7 @@
public boolean isVisible() {
if (isVisible == null) {
isVisible =
- (getCurrentUser() instanceof InternalUser || canPerform(Permission.READ))
+ (getUser().isInternalUser() || canPerform(Permission.READ))
&& canRead();
}
return isVisible;
@@ -205,7 +205,7 @@
// this why for the AllProjects project we allow administrators to push
// configuration changes if they have push without being project owner.
if (!(projectControl.getProjectState().isAllProjects() &&
- getCurrentUser().getCapabilities().canAdministrateServer())) {
+ getUser().getCapabilities().canAdministrateServer())) {
return false;
}
}
@@ -256,12 +256,12 @@
}
boolean owner;
boolean admin;
- switch (getCurrentUser().getAccessPath()) {
+ switch (getUser().getAccessPath()) {
case REST_API:
case JSON_RPC:
case UNKNOWN:
owner = isOwner();
- admin = getCurrentUser().getCapabilities().canAdministrateServer();
+ admin = getUser().getCapabilities().canAdministrateServer();
break;
default:
@@ -301,10 +301,9 @@
final PersonIdent tagger = tag.getTaggerIdent();
if (tagger != null) {
boolean valid;
- if (getCurrentUser().isIdentifiedUser()) {
- final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
+ if (getUser().isIdentifiedUser()) {
final String addr = tagger.getEmailAddress();
- valid = user.hasEmailAddress(addr);
+ valid = getUser().asIdentifiedUser().hasEmailAddress(addr);
} else {
valid = false;
}
@@ -359,12 +358,12 @@
return false;
}
- switch (getCurrentUser().getAccessPath()) {
+ switch (getUser().getAccessPath()) {
case GIT:
return canPushWithForce();
default:
- return getCurrentUser().getCapabilities().canAdministrateServer()
+ return getUser().getCapabilities().canAdministrateServer()
|| (isOwner() && !isForceBlocked(Permission.PUSH))
|| canPushWithForce();
}
@@ -683,5 +682,10 @@
} else if (!Repository.isValidRefName(refPattern)) {
throw new InvalidNameException(refPattern);
}
+ try {
+ Pattern.compile(refPattern.replace("${username}/", ""));
+ } catch (PatternSyntaxException e) {
+ throw new InvalidNameException(refPattern + " " + e.getMessage());
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefFilter.java
new file mode 100644
index 0000000..63fb595
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefFilter.java
@@ -0,0 +1,120 @@
+// Copyright (C) 2015 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 com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
+import com.google.gerrit.extensions.api.projects.RefInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+import java.util.List;
+import java.util.Locale;
+
+public class RefFilter<T extends RefInfo> {
+ private final String prefix;
+ private String matchSubstring;
+ private String matchRegex;
+ private int start;
+ private int limit;
+
+ public RefFilter(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public RefFilter<T> subString(String subString) {
+ this.matchSubstring = subString;
+ return this;
+ }
+
+ public RefFilter<T> regex(String regex) {
+ this.matchRegex = regex;
+ return this;
+ }
+
+ public RefFilter<T> start(int start) {
+ this.start = start;
+ return this;
+ }
+
+ public RefFilter<T> limit(int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public List<T> filter(List<T> refs) throws BadRequestException {
+ FluentIterable<T> results = FluentIterable.from(refs);
+ if (!Strings.isNullOrEmpty(matchSubstring)) {
+ results = results.filter(new SubstringPredicate(matchSubstring));
+ } else if (!Strings.isNullOrEmpty(matchRegex)) {
+ results = results.filter(new RegexPredicate(matchRegex));
+ }
+ if (start > 0) {
+ results = results.skip(start);
+ }
+ if (limit > 0) {
+ results = results.limit(limit);
+ }
+ return results.toList();
+ }
+
+ private class SubstringPredicate implements Predicate<T> {
+ private final String substring;
+
+ private SubstringPredicate(String substring) {
+ this.substring = substring.toLowerCase(Locale.US);
+ }
+
+ @Override
+ public boolean apply(T in) {
+ String ref = in.ref;
+ if (ref.startsWith(prefix)) {
+ ref = ref.substring(prefix.length());
+ }
+ ref = ref.toLowerCase(Locale.US);
+ return ref.contains(substring);
+ }
+ }
+
+ private class RegexPredicate implements Predicate<T> {
+ private final RunAutomaton a;
+
+ private RegexPredicate(String regex) throws BadRequestException {
+ if (regex.startsWith("^")) {
+ regex = regex.substring(1);
+ if (regex.endsWith("$") && !regex.endsWith("\\$")) {
+ regex = regex.substring(0, regex.length() - 1);
+ }
+ }
+ try {
+ a = new RunAutomaton(new RegExp(regex).toAutomaton());
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean apply(T in) {
+ String ref = in.ref;
+ if (ref.startsWith(prefix)) {
+ ref = ref.substring(prefix.length());
+ }
+ return a.run(ref);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
index b18b8ec..ac01de5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
@@ -24,7 +24,6 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.DashboardsCollection.DashboardInfo;
@@ -68,7 +67,6 @@
input.id = Strings.emptyToNull(input.id);
ProjectControl ctl = resource.getControl();
- IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
if (!ctl.isOwner()) {
throw new AuthException("not project owner");
}
@@ -105,7 +103,7 @@
if (!msg.endsWith("\n")) {
msg += "\n";
}
- md.setAuthor(user);
+ md.setAuthor(ctl.getUser().asIdentifiedUser());
md.setMessage(msg);
config.commit(md);
cache.evict(ctl.getProject());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 3b031ee..15c977f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -71,7 +71,6 @@
ResourceNotFoundException, UnprocessableEntityException, IOException {
ProjectControl ctl = rsrc.getControl();
validateParentUpdate(ctl, input.parent, checkIfAdmin);
- IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
try {
MetaDataUpdate md = updateFactory.create(rsrc.getNameKey());
try {
@@ -88,7 +87,7 @@
} else if (!msg.endsWith("\n")) {
msg += "\n";
}
- md.setAuthor(user);
+ md.setAuthor(ctl.getUser().asIdentifiedUser());
md.setMessage(msg);
config.commit(md);
cache.evict(ctl.getProject());
@@ -109,7 +108,7 @@
public void validateParentUpdate(final ProjectControl ctl, String newParent,
boolean checkIfAdmin) throws AuthException, ResourceConflictException,
UnprocessableEntityException {
- IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+ IdentifiedUser user = ctl.getUser().asIdentifiedUser();
if (checkIfAdmin && !user.getCapabilities().canAdministrateServer()) {
throw new AuthException("not administrator");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index e8e29c1..2b35cef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -220,7 +220,7 @@
try {
results = evaluateImpl("locate_submit_rule", "can_submit",
"locate_submit_filter", "filter_submit_results",
- control.getCurrentUser());
+ control.getUser());
} catch (RuleEvalException e) {
return ruleError(e.getMessage(), e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
index 12be5d3..afbd3be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.TypeLiteral;
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 8061a26..9677f5f 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
@@ -544,7 +544,7 @@
}
void cacheVisibleTo(ChangeControl ctl) {
- visibleTo = ctl.getCurrentUser();
+ visibleTo = ctl.getUser();
changeControl = ctl;
}
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 776a7f6..f52edc1 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
@@ -34,8 +34,8 @@
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
-import com.google.gerrit.server.account.VersionedAccountQueries;
import com.google.gerrit.server.account.VersionedAccountDestinations;
+import com.google.gerrit.server.account.VersionedAccountQueries;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
@@ -111,6 +111,7 @@
public static final String FIELD_HASHTAG = "hashtag";
public static final String FIELD_LABEL = "label";
public static final String FIELD_LIMIT = "limit";
+ public static final String FIELD_MERGE = "merge";
public static final String FIELD_MERGEABLE = "mergeable";
public static final String FIELD_MESSAGE = "message";
public static final String FIELD_OWNER = "owner";
@@ -262,8 +263,7 @@
Arguments asUser(Account.Id otherId) {
try {
CurrentUser u = self.get();
- if (u.isIdentifiedUser()
- && otherId.equals(((IdentifiedUser) u).getAccountId())) {
+ if (u.isIdentifiedUser() && otherId.equals(u.getAccountId())) {
return this;
}
} catch (ProvisionException e) {
@@ -274,9 +274,9 @@
IdentifiedUser getIdentifiedUser() throws QueryParseException {
try {
- CurrentUser u = getCurrentUser();
+ CurrentUser u = getUser();
if (u.isIdentifiedUser()) {
- return (IdentifiedUser) u;
+ return u.asIdentifiedUser();
}
throw new QueryParseException(NotSignedInException.MESSAGE);
} catch (ProvisionException e) {
@@ -284,7 +284,7 @@
}
}
- CurrentUser getCurrentUser() throws QueryParseException {
+ CurrentUser getUser() throws QueryParseException {
try {
return self.get();
} catch (ProvisionException e) {
@@ -611,11 +611,7 @@
Account.Id callerId;
try {
CurrentUser caller = args.self.get();
- if (caller.isIdentifiedUser()) {
- callerId = ((IdentifiedUser) caller).getAccountId();
- } else {
- callerId = null;
- }
+ callerId = caller.isIdentifiedUser() ? caller.getAccountId() : null;
} catch (ProvisionException e) {
callerId = null;
}
@@ -678,7 +674,7 @@
}
public Predicate<ChangeData> is_visible() throws QueryParseException {
- return visibleto(args.getCurrentUser());
+ return visibleto(args.getUser());
}
@Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 8bea099..ea8a3ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -21,6 +21,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.strategy.SubmitStrategy;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -42,8 +43,12 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -64,8 +69,7 @@
for (final Change c : changes) {
final ChangeDataCache changeDataCache = new ChangeDataCache(
c, db, args.changeDataFactory, args.projectCache);
- List<String> files = args.changeDataFactory.create(db.get(), c)
- .currentFilePaths();
+ List<String> files = listFiles(c, args, changeDataCache);
List<Predicate<ChangeData>> filePredicates =
Lists.newArrayListWithCapacity(files.size());
for (String file : files) {
@@ -81,7 +85,10 @@
new ProjectPredicate(c.getProject().get()));
predicatesForOneChange.add(
new RefPredicate(c.getDest().get()));
- predicatesForOneChange.add(or(filePredicates));
+
+ predicatesForOneChange.add(or(or(filePredicates),
+ new IsMergePredicate(args, value)));
+
predicatesForOneChange.add(new OperatorPredicate<ChangeData>(
ChangeQueryBuilder.FIELD_CONFLICTS, value) {
@@ -109,17 +116,15 @@
}
try (Repository repo =
args.repoManager.openRepository(otherChange.getProject());
- RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
+ CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
RevFlag canMergeFlag = rw.newFlag("CAN_MERGE");
CodeReviewCommit commit =
- (CodeReviewCommit) rw.parseCommit(changeDataCache.getTestAgainst());
- SubmitStrategy strategy =
- args.submitStrategyFactory.create(submitType,
- db.get(), repo, rw, null, canMergeFlag,
- getAlreadyAccepted(repo, rw, commit),
- otherChange.getDest(), null);
- CodeReviewCommit otherCommit =
- (CodeReviewCommit) rw.parseCommit(other);
+ rw.parseCommit(changeDataCache.getTestAgainst());
+ SubmitStrategy strategy = args.submitStrategyFactory.create(
+ submitType, db.get(), repo, rw, null, canMergeFlag,
+ getAlreadyAccepted(repo, rw, commit), otherChange.getDest(),
+ null);
+ CodeReviewCommit otherCommit = rw.parseCommit(other);
otherCommit.add(canMergeFlag);
conflicts = !strategy.dryRun(commit, otherCommit);
args.conflictsCache.put(conflictsKey, conflicts);
@@ -171,6 +176,42 @@
return changePredicates;
}
+ private static List<String> listFiles(Change c, Arguments args,
+ ChangeDataCache changeDataCache) throws OrmException {
+ try (Repository repo = args.repoManager.openRepository(c.getProject());
+ RevWalk rw = new RevWalk(repo)) {
+ RevCommit ps = rw.parseCommit(changeDataCache.getTestAgainst());
+ if (ps.getParentCount() > 1) {
+ String dest = c.getDest().get();
+ Ref destBranch = repo.getRefDatabase().getRef(dest);
+ destBranch.getObjectId();
+ rw.setRevFilter(RevFilter.MERGE_BASE);
+ rw.markStart(rw.parseCommit(destBranch.getObjectId()));
+ rw.markStart(ps);
+ RevCommit base = rw.next();
+ // TODO(zivkov): handle the case with multiple merge bases
+
+ List<String> files = new ArrayList<>();
+ try (TreeWalk tw = new TreeWalk(repo)) {
+ if (base != null) {
+ tw.setFilter(TreeFilter.ANY_DIFF);
+ tw.addTree(base.getTree());
+ }
+ tw.addTree(ps.getTree());
+ tw.setRecursive(true);
+ while (tw.next()) {
+ files.add(tw.getPathString());
+ }
+ }
+ return files;
+ } else {
+ return args.changeDataFactory.create(args.db.get(), c).currentFilePaths();
+ }
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
@Override
public String toString() {
return ChangeQueryBuilder.FIELD_CONFLICTS + ":" + value;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 10c3db3..aa977dd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -14,20 +14,21 @@
package com.google.gerrit.server.query.change;
+import static com.google.gerrit.server.index.ChangeField.SUBMISSIONID;
import static com.google.gerrit.server.query.Predicate.and;
import static com.google.gerrit.server.query.Predicate.not;
import static com.google.gerrit.server.query.Predicate.or;
import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Iterables;
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
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.server.index.ChangeField;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.index.IndexConfig;
@@ -38,10 +39,15 @@
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+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;
/**
* Execute a single query over changes, for use by Gerrit internals.
@@ -76,14 +82,17 @@
private final IndexConfig indexConfig;
private final QueryProcessor qp;
private final IndexCollection indexes;
+ private final ChangeData.Factory changeDataFactory;
@Inject
InternalChangeQuery(IndexConfig indexConfig,
QueryProcessor queryProcessor,
- IndexCollection indexes) {
+ IndexCollection indexes,
+ ChangeData.Factory changeDataFactory) {
this.indexConfig = indexConfig;
qp = queryProcessor.enforceVisibility(false);
this.indexes = indexes;
+ this.changeDataFactory = changeDataFactory;
}
public InternalChangeQuery setLimit(int n) {
@@ -125,39 +134,62 @@
open()));
}
- public Iterable<ChangeData> byCommitsOnBranchNotMerged(Branch.NameKey branch,
- List<String> hashes) throws OrmException {
- Schema<ChangeData> schema = schema(indexes);
- if (schema != null && schema.hasField(ChangeField.EXACT_COMMIT)) {
- return query(commitsOnBranchNotMerged(branch, commits(schema, hashes)));
- } else {
- return byCommitsOnBranchNotMerged(
- schema, branch, hashes, indexConfig.maxPrefixTerms());
- }
+ public Iterable<ChangeData> byCommitsOnBranchNotMerged(Repository repo,
+ ReviewDb db, Branch.NameKey branch, List<String> hashes)
+ throws OrmException, IOException {
+ return byCommitsOnBranchNotMerged(repo, db, branch, hashes,
+ // Account for all commit predicates plus ref, project, status.
+ indexConfig.maxTerms() - 3);
}
@VisibleForTesting
- Iterable<ChangeData> byCommitsOnBranchNotMerged(Schema<ChangeData> schema,
- Branch.NameKey branch, List<String> hashes, int batchSize)
- throws OrmException {
- List<Predicate<ChangeData>> commits = commits(schema, hashes);
- int numBatches = (hashes.size() / batchSize) + 1;
- List<Predicate<ChangeData>> queries = new ArrayList<>(numBatches);
- for (List<Predicate<ChangeData>> batch
- : Iterables.partition(commits, batchSize)) {
- queries.add(commitsOnBranchNotMerged(branch, batch));
+ Iterable<ChangeData> byCommitsOnBranchNotMerged(Repository repo, ReviewDb db,
+ Branch.NameKey branch, List<String> hashes, int indexLimit)
+ throws OrmException, IOException {
+ if (hashes.size() > indexLimit) {
+ return byCommitsOnBranchNotMergedFromDatabase(repo, db, branch, hashes);
+ } else {
+ return byCommitsOnBranchNotMergedFromIndex(branch, hashes);
}
- try {
- return FluentIterable.from(qp.queryChanges(queries))
- .transformAndConcat(new Function<QueryResult, List<ChangeData>>() {
- @Override
- public List<ChangeData> apply(QueryResult in) {
- return in.changes();
- }
- });
- } catch (QueryParseException e) {
- throw new OrmException(e);
+ }
+
+ private Iterable<ChangeData> byCommitsOnBranchNotMergedFromDatabase(
+ Repository repo, ReviewDb db, Branch.NameKey branch, List<String> hashes)
+ throws OrmException, IOException {
+ Set<Change.Id> changeIds = Sets.newHashSetWithExpectedSize(hashes.size());
+ String lastPrefix = null;
+ for (Ref ref :
+ repo.getRefDatabase().getRefs(RefNames.REFS_CHANGES).values()) {
+ String r = ref.getName();
+ if ((lastPrefix != null && r.startsWith(lastPrefix))
+ || !hashes.contains(ref.getObjectId().name())) {
+ continue;
+ }
+ Change.Id id = Change.Id.fromRef(r);
+ if (id == null) {
+ continue;
+ }
+ if (changeIds.add(id)) {
+ lastPrefix = r.substring(0, r.lastIndexOf('/'));
+ }
}
+
+ List<ChangeData> cds = new ArrayList<>(hashes.size());
+ for (Change c : db.changes().get(changeIds)) {
+ if (c.getDest().equals(branch) && c.getStatus() != Change.Status.MERGED) {
+ cds.add(changeDataFactory.create(db, c));
+ }
+ }
+ return cds;
+ }
+
+ private Iterable<ChangeData> byCommitsOnBranchNotMergedFromIndex(
+ Branch.NameKey branch, List<String> hashes) throws OrmException {
+ return query(and(
+ ref(branch),
+ project(branch.getParentKey()),
+ not(status(Change.Status.MERGED)),
+ or(commits(schema(indexes), hashes))));
}
private static List<Predicate<ChangeData>> commits(Schema<ChangeData> schema,
@@ -169,15 +201,6 @@
return commits;
}
- private static Predicate<ChangeData> commitsOnBranchNotMerged(
- Branch.NameKey branch, List<Predicate<ChangeData>> commits) {
- return and(
- ref(branch),
- project(branch.getParentKey()),
- not(status(Change.Status.MERGED)),
- or(commits));
- }
-
public List<ChangeData> byProjectOpen(Project.NameKey project)
throws OrmException {
return query(and(project(project), open()));
@@ -192,6 +215,14 @@
return query(commit(schema(indexes), id.name()));
}
+ public List<ChangeData> bySubmissionId(String cs) throws OrmException {
+ if (Strings.isNullOrEmpty(cs) || !schema(indexes).hasField(SUBMISSIONID)) {
+ return Collections.emptyList();
+ } else {
+ return query(new SubmissionIdPredicate(cs));
+ }
+ }
+
public List<ChangeData> byProjectGroups(Project.NameKey project,
Collection<String> groups) throws OrmException {
List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java
new file mode 100644
index 0000000..3c02bab
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+
+public class IsMergePredicate extends OperatorPredicate<ChangeData> {
+ private final Arguments args;
+
+ public IsMergePredicate(Arguments args, String value) {
+ super(ChangeQueryBuilder.FIELD_MERGE, value);
+ this.args = args;
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ ObjectId id = ObjectId.fromString(
+ cd.currentPatchSet().getRevision().get());
+ try (Repository repo =
+ args.repoManager.openRepository(cd.change().getProject());
+ RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
+ RevCommit commit = rw.parseCommit(id);
+ return commit.getParentCount() > 1;
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public int getCost() {
+ return 2;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
index b990091..1ac2729 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -33,7 +33,7 @@
ChangeDataSource {
private static String describe(CurrentUser user) {
if (user.isIdentifiedUser()) {
- return ((IdentifiedUser) user).getAccountId().toString();
+ return user.getAccountId().toString();
}
return user.toString();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
index dc7a579..856a559 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -17,7 +17,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.OperatorPredicate;
@@ -27,7 +26,7 @@
class IsVisibleToPredicate extends OperatorPredicate<ChangeData> {
private static String describe(CurrentUser user) {
if (user.isIdentifiedUser()) {
- return ((IdentifiedUser) user).getAccountId().toString();
+ return user.getAccountId().toString();
}
if (user instanceof SingleGroupUser) {
return "group:" + user.getEffectiveGroups().getKnownGroups() //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 606f577..44e0654 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -18,7 +18,6 @@
import com.google.common.collect.Lists;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.query.AndPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryBuilder;
@@ -29,7 +28,7 @@
class IsWatchedByPredicate extends AndPredicate<ChangeData> {
private static String describe(CurrentUser user) {
if (user.isIdentifiedUser()) {
- return ((IdentifiedUser) user).getAccountId().toString();
+ return user.getAccountId().toString();
}
return user.toString();
}
@@ -39,13 +38,13 @@
IsWatchedByPredicate(ChangeQueryBuilder.Arguments args,
boolean checkIsVisible) throws QueryParseException {
super(filters(args, checkIsVisible));
- this.user = args.getCurrentUser();
+ this.user = args.getUser();
}
private static List<Predicate<ChangeData>> filters(
ChangeQueryBuilder.Arguments args,
boolean checkIsVisible) throws QueryParseException {
- CurrentUser user = args.getCurrentUser();
+ CurrentUser user = args.getUser();
List<Predicate<ChangeData>> r = Lists.newArrayList();
ChangeQueryBuilder builder = new ChangeQueryBuilder(args);
for (AccountProjectWatch w : user.getNotificationFilters()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index f73e0e4..b84cc79 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.query.change;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -164,7 +166,7 @@
public void query(String queryString) throws IOException {
out = new PrintWriter( //
new BufferedWriter( //
- new OutputStreamWriter(outputStream, "UTF-8")));
+ new OutputStreamWriter(outputStream, UTF_8)));
try {
if (queryProcessor.isDisabled()) {
ErrorMessage m = new ErrorMessage();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index bd6b297..dfc0f75e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -99,7 +99,8 @@
out = query();
} catch (QueryParseException e) {
// This is a hack to detect an operator that requires authentication.
- Pattern p = Pattern.compile("^Error in operator (.*:self)$");
+ Pattern p = Pattern.compile(
+ "^Error in operator (.*:self|is:watched|is:owner|is:reviewer|has:.*)$");
Matcher m = p.matcher(e.getMessage());
if (m.matches()) {
String op = m.group(1);
@@ -126,7 +127,7 @@
IdentifiedUser self = null;
try {
if (user.get().isIdentifiedUser()) {
- self = (IdentifiedUser) user.get();
+ self = user.get().asIdentifiedUser();
self.asyncStarredChanges();
}
return query0();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
new file mode 100644
index 0000000..3b2dd94
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+class SubmissionIdPredicate extends IndexPredicate<ChangeData> {
+
+ SubmissionIdPredicate(String changeSet) {
+ super(ChangeField.SUBMISSIONID, changeSet);
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ Change change = object.change();
+ if (change == null) {
+ return false;
+ }
+ if (change.getSubmissionId() == null) {
+ return false;
+ }
+ return getValue().equals(change.getSubmissionId());
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
index f50f123..777b5b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
@@ -22,6 +22,7 @@
@Override
protected void configure() {
bind(DataSourceType.class).annotatedWith(Names.named("db2")).to(DB2.class);
+ bind(DataSourceType.class).annotatedWith(Names.named("derby")).to(Derby.class);
bind(DataSourceType.class).annotatedWith(Names.named("h2")).to(H2.class);
bind(DataSourceType.class).annotatedWith(Names.named("jdbc")).to(JDBC.class);
bind(DataSourceType.class).annotatedWith(Names.named("mysql")).to(MySql.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Derby.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Derby.java
new file mode 100644
index 0000000..dd300c6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Derby.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2015 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.schema;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+class Derby extends BaseDataSourceType {
+
+ protected final Config cfg;
+ private final SitePaths site;
+
+ @Inject
+ Derby(@GerritServerConfig Config cfg,
+ SitePaths site) {
+ super("org.apache.derby.jdbc.EmbeddedDriver");
+ this.cfg = cfg;
+ this.site = site;
+ }
+
+ @Override
+ public String getUrl() {
+ String database = cfg.getString("database", null, "database");
+ if (database == null || database.isEmpty()) {
+ database = "db/ReviewDB";
+ }
+ return "jdbc:derby:" + site.resolve(database).toString() + ";create=true";
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 769329d..a4a7ddc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_111> C = Schema_111.class;
+ public static final Class<Schema_113> C = Schema_113.class;
public static int getBinaryVersion() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_108.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_108.java
index 8cbf119..cfafb37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_108.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_108.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -143,9 +144,15 @@
SetMultimap<Project.NameKey, Change.Id> openByProject =
HashMultimap.create();
for (Change c : db.changes().all()) {
- if (c.getStatus().isOpen()) {
- openByProject.put(c.getProject(), c.getId());
+ Status status = c.getStatus();
+ if (status != null && status.isClosed()) {
+ continue;
}
+
+ // The old "submitted" state is not supported anymore
+ // (thus status is null) but it was an opened state and needs
+ // to be migrated as such
+ openByProject.put(c.getProject(), c.getId());
}
return openByProject;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_112.java
similarity index 65%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_112.java
index cd01186..3e879bd 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_112.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.server.schema;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_112 extends SchemaVersion {
+ @Inject
+ Schema_112(Provider<Schema_111> prior) {
+ super(prior);
+ }
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_113.java
similarity index 65%
copy from gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_113.java
index cd01186..32d655e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_113.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2015 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,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.common.data;
+package com.google.gerrit.server.schema;
-/** Detail necessary to display an action. */
-public class UiCommandDetail {
- public String id;
- public String method;
- public String label;
- public String title;
- public boolean enabled;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_113 extends SchemaVersion {
+ @Inject
+ Schema_113(Provider<Schema_112> prior) {
+ super(prior);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
index 8ef32c0..684a72e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.schema;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.CharMatcher;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -73,7 +75,7 @@
}
private List<String> parse(final InputStream in) throws IOException {
- try (BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(in, UTF_8))) {
String delimiter = ";";
List<String> commands = new ArrayList<>();
StringBuilder buffer = new StringBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
index 7665c64..a2693e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
@@ -26,7 +26,6 @@
import java.io.File;
import java.io.IOException;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@@ -36,8 +35,7 @@
@Inject
DefaultSecureStore(SitePaths site) {
- Path secureConfig = site.etc_dir.resolve("secure.config");
- sec = new FileBasedConfig(secureConfig.toFile(), FS.DETECTED);
+ sec = new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED);
try {
sec.load();
} catch (Exception e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java b/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java
index 21634ac..f59bba9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.tools;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.common.Version;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -72,7 +74,7 @@
SortedMap<String, Entry> toc = new TreeMap<>();
final BufferedReader br =
new BufferedReader(new InputStreamReader(new ByteArrayInputStream(
- read("TOC")), "UTF-8"));
+ read("TOC")), UTF_8));
String line;
while ((line = br.readLine()) != null) {
if (line.length() > 0 && !line.startsWith("#")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/FallbackRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/FallbackRequestContext.java
index 5514ef5..55c2992 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/FallbackRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/FallbackRequestContext.java
@@ -37,7 +37,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java
index ee74b35..900bb42 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ManualRequestContext.java
@@ -40,7 +40,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginLogFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginLogFile.java
new file mode 100644
index 0000000..17f6535
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginLogFile.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2015 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 com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.systemstatus.ServerInformation;
+import com.google.inject.Inject;
+
+import org.apache.log4j.AsyncAppender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+public abstract class PluginLogFile implements LifecycleListener {
+
+ private final SystemLog systemLog;
+ private final ServerInformation serverInfo;
+ private final String logName;
+ private final Layout layout;
+
+ @Inject
+ public PluginLogFile(SystemLog systemLog,
+ ServerInformation serverInfo,
+ String logName,
+ Layout layout) {
+ this.systemLog = systemLog;
+ this.serverInfo = serverInfo;
+ this.logName = logName;
+ this.layout = layout;
+ }
+
+ @Override
+ public void start() {
+ AsyncAppender asyncAppender =
+ systemLog.createAsyncAppender(logName, layout);
+ Logger logger = LogManager.getLogger(logName);
+ logger.removeAppender(logName);
+ logger.addAppender(asyncAppender);
+ logger.setAdditivity(false);
+ }
+
+ @Override
+ public void stop() {
+ // stop is called when plugin is unloaded or when the server shutdown.
+ // Only clean up when the server is shutting down to prevent issue when a
+ // plugin is reloaded. The issue is that gerrit load the new plugin and then
+ // unload the old one so because loggers are static, the unload of the old
+ // plugin would remove the appenders just created by the new plugin.
+ if (serverInfo.getState() == ServerInformation.State.SHUTDOWN) {
+ LogManager.getLogger(logName).removeAllAppenders();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java
index a836fd7..943e518 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/PluginRequestContext.java
@@ -29,7 +29,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestContext.java
index 86c74e0..506a1c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestContext.java
@@ -23,6 +23,6 @@
* by the GerritGlobalModule scope.
*/
public interface RequestContext {
- CurrentUser getCurrentUser();
+ CurrentUser getUser();
Provider<ReviewDb> getReviewDbProvider();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java
index f63da5439..d1cf47c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java
@@ -188,8 +188,8 @@
public T call() throws Exception {
RequestContext old = local.setContext(new RequestContext() {
@Override
- public CurrentUser getCurrentUser() {
- return context.getCurrentUser();
+ public CurrentUser getUser() {
+ return context.getUser();
}
@Override
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 2b6b86e..ede3365 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
@@ -31,7 +31,7 @@
}
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return user;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
index 32cdca5..c857c40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.util;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Strings;
import com.google.gerrit.common.Die;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -60,7 +62,7 @@
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
dst.setName(name);
dst.setLayout(layout);
- dst.setEncoding("UTF-8");
+ dst.setEncoding(UTF_8.name());
dst.setFile(resolve(logdir).resolve(name).toString());
dst.setImmediateFlush(true);
dst.setAppend(true);
@@ -72,6 +74,7 @@
public AsyncAppender createAsyncAppender(String name, Layout layout) {
AsyncAppender async = new AsyncAppender();
+ async.setName(name);
async.setBlocking(true);
async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
async.setLocationInfo(false);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
index d1b1da4..3e405a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestContext.java
@@ -53,13 +53,13 @@
@Provides
CurrentUser provideCurrentUser(RequestContext ctx) {
- return ctx.getCurrentUser();
+ return ctx.getUser();
}
@Provides
IdentifiedUser provideCurrentUser(CurrentUser user) {
if (user.isIdentifiedUser()) {
- return (IdentifiedUser) user;
+ return user.asIdentifiedUser();
}
throw new ProvisionException(NotSignedInException.MESSAGE,
new NotSignedInException());
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java
index a63b1e7..a3e1a96 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_1.java
@@ -18,7 +18,6 @@
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
import com.googlecode.prolog_cafe.exceptions.EvaluationException;
@@ -54,7 +53,7 @@
Term resultTerm;
if (curUser.isIdentifiedUser()) {
- Account.Id id = ((IdentifiedUser)curUser).getAccountId();
+ Account.Id id = curUser.getAccountId();
resultTerm = new IntegerTerm(id.get());
} else if (curUser instanceof AnonymousUser) {
resultTerm = anonymous;
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 2a45819..9a4e77c 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -283,6 +283,7 @@
%% - The maximum is never used.
%%
any_with_block(Label, Min, reject(Who)) :-
+ Min < 0,
check_label_range_permission(Label, Min, ok(Who)),
!
.
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
index 2646fd0..d93aa34 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -48,5 +48,6 @@
v = text/x-verilog
vert = x-shader/x-vertex
vh = text/x-verilog
+vhdl = text/x-vhdl
vm = text/velocity
yaml = text/x-yaml
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 e658729..8956e8f 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
@@ -16,6 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.TimeUtil;
import com.google.inject.Guice;
@@ -106,7 +107,7 @@
SymbolTerm pathTerm = SymbolTerm.create(prologResource);
JavaObjectTerm inTerm =
new JavaObjectTerm(new PushbackReader(new BufferedReader(
- new InputStreamReader(in, "UTF-8")), Prolog.PUSHBACK_SIZE));
+ new InputStreamReader(in, UTF_8)), Prolog.PUSHBACK_SIZE));
if (!env.execute(Prolog.BUILTIN, "consult_stream", pathTerm, inTerm)) {
throw new CompileException("Cannot consult " + prologResource);
}
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 e953cbf..1e752d4 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
@@ -204,7 +204,7 @@
@Provides
@Singleton
- CurrentUser getCurrentUser(IdentifiedUser.GenericFactory userFactory) {
+ CurrentUser getUser(IdentifiedUser.GenericFactory userFactory) {
return userFactory.create(ownerId);
}
};
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java
index 6100ffd..50a6c11 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/HashtagsTest.java
@@ -28,7 +28,7 @@
@Test
public void nullCommitMessage() throws Exception {
- assertThat(HashtagsUtil.extractTags(null)).isEmpty();
+ assertThat(HashtagsUtil.extractTags((String) null)).isEmpty();
}
@Test
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 480efb4..ab00ba8 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,6 +14,7 @@
package com.google.gerrit.server.config;
+import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -21,11 +22,98 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
+import com.google.gerrit.extensions.client.Theme;
+
+import org.eclipse.jgit.lib.Config;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
public class ConfigUtilTest {
+ private static final String SECT = "foo";
+ private static final String SUB = "bar";
+
+ static class SectionInfo {
+ public static final String CONSTANT = "42";
+ public transient String missing;
+ public int i;
+ public Integer ii;
+ public Integer id;
+ public long l;
+ public Long ll;
+ public Long ld;
+ public boolean b;
+ public Boolean bb;
+ public Boolean bd;
+ public String s;
+ public String sd;
+ public Theme t;
+ public Theme td;
+ static SectionInfo defaults() {
+ SectionInfo i = new SectionInfo();
+ i.i = 1;
+ i.ii = 2;
+ i.id = 3;
+ i.l = 4L;
+ i.ll = 5L;
+ i.ld = 6L;
+ i.b = true;
+ i.bb = false;
+ i.bd = true;
+ i.s = "foo";
+ i.sd = "bar";
+ i.t = Theme.DEFAULT;
+ i.td = Theme.DEFAULT;
+ return i;
+ }
+ }
+
+ @Test
+ public void testStoreLoadSection() throws Exception {
+ SectionInfo d = SectionInfo.defaults();
+ SectionInfo in = new SectionInfo();
+ in.missing = "42";
+ in.i = 1;
+ in.ii = 43;
+ in.l = 4L;
+ in.ll = -43L;
+ in.b = false;
+ in.bb = true;
+ in.bd = false;
+ in.s = "baz";
+ in.t = Theme.MIDNIGHT;
+
+ Config cfg = new Config();
+ ConfigUtil.storeSection(cfg, SECT, SUB, in, d);
+
+ assertThat(cfg.getString(SECT, SUB, "CONSTANT")).isNull();
+ assertThat(cfg.getString(SECT, SUB, "missing")).isNull();
+ assertThat(cfg.getBoolean(SECT, SUB, "b", false)).isEqualTo(in.b);
+ assertThat(cfg.getBoolean(SECT, SUB, "bb", false)).isEqualTo(in.bb);
+ assertThat(cfg.getInt(SECT, SUB, "i", 0)).isEqualTo(0);
+ assertThat(cfg.getInt(SECT, SUB, "ii", 0)).isEqualTo(in.ii);
+ assertThat(cfg.getLong(SECT, SUB, "l", 0L)).isEqualTo(0L);
+ assertThat(cfg.getLong(SECT, SUB, "ll", 0L)).isEqualTo(in.ll);
+ assertThat(cfg.getString(SECT, SUB, "s")).isEqualTo(in.s);
+ assertThat(cfg.getString(SECT, SUB, "sd")).isNull();
+
+ SectionInfo out = new SectionInfo();
+ ConfigUtil.loadSection(cfg, SECT, SUB, out, d);
+ assertThat(out.i).isEqualTo(in.i);
+ assertThat(out.ii).isEqualTo(in.ii);
+ assertThat(out.id).isEqualTo(d.id);
+ assertThat(out.l).isEqualTo(in.l);
+ assertThat(out.ll).isEqualTo(in.ll);
+ assertThat(out.ld).isEqualTo(d.ld);
+ assertThat(out.b).isEqualTo(in.b);
+ assertThat(out.bb).isEqualTo(in.bb);
+ assertThat(out.bd).isNull();
+ assertThat(out.s).isEqualTo(in.s);
+ assertThat(out.sd).isEqualTo(d.sd);
+ assertThat(out.t).isEqualTo(in.t);
+ assertThat(out.td).isEqualTo(d.td);
+ }
+
@Test
public void testTimeUnit() {
assertEquals(ms(0, MILLISECONDS), parse("0"));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
index ac6d805..128c2a7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
@@ -34,13 +34,18 @@
import com.google.gerrit.server.query.change.OrSource;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
public class IndexRewriteTest {
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
private FakeIndex index;
private IndexCollection indexes;
private ChangeQueryBuilder queryBuilder;
@@ -52,7 +57,8 @@
indexes = new IndexCollection();
indexes.setSearchIndex(index);
queryBuilder = new FakeQueryBuilder(indexes);
- rewrite = new IndexRewriteImpl(indexes);
+ rewrite = new IndexRewriteImpl(indexes,
+ IndexConfig.create(0, 0, 3, 100));
}
@Test
@@ -209,6 +215,17 @@
out.getChildren());
}
+ @Test
+ public void testTooManyTerms() throws Exception {
+ String q = "file:a OR file:b OR file:c";
+ Predicate<ChangeData> in = parse(q);
+ assertEquals(query(in), rewrite(in));
+
+ exception.expect(QueryParseException.class);
+ exception.expectMessage("too many terms in query");
+ rewrite(parse(q + " OR file:d"));
+ }
+
private Predicate<ChangeData> parse(String query) throws QueryParseException {
return queryBuilder.parse(query);
}
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 c7b81a2..145042c 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
@@ -20,8 +20,6 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import java.io.UnsupportedEncodingException;
-
public class AddressTest {
@Rule
public ExpectedException exception = ExpectedException.none();
@@ -155,10 +153,6 @@
}
private static String format(final String name, final String email) {
- try {
- return new Address(name, email).toHeaderString();
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Cannot encode address", e);
- }
+ return new Address(name, email).toHeaderString();
}
}
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 85272d0..9706feb 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
@@ -35,6 +35,7 @@
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.ProjectConfig;
@@ -670,4 +671,29 @@
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
+
+ @Test
+ public void testValidateRefPatternsOK() throws Exception {
+ RefControl.validateRefPattern("refs/*");
+ RefControl.validateRefPattern("^refs/heads/*");
+ RefControl.validateRefPattern("^refs/tags/[0-9a-zA-Z-_.]+");
+ RefControl.validateRefPattern("refs/heads/review/${username}/*");
+ }
+
+ @Test(expected = InvalidNameException.class)
+ public void testValidateBadRefPatternDoubleCaret() throws Exception {
+ RefControl.validateRefPattern("^^refs/*");
+ }
+
+ @Test(expected = InvalidNameException.class)
+ public void testValidateBadRefPatternDanglingCharacter() throws Exception {
+ RefControl
+ .validateRefPattern("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}*");
+ }
+
+ @Test
+ public void testValidateRefPatternNoDanglingCharacter() throws Exception {
+ RefControl
+ .validateRefPattern("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}");
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 499caa2..1d79400 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -53,11 +53,13 @@
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.index.IndexCollection;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -73,6 +75,7 @@
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.revwalk.RevCommit;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
@@ -113,6 +116,7 @@
@ConfigSuite.Parameter public Config config;
@Inject protected AccountManager accountManager;
+ @Inject protected BatchUpdate.Factory updateFactory;
@Inject protected ChangeInserter.Factory changeFactory;
@Inject protected PatchSetInserter.Factory patchSetFactory;
@Inject protected ChangeControl.GenericFactory changeControlFactory;
@@ -163,7 +167,7 @@
userFactory.create(Providers.of(db), requestUserId);
return new RequestContext() {
@Override
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return requestUser;
}
@@ -210,8 +214,8 @@
@Test
public void byId() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
assertQuery("12345");
assertQuery(change1.getId().get(), change1);
@@ -221,7 +225,7 @@
@Test
public void byKey() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change = newChange(repo, null, null, null, null).insert();
+ Change change = insert(newChange(repo, null, null, null, null));
String key = change.getKey().get();
assertQuery("I0000000000000000000000000000000000000000");
@@ -234,7 +238,7 @@
@Test
public void byTriplet() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change = newChange(repo, null, null, null, "branch").insert();
+ Change change = insert(newChange(repo, null, null, null, "branch"));
String k = change.getKey().get();
assertQuery("repo~branch~" + k, change);
@@ -260,11 +264,11 @@
ChangeInserter ins1 = newChange(repo, null, null, null, null);
Change change1 = ins1.getChange();
change1.setStatus(Change.Status.NEW);
- ins1.insert();
+ insert(ins1);
ChangeInserter ins2 = newChange(repo, null, null, null, null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.MERGED);
- ins2.insert();
+ insert(ins2);
assertQuery("status:new", change1);
assertQuery("status:NEW", change1);
@@ -279,15 +283,15 @@
ChangeInserter ins1 = newChange(repo, null, null, null, null);
Change change1 = ins1.getChange();
change1.setStatus(Change.Status.NEW);
- ins1.insert();
+ insert(ins1);
ChangeInserter ins2 = newChange(repo, null, null, null, null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.DRAFT);
- ins2.insert();
+ insert(ins2);
ChangeInserter ins3 = newChange(repo, null, null, null, null);
Change change3 = ins3.getChange();
change3.setStatus(Change.Status.MERGED);
- ins3.insert();
+ insert(ins3);
Change[] expected = new Change[] {change2, change1};
assertQuery("status:open", expected);
@@ -309,15 +313,15 @@
ChangeInserter ins1 = newChange(repo, null, null, null, null);
Change change1 = ins1.getChange();
change1.setStatus(Change.Status.MERGED);
- ins1.insert();
+ insert(ins1);
ChangeInserter ins2 = newChange(repo, null, null, null, null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.ABANDONED);
- ins2.insert();
+ insert(ins2);
ChangeInserter ins3 = newChange(repo, null, null, null, null);
Change change3 = ins3.getChange();
change3.setStatus(Change.Status.NEW);
- ins3.insert();
+ insert(ins3);
Change[] expected = new Change[] {change2, change1};
assertQuery("status:closed", expected);
@@ -337,11 +341,11 @@
ChangeInserter ins1 = newChange(repo, null, null, null, null);
Change change1 = ins1.getChange();
change1.setStatus(Change.Status.NEW);
- ins1.insert();
+ insert(ins1);
ChangeInserter ins2 = newChange(repo, null, null, null, null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.MERGED);
- ins2.insert();
+ insert(ins2);
assertQuery("status:n", change1);
assertQuery("status:ne", change1);
@@ -357,7 +361,7 @@
public void byCommit() throws Exception {
TestRepository<Repo> repo = createProject("repo");
ChangeInserter ins = newChange(repo, null, null, null, null);
- ins.insert();
+ insert(ins);
String sha = ins.getPatchSet().getRevision().get();
assertQuery("0000000000000000000000000000000000000000");
@@ -370,10 +374,10 @@
@Test
public void byOwner() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ Change change1 = insert(newChange(repo, null, null, userId.get(), null));
int user2 = accountManager.authenticate(AuthRequest.forUser("anotheruser"))
.getAccountId().get();
- Change change2 = newChange(repo, null, null, user2, null).insert();
+ Change change2 = insert(newChange(repo, null, null, user2, null));
assertQuery("owner:" + userId.get(), change1);
assertQuery("owner:" + user2, change2);
@@ -382,7 +386,7 @@
@Test
public void byAuthor() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ Change change1 = insert(newChange(repo, null, null, userId.get(), null));
// By exact email address
assertQuery("author:jauthor@example.com", change1);
@@ -405,7 +409,7 @@
@Test
public void byCommitter() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ Change change1 = insert(newChange(repo, null, null, userId.get(), null));
// By exact email address
assertQuery("committer:jcommitter@example.com", change1);
@@ -428,10 +432,10 @@
@Test
public void byOwnerIn() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ Change change1 = insert(newChange(repo, null, null, userId.get(), null));
int user2 = accountManager.authenticate(AuthRequest.forUser("anotheruser"))
.getAccountId().get();
- Change change2 = newChange(repo, null, null, user2, null).insert();
+ Change change2 = insert(newChange(repo, null, null, user2, null));
assertQuery("ownerin:Administrators", change1);
assertQuery("ownerin:\"Registered Users\"", change2, change1);
@@ -441,8 +445,8 @@
public void byProject() throws Exception {
TestRepository<Repo> repo1 = createProject("repo1");
TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = newChange(repo1, null, null, null, null).insert();
- Change change2 = newChange(repo2, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo1, null, null, null, null));
+ Change change2 = insert(newChange(repo2, null, null, null, null));
assertQuery("project:foo");
assertQuery("project:repo");
@@ -454,8 +458,8 @@
public void byProjectPrefix() throws Exception {
TestRepository<Repo> repo1 = createProject("repo1");
TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = newChange(repo1, null, null, null, null).insert();
- Change change2 = newChange(repo2, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo1, null, null, null, null));
+ Change change2 = insert(newChange(repo2, null, null, null, null));
assertQuery("projects:foo");
assertQuery("projects:repo1", change1);
@@ -466,8 +470,8 @@
@Test
public void byBranchAndRef() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, "master").insert();
- Change change2 = newChange(repo, null, null, null, "branch").insert();
+ Change change1 = insert(newChange(repo, null, null, null, "master"));
+ Change change2 = insert(newChange(repo, null, null, null, "branch"));
assertQuery("branch:foo");
assertQuery("branch:master", change1);
@@ -487,24 +491,24 @@
ChangeInserter ins1 = newChange(repo, null, null, null, null);
Change change1 = ins1.getChange();
change1.setTopic("feature1");
- ins1.insert();
+ insert(ins1);
ChangeInserter ins2 = newChange(repo, null, null, null, null);
Change change2 = ins2.getChange();
change2.setTopic("feature2");
- ins2.insert();
+ insert(ins2);
ChangeInserter ins3 = newChange(repo, null, null, null, null);
Change change3 = ins3.getChange();
change3.setTopic("Cherrypick-feature2");
- ins3.insert();
+ insert(ins3);
ChangeInserter ins4 = newChange(repo, null, null, null, null);
Change change4 = ins4.getChange();
change4.setTopic("feature2-fixup");
- ins4.insert();
+ insert(ins4);
- Change change5 = newChange(repo, null, null, null, null).insert();
+ Change change5 = insert(newChange(repo, null, null, null, null));
assertQuery("intopic:foo");
assertQuery("intopic:feature1", change1);
@@ -520,9 +524,9 @@
public void byMessageExact() throws Exception {
TestRepository<Repo> repo = createProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("one").create());
- Change change1 = newChange(repo, commit1, null, null, null).insert();
+ Change change1 = insert(newChange(repo, commit1, null, null, null));
RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
- Change change2 = newChange(repo, commit2, null, null, null).insert();
+ Change change2 = insert(newChange(repo, commit2, null, null, null));
assertQuery("message:foo");
assertQuery("message:one", change1);
@@ -534,10 +538,10 @@
TestRepository<Repo> repo = createProject("repo");
RevCommit commit1 =
repo.parseBody(repo.commit().message("12345 67890").create());
- Change change1 = newChange(repo, commit1, null, null, null).insert();
+ Change change1 = insert(newChange(repo, commit1, null, null, null));
RevCommit commit2 =
repo.parseBody(repo.commit().message("12346 67891").create());
- Change change2 = newChange(repo, commit2, null, null, null).insert();
+ Change change2 = insert(newChange(repo, commit2, null, null, null));
assertQuery("message:1234");
assertQuery("message:12345", change1);
@@ -549,7 +553,7 @@
accountManager.authenticate(AuthRequest.forUser("anotheruser"));
TestRepository<Repo> repo = createProject("repo");
ChangeInserter ins = newChange(repo, null, null, null, null);
- Change change = ins.insert();
+ Change change = insert(ins);
gApi.changes().id(change.getId().get()).current()
.review(new ReviewInput().label("Code-Review", 1));
@@ -591,7 +595,7 @@
Change last = null;
int n = 5;
for (int i = 0; i < n; i++) {
- last = newChange(repo, null, null, null, null).insert();
+ last = insert(newChange(repo, null, null, null, null));
}
for (int i = 1; i <= n + 2; i++) {
@@ -618,7 +622,7 @@
TestRepository<Repo> repo = createProject("repo");
List<Change> changes = Lists.newArrayList();
for (int i = 0; i < 2; i++) {
- changes.add(newChange(repo, null, null, null, null).insert());
+ changes.add(insert(newChange(repo, null, null, null, null)));
}
assertQuery("status:new", changes.get(1), changes.get(0));
@@ -632,7 +636,7 @@
TestRepository<Repo> repo = createProject("repo");
List<Change> changes = Lists.newArrayList();
for (int i = 0; i < 3; i++) {
- changes.add(newChange(repo, null, null, null, null).insert());
+ changes.add(insert(newChange(repo, null, null, null, null)));
}
assertQuery("status:new limit:2", changes.get(2), changes.get(1));
@@ -646,7 +650,7 @@
@Test
public void maxPages() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change = newChange(repo, null, null, null, null).insert();
+ Change change = insert(newChange(repo, null, null, null, null));
QueryRequest query = newQuery("status:new").withLimit(10);
assertQuery(query, change);
@@ -664,7 +668,7 @@
List<Change> changes = Lists.newArrayList();
for (int i = 0; i < 5; i++) {
inserters.add(newChange(repo, null, null, null, null));
- changes.add(inserters.get(i).insert());
+ changes.add(insert(inserters.get(i)));
}
for (int i : ImmutableList.of(2, 0, 1, 4, 3)) {
@@ -686,8 +690,8 @@
clockStepMs = MILLISECONDS.convert(2, MINUTES);
TestRepository<Repo> repo = createProject("repo");
ChangeInserter ins1 = newChange(repo, null, null, null, null);
- Change change1 = ins1.insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(ins1);
+ Change change2 = insert(newChange(repo, null, null, null, null));
assertThat(lastUpdatedMs(change1)).isLessThan(lastUpdatedMs(change2));
assertQuery("status:new", change2, change1);
@@ -708,8 +712,8 @@
public void updatedOrderWithSubMinuteResolution() throws Exception {
TestRepository<Repo> repo = createProject("repo");
ChangeInserter ins1 = newChange(repo, null, null, null, null);
- Change change1 = ins1.insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(ins1);
+ Change change2 = insert(newChange(repo, null, null, null, null));
assertThat(lastUpdatedMs(change1)).isLessThan(lastUpdatedMs(change2));
@@ -730,11 +734,11 @@
@Test
public void filterOutMoreThanOnePageOfResults() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change = newChange(repo, null, null, userId.get(), null).insert();
+ Change change = insert(newChange(repo, null, null, userId.get(), null));
int user2 = accountManager.authenticate(AuthRequest.forUser("anotheruser"))
.getAccountId().get();
for (int i = 0; i < 5; i++) {
- newChange(repo, null, null, user2, null).insert();
+ insert(newChange(repo, null, null, user2, null));
}
assertQuery("status:new ownerin:Administrators", change);
@@ -747,7 +751,7 @@
int user2 = accountManager.authenticate(AuthRequest.forUser("anotheruser"))
.getAccountId().get();
for (int i = 0; i < 5; i++) {
- newChange(repo, null, null, user2, null).insert();
+ insert(newChange(repo, null, null, user2, null));
}
assertQuery("status:new ownerin:Administrators");
@@ -761,7 +765,7 @@
repo.commit().message("one")
.add("dir/file1", "contents1").add("dir/file2", "contents2")
.create());
- Change change = newChange(repo, commit, null, null, null).insert();
+ Change change = insert(newChange(repo, commit, null, null, null));
assertQuery("file:file");
assertQuery("file:dir", change);
@@ -778,7 +782,7 @@
repo.commit().message("one")
.add("dir/file1", "contents1").add("dir/file2", "contents2")
.create());
- Change change = newChange(repo, commit, null, null, null).insert();
+ Change change = insert(newChange(repo, commit, null, null, null));
assertQuery("file:.*file.*");
assertQuery("file:^file.*"); // Whole path only.
@@ -792,7 +796,7 @@
repo.commit().message("one")
.add("dir/file1", "contents1").add("dir/file2", "contents2")
.create());
- Change change = newChange(repo, commit, null, null, null).insert();
+ Change change = insert(newChange(repo, commit, null, null, null));
assertQuery("path:file");
assertQuery("path:dir");
@@ -809,7 +813,7 @@
repo.commit().message("one")
.add("dir/file1", "contents1").add("dir/file2", "contents2")
.create());
- Change change = newChange(repo, commit, null, null, null).insert();
+ Change change = insert(newChange(repo, commit, null, null, null));
assertQuery("path:.*file.*");
assertQuery("path:^dir.file.*", change);
@@ -819,7 +823,7 @@
public void byComment() throws Exception {
TestRepository<Repo> repo = createProject("repo");
ChangeInserter ins = newChange(repo, null, null, null, null);
- Change change = ins.insert();
+ Change change = insert(ins);
ReviewInput input = new ReviewInput();
input.message = "toplevel";
@@ -840,8 +844,8 @@
long thirtyHours = MILLISECONDS.convert(30, HOURS);
clockStepMs = thirtyHours;
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
clockStepMs = 0; // Queried by AgePredicate constructor.
long now = TimeUtil.nowMs();
assertThat(lastUpdatedMs(change2) - lastUpdatedMs(change1))
@@ -862,8 +866,8 @@
public void byBefore() throws Exception {
clockStepMs = MILLISECONDS.convert(30, HOURS);
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
clockStepMs = 0;
assertQuery("before:2009-09-29");
@@ -882,8 +886,8 @@
public void byAfter() throws Exception {
clockStepMs = MILLISECONDS.convert(30, HOURS);
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
clockStepMs = 0;
assertQuery("after:2009-10-03");
@@ -904,8 +908,8 @@
RevCommit commit2 = repo.parseBody(
repo.commit().parent(commit1).add("file1", "foo").create());
- Change change1 = newChange(repo, commit1, null, null, null).insert();
- Change change2 = newChange(repo, commit2, null, null, null).insert();
+ Change change1 = insert(newChange(repo, commit1, null, null, null));
+ Change change2 = insert(newChange(repo, commit2, null, null, null));
assertQuery("added:>4");
assertQuery("-added:<=4");
@@ -954,8 +958,8 @@
private List<Change> setUpHashtagChanges() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
HashtagsInput in = new HashtagsInput();
in.add = ImmutableSet.of("foo");
@@ -997,20 +1001,20 @@
public void byDefault() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
RevCommit commit2 = repo.parseBody(
repo.commit().message("foosubject").create());
- Change change2 = newChange(repo, commit2, null, null, null).insert();
+ Change change2 = insert(newChange(repo, commit2, null, null, null));
RevCommit commit3 = repo.parseBody(
repo.commit()
.add("Foo.java", "foo contents")
.create());
- Change change3 = newChange(repo, commit3, null, null, null).insert();
+ Change change3 = insert(newChange(repo, commit3, null, null, null));
ChangeInserter ins4 = newChange(repo, null, null, null, null);
- Change change4 = ins4.insert();
+ Change change4 = insert(ins4);
ReviewInput ri4 = new ReviewInput();
ri4.message = "toplevel";
ri4.labels = ImmutableMap.<String, Short> of("Code-Review", (short) 1);
@@ -1019,9 +1023,9 @@
ChangeInserter ins5 = newChange(repo, null, null, null, null);
Change change5 = ins5.getChange();
change5.setTopic("feature5");
- ins5.insert();
+ insert(ins5);
- Change change6 = newChange(repo, null, null, null, "branch6").insert();
+ Change change6 = insert(newChange(repo, null, null, null, "branch6"));
assertQuery(change1.getId().get(), change1);
assertQuery(ChangeTriplet.format(change1), change1);
@@ -1042,11 +1046,11 @@
@Test
public void implicitVisibleTo() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ Change change1 = insert(newChange(repo, null, null, userId.get(), null));
ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.DRAFT);
- ins2.insert();
+ insert(ins2);
String q = "project:repo";
assertQuery(q, change2, change1);
@@ -1060,11 +1064,11 @@
@Test
public void explicitVisibleTo() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, userId.get(), null).insert();
+ Change change1 = insert(newChange(repo, null, null, userId.get(), null));
ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.DRAFT);
- ins2.insert();
+ insert(ins2);
String q = "project:repo";
assertQuery(q, change2, change1);
@@ -1079,8 +1083,8 @@
@Test
public void byCommentBy() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
int user2 = accountManager.authenticate(AuthRequest.forUser("anotheruser"))
.getAccountId().get();
@@ -1105,12 +1109,12 @@
@Test
public void byFrom() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
int user2 = accountManager.authenticate(AuthRequest.forUser("anotheruser"))
.getAccountId().get();
ChangeInserter ins2 = newChange(repo, null, null, user2, null);
- Change change2 = ins2.insert();
+ Change change2 = insert(ins2);
ReviewInput input = new ReviewInput();
input.message = "toplevel";
@@ -1146,10 +1150,10 @@
repo.commit()
.add("file4", "contents4")
.create());
- Change change1 = newChange(repo, commit1, null, null, null).insert();
- Change change2 = newChange(repo, commit2, null, null, null).insert();
- Change change3 = newChange(repo, commit3, null, null, null).insert();
- Change change4 = newChange(repo, commit4, null, null, null).insert();
+ Change change1 = insert(newChange(repo, commit1, null, null, null));
+ Change change2 = insert(newChange(repo, commit2, null, null, null));
+ Change change3 = insert(newChange(repo, commit3, null, null, null));
+ Change change4 = insert(newChange(repo, commit4, null, null, null));
assertQuery("conflicts:" + change1.getId().get(), change3);
assertQuery("conflicts:" + change2.getId().get());
@@ -1161,9 +1165,9 @@
public void reviewedBy() throws Exception {
clockStepMs = MILLISECONDS.convert(2, MINUTES);
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
- Change change3 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
+ Change change3 = insert(newChange(repo, null, null, null, null));
gApi.changes()
.id(change1.getId().get())
@@ -1219,7 +1223,7 @@
Branch.NameKey dest = null;
for (int i = 0; i < n; i++) {
ChangeInserter ins = newChange(repo, null, null, null, null);
- ins.insert();
+ insert(ins);
if (dest == null) {
dest = ins.getChange().getDest();
}
@@ -1229,7 +1233,7 @@
for (int i = 1; i <= 11; i++) {
Iterable<ChangeData> cds = internalChangeQuery.byCommitsOnBranchNotMerged(
- indexes.getSearchIndex().getSchema(), dest, shas, i);
+ repo.getRepository(), db, dest, shas, i);
Iterable<Integer> ids = FluentIterable.from(cds).transform(
new Function<ChangeData, Integer>() {
@Override
@@ -1237,7 +1241,7 @@
return in.getId().get();
}
});
- String name = "batch size " + i;
+ String name = "limit " + i;
assertThat(ids).named(name).hasSize(n);
assertThat(ids).named(name)
.containsExactlyElementsIn(expectedIds);
@@ -1248,7 +1252,7 @@
public void prepopulatedFields() throws Exception {
assume().that(notesMigration.enabled()).isFalse();
TestRepository<Repo> repo = createProject("repo");
- Change change = newChange(repo, null, null, null, null).insert();
+ Change change = insert(newChange(repo, null, null, null, null));
db = new DisabledReviewDb();
requestContext.setContext(newRequestContext(userId));
@@ -1309,10 +1313,20 @@
Change change = new Change(new Change.Key(key), id, ownerId,
new Branch.NameKey(project, branch), TimeUtil.nowTs());
IdentifiedUser user = userFactory.create(Providers.of(db), ownerId);
- return changeFactory.create(
- projectControlFactory.controlFor(project, user),
- change,
- commit);
+ RefControl refControl = projectControlFactory.controlFor(project, user)
+ .controlForRef(change.getDest());
+ return changeFactory.create(refControl, change, commit)
+ .setValidatePolicy(CommitValidators.Policy.NONE);
+ }
+
+ protected Change insert(ChangeInserter ins) throws Exception {
+ try (BatchUpdate bu = updateFactory.create(
+ db, ins.getChange().getProject(), ins.getUser(),
+ ins.getChange().getCreatedOn())) {
+ bu.insertChange(ins);
+ bu.execute();
+ return ins.getChange();
+ }
}
protected Change newPatchSet(TestRepository<Repo> repo, Change c)
@@ -1325,13 +1339,23 @@
.message("message")
.add("file" + n, "contents " + n)
.create());
- ChangeControl ctl = changeControlFactory.controlFor(c.getId(), user);
- return patchSetFactory.create(
- repo.getRepository(), repo.getRevWalk(), ctl, commit)
+ RefControl ctl = projectControlFactory.controlFor(c.getProject(), user)
+ .controlForRef(c.getDest());
+
+ PatchSetInserter inserter = patchSetFactory.create(
+ ctl, new PatchSet.Id(c.getId(), n), commit)
.setSendMail(false)
.setRunHooks(false)
- .setValidatePolicy(ValidatePolicy.NONE)
- .insert();
+ .setValidatePolicy(CommitValidators.Policy.NONE);
+ try (BatchUpdate bu = updateFactory.create(
+ db, c.getProject(), user, TimeUtil.nowTs());
+ ObjectInserter oi = repo.getRepository().newObjectInserter()) {
+ bu.setRepository(repo.getRepository(), repo.getRevWalk(), oi);
+ bu.addOp(c.getId(), inserter);
+ bu.execute();
+ }
+
+ return inserter.getChange();
}
protected void assertBadQuery(Object query) throws Exception {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 6122d65..b503a13 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -38,10 +38,10 @@
TestRepository<Repo> repo = createProject("repo");
RevCommit commit1 =
repo.parseBody(repo.commit().message("foo_bar_foo").create());
- Change change1 = newChange(repo, commit1, null, null, null).insert();
+ Change change1 = insert(newChange(repo, commit1, null, null, null));
RevCommit commit2 =
repo.parseBody(repo.commit().message("one.two.three").create());
- Change change2 = newChange(repo, commit2, null, null, null).insert();
+ Change change2 = insert(newChange(repo, commit2, null, null, null));
assertQuery("message:foo_ba");
assertQuery("message:bar", change1);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
index 9bdd795..7e7899b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
@@ -82,9 +82,9 @@
public void isReviewed() throws Exception {
clockStepMs = MILLISECONDS.convert(2, MINUTES);
TestRepository<Repo> repo = createProject("repo");
- Change change1 = newChange(repo, null, null, null, null).insert();
- Change change2 = newChange(repo, null, null, null, null).insert();
- Change change3 = newChange(repo, null, null, null, null).insert();
+ Change change1 = insert(newChange(repo, null, null, null, null));
+ Change change2 = insert(newChange(repo, null, null, null, null));
+ Change change3 = insert(newChange(repo, null, null, null, null));
gApi.changes()
.id(change1.getId().get())
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 69eecc3..8b5e85a 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
@@ -104,7 +104,7 @@
IdentifiedUser user) throws OrmException {
ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class);
expect(ctl.getChange()).andStubReturn(c);
- expect(ctl.getCurrentUser()).andStubReturn(user);
+ expect(ctl.getUser()).andStubReturn(user);
ChangeNotes notes = new ChangeNotes(repoManager, migration, allUsers, c)
.load();
expect(ctl.getNotes()).andStubReturn(notes);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index a84570b..5c897e4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
@@ -51,13 +53,13 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
public abstract class BaseCommand implements Command {
private static final Logger log = LoggerFactory.getLogger(BaseCommand.class);
- public static final String ENC = "UTF-8";
+ public static final Charset ENC = UTF_8;
private static final int PRIVATE_STATUS = 1 << 30;
static final int STATUS_CANCEL = PRIVATE_STATUS | 1;
@@ -87,7 +89,7 @@
private WorkQueue.Executor executor;
@Inject
- private Provider<CurrentUser> userProvider;
+ private Provider<CurrentUser> user;
@Inject
private Provider<SshScope.Context> contextProvider;
@@ -276,7 +278,7 @@
final TaskThunk tt = new TaskThunk(thunk);
if (isAdminHighPriorityCommand()
- && userProvider.get().getCapabilities().canAdministrateServer()) {
+ && user.get().getCapabilities().canAdministrateServer()) {
// Admin commands should not block the main work threads (there
// might be an interactive shell there), nor should they wait
// for the main work threads.
@@ -309,14 +311,7 @@
/** Wrap the supplied output stream in a UTF-8 encoded PrintWriter. */
protected static PrintWriter toPrintWriter(final OutputStream o) {
- try {
- return new PrintWriter(new BufferedWriter(new OutputStreamWriter(o, ENC)));
- } catch (UnsupportedEncodingException e) {
- // Our default encoding is required by the specifications for the
- // runtime APIs, this should never, ever happen.
- //
- throw new RuntimeException("JVM lacks " + ENC + " encoding", e);
- }
+ return new PrintWriter(new BufferedWriter(new OutputStreamWriter(o, ENC)));
}
private int handleError(final Throwable e) {
@@ -337,8 +332,8 @@
if (!(e instanceof UnloggedFailure)) {
final StringBuilder m = new StringBuilder();
m.append("Internal server error");
- if (userProvider.get().isIdentifiedUser()) {
- final IdentifiedUser u = (IdentifiedUser) userProvider.get();
+ if (user.get().isIdentifiedUser()) {
+ final IdentifiedUser u = user.get().asIdentifiedUser();
m.append(" (user ");
m.append(u.getAccount().getUserName());
m.append(" account ");
@@ -403,8 +398,8 @@
StringBuilder m = new StringBuilder();
m.append(context.getCommandLine());
- if (userProvider.get().isIdentifiedUser()) {
- IdentifiedUser u = (IdentifiedUser) userProvider.get();
+ if (user.get().isIdentifiedUser()) {
+ IdentifiedUser u = user.get().asIdentifiedUser();
m.append(" (").append(u.getAccount().getUserName()).append(")");
}
this.taskName = m.toString();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 1a6b2dd..3ce4545 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Preconditions;
@@ -98,7 +99,7 @@
public boolean authenticate(String username, PublicKey suppliedKey,
ServerSession session) {
SshSession sd = session.getAttribute(SshSession.KEY);
- Preconditions.checkState(sd.getCurrentUser() == null);
+ Preconditions.checkState(sd.getUser() == null);
if (PeerDaemonUser.USER_NAME.equals(username)) {
if (myHostKeys.contains(suppliedKey)
|| getPeerKeys().contains(suppliedKey)) {
@@ -193,7 +194,7 @@
}
try {
- byte[] bin = Base64.decodeBase64(line.getBytes("ISO-8859-1"));
+ byte[] bin = Base64.decodeBase64(line.getBytes(ISO_8859_1));
keys.add(new Buffer(bin).getRawPublicKey());
} catch (RuntimeException | SshException e) {
logBadKey(path, line, e);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index 4d6a790..3a8a1f5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -203,7 +203,7 @@
private LoggingEvent log(final String msg) {
final SshSession sd = session.get();
- final CurrentUser user = sd.getCurrentUser();
+ final CurrentUser user = sd.getUser();
final LoggingEvent event = new LoggingEvent( //
Logger.class.getName(), // fqnOfCategoryClass
@@ -224,7 +224,7 @@
String accountId = "-";
if (user != null && user.isIdentifiedUser()) {
- IdentifiedUser u = (IdentifiedUser) user;
+ IdentifiedUser u = user.asIdentifiedUser();
userName = u.getAccount().getUserName();
accountId = "a/" + u.getAccountId().toString();
@@ -261,7 +261,7 @@
} else {
SshSession session = ctx.getSession();
sessionId = IdGenerator.format(session.getSessionId());
- currentUser = session.getCurrentUser();
+ currentUser = session.getUser();
created = ctx.created;
}
auditService.dispatch(new SshAuditEvent(sessionId, currentUser,
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
index cd09cfa..e3455e3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -80,10 +80,10 @@
}
@Override
- public CurrentUser getCurrentUser() {
- final CurrentUser user = session.getCurrentUser();
+ public CurrentUser getUser() {
+ CurrentUser user = session.getUser();
if (user != null && user.isIdentifiedUser()) {
- IdentifiedUser identifiedUser = userFactory.create(((IdentifiedUser) user).getAccountId());
+ IdentifiedUser identifiedUser = userFactory.create(user.getAccountId());
identifiedUser.setAccessPath(user.getAccessPath());
return identifiedUser;
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java
index f055b2f..ff160e0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java
@@ -60,7 +60,7 @@
}
/** Identity of the authenticated user account on the socket. */
- public CurrentUser getCurrentUser() {
+ public CurrentUser getUser() {
return identity;
}
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 b658440..cca426d 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
@@ -121,7 +121,7 @@
public static boolean success(final String username, final ServerSession session,
final SshScope sshScope, final SshLog sshLog,
final SshSession sd, final CurrentUser user) {
- if (sd.getCurrentUser() == null) {
+ if (sd.getUser() == null) {
sd.authenticationSuccess(username, user);
// If this is the first time we've authenticated this
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
index 77738d0..c417f0a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
@@ -102,7 +104,7 @@
if (!msg.endsWith("\n")) {
msg += "\n";
}
- err.write(msg.getBytes("UTF-8"));
+ err.write(msg.getBytes(UTF_8));
err.flush();
onExit(1);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index 647d28d..ec8b94d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -71,7 +71,7 @@
printCommits(r.newlyBanned, "The following commits were banned");
printCommits(r.alreadyBanned, "The following commits were already banned");
printCommits(r.ignored, "The following ids do not represent commits and were ignored");
- } catch (RestApiException | IOException | InterruptedException e) {
+ } catch (RestApiException | IOException e) {
throw die(e);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index aeb69d0..d20a879 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
@@ -33,7 +35,6 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
@@ -82,14 +83,14 @@
}
}
- private String readSshKey() throws UnsupportedEncodingException, IOException {
+ private String readSshKey() throws IOException {
if (sshKey == null) {
return null;
}
if ("-".equals(sshKey)) {
sshKey = "";
BufferedReader br =
- new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ new BufferedReader(new InputStreamReader(in, UTF_8));
String line;
while ((line = br.readLine()) != null) {
sshKey += line + "\n";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 05c1bfc..c6eaebb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -34,12 +34,11 @@
@Override
protected void configure() {
- final CommandName git = Commands.named("git");
- final CommandName gerrit = Commands.named("gerrit");
- CommandName index = Commands.named(gerrit, "index");
- final CommandName logging = Commands.named(gerrit, "logging");
- final CommandName plugin = Commands.named(gerrit, "plugin");
- final CommandName testSubmit = Commands.named(gerrit, "test-submit");
+ CommandName git = Commands.named("git");
+ CommandName gerrit = Commands.named("gerrit");
+ CommandName logging = Commands.named(gerrit, "logging");
+ CommandName plugin = Commands.named(gerrit, "plugin");
+ CommandName testSubmit = Commands.named(gerrit, "test-submit");
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
command(gerrit, AproposCommand.class);
@@ -58,10 +57,6 @@
command(gerrit, VersionCommand.class);
command(gerrit, GarbageCollectionCommand.class);
- command(index).toProvider(new DispatchCommandProvider(index));
- command(index, IndexActivateCommand.class);
- command(index, IndexStartCommand.class);
-
command(gerrit, "plugin").toProvider(new DispatchCommandProvider(plugin));
command(plugin, PluginLsCommand.class);
command(plugin, PluginEnableCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
new file mode 100644
index 0000000..3e7b293
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.sshd.CommandModule;
+import com.google.gerrit.sshd.CommandName;
+import com.google.gerrit.sshd.Commands;
+import com.google.gerrit.sshd.DispatchCommandProvider;
+
+public class IndexCommandsModule extends CommandModule {
+
+ @Override
+ protected void configure() {
+ CommandName gerrit = Commands.named("gerrit");
+ CommandName index = Commands.named(gerrit, "index");
+ command(index).toProvider(new DispatchCommandProvider(index));
+ command(index, IndexActivateCommand.class);
+ command(index, IndexStartCommand.class);
+ }
+}
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 ad72b13..75072e8 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
@@ -19,10 +19,12 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GetGroups;
+import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupJson;
@@ -71,12 +73,13 @@
final Provider<IdentifiedUser> identifiedUser,
final IdentifiedUser.GenericFactory userFactory,
final Provider<GetGroups> accountGetGroups,
- final GroupJson json) {
+ final GroupJson json,
+ GroupBackend groupBackend) {
super(groupCache, groupControlFactory, genericGroupControlFactory,
- identifiedUser, userFactory, accountGetGroups, json);
+ identifiedUser, userFactory, accountGetGroups, json, groupBackend);
}
- void display(final PrintWriter out) throws OrmException {
+ void display(final PrintWriter out) throws OrmException, BadRequestException {
final ColumnFormatter formatter = new ColumnFormatter(out, '\t');
for (final GroupInfo info : get()) {
formatter.addColumn(MoreObjects.firstNonNull(info.name, "n/a"));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
index ff60410..6c07fae 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.Version;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -32,7 +34,6 @@
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
@@ -65,11 +66,10 @@
@Inject
QueryShell(final SchemaFactory<ReviewDb> dbFactory,
- @Assisted final InputStream in, @Assisted final OutputStream out)
- throws UnsupportedEncodingException {
+ @Assisted final InputStream in, @Assisted final OutputStream out) {
this.dbFactory = dbFactory;
- this.in = new BufferedReader(new InputStreamReader(in, "UTF-8"));
- this.out = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
+ this.in = new BufferedReader(new InputStreamReader(in, UTF_8));
+ this.out = new PrintWriter(new OutputStreamWriter(out, UTF_8));
}
public void setOutputFormat(OutputFormat fmt) {
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 1ca4c8c..4570085 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
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.io.CharStreams;
@@ -50,7 +52,6 @@
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;
@@ -249,8 +250,7 @@
}
private ReviewInput reviewFromJson() throws UnloggedFailure {
- try (InputStreamReader r =
- new InputStreamReader(in, StandardCharsets.UTF_8)) {
+ try (InputStreamReader r = new InputStreamReader(in, UTF_8)) {
return OutputFormat.JSON.newGson().
fromJson(CharStreams.toString(r), ReviewInput.class);
} catch (IOException | JsonSyntaxException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
index bdc4cef..194e65f9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
@@ -22,6 +22,8 @@
*/
package com.google.gerrit.sshd.commands;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.server.tools.ToolsCatalog;
import com.google.gerrit.server.tools.ToolsCatalog.Entry;
import com.google.gerrit.sshd.BaseCommand;
@@ -188,7 +190,7 @@
}
}
- out.write("E\n".getBytes("UTF-8"));
+ out.write("E\n".getBytes(UTF_8));
out.flush();
readAck();
}
@@ -210,7 +212,7 @@
buf.append(" ");
buf.append(dir.getName());
buf.append("\n");
- out.write(buf.toString().getBytes("UTF-8"));
+ out.write(buf.toString().getBytes(UTF_8));
out.flush();
}
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 974b5c6..a6b2810 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,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.EmailException;
@@ -226,7 +228,7 @@
in.raw = new RawInput() {
@Override
public InputStream getInputStream() throws IOException {
- return new ByteArrayInputStream(sshKey.getBytes("UTF-8"));
+ return new ByteArrayInputStream(sshKey.getBytes(UTF_8));
}
@Override
@@ -312,7 +314,7 @@
if (idx >= 0) {
StringBuilder sshKey = new StringBuilder();
BufferedReader br =
- new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ new BufferedReader(new InputStreamReader(in, UTF_8));
String line;
while ((line = br.readLine()) != null) {
sshKey.append(line)
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 108df96..8ac9887 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -179,9 +179,9 @@
return "";
}
- final CurrentUser user = sd.getCurrentUser();
+ final CurrentUser user = sd.getUser();
if (user != null && user.isIdentifiedUser()) {
- IdentifiedUser u = (IdentifiedUser) user;
+ IdentifiedUser u = user.asIdentifiedUser();
if (!numeric) {
String name = u.getAccount().getUserName();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
index 2dcff16..3f7914e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.EventListener;
import com.google.gerrit.common.EventSource;
@@ -125,7 +126,7 @@
if (!msg.endsWith("\n")) {
msg += "\n";
}
- err.write(msg.getBytes("UTF-8"));
+ err.write(msg.getBytes(UTF_8));
err.flush();
onExit(1);
return;
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 905d776..5e36318 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -45,6 +45,7 @@
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.mime.MimeUtil2Module;
@@ -64,6 +65,7 @@
import com.google.gerrit.sshd.SshKeyCacheImpl;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.DefaultCommandModule;
+import com.google.gerrit.sshd.commands.IndexCommandsModule;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
@@ -116,6 +118,7 @@
private GuiceFilter filter;
private ServletContext servletContext;
+ private IndexType indexType;
@Override
public void doFilter(ServletRequest req, ServletResponse res,
@@ -165,6 +168,7 @@
}
cfgInjector = createCfgInjector();
+ initIndexType();
config = cfgInjector.getInstance(
Key.get(Config.class, GerritServerConfig.class));
sysInjector = createSysInjector();
@@ -298,15 +302,13 @@
modules.add(new PluginRestApiModule());
modules.add(new RestCacheAdminModule());
modules.add(new GpgModule(config));
- AbstractModule changeIndexModule;
- switch (IndexModule.getIndexType(cfgInjector)) {
+ switch (indexType) {
case LUCENE:
- changeIndexModule = new LuceneIndexModule();
+ modules.add(new LuceneIndexModule());
break;
default:
- throw new IllegalStateException("unsupported index.type");
+ throw new IllegalStateException("unsupported index.type = " + indexType);
}
- modules.add(changeIndexModule);
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
@@ -325,12 +327,19 @@
return cfgInjector.createChildInjector(modules);
}
+ private void initIndexType() {
+ indexType = IndexModule.getIndexType(cfgInjector);
+ }
+
private Injector createSshInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(sysInjector.getInstance(SshModule.class));
modules.add(new SshHostKeyModule());
modules.add(new DefaultCommandModule(false,
sysInjector.getInstance(DownloadConfig.class)));
+ if (indexType == IndexType.LUCENE) {
+ modules.add(new IndexCommandsModule());
+ }
return sysInjector.createChildInjector(modules);
}
diff --git a/gerrit-war/src/main/resources/log4j.properties b/gerrit-war/src/main/resources/log4j.properties
index ef64f3b..8bc9bb2 100644
--- a/gerrit-war/src/main/resources/log4j.properties
+++ b/gerrit-war/src/main/resources/log4j.properties
@@ -16,7 +16,7 @@
log4j.appender.stderr=org.apache.log4j.ConsoleAppender
log4j.appender.stderr.target=System.err
log4j.appender.stderr.layout=org.apache.log4j.PatternLayout
-log4j.appender.stderr.layout.ConversionPattern=[%d] %-5p %c %x: %m%n
+log4j.appender.stderr.layout.ConversionPattern=[%d] [%t] %-5p %c %x: %m%n
# Silence non-critical messages from MINA SSHD.
#
diff --git a/lib/BUCK b/lib/BUCK
index 1dbac0a..73983da 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -26,9 +26,9 @@
maven_jar(
name = 'gwtorm_client',
- id = 'com.google.gerrit:gwtorm:1.14-16-gc4e356a',
- bin_sha1 = '01225468065812bbe5f27972df6dafa9d796d833',
- src_sha1 = '3622460ed58684cb33f786e3748637c8eea324f9',
+ id = 'com.google.gerrit:gwtorm:1.14-20-gec13fdc',
+ bin_sha1 = '60c2f2a5584959343ad1b21c3c79ba0fe825ceac',
+ src_sha1 = '4c562a3aafd1c3828217ee178568ed3d34ec86eb',
license = 'Apache2.0',
repository = GERRIT,
)
@@ -58,8 +58,8 @@
maven_jar(
name = 'guava',
- id = 'com.google.guava:guava:19.0-rc1',
- sha1 = '0364538ac107b8943a1f4d68ac50f1b0421bb983',
+ id = 'com.google.guava:guava:19.0-rc2',
+ sha1 = '93e17f60bc524c2610b41c494bb829c11ca89436',
license = 'Apache2.0',
)
@@ -152,9 +152,17 @@
)
maven_jar(
+ name = 'derby',
+ id = 'org.apache.derby:derby:10.11.1.1',
+ sha1 = 'df4b50061e8e4c348ce243b921f53ee63ba9bbe1',
+ license = 'Apache2.0',
+ attach_source = False,
+)
+
+maven_jar(
name = 'h2',
- id = 'com.h2database:h2:1.3.174',
- sha1 = '2fb55391f525bc3ef9f320a379d19350af96a554',
+ id = 'com.h2database:h2:1.3.176',
+ sha1 = 'fd369423346b2f1525c413e33f8cf95b09c92cbd',
license = 'h2',
)
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 081cdd8..aa29d35 100644
--- a/lib/asciidoctor/java/DocIndexer.java
+++ b/lib/asciidoctor/java/DocIndexer.java
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.gerrit.server.documentation.Constants;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
@@ -108,7 +110,7 @@
String title;
try (BufferedReader titleReader = new BufferedReader(
- new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
+ new InputStreamReader(new FileInputStream(file), UTF_8))) {
title = titleReader.readLine();
if (title != null && title.startsWith("[[")) {
// Generally the first line of the txt is the title. In a few cases the
diff --git a/lib/bouncycastle/BUCK b/lib/bouncycastle/BUCK
index ff6e6c5..0ce5817 100644
--- a/lib/bouncycastle/BUCK
+++ b/lib/bouncycastle/BUCK
@@ -9,7 +9,6 @@
id = 'org.bouncycastle:bcprov-jdk15on:' + VERSION,
sha1 = '88a941faf9819d371e3174b5ed56a3f3f7d73269',
license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
- exclude = ['META-INF/BCKEY.*'],
)
maven_jar(
@@ -17,7 +16,6 @@
id = 'org.bouncycastle:bcpg-jdk15on:' + VERSION,
sha1 = 'ff4665a4b5633ff6894209d5dd10b7e612291858',
license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
- exclude = ['META-INF/BCKEY.*'],
deps = [':bcprov'],
)
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index a7244f2..9756f5d 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -3,8 +3,8 @@
include_defs('//lib/codemirror/closure.defs')
REPO = MAVEN_CENTRAL
-VERSION = '5.5'
-SHA1 = 'd9cee6fe3de8e02372b1ac1e9a627224a4f649a7'
+VERSION = '5.7'
+SHA1 = '839a48a6c8d6b36193832a822911198ccba96bfe'
if REPO == MAVEN_CENTRAL:
URL = REPO + 'org/webjars/codemirror/%s/codemirror-%s.jar' % (VERSION, VERSION)
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
index 8259252..abb6d92 100644
--- a/lib/codemirror/cm.defs
+++ b/lib/codemirror/cm.defs
@@ -9,10 +9,13 @@
'lib/codemirror.js',
'mode/meta.js',
'keymap/vim.js',
+ 'keymap/emacs.js',
]
CM_ADDONS = [
'dialog/dialog.js',
+ 'edit/closebrackets.js',
+ 'edit/matchbrackets.js',
'edit/trailingspace.js',
'scroll/annotatescrollbar.js',
'scroll/simplescrollbars.js',
@@ -78,6 +81,7 @@
'tcl',
'velocity',
'verilog',
+ 'vhdl',
'xml',
'yaml',
]
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 3e2f411..6876dfe 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -15,15 +15,6 @@
id = 'com.google.gwt:gwt-dev:' + VERSION,
sha1 = 'c2c3dd5baf648a0bb199047a818be5e560f48982',
license = 'Apache2.0',
- exported_deps = [
- ':javax-validation',
- ':javax-validation_src',
- '//lib/ow2:ow2-asm',
- '//lib/ow2:ow2-asm-analysis',
- '//lib/ow2:ow2-asm-commons',
- '//lib/ow2:ow2-asm-tree',
- '//lib/ow2:ow2-asm-util',
- ],
attach_source = False,
exclude = ['org/eclipse/jetty/*'],
)
@@ -34,7 +25,7 @@
bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e',
src_sha1 = '7a561191db2203550fbfa40d534d4997624cd369',
license = 'Apache2.0',
- visibility = [],
+ visibility = ['PUBLIC'],
)
maven_jar(
@@ -54,5 +45,5 @@
id = 'org.javassist:javassist:3.18.1-GA',
sha1 = 'd9a09f7732226af26bf99f19e2cffe0ae219db5b',
license = 'Apache2.0',
- visibility = [],
+ visibility = ['PUBLIC'],
)
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index 5e4a82f..c5107d5 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -1,6 +1,6 @@
include_defs('//lib/maven.defs')
-VERSION = '5.2.1'
+VERSION = '5.3.0'
# core and backward-codecs both provide
# META-INF/services/org.apache.lucene.codecs.Codec, so they must be merged.
@@ -16,7 +16,7 @@
maven_jar(
name = 'core_jar',
id = 'org.apache.lucene:lucene-core:' + VERSION,
- sha1 = 'a175590aa8b04e079eb1a136fd159f9163482ba4',
+ sha1 = '9e12bb7c39e964a544e3a23b9c8ffa9599d38f10',
license = 'Apache2.0',
exclude = [
'META-INF/LICENSE.txt',
@@ -28,7 +28,7 @@
maven_jar(
name = 'analyzers-common',
id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION,
- sha1 = '33b7cc17d5a7c939af6fe3f67563f4709926d7f5',
+ sha1 = '1502beac94cf437baff848ffbbb8f76172befa6b',
license = 'Apache2.0',
deps = [':core-and-backward-codecs'],
exclude = [
@@ -40,7 +40,7 @@
maven_jar(
name = 'backward-codecs_jar',
id = 'org.apache.lucene:lucene-backward-codecs:' + VERSION,
- sha1 = '603d1f06b133449272799d698e5118db65e523ba',
+ sha1 = 'f654901e55fe56bdbe4be202767296929c2f8d9e',
license = 'Apache2.0',
deps = [':core_jar'],
exclude = [
@@ -53,7 +53,7 @@
maven_jar(
name = 'misc',
id = 'org.apache.lucene:lucene-misc:' + VERSION,
- sha1 = 'be0a4f0ac06f0a2fa3689b4bf6cd1fe6847f9969',
+ sha1 = 'd03ce6d1bb8ab3926b3acc717418c474a49ade69',
license = 'Apache2.0',
deps = [':core-and-backward-codecs'],
exclude = [
@@ -65,7 +65,7 @@
maven_jar(
name = 'queryparser',
id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
- sha1 = '73be0a2d4ab3e6b574be1938bfb27f7f730f0ad9',
+ sha1 = '2c5e08580316c90b56a52e3cb686e1cf69db3f9e',
license = 'Apache2.0',
deps = [':core-and-backward-codecs'],
exclude = [
diff --git a/lib/maven.defs b/lib/maven.defs
index dd8097e..a16fda1 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -123,7 +123,7 @@
else:
srcjar = None
genrule(
- name = '%s__download_src' % name,
+ name = '%s_src' % name,
cmd = ':>$OUT',
out = '__%s__no_src' % name,
)
diff --git a/lib/prolog/prolog.defs b/lib/prolog/prolog.defs
index 677a9e2..e74c21d 100644
--- a/lib/prolog/prolog.defs
+++ b/lib/prolog/prolog.defs
@@ -33,7 +33,6 @@
genrule(
name = name + '__ln',
cmd = 'ln -s $(location :%s__lib) $OUT' % name,
- deps = [':%s__lib' % name],
out = name + '.jar',
)
prebuilt_jar(
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index ec6ed89..226f4d4 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit ec6ed89c47ba7223f82d9cb512926a6c5081343e
+Subproject commit 226f4d41673257bc5b6f95deae49a49aaabde750
diff --git a/plugins/download-commands b/plugins/download-commands
index 6d4e0a4..86eb557 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 6d4e0a45ad4d7faebc692e5f10e418cbfcf858cb
+Subproject commit 86eb55733599fee4a996be70bacc842586cd9fae
diff --git a/plugins/replication b/plugins/replication
index cc91e0c..32e84b1 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit cc91e0c2987a4606e19b10e320b14f6a0c911c06
+Subproject commit 32e84b1b6131230caf880b0c181f98551edc44c6
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 6fb0101..f6df712 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 6fb010107a7dfdd6baff2b54e65fb74c933d6654
+Subproject commit f6df7121d2704e73c2a315a660e5cc4e12ab1ab9
diff --git a/tools/BUCK b/tools/BUCK
index 0bdff3c..e311ff8 100644
--- a/tools/BUCK
+++ b/tools/BUCK
@@ -42,7 +42,7 @@
return environ.get('PATH')
genrule(
- name = 'buck.properties',
+ name = 'buck',
cmd = 'echo buck=`which buck`>$OUT;' +
("echo PATH=\''%s'\' >>$OUT;" % shquote(os_path())),
deps = [],
diff --git a/tools/build.defs b/tools/build.defs
index da07c1e..893abba 100644
--- a/tools/build.defs
+++ b/tools/build.defs
@@ -42,15 +42,13 @@
):
cmd = ['$(exe //tools:pack_war)', '-o', '$OUT', '--tmp', '$TMP']
for l in libs:
- cmd.extend(['--lib', l])
+ cmd.extend(['--lib', '$(classpath %s)' % l])
for l in pgmlibs:
- cmd.extend(['--pgmlib', l])
+ cmd.extend(['--pgmlib', '$(classpath %s)' % l])
- dep = []
if docs:
cmd.append('$(location %s)' % DOCS_HTML)
- dep.append(DOCS_LIB)
- cmd.extend(['--lib', DOCS_LIB])
+ cmd.extend(['--lib', '$(classpath %s)' % DOCS_LIB])
if context:
for t in context:
cmd.append('$(location %s)' % t)
@@ -58,7 +56,6 @@
genrule(
name = name,
cmd = ' '.join(cmd),
- deps = libs + pgmlibs + dep,
out = name + '.war',
visibility = visibility,
)
diff --git a/tools/default.defs b/tools/default.defs
index 543bf98..7a6e982 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -132,7 +132,8 @@
manifest_file = None,
manifest_entries = [],
type = 'plugin',
- visibility = ['PUBLIC']):
+ visibility = ['PUBLIC'],
+ target_suffix = ''):
tb = traceback.extract_stack()
calling_BUCK_file = tb[-2][0]
calling_BUCK_dir = os.path.abspath(os.path.dirname(calling_BUCK_file))
@@ -193,7 +194,7 @@
gwt_binary(
name = name + '__gwt_application',
modules = [gwt_module],
- deps = GWT_PLUGIN_DEPS + ['//lib/gwt:dev'],
+ deps = GWT_PLUGIN_DEPS + GWT_TRANSITIVE_DEPS + ['//lib/gwt:dev'],
module_deps = [':%s__gwt_module' % name],
local_workers = cpu_count(),
strict = True,
@@ -202,7 +203,7 @@
)
java_binary(
- name = name,
+ name = name + target_suffix,
manifest_file = ':%s__manifest' % name,
merge_manifests = False,
deps = [
diff --git a/tools/download_all.py b/tools/download_all.py
index 3b21882..58316ca 100755
--- a/tools/download_all.py
+++ b/tools/download_all.py
@@ -32,7 +32,7 @@
if m:
n = m.group(1)
if args.src and n.endswith('__download_bin'):
- n = n[:-4] + '_src'
+ n = n[:-13] + 'src'
targets.add(n)
r = p.wait()
if r != 0:
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index 57f3afb..1e13515 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -21,6 +21,8 @@
'//lib/bouncycastle:bcprov',
'//lib/bouncycastle:bcpg',
'//lib/bouncycastle:bcpkix',
+ '//lib/gwt:javax-validation',
+ '//lib/gwt:javax-validation_src',
'//lib/jetty:servlets',
'//lib/prolog:compiler_lib',
'//Documentation:index_lib',
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index f3300fa..67ab138 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -169,7 +169,7 @@
for j in sorted(libs):
s = None
if j.endswith('.jar'):
- s = j[:-4] + '-src.jar'
+ s = j[:-4] + '_src.jar'
if not path.exists(s):
s = None
if args.plugins:
@@ -224,8 +224,8 @@
gen_factorypath()
try:
- targets = ['//tools:buck.properties'] + MAIN + GWT
- check_call(['buck', 'build'] + targets)
+ targets = ['//tools:buck'] + MAIN + GWT
+ check_call(['buck', 'build', '--deep'] + targets)
except CalledProcessError as err:
exit(1)
except KeyboardInterrupt:
diff --git a/tools/gwt-constants.defs b/tools/gwt-constants.defs
index 2584f2d..8bafddb 100644
--- a/tools/gwt-constants.defs
+++ b/tools/gwt-constants.defs
@@ -12,3 +12,13 @@
'//gerrit-plugin-gwtui:gwtui-api-lib',
'//lib/gwt:user',
]
+
+GWT_TRANSITIVE_DEPS = [
+ '//lib/gwt:javax-validation',
+ '//lib/gwt:javax-validation_src',
+ '//lib/ow2:ow2-asm',
+ '//lib/ow2:ow2-asm-analysis',
+ '//lib/ow2:ow2-asm-commons',
+ '//lib/ow2:ow2-asm-tree',
+ '//lib/ow2:ow2-asm-util',
+]
diff --git a/tools/maven/BUCK b/tools/maven/BUCK
index fdc01a8..98a7ade 100644
--- a/tools/maven/BUCK
+++ b/tools/maven/BUCK
@@ -10,16 +10,19 @@
url = URL,
version = GERRIT_VERSION,
jar = {
+ 'gerrit-acceptance-framework': '//gerrit-acceptance-framework:acceptance-framework',
'gerrit-extension-api': '//gerrit-extension-api:extension-api',
'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api',
'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api',
},
src = {
+ 'gerrit-acceptance-framework': '//gerrit-acceptance-framework:acceptance-framework-src',
'gerrit-extension-api': '//gerrit-extension-api:extension-api-src',
'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api-src',
'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api-src',
},
doc = {
+ 'gerrit-acceptance-framework': '//gerrit-acceptance-framework:acceptance-framework-javadoc',
'gerrit-extension-api': '//gerrit-extension-api:extension-api-javadoc',
'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api-javadoc',
'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api-javadoc',
diff --git a/tools/pack_war.py b/tools/pack_war.py
index cfa7e36..8525a56 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -16,7 +16,7 @@
from __future__ import print_function
from optparse import OptionParser
from os import chdir, makedirs, path, symlink
-from subprocess import check_call, check_output
+from subprocess import check_call
import sys
opts = OptionParser()
@@ -30,17 +30,14 @@
root = war[:war.index('buck-out')]
jars = set()
+def prune(l):
+ return [j[j.find('buck-out'):] for e in l for j in e.split(':')]
def link_jars(libs, directory):
makedirs(directory)
while not path.isfile('.buckconfig'):
chdir('..')
- try:
- cp = check_output(['buck', 'audit', 'classpath'] + libs)
- except Exception as e:
- print('call to buck audit failed: %s' % e, file=sys.stderr)
- exit(1)
- for j in cp.strip().splitlines():
+ for j in libs:
if j not in jars:
jars.add(j)
n = path.basename(j)
@@ -49,9 +46,9 @@
symlink(path.join(root, j), path.join(directory, n))
if args.lib:
- link_jars(args.lib, path.join(war, 'WEB-INF', 'lib'))
+ link_jars(prune(args.lib), path.join(war, 'WEB-INF', 'lib'))
if args.pgmlib:
- link_jars(args.pgmlib, path.join(war, 'WEB-INF', 'pgm-lib'))
+ link_jars(prune(args.pgmlib), path.join(war, 'WEB-INF', 'pgm-lib'))
try:
for s in ctx:
check_call(['unzip', '-q', '-d', war, s])
diff --git a/tools/version.py b/tools/version.py
index e2d9ead..9f03a59 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -45,10 +45,10 @@
src_pattern = re.compile(r'^(\s*<version>)([-.\w]+)(</version>\s*)$',
re.MULTILINE)
-for project in ['gerrit-extension-api', 'gerrit-plugin-api',
- 'gerrit-plugin-archetype', 'gerrit-plugin-gwt-archetype',
- 'gerrit-plugin-gwtui', 'gerrit-plugin-js-archetype',
- 'gerrit-war']:
+for project in ['gerrit-acceptance-framework', 'gerrit-extension-api',
+ 'gerrit-plugin-api', 'gerrit-plugin-archetype',
+ 'gerrit-plugin-gwt-archetype', 'gerrit-plugin-gwtui',
+ 'gerrit-plugin-js-archetype', 'gerrit-war']:
pom = os.path.join(project, 'pom.xml')
replace_in_file(pom, src_pattern)