Merge branch 'stable-2.12' * stable-2.12: Buck: Remove non working local_jar rule and documentation CurrentSchemaVersion: Allow to use it in plugins Use REST implementation to list members for label with group operator Fix wrong date/time for commits in refs/users/default branch InitAdminUser: Don't assume the group ID of the Administrators group Move sendemail.allowRegisterNewEmail to auth.allowRegisterNewEmail Update 2.12 release notes Update 2.12 release notes Fix query for changes using a label with a group operator. Add !important back to .cm-searching and .cm-trailingspace HttpPluginServlet: Fix "short read of block" IO error on plugin docs HttpPluginServlet: Use try-with-resource in readWholeEntry StaticModule: Remove unused import Brings Gerrit docs back to life Correct docs since --name is not a valid option for create-project Make InternalChangeQuery.query public so it can be used by plugins InitPlugins: Suggest to upgrade installed plugins per default Change-Id: I2d2d6c5e695e2c331d71853f03a96b0203b9d04b Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/.buckconfig b/.buckconfig index 51318f3..c766d30 100644 --- a/.buckconfig +++ b/.buckconfig
@@ -8,7 +8,9 @@ docs = //Documentation:searchfree firefox = //:firefox gerrit = //:gerrit + gwtgerrit = //:gwtgerrit headless = //:headless + polygerrit = //:polygerrit release = //:release safari = //:safari soyc = //gerrit-gwtui:ui_soyc @@ -22,8 +24,11 @@ src_roots = java, resources [project] - ignore = .git + ignore = .git, eclipse-out [cache] mode = dir dir = ~/.gerritcodereview/buck-cache/locally-built-artifacts + +[test] + excluded_labels = manual
diff --git a/.buckversion b/.buckversion index 9daac2c..3c60745 100644 --- a/.buckversion +++ b/.buckversion
@@ -1 +1 @@ -1b03b4313b91b634bd604fc3487a05f877e59dee +6659a474fb2ba6e921bb38c1b55d4c9ba6073cfa
diff --git a/.gitignore b/.gitignore index 32a1826..ed6b163 100644 --- a/.gitignore +++ b/.gitignore
@@ -15,6 +15,7 @@ /.buckd /buck-cache /buck-out +/eclipse-out /extras /local.properties *.pyc
diff --git a/BUCK b/BUCK index c986874..9657ff3 100644 --- a/BUCK +++ b/BUCK
@@ -1,10 +1,12 @@ include_defs('//tools/build.defs') gerrit_war(name = 'gerrit') -gerrit_war(name = 'headless', ui = None) -gerrit_war(name = 'chrome', ui = 'ui_chrome') -gerrit_war(name = 'firefox', ui = 'ui_firefox') -gerrit_war(name = 'safari', ui = 'ui_safari') +gerrit_war(name = 'gwtgerrit', ui = 'ui_dbg') +gerrit_war(name = 'headless', ui = None) +gerrit_war(name = 'chrome', ui = 'ui_chrome') +gerrit_war(name = 'firefox', ui = 'ui_firefox') +gerrit_war(name = 'safari', ui = 'ui_safari') +gerrit_war(name = 'polygerrit', ui = 'polygerrit') gerrit_war(name = 'withdocs', docs = True) gerrit_war(name = 'release', ui = 'ui_optdbg_r', docs = True, context = ['//plugins:core'], visibility = ['//tools/maven:'])
diff --git a/Documentation/BUCK b/Documentation/BUCK index 126bf1f..62c0e07 100644 --- a/Documentation/BUCK +++ b/Documentation/BUCK
@@ -1,12 +1,16 @@ include_defs('//Documentation/asciidoc.defs') include_defs('//Documentation/config.defs') +include_defs('//Documentation/license.defs') include_defs('//tools/git.defs') DOC_DIR = 'Documentation' -JSUI = '//gerrit-gwtui:ui_module' -MAIN = '//gerrit-pgm:pgm' + +JSUI_JAVA_DEPS = ['//gerrit-gwtui:ui_module'] +JSUI_NON_JAVA_DEPS = ['//polygerrit-ui/app:polygerrit_ui'] +MAIN_JAVA_DEPS = ['//gerrit-pgm:pgm'] SRCS = glob(['*.txt'], excludes = ['licenses.txt']) + genasciidoc( name = 'html', out = 'html.zip', @@ -28,21 +32,20 @@ visibility = ['PUBLIC'], ) -genrule( +genlicenses( name = 'licenses.txt', - cmd = '$(exe :gen_licenses) --asciidoc ' - + '--classpath $(classpath %s) ' % MAIN - + '--classpath $(classpath %s) ' % JSUI - + MAIN + ' ' + JSUI + ' >$OUT', + opts = ['--asciidoc'], + java_deps = JSUI_JAVA_DEPS + MAIN_JAVA_DEPS, + non_java_deps = JSUI_NON_JAVA_DEPS, out = 'licenses.txt', ) # Required by Google for gerrit-review. -genrule( +genlicenses( name = 'js_licenses.txt', - cmd = '$(exe :gen_licenses) --partial ' - + '--classpath $(classpath %s) ' % JSUI - + JSUI + ' >$OUT', + opts = ['--partial'], + java_deps = JSUI_JAVA_DEPS, + non_java_deps = JSUI_NON_JAVA_DEPS, out = 'js_licenses.txt', )
diff --git a/Documentation/cmd-set-reviewers.txt b/Documentation/cmd-set-reviewers.txt index 79f7651..d5b4908 100644 --- a/Documentation/cmd-set-reviewers.txt +++ b/Documentation/cmd-set-reviewers.txt
@@ -10,17 +10,16 @@ [--add <REVIEWER> ... | -a <REVIEWER> ...] [--remove <REVIEWER> ... | -r <REVIEWER> ...] [--] - {COMMIT | CHANGE-ID}... + {CHANGE-ID}... -- == DESCRIPTION Adds or removes reviewers to the specified change, sending email notifications when changes are made. -Changes should be specified as complete or abbreviated Change-Ids -such as 'Iac6b2ac2'. They may also be specified by numeric change -identifiers, such as '8242' or by complete or abbreviated commit -SHA-1s. +Changes can be specified in the +link:rest-api-changes.html#change-id[same format] supported by the REST +API. == OPTIONS
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index 45f7082..fb5bc15 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt
@@ -1414,7 +1414,8 @@ httpd and sshd threads as some request processing code paths may need multiple connections. + -Default is 8. +Default is <<sshd.threads, sshd.threads>> + + <<httpd.maxThreads, httpd.maxThreads>> + 2. + This setting only applies if <<database.connectionPool,database.connectionPool>> is true. @@ -1432,7 +1433,7 @@ Maximum number of connections to keep idle in the pool. If there are more idle connections, connections will be closed instead of being returned back to the pool. -Default is 4. +Default is min(<<database.poolLimit, database.poolLimit>>, 16). + This setting only applies if <<database.connectionPool,database.connectionPool>> is true. @@ -3144,6 +3145,28 @@ + Default is 10x reductionLimit (1,000,000). +[[rules.maxSourceBytes]]rules.maxSourceBytes:: ++ +Maximum input size (in bytes) of a Prolog rules.pl file. Larger +source files may need a larger rules.compileReductionLimit. Consider +using link:pgm-rulec.html[rulec] to precompile larger rule files. ++ +A size of 0 bytes disables rules, same as rules.enable = false. ++ +Common unit suffixes of 'k', 'm', or 'g' are supported. ++ +Default is 128 KiB. + +[[rules.maxPrologDatabaseSize]]rules.maxPrologDatabaseSize:: ++ +Number of predicate clauses allowed to be defined in the Prolog +database by project rules. Very complex rules may need more than the +default 256 limit, but cost more memory and may need more time to +evaluate. Consider using link:pgm-rulec.html[rulec] to precompile +larger rule files. ++ +Default is 256. + [[execution]] === Section execution @@ -3409,7 +3432,7 @@ If additional requests are received while all threads are busy they are queued and serviced in a first-come-first-served order. + -By default, 1.5x the number of CPUs available to the JVM. +By default, 2x the number of CPUs available to the JVM. [[sshd.batchThreads]]sshd.batchThreads:: +
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt index a40c5f3..9cf4e50 100644 --- a/Documentation/config-labels.txt +++ b/Documentation/config-labels.txt
@@ -217,6 +217,19 @@ The label is purely informational and values are not considered when determining whether a change is submittable. +* `PatchSetLock` ++ +The `PatchSetLock` function provides a locking mechanism for patch +sets. This function's values are not considered when determining +whether a change is submittable. When set, no new patchsets can be +created and rebase and abandon are blocked. ++ +This function is designed to allow overlapping locks, so several lock +accounts could lock the same change. ++ +Allowed range of values are 0 (Patch Set Unlocked) to 1 (Patch Set +Locked). + [[label_copyMinScore]] === `label.Label-Name.copyMinScore`
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt index 6e248c8..37e3eba 100644 --- a/Documentation/config-plugins.txt +++ b/Documentation/config-plugins.txt
@@ -386,6 +386,27 @@ link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/config.md[ Configuration] +[[metrics-reporter-elasticsearch]] + +This plugin reports Gerrit metrics to Elasticsearch. + +link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/metrics-reporter-elasticsearch[ +Project]. + +[[metrics-reporter-graphite]] + +This plugin reports Gerrit metrics to Graphite. + +link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/metrics-reporter-graphite[ +Project]. + +[[metrics-reporter-jmx]] + +This plugin reports Gerrit metrics to JMX. + +link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/metrics-reporter-jmx[ +Project]. + [[motd]] === motd
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt index ec8515f..0cd5e84 100644 --- a/Documentation/dev-buck.txt +++ b/Documentation/dev-buck.txt
@@ -6,8 +6,8 @@ Note that you need to use Java 7 for building gerrit. There is currently no binary distribution of Buck, so it has to be manually -built and installed. Apache Ant is required. Currently only Linux and Mac -OS are supported. +built and installed. Apache Ant and gcc are required. Currently only Linux +and Mac OS are supported. Clone the git and build it: @@ -97,7 +97,7 @@ === Gerrit Development WAR File -To build the Gerrit web application: +To build the Gerrit web application that includes GWT UI and PolyGerrit UI: ---- buck build gerrit @@ -109,6 +109,18 @@ buck-out/gen/gerrit/gerrit.war ---- +To build the Gerrit web application that includes only GWT UI: + +---- + buck build gwtgerrit +---- + +The output executable WAR will be placed in: + +---- + buck-out/gen/gwtgerrit/gwtgerrit.war +---- + === Headless Mode
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt index fdc7fab..13b6978 100644 --- a/Documentation/dev-plugins.txt +++ b/Documentation/dev-plugins.txt
@@ -36,7 +36,7 @@ ---- mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \ -DarchetypeArtifactId=gerrit-plugin-archetype \ - -DarchetypeVersion=2.12 \ + -DarchetypeVersion=2.13-SNAPSHOT \ -DgroupId=com.googlesource.gerrit.plugins.testplugin \ -DartifactId=testplugin ---- @@ -587,6 +587,48 @@ $ ssh -p 29418 review.example.com sh ps ---- +[[search_operators]] +=== Search Operators === + +Plugins can define new search operators to extend change searching by +implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface +and registering it to an operator name in the plugin module's +`configure()` method. The search operator name is defined during +registration via the DynamicMap annotation mechanism. The plugin +name will get appended to the annotated name, with an underscore +in between, leading to the final operator name. An example +registration looks like this: + + bind(ChangeOperatorFactory.class) + .annotatedWith(Exports.named("sample")) + .to(SampleOperator.class); + +If this is registered in the `myplugin` plugin, then the resulting +operator will be named `sample_myplugin`. + +The search operator itself is implemented by ensuring that the +`create()` method of the class implementing the +`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a +`Predicate<ChangeData>`. Here is a sample operator factory +defintion which creates a `MyPredicate`: + +[source,java] +---- +@Singleton +public class SampleOperator + implements ChangeQueryBuilder.ChangeOperatorFactory { + public static class MyPredicate extends OperatorPredicate<ChangeData> { + ... + } + + @Override + public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value) + throws QueryParseException { + return new MyPredicate(value); + } +} +---- + [[simple-configuration]] == Simple Configuration in `gerrit.config`
diff --git a/Documentation/gen_licenses.py b/Documentation/gen_licenses.py index db3480b..47a132c 100755 --- a/Documentation/gen_licenses.py +++ b/Documentation/gen_licenses.py
@@ -19,8 +19,8 @@ import argparse from collections import defaultdict, deque +import json from os import chdir, path -import re from shutil import copyfileobj from subprocess import Popen, PIPE from sys import stdout, stderr @@ -28,7 +28,6 @@ parser = argparse.ArgumentParser() parser.add_argument('--asciidoc', action='store_true') parser.add_argument('--partial', action='store_true') -parser.add_argument('--classpath', action='append') parser.add_argument('targets', nargs='+') args = parser.parse_args() @@ -38,37 +37,35 @@ '//lib/bouncycastle:bcprov', ] +for target in args.targets: + if not target.startswith('//'): + print('Target must be absolute: %s' % target, file=stderr) + def parse_graph(): graph = defaultdict(list) while not path.isfile('.buckconfig'): chdir('..') - # TODO(davido): use passed in classpath from Buck instead - p = Popen( - ['buck', 'audit', 'classpath', '--dot'] + args.targets, - stdout = PIPE) - for line in p.stdout: - m = re.search(r'"(//.*?)" -> "(//.*?)";', line) - if not m: - continue - target, dep = m.group(1), m.group(2) - if args.partial: - if dep == '//lib/codemirror:js_minifier': - if target == '//lib/codemirror:js': - continue - if target.startswith('//lib/codemirror:mode_'): - continue - if target == '//gerrit-gwtui:ui_module' and \ - dep == '//gerrit-gwtexpui:CSS': + query = ' + '.join('deps(%s)' % t for t in args.targets) + p = Popen([ + 'buck', 'query', query, + '--output-attributes=buck.direct_dependencies'], stdout=PIPE) + obj = json.load(p.stdout) + for target, attrs in obj.iteritems(): + for dep in attrs['buck.direct_dependencies']: + + if target in KNOWN_PROVIDED_DEPS: continue - # Dependencies included in provided_deps set are contained in audit - # classpath and must be sorted out. That's safe thing to do because - # they are not included in the final artifact. - if "DO_NOT_DISTRIBUTE" in dep: - if not target in KNOWN_PROVIDED_DEPS: - print('DO_NOT_DISTRIBUTE license for target: %s' % target, file=stderr) - exit(1) - else: + if args.partial: + if dep == '//lib/codemirror:js_minifier': + if target == '//lib/codemirror:js': + continue + if target.startswith('//lib/codemirror:mode_'): + continue + if (target == '//gerrit-gwtui:ui_module' + and dep == '//gerrit-gwtexpui:CSS'): + continue + graph[target].append(dep) r = p.wait() if r != 0: @@ -78,14 +75,27 @@ graph = parse_graph() licenses = defaultdict(set) +do_not_distribute = False queue = deque(args.targets) while queue: target = queue.popleft() for dep in graph[target]: if not dep.startswith('//lib:LICENSE-'): continue + if 'DO_NOT_DISTRIBUTE' in dep: + do_not_distribute = True licenses[dep].add(target) queue.extend(graph[target]) + +if do_not_distribute: + print('DO_NOT_DISTRIBUTE license found', file=stderr) + for target in args.targets: + print('...via %s:' % target) + Popen(['buck', 'query', + 'allpaths(%s, //lib:LICENSE-DO_NOT_DISTRIBUTE)' % target], + stdout=stderr).communicate() + exit(1) + used = sorted(licenses.keys()) if args.asciidoc: @@ -156,6 +166,8 @@ p = d[len('//lib:'):] else: p = d[d.index(':')+1:].lower() + if '__' in p: + p = p[:p.index('__')] print('* ' + p) if args.asciidoc: print()
diff --git a/Documentation/license.defs b/Documentation/license.defs new file mode 100644 index 0000000..42dd3eb --- /dev/null +++ b/Documentation/license.defs
@@ -0,0 +1,29 @@ +def genlicenses( + name, + out, + opts = [], + java_deps = [], + non_java_deps = [], + visibility = []): + cmd = ['$(exe :gen_licenses)'] + cmd.extend(opts) + cmd.append('>$OUT') + cmd.extend(java_deps) + cmd.extend(non_java_deps) + + # Must use $(classpath) for Java deps, since transitive dependencies are not + # first-order dependencies of the output jar, so changes would not cause + # invalidation of the build cache key for the genrule. + cmd.extend('; true $(classpath %s)' % d for d in java_deps) + + # Must use $(location) for non-Java deps, since $(classpath) will fail with an + # error. This is ok, because transitive dependencies are included in the + # output artifacts for everything _except_ Java libraries. + cmd.extend('; true $(location %s)' % d for d in non_java_deps) + + genrule( + name = name, + out = out, + cmd = ' '.join(cmd), + visibility = visibility, + )
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt index ad19046..f572785 100644 --- a/Documentation/rest-api-accounts.txt +++ b/Documentation/rest-api-accounts.txt
@@ -1431,7 +1431,11 @@ "status": "NEW", "created": "2013-02-01 09:59:32.126000000", "updated": "2013-02-21 11:16:36.775000000", + "starred": true, "mergeable": true, + "submittable": false, + "insertions": 145, + "deletions": 12, "_number": 3965, "owner": { "name": "John Doe"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index 07f0f65..07d3cc3 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt
@@ -209,8 +209,8 @@ -- * `DETAILED_LABELS`: detailed label information, including numeric values of all existing approvals, recognized label values, values - permitted to be set by the current user, and reviewers that may be - removed by the current user. + permitted to be set by the current user, all reviewers by state, and + reviewers that may be removed by the current user. -- [[current-revision]] @@ -414,35 +414,42 @@ "files": { "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java": { "lines_deleted": 8, - "size_delta": -412 + "size_delta": -412, + "size": 7782 }, "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java": { "lines_inserted": 1, - "size_delta": 23 + "size_delta": 23, + "size": 6762 }, "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java": { "lines_inserted": 11, "lines_deleted": 19, - "size_delta": -298 + "size_delta": -298, + "size": 47023 }, "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java": { "lines_inserted": 23, "lines_deleted": 20, - "size_delta": 132 + "size_delta": 132, + "size": 17727 }, "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java": { "status": "D", "lines_deleted": 139, - "size_delta": -5512 + "size_delta": -5512, + "size": 13098 }, "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java": { "status": "A", "lines_inserted": 204, - "size_delta": 8345 + "size_delta": 8345, + "size": 8345 }, "gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java": { "lines_deleted": 9, - "size_delta": -343 + "size_delta": -343, + "size": 5385 } } } @@ -634,6 +641,22 @@ "username": "jroe" } ], + "reviewers": { + "REVIEWER": [ + { + "_account_id": 1000096, + "name": "John Doe", + "email": "john.doe@example.com", + "username": "jdoe" + }, + { + "_account_id": 1000097, + "name": "Jane Roe", + "email": "jane.roe@example.com", + "username": "jroe" + } + ] + }, "messages": [ { "id": "YH-egE", @@ -1197,6 +1220,13 @@ "_account_id": 1000000 } ], + "reviewers": { + "REVIEWER": [ + { + "_account_id": 1000000 + } + ] + }, "current_revision": "9adb9f4c7b40eeee0646e235de818d09164d7379", "revisions": { "9adb9f4c7b40eeee0646e235de818d09164d7379": { @@ -1308,6 +1338,13 @@ "_account_id": 1000000 } ], + "reviewers": { + "REVIEWER": [ + { + "_account_id": 1000000 + } + ] + }, "current_revision": "1bd7c12a38854a2c6de426feec28800623f492c4", "revisions": { "1bd7c12a38854a2c6de426feec28800623f492c4": { @@ -2227,6 +2264,55 @@ HTTP/1.1 204 No Content ---- +[[list-votes]] +=== List Votes +-- +'GET /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/' +-- + +Lists the votes for a specific reviewer of the change. + +.Request +---- + GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/ HTTP/1.0 +---- + +As result a map is returned that maps the label name to the label value. +The entries in the map are sorted by label name. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "Code-Review": -1, + "Verified": 1 + "Work-In-Progress": 1, + } +---- + +[[delete-vote]] +=== Delete Vote +-- +'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]' +-- + +Deletes a single vote from a change. Note, that even when the last vote of +a reviewer is removed the reviewer itself is still listed on the change. + +.Request +---- + DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers/John%20Doe/votes/Code-Review HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 204 No Content +---- + [[revision-endpoints]] == Revision Endpoints @@ -2440,6 +2526,20 @@ "email": "jane.roe@example.com" } ], + "reviewers": { + "REVIEWER": [ + { + "_account_id": 1000096, + "name": "John Doe", + "email": "john.doe@example.com" + }, + { + "_account_id": 1000097, + "name": "Jane Roe", + "email": "jane.roe@example.com" + } + ] + }, "current_revision": "674ac754f91e64a0efb8087e59a176484bd534d1", "revisions": { "674ac754f91e64a0efb8087e59a176484bd534d1": { @@ -3270,12 +3370,14 @@ "/COMMIT_MSG": { "status": "A", "lines_inserted": 7, - "size_delta": 551 + "size_delta": 551, + "size": 551 }, "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java": { "lines_inserted": 5, "lines_deleted": 3, - "size_delta": 98 + "size_delta": 98, + "size": 23348 } } ---- @@ -3694,6 +3796,10 @@ === \{draft-id\} UUID of a draft comment. +[[label-id]] +=== \{label-id\} +The name of the label. + [[file-id]] \{file-id\} ~~~~~~~~~~~~ @@ -3875,6 +3981,15 @@ The reviewers that can be removed by the calling user as a list of link:rest-api-accounts.html#account-info[AccountInfo] entities. + Only set if link:#detailed-labels[detailed labels] are requested. +|`reviewers` || +The reviewers as a map that maps a reviewer state to a list of +link:rest-api-accounts.html#account-info[AccountInfo] entities. +Possible reviewer states are `REVIEWER`, `CC` and `REMOVED`. + +`REVIEWER`: Users with at least one non-zero vote on the change. + +`CC`: Users that were added to the change, but have not voted. + +`REMOVED`: Users that were previously reviewers on the change, but have +been removed. + +Only set if link:#detailed-labels[detailed labels] are requested. |`messages`|optional| Messages associated with the change as a list of link:#change-message-info[ChangeMessageInfo] entities. + @@ -4218,6 +4333,8 @@ Not set for binary files or if no lines were deleted. |`size_delta` || Number of bytes by which the file size increased/decreased. +|`size` || +File size in bytes. |============================= [[fix-input]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index b1b795c..3a64dc0 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt
@@ -115,6 +115,7 @@ "gerrit": { "all_projects": "All-Projects", "all_users": "All-Users" + "doc_search": true }, "sshd": {}, "suggest": { @@ -1189,6 +1190,8 @@ |`all_users_name` || Name of the link:config-gerrit.html#gerrit.allUsers[project in which meta data of all users is stored]. +|`doc_search` || +Whether documentation search is available. |`doc_url` |optional| Custom base URL where Gerrit server documentation is located. (Documentation may still be available at /Documentation relative to the
diff --git a/VERSION b/VERSION index e84df7e..573f909 100644 --- a/VERSION +++ b/VERSION
@@ -2,4 +2,4 @@ # Used by :api_install and :api_deploy targets # when talking to the destination repository. # -GERRIT_VERSION = '2.12' +GERRIT_VERSION = '2.13-SNAPSHOT'
diff --git a/gerrit-acceptance-framework/BUCK b/gerrit-acceptance-framework/BUCK index d8f0276..9ee1572 100644 --- a/gerrit-acceptance-framework/BUCK +++ b/gerrit-acceptance-framework/BUCK
@@ -2,13 +2,18 @@ DEPS = [ '//gerrit-gpg:gpg', + '//gerrit-launcher:launcher', + '//gerrit-openid:openid', '//gerrit-pgm:daemon', + '//gerrit-pgm:http-jetty', '//gerrit-pgm:util-nodep', + '//gerrit-server/src/main/prolog:common', '//gerrit-server:testutil', '//lib/auto:auto-value', '//lib/httpcomponents:fluent-hc', '//lib/httpcomponents:httpclient', '//lib/httpcomponents:httpcore', + '//lib/jetty:servlet', '//lib/jgit:junit', '//lib/log:impl_log4j', '//lib/log:log4j',
diff --git a/gerrit-acceptance-framework/pom.xml b/gerrit-acceptance-framework/pom.xml index 410c148..2d93315 100644 --- a/gerrit-acceptance-framework/pom.xml +++ b/gerrit-acceptance-framework/pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-acceptance-framework</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Acceptance Test Framework</name> <description>API for Gerrit Plugins</description>
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java index 7dbbfb5..93d8ca2 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -17,7 +17,7 @@ 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 static com.google.gerrit.testutil.GerritServerTests.isNoteDbTestEnabled; import com.google.common.base.Function; import com.google.common.base.Optional; @@ -233,13 +233,6 @@ 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); @@ -548,6 +541,14 @@ saveProjectConfig(project, cfg); } + protected PermissionRule block(String permission, AccountGroup.UUID id, String ref) + throws Exception { + ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); + PermissionRule rule = Util.block(cfg, permission, id, ref); + saveProjectConfig(project, cfg); + return rule; + } + protected void saveProjectConfig(Project.NameKey p, ProjectConfig cfg) throws Exception { MetaDataUpdate md = metaDataUpdateFactory.create(p); @@ -580,16 +581,14 @@ projectCache.evict(config.getProject()); } - protected void blockRead(Project.NameKey project, String ref) throws Exception { - ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); - block(cfg, Permission.READ, REGISTERED_USERS, ref); - saveProjectConfig(project, cfg); + protected void blockRead(String ref) throws Exception { + block(Permission.READ, REGISTERED_USERS, ref); } protected void blockForgeCommitter(Project.NameKey project, String ref) throws Exception { ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); - block(cfg, Permission.FORGE_COMMITTER, REGISTERED_USERS, ref); + Util.block(cfg, Permission.FORGE_COMMITTER, REGISTERED_USERS, ref); saveProjectConfig(project, cfg); }
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java index 58bb9f2..e0f9d4a 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
@@ -23,5 +23,5 @@ @Target({METHOD}) @Retention(RUNTIME) public @interface GerritConfigs { - public GerritConfig[] value(); + GerritConfig[] value(); }
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java index 634db7c..672a5a7 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -18,6 +18,8 @@ import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.gerrit.metrics.DisabledMetricMaker; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePath; @@ -67,6 +69,7 @@ bind(GitRepositoryManager.class) .toInstance(new InMemoryRepositoryManager()); + bind(MetricMaker.class).to(DisabledMetricMaker.class); bind(DataSourceType.class).to(InMemoryH2Type.class); bind(InMemoryDatabase.class).in(SINGLETON); bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {})
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java index 261b894..d76cb81 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/RestResponse.java
@@ -14,9 +14,12 @@ package com.google.gerrit.acceptance; +import static com.google.common.truth.Truth.assert_; import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC; import static java.nio.charset.StandardCharsets.UTF_8; +import org.apache.http.HttpStatus; + import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; @@ -36,4 +39,51 @@ } return reader; } + + public void assertStatus(int status) throws Exception { + assert_() + .withFailureMessage(String.format("Expected status code %d", status)) + .that(getStatusCode()) + .isEqualTo(status); + } + + public void assertOK() throws Exception { + assertStatus(HttpStatus.SC_OK); + } + + public void assertNotFound() throws Exception { + assertStatus(HttpStatus.SC_NOT_FOUND); + } + + public void assertConflict() throws Exception { + assertStatus(HttpStatus.SC_CONFLICT); + } + + public void assertForbidden() throws Exception { + assertStatus(HttpStatus.SC_FORBIDDEN); + } + + public void assertNoContent() throws Exception { + assertStatus(HttpStatus.SC_NO_CONTENT); + } + + public void assertBadRequest() throws Exception { + assertStatus(HttpStatus.SC_BAD_REQUEST); + } + + public void assertUnprocessableEntity() throws Exception { + assertStatus(HttpStatus.SC_UNPROCESSABLE_ENTITY); + } + + public void assertMethodNotAllowed() throws Exception { + assertStatus(HttpStatus.SC_METHOD_NOT_ALLOWED); + } + + public void assertCreated() throws Exception { + assertStatus(HttpStatus.SC_CREATED); + } + + public void assertPreconditionFailed() throws Exception { + assertStatus(HttpStatus.SC_PRECONDITION_FAILED); + } }
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 92d7980..9788a5f 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
@@ -17,15 +17,18 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME; import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT; +import static com.google.gerrit.extensions.client.ReviewerState.CC; +import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER; import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static com.google.gerrit.server.project.Util.blockLabel; import static com.google.gerrit.server.project.Util.category; import static com.google.gerrit.server.project.Util.value; +import static com.google.gerrit.testutil.GerritServerTests.isNoteDbTestEnabled; +import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.acceptance.PushOneCommit; @@ -38,12 +41,14 @@ import com.google.gerrit.extensions.api.changes.RevisionApi; import com.google.gerrit.extensions.client.ChangeStatus; import com.google.gerrit.extensions.client.ListChangesOption; +import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ApprovalInfo; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.GitPerson; import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.common.RevisionInfo; +import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; @@ -58,9 +63,11 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.EnumSet; +import java.util.Iterator; import java.util.List; -import java.util.Set; +import java.util.Map; @NoHttpd public class ChangeIT extends AbstractDaemonTest { @@ -297,17 +304,6 @@ .rebase(ri); } - private Set<Account.Id> getReviewers(String changeId) throws Exception { - ChangeInfo ci = gApi.changes().id(changeId).get(); - Set<Account.Id> result = Sets.newHashSet(); - for (LabelInfo li : ci.labels.values()) { - for (ApprovalInfo ai : li.all) { - result.add(new Account.Id(ai._accountId)); - } - } - return result; - } - @Test public void addReviewer() throws Exception { PushOneCommit.Result r = createChange(); @@ -317,8 +313,20 @@ .id(r.getChangeId()) .addReviewer(in); - assertThat(getReviewers(r.getChangeId())) - .containsExactlyElementsIn(ImmutableSet.of(user.id)); + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + + // When notedb is enabled adding a reviewer records that user as reviewer + // in notedb. When notedb is disabled adding a reviewer results in a dummy 0 + // approval on the change which is treated as CC when the ChangeInfo is + // created. + Collection<AccountInfo> reviewers = isNoteDbTestEnabled() + ? c.reviewers.get(REVIEWER) + : c.reviewers.get(CC); + assertThat(reviewers).hasSize(1); + assertThat(reviewers.iterator().next()._accountId) + .isEqualTo(user.getId().get()); } @Test @@ -333,16 +341,155 @@ .revision(r.getCommit().name()) .submit(); - assertThat(getReviewers(r.getChangeId())) - .containsExactlyElementsIn(ImmutableSet.of(admin.getId())); + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER); + assertThat(reviewers).hasSize(1); + assertThat(reviewers.iterator().next()._accountId) + .isEqualTo(admin.getId().get()); + assertThat(c.reviewers).doesNotContainKey(CC); AddReviewerInput in = new AddReviewerInput(); in.reviewer = user.email; gApi.changes() .id(r.getChangeId()) .addReviewer(in); - assertThat(getReviewers(r.getChangeId())) - .containsExactlyElementsIn(ImmutableSet.of(admin.getId(), user.id)); + + c = gApi.changes() + .id(r.getChangeId()) + .get(); + reviewers = c.reviewers.get(REVIEWER); + if (isNoteDbTestEnabled()) { + // When notedb is enabled adding a reviewer records that user as reviewer + // in notedb. + assertThat(reviewers).hasSize(2); + Iterator<AccountInfo> reviewerIt = reviewers.iterator(); + assertThat(reviewerIt.next()._accountId) + .isEqualTo(admin.getId().get()); + assertThat(reviewerIt.next()._accountId) + .isEqualTo(user.getId().get()); + assertThat(c.reviewers).doesNotContainKey(CC); + } else { + // When notedb is disabled adding a reviewer results in a dummy 0 approval + // on the change which is treated as CC when the ChangeInfo is created. + assertThat(reviewers).hasSize(1); + assertThat(reviewers.iterator().next()._accountId) + .isEqualTo(admin.getId().get()); + Collection<AccountInfo> ccs = c.reviewers.get(CC); + assertThat(ccs).hasSize(1); + assertThat(ccs.iterator().next()._accountId) + .isEqualTo(user.getId().get()); + } + } + + @Test + public void listVotes() throws Exception { + PushOneCommit.Result r = createChange(); + gApi.changes() + .id(r.getChangeId()) + .revision(r.getCommit().name()) + .review(ReviewInput.approve()); + + Map<String, Short> m = gApi.changes() + .id(r.getChangeId()) + .reviewer(admin.getId().toString()) + .votes(); + + assertThat(m).hasSize(1); + assertThat(m).containsEntry("Code-Review", new Short((short)2)); + + setApiUser(user); + gApi.changes() + .id(r.getChangeId()) + .revision(r.getCommit().name()) + .review(ReviewInput.dislike()); + + m = gApi.changes() + .id(r.getChangeId()) + .reviewer(user.getId().toString()) + .votes(); + + assertThat(m).hasSize(1); + assertThat(m).containsEntry("Code-Review", new Short((short)-1)); + } + + @Test + public void deleteVote() throws Exception { + PushOneCommit.Result r = createChange(); + gApi.changes() + .id(r.getChangeId()) + .revision(r.getCommit().name()) + .review(ReviewInput.approve()); + + setApiUser(user); + gApi.changes() + .id(r.getChangeId()) + .revision(r.getCommit().name()) + .review(ReviewInput.recommend()); + + setApiUser(admin); + gApi.changes() + .id(r.getChangeId()) + .reviewer(admin.getId().toString()) + .deleteVote("Code-Review"); + + Map<String, Short> m = gApi.changes() + .id(r.getChangeId()) + .reviewer(admin.getId().toString()) + .votes(); + + if (isNoteDbTestEnabled()) { + // When notedb is enabled each reviewer is explicitly recorded in the + // notedb and this record stays even when all votes of that user have been + // deleted, hence there is no dummy 0 approval left when a vote is + // deleted. + assertThat(m).isEmpty(); + } else { + // When notedb is disabled there is a dummy 0 approval on the change so + // that the user is still returned as CC when all votes of that user have + // been deleted. + assertThat(m).containsEntry("Code-Review", new Short((short)0)); + } + + ChangeInfo c = gApi.changes() + .id(r.getChangeId()) + .get(); + + assertThat(Iterables.getLast(c.messages).message).isEqualTo( + "Removed Code-Review+2 by Administrator <admin@example.com>\n"); + if (isNoteDbTestEnabled()) { + // When notedb is enabled each reviewer is explicitly recorded in the + // notedb and this record stays even when all votes of that user have been + // deleted. + assertThat(getReviewers(c.reviewers.get(REVIEWER))) + .containsExactlyElementsIn( + ImmutableSet.of(admin.getId(), user.getId())); + } else { + // When notedb is disabled users that have only dummy 0 approvals on the + // change are returned as CC and not as REVIEWER. + assertThat(getReviewers(c.reviewers.get(REVIEWER))) + .containsExactlyElementsIn(ImmutableSet.of(user.getId())); + assertThat(getReviewers(c.reviewers.get(CC))) + .containsExactlyElementsIn(ImmutableSet.of(admin.getId())); + } + } + + @Test + public void deleteVoteNotPermitted() throws Exception { + PushOneCommit.Result r = createChange(); + gApi.changes() + .id(r.getChangeId()) + .revision(r.getCommit().name()) + .review(ReviewInput.approve()); + + setApiUser(user); + exception.expect(AuthException.class); + exception.expectMessage("delete not permitted"); + gApi.changes() + .id(r.getChangeId()) + .reviewer(admin.getId().toString()) + .deleteVote("Code-Review"); } @Test @@ -644,4 +791,15 @@ assertThat(rev2.pushCertificate.certificate).isNull(); assertThat(rev2.pushCertificate.key).isNull(); } + + + private static Iterable<Account.Id> getReviewers( + Collection<AccountInfo> r) { + return Iterables.transform(r, new Function<AccountInfo, Account.Id>() { + @Override + public Account.Id apply(AccountInfo account) { + return new Account.Id(account._accountId); + } + }); + } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java index 475803a..37a5071 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/CheckIT.java
@@ -25,6 +25,10 @@ import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ProblemInfo; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.notedb.ChangeUpdate; +import com.google.gerrit.server.project.ChangeControl; +import com.google.inject.Inject; import org.junit.Test; @@ -33,6 +37,15 @@ @NoHttpd public class CheckIT extends AbstractDaemonTest { + @Inject + private ChangeControl.GenericFactory changeControlFactory; + + @Inject + private IdentifiedUser.GenericFactory userFactory; + + @Inject + private ChangeUpdate.Factory changeUpdateFactory; + // Most types of tests belong in ConsistencyCheckerTest; these mostly just // test paths outside of ConsistencyChecker, like API wiring. @Test @@ -66,6 +79,12 @@ Change c = getChange(r); c.setStatus(Change.Status.NEW); db.changes().update(Collections.singleton(c)); + ChangeUpdate changeUpdate = + changeUpdateFactory.create( + changeControlFactory.controlFor( + c, userFactory.create(admin.id))); + changeUpdate.setStatus(Change.Status.NEW); + changeUpdate.commit(); indexer.index(db, c); ChangeInfo info = gApi.changes() @@ -81,6 +100,11 @@ assertThat(info.problems).hasSize(1); assertThat(info.problems.get(0).status).isEqualTo(ProblemInfo.Status.FIXED); assertThat(info.status).isEqualTo(ChangeStatus.MERGED); + + info = gApi.changes() + .id(r.getChangeId()) + .get(); + assertThat(info.status).isEqualTo(ChangeStatus.MERGED); } private Change getChange(PushOneCommit.Result r) throws Exception {
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 3e970e4..3cddcd9 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
@@ -56,6 +56,7 @@ import java.io.ByteArrayOutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -159,7 +160,11 @@ assertThat(orig.get().messages).hasSize(1); ChangeApi cherry = orig.revision(r.getCommit().name()) .cherryPick(in); - assertThat(orig.get().messages).hasSize(2); + + Collection<ChangeMessageInfo> messages = gApi.changes() + .id(project.get() + "~master~" + r.getChangeId()) + .get().messages; + assertThat(messages).hasSize(2); String cherryPickedRevision = cherry.get().currentRevision; String expectedMessage = String.format( @@ -167,7 +172,7 @@ "This patchset was cherry picked to branch %s as commit %s", in.destination, cherryPickedRevision); - Iterator<ChangeMessageInfo> origIt = orig.get().messages.iterator(); + Iterator<ChangeMessageInfo> origIt = messages.iterator(); origIt.next(); assertThat(origIt.next().message).isEqualTo(expectedMessage); @@ -279,7 +284,11 @@ assertThat(orig.get().messages).hasSize(1); ChangeApi cherry = orig.revision(r.getCommit().name()) .cherryPick(in); - assertThat(orig.get().messages).hasSize(2); + + Collection<ChangeMessageInfo> messages = gApi.changes() + .id(project.get() + "~master~" + r.getChangeId()) + .get().messages; + assertThat(messages).hasSize(2); assertThat(cherry.get().subject).contains(in.message); cherry.current().review(ReviewInput.approve());
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 ed20e24..896eb06 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
@@ -19,11 +19,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.http.HttpStatus.SC_CONFLICT; -import static org.apache.http.HttpStatus.SC_FORBIDDEN; -import static org.apache.http.HttpStatus.SC_NOT_FOUND; -import static org.apache.http.HttpStatus.SC_NO_CONTENT; -import static org.apache.http.HttpStatus.SC_OK; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; @@ -185,8 +180,7 @@ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED); Optional<ChangeEdit> edit = editUtil.byChange(change); - RestResponse r = adminSession.post(urlPublish()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.post(urlPublish()).assertNoContent(); edit = editUtil.byChange(change); assertThat(edit.isPresent()).isFalse(); PatchSet newCurrentPatchSet = getCurrentPatchSet(changeId); @@ -204,8 +198,7 @@ modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED); Optional<ChangeEdit> edit = editUtil.byChange(change); - RestResponse r = adminSession.delete(urlEdit()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.delete(urlEdit()).assertNoContent(); edit = editUtil.byChange(change); assertThat(edit.isPresent()).isFalse(); } @@ -219,11 +212,9 @@ assertThat( modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED); - RestResponse r = adminSession.post(urlPublish()); - assertThat(r.getStatusCode()).isEqualTo(SC_FORBIDDEN); + adminSession.post(urlPublish()).assertForbidden(); setUseContributorAgreements(InheritableBoolean.FALSE); - r = adminSession.post(urlPublish()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.post(urlPublish()).assertNoContent(); } @Test @@ -260,8 +251,7 @@ assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo( current.getPatchSetId() - 1); Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen(); - RestResponse r = adminSession.post(urlRebase()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.post(urlRebase()).assertNoContent(); edit = editUtil.byChange(change).get(); assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()), ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW); @@ -287,8 +277,7 @@ pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, FILE_NAME, new String(CONTENT_NEW2), changeId2); push.to("refs/for/master").assertOkStatus(); - RestResponse r = adminSession.post(urlRebase()); - assertThat(r.getStatusCode()).isEqualTo(SC_CONFLICT); + adminSession.post(urlRebase()).assertConflict(); } @Test @@ -370,24 +359,21 @@ @Test public void updateMessageRest() throws Exception { - assertThat(adminSession.get(urlEditMessage()).getStatusCode()) - .isEqualTo(SC_NOT_FOUND); + adminSession.get(urlEditMessage()).assertNotFound(); EditMessage.Input in = new EditMessage.Input(); in.message = String.format("New commit message\n\n" + CONTENT_NEW2_STR + "\n\nChange-Id: %s\n", change.getKey()); - assertThat(adminSession.put(urlEditMessage(), in).getStatusCode()) - .isEqualTo(SC_NO_CONTENT); + adminSession.put(urlEditMessage(), in).assertNoContent(); RestResponse r = adminSession.getJsonAccept(urlEditMessage()); - assertThat(r.getStatusCode()).isEqualTo(SC_OK); + r.assertOK(); assertThat(readContentFromJson(r)).isEqualTo(in.message); 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\n", change.getKey()); - assertThat(adminSession.put(urlEditMessage(), in).getStatusCode()) - .isEqualTo(SC_NO_CONTENT); + adminSession.put(urlEditMessage(), in).assertNoContent(); edit = editUtil.byChange(change); assertThat(edit.get().getEditCommit().getFullMessage()) .isEqualTo(in.message); @@ -400,8 +386,7 @@ @Test public void retrieveEdit() throws Exception { - RestResponse r = adminSession.get(urlEdit()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.get(urlEdit()).assertNoContent(); assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW); Optional<ChangeEdit> edit = editUtil.byChange(change); assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW))) @@ -414,8 +399,7 @@ edit = editUtil.byChange(change); editUtil.delete(edit.get()); - r = adminSession.get(urlEdit()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.get(urlEdit()).assertNoContent(); } @Test @@ -460,8 +444,7 @@ @Test public void createEditByDeletingExistingFileRest() throws Exception { - RestResponse r = adminSession.delete(urlEditFile()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.delete(urlEditFile()).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); exception.expect(ResourceNotFoundException.class); fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), @@ -470,15 +453,13 @@ @Test public void deletingNonExistingEditRest() throws Exception { - RestResponse r = adminSession.delete(urlEdit()); - assertThat(r.getStatusCode()).isEqualTo(SC_NOT_FOUND); + adminSession.delete(urlEdit()).assertNotFound(); } @Test public void deleteExistingFileRest() throws Exception { assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW); - assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo( - SC_NO_CONTENT); + adminSession.delete(urlEditFile()).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); exception.expect(ResourceNotFoundException.class); fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), @@ -527,8 +508,7 @@ Post.Input in = new Post.Input(); in.oldPath = FILE_NAME; in.newPath = FILE_NAME3; - assertThat(adminSession.post(urlEdit(), in).getStatusCode()).isEqualTo( - SC_NO_CONTENT); + adminSession.post(urlEdit(), in).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME3), CONTENT_OLD); @@ -541,8 +521,7 @@ public void restoreDeletedFileInPatchSetRest() throws Exception { Post.Input in = new Post.Input(); in.restorePath = FILE_NAME; - assertThat(adminSession.post(urlEdit2(), in).getStatusCode()).isEqualTo( - SC_NO_CONTENT); + adminSession.post(urlEdit2(), in).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change2); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD); @@ -568,14 +547,12 @@ public void createAndChangeEditInOneRequestRest() throws Exception { Put.Input in = new Put.Input(); in.content = RestSession.newRawInput(CONTENT_NEW); - assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) - .isEqualTo(SC_NO_CONTENT); + adminSession.putRaw(urlEditFile(), in.content).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW); in.content = RestSession.newRawInput(CONTENT_NEW2); - assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) - .isEqualTo(SC_NO_CONTENT); + adminSession.putRaw(urlEditFile(), in.content).assertNoContent(); edit = editUtil.byChange(change); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2); @@ -586,8 +563,7 @@ assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW); Put.Input in = new Put.Input(); in.content = RestSession.newRawInput(CONTENT_NEW); - assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) - .isEqualTo(SC_NO_CONTENT); + adminSession.putRaw(urlEditFile(), in.content).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW); @@ -596,8 +572,7 @@ @Test public void emptyPutRequest() throws Exception { assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW); - assertThat(adminSession.put(urlEditFile()).getStatusCode()).isEqualTo( - SC_NO_CONTENT); + adminSession.put(urlEditFile()).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), "".getBytes()); @@ -605,8 +580,7 @@ @Test public void createEmptyEditRest() throws Exception { - assertThat(adminSession.post(urlEdit()).getStatusCode()).isEqualTo( - SC_NO_CONTENT); + adminSession.post(urlEdit()).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD); @@ -616,14 +590,13 @@ public void getFileContentRest() throws Exception { Put.Input in = new Put.Input(); in.content = RestSession.newRawInput(CONTENT_NEW); - assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()) - .isEqualTo(SC_NO_CONTENT); + adminSession.putRaw(urlEditFile(), in.content).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2))) .isEqualTo(RefUpdate.Result.FORCED); edit = editUtil.byChange(change); RestResponse r = adminSession.getJsonAccept(urlEditFile()); - assertThat(r.getStatusCode()).isEqualTo(SC_OK); + r.assertOK(); assertThat(readContentFromJson(r)).isEqualTo( StringUtils.newStringUtf8(CONTENT_NEW2)); } @@ -631,11 +604,9 @@ @Test public void getFileNotFoundRest() throws Exception { assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW); - assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo( - SC_NO_CONTENT); + adminSession.delete(urlEditFile()).assertNoContent(); Optional<ChangeEdit> edit = editUtil.byChange(change); - RestResponse r = adminSession.get(urlEditFile()); - assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT); + adminSession.get(urlEditFile()).assertNoContent(); exception.expect(ResourceNotFoundException.class); fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()), ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME); @@ -832,9 +803,9 @@ + "/edit:rebase"; } - private EditInfo toEditInfo(boolean files) throws IOException { + private EditInfo toEditInfo(boolean files) throws Exception { RestResponse r = adminSession.get(files ? urlGetFiles() : urlEdit()); - assertThat(r.getStatusCode()).isEqualTo(SC_OK); + r.assertOK(); return newGson().fromJson(r.getReader(), EditInfo.class); }
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 900d85a..21fd1a8 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
@@ -17,6 +17,7 @@ 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 com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -26,11 +27,19 @@ import com.google.gerrit.acceptance.GitUtil; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.TestAccount; +import com.google.gerrit.common.data.LabelType; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.client.InheritableBoolean; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.EditInfo; import com.google.gerrit.extensions.common.LabelInfo; +import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.server.group.SystemGroupBackend; +import com.google.gerrit.server.project.Util; import org.eclipse.jgit.revwalk.RevCommit; import org.joda.time.DateTime; @@ -51,6 +60,7 @@ } private String sshUrl; + private LabelType patchSetLock; @BeforeClass public static void setTimeForTesting() { @@ -73,6 +83,24 @@ @Before public void setUp() throws Exception { sshUrl = sshSession.getUrl(); + ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); + patchSetLock = Util.patchSetLock(); + cfg.getLabelSections().put(patchSetLock.getName(), patchSetLock); + AccountGroup.UUID anonymousUsers = + SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID(); + Util.allow(cfg, Permission.forLabel(patchSetLock.getName()), 0, 1, anonymousUsers, + "refs/heads/*"); + saveProjectConfig(cfg); + grant(Permission.LABEL + "Patch-Set-Lock", project, "refs/heads/*"); + } + + private void saveProjectConfig(ProjectConfig cfg) throws Exception { + MetaDataUpdate md = metaDataUpdateFactory.create(project); + try { + cfg.commit(md); + } finally { + md.close(); + } } protected void selectProtocol(Protocol p) throws Exception { @@ -276,6 +304,18 @@ } @Test + public void testPushNewPatchsetToPatchSetLockedChange() throws Exception { + PushOneCommit.Result r = pushTo("refs/for/master"); + r.assertOkStatus(); + PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo, + PushOneCommit.SUBJECT, "b.txt", "anotherContent", r.getChangeId()); + revision(r).review(new ReviewInput().label("Patch-Set-Lock", 1)); + r = push.to("refs/for/master"); + r.assertErrorStatus("cannot replace " + r.getChange().change().getChangeId() + + ". Change is patch set locked."); + } + + @Test public void testPushForMasterWithApprovals_MissingLabel() throws Exception { PushOneCommit.Result r = pushTo("refs/for/master/%l=Verify"); r.assertErrorStatus("label \"Verify\" is not a configured label");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java index 27b8b0a..41f47a2 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
@@ -15,13 +15,11 @@ package com.google.gerrit.acceptance.git; import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; -import static com.google.gerrit.server.project.Util.block; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.common.data.Permission; -import com.google.gerrit.server.git.ProjectConfig; import org.junit.Before; import org.junit.Test; @@ -31,9 +29,7 @@ @Before public void setUp() throws Exception { - ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); - block(cfg, Permission.PUSH, ANONYMOUS_USERS, "refs/drafts/*"); - saveProjectConfig(project, cfg); + block(Permission.PUSH, ANONYMOUS_USERS, "refs/drafts/*"); } @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java index 3e7c2bf..768c923 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
@@ -34,7 +34,6 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import org.apache.http.HttpStatus; import org.junit.Test; public class CapabilitiesIT extends AbstractDaemonTest { @@ -53,7 +52,7 @@ try { RestResponse r = userSession.get("/accounts/self/capabilities"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); CapabilityInfo info = (new Gson()).fromJson(r.getReader(), new TypeToken<CapabilityInfo>() {}.getType()); for (String c : GlobalCapability.getAllNames()) { @@ -81,7 +80,7 @@ public void testCapabilitiesAdmin() throws Exception { RestResponse r = adminSession.get("/accounts/self/capabilities"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); CapabilityInfo info = (new Gson()).fromJson(r.getReader(), new TypeToken<CapabilityInfo>() {}.getType()); for (String c : GlobalCapability.getAllNames()) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/DiffPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/DiffPreferencesIT.java index 5b2e7ba..9a66958 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/DiffPreferencesIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/DiffPreferencesIT.java
@@ -15,7 +15,6 @@ package com.google.gerrit.acceptance.rest.account; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.RestResponse; @@ -23,23 +22,21 @@ import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace; import com.google.gerrit.extensions.client.Theme; -import org.apache.http.HttpStatus; import org.junit.Test; public class DiffPreferencesIT extends AbstractDaemonTest { @Test public void getDiffPreferencesOfNonExistingAccount_NotFound() throws Exception { - assertEquals(HttpStatus.SC_NOT_FOUND, - adminSession.get("/accounts/non-existing/preferences.diff") - .getStatusCode()); + adminSession.get("/accounts/non-existing/preferences.diff") + .assertNotFound(); } @Test public void getDiffPreferences() throws Exception { RestResponse r = adminSession.get("/accounts/" + admin.email + "/preferences.diff"); - assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + r.assertOK(); DiffPreferencesInfo d = DiffPreferencesInfo.defaults(); DiffPreferencesInfo o = newGson().fromJson(r.getReader(), DiffPreferencesInfo.class); @@ -98,7 +95,7 @@ RestResponse r = adminSession.put("/accounts/" + admin.email + "/preferences.diff", i); - assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + r.assertOK(); DiffPreferencesInfo o = newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
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 index cf89c5a..627d9bd 100644 --- 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
@@ -22,7 +22,6 @@ 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; @@ -32,7 +31,7 @@ public void getSetEditPreferences() throws Exception { String endPoint = "/accounts/" + admin.email + "/preferences.edit"; RestResponse r = adminSession.get(endPoint); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); EditPreferencesInfo out = getEditPrefInfo(r); assertThat(out.lineLength).isEqualTo(100); @@ -62,22 +61,20 @@ out.theme = Theme.TWILIGHT; out.keyMapType = KeyMapType.EMACS; - r = adminSession.put(endPoint, out); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + adminSession.put(endPoint, out).assertNoContent(); r = adminSession.get(endPoint); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); EditPreferencesInfo info = getEditPrefInfo(r); assertEditPreferences(info, out); // Partially filled input record EditPreferencesInfo in = new EditPreferencesInfo(); in.tabSize = 42; - r = adminSession.put(endPoint, in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + adminSession.put(endPoint, in).assertNoContent(); r = adminSession.get(endPoint); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); info = getEditPrefInfo(r); out.tabSize = in.tabSize; assertEditPreferences(info, out);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java index 2543095..9ba7810 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
@@ -26,7 +26,6 @@ import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject; -import org.apache.http.HttpStatus; import org.junit.Test; import java.util.Collections; @@ -41,7 +40,7 @@ in.username = "myUsername"; RestResponse r = adminSession.put("/accounts/" + createUser().get() + "/username", in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); assertThat(newGson().fromJson(r.getReader(), String.class)).isEqualTo( in.username); } @@ -50,25 +49,25 @@ public void setExisting_Conflict() throws Exception { PutUsername.Input in = new PutUsername.Input(); in.username = admin.username; - RestResponse r = - adminSession.put("/accounts/" + createUser().get() + "/username", in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + adminSession + .put("/accounts/" + createUser().get() + "/username", in) + .assertConflict(); } @Test public void setNew_MethodNotAllowed() throws Exception { PutUsername.Input in = new PutUsername.Input(); in.username = "newUsername"; - RestResponse r = - adminSession.put("/accounts/" + admin.username + "/username", in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_METHOD_NOT_ALLOWED); + adminSession + .put("/accounts/" + admin.username + "/username", in) + .assertMethodNotAllowed(); } @Test public void delete_MethodNotAllowed() throws Exception { - RestResponse r = - adminSession.put("/accounts/" + admin.username + "/username"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_METHOD_NOT_ALLOWED); + adminSession + .put("/accounts/" + admin.username + "/username") + .assertMethodNotAllowed(); } private Account.Id createUser() throws OrmException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java index 2229577..b04dc6d 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
@@ -76,11 +76,8 @@ } private void assertApproveFails(TestAccount a, String changeId) throws Exception { - try { - approve(a, changeId); - } catch (AuthException expected) { - // Expected. - } + exception.expect(AuthException.class); + approve(a, changeId); } private void grantApproveToChangeOwner() throws IOException,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java index 47d071f..3f2107c 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
@@ -28,7 +28,6 @@ import com.google.gerrit.server.query.change.ChangeData; import com.google.gwtorm.server.OrmException; -import org.apache.http.HttpStatus; import org.junit.Test; import java.io.IOException; @@ -45,7 +44,7 @@ assertThat(c.status).isEqualTo(ChangeStatus.NEW); RestResponse r = deletePatchSet(changeId, ps, adminSession); assertThat(r.getEntityContent()).isEqualTo("Patch set is not a draft"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + r.assertConflict(); } @Test @@ -58,7 +57,7 @@ assertThat(c.status).isEqualTo(ChangeStatus.DRAFT); RestResponse r = deletePatchSet(changeId, ps, userSession); assertThat(r.getEntityContent()).isEqualTo("Not found: " + changeId); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + r.assertNotFound(); } @Test @@ -69,12 +68,10 @@ ChangeInfo c = get(triplet); assertThat(c.id).isEqualTo(triplet); assertThat(c.status).isEqualTo(ChangeStatus.DRAFT); - RestResponse r = deletePatchSet(changeId, ps, adminSession); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + deletePatchSet(changeId, ps, adminSession).assertNoContent(); assertThat(getChange(changeId).patchSets()).hasSize(1); ps = getCurrentPatchSet(changeId); - r = deletePatchSet(changeId, ps, adminSession); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + deletePatchSet(changeId, ps, adminSession).assertNoContent(); assertThat(queryProvider.get().byKeyPrefix(changeId)).isEmpty(); }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java index 2707507..0c6521c 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java
@@ -29,7 +29,6 @@ import com.google.gerrit.testutil.ConfigSuite; import com.google.gwtorm.server.OrmException; -import org.apache.http.HttpStatus; import org.eclipse.jgit.lib.Config; import org.junit.Test; @@ -52,7 +51,7 @@ assertThat(c.status).isEqualTo(ChangeStatus.NEW); RestResponse response = deleteChange(changeId, adminSession); assertThat(response.getEntityContent()).isEqualTo("Change is not a draft"); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + response.assertConflict(); } @Test @@ -65,8 +64,7 @@ ChangeInfo c = get(triplet); assertThat(c.id).isEqualTo(triplet); assertThat(c.status).isEqualTo(ChangeStatus.DRAFT); - RestResponse response = deleteChange(changeId, adminSession); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + deleteChange(changeId, adminSession).assertNoContent(); exception.expect(ResourceNotFoundException.class); get(triplet); @@ -83,8 +81,7 @@ assertThat(c.id).isEqualTo(triplet); assertThat(c.status).isEqualTo(ChangeStatus.DRAFT); assertThat(c.revisions.get(c.currentRevision).draft).isTrue(); - RestResponse response = publishChange(changeId); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + publishChange(changeId).assertNoContent(); c = get(triplet); assertThat(c.status).isEqualTo(ChangeStatus.NEW); assertThat(c.revisions.get(c.currentRevision).draft).isNull(); @@ -100,8 +97,7 @@ ChangeInfo c = get(triplet); assertThat(c.id).isEqualTo(triplet); assertThat(c.status).isEqualTo(ChangeStatus.DRAFT); - RestResponse response = publishPatchSet(changeId); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + publishPatchSet(changeId).assertNoContent(); assertThat(get(triplet).status).isEqualTo(ChangeStatus.NEW); }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java index 405f8e5..7035bf9 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
@@ -14,27 +14,25 @@ package com.google.gerrit.acceptance.rest.change; -import static com.google.common.truth.Truth.assertThat; - import com.google.gerrit.acceptance.AbstractDaemonTest; -import com.google.gerrit.acceptance.RestResponse; -import org.apache.http.HttpStatus; import org.junit.Test; public class IndexChangeIT extends AbstractDaemonTest { @Test public void indexChange() throws Exception { String changeId = createChange().getChangeId(); - RestResponse r = adminSession.post("/changes/" + changeId + "/index/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + adminSession + .post("/changes/" + changeId + "/index/") + .assertNoContent(); } @Test public void indexChangeOnNonVisibleBranch() throws Exception { String changeId = createChange().getChangeId(); - blockRead(project, "refs/heads/master"); - RestResponse r = userSession.post("/changes/" + changeId + "/index/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + blockRead("refs/heads/master"); + userSession + .post("/changes/" + changeId + "/index/") + .assertNotFound(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java index 7e68a03..7754a53 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
@@ -25,7 +25,6 @@ import com.google.gerrit.server.config.ListCaches.CacheInfo; import com.google.gerrit.server.config.PostCaches; -import org.apache.http.HttpStatus; import org.junit.Test; import java.util.Arrays; @@ -39,7 +38,7 @@ assertThat(cacheInfo.entries.mem).isGreaterThan((long) 0); r = adminSession.post("/config/server/caches/", new PostCaches.Input(FLUSH_ALL)); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); r.consume(); r = adminSession.get("/config/server/caches/project_list"); @@ -49,16 +48,16 @@ @Test public void flushAll_Forbidden() throws Exception { - RestResponse r = userSession.post("/config/server/caches/", - new PostCaches.Input(FLUSH_ALL)); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession.post("/config/server/caches/", + new PostCaches.Input(FLUSH_ALL)).assertForbidden(); } @Test public void flushAll_BadRequest() throws Exception { - RestResponse r = adminSession.post("/config/server/caches/", - new PostCaches.Input(FLUSH_ALL, Arrays.asList("projects"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + adminSession + .post("/config/server/caches/", + new PostCaches.Input(FLUSH_ALL, Arrays.asList("projects"))) + .assertBadRequest(); } @Test @@ -73,7 +72,7 @@ r = adminSession.post("/config/server/caches/", new PostCaches.Input(FLUSH, Arrays.asList("accounts", "project_list"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); r.consume(); r = adminSession.get("/config/server/caches/project_list"); @@ -87,16 +86,18 @@ @Test public void flush_Forbidden() throws Exception { - RestResponse r = userSession.post("/config/server/caches/", - new PostCaches.Input(FLUSH, Arrays.asList("projects"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .post("/config/server/caches/", + new PostCaches.Input(FLUSH, Arrays.asList("projects"))) + .assertForbidden(); } @Test public void flush_BadRequest() throws Exception { - RestResponse r = adminSession.post("/config/server/caches/", - new PostCaches.Input(FLUSH)); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + adminSession + .post("/config/server/caches/", + new PostCaches.Input(FLUSH)) + .assertBadRequest(); } @Test @@ -107,7 +108,7 @@ r = adminSession.post("/config/server/caches/", new PostCaches.Input(FLUSH, Arrays.asList("projects", "unprocessable"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY); + r.assertUnprocessableEntity(); r.consume(); r = adminSession.get("/config/server/caches/projects"); @@ -122,12 +123,13 @@ try { RestResponse r = userSession.post("/config/server/caches/", new PostCaches.Input(FLUSH, Arrays.asList("projects"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); r.consume(); - r = userSession.post("/config/server/caches/", - new PostCaches.Input(FLUSH, Arrays.asList("web_sessions"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .post("/config/server/caches/", + new PostCaches.Input(FLUSH, Arrays.asList("web_sessions"))) + .assertForbidden(); } finally { removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.FLUSH_CACHES, GlobalCapability.VIEW_CACHES);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java index 9d8320ad..0638637 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java
@@ -14,17 +14,13 @@ package com.google.gerrit.acceptance.rest.config; -import static com.google.common.truth.Truth.assertThat; - import com.google.gerrit.acceptance.AbstractDaemonTest; -import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.server.config.ConfirmEmail; import com.google.gerrit.server.mail.EmailTokenVerifier; import com.google.gerrit.testutil.ConfigSuite; import com.google.gwtjsonrpc.server.SignedToken; import com.google.inject.Inject; -import org.apache.http.HttpStatus; import org.eclipse.jgit.lib.Config; import org.junit.Test; @@ -44,23 +40,26 @@ public void confirm() throws Exception { ConfirmEmail.Input in = new ConfirmEmail.Input(); in.token = emailTokenVerifier.encode(admin.getId(), "new.mail@example.com"); - RestResponse r = adminSession.put("/config/server/email.confirm", in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + adminSession + .put("/config/server/email.confirm", in) + .assertNoContent(); } @Test public void confirmForOtherUser_UnprocessableEntity() throws Exception { ConfirmEmail.Input in = new ConfirmEmail.Input(); in.token = emailTokenVerifier.encode(user.getId(), "new.mail@example.com"); - RestResponse r = adminSession.put("/config/server/email.confirm", in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY); + adminSession + .put("/config/server/email.confirm", in) + .assertUnprocessableEntity(); } @Test public void confirmInvalidToken_UnprocessableEntity() throws Exception { ConfirmEmail.Input in = new ConfirmEmail.Input(); in.token = "invalidToken"; - RestResponse r = adminSession.put("/config/server/email.confirm", in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY); + adminSession + .put("/config/server/email.confirm", in) + .assertUnprocessableEntity(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java index bb63928..0113672 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
@@ -22,7 +22,6 @@ import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.server.config.ListCaches.CacheInfo; -import org.apache.http.HttpStatus; import org.junit.Test; public class FlushCacheIT extends AbstractDaemonTest { @@ -34,7 +33,7 @@ assertThat(result.entries.mem).isGreaterThan((long)0); r = adminSession.post("/config/server/caches/groups/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); r.consume(); r = adminSession.get("/config/server/caches/groups"); @@ -44,26 +43,30 @@ @Test public void flushCache_Forbidden() throws Exception { - RestResponse r = userSession.post("/config/server/caches/accounts/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .post("/config/server/caches/accounts/flush") + .assertForbidden(); } @Test public void flushCache_NotFound() throws Exception { - RestResponse r = adminSession.post("/config/server/caches/nonExisting/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + adminSession + .post("/config/server/caches/nonExisting/flush") + .assertNotFound(); } @Test public void flushCacheWithGerritPrefix() throws Exception { - RestResponse r = adminSession.post("/config/server/caches/gerrit-accounts/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + adminSession + .post("/config/server/caches/gerrit-accounts/flush") + .assertOK(); } @Test public void flushWebSessionsCache() throws Exception { - RestResponse r = adminSession.post("/config/server/caches/web_sessions/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + adminSession + .post("/config/server/caches/web_sessions/flush") + .assertOK(); } @Test @@ -72,11 +75,12 @@ GlobalCapability.VIEW_CACHES, GlobalCapability.FLUSH_CACHES); try { RestResponse r = userSession.post("/config/server/caches/accounts/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); r.consume(); - r = userSession.post("/config/server/caches/web_sessions/flush"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .post("/config/server/caches/web_sessions/flush") + .assertForbidden(); } finally { removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.VIEW_CACHES, GlobalCapability.FLUSH_CACHES);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java index 02a1b73..ff8a3b9 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
@@ -21,7 +21,6 @@ import com.google.gerrit.server.config.ListCaches.CacheInfo; import com.google.gerrit.server.config.ListCaches.CacheType; -import org.apache.http.HttpStatus; import org.junit.Test; public class GetCacheIT extends AbstractDaemonTest { @@ -29,7 +28,7 @@ @Test public void getCache() throws Exception { RestResponse r = adminSession.get("/config/server/caches/accounts"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); CacheInfo result = newGson().fromJson(r.getReader(), CacheInfo.class); assertThat(result.name).isEqualTo("accounts"); @@ -45,26 +44,29 @@ userSession.get("/config/server/version").consume(); r = adminSession.get("/config/server/caches/accounts"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); result = newGson().fromJson(r.getReader(), CacheInfo.class); assertThat(result.entries.mem).isEqualTo(2); } @Test public void getCache_Forbidden() throws Exception { - RestResponse r = userSession.get("/config/server/caches/accounts"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .get("/config/server/caches/accounts") + .assertForbidden(); } @Test public void getCache_NotFound() throws Exception { - RestResponse r = adminSession.get("/config/server/caches/nonExisting"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + adminSession + .get("/config/server/caches/nonExisting") + .assertNotFound(); } @Test public void getCacheWithGerritPrefix() throws Exception { - RestResponse r = adminSession.get("/config/server/caches/gerrit-accounts"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + adminSession + .get("/config/server/caches/gerrit-accounts") + .assertOK(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java index acd900c..8455062 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
@@ -21,7 +21,6 @@ import com.google.gerrit.server.config.ListTasks.TaskInfo; import com.google.gson.reflect.TypeToken; -import org.apache.http.HttpStatus; import org.junit.Test; import java.util.List; @@ -32,7 +31,7 @@ public void getTask() throws Exception { RestResponse r = adminSession.get("/config/server/tasks/" + getLogFileCompressorTaskId()); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); TaskInfo info = newGson().fromJson(r.getReader(), new TypeToken<TaskInfo>() {}.getType()); @@ -44,9 +43,9 @@ @Test public void getTask_NotFound() throws Exception { - RestResponse r = - userSession.get("/config/server/tasks/" + getLogFileCompressorTaskId()); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + userSession + .get("/config/server/tasks/" + getLogFileCompressorTaskId()) + .assertNotFound(); } private String getLogFileCompressorTaskId() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java index 11c61c6..88b93c8 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
@@ -21,7 +21,6 @@ import com.google.gerrit.server.config.ListTasks.TaskInfo; import com.google.gson.reflect.TypeToken; -import org.apache.http.HttpStatus; import org.junit.Test; import java.util.List; @@ -37,7 +36,7 @@ assertThat(taskCount).isGreaterThan(0); r = adminSession.delete("/config/server/tasks/" + result.get(0).id); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + r.assertNoContent(); r.consume(); r = adminSession.get("/config/server/tasks/"); @@ -54,8 +53,9 @@ r.consume(); assertThat(result.size()).isGreaterThan(0); - r = userSession.delete("/config/server/tasks/" + result.get(0).id); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + userSession + .delete("/config/server/tasks/" + result.get(0).id) + .assertNotFound(); } @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java index fb89e1b..964c759 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
@@ -24,7 +24,6 @@ import com.google.gerrit.server.config.ListCaches.CacheType; import com.google.gson.reflect.TypeToken; -import org.apache.http.HttpStatus; import org.eclipse.jgit.util.Base64; import org.junit.Test; @@ -37,7 +36,7 @@ @Test public void listCaches() throws Exception { RestResponse r = adminSession.get("/config/server/caches/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); Map<String, CacheInfo> result = newGson().fromJson(r.getReader(), new TypeToken<Map<String, CacheInfo>>() {}.getType()); @@ -56,7 +55,7 @@ userSession.get("/config/server/version").consume(); r = adminSession.get("/config/server/caches/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); result = newGson().fromJson(r.getReader(), new TypeToken<Map<String, CacheInfo>>() {}.getType()); assertThat(result.get("accounts").entries.mem).isEqualTo(2); @@ -64,14 +63,15 @@ @Test public void listCaches_Forbidden() throws Exception { - RestResponse r = userSession.get("/config/server/caches/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .get("/config/server/caches/") + .assertForbidden(); } @Test public void listCacheNames() throws Exception { RestResponse r = adminSession.get("/config/server/caches/?format=LIST"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); List<String> result = newGson().fromJson(r.getReader(), new TypeToken<List<String>>() {}.getType()); @@ -83,7 +83,7 @@ @Test public void listCacheNamesTextList() throws Exception { RestResponse r = adminSession.get("/config/server/caches/?format=TEXT_LIST"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); String result = new String(Base64.decode(r.getEntityContent()), UTF_8.name()); List<String> list = Arrays.asList(result.split("\n")); assertThat(list).contains("accounts"); @@ -93,7 +93,8 @@ @Test public void listCaches_BadRequest() throws Exception { - RestResponse r = adminSession.get("/config/server/caches/?format=NONSENSE"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + adminSession + .get("/config/server/caches/?format=NONSENSE") + .assertBadRequest(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java index 58f3361..0b2c6cc 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
@@ -21,7 +21,6 @@ import com.google.gerrit.server.config.ListTasks.TaskInfo; import com.google.gson.reflect.TypeToken; -import org.apache.http.HttpStatus; import org.junit.Test; import java.util.List; @@ -31,7 +30,7 @@ @Test public void listTasks() throws Exception { RestResponse r = adminSession.get("/config/server/tasks/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); List<TaskInfo> result = newGson().fromJson(r.getReader(), new TypeToken<List<TaskInfo>>() {}.getType()); @@ -52,7 +51,7 @@ @Test public void listTasksWithoutViewQueueCapability() throws Exception { RestResponse r = userSession.get("/config/server/tasks/"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); List<TaskInfo> result = newGson().fromJson(r.getReader(), new TypeToken<List<TaskInfo>>() {}.getType());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddMemberIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddMemberIT.java index 242e1ee..1fbebe8 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddMemberIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddMemberIT.java
@@ -14,19 +14,15 @@ package com.google.gerrit.acceptance.rest.group; -import static com.google.common.truth.Truth.assertThat; - import com.google.gerrit.acceptance.AbstractDaemonTest; -import org.apache.http.HttpStatus; import org.junit.Test; public class AddMemberIT extends AbstractDaemonTest { @Test public void addNonExistingMember_NotFound() throws Exception { - int status = - adminSession.put("/groups/Administrators/members/non-existing") - .getStatusCode(); - assertThat(status).isEqualTo(HttpStatus.SC_NOT_FOUND); + adminSession + .put("/groups/Administrators/members/non-existing") + .assertNotFound(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java index 682059c..0362f59 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
@@ -23,7 +23,6 @@ import com.google.gerrit.server.project.BanCommit; import com.google.gerrit.server.project.BanCommit.BanResultInfo; -import org.apache.http.HttpStatus; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PushResult; import org.junit.Test; @@ -39,7 +38,7 @@ RestResponse r = adminSession.put("/projects/" + project.get() + "/ban/", BanCommit.Input.fromCommits(c.name())); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); BanResultInfo info = newGson().fromJson(r.getReader(), BanResultInfo.class); assertThat(Iterables.getOnlyElement(info.newlyBanned)).isEqualTo(c.name()); assertThat(info.alreadyBanned).isNull(); @@ -59,7 +58,7 @@ r = adminSession.put("/projects/" + project.get() + "/ban/", BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96")); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); BanResultInfo info = newGson().fromJson(r.getReader(), BanResultInfo.class); assertThat(Iterables.getOnlyElement(info.alreadyBanned)) .isEqualTo("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"); @@ -69,9 +68,9 @@ @Test public void banCommit_Forbidden() throws Exception { - RestResponse r = - userSession.put("/projects/" + project.get() + "/ban/", - BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96")); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .put("/projects/" + project.get() + "/ban/", BanCommit.Input.fromCommits( + "a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96")) + .assertForbidden(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java index 62dd729..46f93b6 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -17,7 +17,6 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; -import static com.google.gerrit.server.project.Util.block; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.NoHttpd; @@ -29,7 +28,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.reviewdb.client.Branch; -import com.google.gerrit.server.git.ProjectConfig; import org.eclipse.jgit.lib.Constants; import org.junit.Before; @@ -84,9 +82,7 @@ } private void blockCreateReference() throws Exception { - ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); - block(cfg, Permission.CREATE, ANONYMOUS_USERS, "refs/*"); - saveProjectConfig(project, cfg); + block(Permission.CREATE, ANONYMOUS_USERS, "refs/*"); } private void grantOwner() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java index 030897b..07a2110 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -39,7 +39,6 @@ import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.project.ProjectState; -import org.apache.http.HttpStatus; import org.apache.http.message.BasicHeader; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.Constants; @@ -58,7 +57,7 @@ public void testCreateProjectHttp() throws Exception { String newProjectName = name("newProject"); RestResponse r = adminSession.put("/projects/" + newProjectName); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED); + r.assertCreated(); ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class); assertThat(p.name).isEqualTo(newProjectName); ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName)); @@ -70,32 +69,36 @@ @Test public void testCreateProjectHttpWhenProjectAlreadyExists_Conflict() throws Exception { - RestResponse r = adminSession.put("/projects/" + allProjects.get()); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + adminSession + .put("/projects/" + allProjects.get()) + .assertConflict(); } @Test public void testCreateProjectHttpWhenProjectAlreadyExists_PreconditionFailed() throws Exception { - RestResponse r = adminSession.putWithHeader("/projects/" + allProjects.get(), - new BasicHeader(HttpHeaders.IF_NONE_MATCH, "*")); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_PRECONDITION_FAILED); + adminSession + .putWithHeader("/projects/" + allProjects.get(), + new BasicHeader(HttpHeaders.IF_NONE_MATCH, "*")) + .assertPreconditionFailed(); } @Test @UseLocalDisk public void testCreateProjectHttpWithUnreasonableName_BadRequest() throws Exception { - RestResponse r = adminSession.put("/projects/" + Url.encode(name("invalid/../name"))); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + adminSession + .put("/projects/" + Url.encode(name("invalid/../name"))) + .assertBadRequest(); } @Test public void testCreateProjectHttpWithNameMismatch_BadRequest() throws Exception { ProjectInput in = new ProjectInput(); in.name = name("otherName"); - RestResponse r = adminSession.put("/projects/" + name("someName"), in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + adminSession + .put("/projects/" + name("someName"), in) + .assertBadRequest(); } @Test @@ -103,8 +106,9 @@ throws Exception { ProjectInput in = new ProjectInput(); in.branches = Collections.singletonList(name("invalid ref name")); - RestResponse r = adminSession.put("/projects/" + name("newProject"), in); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_BAD_REQUEST); + adminSession + .put("/projects/" + name("newProject"), in) + .assertBadRequest(); } @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java index c9347cd..b02357e 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -16,7 +16,6 @@ import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; -import static com.google.gerrit.server.project.Util.block; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.NoHttpd; @@ -26,7 +25,6 @@ import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.reviewdb.client.Branch; -import com.google.gerrit.server.git.ProjectConfig; import org.junit.Before; import org.junit.Test; @@ -76,9 +74,7 @@ } private void blockForcePush() throws Exception { - ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); - block(cfg, Permission.PUSH, ANONYMOUS_USERS, "refs/heads/*").setForce(true); - saveProjectConfig(project, cfg); + block(Permission.PUSH, ANONYMOUS_USERS, "refs/heads/*").setForce(true); } private void grantOwner() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java index d680541..f55f591 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
@@ -14,8 +14,6 @@ package com.google.gerrit.acceptance.rest.project; -import static com.google.common.truth.Truth.assertThat; - import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.GcAssert; import com.google.gerrit.acceptance.RestResponse; @@ -23,7 +21,6 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.inject.Inject; -import org.apache.http.HttpStatus; import org.junit.Before; import org.junit.Test; @@ -43,29 +40,27 @@ @Test public void testGcNonExistingProject_NotFound() throws Exception { - assertThat(POST("/projects/non-existing/gc")).isEqualTo( - HttpStatus.SC_NOT_FOUND); + POST("/projects/non-existing/gc").assertNotFound(); } @Test public void testGcNotAllowed_Forbidden() throws Exception { - assertThat( - userSession.post("/projects/" + allProjects.get() + "/gc") - .getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + userSession + .post("/projects/" + allProjects.get() + "/gc") + .assertForbidden(); } @Test @UseLocalDisk public void testGcOneProject() throws Exception { - assertThat(POST("/projects/" + allProjects.get() + "/gc")).isEqualTo( - HttpStatus.SC_OK); + POST("/projects/" + allProjects.get() + "/gc").assertOK(); gcAssert.assertHasPackFile(allProjects); gcAssert.assertHasNoPackFile(project, project2); } - private int POST(String endPoint) throws IOException { + private RestResponse POST(String endPoint) throws IOException { RestResponse r = adminSession.post(endPoint); r.consume(); - return r.getStatusCode(); + return r; } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java index ea3a5db..f87b921 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -14,7 +14,6 @@ package com.google.gerrit.acceptance.rest.project; -import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo; import com.google.gerrit.acceptance.AbstractDaemonTest; @@ -71,10 +70,8 @@ private void assertChildNotFound(Project.NameKey parent, String child) throws Exception { - try { - gApi.projects().name(parent.get()).child(child); - } catch (ResourceNotFoundException e) { - assertThat(e.getMessage()).contains(child); - } + exception.expect(ResourceNotFoundException.class); + exception.expectMessage(child); + gApi.projects().name(parent.get()).child(child).get(); } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java index 7044ad0..d32be8b 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -25,7 +25,6 @@ import com.google.gerrit.extensions.common.CommitInfo; import com.google.gerrit.server.git.ProjectConfig; -import org.apache.http.HttpStatus; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -40,7 +39,7 @@ @Before public void setUp() throws Exception { repo = GitUtil.newTestRepository(repoManager.openRepository(project)); - blockRead(project, "refs/*"); + blockRead("refs/*"); } @After @@ -130,15 +129,15 @@ } private void assertNotFound(ObjectId id) throws Exception { - RestResponse r = userSession.get( - "/projects/" + project.get() + "/commits/" + id.name()); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND); + userSession + .get("/projects/" + project.get() + "/commits/" + id.name()) + .assertNotFound(); } private CommitInfo getCommit(ObjectId id) throws Exception { RestResponse r = userSession.get( "/projects/" + project.get() + "/commits/" + id.name()); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); CommitInfo result = newGson().fromJson(r.getReader(), CommitInfo.class); r.consume(); return result;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java index 0799d48..a3a107d 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
@@ -37,7 +37,7 @@ @Test public void listBranchesOfNonVisibleProject_NotFound() throws Exception { - blockRead(project, "refs/*"); + blockRead("refs/*"); setApiUser(user); exception.expect(ResourceNotFoundException.class); gApi.projects().name(project.get()).branches().get(); @@ -66,7 +66,7 @@ @Test public void listBranchesSomeHidden() throws Exception { - blockRead(project, "refs/heads/dev"); + blockRead("refs/heads/dev"); String master = pushTo("refs/heads/master").getCommit().name(); pushTo("refs/heads/dev"); setApiUser(user); @@ -79,7 +79,7 @@ @Test public void listBranchesHeadHidden() throws Exception { - blockRead(project, "refs/heads/master"); + blockRead("refs/heads/master"); pushTo("refs/heads/master"); String dev = pushTo("refs/heads/dev").getCommit().name(); setApiUser(user);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java index 80ad493..78e0ba2 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -14,7 +14,6 @@ package com.google.gerrit.acceptance.rest.project; -import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertThatNameList; import com.google.gerrit.acceptance.AbstractDaemonTest; @@ -29,11 +28,9 @@ @Test public void listChildrenOfNonExistingProject_NotFound() throws Exception { - try { - gApi.projects().name(name("non-existing")).child("children"); - } catch (ResourceNotFoundException e) { - assertThat(e.getMessage()).contains("non-existing"); - } + exception.expect(ResourceNotFoundException.class); + exception.expectMessage("non-existing"); + gApi.projects().name(name("non-existing")).child("children"); } @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java index 1e51571..e86bb29 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertThatNameList; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; +import static org.junit.Assert.fail; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @@ -197,9 +198,10 @@ .containsExactly(allProjects, allUsers, project).inOrder(); } - private static void assertBadRequest(ListRequest req) throws Exception { + private void assertBadRequest(ListRequest req) throws Exception { try { req.get(); + fail("Expected BadRequestException"); } catch (BadRequestException expected) { // Expected. }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java index 67d4d1f..e09c63a 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
@@ -21,7 +21,6 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.project.SetParent; -import org.apache.http.HttpStatus; import org.junit.Test; public class SetParentIT extends AbstractDaemonTest { @@ -31,7 +30,7 @@ RestResponse r = userSession.put("/projects/" + project.get() + "/parent", newParentInput(parent)); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_FORBIDDEN); + r.assertForbidden(); r.consume(); } @@ -41,11 +40,11 @@ RestResponse r = adminSession.put("/projects/" + project.get() + "/parent", newParentInput(parent)); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); r.consume(); r = adminSession.get("/projects/" + project.get() + "/parent"); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK); + r.assertOK(); String newParent = newGson().fromJson(r.getReader(), String.class); assertThat(newParent).isEqualTo(parent); @@ -57,7 +56,7 @@ RestResponse r = adminSession.put("/projects/" + allProjects.get() + "/parent", newParentInput(project.get())); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + r.assertConflict(); r.consume(); } @@ -66,19 +65,19 @@ RestResponse r = adminSession.put("/projects/" + project.get() + "/parent", newParentInput(project.get())); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + r.assertConflict(); r.consume(); Project.NameKey child = createProject("child", project, true); r = adminSession.put("/projects/" + project.get() + "/parent", newParentInput(child.get())); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + r.assertConflict(); r.consume(); String grandchild = createProject("grandchild", child, true).get(); r = adminSession.put("/projects/" + project.get() + "/parent", newParentInput(grandchild)); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT); + r.assertConflict(); r.consume(); } @@ -87,7 +86,7 @@ RestResponse r = adminSession.put("/projects/" + project.get() + "/parent", newParentInput("non-existing")); - assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY); + r.assertUnprocessableEntity(); r.consume(); }
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 b22d26b9..33cfe99 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
@@ -26,7 +26,6 @@ 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; @@ -42,8 +41,9 @@ @Test public void listTagsOfNonExistingProject() throws Exception { - assertThat(adminSession.get("/projects/non-existing/tags").getStatusCode()) - .isEqualTo(HttpStatus.SC_NOT_FOUND); + adminSession + .get("/projects/non-existing/tags") + .assertNotFound(); } @Test @@ -60,15 +60,15 @@ @Test public void listTagsOfNonVisibleProject() throws Exception { - blockRead(project, "refs/*"); - assertThat( - userSession.get("/projects/" + project.get() + "/tags").getStatusCode()) - .isEqualTo(HttpStatus.SC_NOT_FOUND); + blockRead("refs/*"); + userSession + .get("/projects/" + project.get() + "/tags") + .assertNotFound(); } @Test public void listTagsOfNonVisibleProjectWithApi() throws Exception { - blockRead(project, "refs/*"); + blockRead("refs/*"); setApiUser(user); exception.expect(ResourceNotFoundException.class); gApi.projects().name(project.get()).tags().get(); @@ -76,7 +76,7 @@ @Test public void getTagOfNonVisibleProjectWithApi() throws Exception { - blockRead(project, "refs/*"); + blockRead("refs/*"); exception.expect(ResourceNotFoundException.class); gApi.projects().name(project.get()).tag("tag").get(); } @@ -211,7 +211,7 @@ assertThat(result.get(1).ref).isEqualTo("refs/tags/" + tag2.name); assertThat(result.get(1).revision).isEqualTo(r2.getCommitId().getName()); - blockRead(project, "refs/heads/hidden"); + blockRead("refs/heads/hidden"); result = getTags().get(); assertThat(result).hasSize(1); assertThat(result.get(0).ref).isEqualTo("refs/tags/" + tag1.name);
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 b784f05..ae4003c 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
@@ -289,6 +289,20 @@ } @Test + public void listChangeWithDrafts() throws Exception { + for (Integer line : lines) { + PushOneCommit.Result r = createChange(); + String changeId = r.getChangeId(); + String revId = r.getCommit().getName(); + DraftInput comment = newDraft( + "file1", Side.REVISION, line, "comment 1"); + addDraft(changeId, revId, comment); + assertThat(gApi.changes().query( + "change:" + changeId + " has:draft").get()).hasSize(1); + } + } + + @Test public void publishCommentsAllRevisions() throws Exception { PushOneCommit.Result r1 = createChange();
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 357f268..387092d 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
@@ -18,6 +18,7 @@ import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; import static com.google.gerrit.server.project.Util.category; import static com.google.gerrit.server.project.Util.value; +import static com.google.gerrit.testutil.GerritServerTests.isNoteDbTestEnabled; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.NoHttpd; @@ -127,7 +128,7 @@ 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.all).hasSize(isNoteDbTestEnabled() ? 1 : 2); assertThat(q.disliked).isNull(); assertThat(q.rejected).isNull(); assertThat(q.blocking).isNull();
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 363a7e4..f7e11fb 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
@@ -24,6 +24,7 @@ import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.LabelInfo; +import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.project.Util; @@ -46,6 +47,15 @@ } @Test + public void failChangedLabelValueOnClosedChange() throws Exception { + PushOneCommit.Result r = createChange(); + merge(r); + exception.expect(ResourceConflictException.class); + exception.expectMessage("change is closed"); + revision(r).review(ReviewInput.reject()); + } + + @Test public void noCopyMinScoreOnRework() throws Exception { codeReview.setCopyMinScore(false); saveLabelConfig();
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 28e0d24..b6cd45f 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
@@ -49,6 +49,7 @@ public static final String ADMIN_CREATE_PROJECT = "/admin/create-project/"; public static final String ADMIN_PLUGINS = "/admin/plugins/"; public static final String MY_GROUPS = "/groups/self"; + public static final String DOCUMENTATION = "/Documentation/"; public static String toChange(final ChangeInfo c) { return toChange(c.getId()); @@ -140,6 +141,10 @@ return SETTINGS_EXTENSION + pluginName + "/" + path; } + public static String toDocumentationQuery(String query) { + return DOCUMENTATION + KeyUtil.encode(query); + } + private static String status(Status status) { switch (status) { case ABANDONED:
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml index e879347..07f2965 100644 --- a/gerrit-extension-api/pom.xml +++ b/gerrit-extension-api/pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-extension-api</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Extension API</name> <description>API for Gerrit Extensions</description>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java index 8b9f520..4404f94 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java
@@ -22,11 +22,11 @@ import com.google.gerrit.extensions.restapi.NotImplementedException; public interface GerritApi { - public Accounts accounts(); - public Changes changes(); - public Config config(); - public Groups groups(); - public Projects projects(); + Accounts accounts(); + Changes changes(); + Config config(); + Groups groups(); + Projects projects(); /** * A default implementation which allows source compatibility
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java index ce07098..d927231 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -58,6 +58,19 @@ */ RevisionApi revision(String id) throws RestApiException; + /** + * Look up the reviewer of the change. + * <p> + * @param id ID of the account, can be a string of the format + * "Full Name <mail@example.com>", just the email address, a full name + * if it is unique, an account ID, a user name or 'self' for the + * calling user. + * @return API for accessing the reviewer. + * @throws RestApiException if id is not account ID or is a user that isn't + * known to be a reviewer for this change. + */ + ReviewerApi reviewer(String id) throws RestApiException; + void abandon() throws RestApiException; void abandon(AbandonInput in) throws RestApiException; @@ -177,6 +190,11 @@ } @Override + public ReviewerApi reviewer(String id) throws RestApiException { + throw new NotImplementedException(); + } + + @Override public RevisionApi revision(String id) throws RestApiException { throw new NotImplementedException(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java new file mode 100644 index 0000000..11b670d --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewerApi.java
@@ -0,0 +1,25 @@ +// 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.api.changes; + +import com.google.gerrit.extensions.restapi.RestApiException; + +import java.util.Map; + +public interface ReviewerApi { + + Map<String, Short> votes() throws RestApiException; + void deleteVote(String label) throws RestApiException; +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerState.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ReviewerState.java similarity index 63% rename from gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerState.java rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ReviewerState.java index b829a69..a58c959 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerState.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ReviewerState.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,28 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.server.notedb; +package com.google.gerrit.extensions.client; -import org.eclipse.jgit.revwalk.FooterKey; - -/** State of a reviewer on a change. */ public enum ReviewerState { /** The user has contributed at least one nonzero vote on the change. */ - REVIEWER(new FooterKey("Reviewer")), + REVIEWER, /** The reviewer was added to the change, but has not voted. */ - CC(new FooterKey("CC")), + CC, /** The user was previously a reviewer on the change, but was removed. */ - REMOVED(new FooterKey("Removed")); - - private final FooterKey footerKey; - - private ReviewerState(FooterKey footerKey) { - this.footerKey = footerKey; - } - - FooterKey getFooterKey() { - return footerKey; - } + REMOVED; }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java index cdfe0c6..455243a 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -15,6 +15,7 @@ package com.google.gerrit.extensions.common; import com.google.gerrit.extensions.client.ChangeStatus; +import com.google.gerrit.extensions.client.ReviewerState; import java.sql.Timestamp; import java.util.Collection; @@ -48,6 +49,7 @@ public Map<String, LabelInfo> labels; public Map<String, Collection<String>> permittedLabels; public Collection<AccountInfo> removableReviewers; + public Map<ReviewerState, Collection<AccountInfo>> reviewers; public Collection<ChangeMessageInfo> messages; public String currentRevision;
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 00d0c18..a812908 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
@@ -21,4 +21,5 @@ public Integer linesInserted; public Integer linesDeleted; public long sizeDelta; + public long size; }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/LifecycleListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/LifecycleListener.java index 93da347..b3ed37b 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/LifecycleListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/LifecycleListener.java
@@ -21,9 +21,9 @@ /** Listener interested in server startup and shutdown events. */ @ExtensionPoint public interface LifecycleListener extends EventListener { - /** Invoke when the server is starting. */ - public void start(); + /** Invoked when the server is starting. */ + void start(); /** Invoked when the server is stopping. */ - public void stop(); + void stop(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/UsageDataPublishedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/UsageDataPublishedListener.java index 365d056..a6b8d38 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/UsageDataPublishedListener.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/UsageDataPublishedListener.java
@@ -35,10 +35,10 @@ } public interface MetaData { - public String getName(); - public String getUnitName(); - public String getUnitSymbol(); - public String getDescription(); + String getName(); + String getUnitName(); + String getUnitSymbol(); + String getDescription(); } void onUsageDataPublished(Event event);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java index 2243786..4688f7c 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java
@@ -17,5 +17,5 @@ /** Handle for registered information. */ public interface RegistrationHandle { /** Delete this registration. */ - public void remove(); + void remove(); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java index 7284296..ef38303 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java
@@ -18,7 +18,7 @@ import com.google.inject.Provider; public interface ReloadableRegistrationHandle<T> extends RegistrationHandle { - public Key<T> getKey(); + Key<T> getKey(); - public RegistrationHandle replace(Key<T> key, Provider<T> item); + RegistrationHandle replace(Key<T> key, Provider<T> item); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java index f95161d..3b32829 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ETagView.java
@@ -18,5 +18,5 @@ * A view which may change, although the underlying resource did not change */ public interface ETagView<R extends RestResource> extends RestReadView<R> { - public String getETag(R rsrc); + String getETag(R rsrc); }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestResource.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestResource.java index 8032531..29c824f 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestResource.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestResource.java
@@ -30,11 +30,11 @@ * @return time for the Last-Modified header. HTTP truncates the header * value to seconds. */ - public Timestamp getLastModified(); + Timestamp getLastModified(); } /** A resource with an ETag. */ public interface HasETag { - public String getETag(); + String getETag(); } }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java index 63172ce..fb81e0c 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java
@@ -29,7 +29,7 @@ * assumed unavailable and not presented. This is usually the same as * {@code setVisible(false)}. */ - public Description getDescription(R resource); + Description getDescription(R resource); /** Describes an action invokable through the web interface. */ public static class Description {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandFilter.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandFilter.java index 05f41d4..4b67260 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandFilter.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandFilter.java
@@ -15,5 +15,5 @@ package com.google.gwtexpui.globalkey.client; public interface KeyCommandFilter { - public boolean include(KeyCommand key); + boolean include(KeyCommand key); }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/FindReplace.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/FindReplace.java index f7bc907..487e613 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/FindReplace.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/FindReplace.java
@@ -22,7 +22,7 @@ * @return regular expression to match substrings with; should be treated as * immutable. */ - public RegExp pattern(); + RegExp pattern(); /** * Find and replace a single instance of this pattern in an input. @@ -36,5 +36,5 @@ * @return result of regular expression replacement. * @throws IllegalArgumentException if the input could not be safely sanitized. */ - public String replace(String input); + String replace(String input); }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheHeaders.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheHeaders.java index 950400a..d94f243 100644 --- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheHeaders.java +++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheHeaders.java
@@ -129,9 +129,8 @@ } public static boolean hasCacheHeader(HttpServletResponse res) { - return res.getHeader("Cache-Control") != null - || res.getHeader("Expires") != null - || "no-cache".equals(res.getHeader("Pragma")); + return res.containsHeader("Cache-Control") + || res.containsHeader("Expires"); } private static void cache(HttpServletResponse res,
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 a5a02cd..eca28a0 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
@@ -19,92 +19,92 @@ public interface Resources extends ClientBundle { @Source("addFileComment.png") - public ImageResource addFileComment(); + ImageResource addFileComment(); @Source("arrowDown.png") - public ImageResource arrowDown(); + ImageResource arrowDown(); @Source("arrowRight.png") - public ImageResource arrowRight(); + ImageResource arrowRight(); @Source("arrowUp.png") - public ImageResource arrowUp(); + ImageResource arrowUp(); @Source("deleteHover.png") - public ImageResource deleteHover(); + ImageResource deleteHover(); @Source("deleteNormal.png") - public ImageResource deleteNormal(); + ImageResource deleteNormal(); @Source("diffy26.png") - public ImageResource gerritAvatar26(); + ImageResource gerritAvatar26(); @Source("downloadIcon.png") - public ImageResource downloadIcon(); + ImageResource downloadIcon(); @Source("draftComments.png") - public ImageResource draftComments(); + ImageResource draftComments(); @Source("editText.png") - public ImageResource edit(); + ImageResource edit(); @Source("editUndo.png") - public ImageResource editUndo(); + ImageResource editUndo(); @Source("gear.png") - public ImageResource gear(); + ImageResource gear(); @Source("goNext.png") - public ImageResource goNext(); + ImageResource goNext(); @Source("goPrev.png") - public ImageResource goPrev(); + ImageResource goPrev(); @Source("goUp.png") - public ImageResource goUp(); + ImageResource goUp(); @Source("greenCheck.png") - public ImageResource greenCheck(); + ImageResource greenCheck(); @Source("info.png") - public ImageResource info(); + ImageResource info(); @Source("listAdd.png") - public ImageResource listAdd(); + ImageResource listAdd(); @Source("mediaFloppy.png") - public ImageResource save(); + ImageResource save(); @Source("merge.png") - public ImageResource merge(); + ImageResource merge(); @Source("queryIcon.png") - public ImageResource queryIcon(); + ImageResource queryIcon(); @Source("readOnly.png") - public ImageResource readOnly(); + ImageResource readOnly(); @Source("redNot.png") - public ImageResource redNot(); + ImageResource redNot(); @Source("sideBySideDiff.png") - public ImageResource sideBySideDiff(); + ImageResource sideBySideDiff(); @Source("starFilled.png") - public ImageResource starFilled(); + ImageResource starFilled(); @Source("starOpen.png") - public ImageResource starOpen(); + ImageResource starOpen(); @Source("undoNormal.png") - public ImageResource undoNormal(); + ImageResource undoNormal(); @Source("unifiedDiff.png") - public ImageResource unifiedDiff(); + ImageResource unifiedDiff(); @Source("warning.png") - public ImageResource warning(); + ImageResource warning(); @Source("question.png") - public ImageResource question(); + 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 cace7ad..c5639c1 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
@@ -30,6 +30,7 @@ import java.sql.Timestamp; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; @@ -84,6 +85,16 @@ return allLabels().keySet(); } + public final Set<Integer> removableReviewerIds() { + Set<Integer> removable = new HashSet<>(); + if (removableReviewers() != null) { + for (AccountInfo a : Natives.asList(removableReviewers())) { + removable.add(a._accountId()); + } + } + return removable; + } + public final native String id() /*-{ return this.id; }-*/; public final native String project() /*-{ return this.project; }-*/; public final native String branch() /*-{ return this.branch; }-*/;
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 d95f9ef..555e06e 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
@@ -34,6 +34,11 @@ // 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 size() { + return (long)_size(); + } + private final native double _size() /*-{ return this.size || 0; }-*/; + public final long sizeDelta() { return (long)_sizeDelta(); }
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 55ef892..750412d 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
@@ -36,6 +36,7 @@ public final native String allProjects() /*-{ return this.all_projects; }-*/; public final native String allUsers() /*-{ return this.all_users; }-*/; + public final native boolean docSearch() /*-{ return this.doc_search; }-*/; 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; }-*/;
diff --git a/gerrit-gwtui/gwt.defs b/gerrit-gwtui/gwt.defs index 783c343..88d603a 100644 --- a/gerrit-gwtui/gwt.defs +++ b/gerrit-gwtui/gwt.defs
@@ -70,7 +70,7 @@ strict = True, experimental_args = args, vm_args = GWT_JVM_ARGS, - visibility = ['//:eclipse'], + visibility = ['PUBLIC'], ) gwt_binary(
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 c77b71f..1c44ebc 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
@@ -14,6 +14,7 @@ package com.google.gerrit.client; +import com.google.gerrit.client.change.Resources; import com.google.gerrit.client.info.AccountInfo; import com.google.gerrit.client.info.AccountPreferencesInfo; import com.google.gerrit.reviewdb.client.Account; @@ -134,4 +135,19 @@ + NumberFormat.getFormat("#.0").format(bytes / Math.pow(1024, exp)) + " " + "KMGTPE".charAt(exp - 1) + "iB"; } + + public static String formatPercentage(long size, long delta) { + if (size == 0) { + return Resources.C.notAvailable(); + } + return (delta > 0 ? "+" : "-") + formatAbsPercentage(size, delta); + } + + public static String formatAbsPercentage(long size, long delta) { + if (size == 0) { + return Resources.C.notAvailable(); + } + int p = Math.abs(Math.round(delta * 100 / size)); + return p + "%"; + } }
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 560fa9e..139b9c0 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
@@ -25,6 +25,7 @@ import com.google.gerrit.client.admin.ProjectScreen; import com.google.gerrit.client.api.ApiGlue; import com.google.gerrit.client.api.PluginLoader; +import com.google.gerrit.client.change.LocalComments; import com.google.gerrit.client.changes.ChangeConstants; import com.google.gerrit.client.changes.ChangeListScreen; import com.google.gerrit.client.config.ConfigServerApi; @@ -117,6 +118,7 @@ private static AccountPreferencesInfo myPrefs; private static UrlAliasMatcher urlAliasMatcher; private static boolean hasDocumentation; + private static boolean docSearch; private static String docUrl; private static HostPageData.Theme myTheme; private static String defaultScreenToken; @@ -205,6 +207,9 @@ doSignIn(token); } else { view.setToken(token); + if (isSignedIn()) { + LocalComments.saveInlineComments(); + } body.setView(view); } } @@ -483,6 +488,7 @@ hasDocumentation = true; docUrl = du; } + docSearch = info.gerrit().docSearch(); } })); HostPageDataService hpd = GWT.create(HostPageDataService.class); @@ -914,6 +920,10 @@ urlAliasMatcher.updateUserAliases(myPrefs.urlAliases()); } + public static boolean hasDocSearch() { + return docSearch; + } + private static void getDocIndex(final AsyncCallback<DocInfo> cb) { RequestBuilder req = new RequestBuilder(RequestBuilder.HEAD, GWT.getHostPageBaseURL()
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 269999c..6802a0d 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
@@ -126,4 +126,7 @@ String stringListPanelDelete(); String stringListPanelUp(); String stringListPanelDown(); + + String searchDropdownChanges(); + String searchDropdownDoc(); }
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 fb74506..83736cd 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
@@ -109,3 +109,6 @@ stringListPanelDelete = Delete stringListPanelUp = Up stringListPanelDown = Down + +searchDropdownChanges = Changes +searchDropdownDoc = Docs
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java index 45b1d52..83a187b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
@@ -27,6 +27,7 @@ import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.SuggestBox; import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; import com.google.gwtexpui.globalkey.client.GlobalKey; @@ -34,6 +35,7 @@ class SearchPanel extends Composite { private final HintTextBox searchBox; + private final ListBox dropdown; private HandlerRegistration regFocus; SearchPanel() { @@ -54,6 +56,18 @@ } }); + if (Gerrit.hasDocSearch()) { + dropdown = new ListBox(); + dropdown.setStyleName("searchDropdown"); + dropdown.addItem(Gerrit.C.searchDropdownChanges()); + dropdown.addItem(Gerrit.C.searchDropdownDoc()); + dropdown.setVisibleItemCount(1); + dropdown.setSelectedIndex(0); + } else { + // Doc search is NOT available. + dropdown = null; + } + final SuggestBox suggestBox = new SuggestBox(new SearchSuggestOracle(), searchBox, suggestionDisplay); searchBox.setStyleName("searchTextBox"); @@ -70,6 +84,9 @@ }); body.add(suggestBox); + if (dropdown != null) { + body.add(dropdown); + } body.add(searchButton); } @@ -110,14 +127,23 @@ searchBox.setFocus(false); - if (query.matches("^[1-9][0-9]*$")) { - Gerrit.display(PageLinks.toChange(Change.Id.parse(query))); + if (dropdown != null + && dropdown.getSelectedValue().equals(Gerrit.C.searchDropdownDoc())) { + // doc + Gerrit.display(PageLinks.toDocumentationQuery(query)); } else { - Gerrit.display(PageLinks.toChangeQuery(query), QueryScreen.forQuery(query)); + // changes + if (query.matches("^[1-9][0-9]*$")) { + Gerrit.display(PageLinks.toChange(Change.Id.parse(query))); + } else { + Gerrit.display( + PageLinks.toChangeQuery(query), QueryScreen.forQuery(query)); + } } } - private static class MySuggestionDisplay extends SuggestBox.DefaultSuggestionDisplay { + private static class MySuggestionDisplay + extends SuggestBox.DefaultSuggestionDisplay { private boolean isSuggestionSelected; private MySuggestionDisplay() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java index f1ac27f..63ae5f4 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminResources.java
@@ -25,11 +25,11 @@ AdminCss css(); @Source("deleteNormal.png") - public ImageResource deleteNormal(); + ImageResource deleteNormal(); @Source("deleteHover.png") - public ImageResource deleteHover(); + ImageResource deleteHover(); @Source("undoNormal.png") - public ImageResource undoNormal(); + ImageResource undoNormal(); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java index b86d0a8..0bf1b4b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -231,19 +231,20 @@ private Set<String> getDiffs(ProjectAccess wantedAccess, ProjectAccess newAccess) { - final List<AccessSection> wantedSections = + List<AccessSection> wantedSections = mergeSections(removeEmptyPermissionsAndSections(wantedAccess.getLocal())); - final HashSet<AccessSection> same = new HashSet<>(wantedSections); - final HashSet<AccessSection> different = - new HashSet<>(wantedSections.size() - + newAccess.getLocal().size()); + List<AccessSection> newSections = + removeEmptyPermissionsAndSections(newAccess.getLocal()); + HashSet<AccessSection> same = new HashSet<>(wantedSections); + HashSet<AccessSection> different = + new HashSet<>(wantedSections.size() + newSections.size()); different.addAll(wantedSections); - different.addAll(newAccess.getLocal()); - same.retainAll(newAccess.getLocal()); + different.addAll(newSections); + same.retainAll(newSections); different.removeAll(same); - final Set<String> differentNames = new HashSet<>(); - for (final AccessSection s : different) { + Set<String> differentNames = new HashSet<>(); + for (AccessSection s : different) { differentNames.add(s.getName()); } return differentNames;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java index f5b26d1..63de389 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
@@ -16,7 +16,7 @@ import com.google.gwt.i18n.client.Constants; -interface ChangeConstants extends Constants { +public interface ChangeConstants extends Constants { String previousChange(); String nextChange(); String openChange();
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 2673f49..60d21ba 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
@@ -146,6 +146,7 @@ private boolean hasDraftComments; private CommentLinkProcessor commentLinkProcessor; private EditInfo edit; + private LocalComments lc; private List<HandlerRegistration> handlers = new ArrayList<>(4); private UpdateCheckTimer updateCheck; @@ -232,6 +233,7 @@ this.revision = normalize(revision); this.openReplyBox = openReplyBox; this.fileTableMode = mode; + this.lc = new LocalComments(changeId); add(uiBinder.createAndBindUi(this)); } @@ -363,7 +365,7 @@ .openDiv() .append(Gerrit.info().change().replyLabel()) .closeDiv()); - if (hasDraftComments) { + if (hasDraftComments || lc.hasReplyComment()) { reply.setStyleName(style.highlight()); } reply.setVisible(true);
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 d1ca517..01337ea 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
@@ -15,7 +15,9 @@ package com.google.gerrit.client.change; import static com.google.gerrit.client.FormatUtil.formatAbsBytes; +import static com.google.gerrit.client.FormatUtil.formatAbsPercentage; import static com.google.gerrit.client.FormatUtil.formatBytes; +import static com.google.gerrit.client.FormatUtil.formatPercentage; import com.google.gerrit.client.Dispatcher; import com.google.gerrit.client.Gerrit; @@ -464,6 +466,7 @@ private boolean hasNonBinaryFile; private int inserted; private int deleted; + private long binOldSize; private long bytesInserted; private long bytesDeleted; @@ -520,6 +523,7 @@ private void computeInsertedDeleted() { inserted = 0; deleted = 0; + binOldSize = 0; bytesInserted = 0; bytesDeleted = 0; for (int i = 0; i < list.length(); i++) { @@ -531,6 +535,7 @@ deleted += info.linesDeleted(); } else { hasBinaryFile = true; + binOldSize += info.size() - info.sizeDelta(); if (info.sizeDelta() >= 0) { bytesInserted += info.sizeDelta(); } else { @@ -771,6 +776,12 @@ } } else if (info.binary()) { sb.append(formatBytes(info.sizeDelta())); + long oldSize = info.size() - info.sizeDelta(); + if (oldSize != 0) { + sb.append(" (") + .append(formatPercentage(oldSize, info.sizeDelta())) + .append(")"); + } } sb.closeTd(); } @@ -827,8 +838,17 @@ if (hasNonBinaryFile) { sb.br(); } - sb.append(Util.M.patchTableSize_ModifyBinaryFiles( - formatAbsBytes(bytesInserted), formatAbsBytes(bytesDeleted))); + if (binOldSize != 0) { + sb.append(Util.M.patchTableSize_ModifyBinaryFilesWithPercentages( + formatAbsBytes(bytesInserted), + formatAbsPercentage(binOldSize, bytesInserted), + formatAbsBytes(bytesDeleted), + formatAbsPercentage(binOldSize, bytesDeleted))); + } else { + sb.append(Util.M.patchTableSize_ModifyBinaryFiles( + formatAbsBytes(bytesInserted), + formatAbsBytes(bytesDeleted))); + } } sb.closeTh();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java index 4139348..bba63c9 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -48,20 +48,26 @@ /** Displays a table of label and reviewer scores. */ class Labels extends Grid { private static final String DATA_ID = "data-id"; - private static final String REMOVE; + private static final String DATA_VOTE = "data-vote"; + private static final String REMOVE_REVIEWER; + private static final String REMOVE_VOTE; static { - REMOVE = DOM.createUniqueId().replace('-', '_'); - init(REMOVE); + REMOVE_REVIEWER = DOM.createUniqueId().replace('-', '_'); + REMOVE_VOTE = DOM.createUniqueId().replace('-', '_'); + init(REMOVE_REVIEWER, REMOVE_VOTE); } - private static final native void init(String r) /*-{ + private static final native void init(String r, String v) /*-{ $wnd[r] = $entry(function(e) { - @com.google.gerrit.client.change.Labels::onRemove(Lcom/google/gwt/dom/client/NativeEvent;)(e) + @com.google.gerrit.client.change.Labels::onRemoveReviewer(Lcom/google/gwt/dom/client/NativeEvent;)(e) + }); + $wnd[v] = $entry(function(e) { + @com.google.gerrit.client.change.Labels::onRemoveVote(Lcom/google/gwt/dom/client/NativeEvent;)(e) }); }-*/; - private static void onRemove(NativeEvent event) { + private static void onRemoveReviewer(NativeEvent event) { Integer user = getDataId(event); if (user != null) { final ChangeScreen screen = ChangeScreen.get(event); @@ -77,6 +83,23 @@ } } + private static void onRemoveVote(NativeEvent event) { + Integer user = getDataId(event); + String vote = getVoteId(event); + if (user != null && vote != null) { + final ChangeScreen screen = ChangeScreen.get(event); + ChangeApi.vote(screen.getChangeId().get(), user, vote).delete( + new GerritCallback<JavaScriptObject>() { + @Override + public void onSuccess(JavaScriptObject result) { + if (screen.isCurrentView()) { + Gerrit.display(PageLinks.toChange(screen.getChangeId())); + } + } + }); + } + } + private static Integer getDataId(NativeEvent event) { Element e = event.getEventTarget().cast(); while (e != null) { @@ -89,6 +112,18 @@ return null; } + private static String getVoteId(NativeEvent event) { + Element e = event.getEventTarget().cast(); + while (e != null) { + String v = e.getAttribute(DATA_VOTE); + if (!v.isEmpty()) { + return v; + } + e = e.getParentElement(); + } + return null; + } + private ChangeScreen.Style style; void init(ChangeScreen.Style style) { @@ -97,6 +132,7 @@ void set(ChangeInfo info) { List<String> names = new ArrayList<>(info.labels()); + Set<Integer> removable = info.removableReviewerIds(); Collections.sort(names); resize(names.size(), 2); @@ -106,14 +142,14 @@ LabelInfo label = info.label(name); setText(row, 0, name); if (label.all() != null) { - setWidget(row, 1, renderUsers(label)); + setWidget(row, 1, renderUsers(label, removable)); } getCellFormatter().setStyleName(row, 0, style.labelName()); getCellFormatter().addStyleName(row, 0, getStyleForLabel(label)); } } - private Widget renderUsers(LabelInfo label) { + private Widget renderUsers(LabelInfo label, Set<Integer> removable) { Map<Integer, List<ApprovalInfo>> m = new HashMap<>(4); int approved = 0; int rejected = 0; @@ -150,8 +186,8 @@ html.setStyleName(style.label_reject()); } html.append(val).append(" "); - html.append(formatUserList(style, m.get(v), - Collections.<Integer> emptySet(), null)); + html.append(formatUserList(style, m.get(v), removable, + label.name(), null)); html.closeSpan(); } return html.toBlockWidget(); @@ -198,6 +234,7 @@ static SafeHtml formatUserList(ChangeScreen.Style style, Collection<? extends AccountInfo> in, Set<Integer> removable, + String label, Map<Integer, VotableInfo> votable) { List<AccountInfo> users = new ArrayList<>(in); Collections.sort(users, new Comparator<AccountInfo>() { @@ -257,6 +294,9 @@ .setAttribute(DATA_ID, ai._accountId()) .setAttribute("title", getTitle(ai, votableCategories)) .setStyleName(style.label_user()); + if (label != null) { + html.setAttribute(DATA_VOTE, label); + } if (img != null) { html.openElement("img") .setStyleName(style.avatar()) @@ -271,10 +311,15 @@ } html.append(name); if (removable.contains(ai._accountId())) { - html.openElement("button") - .setAttribute("title", Util.M.removeReviewer(name)) - .setAttribute("onclick", REMOVE + "(event)") - .append("×") + html.openElement("button"); + if (label != null) { + html.setAttribute("title", Util.M.removeVote(label)) + .setAttribute("onclick", REMOVE_VOTE + "(event)"); + } else { + html.setAttribute("title", Util.M.removeReviewer(name)) + .setAttribute("onclick", REMOVE_REVIEWER + "(event)"); + } + html.append("×") .closeElement("button"); } html.closeSpan();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LocalComments.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LocalComments.java new file mode 100644 index 0000000..a0480d0 --- /dev/null +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LocalComments.java
@@ -0,0 +1,238 @@ +// 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.change; + +import com.google.gerrit.client.changes.CommentApi; +import com.google.gerrit.client.changes.CommentInfo; +import com.google.gerrit.client.diff.CommentRange; +import com.google.gerrit.client.rpc.GerritCallback; +import com.google.gerrit.extensions.client.Side; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gwt.storage.client.Storage; +import com.google.gwt.user.client.Cookies; + +import java.util.ArrayList; +import java.util.Collection; + +public class LocalComments { + private final Change.Id changeId; + private final PatchSet.Id psId; + private final StorageBackend storage; + + private static class InlineComment { + final PatchSet.Id psId; + final CommentInfo commentInfo; + + InlineComment(PatchSet.Id psId, CommentInfo commentInfo) { + this.psId = psId; + this.commentInfo = commentInfo; + } + } + + private static class StorageBackend { + private final Storage storageBackend; + + StorageBackend() { + storageBackend = (Storage.isLocalStorageSupported()) + ? Storage.getLocalStorageIfSupported() + : Storage.getSessionStorageIfSupported(); + } + + String getItem(String key) { + if (storageBackend == null) { + return Cookies.getCookie(key); + } + return storageBackend.getItem(key); + } + + void setItem(String key, String value) { + if (storageBackend == null) { + Cookies.setCookie(key, value); + return; + } + storageBackend.setItem(key, value); + } + + void removeItem(String key) { + if (storageBackend == null) { + Cookies.removeCookie(key); + return; + } + storageBackend.removeItem(key); + } + + Collection<String> getKeys() { + if (storageBackend == null) { + return Cookies.getCookieNames(); + } + ArrayList<String> result = new ArrayList<>(storageBackend.getLength()); + for (int i = 0; i < storageBackend.getLength(); i++) { + result.add(storageBackend.key(i)); + } + return result; + } + } + + public LocalComments(Change.Id changeId) { + this.changeId = changeId; + this.psId = null; + this.storage = new StorageBackend(); + } + + public LocalComments(PatchSet.Id psId) { + this.changeId = psId.getParentKey(); + this.psId = psId; + this.storage = new StorageBackend(); + } + + public String getReplyComment() { + String comment = storage.getItem(getReplyCommentName()); + storage.removeItem(getReplyCommentName()); + return comment; + } + + public void setReplyComment(String comment) { + storage.setItem(getReplyCommentName(), comment.trim()); + } + + public boolean hasReplyComment() { + return storage.getKeys().contains(getReplyCommentName()); + } + + public void removeReplyComment() { + if (hasReplyComment()) { + storage.removeItem(getReplyCommentName()); + } + } + + private String getReplyCommentName() { + return "savedReplyComment-" + changeId.toString(); + } + + public static void saveInlineComments() { + final StorageBackend storage = new StorageBackend(); + for (final String cookie : storage.getKeys()) { + if (isInlineComment(cookie)) { + GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() { + @Override + public void onSuccess(CommentInfo result) { + storage.removeItem(cookie); + } + }; + InlineComment input = getInlineComment(cookie); + if (input.commentInfo.id() == null) { + CommentApi.createDraft(input.psId, input.commentInfo, cb); + } else { + CommentApi.updateDraft(input.psId, input.commentInfo.id(), + input.commentInfo, cb); + } + } + } + } + + public void setInlineComment(CommentInfo comment) { + String name = getInlineCommentName(comment); + if (name == null) { + // Failed to get the store key -- so we can't continue. + return; + } + storage.setItem(name, comment.message().trim()); + } + + public boolean hasInlineComments() { + for (String cookie : storage.getKeys()) { + if (isInlineComment(cookie)) { + return true; + } + } + return false; + } + + private static boolean isInlineComment(String key) { + return key.startsWith("patchCommentEdit-") || key.startsWith("patchReply-") + || key.startsWith("patchComment-"); + } + + private static InlineComment getInlineComment(String key) { + String path; + Side side = Side.PARENT; + int line = 0; + CommentRange range; + StorageBackend storage = new StorageBackend(); + + String[] elements = key.split("-"); + int offset = 1; + if (key.startsWith("patchReply-") || key.startsWith("patchCommentEdit-")) { + offset = 2; + } + Change.Id changeId = new Change.Id(Integer.parseInt(elements[offset + 0])); + PatchSet.Id psId = + new PatchSet.Id(changeId, Integer.parseInt(elements[offset + 1])); + path = atob(elements[offset + 2]); + side = (Side.PARENT.toString() == elements[offset + 3]) ? Side.PARENT + : Side.REVISION; + range = null; + if (elements[offset + 4].startsWith("R")) { + String rangeStart = elements[offset + 4].substring(1); + String rangeEnd = elements[offset + 5]; + String[] split = rangeStart.split(","); + int sl = Integer.parseInt(split[0]); + int sc = Integer.parseInt(split[1]); + split = rangeEnd.split(","); + int el = Integer.parseInt(split[0]); + int ec = Integer.parseInt(split[1]); + range = CommentRange.create(sl, sc, el, ec); + line = sl; + } else { + line = Integer.parseInt(elements[offset + 4]); + } + CommentInfo info = CommentInfo.create(path, side, line, range); + info.message(storage.getItem(key)); + if (key.startsWith("patchReply-")) { + info.inReplyTo(elements[1]); + } else if (key.startsWith("patchCommentEdit-")) { + info.id(elements[1]); + } + InlineComment inlineComment = new InlineComment(psId, info); + return inlineComment; + } + + private String getInlineCommentName(CommentInfo comment) { + if (psId == null) { + return null; + } + String result = "patchComment-"; + if (comment.id() != null) { + result = "patchCommentEdit-" + comment.id() + "-"; + } else if (comment.inReplyTo() != null) { + result = "patchReply-" + comment.inReplyTo() + "-"; + } + result += changeId + "-" + psId.getId() + "-" + btoa(comment.path()) + "-" + + comment.side() + "-"; + if (comment.hasRange()) { + result += "R" + comment.range().startLine() + "," + + comment.range().startCharacter() + "-" + comment.range().endLine() + + "," + comment.range().endCharacter(); + } else { + result += comment.line(); + } + return result; + } + + private static native String btoa(String a) /*-{ return btoa(a); }-*/; + + private static native String atob(String b) /*-{ return atob(b); }-*/; +}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java index 2ec4b6b..133d303 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -28,6 +28,7 @@ import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.NativeMap; import com.google.gerrit.client.rpc.Natives; +import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.ui.CommentLinkProcessor; import com.google.gerrit.common.PageLinks; import com.google.gerrit.common.data.LabelValue; @@ -90,6 +91,7 @@ private final String revision; private ReviewInput in = ReviewInput.create(); private int labelHelpColumn; + private LocalComments lc; @UiField Styles style; @UiField TextArea message; @@ -109,6 +111,7 @@ this.clp = clp; this.psId = psId; this.revision = revision; + this.lc = new LocalComments(psId.getParentKey()); initWidget(uiBinder.createAndBindUi(this)); List<String> names = new ArrayList<>(permitted.keySet()); @@ -140,6 +143,10 @@ protected void onLoad() { commentsPanel.setVisible(false); post.setEnabled(false); + if (lc.hasReplyComment()) { + message.setText(lc.getReplyComment()); + lc.removeReplyComment(); + } ChangeApi.drafts(psId.getParentKey().get()) .get(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() { @Override @@ -201,6 +208,13 @@ psId.getParentKey(), String.valueOf(psId.get()))); } + @Override + public void onFailure(final Throwable caught) { + if (RestApi.isNotSignedIn(caught)) { + lc.setReplyComment(message.getText()); + } + super.onFailure(caught); + } }); hide(); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml index 52f6b6a..6903b91 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
@@ -38,7 +38,10 @@ position: absolute; bottom: 5px; right: 5px; + background-color: #eee; + background-image: -webkit-linear-gradient(top, #eee, #eee); } + .cancel div { color: #444; } .comments { max-height: 275px; width: 526px;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java index 3937ade..ae648dc 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -17,6 +17,7 @@ import com.google.gerrit.client.ConfirmationCallback; import com.google.gerrit.client.ConfirmationDialog; import com.google.gerrit.client.Gerrit; +import com.google.gerrit.client.NotSignedInDialog; import com.google.gerrit.client.changes.ChangeApi; import com.google.gerrit.client.changes.Util; import com.google.gerrit.client.info.AccountInfo; @@ -50,7 +51,6 @@ import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -177,10 +177,14 @@ @Override public void onFailure(Throwable err) { - UIObject.setVisible(error, true); - error.setInnerText(err instanceof StatusCodeException - ? ((StatusCodeException) err).getEncodedResponse() - : err.getMessage()); + if (isSigninFailure(err)) { + new NotSignedInDialog().center(); + } else { + UIObject.setVisible(error, true); + error.setInnerText(err instanceof StatusCodeException + ? ((StatusCodeException) err).getEncodedResponse() + : err.getMessage()); + } } }); } @@ -209,20 +213,13 @@ cc.remove(i); } cc.remove(info.owner()._accountId()); - - Set<Integer> removable = new HashSet<>(); - if (info.removableReviewers() != null) { - for (AccountInfo a : Natives.asList(info.removableReviewers())) { - removable.add(a._accountId()); - } - } - + Set<Integer> removable = info.removableReviewerIds(); Map<Integer, VotableInfo> votable = votable(info); SafeHtml rHtml = Labels.formatUserList(style, - r.values(), removable, votable); + r.values(), removable, null, votable); SafeHtml ccHtml = Labels.formatUserList(style, - cc.values(), removable, votable); + cc.values(), removable, null, votable); reviewersText.setInnerSafeHtml(rHtml); ccText.setInnerSafeHtml(ccHtml);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java index 9cafeec..8314e3e 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -155,6 +155,10 @@ .addParameter("n", n); } + public static RestApi vote(int id, int reviewer, String vote) { + return reviewer(id, reviewer).view("votes").id(vote); + } + public static RestApi reviewer(int id, int reviewer) { return change(id).view("reviewers").id(reviewer); }
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 ef74a65..eb09657 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
@@ -36,11 +36,14 @@ String patchTableSize_Modify(int insertions, int deletions); String patchTableSize_ModifyBinaryFiles(String bytesInserted, String bytesDeleted); + String patchTableSize_ModifyBinaryFilesWithPercentages(String bytesInserted, + String percentageInserted, String bytesDeleted, String percentageDeleted); String patchTableSize_LongModify(int insertions, int deletions); String patchTableSize_Lines(@PluralCount int insertions); String removeHashtag(String name); String removeReviewer(String fullName); + String removeVote(String label); String messageWrittenOn(String date); String renamedFrom(String sourcePath);
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 67ef2c3..b3c3980 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
@@ -18,11 +18,13 @@ patchTableDrafts = {0} drafts patchTableSize_Modify = +{0}, -{1} patchTableSize_ModifyBinaryFiles = +{0}, -{1} +patchTableSize_ModifyBinaryFilesWithPercentages = +{0} (+{1}), -{2} (-{3}) patchTableSize_LongModify = {0} inserted, {1} deleted patchTableSize_Lines = {0} lines removeHashtag = Remove hashtag {0} removeReviewer = Remove reviewer {0} +removeVote = Remove vote {0} messageWrittenOn = on {0} renamedFrom = renamed from {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java index be64a3d..8750389 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
@@ -38,7 +38,7 @@ /** Handler that can receive notifications of a change's starred status. */ public static interface ChangeStarHandler { - public void onChangeStar(ChangeStarEvent event); + void onChangeStar(ChangeStarEvent event); } /** Event fired when a star changes status. The new status is reported. */
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java index 21b2f50..46c37ed 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
@@ -15,10 +15,12 @@ package com.google.gerrit.client.diff; import com.google.gerrit.client.FormatUtil; +import com.google.gerrit.client.change.LocalComments; import com.google.gerrit.client.changes.CommentApi; import com.google.gerrit.client.changes.CommentInfo; import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.GerritCallback; +import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.ui.CommentLinkProcessor; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gwt.core.client.GWT; @@ -289,6 +291,7 @@ enableEdit(false); pendingGroup = group; + final LocalComments lc = new LocalComments(psId); GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() { @Override public void onSuccess(CommentInfo result) { @@ -306,6 +309,11 @@ public void onFailure(Throwable e) { enableEdit(true); pendingGroup = null; + if (RestApi.isNotSignedIn(e)) { + CommentInfo saved = CommentInfo.copy(comment); + saved.message(editArea.getValue().trim()); + lc.setInlineComment(saved); + } super.onFailure(e); } };
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 91baf90..f363f9b 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
@@ -20,7 +20,6 @@ import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_LEADING_AND_TRAILING; import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_NONE; import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_TRAILING; - import static com.google.gwt.event.dom.client.KeyCodes.KEY_ESCAPE; import com.google.gerrit.client.Gerrit;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css index 383f278..26f8ff5 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css
@@ -27,12 +27,14 @@ .delete { background-color: #faa; + min-width: 12px; } .insert { background-color: #9f9; + min-width: 12px; } .edit { - border-left: 3px solid #faa; - width: 2px !important; + border-left: 6px solid #faa; + width: 6px !important; background-color: #9f9; }
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 0987e83..3c9a8b9 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
@@ -307,7 +307,15 @@ } .searchPanel .searchTextBox { font-size: 9pt; - margin: 5.286px 3px 0 0; + margin: 8.286px 3px 0 0; +} +.searchPanel .searchDropdown { + font-size: 8pt; + border: 2px solid; + border-color: rgba(0, 0, 0, 0.15); + height: 16px; + border-radius: 2px; + box-sizing: content-box; } .searchPanel .searchButton { text-align: center;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java index 81494c0..3cf29e5 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
@@ -15,6 +15,7 @@ package com.google.gerrit.client.patches; import com.google.gerrit.client.Gerrit; +import com.google.gerrit.client.change.LocalComments; import com.google.gerrit.client.changes.CommentApi; import com.google.gerrit.client.changes.CommentInfo; import com.google.gerrit.client.rpc.GerritCallback; @@ -243,6 +244,7 @@ discard.setEnabled(false); final PatchSet.Id psId = comment.getKey().getParentKey().getParentKey(); + final LocalComments lc = new LocalComments(psId); final boolean wasNew = isNew(); GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() { @Override @@ -264,6 +266,7 @@ save.setEnabled(true); cancel.setEnabled(true); discard.setEnabled(true); + lc.setInlineComment(toInput(comment)); super.onFailure(caught); onSave.onFailure(caught); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java index bccd237..daac7cf 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -38,15 +38,10 @@ } public static void showFailure(Throwable caught) { - if (isNotSignedIn(caught) || isInvalidXSRF(caught)) { + if (isSigninFailure(caught)) { new NotSignedInDialog().center(); - } else if (isNoSuchEntity(caught)) { - if (Gerrit.isSignedIn()) { - new ErrorDialog(Gerrit.C.notFoundBody()).center(); - } else { - new NotSignedInDialog().center(); - } + new ErrorDialog(Gerrit.C.notFoundBody()).center(); } else if (isInactiveAccount(caught)) { new ErrorDialog(Gerrit.C.inactiveAccountBody()).center(); @@ -77,12 +72,20 @@ } } - private static boolean isInvalidXSRF(final Throwable caught) { + public static boolean isSigninFailure(Throwable caught) { + if (isNotSignedIn(caught) || isInvalidXSRF(caught) || + (isNoSuchEntity(caught) && !Gerrit.isSignedIn())) { + return true; + } + return false; + } + + protected static boolean isInvalidXSRF(final Throwable caught) { return caught instanceof InvocationException && caught.getMessage().equals(JsonConstants.ERROR_INVALID_XSRF); } - private static boolean isNotSignedIn(Throwable caught) { + protected static boolean isNotSignedIn(Throwable caught) { return RestApi.isNotSignedIn(caught) || (caught instanceof RemoteJsonException && caught.getMessage().equals(NotSignedInException.MESSAGE)); @@ -99,17 +102,17 @@ && caught.getMessage().startsWith(InactiveAccountException.MESSAGE); } - private static boolean isNoSuchAccount(final Throwable caught) { + protected static boolean isNoSuchAccount(final Throwable caught) { return caught instanceof RemoteJsonException && caught.getMessage().startsWith(NoSuchAccountException.MESSAGE); } - private static boolean isNameAlreadyUsed(final Throwable caught) { + protected static boolean isNameAlreadyUsed(final Throwable caught) { return caught instanceof RemoteJsonException && caught.getMessage().startsWith(NameAlreadyUsedException.MESSAGE); } - private static boolean isNoSuchGroup(final Throwable caught) { + protected static boolean isNoSuchGroup(final Throwable caught) { return caught instanceof RemoteJsonException && caught.getMessage().startsWith(NoSuchGroupException.MESSAGE); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java index 8128afe..97ed559 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/ScreenLoadCallback.java
@@ -44,12 +44,10 @@ @Override public void onFailure(final Throwable caught) { - if (isNoSuchEntity(caught)) { - if (Gerrit.isSignedIn()) { - Gerrit.display(screen.getToken(), new NotFoundScreen()); - } else { - new NotSignedInDialog().center(); - } + if (isSigninFailure(caught)) { + new NotSignedInDialog().center(); + } else if (isNoSuchEntity(caught)) { + Gerrit.display(screen.getToken(), new NotFoundScreen()); } else { super.onFailure(caught); }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java index 8b58403..36708dc 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectListPopup.java
@@ -43,7 +43,8 @@ private HorizontalPanel filterPanel; private String match; private Query query; - private Button close; + private Button closeTop; + private Button closeBottom; private ScrollPanel sp; private PopupPanel.PositionCallback popupPosition; private int preferredTop; @@ -55,10 +56,11 @@ createWidgets(popupText, currentPageLink); final FlowPanel pfp = new FlowPanel(); pfp.add(filterPanel); + pfp.add(closeTop); sp = new ScrollPanel(projectsTab); sp.setSize("100%", "100%"); pfp.add(sp); - pfp.add(close); + pfp.add(closeBottom); popup.setWidget(pfp); popup.setHeight("100%"); popupPosition = getPositionCallback(); @@ -147,17 +149,23 @@ }; projectsTab.setSavePointerId(currentPageLink); - close = new Button(Util.C.projectsClose()); + closeTop = createCloseButton(); + closeBottom = createCloseButton(); + + popup = new DialogBox(); + popup.setModal(false); + popup.setText(popupText); + } + + private Button createCloseButton() { + Button close = new Button(Util.C.projectsClose()); close.addClickHandler(new ClickHandler() { @Override public void onClick(final ClickEvent event) { closePopup(); } }); - - popup = new DialogBox(); - popup.setModal(false); - popup.setText(popupText); + return close; } public void displayPopup() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ScreenLoadHandler.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ScreenLoadHandler.java index a91becf..9a5eb03 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ScreenLoadHandler.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ScreenLoadHandler.java
@@ -17,5 +17,5 @@ import com.google.gwt.event.shared.EventHandler; public interface ScreenLoadHandler extends EventHandler { - public void onScreenLoad(ScreenLoadEvent event); + void onScreenLoad(ScreenLoadEvent event); }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java index 639e5e7..d0c6f9d 100644 --- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java +++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -401,23 +401,23 @@ } public interface EventHandler { - public void handle(CodeMirror instance, NativeEvent event); + void handle(CodeMirror instance, NativeEvent event); } public interface RenderLineHandler { - public void handle(CodeMirror instance, LineHandle handle, Element element); + void handle(CodeMirror instance, LineHandle handle, Element element); } public interface GutterClickHandler { - public void handle(CodeMirror instance, int line, String gutter, + void handle(CodeMirror instance, int line, String gutter, NativeEvent clickEvent); } public interface BeforeSelectionChangeHandler { - public void handle(CodeMirror instance, Pos anchor, Pos head); + void handle(CodeMirror instance, Pos anchor, Pos head); } public interface ChangesHandler { - public void handle(CodeMirror instance); + void handle(CodeMirror instance); } }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css b/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css index 6ce70db..4b97da1 100644 --- a/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css +++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/style.css
@@ -18,6 +18,7 @@ @external .CodeMirror-linenumber; @external .CodeMirror-overlayscroll-horizontal; @external .CodeMirror-overlayscroll-vertical; +@external .CodeMirror-scrollbar-filler; @external .cm-tab; @external .cm-searching; @external .cm-trailingspace; @@ -38,7 +39,17 @@ .CodeMirror-overlayscroll-vertical div { min-height: 25px; } - +/* Ensure the scrollbars are not too narrow */ +.CodeMirror-overlayscroll-horizontal { + min-height: 12px; +} +.CodeMirror-overlayscroll-vertical { + min-width: 12px; +} +.CodeMirror-scrollbar-filler { + min-height: 12px; + min-width: 12px; +} /* Stack the scrollbar so annotations can receive clicks. */ .CodeMirror-overlayscroll-vertical { z-index: inherit;
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 index 0f659c9..04dccdb 100644 --- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java +++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java
@@ -15,6 +15,7 @@ package com.google.gerrit.client; import static com.google.gerrit.client.FormatUtil.formatBytes; +import static com.google.gerrit.client.FormatUtil.formatPercentage; import static org.junit.Assert.assertEquals; import com.googlecode.gwt.test.GwtModule; @@ -45,4 +46,17 @@ assertEquals("-27 B", formatBytes(-27)); assertEquals("-1.7 MiB", formatBytes(-1728)); } + + @Test + public void testFormatPercentage() { + assertEquals("N/A", formatPercentage(0, 10)); + assertEquals("0%", formatPercentage(100, 0)); + assertEquals("+25%", formatPercentage(100, 25)); + assertEquals("-25%", formatPercentage(100, -25)); + assertEquals("+50%", formatPercentage(100, 50)); + assertEquals("-50%", formatPercentage(100, -50)); + assertEquals("+100%", formatPercentage(100, 100)); + assertEquals("-100%", formatPercentage(100, -100)); + assertEquals("+500%", formatPercentage(100, 500)); + } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java index adfe86c..c1a0f44 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java
@@ -14,20 +14,36 @@ package com.google.gerrit.httpd; +import org.eclipse.jgit.lib.Config; + public class GerritOptions { private final boolean headless; private final boolean slave; + private final boolean enablePolyGerrit; + private final boolean forcePolyGerritDev; - public GerritOptions(boolean headless, boolean slave) { + public GerritOptions(Config cfg, boolean headless, boolean slave, + boolean forcePolyGerritDev) { this.headless = headless; this.slave = slave; + this.enablePolyGerrit = forcePolyGerritDev + || cfg.getBoolean("gerrit", null, "enablePolyGerrit", false); + this.forcePolyGerritDev = forcePolyGerritDev; } public boolean enableDefaultUi() { - return !headless; + return !headless && !enablePolyGerrit; } public boolean enableMasterFeatures() { return !slave; } + + public boolean enablePolyGerrit() { + return !headless && enablePolyGerrit; + } + + public boolean forcePolyGerritDev() { + return !headless && forcePolyGerritDev; + } }
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 89d62dd..13b1a48 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
@@ -23,15 +23,14 @@ import com.google.gerrit.server.AccessPath; import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.CurrentUser; -import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.cache.CacheModule; import com.google.gerrit.server.git.AsyncReceiveCommits; import com.google.gerrit.server.git.ChangeCache; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.ReceiveCommits; -import com.google.gerrit.server.git.ReceivePackInitializer; import com.google.gerrit.server.git.TagCache; import com.google.gerrit.server.git.TransferConfig; +import com.google.gerrit.server.git.UploadPackMetricsHook; import com.google.gerrit.server.git.VisibleRefFilter; import com.google.gerrit.server.git.validators.UploadValidators; import com.google.gerrit.server.project.NoSuchProjectException; @@ -50,8 +49,6 @@ import org.eclipse.jgit.http.server.resolver.AsIsFileService; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.PostReceiveHook; -import org.eclipse.jgit.transport.PostReceiveHookChain; import org.eclipse.jgit.transport.PreUploadHook; import org.eclipse.jgit.transport.PreUploadHookChain; import org.eclipse.jgit.transport.ReceivePack; @@ -200,11 +197,15 @@ static class UploadFactory implements UploadPackFactory<HttpServletRequest> { private final TransferConfig config; + private final UploadPackMetricsHook uploadMetrics; private final DynamicSet<PreUploadHook> preUploadHooks; @Inject - UploadFactory(TransferConfig tc, DynamicSet<PreUploadHook> preUploadHooks) { + UploadFactory(TransferConfig tc, + UploadPackMetricsHook uploadMetrics, + DynamicSet<PreUploadHook> preUploadHooks) { this.config = tc; + this.uploadMetrics = uploadMetrics; this.preUploadHooks = preUploadHooks; } @@ -215,6 +216,7 @@ up.setTimeout(config.getTimeout()); up.setPreUploadHook(PreUploadHookChain.newChain( Lists.newArrayList(preUploadHooks))); + up.setPostUploadHook(uploadMetrics); return up; } } @@ -273,18 +275,10 @@ static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> { private final AsyncReceiveCommits.Factory factory; - private final TransferConfig config; - private DynamicSet<ReceivePackInitializer> receivePackInitializers; - private DynamicSet<PostReceiveHook> postReceiveHooks; @Inject - ReceiveFactory(AsyncReceiveCommits.Factory factory, TransferConfig config, - DynamicSet<ReceivePackInitializer> receivePackInitializers, - DynamicSet<PostReceiveHook> postReceiveHooks) { + ReceiveFactory(AsyncReceiveCommits.Factory factory) { this.factory = factory; - this.config = config; - this.receivePackInitializers = receivePackInitializers; - this.postReceiveHooks = postReceiveHooks; } @Override @@ -297,24 +291,13 @@ throw new ServiceNotAuthorizedException(); } - final IdentifiedUser user = pc.getUser().asIdentifiedUser(); - final ReceiveCommits rc = factory.create(pc, db).getReceiveCommits(); + ReceiveCommits rc = factory.create(pc, db).getReceiveCommits(); + rc.init(); + ReceivePack rp = rc.getReceivePack(); - rp.setRefLogIdent(user.newRefLogIdent()); - rp.setTimeout(config.getTimeout()); - rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit()); - init(pc.getProject().getNameKey(), rp); - rp.setPostReceiveHook(PostReceiveHookChain.newChain( - Lists.newArrayList(postReceiveHooks))); req.setAttribute(ATT_RC, rc); return rp; } - - private void init(Project.NameKey project, ReceivePack rp) { - for (ReceivePackInitializer initializer : receivePackInitializers) { - initializer.init(project, rp); - } - } } static class DisabledReceiveFactory implements
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestMetrics.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestMetrics.java new file mode 100644 index 0000000..ec193c9 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestMetrics.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.httpd; + +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class RequestMetrics { + final Counter1<Integer> errors; + final Counter1<Integer> successes; + + @Inject + public RequestMetrics(MetricMaker metricMaker) { + errors = metricMaker.newCounter( + "http/server/error_count", + new Description("Rate of REST API error responses") + .setRate() + .setUnit("errors"), + Field.ofInteger("status", "HTTP status code")); + successes = metricMaker.newCounter( + "http/server/success_count", + new Description("Rate of REST API success responses") + .setRate() + .setUnit("successes"), + Field.ofInteger("status", "HTTP status code")); + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestMetricsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestMetricsFilter.java new file mode 100644 index 0000000..48b2a2f --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestMetricsFilter.java
@@ -0,0 +1,108 @@ +// 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.httpd; + +import com.google.inject.Inject; +import com.google.inject.Module; +import com.google.inject.Singleton; +import com.google.inject.servlet.ServletModule; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +@Singleton +public class RequestMetricsFilter implements Filter { + public static Module module() { + return new ServletModule() { + @Override + protected void configureServlets() { + filter("/*").through(RequestMetricsFilter.class); + } + }; + } + + private final RequestMetrics metrics; + + @Inject + RequestMetricsFilter(RequestMetrics metrics) { + this.metrics = metrics; + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + Response rsp = new Response((HttpServletResponse) response, metrics); + + chain.doFilter(request, rsp); + } + + @Override + public void init(FilterConfig cfg) throws ServletException { + } + + private static class Response extends HttpServletResponseWrapper { + private final RequestMetrics metrics; + + Response(HttpServletResponse response, RequestMetrics metrics) { + super(response); + this.metrics = metrics; + } + + @Override + public void sendError(int sc, String msg) throws IOException { + status(sc); + super.sendError(sc, msg); + } + + @Override + public void sendError(int sc) throws IOException { + status(sc); + super.sendError(sc); + } + + @Override + @Deprecated + public void setStatus(int sc, String sm) { + status(sc); + super.setStatus(sc, sm); + } + + @Override + public void setStatus(int sc) { + status(sc); + super.setStatus(sc); + } + + private void status(int sc) { + if (sc >= SC_BAD_REQUEST) { + metrics.errors.increment(sc); + } else { + metrics.successes.increment(sc); + } + } + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequireSslFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequireSslFilter.java index 92809c0..4cb8e92 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequireSslFilter.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequireSslFilter.java
@@ -34,11 +34,20 @@ /** Requires the connection to use SSL, redirects if not. */ @Singleton -class RequireSslFilter implements Filter { - static class Module extends ServletModule { +public class RequireSslFilter implements Filter { + public static class Module extends ServletModule { + private final boolean wantSsl; + + @Inject + Module(@Nullable @CanonicalWebUrl String canonicalUrl) { + this.wantSsl = canonicalUrl != null && canonicalUrl.startsWith("https:"); + } + @Override protected void configureServlets() { - filter("/*").through(RequireSslFilter.class); + if (wantSsl) { + filter("/*").through(RequireSslFilter.class); + } } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java index 7ecf2fa..d8935fd 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -23,7 +23,6 @@ import com.google.gerrit.httpd.raw.LegacyGerritServlet; import com.google.gerrit.httpd.raw.RobotsServlet; import com.google.gerrit.httpd.raw.SshInfoServlet; -import com.google.gerrit.httpd.raw.StaticModule; import com.google.gerrit.httpd.raw.ToolServlet; import com.google.gerrit.httpd.rpc.access.AccessRestApiServlet; import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet; @@ -65,6 +64,7 @@ bind(Key.get(CacheControlFilter.class)).in(SINGLETON); if (options.enableDefaultUi()) { + filter("/").through(XsrfCookieFilter.class); serve("/").with(HostPageServlet.class); serve("/Gerrit").with(LegacyGerritServlet.class); serve("/Gerrit/*").with(legacyGerritScreen()); @@ -106,8 +106,6 @@ filter("/Documentation/").through(QueryDocumentationFilter.class); serve("/robots.txt").with(RobotsServlet.class); - - install(new StaticModule()); } private Key<HttpServlet> notFound() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java index 9e425e9..422a83d 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -16,7 +16,6 @@ import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors; -import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.webui.WebUiPlugin; import com.google.gerrit.httpd.auth.become.BecomeAnyAccountModule; @@ -28,7 +27,6 @@ import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.server.RemotePeer; import com.google.gerrit.server.config.AuthConfig; -import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.GerritRequestModule; import com.google.gerrit.server.config.GitwebCgiConfig; import com.google.gerrit.server.git.AsyncReceiveCommits; @@ -42,17 +40,14 @@ public class WebModule extends LifecycleModule { private final AuthConfig authConfig; - private final boolean wantSSL; private final GitwebCgiConfig gitwebCgiConfig; private final GerritOptions options; @Inject WebModule(AuthConfig authConfig, - @CanonicalWebUrl @Nullable String canonicalUrl, GerritOptions options, GitwebCgiConfig gitwebCgiConfig) { this.authConfig = authConfig; - this.wantSSL = canonicalUrl != null && canonicalUrl.startsWith("https:"); this.options = options; this.gitwebCgiConfig = gitwebCgiConfig; } @@ -62,9 +57,6 @@ bind(RequestScopePropagator.class).to(GuiceRequestScopePropagator.class); bind(HttpRequestContext.class); - if (wantSSL) { - install(new RequireSslFilter.Module()); - } install(new RunAsFilter.Module()); installAuthModule();
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 b2d32fc..327aaa3 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
@@ -21,18 +21,18 @@ import com.google.gerrit.server.account.AuthResult; public interface WebSession { - public boolean isSignedIn(); - public String getXGerritAuth(); - public boolean isValidXGerritAuth(String keyIn); - public AccountExternalId.Key getLastLoginExternalId(); - public CurrentUser getUser(); - public void login(AuthResult res, boolean rememberMe); + boolean isSignedIn(); + String getXGerritAuth(); + boolean isValidXGerritAuth(String keyIn); + AccountExternalId.Key getLastLoginExternalId(); + CurrentUser getUser(); + void login(AuthResult res, boolean rememberMe); /** Set the user account for this current request only. */ - public void setUserAccountId(Account.Id id); - public boolean isAccessPathOk(AccessPath path); - public void setAccessPathOk(AccessPath path, boolean ok); + void setUserAccountId(Account.Id id); + boolean isAccessPathOk(AccessPath path); + void setAccessPathOk(AccessPath path, boolean ok); - public void logout(); - public String getSessionId(); + void logout(); + String getSessionId(); }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/XsrfCookieFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/XsrfCookieFilter.java new file mode 100644 index 0000000..c14d043 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/XsrfCookieFilter.java
@@ -0,0 +1,81 @@ +// 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.httpd; + +import com.google.gerrit.common.data.HostPageData; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.CurrentUser; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Singleton +public class XsrfCookieFilter implements Filter { + private final Provider<CurrentUser> user; + private final DynamicItem<WebSession> session; + + @Inject + XsrfCookieFilter( + Provider<CurrentUser> user, + DynamicItem<WebSession> session) { + this.user = user; + this.session = session; + } + + @Override + public void doFilter(ServletRequest req, ServletResponse rsp, + FilterChain chain) throws IOException, ServletException { + WebSession s = user.get().isIdentifiedUser() ? session.get() : null; + setXsrfTokenCookie( + (HttpServletRequest) req, (HttpServletResponse) rsp, s); + chain.doFilter(req, rsp); + } + + private static void setXsrfTokenCookie(HttpServletRequest req, + HttpServletResponse rsp, WebSession session) { + String v = session != null ? session.getXGerritAuth() : ""; + Cookie c = new Cookie(HostPageData.XSRF_COOKIE_NAME, v); + c.setPath("/"); + c.setSecure(isSecure(req)); + c.setMaxAge(session != null + ? -1 // Set the cookie for this browser session. + : 0); // Remove the cookie (expire immediately). + rsp.addCookie(c); + } + + private static boolean isSecure(HttpServletRequest req) { + return req.isSecure() || "https".equals(req.getScheme()); + } + + @Override + public void init(FilterConfig config) { + } + + @Override + public void destroy() { + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.java new file mode 100644 index 0000000..86bbf98 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BowerComponentsServlet.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.httpd.raw; + +import com.google.common.cache.Cache; +import com.google.gerrit.launcher.GerritLauncher; + +import java.io.IOException; +import java.nio.file.Path; + +class BowerComponentsServlet extends ResourceServlet { + private static final long serialVersionUID = 1L; + + static Path getZipPath(Path buckOut) { + if (buckOut == null) { + return null; + } + return buckOut.resolve("gen") + .resolve("polygerrit-ui") + .resolve("polygerrit_components") + .resolve("polygerrit_components.bower_components.zip"); + } + + private final Path zip; + + BowerComponentsServlet(Cache<Path, Resource> cache, Path buckOut) { + super(cache, true); + this.zip = getZipPath(buckOut); + } + + @Override + protected Path getResourcePath(String pathInfo) throws IOException { + return GerritLauncher.getZipFileSystem(zip) + .getPath("bower_components/" + pathInfo); + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BuckUtils.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BuckUtils.java new file mode 100644 index 0000000..0b4a02e --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/BuckUtils.java
@@ -0,0 +1,118 @@ +// 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.httpd.raw; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.escape.Escaper; +import com.google.common.html.HtmlEscapers; +import com.google.common.io.ByteStreams; +import com.google.gerrit.common.TimeUtil; +import com.google.gwtexpui.server.CacheHeaders; + +import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +import javax.servlet.http.HttpServletResponse; + +class BuckUtils { + private static final Logger log = + LoggerFactory.getLogger(BuckUtils.class); + + static void build(Path root, Path gen, String target) + throws IOException, BuildFailureException { + log.info("buck build " + target); + Properties properties = loadBuckProperties(gen); + String buck = firstNonNull(properties.getProperty("buck"), "buck"); + ProcessBuilder proc = new ProcessBuilder(buck, "build", target) + .directory(root.toFile()) + .redirectErrorStream(true); + if (properties.containsKey("PATH")) { + proc.environment().put("PATH", properties.getProperty("PATH")); + } + long start = TimeUtil.nowMs(); + Process rebuild = proc.start(); + byte[] out; + try (InputStream in = rebuild.getInputStream()) { + out = ByteStreams.toByteArray(in); + } finally { + rebuild.getOutputStream().close(); + } + + int status; + try { + status = rebuild.waitFor(); + } catch (InterruptedException e) { + throw new InterruptedIOException("interrupted waiting for " + buck); + } + if (status != 0) { + throw new BuildFailureException(out); + } + + long time = TimeUtil.nowMs() - start; + log.info(String.format("UPDATED %s in %.3fs", target, time / 1000.0)); + } + + private static Properties loadBuckProperties(Path gen) throws IOException { + Properties properties = new Properties(); + Path p = gen.resolve(Paths.get("tools/buck/buck.properties")); + try (InputStream in = Files.newInputStream(p)) { + properties.load(in); + } catch (NoSuchFileException e) { + // Ignore; will be run from PATH, with a descriptive error if it fails. + } + return properties; + } + + static void displayFailure(String rule, byte[] why, HttpServletResponse res) + throws IOException { + res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + res.setContentType("text/html"); + res.setCharacterEncoding(UTF_8.name()); + CacheHeaders.setNotCacheable(res); + + Escaper html = HtmlEscapers.htmlEscaper(); + try (PrintWriter w = res.getWriter()) { + w.write("<html><title>BUILD FAILED</title><body>"); + w.format("<h1>%s FAILED</h1>", html.escape(rule)); + w.write("<pre>"); + w.write(html.escape(RawParseUtils.decode(why))); + w.write("</pre>"); + w.write("</body></html>"); + } + } + + static class BuildFailureException extends Exception { + private static final long serialVersionUID = 1L; + + final byte[] why; + + BuildFailureException(byte[] why) { + this.why = why; + } + } +}
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 ab3728e..43c66db 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
@@ -25,13 +25,11 @@ import com.google.gerrit.common.Version; import com.google.gerrit.common.data.HostPageData; import com.google.gerrit.extensions.client.DiffPreferencesInfo; -import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.systemstatus.MessageOfTheDay; import com.google.gerrit.extensions.webui.WebUiPlugin; 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.account.AccountResource; @@ -67,7 +65,6 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -83,7 +80,6 @@ private static final int DEFAULT_JS_LOAD_TIMEOUT = 5000; private final Provider<CurrentUser> currentUser; - private final DynamicItem<WebSession> session; private final DynamicSet<WebUiPlugin> plugins; private final DynamicSet<MessageOfTheDay> messages; private final HostPageData.Theme signedOutTheme; @@ -101,7 +97,6 @@ @Inject HostPageServlet( Provider<CurrentUser> cu, - DynamicItem<WebSession> w, SitePaths sp, ThemeFactory themeFactory, ServletContext servletContext, @@ -113,7 +108,6 @@ GetDiffPreferences diffPref) throws IOException, ServletException { currentUser = cu; - session = w; plugins = webUiPlugins; messages = motd; signedOutTheme = themeFactory.getSignedOutTheme(); @@ -193,7 +187,6 @@ StringWriter w = new StringWriter(); CurrentUser user = currentUser.get(); if (user.isIdentifiedUser()) { - setXGerritAuthCookie(req, rsp, session.get()); w.write(HPD_ID + ".accountDiffPref="); json(getDiffPreferences(user.asIdentifiedUser()), w); w.write(";"); @@ -202,7 +195,6 @@ json(signedInTheme, w); w.write(";"); } else { - setXGerritAuthCookie(req, rsp, null); w.write(HPD_ID + ".theme="); json(signedOutTheme, w); w.write(";"); @@ -229,23 +221,6 @@ } } - private static void setXGerritAuthCookie(HttpServletRequest req, - HttpServletResponse rsp, WebSession session) { - String v = session != null ? session.getXGerritAuth() : ""; - Cookie c = new Cookie(HostPageData.XSRF_COOKIE_NAME, v); - c.setPath("/"); - c.setHttpOnly(false); - c.setSecure(isSecure(req)); - c.setMaxAge(session != null - ? -1 // Set the cookie for this browser session. - : 0); // Remove the cookie (expire immediately). - rsp.addCookie(c); - } - - private static boolean isSecure(HttpServletRequest req) { - return req.isSecure() || "https".equals(req.getScheme()); - } - private DiffPreferencesInfo getDiffPreferences(IdentifiedUser user) { try { return getDiff.apply(new AccountResource(user));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/PolyGerritUiIndexServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/PolyGerritUiIndexServlet.java new file mode 100644 index 0000000..3b225c9 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/PolyGerritUiIndexServlet.java
@@ -0,0 +1,35 @@ +// 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.httpd.raw; + +import com.google.common.cache.Cache; + +import java.nio.file.Path; + +class PolyGerritUiIndexServlet extends ResourceServlet { + private static final long serialVersionUID = 1L; + + private final Path index; + + PolyGerritUiIndexServlet(Cache<Path, Resource> cache, Path ui) { + super(cache, true); + index = ui.resolve("index.html"); + } + + @Override + protected Path getResourcePath(String pathInfo) { + return index; + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java new file mode 100644 index 0000000..4ca8b1c --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java
@@ -0,0 +1,35 @@ +// 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.httpd.raw; + +import com.google.common.cache.Cache; + +import java.nio.file.Path; + +class PolyGerritUiServlet extends ResourceServlet { + private static final long serialVersionUID = 1L; + + private final Path ui; + + PolyGerritUiServlet(Cache<Path, Resource> cache, Path ui) { + super(cache, true); + this.ui = ui; + } + + @Override + protected Path getResourcePath(String pathInfo) { + return ui.resolve(pathInfo); + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RebuildBowerComponentsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RebuildBowerComponentsFilter.java new file mode 100644 index 0000000..38eaa0a --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RebuildBowerComponentsFilter.java
@@ -0,0 +1,74 @@ +// 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.httpd.raw; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.gerrit.httpd.raw.BuckUtils.BuildFailureException; +import com.google.gerrit.launcher.GerritLauncher; +import com.google.inject.Singleton; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +@Singleton +class RebuildBowerComponentsFilter implements Filter { + private static final String TARGET = "//polygerrit-ui:polygerrit_components"; + + private final Path gen; + private final Path root; + private final Path zip; + + RebuildBowerComponentsFilter(Path buckOut) { + gen = buckOut.resolve("gen"); + root = buckOut.getParent(); + zip = BowerComponentsServlet.getZipPath(buckOut); + } + + @Override + public synchronized void doFilter(ServletRequest sreq, ServletResponse sres, + FilterChain chain) throws IOException, ServletException { + HttpServletResponse res = (HttpServletResponse) sres; + try { + BuckUtils.build(root, gen, TARGET); + } catch (BuildFailureException e) { + BuckUtils.displayFailure(TARGET, e.why, res); + return; + } + if (!Files.exists(zip)) { + String msg = "`buck build` did not produce " + zip.toAbsolutePath(); + BuckUtils.displayFailure(TARGET, msg.getBytes(UTF_8), res); + } + GerritLauncher.reloadZipFileSystem(zip); + chain.doFilter(sreq, sres); + } + + @Override + public void init(FilterConfig config) throws ServletException { + } + + @Override + public void destroy() { + } +}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java index a5bc6c6..1984cbb 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RecompileGwtUiFilter.java
@@ -14,33 +14,16 @@ package com.google.gerrit.httpd.raw; -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.base.MoreObjects; -import com.google.common.escape.Escaper; -import com.google.common.html.HtmlEscapers; -import com.google.common.io.ByteStreams; -import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.httpd.raw.BuckUtils.BuildFailureException; import com.google.gwtexpui.linker.server.UserAgentRule; -import com.google.gwtexpui.server.CacheHeaders; - -import org.eclipse.jgit.util.RawParseUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.PrintWriter; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Enumeration; import java.util.HashSet; -import java.util.Properties; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -55,9 +38,6 @@ import javax.servlet.http.HttpServletResponse; class RecompileGwtUiFilter implements Filter { - private static final Logger log = - LoggerFactory.getLogger(RecompileGwtUiFilter.class); - private final boolean gwtuiRecompile = System.getProperty("gerrit.disable-gwtui-recompile") == null; private final UserAgentRule rule = new UserAgentRule(); @@ -92,9 +72,9 @@ synchronized (this) { try { - build(root, gen, rule); + BuckUtils.build(root, gen, rule); } catch (BuildFailureException e) { - displayFailure(rule, e.why, (HttpServletResponse) res); + BuckUtils.displayFailure(rule, e.why, (HttpServletResponse) res); return; } @@ -109,24 +89,6 @@ chain.doFilter(request, res); } - private void displayFailure(String rule, byte[] why, HttpServletResponse res) - throws IOException { - res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - res.setContentType("text/html"); - res.setCharacterEncoding(UTF_8.name()); - CacheHeaders.setNotCacheable(res); - - Escaper html = HtmlEscapers.htmlEscaper(); - try (PrintWriter w = res.getWriter()) { - w.write("<html><title>BUILD FAILED</title><body>"); - w.format("<h1>%s FAILED</h1>", html.escape(rule)); - w.write("<pre>"); - w.write(html.escape(RawParseUtils.decode(why))); - w.write("</pre>"); - w.write("</body></html>"); - } - } - @Override public void init(FilterConfig config) { } @@ -166,59 +128,6 @@ } } - 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.toFile()) - .redirectErrorStream(true); - if (properties.containsKey("PATH")) { - proc.environment().put("PATH", properties.getProperty("PATH")); - } - long start = TimeUtil.nowMs(); - Process rebuild = proc.start(); - byte[] out; - try (InputStream in = rebuild.getInputStream()) { - out = ByteStreams.toByteArray(in); - } finally { - rebuild.getOutputStream().close(); - } - - int status; - try { - status = rebuild.waitFor(); - } catch (InterruptedException e) { - throw new InterruptedIOException("interrupted waiting for " + buck); - } - if (status != 0) { - throw new BuildFailureException(out); - } - - long time = TimeUtil.nowMs() - start; - log.info(String.format("UPDATED %s in %.3fs", target, time / 1000.0)); - } - - private static Properties loadBuckProperties(Path gen) - throws FileNotFoundException, IOException { - Properties properties = new Properties(); - try (InputStream in = new FileInputStream( - gen.resolve(Paths.get("tools/buck/buck.properties")).toFile())) { - properties.load(in); - } - return properties; - } - - @SuppressWarnings("serial") - private static class BuildFailureException extends Exception { - final byte[] why; - - BuildFailureException(byte[] why) { - this.why = why; - } - } - private static void mkdir(File dir) throws IOException { if (!dir.isDirectory()) { mkdir(dir.getParentFile());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java index 507bc59..c6eece2 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
@@ -108,8 +108,9 @@ * * @param pathInfo result of {@link HttpServletRequest#getPathInfo()}. * @return path where static content can be found. + * @throws IOException if an error occurred resolving the resource. */ - protected abstract Path getResourcePath(String pathInfo); + protected abstract Path getResourcePath(String pathInfo) throws IOException; protected FileTime getLastModifiedTime(Path p) throws IOException { return Files.getLastModifiedTime(p); @@ -118,7 +119,12 @@ @Override protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException { - String name = CharMatcher.is('/').trimFrom(req.getPathInfo()); + String name; + if (req.getPathInfo() == null) { + name = "/"; + } else { + name = CharMatcher.is('/').trimFrom(req.getPathInfo()); + } if (isUnreasonableName(name)) { notFound(rsp); return; @@ -190,7 +196,7 @@ try { Path p = getResourcePath(name); return cache.get(p, newLoader(p)); - } catch (ExecutionException e) { + } catch (ExecutionException | IOException e) { log.warn(String.format("Cannot load static resource %s", name), e); return null; } @@ -277,7 +283,7 @@ }; } - static class Resource { + public static class Resource { static final Resource NOT_FOUND = new Resource(FileTime.fromMillis(0), "", new byte[] {}); @@ -301,7 +307,7 @@ } } - static class Weigher + public static class Weigher implements com.google.common.cache.Weigher<Path, Resource> { @Override public int weigh(Path p, Resource r) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java index cf99d3c..6365306 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SiteStaticDirectoryServlet.java
@@ -51,15 +51,6 @@ @Override protected Path getResourcePath(String pathInfo) { - Path p = staticBase.resolve(pathInfo); - try { - p = p.toRealPath().normalize(); - if (!p.startsWith(staticBase)) { - return null; - } - return p; - } catch (IOException e) { - return null; - } + return staticBase.resolve(pathInfo); } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java index ded81f1..da66ba3 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -14,10 +14,16 @@ package com.google.gerrit.httpd.raw; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.cache.Cache; +import com.google.common.collect.ImmutableList; +import com.google.gerrit.httpd.GerritOptions; +import com.google.gerrit.httpd.XsrfCookieFilter; import com.google.gerrit.httpd.raw.ResourceServlet.Resource; import com.google.gerrit.launcher.GerritLauncher; import com.google.gerrit.server.cache.CacheModule; +import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.Provides; import com.google.inject.ProvisionException; @@ -37,24 +43,38 @@ import javax.servlet.http.HttpServletResponse; public class StaticModule extends ServletModule { + public static final String CACHE = "static_content"; + + public static final ImmutableList<String> POLYGERRIT_INDEX_PATHS = + ImmutableList.of( + "/", + "/c/*", + "/q/*", + "/x/*", + "/admin/*", + "/dashboard/*", + "/settings/*", + // TODO(dborowitz): These fragments conflict with the REST API + // namespace, so they will need to use a different path. + "/groups/*", + "/projects/*"); + private static final String GWT_UI_SERVLET = "GwtUiServlet"; private static final String DOC_SERVLET = "DocServlet"; - static final String CACHE = "static_content"; + private final GerritOptions options; + private Paths paths; - private final FileSystem warFs; - private final Path buckOut; - private final Path unpackedWar; + @Inject + public StaticModule(GerritOptions options) { + this.options = options; + } - public StaticModule() { - warFs = getDistributionArchive(); - if (warFs == null) { - buckOut = getDeveloperBuckOut(); - unpackedWar = makeWarTempDir(); - } else { - buckOut = null; - unpackedWar = null; + private Paths getPaths() { + if (paths == null) { + paths = new Paths(); } + return paths; } @Override @@ -62,7 +82,6 @@ serveRegex("^/Documentation/(.+)$").with( Key.get(HttpServlet.class, Names.named(DOC_SERVLET))); serve("/static/*").with(SiteStaticDirectoryServlet.class); - serveGwtUi(); install(new CacheModule() { @Override protected void configure() { @@ -71,13 +90,10 @@ .weigher(ResourceServlet.Weigher.class); } }); - } - - private void serveGwtUi() { - serveRegex("^/gerrit_ui/(?!rpc/)(.*)$") - .with(Key.get(HttpServlet.class, Names.named(GWT_UI_SERVLET))); - if (warFs == null) { - filter("/").through(new RecompileGwtUiFilter(buckOut, unpackedWar)); + if (options.enablePolyGerrit()) { + install(new PolyGerritUiModule()); + } else if (options.enableDefaultUi()) { + install(new GwtUiModule()); } } @@ -85,8 +101,9 @@ @Singleton @Named(DOC_SERVLET) HttpServlet getDocServlet(@Named(CACHE) Cache<Path, Resource> cache) { - if (warFs != null) { - return new WarDocServlet(cache, warFs); + Paths p = getPaths(); + if (p.warFs != null) { + return new WarDocServlet(cache, p.warFs); } else { return new HttpServlet() { private static final long serialVersionUID = 1L; @@ -100,65 +117,167 @@ } } - @Provides - @Singleton - @Named(GWT_UI_SERVLET) - HttpServlet getGwtUiServlet(@Named(CACHE) Cache<Path, Resource> cache) - throws IOException { - if (warFs != null) { - return new WarGwtUiServlet(cache, warFs); - } else { - return new DeveloperGwtUiServlet(cache, unpackedWar); + private class GwtUiModule extends ServletModule { + @Override + public void configureServlets() { + serveRegex("^/gerrit_ui/(?!rpc/)(.*)$") + .with(Key.get(HttpServlet.class, Names.named(GWT_UI_SERVLET))); + Paths p = getPaths(); + if (p.warFs == null && p.buckOut != null) { + filter("/").through(new RecompileGwtUiFilter(p.buckOut, p.unpackedWar)); + } + } + + @Provides + @Singleton + @Named(GWT_UI_SERVLET) + HttpServlet getGwtUiServlet(@Named(CACHE) Cache<Path, Resource> cache) + throws IOException { + Paths p = getPaths(); + if (p.warFs != null) { + return new WarGwtUiServlet(cache, p.warFs); + } else { + return new DeveloperGwtUiServlet(cache, p.unpackedWar); + } } } - private static FileSystem getDistributionArchive() { - try { - return GerritLauncher.getDistributionArchiveFileSystem(); - } catch (IOException e) { - if ((e instanceof FileNotFoundException) - && GerritLauncher.NOT_ARCHIVED.equals(e.getMessage())) { - return null; + private class PolyGerritUiModule extends ServletModule { + @Override + public void configureServlets() { + Path buckOut = getPaths().buckOut; + if (buckOut != null) { + RebuildBowerComponentsFilter rebuildFilter = + new RebuildBowerComponentsFilter(buckOut); + for (String p : POLYGERRIT_INDEX_PATHS) { + // Rebuilding bower_components once per load on the index request, + // is sufficient, since it will finish building before attempting to + // access any bower_components resources. Plus it saves contention and + // extraneous buck builds. + filter(p).through(rebuildFilter); + } + serve("/bower_components/*").with(BowerComponentsServlet.class); } else { + // In the war case, bower_components are either inlined by vulcanize, or + // live under /polygerrit_ui in the war file, so we don't need a + // separate servlet. + } + + for (String p : POLYGERRIT_INDEX_PATHS) { + filter(p).through(XsrfCookieFilter.class); + serve(p).with(PolyGerritUiIndexServlet.class); + } + serve("/*").with(PolyGerritUiServlet.class); + } + + @Provides + @Singleton + PolyGerritUiIndexServlet getPolyGerritUiIndexServlet( + @Named(CACHE) Cache<Path, Resource> cache) { + return new PolyGerritUiIndexServlet(cache, polyGerritBasePath()); + } + + @Provides + @Singleton + PolyGerritUiServlet getPolyGerritUiServlet( + @Named(CACHE) Cache<Path, Resource> cache) { + return new PolyGerritUiServlet(cache, polyGerritBasePath()); + } + + @Provides + @Singleton + BowerComponentsServlet getBowerComponentsServlet( + @Named(CACHE) Cache<Path, Resource> cache) { + return new BowerComponentsServlet(cache, getPaths().buckOut); + } + + private Path polyGerritBasePath() { + Paths p = getPaths(); + boolean forceDev = options.forcePolyGerritDev(); + if (forceDev) { + checkArgument(p.buckOut != null, + "no buck-out directory found for PolyGerrit developer mode"); + } + return forceDev || p.warFs == null + ? p.buckOut.getParent().resolve("polygerrit-ui").resolve("app") + : p.warFs.getPath("/polygerrit_ui"); + } + } + + private class Paths { + private final FileSystem warFs; + private final Path buckOut; + private final Path unpackedWar; + + private Paths() { + try { + warFs = getDistributionArchive(); + if (warFs == null) { + buckOut = getDeveloperBuckOut(); + unpackedWar = makeWarTempDir(); + } else if (options.forcePolyGerritDev()) { + buckOut = getDeveloperBuckOut(); + unpackedWar = null; + } else { + buckOut = null; + unpackedWar = null; + } + } catch (IOException e) { + throw new ProvisionException( + "Error initializing static content paths", e); + } + } + + private FileSystem getDistributionArchive() throws IOException { + File war; + try { + war = GerritLauncher.getDistributionArchive(); + } catch (IOException e) { + if ((e instanceof FileNotFoundException) + && GerritLauncher.NOT_ARCHIVED.equals(e.getMessage())) { + return null; + } else { + ProvisionException pe = + new ProvisionException("Error reading gerrit.war"); + pe.initCause(e); + throw pe; + } + } + return GerritLauncher.getZipFileSystem(war.toPath()); + } + + private Path getDeveloperBuckOut() { + try { + return GerritLauncher.getDeveloperBuckOut(); + } catch (FileNotFoundException e) { + return null; + } + } + + private Path makeWarTempDir() { + // Obtain our local temporary directory, but it comes back as a file + // so we have to switch it to be a directory post creation. + // + try { + File dstwar = GerritLauncher.createTempFile("gerrit_", "war"); + if (!dstwar.delete() || !dstwar.mkdir()) { + throw new IOException("Cannot mkdir " + dstwar.getAbsolutePath()); + } + + // Jetty normally refuses to serve out of a symlinked directory, as + // a security feature. Try to resolve out any symlinks in the path. + // + try { + return dstwar.getCanonicalFile().toPath(); + } catch (IOException e) { + return dstwar.getAbsoluteFile().toPath(); + } + } catch (IOException e) { ProvisionException pe = - new ProvisionException("Error reading gerrit.war"); + new ProvisionException("Cannot create war tempdir"); pe.initCause(e); throw pe; } } } - - private static Path getDeveloperBuckOut() { - try { - return GerritLauncher.getDeveloperBuckOut(); - } catch (FileNotFoundException e) { - return null; - } - } - - private static Path makeWarTempDir() { - // Obtain our local temporary directory, but it comes back as a file - // so we have to switch it to be a directory post creation. - // - try { - File dstwar = GerritLauncher.createTempFile("gerrit_", "war"); - if (!dstwar.delete() || !dstwar.mkdir()) { - throw new IOException("Cannot mkdir " + dstwar.getAbsolutePath()); - } - - // Jetty normally refuses to serve out of a symlinked directory, as - // a security feature. Try to resolve out any symlinks in the path. - // - try { - return dstwar.getCanonicalFile().toPath(); - } catch (IOException e) { - return dstwar.getAbsoluteFile().toPath(); - } - } catch (IOException e) { - ProvisionException pe = - new ProvisionException("Cannot create war tempdir"); - pe.initCause(e); - throw pe; - } - } }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java index ef5a1df..182ee7e 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/resources/ResourceKey.java
@@ -15,5 +15,5 @@ package com.google.gerrit.httpd.resources; public interface ResourceKey { - public int weigh(); + int weigh(); }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java new file mode 100644 index 0000000..2a01b77 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
@@ -0,0 +1,87 @@ +// 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.httpd.restapi; + +import com.google.common.base.Strings; +import com.google.gerrit.httpd.restapi.RestApiServlet.ViewData; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Counter2; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Histogram1; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer1; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class RestApiMetrics { + private static final String[] PKGS = { + "com.google.gerrit.server.", + "com.google.gerrit.", + }; + + final Counter1<String> count; + final Counter2<String, Integer> errorCount; + final Timer1<String> serverLatency; + final Histogram1<String> responseBytes; + + @Inject + RestApiMetrics(MetricMaker metrics) { + Field<String> view = Field.ofString("view", "view implementation class"); + count = metrics.newCounter( + "http/server/rest_api/count", + new Description("REST API calls by view") + .setRate(), + view); + + errorCount = metrics.newCounter( + "http/server/rest_api/error_count", + new Description("REST API calls by view") + .setRate(), + view, + Field.ofInteger("error_code", "HTTP status code")); + + serverLatency = metrics.newTimer( + "http/server/rest_api/server_latency", + new Description("REST API call latency by view") + .setCumulative() + .setUnit(Units.MILLISECONDS), + view); + + responseBytes = metrics.newHistogram( + "http/server/rest_api/response_bytes", + new Description("Size of response on network (may be gzip compressed)") + .setCumulative() + .setUnit(Units.BYTES), + view); + } + + String view(ViewData viewData) { + String impl = viewData.view.getClass().getName().replace('$', '.'); + for (String p : PKGS) { + if (impl.startsWith(p)) { + impl = impl.substring(p.length()); + break; + } + } + if (!Strings.isNullOrEmpty(viewData.pluginName) + && !"gerrit".equals(viewData.pluginName)) { + impl = viewData.pluginName + '-' + impl; + } + return impl; + } +}
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 7347cd9..f2ca49d 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
@@ -43,6 +43,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.common.io.BaseEncoding; +import com.google.common.io.CountingOutputStream; import com.google.common.math.IntMath; import com.google.common.net.HttpHeaders; import com.google.gerrit.audit.AuditService; @@ -125,6 +126,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.zip.GZIPOutputStream; @@ -164,16 +166,19 @@ final DynamicItem<WebSession> webSession; final Provider<ParameterParser> paramParser; final AuditService auditService; + final RestApiMetrics metrics; @Inject Globals(Provider<CurrentUser> currentUser, DynamicItem<WebSession> webSession, Provider<ParameterParser> paramParser, - AuditService auditService) { + AuditService auditService, + RestApiMetrics metrics) { this.currentUser = currentUser; this.webSession = webSession; this.paramParser = paramParser; this.auditService = auditService; + this.metrics = metrics; } } @@ -197,10 +202,12 @@ @Override protected final void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + final long startNanos = System.nanoTime(); long auditStartTs = TimeUtil.nowMs(); res.setHeader("Content-Disposition", "attachment"); res.setHeader("X-Content-Type-Options", "nosniff"); int status = SC_OK; + long responseBytes = -1; Object result = null; Multimap<String, String> params = LinkedHashMultimap.create(); Object inputRequestBody = null; @@ -348,47 +355,62 @@ if (result != Response.none()) { result = Response.unwrap(result); if (result instanceof BinaryResult) { - replyBinaryResult(req, res, (BinaryResult) result); + responseBytes = replyBinaryResult(req, res, (BinaryResult) result); } else { - replyJson(req, res, config, result); + responseBytes = replyJson(req, res, config, result); } } } catch (MalformedJsonException e) { - replyError(req, res, status = SC_BAD_REQUEST, + responseBytes = replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e); } catch (JsonParseException e) { - replyError(req, res, status = SC_BAD_REQUEST, + responseBytes = replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e); } catch (BadRequestException e) { - replyError(req, res, status = SC_BAD_REQUEST, messageOr(e, "Bad Request"), - e.caching(), e); + responseBytes = replyError(req, res, status = SC_BAD_REQUEST, + messageOr(e, "Bad Request"), e.caching(), e); } catch (AuthException e) { - replyError(req, res, status = SC_FORBIDDEN, messageOr(e, "Forbidden"), - e.caching(), e); + responseBytes = replyError(req, res, status = SC_FORBIDDEN, + messageOr(e, "Forbidden"), e.caching(), e); } catch (AmbiguousViewException e) { - replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e); + responseBytes = replyError(req, res, status = SC_NOT_FOUND, + messageOr(e, "Ambiguous"), e); } catch (ResourceNotFoundException e) { - replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Not Found"), - e.caching(), e); + responseBytes = replyError(req, res, status = SC_NOT_FOUND, + messageOr(e, "Not Found"), e.caching(), e); } catch (MethodNotAllowedException e) { - replyError(req, res, status = SC_METHOD_NOT_ALLOWED, + responseBytes = replyError(req, res, status = SC_METHOD_NOT_ALLOWED, messageOr(e, "Method Not Allowed"), e.caching(), e); } catch (ResourceConflictException e) { - replyError(req, res, status = SC_CONFLICT, messageOr(e, "Conflict"), - e.caching(), e); + responseBytes = replyError(req, res, status = SC_CONFLICT, + messageOr(e, "Conflict"), e.caching(), e); } catch (PreconditionFailedException e) { - replyError(req, res, status = SC_PRECONDITION_FAILED, + responseBytes = replyError(req, res, status = SC_PRECONDITION_FAILED, messageOr(e, "Precondition Failed"), e.caching(), e); } catch (UnprocessableEntityException e) { - replyError(req, res, status = 422, messageOr(e, "Unprocessable Entity"), - e.caching(), e); + responseBytes = replyError(req, res, status = 422, + messageOr(e, "Unprocessable Entity"), e.caching(), e); } catch (NotImplementedException e) { - replyError(req, res, status = SC_NOT_IMPLEMENTED, + responseBytes = replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e); } catch (Exception e) { status = SC_INTERNAL_SERVER_ERROR; - handleException(e, req, res); + responseBytes = handleException(e, req, res); } finally { + String metric = viewData != null && viewData.view != null + ? globals.metrics.view(viewData) + : "_unknown"; + globals.metrics.count.increment(metric); + if (status >= SC_BAD_REQUEST) { + globals.metrics.errorCount.increment(metric, status); + } + if (responseBytes != -1) { + globals.metrics.responseBytes.record(metric, responseBytes); + } + globals.metrics.serverLatency.record( + metric, + System.nanoTime() - startNanos, + TimeUnit.NANOSECONDS); globals.auditService.dispatch(new ExtendedHttpAuditEvent(globals.webSession.get() .getSessionId(), globals.currentUser.get(), req, auditStartTs, params, inputRequestBody, status, @@ -651,7 +673,7 @@ throw new InstantiationException("Cannot make " + type); } - public static void replyJson(@Nullable HttpServletRequest req, + public static long replyJson(@Nullable HttpServletRequest req, HttpServletResponse res, Multimap<String, String> config, Object result) @@ -667,7 +689,7 @@ } w.write('\n'); w.flush(); - replyBinaryResult(req, res, asBinaryResult(buf) + return replyBinaryResult(req, res, asBinaryResult(buf) .setContentType(JSON_TYPE) .setCharacterEncoding(UTF_8)); } @@ -736,7 +758,7 @@ } @SuppressWarnings("resource") - static void replyBinaryResult( + static long replyBinaryResult( @Nullable HttpServletRequest req, HttpServletResponse res, BinaryResult bin) throws IOException { @@ -767,10 +789,13 @@ } if (req == null || !"HEAD".equals(req.getMethod())) { - try (OutputStream dst = res.getOutputStream()) { + try (CountingOutputStream dst = + new CountingOutputStream(res.getOutputStream())) { bin.writeTo(dst); + return dst.getCount(); } } + return 0; } finally { appResult.close(); } @@ -977,7 +1002,7 @@ viewData.pluginName, viewData.view.getClass()); } - private static void handleException(Throwable err, HttpServletRequest req, + private static long handleException(Throwable err, HttpServletRequest req, HttpServletResponse res) throws IOException { String uri = req.getRequestURI(); if (!Strings.isNullOrEmpty(req.getQueryString())) { @@ -987,16 +1012,17 @@ if (!res.isCommitted()) { res.reset(); - replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err); + return replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err); } + return 0; } - public static void replyError(HttpServletRequest req, HttpServletResponse res, + public static long replyError(HttpServletRequest req, HttpServletResponse res, int statusCode, String msg, @Nullable Throwable err) throws IOException { - replyError(req, res, statusCode, msg, CacheControl.NONE, err); + return replyError(req, res, statusCode, msg, CacheControl.NONE, err); } - public static void replyError(HttpServletRequest req, + public static long replyError(HttpServletRequest req, HttpServletResponse res, int statusCode, String msg, CacheControl c, @Nullable Throwable err) throws IOException { if (err != null) { @@ -1004,18 +1030,18 @@ } configureCaching(req, res, null, null, c); res.setStatus(statusCode); - replyText(req, res, msg); + return replyText(req, res, msg); } - static void replyText(@Nullable HttpServletRequest req, + static long replyText(@Nullable HttpServletRequest req, HttpServletResponse res, String text) throws IOException { if ((req == null || isGetOrHead(req)) && isMaybeHTML(text)) { - replyJson(req, res, ImmutableMultimap.of("pp", "0"), new JsonPrimitive(text)); + return replyJson(req, res, ImmutableMultimap.of("pp", "0"), new JsonPrimitive(text)); } else { if (!text.endsWith("\n")) { text += "\n"; } - replyBinaryResult(req, res, + return replyBinaryResult(req, res, BinaryResult.create(text).setContentType("text/plain")); } } @@ -1099,7 +1125,7 @@ } } - private static class ViewData { + static class ViewData { String pluginName; RestView<RestResource> view;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java index e0d63c8..3c1c12d 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -153,7 +153,7 @@ } Account account = user.get().getAccount(); - hooks.doClaSignupHook(account, ca); + hooks.doClaSignupHook(account, ca.getName()); final AccountGroupMember.Key key = new AccountGroupMember.Key(account.getId(), group.getId());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java index e877d1e..4496fc1 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -24,6 +24,7 @@ import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.GroupBackend; import com.google.gerrit.server.config.AllProjectsNameProvider; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.project.NoSuchProjectException; @@ -52,6 +53,7 @@ } private final ChangeHooks hooks; + private final GitReferenceUpdated gitRefUpdated; private final IdentifiedUser user; private final ProjectAccessFactory.Factory projectAccessFactory; private final ProjectCache projectCache; @@ -63,7 +65,9 @@ MetaDataUpdate.User metaDataUpdateFactory, AllProjectsNameProvider allProjects, Provider<SetParent> setParent, - ChangeHooks hooks, IdentifiedUser user, + ChangeHooks hooks, + GitReferenceUpdated gitRefUpdated, + IdentifiedUser user, @Assisted("projectName") Project.NameKey projectName, @Nullable @Assisted ObjectId base, @Assisted List<AccessSection> sectionList, @@ -75,6 +79,7 @@ this.projectAccessFactory = projectAccessFactory; this.projectCache = projectCache; this.hooks = hooks; + this.gitRefUpdated = gitRefUpdated; this.user = user; } @@ -84,6 +89,8 @@ throws IOException, NoSuchProjectException, ConfigInvalidException { RevCommit commit = config.commit(md); + gitRefUpdated.fire(config.getProject().getNameKey(), RefNames.REFS_CONFIG, + base, commit.getId()); hooks.doRefUpdatedHook( new Branch.NameKey(config.getProject().getNameKey(), RefNames.REFS_CONFIG), base, commit.getId(), user.getAccount());
diff --git a/gerrit-launcher/BUCK b/gerrit-launcher/BUCK index 687e02f..5be25fa 100644 --- a/gerrit-launcher/BUCK +++ b/gerrit-launcher/BUCK
@@ -4,6 +4,7 @@ name = 'launcher', srcs = ['src/main/java/com/google/gerrit/launcher/GerritLauncher.java'], visibility = [ + '//gerrit-acceptance-framework/...', '//gerrit-acceptance-tests/...', '//gerrit-httpd:', '//gerrit-main:main_lib',
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 fb54bcf..8c05a57 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
@@ -32,13 +32,16 @@ import java.net.URLClassLoader; import java.nio.file.FileSystem; import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.CodeSource; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.jar.Attributes; @@ -300,9 +303,10 @@ } private static volatile File myArchive; - private static volatile FileSystem myArchiveFs; private static volatile File myHome; + private static final Map<Path, FileSystem> zipFileSystems = new HashMap<>(); + /** * Locate the JAR/WAR file we were launched from. * @@ -319,19 +323,54 @@ return result; } result = locateMyArchive(); - myArchiveFs = FileSystems.newFileSystem( - URI.create("jar:" + result.toPath().toUri()), - Collections.<String, String> emptyMap()); myArchive = result; } } return result; } - public static FileSystem getDistributionArchiveFileSystem() - throws FileNotFoundException, IOException { - getDistributionArchive(); - return myArchiveFs; + public static synchronized FileSystem getZipFileSystem(Path zip) + throws IOException { + // FileSystems canonicalizes the path, so we should too. + zip = zip.toRealPath(); + FileSystem zipFs = zipFileSystems.get(zip); + if (zipFs == null) { + zipFs = newZipFileSystem(zip); + zipFileSystems.put(zip, zipFs); + } + return zipFs; + } + + /** + * Reload the zip {@link FileSystem} for a path. + * <p> + * <strong>Warning</strong>: This calls {@link FileSystem#close()} on any + * previously open instance of the filesystem at this path, which may cause + * {@code IOException}s in any open path handles created with the old + * filesystem. Use with caution. + * + * @param zip path to zip file. + * @return reloaded filesystem instance. + * @throws IOException if there was an error reading the zip file. + */ + public static synchronized FileSystem reloadZipFileSystem(Path zip) + throws IOException { + // FileSystems canonicalizes the path, so we should too. + zip = zip.toRealPath(); + @SuppressWarnings("resource") // Caching resource for later use. + FileSystem zipFs = zipFileSystems.get(zip); + if (zipFs != null) { + zipFs.close(); + } + zipFs = newZipFileSystem(zip); + zipFileSystems.put(zip, zipFs); + return zipFs; + } + + private static FileSystem newZipFileSystem(Path zip) throws IOException { + return FileSystems.newFileSystem( + URI.create("jar:" + zip.toUri()), + Collections.<String, String> emptyMap()); } private static File locateMyArchive() throws FileNotFoundException { @@ -562,58 +601,68 @@ } /** + * Locate the path of the {@code eclipse-out} directory in a source tree. + * + * @throws FileNotFoundException if the directory cannot be found. + */ + public static Path getDeveloperEclipseOut() throws FileNotFoundException { + return resolveInSourceRoot("eclipse-out"); + } + + /** * Locate the path of the {@code buck-out} directory in a source tree. * * @throws FileNotFoundException if the directory cannot be found. */ public static Path getDeveloperBuckOut() throws FileNotFoundException { - // Find ourselves in the CLASSPATH, we should be a loose class file. + return resolveInSourceRoot("buck-out"); + } + + private static Path resolveInSourceRoot(String name) + throws FileNotFoundException { + // Find ourselves in the classpath, as a loose class file or jar. Class<GerritLauncher> self = GerritLauncher.class; URL u = self.getResource(self.getSimpleName() + ".class"); if (u == null) { throw new FileNotFoundException("Cannot find class " + self.getName()); - } else if (!"file".equals(u.getProtocol())) { - throw new FileNotFoundException("Cannot find extract path from " + u); - } - - // Pop up to the top level classes folder that contains us. - Path dir = Paths.get(u.getPath()); - String myName = self.getName(); - for (;;) { - int dot = myName.lastIndexOf('.'); - if (dot < 0) { - dir = dir.getParent(); - break; + } else if ("jar".equals(u.getProtocol())) { + String p = u.getPath(); + try { + u = new URL(p.substring(0, p.indexOf('!'))); + } catch (MalformedURLException e) { + FileNotFoundException fnfe = + new FileNotFoundException("Not a valid jar file: " + u); + fnfe.initCause(e); + throw fnfe; } - myName = myName.substring(0, dot); - dir = dir.getParent(); + } + if (!"file".equals(u.getProtocol())) { + throw new FileNotFoundException("Cannot extract path from " + u); } - dir = popdir(u, dir, "classes"); - dir = popdir(u, dir, "eclipse"); - if (last(dir).equals("buck-out")) { - return dir; + // Pop up to the top-level source folder by looking for .buckconfig. + Path dir = Paths.get(u.getPath()); + while (!Files.isRegularFile(dir.resolve(".buckconfig"))) { + Path parent = dir.getParent(); + if (parent == null) { + throw new FileNotFoundException("Cannot find source root from " + u); + } + dir = parent; } - throw new FileNotFoundException("Cannot find buck-out from " + u); - } - 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 (last(dir).equals(name)) { - return dir.getParent(); + Path ret = dir.resolve(name); + if (!Files.exists(ret)) { + throw new FileNotFoundException( + name + " not found in source root " + dir); } - throw new FileNotFoundException("Cannot find buck-out from " + u); + return ret; } private static ClassLoader useDevClasspath() throws MalformedURLException, FileNotFoundException { - Path out = getDeveloperBuckOut(); + Path out = getDeveloperEclipseOut(); List<URL> dirs = new ArrayList<>(); - dirs.add(out.resolve("eclipse").resolve("classes").toUri().toURL()); + dirs.add(out.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/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java index 5c3b629..c05c8f0 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
@@ -131,7 +131,6 @@ private static final String APPROVAL_FIELD = ChangeField.APPROVAL.getName(); private static final String CHANGE_FIELD = ChangeField.CHANGE.getName(); private static final String DELETED_FIELD = ChangeField.DELETED.getName(); - private static final String ID_FIELD = ChangeField.LEGACY_ID2.getName(); private static final String MERGEABLE_FIELD = ChangeField.MERGEABLE.getName(); private static final String PATCH_SET_FIELD = ChangeField.PATCH_SET.getName(); private static final String REVIEWEDBY_FIELD = @@ -139,10 +138,6 @@ private static final String UPDATED_SORT_FIELD = sortFieldName(ChangeField.UPDATED); - private static final ImmutableSet<String> FIELDS = ImmutableSet.of( - ADDED_FIELD, APPROVAL_FIELD, CHANGE_FIELD, DELETED_FIELD, ID_FIELD, - MERGEABLE_FIELD, PATCH_SET_FIELD, REVIEWEDBY_FIELD); - private static final Map<String, String> CUSTOM_CHAR_MAPPING = ImmutableMap.of( "_", " ", ".", " "); @@ -438,10 +433,12 @@ List<ChangeData> result = Lists.newArrayListWithCapacity(docs.scoreDocs.length); + Set<String> fields = fields(opts); + String idFieldName = idFieldName(); for (int i = opts.start(); i < docs.scoreDocs.length; i++) { ScoreDoc sd = docs.scoreDocs[i]; - Document doc = searchers[sd.shardIndex].doc(sd.doc, FIELDS); - result.add(toChangeData(doc)); + Document doc = searchers[sd.shardIndex].doc(sd.doc, fields); + result.add(toChangeData(doc, fields, idFieldName)); } final List<ChangeData> r = Collections.unmodifiableList(result); @@ -477,19 +474,62 @@ } } - private ChangeData toChangeData(Document doc) { + @SuppressWarnings("deprecation") + private Set<String> fields(QueryOptions opts) { + if (schemaHasRequestedField(ChangeField.LEGACY_ID2, opts.fields()) + || schemaHasRequestedField(ChangeField.CHANGE, opts.fields()) + || schemaHasRequestedField(ChangeField.LEGACY_ID, opts.fields())) { + return opts.fields(); + } + // Request the numeric ID field even if the caller did not request it, + // otherwise we can't actually construct a ChangeData. + return Sets.union(opts.fields(), ImmutableSet.of(idFieldName())); + } + + private boolean schemaHasRequestedField(FieldDef<ChangeData, ?> field, + Set<String> requested) { + return schema.hasField(field) && requested.contains(field.getName()); + } + + @SuppressWarnings("deprecation") + private String idFieldName() { + return schema.getField(ChangeField.LEGACY_ID2, ChangeField.LEGACY_ID).get() + .getName(); + } + + private ChangeData toChangeData(Document doc, Set<String> fields, + String idFieldName) { + ChangeData cd; + // Either change or the ID field was guaranteed to be included in the call + // to fields() above. BytesRef cb = doc.getBinaryValue(CHANGE_FIELD); - if (cb == null) { - int id = doc.getField(ID_FIELD).numericValue().intValue(); - return changeDataFactory.create(db.get(), new Change.Id(id)); + if (cb != null) { + cd = changeDataFactory.create(db.get(), + ChangeProtoField.CODEC.decode(cb.bytes, cb.offset, cb.length)); + } else { + int id = doc.getField(idFieldName).numericValue().intValue(); + cd = changeDataFactory.create(db.get(), new Change.Id(id)); } - // Change proto. - Change change = ChangeProtoField.CODEC.decode( - cb.bytes, cb.offset, cb.length); - ChangeData cd = changeDataFactory.create(db.get(), change); + if (fields.contains(PATCH_SET_FIELD)) { + decodePatchSets(doc, cd); + } + if (fields.contains(APPROVAL_FIELD)) { + decodeApprovals(doc, cd); + } + if (fields.contains(ADDED_FIELD) && fields.contains(DELETED_FIELD)) { + decodeChangedLines(doc, cd); + } + if (fields.contains(MERGEABLE_FIELD)) { + decodeMergeable(doc, cd); + } + if (fields.contains(REVIEWEDBY_FIELD)) { + decodeReviewedBy(doc, cd); + } + return cd; + } - // Patch sets. + private void decodePatchSets(Document doc, ChangeData cd) { List<PatchSet> patchSets = decodeProtos(doc, PATCH_SET_FIELD, PatchSetProtoField.CODEC); if (!patchSets.isEmpty()) { @@ -497,12 +537,14 @@ // this cannot be valid since a change needs at least one patch set. cd.setPatchSets(patchSets); } + } - // Approvals. + private void decodeApprovals(Document doc, ChangeData cd) { cd.setCurrentApprovals( decodeProtos(doc, APPROVAL_FIELD, PatchSetApprovalProtoField.CODEC)); + } - // Changed lines. + private void decodeChangedLines(Document doc, ChangeData cd) { IndexableField added = doc.getField(ADDED_FIELD); IndexableField deleted = doc.getField(DELETED_FIELD); if (added != null && deleted != null) { @@ -510,16 +552,18 @@ added.numericValue().intValue(), deleted.numericValue().intValue()); } + } - // Mergeable. + private void decodeMergeable(Document doc, ChangeData cd) { String mergeable = doc.get(MERGEABLE_FIELD); if ("1".equals(mergeable)) { cd.setMergeable(true); } else if ("0".equals(mergeable)) { cd.setMergeable(false); } + } - // Reviewed-by. + private void decodeReviewedBy(Document doc, ChangeData cd) { IndexableField[] reviewedBy = doc.getFields(REVIEWEDBY_FIELD); if (reviewedBy.length > 0) { Set<Account.Id> accounts = @@ -533,8 +577,6 @@ } cd.setReviewedBy(accounts); } - - return cd; } private static <T> List<T> decodeProtos(Document doc, String fieldName,
diff --git a/gerrit-openid/BUCK b/gerrit-openid/BUCK index 78abce8..0a6363b 100644 --- a/gerrit-openid/BUCK +++ b/gerrit-openid/BUCK
@@ -3,6 +3,9 @@ srcs = glob(['src/main/java/**/*.java']), resources = glob(['src/main/resources/**/*']), deps = [ + '//lib/openid:consumer', + ], + provided_deps = [ '//gerrit-common:annotations', '//gerrit-common:server', '//gerrit-extension-api:api', @@ -12,13 +15,12 @@ '//gerrit-server:server', '//lib:guava', '//lib:gwtorm', + '//lib:servlet-api-3_1', '//lib/commons:codec', '//lib/guice:guice', '//lib/guice:guice-servlet', '//lib/jgit:jgit', '//lib/log:api', - '//lib/openid:consumer', ], - provided_deps = ['//lib:servlet-api-3_1'], visibility = ['PUBLIC'], )
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK index c57ec52..3a14ffd 100644 --- a/gerrit-pgm/BUCK +++ b/gerrit-pgm/BUCK
@@ -3,23 +3,26 @@ INIT_API_SRCS = glob([SRCS + 'init/api/*.java']) -DEPS = [ - '//gerrit-common:server', - '//gerrit-extension-api:api', - '//gerrit-gwtexpui:linker_server', - '//gerrit-gwtexpui:server', - '//gerrit-httpd:httpd', - '//gerrit-server:server', - '//gerrit-sshd:sshd', - '//gerrit-reviewdb:server', - '//lib:guava', - '//lib/guice:guice', - '//lib/guice:guice-assistedinject', - '//lib/guice:guice-servlet', - '//lib/jgit:jgit', - '//lib/log:api', - '//lib/log:jsonevent-layout', - '//lib/log:log4j' +BASE_JETTY_DEPS = [ + '//gerrit-common:server', + '//gerrit-extension-api:api', + '//gerrit-gwtexpui:linker_server', + '//gerrit-gwtexpui:server', + '//gerrit-httpd:httpd', + '//gerrit-server:server', + '//gerrit-sshd:sshd', + '//lib:guava', + '//lib/guice:guice', + '//lib/guice:guice-assistedinject', + '//lib/guice:guice-servlet', + '//lib/jgit:jgit', + '//lib/log:api', + '//lib/log:log4j', +] + +DEPS = BASE_JETTY_DEPS + [ + '//gerrit-reviewdb:server', + '//lib/log:jsonevent-layout', ] java_library( @@ -83,24 +86,31 @@ name = 'util-nodep', srcs = glob([SRCS + 'util/*.java']), provided_deps = DEPS + REST_UTIL_DEPS, - visibility = [ - '//gerrit-acceptance-framework/...', - ], + visibility = ['//gerrit-acceptance-framework/...'], ) +JETTY_DEPS = [ + '//lib/jetty:jmx', + '//lib/jetty:server', + '//lib/jetty:servlet', +] + java_library( name = 'http', - srcs = glob([SRCS + 'http/**/*.java']), - deps = DEPS + [ - '//lib/jetty:jmx', - '//lib/jetty:server', - '//lib/jetty:servlet', - ], - provided_deps = [ + deps = DEPS + JETTY_DEPS, + exported_deps = [':http-jetty'], + visibility = ['//gerrit-war:'], +) + +java_library( + name = 'http-jetty', + srcs = glob([SRCS + 'http/jetty/*.java']), + provided_deps = JETTY_DEPS + BASE_JETTY_DEPS + [ '//gerrit-launcher:launcher', + '//gerrit-reviewdb:client', '//lib:servlet-api-3_1', ], - visibility = ['//gerrit-war:'], + visibility = ['//gerrit-acceptance-framework/...'], ) REST_PGM_DEPS = [
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 39a5cbd..ec41d1f 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
@@ -28,13 +28,17 @@ import com.google.gerrit.httpd.H2CacheBasedWebSession; import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider; import com.google.gerrit.httpd.RequestContextFilter; +import com.google.gerrit.httpd.RequestMetricsFilter; +import com.google.gerrit.httpd.RequireSslFilter; import com.google.gerrit.httpd.WebModule; import com.google.gerrit.httpd.WebSshGlueModule; import com.google.gerrit.httpd.auth.oauth.OAuthModule; import com.google.gerrit.httpd.auth.openid.OpenIdModule; import com.google.gerrit.httpd.plugins.HttpPluginModule; +import com.google.gerrit.httpd.raw.StaticModule; import com.google.gerrit.lifecycle.LifecycleManager; import com.google.gerrit.lucene.LuceneIndexModule; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; import com.google.gerrit.pgm.http.jetty.JettyEnv; import com.google.gerrit.pgm.http.jetty.JettyModule; import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter; @@ -138,6 +142,9 @@ @Option(name = "--headless", usage = "Don't start the UI frontend") private boolean headless; + @Option(name = "--polygerrit-dev", usage = "Force PolyGerrit UI for development") + private boolean polyGerritDev; + @Option(name = "--init", aliases = {"-i"}, usage = "Init site before starting the daemon") private boolean doInit; @@ -273,7 +280,7 @@ @VisibleForTesting public void start() throws IOException { if (dbInjector == null) { - dbInjector = createDbInjector(MULTI_USER); + dbInjector = createDbInjector(true /* enableMetrics */, MULTI_USER); } cfgInjector = createCfgInjector(); config = cfgInjector.getInstance( @@ -322,6 +329,7 @@ private Injector createSysInjector() { final List<Module> modules = new ArrayList<>(); modules.add(SchemaVersionCheck.module()); + modules.add(new DropWizardMetricMaker.RestModule()); modules.add(new LogFileCompressor.Module()); modules.add(new WorkQueue.Module()); modules.add(new ChangeHookRunner.Module()); @@ -365,7 +373,8 @@ modules.add(new AbstractModule() { @Override protected void configure() { - bind(GerritOptions.class).toInstance(new GerritOptions(headless, slave)); + bind(GerritOptions.class).toInstance( + new GerritOptions(config, headless, slave, polyGerritDev)); if (test) { bind(String.class).annotatedWith(SecureStoreClassName.class) .toInstance(DefaultSecureStore.class.getName()); @@ -442,9 +451,12 @@ } modules.add(RequestContextFilter.module()); modules.add(AllRequestFilter.module()); + modules.add(RequestMetricsFilter.module()); modules.add(H2CacheBasedWebSession.module()); modules.add(sysInjector.getInstance(GitOverHttpModule.class)); modules.add(sysInjector.getInstance(WebModule.class)); + modules.add(sysInjector.getInstance(StaticModule.class)); + modules.add(sysInjector.getInstance(RequireSslFilter.Module.class)); modules.add(new HttpPluginModule()); if (sshd) { modules.add(sshInjector.getInstance(WebSshGlueModule.class));
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 0684650..adfea594 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
@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.config.ThreadSettingsConfig; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; @@ -127,11 +128,14 @@ private boolean reverseProxy; @Inject - JettyServer(@GerritServerConfig final Config cfg, final SitePaths site, - final JettyEnv env, final HttpLogFactory httpLogFactory) { + JettyServer(@GerritServerConfig Config cfg, + ThreadSettingsConfig threadSettingsConfig, + SitePaths site, + JettyEnv env, + HttpLogFactory httpLogFactory) { this.site = site; - httpd = new Server(threadPool(cfg)); + httpd = new Server(threadPool(cfg, threadSettingsConfig)); httpd.setConnectors(listen(httpd, cfg)); Handler app = makeContext(env, cfg); @@ -315,8 +319,8 @@ return site.resolve(path); } - private ThreadPool threadPool(Config cfg) { - int maxThreads = cfg.getInt("httpd", null, "maxthreads", 25); + private ThreadPool threadPool(Config cfg, ThreadSettingsConfig threadSettingsConfig) { + int maxThreads = threadSettingsConfig.getHttpdMaxThreads(); int minThreads = cfg.getInt("httpd", null, "minthreads", 5); int maxQueued = cfg.getInt("httpd", null, "maxqueued", 200); int idleTimeout = (int)MILLISECONDS.convert(60, SECONDS);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java index c30edf8..bc9ce8c 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
@@ -23,5 +23,5 @@ * Performs database platform specific configuration steps and writes * configuration parameters into the given database section */ - public void initConfig(Section databaseSection); + void initConfig(Section databaseSection); }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java index 6b7386d..68af83f 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java
@@ -33,7 +33,7 @@ * IOException caused by dealing with the InputStream back to the * caller */ - public void process(String pluginName, InputStream in) throws IOException; + void process(String pluginName, InputStream in) throws IOException; } /** @@ -45,7 +45,7 @@ * @throws IOException in case of any other IO error caused by reading the * plugin input stream */ - public void foreach(Processor processor) throws FileNotFoundException, IOException; + void foreach(Processor processor) throws FileNotFoundException, IOException; /** * List plugins included in the Gerrit distribution @@ -53,5 +53,5 @@ * @throws FileNotFoundException if the location of the plugins couldn't be * determined */ - public List<String> listPluginNames() throws FileNotFoundException; + List<String> listPluginNames() throws FileNotFoundException; }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java index 250cf59..fd28399 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
@@ -16,8 +16,8 @@ /** A single step in the site initialization process. */ public interface InitStep { - public void run() throws Exception; + void run() throws Exception; /** Executed after the site has been initialized */ - public void postRun() throws Exception; + void postRun() throws Exception; }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java index 048c2ee..6443e21 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
@@ -15,8 +15,10 @@ package com.google.gerrit.pgm.util; import com.google.gerrit.common.SiteLibraryLoaderUtil; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.config.ThreadSettingsConfig; import com.google.gerrit.server.schema.DataSourceProvider; import com.google.gerrit.server.schema.DataSourceType; import com.google.inject.Inject; @@ -37,9 +39,11 @@ @Inject SiteLibraryBasedDataSourceProvider(SitePaths site, @GerritServerConfig Config cfg, + MetricMaker metrics, + ThreadSettingsConfig tsc, DataSourceProvider.Context ctx, DataSourceType dst) { - super(cfg, ctx, dst); + super(cfg, metrics, tsc, ctx, dst); libdir = site.lib_dir; }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java index a6f1f93..bf69d9b 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -22,6 +22,9 @@ import com.google.gerrit.common.Die; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.gerrit.metrics.DisabledMetricMaker; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfigModule; import com.google.gerrit.server.config.SitePath; @@ -93,7 +96,13 @@ } /** @return provides database connectivity and site path. */ - protected Injector createDbInjector(final DataSourceProvider.Context context) { + protected Injector createDbInjector(DataSourceProvider.Context context) { + return createDbInjector(false, context); + } + + /** @return provides database connectivity and site path. */ + protected Injector createDbInjector(final boolean enableMetrics, + final DataSourceProvider.Context context) { final Path sitePath = getSitePath(); final List<Module> modules = new ArrayList<>(); @@ -107,6 +116,17 @@ }; modules.add(sitePathModule); + if (enableMetrics) { + modules.add(new DropWizardMetricMaker.ApiModule()); + } else { + modules.add(new AbstractModule() { + @Override + protected void configure() { + bind(MetricMaker.class).to(DisabledMetricMaker.class); + } + }); + } + modules.add(new LifecycleModule() { @Override protected void configure() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java index 44361aa..dfe0403 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ThreadLimiter.java
@@ -15,7 +15,7 @@ package com.google.gerrit.pgm.util; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.schema.DataSourceProvider; +import com.google.gerrit.server.config.ThreadSettingsConfig; import com.google.gerrit.server.schema.DataSourceType; import com.google.inject.Injector; import com.google.inject.Key; @@ -34,14 +34,15 @@ return limitThreads( dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)), dbInjector.getInstance(DataSourceType.class), + dbInjector.getInstance(ThreadSettingsConfig.class), threads); } - private static int limitThreads(Config cfg, DataSourceType dst, int threads) { + private static int limitThreads(Config cfg, DataSourceType dst, + ThreadSettingsConfig threadSettingsConfig, int threads) { boolean usePool = cfg.getBoolean("database", "connectionpool", dst.usePool()); - int poolLimit = cfg.getInt("database", "poollimit", - DataSourceProvider.DEFAULT_POOL_LIMIT); + int poolLimit = threadSettingsConfig.getDatabasePoolLimit(); if (usePool && threads > poolLimit) { log.warn("Limiting program to " + poolLimit + " threads due to database.poolLimit");
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK index abcacf9..b58c10a 100644 --- a/gerrit-plugin-api/BUCK +++ b/gerrit-plugin-api/BUCK
@@ -28,6 +28,7 @@ '//gerrit-gwtexpui:server', '//gerrit-reviewdb:server', '//lib:args4j', + '//lib/dropwizard:dropwizard-core', '//lib:guava', '//lib:gwtorm', '//lib:jsch',
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml index 86d2e4b9..337f5e6 100644 --- a/gerrit-plugin-api/pom.xml +++ b/gerrit-plugin-api/pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-api</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Plugin API</name> <description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml index 10564de..55de829 100644 --- a/gerrit-plugin-archetype/pom.xml +++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@ <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-archetype</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <name>Gerrit Code Review - Plugin Archetype</name> <description>Maven Archetype for Gerrit Plugins</description> <url>https://www.gerritcodereview.com/</url>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml index 0d7bfb6..91e4b69 100644 --- a/gerrit-plugin-gwt-archetype/pom.xml +++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@ <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-gwt-archetype</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <name>Gerrit Code Review - Web UI GWT Plugin Archetype</name> <description>Maven Archetype for Gerrit Web UI GWT Plugins</description> <url>https://www.gerritcodereview.com/</url>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml index 1e2c7b9..c02098c 100644 --- a/gerrit-plugin-gwtui/pom.xml +++ b/gerrit-plugin-gwtui/pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-gwtui</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Plugin GWT UI</name> <description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java index 6d4e719..1fff691 100644 --- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java +++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/extension/Panel.java
@@ -56,7 +56,7 @@ * * @param panel panel that will contain the panel widget. */ - public void onLoad(Panel panel); + void onLoad(Panel panel); } static final class Context extends JavaScriptObject {
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java index 2a872fd..f91464f 100644 --- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java +++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java
@@ -58,7 +58,7 @@ * * @param screen panel that will contain the screen widget. */ - public void onLoad(Screen screen); + void onLoad(Screen screen); } static final class Context extends JavaScriptObject {
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml index dc7e832..0565b80 100644 --- a/gerrit-plugin-js-archetype/pom.xml +++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@ <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-js-archetype</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name> <description>Maven Archetype for Gerrit Web UI JavaScript Plugins</description> <url>https://www.gerritcodereview.com/</url>
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/SparseHtmlFile.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/SparseHtmlFile.java index 0c2af36..3fd34bf 100644 --- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/SparseHtmlFile.java +++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/SparseHtmlFile.java
@@ -18,14 +18,14 @@ public interface SparseHtmlFile { /** @return the line of formatted HTML. */ - public SafeHtml getSafeHtmlLine(int lineNo); + SafeHtml getSafeHtmlLine(int lineNo); /** @return the number of lines in this sparse list. */ - public int size(); + int size(); /** @return true if the line is valid in this sparse list. */ - public boolean contains(int idx); + boolean contains(int idx); /** @return true if this line ends in the middle of a character edit span. */ - public boolean hasTrailingEdit(int idx); + boolean hasTrailingEdit(int idx); }
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 5d2a1fd..72517ca 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
@@ -40,6 +40,7 @@ /** Configurations of project-specific dashboards (canned search queries). */ public static final String REFS_DASHBOARDS = "refs/meta/dashboards/"; + /** Draft inline comments of a user on a change */ public static final String REFS_DRAFT_COMMENTS = "refs/draft-comments/"; /** @@ -88,6 +89,17 @@ public static String refsDraftComments(Account.Id accountId, Change.Id changeId) { + StringBuilder r = buildRefsDraftCommentsPrefix(accountId); + r.append(changeId.get()); + return r.toString(); + } + + public static String refsDraftCommentsPrefix(Account.Id accountId) { + return buildRefsDraftCommentsPrefix(accountId).toString(); + } + + public static StringBuilder buildRefsDraftCommentsPrefix( + Account.Id accountId) { StringBuilder r = new StringBuilder(); r.append(REFS_DRAFT_COMMENTS); int n = accountId.get() % 100; @@ -98,8 +110,7 @@ r.append('/'); r.append(accountId.get()); r.append('-'); - r.append(changeId.get()); - return r.toString(); + return r; } /**
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java index b0981a7..baae696 100644 --- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java +++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
@@ -43,6 +43,12 @@ } @Test + public void refsDraftCommentsPrefix() throws Exception { + assertThat(RefNames.refsDraftCommentsPrefix(accountId)) + .isEqualTo("refs/draft-comments/23/1011123-"); + } + + @Test public void refsEdit() throws Exception { assertThat(RefNames.refsEdit(accountId, changeId, psId)) .isEqualTo("refs/users/23/1011123/edit-67473/42");
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK index c264779..b192432 100644 --- a/gerrit-server/BUCK +++ b/gerrit-server/BUCK
@@ -53,6 +53,7 @@ '//lib/commons:lang', '//lib/commons:net', '//lib/commons:validator', + '//lib/dropwizard:dropwizard-core', '//lib/guice:guice', '//lib/guice:guice-assistedinject', '//lib/guice:guice-servlet', @@ -137,6 +138,7 @@ srcs = PROLOG_TEST_CASE, deps = [ ':server', + ':testutil', '//gerrit-common:server', '//gerrit-extension-api:api', '//lib:guava', @@ -193,6 +195,7 @@ '//lib:args4j', '//lib:grappa', '//lib:guava', + '//lib/dropwizard:dropwizard-core', '//lib/guice:guice-assistedinject', '//lib/joda:joda-time', '//lib/prolog:runtime',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java index 889f008..d9934f6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -16,7 +16,6 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.gerrit.common.data.ContributorAgreement; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.extensions.events.LifecycleListener; @@ -715,12 +714,12 @@ } @Override - public void doClaSignupHook(Account account, ContributorAgreement cla) { + public void doClaSignupHook(Account account, String claName) { if (account != null) { List<String> args = new ArrayList<>(); addArg(args, "--submitter", getDisplayName(account)); addArg(args, "--user-id", account.getId().toString()); - addArg(args, "--cla-name", cla.getName()); + addArg(args, "--cla-name", claName); runHook(claSignedHook, args); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java index b16a8a5..a70df51 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -15,7 +15,6 @@ package com.google.gerrit.common; import com.google.gerrit.common.ChangeHookRunner.HookResult; -import com.google.gerrit.common.data.ContributorAgreement; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Change; @@ -39,7 +38,7 @@ * @param patchSet The Patchset that was created. * @throws OrmException */ - public void doPatchsetCreatedHook(Change change, PatchSet patchSet, + void doPatchsetCreatedHook(Change change, PatchSet patchSet, ReviewDb db) throws OrmException; /** @@ -49,7 +48,7 @@ * @param patchSet The Patchset that was published. * @throws OrmException */ - public void doDraftPublishedHook(Change change, PatchSet patchSet, + void doDraftPublishedHook(Change change, PatchSet patchSet, ReviewDb db) throws OrmException; /** @@ -62,7 +61,7 @@ * @param approvals Map of label IDs to scores * @throws OrmException */ - public void doCommentAddedHook(Change change, Account account, + void doCommentAddedHook(Change change, Account account, PatchSet patchSet, String comment, Map<String, Short> approvals, ReviewDb db) throws OrmException; @@ -76,7 +75,7 @@ * @param mergeResultRev The SHA-1 of the merge result revision. * @throws OrmException */ - public void doChangeMergedHook(Change change, Account account, + void doChangeMergedHook(Change change, Account account, PatchSet patchSet, ReviewDb db, String mergeResultRev) throws OrmException; /** @@ -88,7 +87,7 @@ * @param reason The reason that the change failed to merge. * @throws OrmException */ - public void doMergeFailedHook(Change change, Account account, + void doMergeFailedHook(Change change, Account account, PatchSet patchSet, String reason, ReviewDb db) throws OrmException; /** @@ -99,7 +98,7 @@ * @param reason Reason for abandoning the change. * @throws OrmException */ - public void doChangeAbandonedHook(Change change, Account account, + void doChangeAbandonedHook(Change change, Account account, PatchSet patchSet, String reason, ReviewDb db) throws OrmException; /** @@ -110,7 +109,7 @@ * @param reason Reason for restoring the change. * @throws OrmException */ - public void doChangeRestoredHook(Change change, Account account, + void doChangeRestoredHook(Change change, Account account, PatchSet patchSet, String reason, ReviewDb db) throws OrmException; /** @@ -120,7 +119,7 @@ * @param refUpdate An actual RefUpdate object * @param account The gerrit user who moved the ref */ - public void doRefUpdatedHook(Branch.NameKey refName, RefUpdate refUpdate, + void doRefUpdatedHook(Branch.NameKey refName, RefUpdate refUpdate, Account account); /** @@ -131,7 +130,7 @@ * @param newId The ref's new id * @param account The gerrit user who moved the ref */ - public void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId, + void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId, ObjectId newId, Account account); /** @@ -141,7 +140,7 @@ * @param patchSet The patchset that the reviewer was added on. * @param account The gerrit user who was added as reviewer. */ - public void doReviewerAddedHook(Change change, Account account, + void doReviewerAddedHook(Change change, Account account, PatchSet patchSet, ReviewDb db) throws OrmException; /** @@ -151,10 +150,10 @@ * @param account The gerrit user who changed the topic. * @param oldTopic The old topic name. */ - public void doTopicChangedHook(Change change, Account account, + void doTopicChangedHook(Change change, Account account, String oldTopic, ReviewDb db) throws OrmException; - public void doClaSignupHook(Account account, ContributorAgreement cla); + void doClaSignupHook(Account account, String claName); /** * Fire the Ref update Hook @@ -165,7 +164,7 @@ * @param oldId The ref's old id * @param newId The ref's new id */ - public HookResult doRefUpdateHook(Project project, String refName, + HookResult doRefUpdateHook(Project project, String refName, Account uploader, ObjectId oldId, ObjectId newId); /** @@ -178,7 +177,7 @@ * @param db The database * @throws OrmException */ - public void doHashtagsChangedHook(Change change, Account account, + void doHashtagsChangedHook(Change change, Account account, Set<String>added, Set<String> removed, Set<String> hashtags, ReviewDb db) throws OrmException; @@ -188,5 +187,5 @@ * @param project The project that was created * @param headName The head name of the created project */ - public void doProjectCreatedHook(Project.NameKey project, String headName); + void doProjectCreatedHook(Project.NameKey project, String headName); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java index bed77a7..1e0afd0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -15,7 +15,6 @@ package com.google.gerrit.common; import com.google.gerrit.common.ChangeHookRunner.HookResult; -import com.google.gerrit.common.data.ContributorAgreement; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Change; @@ -59,7 +58,7 @@ } @Override - public void doClaSignupHook(Account account, ContributorAgreement cla) { + public void doClaSignupHook(Account account, String claName) { } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java index b74771f..f71b5c68 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventDispatcher.java
@@ -31,7 +31,7 @@ * @param db The database * @throws OrmException */ - public void postEvent(Change change, Event event, ReviewDb db) + void postEvent(Change change, Event event, ReviewDb db) throws OrmException; /** @@ -40,5 +40,5 @@ * @param branchName The branch that the event is related to * @param event The event to post */ - public void postEvent(Branch.NameKey branchName, Event event); + void postEvent(Branch.NameKey branchName, Event event); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java index 7e8a794..97be844 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventListener.java
@@ -19,5 +19,5 @@ @ExtensionPoint public interface EventListener { - public void onEvent(Event event); + void onEvent(Event event); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java index e2c4b34..bde6f5d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventSource.java
@@ -18,7 +18,7 @@ /** Distributes Events to ChangeListeners. Register listeners here. */ public interface EventSource { - public void addEventListener(EventListener listener, CurrentUser user); + void addEventListener(EventListener listener, CurrentUser user); - public void removeEventListener(EventListener listener); + void removeEventListener(EventListener listener); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric.java new file mode 100644 index 0000000..1714c7a --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric.java
@@ -0,0 +1,28 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + + +/** + * Metric whose value is supplied when the trigger is invoked. + * + * @see CallbackMetric0 + * @param <V> type of the metric value, typically Integer or Long. + */ +public interface CallbackMetric<V> extends RegistrationHandle { + void prune(); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric0.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric0.java new file mode 100644 index 0000000..829dd22 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric0.java
@@ -0,0 +1,43 @@ +// 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.metrics; + +/** + * Metric whose value is supplied when the trigger is invoked. + * + * <pre> + * CallbackMetric0<Long> hits = metricMaker.newCallbackMetric("hits", ...); + * CallbackMetric0<Long> total = metricMaker.newCallbackMetric("total", ...); + * metricMaker.newTrigger(hits, total, new Runnable() { + * public void run() { + * hits.set(1); + * total.set(5); + * } + * }); + * </pre> + * + * @param <V> type of the metric value, typically Integer or Long. + */ +public abstract class CallbackMetric0<V> implements CallbackMetric<V> { + /** + * Supply the current value of the metric. + * + * @param value current value. + */ + public abstract void set(V value); + + @Override + public void prune() {} +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java new file mode 100644 index 0000000..0888e21 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/CallbackMetric1.java
@@ -0,0 +1,39 @@ +// 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.metrics; + +/** + * Metric whose value is supplied when the trigger is invoked. + * + * @param <F1> type of the field. + * @param <V> type of the metric value, typically Integer or Long. + */ +public abstract class CallbackMetric1<F1, V> implements CallbackMetric<V> { + /** + * Supply the current value of the metric. + * + * @param field1 bucket to increment. + * @param value current value. + */ + public abstract void set(F1 field1, V value); + + /** Ensure a zeroed metric is created for the field value. */ + public abstract void forceCreate(F1 field1); + + /** Prune any submetrics that were not assigned during this trigger. */ + @Override + public void prune() { + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter0.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter0.java new file mode 100644 index 0000000..a2af7e4 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter0.java
@@ -0,0 +1,41 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Metric whose value increments during the life of the process. + * <p> + * Suitable uses are "total requests handled", "bytes sent", etc. + * Use {@link Description#setRate()} to suggest the monitoring system + * should also track the rate of increments if this is of interest. + * <p> + * For an instantaneous read of a value that can change over time + * (e.g. "memory in use") use a {@link CallbackMetric}. + */ +public abstract class Counter0 implements RegistrationHandle { + /** Increment the counter by one event. */ + public void increment() { + incrementBy(1); + } + + /** + * Increment the counter by a specified amount. + * + * @param value value to increment by, must be >= 0. + */ + public abstract void incrementBy(long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter1.java new file mode 100644 index 0000000..8627dcc --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter1.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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Metric whose value increments during the life of the process. + * <p> + * Suitable uses are "total requests handled", "bytes sent", etc. + * Use {@link Description#setRate()} to suggest the monitoring system + * should also track the rate of increments if this is of interest. + * <p> + * For an instantaneous read of a value that can change over time + * (e.g. "memory in use") use a {@link CallbackMetric}. + * + * @param <F1> type of the field. + */ +public abstract class Counter1<F1> implements RegistrationHandle { + /** Increment the counter by one event. */ + public void increment(F1 field1) { + incrementBy(field1, 1); + } + + /** + * Increment the counter by a specified amount. + * + * @param field1 bucket to increment. + * @param value value to increment by, must be >= 0. + */ + public abstract void incrementBy(F1 field1, long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter2.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter2.java new file mode 100644 index 0000000..40efe95 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter2.java
@@ -0,0 +1,46 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Metric whose value increments during the life of the process. + * <p> + * Suitable uses are "total requests handled", "bytes sent", etc. + * Use {@link Description#setRate()} to suggest the monitoring system + * should also track the rate of increments if this is of interest. + * <p> + * For an instantaneous read of a value that can change over time + * (e.g. "memory in use") use a {@link CallbackMetric}. + * + * @param <F1> type of the field. + * @param <F2> type of the field. + */ +public abstract class Counter2<F1, F2> implements RegistrationHandle { + /** Increment the counter by one event. */ + public void increment(F1 field1, F2 field2) { + incrementBy(field1, field2, 1); + } + + /** + * Increment the counter by a specified amount. + * + * @param field1 bucket to increment. + * @param field2 bucket to increment. + * @param value value to increment by, must be >= 0. + */ + public abstract void incrementBy(F1 field1, F2 field2, long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter3.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter3.java new file mode 100644 index 0000000..d722930 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Counter3.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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Metric whose value increments during the life of the process. + * <p> + * Suitable uses are "total requests handled", "bytes sent", etc. + * Use {@link Description#setRate()} to suggest the monitoring system + * should also track the rate of increments if this is of interest. + * <p> + * For an instantaneous read of a value that can change over time + * (e.g. "memory in use") use a {@link CallbackMetric}. + * + * @param <F1> type of the field. + * @param <F2> type of the field. + * @param <F3> type of the field. + */ +public abstract class Counter3<F1, F2, F3> implements RegistrationHandle { + /** Increment the counter by one event. */ + public void increment(F1 field1, F2 field2, F3 field3) { + incrementBy(field1, field2, field3, 1); + } + + /** + * Increment the counter by a specified amount. + * + * @param field1 bucket to increment. + * @param field2 bucket to increment. + * @param field3 bucket to increment. + * @param value value to increment by, must be >= 0. + */ + public abstract void incrementBy(F1 field1, F2 field2, F3 field3, long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java new file mode 100644 index 0000000..43e5c8a --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Description.java
@@ -0,0 +1,188 @@ +// 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.metrics; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** Describes a metric created by {@link MetricMaker}. */ +public class Description { + public static final String DESCRIPTION = "DESCRIPTION"; + public static final String UNIT = "UNIT"; + public static final String CUMULATIVE = "CUMULATIVE"; + public static final String RATE = "RATE"; + public static final String GAUGE = "GAUGE"; + public static final String CONSTANT = "CONSTANT"; + public static final String FIELD_ORDERING = "FIELD_ORDERING"; + public static final String TRUE_VALUE = "1"; + + public static class Units { + public static final String SECONDS = "seconds"; + public static final String MILLISECONDS = "milliseconds"; + public static final String MICROSECONDS = "microseconds"; + public static final String NANOSECONDS = "nanoseconds"; + + public static final String BYTES = "bytes"; + + private Units() { + } + } + + public static enum FieldOrdering { + /** Default ordering places fields at end of the parent metric name. */ + AT_END, + + /** + * Splits the metric name by inserting field values before the last '/' in + * the metric name. For example {@code "plugins/replication/push_latency"} + * with a {@code Field.ofString("remote")} will create submetrics named + * {@code "plugins/replication/some-server/push_latency"}. + */ + PREFIX_FIELDS_BASENAME; + } + + private final Map<String, String> annotations; + + /** + * Describe a metric. + * + * @param helpText a short one-sentence string explaining the values captured + * by the metric. This may be made available to administrators as + * documentation in the reporting tools. + */ + public Description(String helpText) { + annotations = Maps.newLinkedHashMapWithExpectedSize(4); + annotations.put(DESCRIPTION, helpText); + } + + /** Unit used to describe the value, e.g. "requests", "seconds", etc. */ + public Description setUnit(String unitName) { + annotations.put(UNIT, unitName); + return this; + } + + /** + * Mark the value as constant for the life of this process. Typically used for + * software versions, command line arguments, etc. that cannot change without + * a process restart. + */ + public Description setConstant() { + annotations.put(CONSTANT, TRUE_VALUE); + return this; + } + + /** + * Indicates the metric may be usefully interpreted as a count over short + * periods of time, such as request arrival rate. May only be applied to a + * {@link Counter0}. + */ + public Description setRate() { + annotations.put(RATE, TRUE_VALUE); + return this; + } + + /** + * Instantaneously sampled value that may increase or decrease at a later + * time. Memory allocated or open network connections are examples of gauges. + */ + public Description setGauge() { + annotations.put(GAUGE, TRUE_VALUE); + return this; + } + + /** + * Indicates the metric accumulates over the lifespan of the process. A + * {@link Counter0} like total requests handled accumulates over the process + * and should be {@code setCumulative()}. + */ + public Description setCumulative() { + annotations.put(CUMULATIVE, TRUE_VALUE); + return this; + } + + /** Configure how fields are ordered into submetric names. */ + public Description setFieldOrdering(FieldOrdering ordering) { + annotations.put(FIELD_ORDERING, ordering.name()); + return this; + } + + /** True if the metric value never changes after startup. */ + public boolean isConstant() { + return TRUE_VALUE.equals(annotations.get(CONSTANT)); + } + + /** True if the metric may be interpreted as a rate over time. */ + public boolean isRate() { + return TRUE_VALUE.equals(annotations.get(RATE)); + } + + /** True if the metric is an instantaneous sample. */ + public boolean isGauge() { + return TRUE_VALUE.equals(annotations.get(GAUGE)); + } + + /** True if the metric accumulates over the lifespan of the process. */ + public boolean isCumulative() { + return TRUE_VALUE.equals(annotations.get(CUMULATIVE)); + } + + /** Get the suggested field ordering. */ + public FieldOrdering getFieldOrdering() { + String o = annotations.get(FIELD_ORDERING); + return o != null ? FieldOrdering.valueOf(o) : FieldOrdering.AT_END; + } + + /** + * Decode the unit as a unit of time. + * + * @return valid time unit. + * @throws IllegalArgumentException if the unit is not a valid unit of time. + */ + public TimeUnit getTimeUnit() { + return getTimeUnit(annotations.get(UNIT)); + } + + private static final ImmutableMap<String, TimeUnit> TIME_UNITS = ImmutableMap.of( + Units.NANOSECONDS, TimeUnit.NANOSECONDS, + Units.MICROSECONDS, TimeUnit.MICROSECONDS, + Units.MILLISECONDS, TimeUnit.MILLISECONDS, + Units.SECONDS, TimeUnit.SECONDS); + + public static TimeUnit getTimeUnit(String unit) { + if (Strings.isNullOrEmpty(unit)) { + throw new IllegalArgumentException("no unit configured"); + } + TimeUnit u = TIME_UNITS.get(unit); + if (u == null) { + throw new IllegalArgumentException(String.format( + "unit %s not TimeUnit", unit)); + } + return u; + } + + /** Immutable copy of all annotations (configurable properties). */ + public ImmutableMap<String, String> getAnnotations() { + return ImmutableMap.copyOf(annotations); + } + + @Override + public String toString() { + return annotations.toString(); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java new file mode 100644 index 0000000..1b05e2c --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/DisabledMetricMaker.java
@@ -0,0 +1,155 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** Exports no metrics, useful for running batch programs. */ +public class DisabledMetricMaker extends MetricMaker { + @Override + public Counter0 newCounter(String name, Description desc) { + return new Counter0() { + @Override public void incrementBy(long value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1> Counter1<F1> newCounter(String name, Description desc, + Field<F1> field1) { + return new Counter1<F1>() { + @Override public void incrementBy(F1 field1, long value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, F2> Counter2<F1, F2> newCounter(String name, Description desc, + Field<F1> field1, Field<F2> field2) { + return new Counter2<F1, F2>() { + @Override public void incrementBy(F1 field1, F2 field2, long value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, F2, F3> Counter3<F1, F2, F3> newCounter(String name, + Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) { + return new Counter3<F1, F2, F3>() { + @Override public void incrementBy(F1 field1, F2 field2, F3 field3, long value) {} + @Override public void remove() {} + }; + } + + @Override + public Timer0 newTimer(String name, Description desc) { + return new Timer0() { + @Override public void record(long value, TimeUnit unit) {} + @Override public void remove() {} + }; + } + + @Override + public <F1> Timer1<F1> newTimer(String name, Description desc, + Field<F1> field1) { + return new Timer1<F1>() { + @Override public void record(F1 field1, long value, TimeUnit unit) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, F2> Timer2<F1, F2> newTimer(String name, Description desc, + Field<F1> field1, Field<F2> field2) { + return new Timer2<F1, F2>() { + @Override public void record(F1 field1, F2 field2, long value, TimeUnit unit) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, F2, F3> Timer3<F1, F2, F3> newTimer(String name, + Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) { + return new Timer3<F1, F2, F3>() { + @Override public void record(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {} + @Override public void remove() {} + }; + } + + @Override + public Histogram0 newHistogram(String name, Description desc) { + return new Histogram0() { + @Override public void record(long value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1> Histogram1<F1> newHistogram(String name, Description desc, + Field<F1> field1) { + return new Histogram1<F1>() { + @Override public void record(F1 field1, long value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, F2> Histogram2<F1, F2> newHistogram(String name, Description desc, + Field<F1> field1, Field<F2> field2) { + return new Histogram2<F1, F2>() { + @Override public void record(F1 field1, F2 field2, long value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram(String name, + Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) { + return new Histogram3<F1, F2, F3>() { + @Override public void record(F1 field1, F2 field2, F3 field3, long value) {} + @Override public void remove() {} + }; + } + + @Override + public <V> CallbackMetric0<V> newCallbackMetric(String name, + Class<V> valueClass, Description desc) { + return new CallbackMetric0<V>() { + @Override public void set(V value) {} + @Override public void remove() {} + }; + } + + @Override + public <F1, V> CallbackMetric1<F1, V> newCallbackMetric(String name, + Class<V> valueClass, Description desc, Field<F1> field1) { + return new CallbackMetric1<F1, V>() { + @Override public void set(F1 field1, V value) {} + @Override public void forceCreate(F1 field1) {} + @Override public void remove() {} + }; + } + + @Override + public RegistrationHandle newTrigger(Set<CallbackMetric<?>> metrics, + Runnable trigger) { + return new RegistrationHandle() { + @Override public void remove() {} + }; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java new file mode 100644 index 0000000..a91e428 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java
@@ -0,0 +1,136 @@ +// 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.metrics; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Function; +import com.google.common.base.Functions; + +/** Describes a bucketing field used by a metric. */ +public class Field<T> { + /** Break down metrics by boolean true/false. */ + public static Field<Boolean> ofBoolean(String name) { + return ofBoolean(name, null); + } + + /** Break down metrics by boolean true/false. */ + public static Field<Boolean> ofBoolean(String name, String description) { + return new Field<>(name, Boolean.class, description); + } + + /** Break down metrics by cases of an enum. */ + public static <E extends Enum<E>> Field<E> ofEnum(Class<E> enumType, + String name) { + return ofEnum(enumType, name, null); + } + + /** Break down metrics by cases of an enum. */ + public static <E extends Enum<E>> Field<E> ofEnum(Class<E> enumType, + String name, String description) { + return new Field<>(name, enumType, description); + } + + /** + * Break down metrics by string. + * <p> + * Each unique string will allocate a new submetric. <b>Do not use user + * content as a field value</b> as field values are never reclaimed. + */ + public static Field<String> ofString(String name) { + return ofString(name, null); + } + + /** + * Break down metrics by string. + * <p> + * Each unique string will allocate a new submetric. <b>Do not use user + * content as a field value</b> as field values are never reclaimed. + */ + public static Field<String> ofString(String name, String description) { + return new Field<>(name, String.class, description); + } + + /** + * Break down metrics by integer. + * <p> + * Each unique integer will allocate a new submetric. <b>Do not use user + * content as a field value</b> as field values are never reclaimed. + */ + public static Field<Integer> ofInteger(String name) { + return ofInteger(name, null); + } + + /** + * Break down metrics by integer. + * <p> + * Each unique integer will allocate a new submetric. <b>Do not use user + * content as a field value</b> as field values are never reclaimed. + */ + public static Field<Integer> ofInteger(String name, String description) { + return new Field<>(name, Integer.class, description); + } + + private final String name; + private final Class<T> keyType; + private final Function<T, String> formatter; + private final String description; + + private Field(String name, Class<T> keyType, String description) { + checkArgument(name.matches("^[a-z_]+$"), "name must match [a-z_]"); + this.name = name; + this.keyType = keyType; + this.formatter = initFormatter(keyType); + this.description = description; + } + + /** Name of this field within the metric. */ + public String getName() { + return name; + } + + /** Type of value used within the field. */ + public Class<T> getType() { + return keyType; + } + + /** Description text for the field explaining its range of values. */ + public String getDescription() { + return description; + } + + public Function<T, String> formatter() { + return formatter; + } + + @SuppressWarnings("unchecked") + private static <T> Function<T, String> initFormatter(Class<T> keyType) { + if (keyType == String.class) { + return (Function<T, String>) Functions.<String> identity(); + + } else if (keyType == Integer.class || keyType == Boolean.class) { + return (Function<T, String>) Functions.toStringFunction(); + + } else if (Enum.class.isAssignableFrom(keyType)) { + return new Function<T, String>() { + @Override + public String apply(T in) { + return ((Enum<?>) in).name(); + } + }; + } + throw new IllegalStateException("unsupported type " + keyType.getName()); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram0.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram0.java new file mode 100644 index 0000000..3a6fbd9 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram0.java
@@ -0,0 +1,27 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Measures the statistical distribution of values in a stream of data. + * <p> + * Suitable uses are "response size in bytes", etc. + */ +public abstract class Histogram0 implements RegistrationHandle { + /** Record a sample of a specified amount. */ + public abstract void record(long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram1.java new file mode 100644 index 0000000..16df8e4 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram1.java
@@ -0,0 +1,29 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Measures the statistical distribution of values in a stream of data. + * <p> + * Suitable uses are "response size in bytes", etc. + * + * @param <F1> type of the field. + */ +public abstract class Histogram1<F1> implements RegistrationHandle { + /** Record a sample of a specified amount. */ + public abstract void record(F1 field1, long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram2.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram2.java new file mode 100644 index 0000000..2eca402 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram2.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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Measures the statistical distribution of values in a stream of data. + * <p> + * Suitable uses are "response size in bytes", etc. + * + * @param <F1> type of the field. + * @param <F2> type of the field. + */ +public abstract class Histogram2<F1, F2> implements RegistrationHandle { + /** Record a sample of a specified amount. */ + public abstract void record(F1 field1, F2 field2, long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram3.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram3.java new file mode 100644 index 0000000..09a756d --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Histogram3.java
@@ -0,0 +1,31 @@ +// 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.metrics; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +/** + * Measures the statistical distribution of values in a stream of data. + * <p> + * Suitable uses are "response size in bytes", etc. + * + * @param <F1> type of the field. + * @param <F2> type of the field. + * @param <F3> type of the field. + */ +public abstract class Histogram3<F1, F2, F3> implements RegistrationHandle { + /** Record a sample of a specified amount. */ + public abstract void record(F1 field1, F2 field2, F3 field3, long value); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java new file mode 100644 index 0000000..2122594 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/MetricMaker.java
@@ -0,0 +1,137 @@ +// 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.metrics; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.extensions.registration.RegistrationHandle; + +import java.util.Set; + +/** Factory to create metrics for monitoring. */ +public abstract class MetricMaker { + /** Metric whose value increments during the life of the process. */ + public abstract Counter0 newCounter(String name, Description desc); + public abstract <F1> Counter1<F1> newCounter( + String name, Description desc, + Field<F1> field1); + public abstract <F1, F2> Counter2<F1, F2> newCounter( + String name, Description desc, + Field<F1> field1, Field<F2> field2); + public abstract <F1, F2, F3> Counter3<F1, F2, F3> newCounter( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3); + + /** Metric recording time spent on an operation. */ + public abstract Timer0 newTimer(String name, Description desc); + public abstract <F1> Timer1<F1> newTimer( + String name, Description desc, + Field<F1> field1); + public abstract <F1, F2> Timer2<F1, F2> newTimer( + String name, Description desc, + Field<F1> field1, Field<F2> field2); + public abstract <F1, F2, F3> Timer3<F1, F2, F3> newTimer( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3); + + /** Metric statistical distribution of values. */ + public abstract Histogram0 newHistogram(String name, Description desc); + public abstract <F1> Histogram1<F1> newHistogram( + String name, Description desc, + Field<F1> field1); + public abstract <F1, F2> Histogram2<F1, F2> newHistogram( + String name, Description desc, + Field<F1> field1, Field<F2> field2); + public abstract <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3); + + /** + * Constant value that does not change. + * + * @param name unique name of the metric. + * @param value only value of the metric. + * @param desc description of the metric. + */ + public <V> void newConstantMetric(String name, final V value, Description desc) { + desc.setConstant(); + + @SuppressWarnings("unchecked") + Class<V> type = (Class<V>) value.getClass(); + final CallbackMetric0<V> metric = newCallbackMetric(name, type, desc); + newTrigger(metric, new Runnable() { + @Override + public void run() { + metric.set(value); + } + }); + } + + /** + * Instantaneous reading of a value. + * + * <pre> + * metricMaker.newCallbackMetric("memory", + * new Description("Total bytes of memory used") + * .setGauge() + * .setUnit(Units.BYTES), + * new Supplier<Long>() { + * public Long get() { + * return Runtime.getRuntime().totalMemory(); + * } + * }); + * </pre> + * + * @param name unique name of the metric. + * @param valueClass type of value recorded by the metric. + * @param desc description of the metric. + * @param trigger function to compute the value of the metric. + */ + public <V> void newCallbackMetric(String name, + Class<V> valueClass, Description desc, final Supplier<V> trigger) { + final CallbackMetric0<V> metric = newCallbackMetric(name, valueClass, desc); + newTrigger(metric, new Runnable() { + @Override + public void run() { + metric.set(trigger.get()); + } + }); + } + + /** Instantaneous reading of a single value. */ + public abstract <V> CallbackMetric0<V> newCallbackMetric( + String name, Class<V> valueClass, Description desc); + public abstract <F1, V> CallbackMetric1<F1, V> newCallbackMetric( + String name, Class<V> valueClass, Description desc, + Field<F1> field1); + + /** Connect logic to populate a previously created {@link CallbackMetric}. */ + public RegistrationHandle newTrigger(CallbackMetric<?> metric1, Runnable trigger) { + return newTrigger(ImmutableSet.<CallbackMetric<?>>of(metric1), trigger); + } + + public RegistrationHandle newTrigger(CallbackMetric<?> metric1, + CallbackMetric<?> metric2, Runnable trigger) { + return newTrigger(ImmutableSet.of(metric1, metric2), trigger); + } + + public RegistrationHandle newTrigger(CallbackMetric<?> metric1, + CallbackMetric<?> metric2, CallbackMetric<?> metric3, Runnable trigger) { + return newTrigger(ImmutableSet.of(metric1, metric2, metric3), trigger); + } + + public abstract RegistrationHandle newTrigger(Set<CallbackMetric<?>> metrics, + Runnable trigger); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer0.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer0.java new file mode 100644 index 0000000..ff0735e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer0.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.metrics; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +import java.util.concurrent.TimeUnit; + +/** + * Records elapsed time for an operation or span. + * <p> + * Typical usage in a try-with-resources block: + * + * <pre> + * try (Timer.Context ctx = timer.start()) { + * } + * </pre> + */ +public abstract class Timer0 implements RegistrationHandle { + public static class Context extends TimerContext { + private final Timer0 timer; + + Context(Timer0 timer) { + this.timer = timer; + } + + @Override + public void record(long elapsed) { + timer.record(elapsed, NANOSECONDS); + } + } + + /** Begin a timer for the current block, value will be recorded when closed. */ + public Context start() { + return new Context(this); + } + + /** Record a value in the distribution. */ + public abstract void record(long value, TimeUnit unit); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer1.java new file mode 100644 index 0000000..e0bc4fc --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer1.java
@@ -0,0 +1,59 @@ +// 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.metrics; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +import java.util.concurrent.TimeUnit; + +/** + * Records elapsed time for an operation or span. + * <p> + * Typical usage in a try-with-resources block: + * + * <pre> + * try (Timer1.Context ctx = timer.start(field)) { + * } + * </pre> + * + * @param <F1> type of the field. + */ +public abstract class Timer1<F1> implements RegistrationHandle { + public static class Context extends TimerContext { + private final Timer1<Object> timer; + private final Object field1; + + @SuppressWarnings("unchecked") + <F1> Context(Timer1<F1> timer, F1 field1) { + this.timer = (Timer1<Object>) timer; + this.field1 = field1; + } + + @Override + public void record(long elapsed) { + timer.record(field1, elapsed, NANOSECONDS); + } + } + + /** Begin a timer for the current block, value will be recorded when closed. */ + public Context start(F1 field1) { + return new Context(this, field1); + } + + /** Record a value in the distribution. */ + public abstract void record(F1 field1, long value, TimeUnit unit); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer2.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer2.java new file mode 100644 index 0000000..5bfaba0 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer2.java
@@ -0,0 +1,62 @@ +// 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.metrics; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +import java.util.concurrent.TimeUnit; + +/** + * Records elapsed time for an operation or span. + * <p> + * Typical usage in a try-with-resources block: + * + * <pre> + * try (Timer2.Context ctx = timer.start(field)) { + * } + * </pre> + * + * @param <F1> type of the field. + * @param <F2> type of the field. + */ +public abstract class Timer2<F1, F2> implements RegistrationHandle { + public static class Context extends TimerContext { + private final Timer2<Object, Object> timer; + private final Object field1; + private final Object field2; + + @SuppressWarnings("unchecked") + <F1, F2> Context(Timer2<F1, F2> timer, F1 field1, F2 field2) { + this.timer = (Timer2<Object, Object>) timer; + this.field1 = field1; + this.field2 = field2; + } + + @Override + public void record(long elapsed) { + timer.record(field1, field2, elapsed, NANOSECONDS); + } + } + + /** Begin a timer for the current block, value will be recorded when closed. */ + public Context start(F1 field1, F2 field2) { + return new Context(this, field1, field2); + } + + /** Record a value in the distribution. */ + public abstract void record(F1 field1, F2 field2, long value, TimeUnit unit); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer3.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer3.java new file mode 100644 index 0000000..c564d42 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Timer3.java
@@ -0,0 +1,66 @@ +// 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.metrics; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.gerrit.extensions.registration.RegistrationHandle; + +import java.util.concurrent.TimeUnit; + +/** + * Records elapsed time for an operation or span. + * <p> + * Typical usage in a try-with-resources block: + * + * <pre> + * try (Timer3.Context ctx = timer.start(field)) { + * } + * </pre> + * + * @param <F1> type of the field. + * @param <F2> type of the field. + * @param <F3> type of the field. + */ +public abstract class Timer3<F1, F2, F3> implements RegistrationHandle { + public static class Context extends TimerContext { + private final Timer3<Object, Object, Object> timer; + private final Object field1; + private final Object field2; + private final Object field3; + + @SuppressWarnings("unchecked") + <F1, F2, F3> Context(Timer3<F1, F2, F3> timer, F1 f1, F2 f2, F3 f3) { + this.timer = (Timer3<Object, Object, Object>) timer; + this.field1 = f1; + this.field2 = f2; + this.field3 = f3; + } + + @Override + public void record(long elapsed) { + timer.record(field1, field2, field3, elapsed, NANOSECONDS); + } + } + + /** Begin a timer for the current block, value will be recorded when closed. */ + public Context start(F1 field1, F2 field2, F3 field3) { + return new Context(this, field1, field2, field3); + } + + /** Record a value in the distribution. */ + public abstract void record(F1 field1, F2 field2, F3 field3, + long value, TimeUnit unit); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/TimerContext.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/TimerContext.java new file mode 100644 index 0000000..32ef753 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/TimerContext.java
@@ -0,0 +1,59 @@ +// 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.metrics; + +abstract class TimerContext implements AutoCloseable { + private final long startNanos; + private boolean stopped; + + TimerContext() { + this.startNanos = System.nanoTime(); + } + + /** + * Record the elapsed time to the timer. + * + * @param elapsed Elapsed time in nanoseconds. + */ + public abstract void record(long elapsed); + + /** Get the start time in system time nanoseconds. */ + public long getStartTime() { + return startNanos; + } + + /** + * Stop the timer and record the elapsed time. + * + * @return the elapsed time in nanoseconds. + * @throws IllegalStateException if the timer is already stopped. + */ + public long stop() { + if (!stopped) { + stopped = true; + long elapsed = System.nanoTime() - startNanos; + record(elapsed); + return elapsed; + } + throw new IllegalStateException("Already stopped"); + } + + @Override + public void close() { + if (!stopped) { + stop(); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java new file mode 100644 index 0000000..629cf96 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
@@ -0,0 +1,149 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Abstract callback metric broken down into buckets. */ +abstract class BucketedCallback<V> implements BucketedMetric { + private final DropWizardMetricMaker metrics; + private final MetricRegistry registry; + private final String name; + private final Description.FieldOrdering ordering; + protected final Field<?>[] fields; + private final V zero; + private final Map<Object, ValueGauge> cells; + protected volatile Runnable trigger; + + BucketedCallback(DropWizardMetricMaker metrics, MetricRegistry registry, + String name, Class<V> valueType, Description desc, Field<?>... fields) { + this.metrics = metrics; + this.registry = registry; + this.name = name; + this.ordering = desc.getFieldOrdering(); + this.fields = fields; + this.zero = CallbackMetricImpl0.zeroFor(valueType); + this.cells = new ConcurrentHashMap<>(); + } + + void doRemove() { + for (Object key : cells.keySet()) { + registry.remove(submetric(key)); + } + metrics.remove(name); + } + + void doBeginSet() { + for (ValueGauge g : cells.values()) { + g.set = false; + } + } + + void doPrune() { + Iterator<Map.Entry<Object, ValueGauge>> i = cells.entrySet().iterator(); + while (i.hasNext()) { + if (!i.next().getValue().set) { + i.remove(); + } + } + } + + void doEndSet() { + for (ValueGauge g : cells.values()) { + if (!g.set) { + g.value = zero; + } + } + } + + ValueGauge getOrCreate(Object f1, Object f2) { + return getOrCreate(ImmutableList.of(f1, f2)); + } + + ValueGauge getOrCreate(Object f1, Object f2, Object f3) { + return getOrCreate(ImmutableList.of(f1, f2, f3)); + } + + ValueGauge getOrCreate(Object key) { + ValueGauge c = cells.get(key); + if (c != null) { + return c; + } + + synchronized (cells) { + c = cells.get(key); + if (c == null) { + c = new ValueGauge(); + registry.register(submetric(key), c); + cells.put(key, c); + } + return c; + } + } + + private String submetric(Object key) { + return DropWizardMetricMaker.name(ordering, name, name(key)); + } + + abstract String name(Object key); + + @Override + public Metric getTotal() { + return null; + } + + @Override + public Field<?>[] getFields() { + return fields; + } + + @Override + public Map<Object, Metric> getCells() { + return Maps.transformValues( + cells, + new Function<ValueGauge, Metric> () { + @Override + public Metric apply(ValueGauge in) { + return in; + } + }); + } + + final class ValueGauge implements Gauge<V> { + volatile V value = zero; + boolean set; + + @Override + public V getValue() { + Runnable t = trigger; + if (t != null) { + t.run(); + } + return value; + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java new file mode 100644 index 0000000..22af5ca --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java
@@ -0,0 +1,109 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker.CounterImpl; + +import com.codahale.metrics.Metric; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Abstract counter broken down into buckets by {@link Field} values. */ +abstract class BucketedCounter implements BucketedMetric { + private final DropWizardMetricMaker metrics; + private final String name; + private final boolean isRate; + private final Description.FieldOrdering ordering; + protected final Field<?>[] fields; + protected final CounterImpl total; + private final Map<Object, CounterImpl> cells; + + BucketedCounter(DropWizardMetricMaker metrics, + String name, Description desc, Field<?>... fields) { + this.metrics = metrics; + this.name = name; + this.isRate = desc.isRate(); + this.ordering = desc.getFieldOrdering(); + this.fields = fields; + this.total = metrics.newCounterImpl(name + "_total", isRate); + this.cells = new ConcurrentHashMap<>(); + } + + void doRemove() { + for (CounterImpl c : cells.values()) { + c.remove(); + } + total.remove(); + metrics.remove(name); + } + + CounterImpl forceCreate(Object f1, Object f2) { + return forceCreate(ImmutableList.of(f1, f2)); + } + + CounterImpl forceCreate(Object f1, Object f2, Object f3) { + return forceCreate(ImmutableList.of(f1, f2, f3)); + } + + CounterImpl forceCreate(Object key) { + CounterImpl c = cells.get(key); + if (c != null) { + return c; + } + + synchronized (cells) { + c = cells.get(key); + if (c == null) { + c = metrics.newCounterImpl(submetric(key), isRate); + cells.put(key, c); + } + return c; + } + } + + private String submetric(Object key) { + return DropWizardMetricMaker.name(ordering, name, name(key)); + } + + abstract String name(Object key); + + @Override + public Metric getTotal() { + return total.metric; + } + + @Override + public Field<?>[] getFields() { + return fields; + } + + @Override + public Map<Object, Metric> getCells() { + return Maps.transformValues( + cells, + new Function<CounterImpl, Metric> () { + @Override + public Metric apply(CounterImpl in) { + return in.metric; + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java new file mode 100644 index 0000000..51c5fea --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java
@@ -0,0 +1,107 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker.HistogramImpl; + +import com.codahale.metrics.Metric; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Abstract histogram broken down into buckets by {@link Field} values. */ +abstract class BucketedHistogram implements BucketedMetric { + private final DropWizardMetricMaker metrics; + private final String name; + private final Description.FieldOrdering ordering; + protected final Field<?>[] fields; + protected final HistogramImpl total; + private final Map<Object, HistogramImpl> cells; + + BucketedHistogram(DropWizardMetricMaker metrics, String name, + Description desc, Field<?>... fields) { + this.metrics = metrics; + this.name = name; + this.ordering = desc.getFieldOrdering(); + this.fields = fields; + this.total = metrics.newHistogramImpl(name + "_total"); + this.cells = new ConcurrentHashMap<>(); + } + + void doRemove() { + for (HistogramImpl c : cells.values()) { + c.remove(); + } + total.remove(); + metrics.remove(name); + } + + HistogramImpl forceCreate(Object f1, Object f2) { + return forceCreate(ImmutableList.of(f1, f2)); + } + + HistogramImpl forceCreate(Object f1, Object f2, Object f3) { + return forceCreate(ImmutableList.of(f1, f2, f3)); + } + + HistogramImpl forceCreate(Object key) { + HistogramImpl c = cells.get(key); + if (c != null) { + return c; + } + + synchronized (cells) { + c = cells.get(key); + if (c == null) { + c = metrics.newHistogramImpl(submetric(key)); + cells.put(key, c); + } + return c; + } + } + + private String submetric(Object key) { + return DropWizardMetricMaker.name(ordering, name, name(key)); + } + + abstract String name(Object key); + + @Override + public Metric getTotal() { + return total.metric; + } + + @Override + public Field<?>[] getFields() { + return fields; + } + + @Override + public Map<Object, Metric> getCells() { + return Maps.transformValues( + cells, + new Function<HistogramImpl, Metric> () { + @Override + public Metric apply(HistogramImpl in) { + return in.metric; + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedMetric.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedMetric.java new file mode 100644 index 0000000..799e594 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedMetric.java
@@ -0,0 +1,29 @@ +// 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.metrics.dropwizard; + +import com.google.gerrit.common.Nullable; +import com.google.gerrit.metrics.Field; + +import com.codahale.metrics.Metric; + +import java.util.Map; + +/** Metric broken down into buckets by {@link Field} values. */ +interface BucketedMetric extends Metric { + @Nullable Metric getTotal(); + Field<?>[] getFields(); + Map<?, Metric> getCells(); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java new file mode 100644 index 0000000..ec12e00 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
@@ -0,0 +1,107 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker.TimerImpl; + +import com.codahale.metrics.Metric; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Abstract timer broken down into buckets by {@link Field} values. */ +abstract class BucketedTimer implements BucketedMetric { + private final DropWizardMetricMaker metrics; + private final String name; + private final Description.FieldOrdering ordering; + protected final Field<?>[] fields; + protected final TimerImpl total; + private final Map<Object, TimerImpl> cells; + + BucketedTimer(DropWizardMetricMaker metrics, String name, + Description desc, Field<?>... fields) { + this.metrics = metrics; + this.name = name; + this.ordering = desc.getFieldOrdering(); + this.fields = fields; + this.total = metrics.newTimerImpl(name + "_total"); + this.cells = new ConcurrentHashMap<>(); + } + + void doRemove() { + for (TimerImpl c : cells.values()) { + c.remove(); + } + total.remove(); + metrics.remove(name); + } + + TimerImpl forceCreate(Object f1, Object f2) { + return forceCreate(ImmutableList.of(f1, f2)); + } + + TimerImpl forceCreate(Object f1, Object f2, Object f3) { + return forceCreate(ImmutableList.of(f1, f2, f3)); + } + + TimerImpl forceCreate(Object key) { + TimerImpl c = cells.get(key); + if (c != null) { + return c; + } + + synchronized (cells) { + c = cells.get(key); + if (c == null) { + c = metrics.newTimerImpl(submetric(key)); + cells.put(key, c); + } + return c; + } + } + + private String submetric(Object key) { + return DropWizardMetricMaker.name(ordering, name, name(key)); + } + + abstract String name(Object key); + + @Override + public Metric getTotal() { + return total.metric; + } + + @Override + public Field<?>[] getFields() { + return fields; + } + + @Override + public Map<Object, Metric> getCells() { + return Maps.transformValues( + cells, + new Function<TimerImpl, Metric> () { + @Override + public Metric apply(TimerImpl in) { + return in.metric; + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackGroup.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackGroup.java new file mode 100644 index 0000000..372bdcb --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackGroup.java
@@ -0,0 +1,70 @@ +// 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.metrics.dropwizard; + +import com.google.common.collect.ImmutableSet; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Run a user specified trigger only once every 2 seconds. + * <p> + * This allows the same Runnable trigger to be applied to several metrics. When + * a recorder is sampling the related metrics only the first access will perform + * recomputation. Reading other related metrics will rely on the already set + * values for the next several seconds. + */ +class CallbackGroup implements Runnable { + private static final long PERIOD = TimeUnit.SECONDS.toNanos(2); + + private final AtomicLong reloadAt; + private final Runnable trigger; + private final ImmutableSet<CallbackMetricGlue> metrics; + private final Object reloadLock = new Object(); + + CallbackGroup(Runnable trigger, ImmutableSet<CallbackMetricGlue> metrics) { + this.reloadAt = new AtomicLong(0); + this.trigger = trigger; + this.metrics = metrics; + } + + @Override + public void run() { + if (reload()) { + synchronized (reloadLock) { + for (CallbackMetricGlue m : metrics) { + m.beginSet(); + } + trigger.run(); + for (CallbackMetricGlue m : metrics) { + m.endSet(); + } + } + } + } + + private boolean reload() { + for (;;) { + long now = System.nanoTime(); + long next = reloadAt.get(); + if (next > now) { + return false; + } else if (reloadAt.compareAndSet(next, now + PERIOD)) { + return true; + } + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java new file mode 100644 index 0000000..4f5b7ad --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricGlue.java
@@ -0,0 +1,22 @@ +// 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.metrics.dropwizard; + +interface CallbackMetricGlue { + void beginSet(); + void endSet(); + void register(Runnable trigger); + void remove(); +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.java new file mode 100644 index 0000000..dcab692 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl0.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.metrics.dropwizard; + +import com.google.gerrit.metrics.CallbackMetric0; + +import com.codahale.metrics.MetricRegistry; + +class CallbackMetricImpl0<V> + extends CallbackMetric0<V> + implements CallbackMetricGlue { + @SuppressWarnings("unchecked") + static <V> V zeroFor(Class<V> valueClass) { + if (valueClass == Integer.class) { + return (V) Integer.valueOf(0); + } else if (valueClass == Long.class) { + return (V) Long.valueOf(0); + } else if (valueClass == Double.class) { + return (V) Double.valueOf(0); + } else if (valueClass == Float.class) { + return (V) Float.valueOf(0); + } else if (valueClass == String.class) { + return (V) ""; + } else if (valueClass == Boolean.class) { + return (V) Boolean.FALSE; + } else { + throw new IllegalArgumentException("unsupported value type " + + valueClass.getName()); + } + } + + private final DropWizardMetricMaker metrics; + private final MetricRegistry registry; + private final String name; + private volatile V value; + + CallbackMetricImpl0(DropWizardMetricMaker metrics, MetricRegistry registry, + String name, Class<V> valueType) { + this.metrics = metrics; + this.registry = registry; + this.name = name; + this.value = zeroFor(valueType); + } + + @Override + public void beginSet() { + } + + @Override + public void endSet() { + } + + @Override + public void set(V value) { + this.value = value; + } + + @Override + public void remove() { + metrics.remove(name); + registry.remove(name); + } + + @Override + public void register(final Runnable trigger) { + registry.register(name, new com.codahale.metrics.Gauge<V>() { + @Override + public V getValue() { + trigger.run(); + return value; + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java new file mode 100644 index 0000000..81d5ff5 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java
@@ -0,0 +1,84 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +import com.codahale.metrics.MetricRegistry; + +/** Optimized version of {@link BucketedCallback} for single dimension. */ +class CallbackMetricImpl1<F1, V> extends BucketedCallback<V> { + CallbackMetricImpl1(DropWizardMetricMaker metrics, MetricRegistry registry, + String name, Class<V> valueClass, Description desc, Field<F1> field1) { + super(metrics, registry, name, valueClass, desc, field1); + } + + CallbackMetric1<F1, V> create() { + return new Impl1(); + } + + private final class Impl1 + extends CallbackMetric1<F1, V> + implements CallbackMetricGlue { + @Override + public void beginSet() { + doBeginSet(); + } + + @Override + public void set(F1 field1, V value) { + BucketedCallback<V>.ValueGauge cell = getOrCreate(field1); + cell.value = value; + cell.set = true; + } + + @Override + public void prune() { + doPrune(); + } + + @Override + public void endSet() { + doEndSet(); + } + + @Override + public void forceCreate(F1 field1) { + getOrCreate(field1); + } + + @Override + public void register(Runnable t) { + trigger = t; + } + + @Override + public void remove() { + doRemove(); + } + } + + @Override + String name(Object field1) { + @SuppressWarnings("unchecked") + Function<Object, String> fmt = + (Function<Object, String>) fields[0].formatter(); + + return fmt.apply(field1).replace('/', '-'); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java new file mode 100644 index 0000000..25647ef --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java
@@ -0,0 +1,52 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +/** Optimized version of {@link BucketedCounter} for single dimension. */ +class CounterImpl1<F1> extends BucketedCounter { + CounterImpl1(DropWizardMetricMaker metrics, String name, Description desc, + Field<F1> field1) { + super(metrics, name, desc, field1); + } + + Counter1<F1> counter() { + return new Counter1<F1>() { + @Override + public void incrementBy(F1 field1, long value) { + total.incrementBy(value); + forceCreate(field1).incrementBy(value); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + @Override + String name(Object field1) { + @SuppressWarnings("unchecked") + Function<Object, String> fmt = + (Function<Object, String>) fields[0].formatter(); + + return fmt.apply(field1).replace('/', '-'); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java new file mode 100644 index 0000000..a2f1f84 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java
@@ -0,0 +1,75 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.gerrit.metrics.Counter2; +import com.google.gerrit.metrics.Counter3; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +/** Generalized implementation of N-dimensional counter metrics. */ +class CounterImplN extends BucketedCounter implements BucketedMetric { + CounterImplN(DropWizardMetricMaker metrics, String name, Description desc, + Field<?>... fields) { + super(metrics, name, desc, fields); + } + + <F1, F2> Counter2<F1, F2> counter2() { + return new Counter2<F1, F2>() { + @Override + public void incrementBy(F1 field1, F2 field2, long value) { + total.incrementBy(value); + forceCreate(field1, field2).incrementBy(value); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + <F1, F2, F3> Counter3<F1, F2, F3> counter3() { + return new Counter3<F1, F2, F3>() { + @Override + public void incrementBy(F1 field1, F2 field2, F3 field3, long value) { + total.incrementBy(value); + forceCreate(field1, field2, field3).incrementBy(value); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + String name(Object key) { + ImmutableList<Object> keyList = (ImmutableList<Object>) key; + String[] parts = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + Function<Object, String> fmt = + (Function<Object, String>) fields[i].formatter(); + + parts[i] = fmt.apply(keyList.get(i)).replace('/', '-'); + } + return Joiner.on('/').join(parts); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java new file mode 100644 index 0000000..8f6e5d3 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
@@ -0,0 +1,428 @@ +// 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.metrics.dropwizard; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.gerrit.metrics.dropwizard.MetricResource.METRIC_KIND; +import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.registration.RegistrationHandle; +import com.google.gerrit.extensions.restapi.RestApiModule; +import com.google.gerrit.metrics.CallbackMetric; +import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Counter2; +import com.google.gerrit.metrics.Counter3; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.FieldOrdering; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Histogram0; +import com.google.gerrit.metrics.Histogram1; +import com.google.gerrit.metrics.Histogram2; +import com.google.gerrit.metrics.Histogram3; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; +import com.google.gerrit.metrics.Timer1; +import com.google.gerrit.metrics.Timer2; +import com.google.gerrit.metrics.Timer3; +import com.google.gerrit.metrics.proc.JGitMetricModule; +import com.google.gerrit.metrics.proc.ProcMetricModule; +import com.google.gerrit.server.cache.CacheMetrics; +import com.google.inject.Inject; +import com.google.inject.Scopes; +import com.google.inject.Singleton; + +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * Connects Gerrit metric package onto DropWizard. + * + * @see <a href="http://www.dropwizard.io/">DropWizard</a> + */ +@Singleton +public class DropWizardMetricMaker extends MetricMaker { + public static class ApiModule extends RestApiModule { + @Override + protected void configure() { + bind(MetricRegistry.class).in(Scopes.SINGLETON); + bind(DropWizardMetricMaker.class).in(Scopes.SINGLETON); + bind(MetricMaker.class).to(DropWizardMetricMaker.class); + + install(new ProcMetricModule()); + install(new JGitMetricModule()); + } + } + + public static class RestModule extends RestApiModule { + @Override + protected void configure() { + DynamicMap.mapOf(binder(), METRIC_KIND); + child(CONFIG_KIND, "metrics").to(MetricsCollection.class); + get(METRIC_KIND).to(GetMetric.class); + bind(CacheMetrics.class); + } + } + + private final MetricRegistry registry; + private final Map<String, BucketedMetric> bucketed; + private final Map<String, ImmutableMap<String, String>> descriptions; + + @Inject + DropWizardMetricMaker(MetricRegistry registry) { + this.registry = registry; + this.bucketed = new ConcurrentHashMap<>(); + this.descriptions = new ConcurrentHashMap<>(); + } + + Iterable<String> getMetricNames() { + return descriptions.keySet(); + } + + /** Get the underlying metric implementation. */ + public Metric getMetric(String name) { + Metric m = bucketed.get(name); + return m != null ? m : registry.getMetrics().get(name); + } + + /** Lookup annotations from a metric's {@link Description}. */ + public ImmutableMap<String, String> getAnnotations(String name) { + return descriptions.get(name); + } + + @Override + public synchronized Counter0 newCounter(String name, Description desc) { + checkCounterDescription(name, desc); + define(name, desc); + return newCounterImpl(name, desc.isRate()); + } + + @Override + public synchronized <F1> Counter1<F1> newCounter( + String name, Description desc, + Field<F1> field1) { + checkCounterDescription(name, desc); + CounterImpl1<F1> m = new CounterImpl1<>(this, name, desc, field1); + define(name, desc); + bucketed.put(name, m); + return m.counter(); + } + + @Override + public synchronized <F1, F2> Counter2<F1, F2> newCounter( + String name, Description desc, + Field<F1> field1, Field<F2> field2) { + checkCounterDescription(name, desc); + CounterImplN m = new CounterImplN(this, name, desc, field1, field2); + define(name, desc); + bucketed.put(name, m); + return m.counter2(); + } + + @Override + public synchronized <F1, F2, F3> Counter3<F1, F2, F3> newCounter( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3) { + checkCounterDescription(name, desc); + CounterImplN m = new CounterImplN(this, name, desc, field1, field2, field3); + define(name, desc); + bucketed.put(name, m); + return m.counter3(); + } + + private static void checkCounterDescription(String name, Description desc) { + checkMetricName(name); + checkArgument(!desc.isConstant(), "counters must not be constant"); + checkArgument(!desc.isGauge(), "counters must not be gauge"); + } + + CounterImpl newCounterImpl(String name, boolean isRate) { + if (isRate) { + final com.codahale.metrics.Meter m = registry.meter(name); + return new CounterImpl(name, m) { + @Override + public void incrementBy(long delta) { + checkArgument(delta >= 0, "counter delta must be >= 0"); + m.mark(delta); + } + }; + } else { + final com.codahale.metrics.Counter m = registry.counter(name); + return new CounterImpl(name, m) { + @Override + public void incrementBy(long delta) { + checkArgument(delta >= 0, "counter delta must be >= 0"); + m.inc(delta); + } + }; + } + } + + @Override + public synchronized Timer0 newTimer(String name, Description desc) { + checkTimerDescription(name, desc); + define(name, desc); + return newTimerImpl(name); + } + + @Override + public synchronized <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) { + checkTimerDescription(name, desc); + TimerImpl1<F1> m = new TimerImpl1<>(this, name, desc, field1); + define(name, desc); + bucketed.put(name, m); + return m.timer(); + } + + @Override + public synchronized <F1, F2> Timer2<F1, F2> newTimer(String name, Description desc, + Field<F1> field1, Field<F2> field2) { + checkTimerDescription(name, desc); + TimerImplN m = new TimerImplN(this, name, desc, field1, field2); + define(name, desc); + bucketed.put(name, m); + return m.timer2(); + } + + @Override + public synchronized <F1, F2, F3> Timer3<F1, F2, F3> newTimer( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3) { + checkTimerDescription(name, desc); + TimerImplN m = new TimerImplN(this, name, desc, field1, field2, field3); + define(name, desc); + bucketed.put(name, m); + return m.timer3(); + } + + private static void checkTimerDescription(String name, Description desc) { + checkMetricName(name); + checkArgument(!desc.isConstant(), "timer must not be constant"); + checkArgument(!desc.isGauge(), "timer must not be a gauge"); + checkArgument(!desc.isRate(), "timer must not be a rate"); + checkArgument(desc.isCumulative(), "timer must be cumulative"); + checkArgument(desc.getTimeUnit() != null, "timer must have a unit"); + } + + TimerImpl newTimerImpl(String name) { + return new TimerImpl(name, registry.timer(name)); + } + + @Override + public synchronized Histogram0 newHistogram(String name, Description desc) { + checkHistogramDescription(name, desc); + define(name, desc); + return newHistogramImpl(name); + } + + @Override + public synchronized <F1> Histogram1<F1> newHistogram(String name, + Description desc, Field<F1> field1) { + checkHistogramDescription(name, desc); + HistogramImpl1<F1> m = new HistogramImpl1<>(this, name, desc, field1); + define(name, desc); + bucketed.put(name, m); + return m.histogram1(); + } + + @Override + public synchronized <F1, F2> Histogram2<F1, F2> newHistogram(String name, + Description desc, Field<F1> field1, Field<F2> field2) { + checkHistogramDescription(name, desc); + HistogramImplN m = new HistogramImplN(this, name, desc, field1, field2); + define(name, desc); + bucketed.put(name, m); + return m.histogram2(); + } + + @Override + public synchronized <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3) { + checkHistogramDescription(name, desc); + HistogramImplN m = new HistogramImplN(this, name, desc, field1, field2, field3); + define(name, desc); + bucketed.put(name, m); + return m.histogram3(); + } + + private static void checkHistogramDescription(String name, Description desc) { + checkMetricName(name); + checkArgument(!desc.isConstant(), "histogram must not be constant"); + checkArgument(!desc.isGauge(), "histogram must not be a gauge"); + checkArgument(!desc.isRate(), "histogram must not be a rate"); + checkArgument(desc.isCumulative(), "histogram must be cumulative"); + } + + HistogramImpl newHistogramImpl(String name) { + return new HistogramImpl(name, registry.histogram(name)); + } + + @Override + public <V> CallbackMetric0<V> newCallbackMetric( + String name, Class<V> valueClass, Description desc) { + checkMetricName(name); + define(name, desc); + return new CallbackMetricImpl0<>(this, registry, name, valueClass); + } + + @Override + public <F1, V> CallbackMetric1<F1, V> newCallbackMetric( + String name, Class<V> valueClass, Description desc, Field<F1> field1) { + checkMetricName(name); + CallbackMetricImpl1<F1, V> m = new CallbackMetricImpl1<>(this, registry, + name, valueClass, desc, field1); + define(name, desc); + bucketed.put(name, m); + return m.create(); + } + + @Override + public synchronized RegistrationHandle newTrigger( + Set<CallbackMetric<?>> metrics, Runnable trigger) { + final ImmutableSet<CallbackMetricGlue> all = FluentIterable.from(metrics) + .transform( + new Function<CallbackMetric<?>, CallbackMetricGlue>() { + @Override + public CallbackMetricGlue apply(CallbackMetric<?> input) { + return (CallbackMetricGlue) input; + } + }) + .toSet(); + + trigger = new CallbackGroup(trigger, all); + for (CallbackMetricGlue m : all) { + m.register(trigger); + } + trigger.run(); + + return new RegistrationHandle() { + @Override + public void remove() { + for (CallbackMetricGlue m : all) { + m.remove(); + } + } + }; + } + + synchronized void remove(String name) { + bucketed.remove(name); + descriptions.remove(name); + } + + private synchronized void define(String name, Description desc) { + if (descriptions.containsKey(name)) { + throw new IllegalStateException(String.format( + "metric %s already defined", name)); + } + descriptions.put(name, desc.getAnnotations()); + } + + private static final Pattern METRIC_NAME_PATTERN = Pattern + .compile("[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)*"); + + private static void checkMetricName(String name) { + checkArgument( + METRIC_NAME_PATTERN.matcher(name).matches(), + "metric name must match %s", METRIC_NAME_PATTERN.pattern()); + } + + static String name(Description.FieldOrdering ordering, + String codeName, + String fieldValues) { + if (ordering == FieldOrdering.PREFIX_FIELDS_BASENAME) { + int s = codeName.lastIndexOf('/'); + if (s > 0) { + String prefix = codeName.substring(0, s); + String metric = codeName.substring(s + 1); + return prefix + '/' + fieldValues + '/' + metric; + } + } + return codeName + '/' + fieldValues; + } + + abstract class CounterImpl extends Counter0 { + private final String name; + final Metric metric; + + CounterImpl(String name, Metric metric) { + this.name = name; + this.metric = metric; + } + + @Override + public void remove() { + descriptions.remove(name); + registry.remove(name); + } + } + + class TimerImpl extends Timer0 { + private final String name; + final com.codahale.metrics.Timer metric; + + private TimerImpl(String name, com.codahale.metrics.Timer metric) { + this.name = name; + this.metric = metric; + } + + @Override + public void record(long value, TimeUnit unit) { + checkArgument(value >= 0, "timer delta must be >= 0"); + metric.update(value, unit); + } + + @Override + public void remove() { + descriptions.remove(name); + registry.remove(name); + } + } + + class HistogramImpl extends Histogram0 { + private final String name; + final com.codahale.metrics.Histogram metric; + + private HistogramImpl(String name, com.codahale.metrics.Histogram metric) { + this.name = name; + this.metric = metric; + } + + @Override + public void record(long value) { + metric.update(value); + } + + @Override + public void remove() { + descriptions.remove(name); + registry.remove(name); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/GetMetric.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/GetMetric.java new file mode 100644 index 0000000..47064df --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
@@ -0,0 +1,47 @@ +// 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.metrics.dropwizard; + +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.server.CurrentUser; +import com.google.inject.Inject; + +import org.kohsuke.args4j.Option; + +class GetMetric implements RestReadView<MetricResource> { + private final CurrentUser user; + private final DropWizardMetricMaker metrics; + + @Option(name = "--data-only", usage = "return only values") + boolean dataOnly; + + @Inject + GetMetric(CurrentUser user, DropWizardMetricMaker metrics) { + this.user = user; + this.metrics = metrics; + } + + @Override + public MetricJson apply(MetricResource resource) throws AuthException { + if (!user.getCapabilities().canViewCaches()) { + throw new AuthException("restricted to viewCaches"); + } + return new MetricJson( + resource.getMetric(), + metrics.getAnnotations(resource.getName()), + dataOnly); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java new file mode 100644 index 0000000..e3f9e1c --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java
@@ -0,0 +1,52 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Histogram1; + +/** Optimized version of {@link BucketedHistogram} for single dimension. */ +class HistogramImpl1<F1> extends BucketedHistogram implements BucketedMetric { + HistogramImpl1(DropWizardMetricMaker metrics, String name, + Description desc, Field<F1> field1) { + super(metrics, name, desc, field1); + } + + Histogram1<F1> histogram1() { + return new Histogram1<F1>() { + @Override + public void record(F1 field1, long value) { + total.record(value); + forceCreate(field1).record(value); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + @Override + String name(Object field1) { + @SuppressWarnings("unchecked") + Function<Object, String> fmt = + (Function<Object, String>) fields[0].formatter(); + + return fmt.apply(field1).replace('/', '-'); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java new file mode 100644 index 0000000..d832c60 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java
@@ -0,0 +1,75 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Histogram2; +import com.google.gerrit.metrics.Histogram3; + +/** Generalized implementation of N-dimensional Histogram metrics. */ +class HistogramImplN extends BucketedHistogram implements BucketedMetric { + HistogramImplN(DropWizardMetricMaker metrics, String name, + Description desc, Field<?>... fields) { + super(metrics, name, desc, fields); + } + + <F1, F2> Histogram2<F1, F2> histogram2() { + return new Histogram2<F1, F2>() { + @Override + public void record(F1 field1, F2 field2, long value) { + total.record(value); + forceCreate(field1, field2).record(value); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + <F1, F2, F3> Histogram3<F1, F2, F3> histogram3() { + return new Histogram3<F1, F2, F3>() { + @Override + public void record(F1 field1, F2 field2, F3 field3, long value) { + total.record(value); + forceCreate(field1, field2, field3).record(value); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + String name(Object key) { + ImmutableList<Object> keyList = (ImmutableList<Object>) key; + String[] parts = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + Function<Object, String> fmt = + (Function<Object, String>) fields[i].formatter(); + + parts[i] = fmt.apply(keyList.get(i)).replace('/', '-'); + } + return Joiner.on('/').join(parts); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java new file mode 100644 index 0000000..04d10a2 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
@@ -0,0 +1,96 @@ +// 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.metrics.dropwizard; + +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.ConfigResource; +import com.google.inject.Inject; + +import com.codahale.metrics.Metric; + +import org.kohsuke.args4j.Option; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +class ListMetrics implements RestReadView<ConfigResource> { + private final CurrentUser user; + private final DropWizardMetricMaker metrics; + + @Option(name = "--data-only", usage = "return only values") + boolean dataOnly; + + @Option(name = "--prefix", aliases = {"-p"}, metaVar = "PREFIX", + usage = "match metric by exact match or prefix") + List<String> query = new ArrayList<>(); + + @Inject + ListMetrics(CurrentUser user, DropWizardMetricMaker metrics) { + this.user = user; + this.metrics = metrics; + } + + @Override + public Map<String, MetricJson> apply(ConfigResource resource) + throws AuthException { + if (!user.getCapabilities().canViewCaches()) { + throw new AuthException("restricted to viewCaches"); + } + + SortedMap<String, MetricJson> out = new TreeMap<>(); + List<String> prefixes = new ArrayList<>(query.size()); + for (String q : query) { + if (q.endsWith("/")) { + prefixes.add(q); + } else { + Metric m = metrics.getMetric(q); + if (m != null) { + out.put(q, toJson(q, m)); + } + } + } + + if (query.isEmpty() || !prefixes.isEmpty()) { + for (String name : metrics.getMetricNames()) { + if (include(prefixes, name)) { + out.put(name, toJson(name, metrics.getMetric(name))); + } + } + } + + return out; + } + + private MetricJson toJson(String q, Metric m) { + return new MetricJson(m, metrics.getAnnotations(q), dataOnly); + } + + private static boolean include(List<String> prefixes, String name) { + if (prefixes.isEmpty()) { + return true; + } + for (String p : prefixes) { + if (name.startsWith(p)) { + return true; + } + } + return false; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java new file mode 100644 index 0000000..b332262 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
@@ -0,0 +1,210 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +class MetricJson { + String description; + String unit; + Boolean constant; + Boolean rate; + Boolean gauge; + Boolean cumulative; + + Long count; + Object value; + + Double rate_1m; + Double rate_5m; + Double rate_15m; + Double rate_mean; + + Double p50; + Double p75; + Double p95; + Double p98; + Double p99; + Double p99_9; + + Double min; + Double avg; + Double max; + Double sum; + Double std_dev; + + List<FieldJson> fields; + Map<String, Object> buckets; + + MetricJson(Metric metric, ImmutableMap<String, String> atts, boolean dataOnly) { + if (!dataOnly) { + description = atts.get(Description.DESCRIPTION); + unit = atts.get(Description.UNIT); + constant = toBool(atts, Description.CONSTANT); + rate = toBool(atts, Description.RATE); + gauge = toBool(atts, Description.GAUGE); + cumulative = toBool(atts, Description.CUMULATIVE); + } + init(metric, atts); + } + + private void init(Metric metric, ImmutableMap<String, String> atts) { + if (metric instanceof BucketedMetric) { + BucketedMetric m = (BucketedMetric) metric; + if (m.getTotal() != null) { + init(m.getTotal(), atts); + } + + Field<?>[] fieldList = m.getFields(); + fields = new ArrayList<>(fieldList.length); + for (Field<?> f : fieldList) { + fields.add(new FieldJson(f)); + } + buckets = makeBuckets(fieldList, m.getCells(), atts); + + } else if (metric instanceof Counter) { + Counter c = (Counter) metric; + count = c.getCount(); + + } else if (metric instanceof Gauge) { + Gauge<?> g = (Gauge<?>) metric; + value = g.getValue(); + + } else if (metric instanceof Meter) { + Meter m = (Meter) metric; + count = m.getCount(); + rate_1m = m.getOneMinuteRate(); + rate_5m = m.getFiveMinuteRate(); + rate_15m = m.getFifteenMinuteRate(); + + } else if (metric instanceof Timer) { + Timer m = (Timer) metric; + Snapshot s = m.getSnapshot(); + count = m.getCount(); + rate_1m = m.getOneMinuteRate(); + rate_5m = m.getFiveMinuteRate(); + rate_15m = m.getFifteenMinuteRate(); + + double div = + Description.getTimeUnit(atts.get(Description.UNIT)).toNanos(1); + p50 = s.getMedian() / div; + p75 = s.get75thPercentile() / div; + p95 = s.get95thPercentile() / div; + p98 = s.get98thPercentile() / div; + p99 = s.get99thPercentile() / div; + p99_9 = s.get999thPercentile() / div; + + min = s.getMin() / div; + max = s.getMax() / div; + std_dev = s.getStdDev() / div; + + } else if (metric instanceof Histogram) { + Histogram m = (Histogram) metric; + Snapshot s = m.getSnapshot(); + count = m.getCount(); + + p50 = s.getMedian(); + p75 = s.get75thPercentile(); + p95 = s.get95thPercentile(); + p98 = s.get98thPercentile(); + p99 = s.get99thPercentile(); + p99_9 = s.get999thPercentile(); + + min = (double) s.getMin(); + avg = (double) s.getMean(); + max = (double) s.getMax(); + sum = s.getMean() * m.getCount(); + std_dev = s.getStdDev(); + } + } + + private static Boolean toBool(ImmutableMap<String, String> atts, String key) { + return Description.TRUE_VALUE.equals(atts.get(key)) ? true : null; + } + + @SuppressWarnings("unchecked") + private static Map<String, Object> makeBuckets( + Field<?>[] fields, + Map<?, Metric> metrics, + ImmutableMap<String, String> atts) { + if (fields.length == 1) { + Function<Object, String> fmt = + (Function<Object, String>) fields[0].formatter(); + Map<String, Object> out = new TreeMap<>(); + for (Map.Entry<?, Metric> e : metrics.entrySet()) { + out.put( + fmt.apply(e.getKey()), + new MetricJson(e.getValue(), atts, true)); + } + return out; + } + + Map<String, Object> out = new TreeMap<>(); + for (Map.Entry<?, Metric> e : metrics.entrySet()) { + ImmutableList<Object> keys = (ImmutableList<Object>) e.getKey(); + Map<String, Object> dst = out; + + for (int i = 0; i < fields.length - 1; i++) { + Function<Object, String> fmt = + (Function<Object, String>) fields[i].formatter(); + String key = fmt.apply(keys.get(i)); + Map<String, Object> t = (Map<String, Object>) dst.get(key); + if (t == null) { + t = new TreeMap<>(); + dst.put(key, t); + } + dst = t; + } + + Function<Object, String> fmt = + (Function<Object, String>) fields[fields.length - 1].formatter(); + dst.put( + fmt.apply(keys.get(fields.length - 1)), + new MetricJson(e.getValue(), atts, true)); + } + return out; + } + + static class FieldJson { + String name; + String type; + String description; + + FieldJson(Field<?> field) { + this.name = field.getName(); + this.description = field.getDescription(); + this.type = Enum.class.isAssignableFrom(field.getType()) + ? field.getType().getSimpleName() + : null; + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricResource.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricResource.java new file mode 100644 index 0000000..d073f37 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricResource.java
@@ -0,0 +1,42 @@ +// 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.metrics.dropwizard; + +import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.server.config.ConfigResource; +import com.google.inject.TypeLiteral; + +import com.codahale.metrics.Metric; + +class MetricResource extends ConfigResource { + static final TypeLiteral<RestView<MetricResource>> METRIC_KIND = + new TypeLiteral<RestView<MetricResource>>() {}; + + private final String name; + private final Metric metric; + + MetricResource(String name, Metric metric) { + this.name = name; + this.metric = metric; + } + + String getName() { + return name; + } + + Metric getMetric() { + return metric; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricsCollection.java new file mode 100644 index 0000000..81945f1 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/MetricsCollection.java
@@ -0,0 +1,72 @@ +// 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.metrics.dropwizard; + +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.ChildCollection; +import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.ConfigResource; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import com.codahale.metrics.Metric; + +@Singleton +class MetricsCollection implements + ChildCollection<ConfigResource, MetricResource> { + private final DynamicMap<RestView<MetricResource>> views; + private final Provider<ListMetrics> list; + private final Provider<CurrentUser> user; + private final DropWizardMetricMaker metrics; + + @Inject + MetricsCollection(DynamicMap<RestView<MetricResource>> views, + Provider<ListMetrics> list, Provider<CurrentUser> user, + DropWizardMetricMaker metrics) { + this.views = views; + this.list = list; + this.user = user; + this.metrics = metrics; + } + + @Override + public DynamicMap<RestView<MetricResource>> views() { + return views; + } + + @Override + public RestView<ConfigResource> list() { + return list.get(); + } + + @Override + public MetricResource parse(ConfigResource parent, IdString id) + throws ResourceNotFoundException, AuthException { + if (!user.get().getCapabilities().canViewCaches()) { + throw new AuthException("restricted to viewCaches"); + } + + Metric metric = metrics.getMetric(id.get()); + if (metric == null) { + throw new ResourceNotFoundException(id.get()); + } + return new MetricResource(id.get(), metric); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java new file mode 100644 index 0000000..0164f6f --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Timer1; + +import java.util.concurrent.TimeUnit; + +/** Optimized version of {@link BucketedTimer} for single dimension. */ +class TimerImpl1<F1> extends BucketedTimer implements BucketedMetric { + TimerImpl1(DropWizardMetricMaker metrics, String name, + Description desc, Field<F1> field1) { + super(metrics, name, desc, field1); + } + + Timer1<F1> timer() { + return new Timer1<F1>() { + @Override + public void record(F1 field1, long value, TimeUnit unit) { + total.record(value, unit); + forceCreate(field1).record(value, unit); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + @Override + String name(Object field1) { + @SuppressWarnings("unchecked") + Function<Object, String> fmt = + (Function<Object, String>) fields[0].formatter(); + + return fmt.apply(field1).replace('/', '-'); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java new file mode 100644 index 0000000..49c9f14 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
@@ -0,0 +1,78 @@ +// 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.metrics.dropwizard; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Timer2; +import com.google.gerrit.metrics.Timer3; + +import java.util.concurrent.TimeUnit; + +/** Generalized implementation of N-dimensional timer metrics. */ +class TimerImplN extends BucketedTimer implements BucketedMetric { + TimerImplN(DropWizardMetricMaker metrics, String name, + Description desc, Field<?>... fields) { + super(metrics, name, desc, fields); + } + + <F1, F2> Timer2<F1, F2> timer2() { + return new Timer2<F1, F2>() { + @Override + public void record(F1 field1, F2 field2, long value, TimeUnit unit) { + total.record(value, unit); + forceCreate(field1, field2).record(value, unit); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + <F1, F2, F3> Timer3<F1, F2, F3> timer3() { + return new Timer3<F1, F2, F3>() { + @Override + public void record(F1 field1, F2 field2, F3 field3, + long value, TimeUnit unit) { + total.record(value, unit); + forceCreate(field1, field2, field3).record(value, unit); + } + + @Override + public void remove() { + doRemove(); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + String name(Object key) { + ImmutableList<Object> keyList = (ImmutableList<Object>) key; + String[] parts = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + Function<Object, String> fmt = + (Function<Object, String>) fields[i].formatter(); + + parts[i] = fmt.apply(keyList.get(i)).replace('/', '-'); + } + return Joiner.on('/').join(parts); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.java new file mode 100644 index 0000000..b5a2fcc8 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/JGitMetricModule.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.metrics.proc; + +import com.google.common.base.Supplier; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.MetricMaker; + +import org.eclipse.jgit.internal.storage.file.WindowCacheStatAccessor; + +public class JGitMetricModule extends MetricModule { + @Override + protected void configure(MetricMaker metrics) { + metrics.newCallbackMetric( + "jgit/block_cache/cache_used", + Long.class, + new Description("Bytes of memory retained in JGit block cache.") + .setGauge() + .setUnit(Units.BYTES), + new Supplier<Long>() { + @Override + public Long get() { + return WindowCacheStatAccessor.getOpenBytes(); + } + }); + + metrics.newCallbackMetric( + "jgit/block_cache/open_files", + Integer.class, + new Description("File handles held open by JGit block cache.") + .setGauge() + .setUnit("fds"), + new Supplier<Integer>() { + @Override + public Integer get() { + return WindowCacheStatAccessor.getOpenFiles(); + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java new file mode 100644 index 0000000..c556ee4 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/MetricModule.java
@@ -0,0 +1,43 @@ +// 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.metrics.proc; + +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; + +/** Guice module to configure metrics on server startup. */ +public abstract class MetricModule extends LifecycleModule { + /** Configure metrics during server startup. */ + protected abstract void configure(MetricMaker metrics); + + @Override + protected void configure() { + listener().toInstance(new LifecycleListener() { + @Inject + MetricMaker metrics; + + @Override + public void start() { + configure(metrics); + } + + @Override + public void stop() { + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java new file mode 100644 index 0000000..53b860c --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
@@ -0,0 +1,226 @@ +// 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.metrics.proc; + +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.common.Version; +import com.google.gerrit.metrics.CallbackMetric; +import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; + +import com.sun.management.OperatingSystemMXBean; +import com.sun.management.UnixOperatingSystemMXBean; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.ThreadMXBean; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("restriction") +public class ProcMetricModule extends MetricModule { + @Override + protected void configure(MetricMaker metrics) { + buildLabel(metrics); + procUptime(metrics); + procCpuUsage(metrics); + procJvmGc(metrics); + procJvmMemory(metrics); + procJvmThread(metrics); + } + + private void buildLabel(MetricMaker metrics) { + metrics.newConstantMetric( + "build/label", + Strings.nullToEmpty(Version.getVersion()), + new Description("Version of Gerrit server software")); + } + + private void procUptime(MetricMaker metrics) { + metrics.newConstantMetric( + "proc/birth_timestamp", + Long.valueOf(TimeUnit.MILLISECONDS.toMicros( + System.currentTimeMillis())), + new Description("Time at which the process started") + .setUnit(Units.MICROSECONDS)); + + metrics.newCallbackMetric( + "proc/uptime", + Long.class, + new Description("Uptime of this process") + .setUnit(Units.MILLISECONDS), + new Supplier<Long>() { + @Override + public Long get() { + return ManagementFactory.getRuntimeMXBean().getUptime(); + } + }); + } + + private void procCpuUsage(MetricMaker metrics) { + final OperatingSystemMXBean sys = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + if (sys.getProcessCpuTime() != -1) { + metrics.newCallbackMetric( + "proc/cpu/usage", + Double.class, + new Description("CPU time used by the process") + .setCumulative() + .setUnit(Units.SECONDS), + new Supplier<Double>() { + @Override + public Double get() { + return sys.getProcessCpuTime() / 1e9; + } + }); + } + if (sys instanceof UnixOperatingSystemMXBean) { + final UnixOperatingSystemMXBean unix = (UnixOperatingSystemMXBean) sys; + if (unix.getOpenFileDescriptorCount() != -1) { + metrics.newCallbackMetric( + "proc/num_open_fds", + Long.class, + new Description("Number of open file descriptors") + .setGauge() + .setUnit("fds"), + new Supplier<Long>() { + @Override + public Long get() { + return unix.getOpenFileDescriptorCount(); + } + }); + } + } + } + + private void procJvmMemory(MetricMaker metrics) { + final CallbackMetric0<Long> heapCommitted = metrics.newCallbackMetric( + "proc/jvm/memory/heap_committed", + Long.class, + new Description("Amount of memory guaranteed for user objects.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0<Long> heapUsed = metrics.newCallbackMetric( + "proc/jvm/memory/heap_used", + Long.class, + new Description("Amount of memory holding user objects.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0<Long> nonHeapCommitted = metrics.newCallbackMetric( + "proc/jvm/memory/non_heap_committed", + Long.class, + new Description("Amount of memory guaranteed for classes, etc.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0<Long> nonHeapUsed = metrics.newCallbackMetric( + "proc/jvm/memory/non_heap_used", + Long.class, + new Description("Amount of memory holding classes, etc.") + .setGauge() + .setUnit(Units.BYTES)); + + final CallbackMetric0<Integer> objectPendingFinalizationCount = + metrics.newCallbackMetric( + "proc/jvm/memory/object_pending_finalization_count", + Integer.class, + new Description("Approximate number of objects needing finalization.") + .setGauge() + .setUnit("objects")); + + final MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); + metrics.newTrigger( + ImmutableSet.<CallbackMetric<?>> of( + heapCommitted, heapUsed, nonHeapCommitted, + nonHeapUsed, objectPendingFinalizationCount), + new Runnable() { + @Override + public void run() { + try { + MemoryUsage stats = memory.getHeapMemoryUsage(); + heapCommitted.set(stats.getCommitted()); + heapUsed.set(stats.getUsed()); + } catch (IllegalArgumentException e) { + // MXBean may throw due to a bug in Java 7; ignore. + } + + MemoryUsage stats = memory.getNonHeapMemoryUsage(); + nonHeapCommitted.set(stats.getCommitted()); + nonHeapUsed.set(stats.getUsed()); + + objectPendingFinalizationCount.set( + memory.getObjectPendingFinalizationCount()); + } + }); + } + + private void procJvmGc(MetricMaker metrics) { + final CallbackMetric1<String, Long> gcCount = metrics.newCallbackMetric( + "proc/jvm/gc/count", + Long.class, + new Description("Number of GCs").setCumulative(), + Field.ofString("gc_name", "The name of the garbage collector")); + + final CallbackMetric1<String, Long> gcTime = metrics.newCallbackMetric( + "proc/jvm/gc/time", + Long.class, + new Description("Approximate accumulated GC elapsed time") + .setCumulative() + .setUnit(Units.MILLISECONDS), + Field.ofString("gc_name", "The name of the garbage collector")); + + metrics.newTrigger(gcCount, gcTime, new Runnable() { + @Override + public void run() { + for (GarbageCollectorMXBean gc : ManagementFactory + .getGarbageCollectorMXBeans()) { + long count = gc.getCollectionCount(); + if (count != -1) { + gcCount.set(gc.getName(), count); + } + long time = gc.getCollectionTime(); + if (time != -1) { + gcTime.set(gc.getName(), time); + } + } + } + }); + } + + private void procJvmThread(MetricMaker metrics) { + final ThreadMXBean thread = ManagementFactory.getThreadMXBean(); + metrics.newCallbackMetric( + "proc/jvm/thread/num_live", + Integer.class, + new Description("Current live thread count") + .setGauge() + .setUnit("threads"), + new Supplier<Integer>() { + @Override + public Integer get() { + return thread.getThreadCount(); + } + }); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java index 4bd9423..9d32e38 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PredicateProvider.java
@@ -30,5 +30,5 @@ @ExtensionPoint public interface PredicateProvider { /** Return set of packages that contain Prolog predicates */ - public ImmutableSet<String> getPackages(); + ImmutableSet<String> getPackages(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java index 5dd26f9..f6df335 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
@@ -75,20 +75,9 @@ */ @Singleton public class RulesCache { - /** Maximum size of a dynamic Prolog script, in bytes. */ - private static final int SRC_LIMIT = 128 * 1024; - - /** Default size of the internal Prolog database within each interpreter. */ - private static final int DB_MAX = 256; - private static final List<String> PACKAGE_LIST = ImmutableList.of( Prolog.BUILTIN, "gerrit"); - private final Map<ObjectId, MachineRef> machineCache = new HashMap<>(); - - private final ReferenceQueue<PrologMachineCopy> dead = - new ReferenceQueue<>(); - private static final class MachineRef extends WeakReference<PrologMachineCopy> { final ObjectId key; @@ -100,17 +89,25 @@ } private final boolean enableProjectRules; + private final int maxDbSize; + private final int maxSrcBytes; private final Path cacheDir; private final Path rulesDir; private final GitRepositoryManager gitMgr; private final DynamicSet<PredicateProvider> predicateProviders; private final ClassLoader systemLoader; private final PrologMachineCopy defaultMachine; + private final Map<ObjectId, MachineRef> machineCache = new HashMap<>(); + private final ReferenceQueue<PrologMachineCopy> dead = + new ReferenceQueue<>(); @Inject protected RulesCache(@GerritServerConfig Config config, SitePaths site, GitRepositoryManager gm, DynamicSet<PredicateProvider> predicateProviders) { - enableProjectRules = config.getBoolean("rules", null, "enable", true); + maxDbSize = config.getInt("rules", null, "maxPrologDatabaseSize", 256); + maxSrcBytes = config.getInt("rules", null, "maxSourceBytes", 128 << 10); + enableProjectRules = config.getBoolean("rules", null, "enable", true) + && maxSrcBytes > 0; cacheDir = site.resolve(config.getString("cache", null, "directory")); rulesDir = cacheDir != null ? cacheDir.resolve("rules") : null; gitMgr = gm; @@ -267,7 +264,7 @@ try (Repository git = gitMgr.openRepository(project)) { try { ObjectLoader ldr = git.open(rulesId, Constants.OBJ_BLOB); - byte[] raw = ldr.getCachedBytes(SRC_LIMIT); + byte[] raw = ldr.getCachedBytes(maxSrcBytes); return RawParseUtils.decode(raw); } catch (LargeObjectException e) { throw new CompileException("rules of " + project + " are too large", e); @@ -281,7 +278,7 @@ private BufferingPrologControl newEmptyMachine(ClassLoader cl) { BufferingPrologControl ctl = new BufferingPrologControl(); - ctl.setMaxDatabaseSize(DB_MAX); + ctl.setMaxDatabaseSize(maxDbSize); ctl.setPrologClassLoader(new PrologClassLoader(new PredicateClassLoader( predicateProviders, cl))); ctl.setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
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 31058bc..7551d5f 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
@@ -15,6 +15,8 @@ package com.google.gerrit.server; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; @@ -44,7 +46,7 @@ import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gerrit.server.notedb.NotesMigration; -import com.google.gerrit.server.notedb.ReviewerState; +import com.google.gerrit.server.notedb.ReviewerStateInternal; import com.google.gerrit.server.project.ChangeControl; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -114,10 +116,10 @@ * @param notes change notes. * @return multimap of reviewers keyed by state, where each account appears * exactly once in {@link SetMultimap#values()}, and - * {@link ReviewerState#REMOVED} is not present. + * {@link ReviewerStateInternal#REMOVED} is not present. * @throws OrmException if reviewers for the change could not be read. */ - public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers( + public ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers( ReviewDb db, ChangeNotes notes) throws OrmException { if (!migration.readChanges()) { return getReviewers(db.patchSetApprovals().byChange(notes.getChangeId())); @@ -132,9 +134,9 @@ * change. * @return multimap of reviewers keyed by state, where each account appears * exactly once in {@link SetMultimap#values()}, and - * {@link ReviewerState#REMOVED} is not present. + * {@link ReviewerStateInternal#REMOVED} is not present. */ - public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers( + public ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers( ChangeNotes notes, Iterable<PatchSetApproval> allApprovals) throws OrmException { if (!migration.readChanges()) { @@ -143,10 +145,10 @@ return notes.load().getReviewers(); } - private static ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers( + private static ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers( Iterable<PatchSetApproval> allApprovals) { PatchSetApproval first = null; - SetMultimap<ReviewerState, Account.Id> reviewers = + SetMultimap<ReviewerStateInternal, Account.Id> reviewers = LinkedHashMultimap.create(); for (PatchSetApproval psa : allApprovals) { if (first == null) { @@ -159,10 +161,10 @@ } Account.Id id = psa.getAccountId(); if (psa.getValue() != 0) { - reviewers.put(ReviewerState.REVIEWER, id); - reviewers.remove(ReviewerState.CC, id); - } else if (!reviewers.containsEntry(ReviewerState.REVIEWER, id)) { - reviewers.put(ReviewerState.CC, id); + reviewers.put(REVIEWER, id); + reviewers.remove(CC, id); + } else if (!reviewers.containsEntry(REVIEWER, id)) { + reviewers.put(CC, id); } } return ImmutableSetMultimap.copyOf(reviewers); @@ -215,7 +217,7 @@ cells.add(new PatchSetApproval( new PatchSetApproval.Key(psId, account, labelId), (short) 0, TimeUtil.nowTs())); - update.putReviewer(account, ReviewerState.REVIEWER); + update.putReviewer(account, REVIEWER); } db.patchSetApprovals().insert(cells); return Collections.unmodifiableList(cells);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java index f31a65b..94973f2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -54,7 +54,7 @@ return sortChangeMessages(db.changeMessages().byChange(notes.getChangeId())); } else { - return sortChangeMessages(notes.load().getChangeMessages().values()); + return notes.load().getChangeMessages(); } } @@ -63,7 +63,7 @@ if (!migration.readChanges()) { return db.changeMessages().byPatchSet(psId); } - return notes.load().getChangeMessages().get(psId); + return notes.load().getChangeMessagesByPatchSet().get(psId); } public void addChangeMessage(ReviewDb db, ChangeUpdate update,
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 6c80d26..0cbe540 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
@@ -14,15 +14,13 @@ package com.google.gerrit.server; -import static com.google.gerrit.server.query.change.ChangeData.asChanges; - import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import com.google.gerrit.common.TimeUtil; 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.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; @@ -43,6 +41,7 @@ import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.NoSuchChangeException; 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.util.IdGenerator; import com.google.gwtorm.server.OrmConcurrencyException; @@ -73,6 +72,7 @@ import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -180,6 +180,7 @@ private final Provider<IdentifiedUser> user; private final Provider<ReviewDb> db; private final Provider<InternalChangeQuery> queryProvider; + private final ChangeControl.GenericFactory changeControlFactory; private final RevertedSender.Factory revertedSenderFactory; private final ChangeInserter.Factory changeInserterFactory; private final GitRepositoryManager gitManager; @@ -193,6 +194,7 @@ ChangeUtil(Provider<IdentifiedUser> user, Provider<ReviewDb> db, Provider<InternalChangeQuery> queryProvider, + ChangeControl.GenericFactory changeControlFactory, RevertedSender.Factory revertedSenderFactory, ChangeInserter.Factory changeInserterFactory, GitRepositoryManager gitManager, @@ -204,6 +206,7 @@ this.user = user; this.db = db; this.queryProvider = queryProvider; + this.changeControlFactory = changeControlFactory; this.revertedSenderFactory = revertedSenderFactory; this.changeInserterFactory = changeInserterFactory; this.gitManager = gitManager; @@ -425,34 +428,51 @@ * * @param id change identifier, either a numeric ID, a Change-Id, or * project~branch~id triplet. - * @return all matching changes, even if they are not visible to the current - * user. + * @param user user to wrap in controls. + * @return possibly-empty list of controls for all matching changes, + * corresponding to the given user; may or may not be visible. + * @throws OrmException if an error occurred querying the database. */ - public List<Change> findChanges(String id) - throws OrmException, ResourceNotFoundException { + public List<ChangeControl> findChanges(String id, CurrentUser user) + throws OrmException { // Try legacy id if (id.matches("^[1-9][0-9]*$")) { - Change c = db.get().changes().get(Change.Id.parse(id)); - if (c != null) { - return ImmutableList.of(c); + try { + return ImmutableList.of( + changeControlFactory.controlFor(Change.Id.parse(id), user)); + } catch (NoSuchChangeException e) { + return Collections.emptyList(); } - return Collections.emptyList(); } + // Use the index to search for changes, but don't return any stored fields, + // to force rereading in case the index is stale. + InternalChangeQuery query = queryProvider.get() + .setRequestedFields(ImmutableSet.<String> of()); + // Try isolated changeId if (!id.contains("~")) { - return asChanges(queryProvider.get().byKeyPrefix(id)); + return asChangeControls(query.byKeyPrefix(id)); } // Try change triplet Optional<ChangeTriplet> triplet = ChangeTriplet.parse(id); if (triplet.isPresent()) { - return asChanges(queryProvider.get().byBranchKey( + return asChangeControls(query.byBranchKey( triplet.get().branch(), triplet.get().id())); } - throw new ResourceNotFoundException(id); + return Collections.emptyList(); + } + + private List<ChangeControl> asChangeControls(List<ChangeData> cds) + throws OrmException { + List<ChangeControl> ctls = new ArrayList<>(cds.size()); + for (ChangeData cd : cds) { + ctls.add(cd.changeControl(user.get())); + } + return ctls; } private static void deleteOnlyDraftPatchSetPreserveRef(ReviewDb db,
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 7b182b1..3e67b27 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
@@ -275,14 +275,10 @@ return sort(db.patchComments().draftByAuthor(author).toList()); } - // TODO(dborowitz): Just scan author space. - Set<String> refNames = getRefNamesAllUsers(RefNames.REFS_DRAFT_COMMENTS); + Set<String> refNames = + getRefNamesAllUsers(RefNames.refsDraftCommentsPrefix(author)); List<PatchLineComment> comments = Lists.newArrayList(); for (String refName : refNames) { - Account.Id id = Account.Id.fromRefPart(refName); - if (!author.equals(id)) { - continue; - } Change.Id changeId = Change.Id.parse(refName); comments.addAll( draftFactory.create(changeId, author).load().getComments().values()); @@ -364,7 +360,7 @@ } private Set<String> getRefNamesAllUsers(String prefix) throws OrmException { - try (Repository repo = repoManager.openRepository(allUsers)) { + try (Repository repo = repoManager.openMetadataRepository(allUsers)) { RefDatabase refDb = repo.getRefDatabase(); return refDb.getRefs(prefix).keySet(); } catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java index 406ab52..9001ea5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
@@ -20,7 +20,7 @@ /** Translates an email address to a set of matching accounts. */ public interface AccountByEmailCache { - public Set<Account.Id> get(String email); + Set<Account.Id> get(String email); - public void evict(String email); + void evict(String email); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java index 86308a9..d7d418d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java
@@ -18,13 +18,13 @@ /** Caches important (but small) account state to avoid database hits. */ public interface AccountCache { - public AccountState get(Account.Id accountId); + AccountState get(Account.Id accountId); - public AccountState getIfPresent(Account.Id accountId); + AccountState getIfPresent(Account.Id accountId); - public AccountState getByUsername(String username); + AccountState getByUsername(String username); - public void evict(Account.Id accountId); + void evict(Account.Id accountId); - public void evictByUsername(String username); + void evictByUsername(String username); }
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 7bb00c5..9d580f5 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
@@ -40,7 +40,7 @@ /** Access control management for server-wide capabilities. */ public class CapabilityControl { public static interface Factory { - public CapabilityControl create(CurrentUser user); + CapabilityControl create(CurrentUser user); } private final CapabilityCollection capabilities;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java index f60492e..166f97e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
@@ -19,9 +19,9 @@ * Expands user name to a local email address, usually by adding a domain. */ public interface EmailExpander { - public boolean canExpand(String user); + boolean canExpand(String user); - public String expand(String user); + String expand(String user); public static class None implements EmailExpander { public static final None INSTANCE = new None();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java index c1a4e0f..c7a2241 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
@@ -19,9 +19,9 @@ /** Tracks group objects in memory for efficient access. */ public interface GroupCache { - public AccountGroup get(AccountGroup.Id groupId); + AccountGroup get(AccountGroup.Id groupId); - public AccountGroup get(AccountGroup.NameKey name); + AccountGroup get(AccountGroup.NameKey name); /** * Lookup a group definition by its UUID. The returned definition may be null @@ -29,16 +29,16 @@ * copied from another server. */ @Nullable - public AccountGroup get(AccountGroup.UUID uuid); + AccountGroup get(AccountGroup.UUID uuid); /** @return sorted iteration of groups. */ - public abstract Iterable<AccountGroup> all(); + Iterable<AccountGroup> all(); /** Notify the cache that a new group was constructed. */ - public void onCreateGroup(AccountGroup.NameKey newGroupName); + void onCreateGroup(AccountGroup.NameKey newGroupName); - public void evict(AccountGroup group); + void evict(AccountGroup group); - public void evictAfterRename(final AccountGroup.NameKey oldName, + void evictAfterRename(final AccountGroup.NameKey oldName, final AccountGroup.NameKey newName); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java index 6ba6bf0..9971301 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCache.java
@@ -21,14 +21,14 @@ /** Tracks group inclusions in memory for efficient access. */ public interface GroupIncludeCache { /** @return groups directly a member of the passed group. */ - public Set<AccountGroup.UUID> subgroupsOf(AccountGroup.UUID group); + Set<AccountGroup.UUID> subgroupsOf(AccountGroup.UUID group); /** @return any groups the passed group belongs to. */ - public Set<AccountGroup.UUID> parentGroupsOf(AccountGroup.UUID groupId); + Set<AccountGroup.UUID> parentGroupsOf(AccountGroup.UUID groupId); /** @return set of any UUIDs that are not internal groups. */ - public Set<AccountGroup.UUID> allExternalMembers(); + Set<AccountGroup.UUID> allExternalMembers(); - public void evictSubgroupsOf(AccountGroup.UUID groupId); - public void evictParentGroupsOf(AccountGroup.UUID groupId); + void evictSubgroupsOf(AccountGroup.UUID groupId); + void evictParentGroupsOf(AccountGroup.UUID groupId); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java index 056fa85b..6fd76d4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -22,26 +22,26 @@ public interface Realm { /** Can the end-user modify this field of their own account? */ - public boolean allowsEdit(Account.FieldName field); + boolean allowsEdit(Account.FieldName field); /** Returns the account fields that the end-user can modify. */ - public Set<Account.FieldName> getEditableFields(); + Set<Account.FieldName> getEditableFields(); - public AuthRequest authenticate(AuthRequest who) throws AccountException; + AuthRequest authenticate(AuthRequest who) throws AccountException; - public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) + AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) throws AccountException; - public AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who) + AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who) throws AccountException; - public void onCreateAccount(AuthRequest who, Account account); + void onCreateAccount(AuthRequest who, Account account); /** @return true if the user has the given email address. */ - public boolean hasEmailAddress(IdentifiedUser who, String email); + boolean hasEmailAddress(IdentifiedUser who, String email); /** @return all known email addresses for the identified user. */ - public Set<String> getEmailAddresses(IdentifiedUser who); + Set<String> getEmailAddresses(IdentifiedUser who); /** * Locate an account whose local username is the given account name. @@ -51,5 +51,5 @@ * how to convert the accountName into an email address, and then locate the * user by that email address. */ - public Account.Id lookup(String accountName); + Account.Id lookup(String accountName); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java index a3c0d37..76ab25a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
@@ -72,7 +72,7 @@ user.asyncStarredChanges(); ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id); - if (user.getStarredChanges().contains(change.getChange().getId())) { + if (user.getStarredChanges().contains(change.getId())) { return new AccountResource.StarredChange(user, change); } throw new ResourceNotFoundException(id); @@ -141,7 +141,7 @@ dbProvider.get().starredChanges().insert(Collections.singleton( new StarredChange(new StarredChange.Key( rsrc.getUser().getAccountId(), - change.getChange().getId())))); + change.getId())))); } catch (OrmDuplicateKeyException e) { return Response.none(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java index 837e1ed..1cc3c94 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
@@ -15,8 +15,6 @@ package com.google.gerrit.server.account; import com.google.common.base.Strings; -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.Ordering; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.RestReadView; @@ -24,6 +22,7 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.api.accounts.AccountInfoComparator; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -33,7 +32,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -140,15 +138,7 @@ } List<AccountInfo> m = new ArrayList<>(matches.values()); - Collections.sort(m, new Comparator<AccountInfo>() { - @Override - public int compare(AccountInfo a, AccountInfo b) { - return ComparisonChain.start() - .compare(a.name, b.name, Ordering.natural().nullsLast()) - .compare(a.email, b.email, Ordering.natural().nullsLast()) - .result(); - } - }); + Collections.sort(m, AccountInfoComparator.ORDER_NULLS_LAST); return m; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountInfoComparator.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountInfoComparator.java new file mode 100644 index 0000000..3794701 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountInfoComparator.java
@@ -0,0 +1,56 @@ +// 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.accounts; + +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; +import com.google.gerrit.extensions.common.AccountInfo; + +import java.util.Comparator; + +public class AccountInfoComparator extends Ordering<AccountInfo> + implements Comparator<AccountInfo> { + public static final AccountInfoComparator ORDER_NULLS_FIRST = + new AccountInfoComparator(); + public static final AccountInfoComparator ORDER_NULLS_LAST = + new AccountInfoComparator().setNullsLast(); + + private boolean nullsLast; + + private AccountInfoComparator() { + } + + private AccountInfoComparator setNullsLast() { + this.nullsLast = true; + return this; + } + + @Override + public int compare(AccountInfo a, AccountInfo b) { + return ComparisonChain.start() + .compare(a.name, b.name, createOrdering()) + .compare(a.email, b.email, createOrdering()) + .compare(a._accountId, b._accountId, createOrdering()) + .result(); + } + + private <S extends Comparable<?>> Ordering<S> createOrdering() { + if (nullsLast) { + return Ordering.natural().nullsLast(); + } else { + return Ordering.natural().nullsFirst(); + } + } +}
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 b55642c..d7f8cde 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
@@ -22,6 +22,7 @@ import com.google.gerrit.extensions.api.changes.HashtagsInput; import com.google.gerrit.extensions.api.changes.RestoreInput; import com.google.gerrit.extensions.api.changes.RevertInput; +import com.google.gerrit.extensions.api.changes.ReviewerApi; import com.google.gerrit.extensions.api.changes.RevisionApi; import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.ChangeInfo; @@ -46,6 +47,7 @@ import com.google.gerrit.server.change.PutTopic; import com.google.gerrit.server.change.Restore; import com.google.gerrit.server.change.Revert; +import com.google.gerrit.server.change.Reviewers; import com.google.gerrit.server.change.Revisions; import com.google.gerrit.server.change.SubmittedTogether; import com.google.gerrit.server.change.SuggestReviewers; @@ -68,7 +70,9 @@ private final Provider<CurrentUser> user; private final Changes changeApi; + private final Reviewers reviewers; private final Revisions revisions; + private final ReviewerApiImpl.Factory reviewerApi; private final RevisionApiImpl.Factory revisionApi; private final Provider<SuggestReviewers> suggestReviewers; private final ChangeResource change; @@ -90,7 +94,9 @@ @Inject ChangeApiImpl(Provider<CurrentUser> user, Changes changeApi, + Reviewers reviewers, Revisions revisions, + ReviewerApiImpl.Factory reviewerApi, RevisionApiImpl.Factory revisionApi, Provider<SuggestReviewers> suggestReviewers, Abandon abandon, @@ -111,7 +117,9 @@ this.user = user; this.changeApi = changeApi; this.revert = revert; + this.reviewers = reviewers; this.revisions = revisions; + this.reviewerApi = reviewerApi; this.revisionApi = revisionApi; this.suggestReviewers = suggestReviewers; this.abandon = abandon; @@ -132,7 +140,7 @@ @Override public String id() { - return Integer.toString(change.getChange().getId().get()); + return Integer.toString(change.getId().get()); } @Override @@ -156,6 +164,16 @@ } @Override + public ReviewerApi reviewer(String id) throws RestApiException { + try { + return reviewerApi.create( + reviewers.parse(change, IdString.fromDecoded(id))); + } catch (OrmException e) { + throw new RestApiException("Cannot parse reviewer", e); + } + } + + @Override public void abandon() throws RestApiException { abandon(new AbandonInput()); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java index a5e584e..228dad6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
@@ -27,5 +27,6 @@ factory(DraftApiImpl.Factory.class); factory(RevisionApiImpl.Factory.class); factory(FileApiImpl.Factory.class); + factory(ReviewerApiImpl.Factory.class); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java new file mode 100644 index 0000000..49b3432 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java
@@ -0,0 +1,65 @@ +// 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.api.changes; + +import com.google.gerrit.extensions.api.changes.ReviewerApi; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.change.DeleteVote; +import com.google.gerrit.server.change.ReviewerResource; +import com.google.gerrit.server.change.VoteResource; +import com.google.gerrit.server.change.Votes; +import com.google.gerrit.server.git.UpdateException; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import java.util.Map; + +public class ReviewerApiImpl implements ReviewerApi { + interface Factory { + ReviewerApiImpl create(ReviewerResource r); + } + + private final ReviewerResource reviewer; + private final Votes.List listVotes; + private final DeleteVote deleteVote; + + @Inject + ReviewerApiImpl(Votes.List listVotes, + DeleteVote deleteVote, + @Assisted ReviewerResource reviewer) { + this.listVotes = listVotes; + this.deleteVote = deleteVote; + this.reviewer = reviewer; + } + + @Override + public Map<String, Short> votes() throws RestApiException { + try { + return listVotes.apply(reviewer); + } catch (OrmException e) { + throw new RestApiException("Cannot list votes", e); + } + } + + @Override + public void deleteVote(String label) throws RestApiException { + try { + deleteVote.apply(new VoteResource(reviewer, label), null); + } catch (UpdateException e) { + throw new RestApiException("Cannot delete vote", e); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/avatar/AvatarProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/avatar/AvatarProvider.java index 4b30a8d..571a7e5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/avatar/AvatarProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/avatar/AvatarProvider.java
@@ -37,7 +37,7 @@ * {@code null} is acceptable, and results in the server responding * with a 404. This will hide the avatar image in the web UI. */ - public String getUrl(IdentifiedUser forUser, int imageSize); + String getUrl(IdentifiedUser forUser, int imageSize); /** * Gets a URL for a user to modify their avatar image. @@ -46,5 +46,5 @@ * @return a URL the user should visit to modify their avatar, or null if * modification is not possible. */ - public String getChangeAvatarUrl(IdentifiedUser forUser); + String getChangeAvatarUrl(IdentifiedUser forUser); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheMetrics.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheMetrics.java new file mode 100644 index 0000000..41a48af --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheMetrics.java
@@ -0,0 +1,90 @@ +package com.google.gerrit.server.cache; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheStats; +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.metrics.CallbackMetric; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.util.Set; + +@Singleton +public class CacheMetrics { + @Inject + public CacheMetrics(MetricMaker metrics, + final DynamicMap<Cache<?, ?>> cacheMap) { + Field<String> F_NAME = Field.ofString("cache_name"); + + final CallbackMetric1<String, Long> memEnt = + metrics.newCallbackMetric("caches/memory_cached", Long.class, + new Description("Memory entries").setGauge().setUnit("entries"), + F_NAME); + final CallbackMetric1<String, Double> memHit = + metrics.newCallbackMetric("caches/memory_hit_ratio", Double.class, + new Description("Memory hit ratio").setGauge().setUnit("percent"), + F_NAME); + final CallbackMetric1<String, Long> memEvict = + metrics.newCallbackMetric("caches/memory_eviction_count", Long.class, + new Description("Memory eviction count").setGauge() + .setUnit("evicted entries"), + F_NAME); + final CallbackMetric1<String, Long> perDiskEnt = + metrics.newCallbackMetric("caches/disk_cached", Long.class, + new Description("Disk entries used by persistent cache").setGauge() + .setUnit("entries"), + F_NAME); + final CallbackMetric1<String, Double> perDiskHit = + metrics.newCallbackMetric("caches/disk_hit_ratio", Double.class, + new Description("Disk hit ratio for persistent cache").setGauge() + .setUnit("percent"), + F_NAME); + + final Set<CallbackMetric<?>> cacheMetrics = + ImmutableSet.<CallbackMetric<?>> of(memEnt, memHit, memEvict, + perDiskEnt, perDiskHit); + + metrics.newTrigger(cacheMetrics, new Runnable() { + @Override + public void run() { + for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) { + Cache<?, ?> c = e.getProvider().get(); + String name = metricNameOf(e); + CacheStats cstats = c.stats(); + memEnt.set(name, c.size()); + memHit.set(name, cstats.hitRate() * 100); + memEvict.set(name, cstats.evictionCount()); + if (c instanceof PersistentCache) { + PersistentCache.DiskStats d = ((PersistentCache) c).diskStats(); + perDiskEnt.set(name, d.size()); + perDiskHit.set(name, hitRatio(d)); + } + } + for (CallbackMetric<?> cbm : cacheMetrics) { + cbm.prune(); + } + } + }); + } + + private static double hitRatio(PersistentCache.DiskStats d) { + if (d.requestCount() <= 0) { + return 100; + } + return ((double) d.hitCount() / d.requestCount() * 100); + } + + private static String metricNameOf(DynamicMap.Entry<Cache<?, ?>> e) { + if ("gerrit".equals(e.getPluginName())) { + return e.getExportName(); + } else { + return String.format("plugin/%s/%s", e.getPluginName(), + e.getExportName()); + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java index 078f2dc..bdc1220 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java
@@ -17,7 +17,7 @@ import com.google.common.cache.RemovalNotification; public interface CacheRemovalListener<K,V> { - public void onRemoval(String pluginName, + void onRemoval(String pluginName, String cacheName, RemovalNotification<K, V> notification); -} \ No newline at end of file +}
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 f2d40c8..ff10351 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
@@ -82,7 +82,7 @@ throws RestApiException, UpdateException, OrmException { ChangeControl control = req.getControl(); IdentifiedUser caller = control.getUser().asIdentifiedUser(); - if (!control.canAbandon()) { + if (!control.canAbandon(dbProvider.get())) { throw new AuthException("abandon not permitted"); } Change change = abandon(control, input.message, caller.getAccount()); @@ -174,12 +174,18 @@ @Override public UiAction.Description getDescription(ChangeResource resource) { + boolean canAbandon = false; + try { + canAbandon = resource.getControl().canAbandon(dbProvider.get()); + } catch (OrmException e) { + log.error("Cannot check canAbandon status. Assuming false.", e); + } return new UiAction.Description() .setLabel("Abandon") .setTitle("Abandon the change") .setVisible(resource.getChange().getStatus().isOpen() && resource.getChange().getStatus() != Change.Status.DRAFT - && resource.getControl().canAbandon()); + && canAbandon); } private static String status(Change change) {
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 cb3729b..2d06e93 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
@@ -177,7 +177,7 @@ if (edit.isPresent()) { throw new ResourceConflictException(String.format( "edit already exists for the change %s", - resource.getChange().getChangeId())); + resource.getId())); } edit = createEdit(); if (!Strings.isNullOrEmpty(path)) {
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 90d6aae..99cef4b 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
@@ -21,6 +21,7 @@ import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.common.data.LabelTypes; +import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; @@ -46,7 +47,6 @@ 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.InvalidChangeOperationException; import com.google.gerrit.server.project.RefControl; import com.google.gerrit.server.ssh.NoSshInfo; import com.google.gerrit.server.util.RequestScopePropagator; @@ -228,7 +228,7 @@ @Override public void updateRepo(RepoContext ctx) - throws InvalidChangeOperationException, IOException { + throws ResourceConflictException, IOException { validate(ctx); patchSetInfo = patchSetInfoFactory.get( ctx.getRevWalk(), commit, patchSet.getId()); @@ -307,7 +307,7 @@ } private void validate(RepoContext ctx) - throws IOException, InvalidChangeOperationException { + throws IOException, ResourceConflictException { if (validatePolicy == CommitValidators.Policy.NONE) { return; } @@ -339,7 +339,7 @@ break; } } catch (CommitValidationException e) { - throw new InvalidChangeOperationException(e.getMessage()); + throw new ResourceConflictException(e.getFullMessage()); } } }
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 733d7a2..7d64591 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
@@ -90,10 +90,12 @@ 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.AccountInfoComparator; 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; +import com.google.gerrit.server.notedb.ReviewerStateInternal; import com.google.gerrit.server.patch.PatchListNotAvailableException; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ProjectCache; @@ -119,6 +121,7 @@ import java.util.Collection; import java.util.Collections; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -140,7 +143,6 @@ private final GitRepositoryManager repoManager; private final ProjectCache projectCache; private final MergeUtil.Factory mergeUtilFactory; - private final Submit submit; private final IdentifiedUser.GenericFactory userFactory; private final ChangeData.Factory changeDataFactory; private final FileInfoJson fileInfoJson; @@ -166,7 +168,6 @@ GitRepositoryManager repoManager, ProjectCache projectCache, MergeUtil.Factory mergeUtilFactory, - Submit submit, IdentifiedUser.GenericFactory uf, ChangeData.Factory cdf, FileInfoJson fileInfoJson, @@ -187,7 +188,6 @@ this.repoManager = repoManager; this.userFactory = uf; this.projectCache = projectCache; - this.submit = submit; this.mergeUtilFactory = mergeUtilFactory; this.fileInfoJson = fileInfoJson; this.accountLoaderFactory = ailf; @@ -407,7 +407,7 @@ // the response and avoid making a request to /submit_type from the UI. out.mergeable = in.getStatus() == Change.Status.MERGED ? null : cd.isMergeable(); - out.submittable = submit.submittable(cd); + out.submittable = Submit.submittable(cd); ChangedLines changedLines = cd.changedLines(); if (changedLines != null) { out.insertions = changedLines.insertions; @@ -437,6 +437,13 @@ out.permittedLabels = permittedLabels(ctl, cd); } out.removableReviewers = removableReviewers(ctl, out.labels.values()); + + out.reviewers = new HashMap<>(); + for (Map.Entry<ReviewerStateInternal, Collection<Account.Id>> e + : cd.reviewers().asMap().entrySet()) { + out.reviewers.put(e.getKey().asReviewerState(), + toAccountInfo(e.getValue())); + } } boolean needMessages = has(MESSAGES); @@ -828,6 +835,18 @@ return result; } + private Collection<AccountInfo> toAccountInfo( + Collection<Account.Id> accounts) { + return FluentIterable.from(accounts) + .transform(new Function<Account.Id, AccountInfo>() { + @Override + public AccountInfo apply(Account.Id id) { + return accountLoader.get(id); + } + }) + .toSortedList(AccountInfoComparator.ORDER_NULLS_FIRST); + } + private Map<String, RevisionInfo> revisions(ChangeControl ctl, Map<PatchSet.Id, PatchSet> map) throws PatchListNotAvailableException, GpgException, OrmException, IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java index 7b55b63..5148f9a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -29,8 +29,8 @@ * implementation changes, which might invalidate old entries). */ public interface ChangeKindCache { - public ChangeKind getChangeKind(ProjectState project, Repository repo, + ChangeKind getChangeKind(ProjectState project, Repository repo, ObjectId prior, ObjectId next); - public ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch); + ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch); }
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 03d189f..934a408 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
@@ -49,6 +49,10 @@ return control; } + public Change.Id getId() { + return getControl().getId(); + } + public Change getChange() { return getControl().getChange(); } @@ -90,7 +94,7 @@ public String getETag() { CurrentUser user = control.getUser(); Hasher h = Hashing.md5().newHasher() - .putBoolean(user.getStarredChanges().contains(getChange().getId())); + .putBoolean(user.getStarredChanges().contains(getId())); prepareETag(h, user); return h.hash().toString(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java index 1648a5d..c671534 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -24,11 +24,11 @@ import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.index.ChangeIndexer; import com.google.gerrit.server.project.ChangeControl; -import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.query.change.QueryChanges; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -42,8 +42,8 @@ public class ChangesCollection implements RestCollection<TopLevelResource, ChangeResource>, AcceptsPost<TopLevelResource> { + private final Provider<ReviewDb> db; private final Provider<CurrentUser> user; - private final ChangeControl.GenericFactory changeControlFactory; private final Provider<QueryChanges> queryFactory; private final DynamicMap<RestView<ChangeResource>> views; private final ChangeUtil changeUtil; @@ -52,15 +52,15 @@ @Inject ChangesCollection( + Provider<ReviewDb> db, Provider<CurrentUser> user, - ChangeControl.GenericFactory changeControlFactory, Provider<QueryChanges> queryFactory, DynamicMap<RestView<ChangeResource>> views, ChangeUtil changeUtil, CreateChange createChange, ChangeIndexer changeIndexer) { + this.db = db; this.user = user; - this.changeControlFactory = changeControlFactory; this.queryFactory = queryFactory; this.views = views; this.changeUtil = changeUtil; @@ -81,8 +81,8 @@ @Override public ChangeResource parse(TopLevelResource root, IdString id) throws ResourceNotFoundException, OrmException { - List<Change> changes = changeUtil.findChanges(id.encoded()); - if (changes.isEmpty()) { + List<ChangeControl> ctls = changeUtil.findChanges(id.encoded(), user.get()); + if (ctls.isEmpty()) { Integer changeId = Ints.tryParse(id.get()); if (changeId != null) { try { @@ -92,17 +92,15 @@ } } } - if (changes.size() != 1) { + if (ctls.size() != 1) { throw new ResourceNotFoundException(id); } - ChangeControl control; - try { - control = changeControlFactory.validateFor(changes.get(0), user.get()); - } catch (NoSuchChangeException e) { + ChangeControl ctl = ctls.get(0); + if (!ctl.isVisible(db.get())) { throw new ResourceNotFoundException(id); } - return new ChangeResource(control); + return new ChangeResource(ctl); } public ChangeResource parse(Change.Id id)
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 ddeb5c0..349117f 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
@@ -45,8 +45,12 @@ 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.index.ChangeIndexer; +import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gerrit.server.patch.PatchSetInfoFactory; import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.ProjectControl; import com.google.gerrit.server.project.RefControl; @@ -114,6 +118,9 @@ private final PatchSetInfoFactory patchSetInfoFactory; private final PatchSetInserter.Factory patchSetInserterFactory; private final BatchUpdate.Factory updateFactory; + private final ChangeIndexer indexer; + private final ChangeControl.GenericFactory changeControlFactory; + private final ChangeUpdate.Factory changeUpdateFactory; private FixInput fix; private Change change; @@ -135,7 +142,10 @@ ProjectControl.GenericFactory projectControlFactory, PatchSetInfoFactory patchSetInfoFactory, PatchSetInserter.Factory patchSetInserterFactory, - BatchUpdate.Factory updateFactory) { + BatchUpdate.Factory updateFactory, + ChangeIndexer indexer, + ChangeControl.GenericFactory changeControlFactory, + ChangeUpdate.Factory changeUpdateFactory) { this.db = db; this.repoManager = repoManager; this.user = user; @@ -144,6 +154,9 @@ this.patchSetInfoFactory = patchSetInfoFactory; this.patchSetInserterFactory = patchSetInserterFactory; this.updateFactory = updateFactory; + this.indexer = indexer; + this.changeControlFactory = changeControlFactory; + this.changeUpdateFactory = changeUpdateFactory; reset(); } @@ -507,9 +520,15 @@ return c; } }); + ChangeUpdate changeUpdate = + changeUpdateFactory.create( + changeControlFactory.controlFor(change, user.get())); + changeUpdate.fixStatus(Change.Status.MERGED); + changeUpdate.commit(); + indexer.index(db.get(), change); p.status = Status.FIXED; p.outcome = "Marked change as merged"; - } catch (OrmException e) { + } catch (OrmException | IOException | NoSuchChangeException e) { log.warn("Error marking " + change.getId() + "as merged", e); p.status = Status.FIX_FAILED; p.outcome = "Error updating status to merged";
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 3768738..576ae76 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
@@ -44,6 +44,7 @@ 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.ProjectResource; import com.google.gerrit.server.project.ProjectsCollection; @@ -154,19 +155,19 @@ ObjectId parentCommit; List<String> groups; if (input.baseChange != null) { - List<Change> changes = changeUtil.findChanges(input.baseChange); - if (changes.size() != 1) { + List<ChangeControl> ctls = changeUtil.findChanges( + input.baseChange, rsrc.getControl().getUser()); + if (ctls.size() != 1) { throw new InvalidChangeOperationException( "Base change not found: " + input.baseChange); } - Change change = Iterables.getOnlyElement(changes); - if (!rsrc.getControl().controlFor(change).isVisible(db.get())) { + ChangeControl ctl = Iterables.getOnlyElement(ctls); + if (!ctl.isVisible(db.get())) { throw new InvalidChangeOperationException( "Base change not found: " + input.baseChange); } - PatchSet ps = db.get().patchSets().get( - new PatchSet.Id(change.getId(), - change.currentPatchSetId().get())); + PatchSet ps = + db.get().patchSets().get(ctl.getChange().currentPatchSetId()); parentCommit = ObjectId.fromString(ps.getRevision().get()); groups = ps.getGroups(); } else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java index b276aae..2a99202 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -85,8 +85,7 @@ public UiAction.Description getDescription(ChangeResource rsrc) { try { return new UiAction.Description() - .setTitle(String.format("Delete draft change %d", - rsrc.getChange().getChangeId())) + .setTitle("Delete draft change " + rsrc.getId()) .setVisible(allowDrafts && rsrc.getChange().getStatus() == Status.DRAFT && rsrc.getControl().canDeleteDraft(dbProvider.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 6c37252..3a70e09 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
@@ -75,7 +75,7 @@ throws AuthException, ResourceNotFoundException, OrmException, IOException { ChangeControl control = rsrc.getControl(); - Change.Id changeId = rsrc.getChange().getId(); + Change.Id changeId = rsrc.getId(); ReviewDb db = dbProvider.get(); ChangeUpdate update = updateFactory.create(rsrc.getControl()); @@ -103,13 +103,13 @@ if (del.isEmpty()) { throw new ResourceNotFoundException(); } - ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getChange().getId(), db); + ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getId(), db); db.patchSetApprovals().delete(del); update.removeReviewer(rsrc.getUser().getAccountId()); if (msg.length() > 0) { ChangeMessage changeMessage = - new ChangeMessage(new ChangeMessage.Key(rsrc.getChange().getId(), + new ChangeMessage(new ChangeMessage.Key(rsrc.getId(), ChangeUtil.messageUUID(db)), control.getUser().getAccountId(), TimeUtil.nowTs(), rsrc.getChange().currentPatchSetId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java new file mode 100644 index 0000000..b3e59b9 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -0,0 +1,151 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.extensions.restapi.AuthException; +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.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.PatchSetApproval; +import com.google.gerrit.reviewdb.server.ReviewDb; +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.change.DeleteVote.Input; +import com.google.gerrit.server.git.BatchUpdate; +import com.google.gerrit.server.git.BatchUpdate.ChangeContext; +import com.google.gerrit.server.git.UpdateException; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import java.util.Collections; + +@Singleton +public class DeleteVote implements RestModifyView<VoteResource, Input> { + public static class Input { + } + + private final Provider<ReviewDb> db; + private final BatchUpdate.Factory batchUpdateFactory; + private final ApprovalsUtil approvalsUtil; + private final ChangeMessagesUtil cmUtil; + private final IdentifiedUser.GenericFactory userFactory; + + @Inject + DeleteVote(Provider<ReviewDb> db, + BatchUpdate.Factory batchUpdateFactory, + ApprovalsUtil approvalsUtil, + ChangeMessagesUtil cmUtil, + IdentifiedUser.GenericFactory userFactory) { + this.db = db; + this.batchUpdateFactory = batchUpdateFactory; + this.approvalsUtil = approvalsUtil; + this.cmUtil = cmUtil; + this.userFactory = userFactory; + } + + @Override + public Response<?> apply(VoteResource rsrc, Input input) + throws RestApiException, UpdateException { + ReviewerResource r = rsrc.getReviewer(); + ChangeControl ctl = r.getControl(); + Change change = r.getChange(); + try (BatchUpdate bu = batchUpdateFactory.create(db.get(), + change.getProject(), ctl.getUser().asIdentifiedUser(), + TimeUtil.nowTs())) { + bu.addOp(change.getId(), + new Op(r.getUser().getAccountId(), rsrc.getLabel())); + bu.execute(); + } + + return Response.none(); + } + + private class Op extends BatchUpdate.Op { + private final Account.Id accountId; + private final String label; + + private Op(Account.Id accountId, String label) { + this.accountId = accountId; + this.label = label; + } + + @Override + public void updateChange(ChangeContext ctx) + throws OrmException, AuthException, ResourceNotFoundException { + IdentifiedUser user = ctx.getUser().asIdentifiedUser(); + Change change = ctx.getChange(); + ChangeControl ctl = ctx.getChangeControl(); + PatchSet.Id psId = change.currentPatchSetId(); + + PatchSetApproval psa = null; + StringBuilder msg = new StringBuilder(); + for (PatchSetApproval a : approvalsUtil.byPatchSetUser( + ctx.getDb(), ctl, psId, accountId)) { + if (ctl.canRemoveReviewer(a)) { + if (a.getLabel().equals(label)) { + msg.append("Removed ") + .append(a.getLabel()).append(formatLabelValue(a.getValue())) + .append(" by ").append(userFactory.create(user.getAccountId()) + .getNameEmail()) + .append("\n"); + psa = a; + a.setValue((short)0); + ctx.getChangeUpdate().setPatchSetId(psId); + ctx.getChangeUpdate().removeApproval(label); + break; + } + } else { + throw new AuthException("delete not permitted"); + } + } + if (psa == null) { + throw new ResourceNotFoundException(); + } + ChangeUtil.bumpRowVersionNotLastUpdatedOn(change.getId(), ctx.getDb()); + ctx.getDb().patchSetApprovals().update(Collections.singleton(psa)); + + if (msg.length() > 0) { + ChangeMessage changeMessage = + new ChangeMessage(new ChangeMessage.Key(change.getId(), + ChangeUtil.messageUUID(ctx.getDb())), + user.getAccountId(), + ctx.getWhen(), + change.currentPatchSetId()); + changeMessage.setMessage(msg.toString()); + cmUtil.addChangeMessage(ctx.getDb(), ctx.getChangeUpdate(), + changeMessage); + } + } + } + + private static String formatLabelValue(short value) { + if (value > 0) { + return "+" + value; + } else { + return Short.toString(value); + } + } +}
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 71974c4..e31b11f 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
@@ -64,6 +64,7 @@ ? e.getChangeType().getCode() : null; d.oldPath = e.getOldName(); d.sizeDelta = e.getSizeDelta(); + d.size = e.getSize(); if (e.getPatchType() == Patch.PatchType.BINARY) { d.binary = true; } else { @@ -78,6 +79,7 @@ // a single record with data from both sides. d.status = Patch.ChangeType.REWRITE.getCode(); d.sizeDelta = o.sizeDelta; + d.size = o.size; if (o.binary != null && o.binary) { d.binary = true; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java index 547a500..c099e43 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java
@@ -39,9 +39,9 @@ } } - public boolean get(ObjectId commit, Ref intoRef, SubmitType submitType, + boolean get(ObjectId commit, Ref intoRef, SubmitType submitType, String mergeStrategy, Branch.NameKey dest, Repository repo, ReviewDb db); - public Boolean getIfPresent(ObjectId commit, Ref intoRef, + Boolean getIfPresent(ObjectId commit, Ref intoRef, SubmitType submitType, String mergeStrategy); }
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 9c588ed..0ef8b51 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
@@ -21,6 +21,7 @@ import static com.google.gerrit.server.change.FileResource.FILE_KIND; import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND; import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND; +import static com.google.gerrit.server.change.VoteResource.VOTE_KIND; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.restapi.RestApiModule; @@ -37,6 +38,7 @@ bind(DraftComments.class); bind(Comments.class); bind(Files.class); + bind(Votes.class); DynamicMap.mapOf(binder(), CHANGE_KIND); DynamicMap.mapOf(binder(), COMMENT_KIND); @@ -45,6 +47,7 @@ DynamicMap.mapOf(binder(), REVIEWER_KIND); DynamicMap.mapOf(binder(), REVISION_KIND); DynamicMap.mapOf(binder(), CHANGE_EDIT_KIND); + DynamicMap.mapOf(binder(), VOTE_KIND); get(CHANGE_KIND).to(GetChange.class); get(CHANGE_KIND, "detail").to(GetDetail.class); @@ -73,6 +76,8 @@ child(CHANGE_KIND, "reviewers").to(Reviewers.class); get(REVIEWER_KIND).to(GetReviewer.class); delete(REVIEWER_KIND).to(DeleteReviewer.class); + child(REVIEWER_KIND, "votes").to(Votes.class); + delete(VOTE_KIND).to(DeleteVote.class); child(CHANGE_KIND, "revisions").to(Revisions.class); get(REVISION_KIND, "actions").to(GetRevisionActions.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java index c34fb43..9a72b07 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
@@ -17,6 +17,8 @@ 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 static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import com.google.common.collect.SetMultimap; import com.google.gerrit.common.ChangeHooks; @@ -42,7 +44,7 @@ import com.google.gerrit.server.git.validators.CommitValidationException; import com.google.gerrit.server.git.validators.CommitValidators; import com.google.gerrit.server.mail.ReplacePatchSetSender; -import com.google.gerrit.server.notedb.ReviewerState; +import com.google.gerrit.server.notedb.ReviewerStateInternal; import com.google.gerrit.server.patch.PatchSetInfoFactory; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeModifiedException; @@ -106,7 +108,7 @@ private PatchSet patchSet; private PatchSetInfo patchSetInfo; private ChangeMessage changeMessage; - private SetMultimap<ReviewerState, Account.Id> oldReviewers; + private SetMultimap<ReviewerStateInternal, Account.Id> oldReviewers; @AssistedInject public PatchSetInserter(ChangeHooks hooks, @@ -235,8 +237,7 @@ if (message != null) { changeMessage = new ChangeMessage( - new ChangeMessage.Key( - ctl.getChange().getId(), ChangeUtil.messageUUID(db)), + new ChangeMessage.Key(ctl.getId(), ChangeUtil.messageUUID(db)), ctx.getUser().getAccountId(), ctx.getWhen(), patchSet.getId()); changeMessage.setMessage(message); } @@ -280,8 +281,8 @@ 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.addReviewers(oldReviewers.get(REVIEWER)); + cm.addExtraCC(oldReviewers.get(CC)); cm.send(); } catch (Exception err) { log.error("Cannot send email for new patch set on change " @@ -328,7 +329,7 @@ break; } } catch (CommitValidationException e) { - throw new ResourceConflictException(e.getMessage()); + throw new ResourceConflictException(e.getFullMessage()); } } }
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 a3fc2e1..6d7720d 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
@@ -52,7 +52,7 @@ req.getChange().getProject(), req.getControl().getUser(), TimeUtil.nowTs())) { SetHashtagsOp op = hashtagsFactory.create(input); - bu.addOp(req.getChange().getId(), op); + bu.addOp(req.getId(), op); bu.execute(); return Response.<ImmutableSortedSet<String>> ok(op.getUpdatedHashtags()); }
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 e2473e7..f2d0ffa 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
@@ -17,6 +17,7 @@ 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 static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.auto.value.AutoValue; @@ -349,7 +350,8 @@ } @Override - public void updateChange(ChangeContext ctx) throws OrmException { + public void updateChange(ChangeContext ctx) + throws OrmException, ResourceConflictException { user = ctx.getUser().asIdentifiedUser(); change = ctx.getChange(); if (change.getLastUpdatedOn().before(ctx.getWhen())) { @@ -500,7 +502,8 @@ return drafts; } - private boolean updateLabels(ChangeContext ctx) throws OrmException { + private boolean updateLabels(ChangeContext ctx) + throws OrmException, ResourceConflictException { Map<String, Short> labels = in.labels; if (labels == null) { labels = Collections.emptyMap(); @@ -515,10 +518,6 @@ 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; - } PatchSetApproval c = current.remove(lt.getName()); String normName = lt.getName(); @@ -550,10 +549,16 @@ ups.add(c); addLabelDelta(normName, c.getValue()); categories.put(normName, c.getValue()); + update.putReviewer(user.getAccountId(), REVIEWER); update.putApproval(ent.getKey(), ent.getValue()); } } + if (!del.isEmpty() || !ups.isEmpty()) { + if (ctx.getChange().getStatus().isClosed()) { + throw new ResourceConflictException("change is closed"); + } + } forceCallerAsReviewer(ctx, current, ups, del); ctx.getDb().patchSetApprovals().delete(del); ctx.getDb().patchSetApprovals().upsert(ups);
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 3ab84ab..1766ed2 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
@@ -230,9 +230,9 @@ ReviewDb db = dbProvider.get(); ChangeUpdate update = updateFactory.create(rsrc.getControl()); List<PatchSetApproval> added; - db.changes().beginTransaction(rsrc.getChange().getId()); + db.changes().beginTransaction(rsrc.getId()); try { - ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getChange().getId(), db); + ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getId(), db); added = approvalsUtil.addReviewers(db, rsrc.getNotes(), update, rsrc.getControl().getLabelTypes(), rsrc.getChange(), reviewers.keySet()); @@ -243,7 +243,7 @@ update.commit(); CheckedFuture<?, IOException> indexFuture = - indexer.indexAsync(rsrc.getChange().getId()); + indexer.indexAsync(rsrc.getId()); result.reviewers = Lists.newArrayListWithCapacity(added.size()); for (PatchSetApproval psa : added) { // New reviewers have value 0, don't bother normalizing.
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 1061d4b..df629bb 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
@@ -77,7 +77,7 @@ 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.addOp(req.getId(), op); u.execute(); } return Strings.isNullOrEmpty(op.newTopicName) @@ -101,8 +101,8 @@ @Override public void updateChange(ChangeContext ctx) throws OrmException { change = ctx.getChange(); - String newTopicName = Strings.nullToEmpty(input.topic); - String oldTopicName = Strings.nullToEmpty(change.getTopic()); + newTopicName = Strings.nullToEmpty(input.topic); + oldTopicName = Strings.nullToEmpty(change.getTopic()); if (oldTopicName.equals(newTopicName)) { return; }
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 60f285f..5bcd23c 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
@@ -90,7 +90,7 @@ ObjectInserter oi = repo.newObjectInserter(); BatchUpdate bu = updateFactory.create(dbProvider.get(), change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) { - if (!control.canRebase()) { + if (!control.canRebase(dbProvider.get())) { throw new AuthException("rebase not permitted"); } else if (!change.getStatus().isOpen()) { throw new ResourceConflictException("change is " @@ -209,9 +209,15 @@ public UiAction.Description getDescription(RevisionResource resource) { PatchSet patchSet = resource.getPatchSet(); Branch.NameKey dest = resource.getChange().getDest(); + boolean canRebase = false; + try { + canRebase = resource.getControl().canRebase(dbProvider.get()); + } catch (OrmException e) { + log.error("Cannot check canRebase status. Assuming false.", e); + } boolean visible = resource.getChange().getStatus().isOpen() && resource.isCurrent() - && resource.getControl().canRebase(); + && canRebase; boolean enabled = true; if (visible) {
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 5b0eb6d..421fced 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
@@ -80,14 +80,14 @@ public ChangeInfo apply(ChangeResource req, RestoreInput input) throws RestApiException, UpdateException, OrmException { ChangeControl ctl = req.getControl(); - if (!ctl.canRestore()) { + if (!ctl.canRestore(dbProvider.get())) { throw new AuthException("restore not permitted"); } 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(); + u.addOp(req.getId(), op).execute(); } return json.create(ChangeJson.NO_OPTIONS).format(op.change); } @@ -160,11 +160,17 @@ @Override public UiAction.Description getDescription(ChangeResource resource) { + boolean canRestore = false; + try { + canRestore = resource.getControl().canRestore(dbProvider.get()); + } catch (OrmException e) { + log.error("Cannot check canRestore status. Assuming false.", e); + } return new UiAction.Description() .setLabel("Restore") .setTitle("Restore the change") .setVisible(resource.getChange().getStatus() == Status.ABANDONED - && resource.getControl().canRestore()); + && canRestore); } private static String status(Change change) {
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 dc2ed5d..7e3845c 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
@@ -29,8 +29,8 @@ 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.NoSuchChangeException; +import com.google.gerrit.server.project.RefControl; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -59,9 +59,9 @@ public ChangeInfo apply(ChangeResource req, RevertInput input) throws IOException, OrmException, RestApiException, UpdateException { - ChangeControl control = req.getControl(); + RefControl refControl = req.getControl().getRefControl(); Change change = req.getChange(); - if (!control.canAddPatchSet()) { + if (!refControl.canUpload()) { throw new AuthException("revert not permitted"); } else if (change.getStatus() != Status.MERGED) { throw new ResourceConflictException("change is " + status(change)); @@ -69,7 +69,7 @@ Change.Id revertedChangeId; try { - revertedChangeId = changeUtil.revert(control, + revertedChangeId = changeUtil.revert(req.getControl(), change.currentPatchSetId(), Strings.emptyToNull(input.message), new PersonIdent(myIdent, TimeUtil.nowTs()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java index bb5775b..734ebb6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -129,7 +129,7 @@ // Chance of collision rises; look at all patch sets on the change. List<RevisionResource> out = Lists.newArrayList(); for (PatchSet ps : dbProvider.get().patchSets() - .byChange(change.getChange().getId())) { + .byChange(change.getId())) { if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) { out.add(new RevisionResource(change, ps)); } @@ -141,7 +141,7 @@ private List<RevisionResource> byLegacyPatchSetId(ChangeResource change, String id) throws OrmException { PatchSet ps = dbProvider.get().patchSets().get(new PatchSet.Id( - change.getChange().getId(), + change.getId(), Integer.parseInt(id))); if (ps != null) { return Collections.singletonList(new RevisionResource(change, ps)); @@ -161,8 +161,7 @@ throws AuthException, IOException { Optional<ChangeEdit> edit = editUtil.byChange(change.getChange()); if (edit.isPresent()) { - PatchSet ps = new PatchSet(new PatchSet.Id( - change.getChange().getId(), 0)); + PatchSet ps = new PatchSet(new PatchSet.Id(change.getId(), 0)); ps.setRevision(edit.get().getRevision()); if (revid == null || edit.get().getRevision().equals(revid)) { return Collections.singletonList( @@ -174,7 +173,7 @@ private static List<RevisionResource> toResources(final ChangeResource change, Iterable<PatchSet> patchSets) { - final Change.Id changeId = change.getChange().getId(); + final Change.Id changeId = change.getId(); return FluentIterable.from(patchSets) .filter(new Predicate<PatchSet>() { @Override
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 1a92ec0..d22b1dd 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
@@ -259,7 +259,7 @@ * @param cd the change to check for submittability * @return if the change has any problems for submission */ - public boolean submittable(ChangeData cd) { + public static boolean submittable(ChangeData cd) { try { MergeOp.checkSubmitRule(cd); return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/VoteResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/VoteResource.java new file mode 100644 index 0000000..4dfaff0 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/VoteResource.java
@@ -0,0 +1,40 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.gerrit.extensions.restapi.RestResource; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.inject.TypeLiteral; + +public class VoteResource implements RestResource { + public static final TypeLiteral<RestView<VoteResource>> VOTE_KIND = + new TypeLiteral<RestView<VoteResource>>() {}; + + private final ReviewerResource reviewer; + private final String label; + + public VoteResource(ReviewerResource reviewer, String label) { + this.reviewer = reviewer; + this.label = label; + } + + public ReviewerResource getReviewer() { + return reviewer; + } + + public String getLabel() { + return label; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Votes.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Votes.java new file mode 100644 index 0000000..a86ce7f --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Votes.java
@@ -0,0 +1,89 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.change; + +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.ChildCollection; +import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.reviewdb.client.PatchSetApproval; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.ApprovalsUtil; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import java.util.Map; +import java.util.TreeMap; + +@Singleton +public class Votes implements ChildCollection<ReviewerResource, VoteResource> { + private final DynamicMap<RestView<VoteResource>> views; + private final List list; + + @Inject + Votes(DynamicMap<RestView<VoteResource>> views, + List list) { + this.views = views; + this.list = list; + } + + @Override + public DynamicMap<RestView<VoteResource>> views() { + return views; + } + + @Override + public RestView<ReviewerResource> list() throws AuthException { + return list; + } + + @Override + public VoteResource parse(ReviewerResource reviewer, IdString id) + throws ResourceNotFoundException, OrmException, AuthException { + return new VoteResource(reviewer, id.get()); + } + + @Singleton + public static class List implements RestReadView<ReviewerResource> { + private final Provider<ReviewDb> db; + private final ApprovalsUtil approvalsUtil; + + @Inject + List(Provider<ReviewDb> db, + ApprovalsUtil approvalsUtil) { + this.db = db; + this.approvalsUtil = approvalsUtil; + } + + @Override + public Map<String, Short> apply(ReviewerResource rsrc) throws OrmException { + Map<String, Short> votes = new TreeMap<>(); + Iterable<PatchSetApproval> byPatchSetUser = approvalsUtil.byPatchSetUser( + db.get(), + rsrc.getControl(), + rsrc.getChange().currentPatchSetId(), + rsrc.getUser().getAccountId()); + for (PatchSetApproval psa : byPatchSetUser) { + votes.put(psa.getLabel(), psa.getValue()); + } + return votes; + } + } +}
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 ca3afad..6b93a68 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
@@ -75,6 +75,7 @@ import com.google.gerrit.server.change.ChangeKindCacheImpl; import com.google.gerrit.server.change.MergeabilityCacheImpl; import com.google.gerrit.server.events.EventFactory; +import com.google.gerrit.server.events.EventsMetrics; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.EmailMerge; @@ -124,6 +125,7 @@ import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.SectionSortCache; import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.ChangeQueryBuilder; import com.google.gerrit.server.query.change.ConflictsCacheImpl; import com.google.gerrit.server.ssh.SshAddressesModule; import com.google.gerrit.server.tools.ToolsCatalog; @@ -270,6 +272,7 @@ DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ReindexAfterUpdate.class); DynamicSet.bind(binder(), GitReferenceUpdatedListener.class) .to(ProjectConfigEntry.UpdateChecker.class); + DynamicSet.bind(binder(), EventListener.class).to(EventsMetrics.class); DynamicSet.setOf(binder(), EventListener.class); DynamicSet.setOf(binder(), CommitValidationListener.class); DynamicSet.setOf(binder(), RefOperationValidationListener.class); @@ -297,6 +300,8 @@ factory(UploadValidators.Factory.class); DynamicSet.setOf(binder(), UploadValidationListener.class); + DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class); + bind(AnonymousUser.class); factory(CommitValidators.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 9eca842..e5ac370 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
@@ -35,6 +35,7 @@ import com.google.gerrit.server.change.ArchiveFormat; import com.google.gerrit.server.change.GetArchive; import com.google.gerrit.server.change.Submit; +import com.google.gerrit.server.documentation.QueryDocumentationExecutor; import com.google.inject.Inject; import org.eclipse.jgit.lib.Config; @@ -64,6 +65,7 @@ private final GitwebConfig gitwebConfig; private final DynamicItem<AvatarProvider> avatar; private final boolean enableSignedPush; + private final QueryDocumentationExecutor docSearcher; @Inject public GetServerInfo( @@ -79,7 +81,8 @@ @AnonymousCowardName String anonymousCowardName, GitwebConfig gitwebConfig, DynamicItem<AvatarProvider> avatar, - @EnableSignedPush boolean enableSignedPush) { + @EnableSignedPush boolean enableSignedPush, + QueryDocumentationExecutor docSearcher) { this.config = config; this.authConfig = authConfig; this.realm = realm; @@ -93,6 +96,7 @@ this.gitwebConfig = gitwebConfig; this.avatar = avatar; this.enableSignedPush = enableSignedPush; + this.docSearcher = docSearcher; } @Override @@ -238,6 +242,7 @@ info.reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl"); info.reportBugText = cfg.getString("gerrit", null, "reportBugText"); info.docUrl = getDocUrl(cfg); + info.docSearch = docSearcher.isAvailable(); info.editGpgKeys = toBoolean(enableSignedPush && cfg.getBoolean("gerrit", null, "editGpgKeys", true)); return info; @@ -366,10 +371,11 @@ public static class GerritInfo { public String allProjects; public String allUsers; + public Boolean docSearch; public String docUrl; + public Boolean editGpgKeys; 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 382c8fd..653f5bd 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
@@ -58,15 +58,19 @@ this.cacheMap = cacheMap; } + public Map<String, CacheInfo> getCacheInfos() { + Map<String, CacheInfo> cacheInfos = new TreeMap<>(); + for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) { + cacheInfos.put(cacheNameOf(e.getPluginName(), e.getExportName()), + new CacheInfo(e.getProvider().get())); + } + return cacheInfos; + } + @Override public Object apply(ConfigResource rsrc) { if (format == null) { - Map<String, CacheInfo> cacheInfos = new TreeMap<>(); - for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) { - cacheInfos.put(cacheNameOf(e.getPluginName(), e.getExportName()), - new CacheInfo(e.getProvider().get())); - } - return cacheInfos; + return getCacheInfos(); } else { List<String> cacheNames = new ArrayList<>(); for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java index 23615d6..34946ec 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -37,7 +37,7 @@ public class ProjectOwnerGroupsProvider extends GroupSetProvider { public interface Factory { - public ProjectOwnerGroupsProvider create(Project.NameKey project); + ProjectOwnerGroupsProvider create(Project.NameKey project); } @AssistedInject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ThreadSettingsConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ThreadSettingsConfig.java new file mode 100644 index 0000000..c62583e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ThreadSettingsConfig.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.config; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.jgit.lib.Config; + +@Singleton +public class ThreadSettingsConfig { + private final int sshdThreads; + private final int httpdMaxThreads; + private final int sshdBatchThreads; + private final int databasePoolLimit; + + @Inject + ThreadSettingsConfig(@GerritServerConfig Config cfg) { + int cores = Runtime.getRuntime().availableProcessors(); + sshdThreads = cfg.getInt("sshd", "threads", 2 * cores); + httpdMaxThreads = cfg.getInt("httpd", "maxThreads", 25); + int defaultDatabasePoolLimit = sshdThreads + httpdMaxThreads + 2; + databasePoolLimit = + cfg.getInt("database", "poolLimit", defaultDatabasePoolLimit); + sshdBatchThreads = cores == 1 ? 1 : 2; + } + + public int getDatabasePoolLimit() { + return databasePoolLimit; + } + + public int getHttpdMaxThreads() { + return httpdMaxThreads; + } + + public int getSshdThreads() { + return sshdThreads; + } + + public int getSshdBatchTreads() { + return sshdBatchThreads; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java index 446013e..438795f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -74,7 +74,7 @@ } public List<DocResult> doQuery(String q) throws DocQueryException { - if (parser == null || searcher == null) { + if (!isAvailable()) { throw new DocQueryException("Documentation search not available"); } try { @@ -123,6 +123,10 @@ return dir; } + public boolean isAvailable() { + return parser != null && searcher != null; + } + @SuppressWarnings("serial") public static class DocQueryException extends Exception { DocQueryException() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventsMetrics.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventsMetrics.java new file mode 100644 index 0000000..eaaf1a83 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventsMetrics.java
@@ -0,0 +1,43 @@ +// 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.events; + +import com.google.gerrit.common.EventListener; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class EventsMetrics implements EventListener { + private final Counter1<String> events; + + @Inject + public EventsMetrics(MetricMaker metricMaker) { + events = metricMaker.newCounter( + "events", + new Description("Triggered events") + .setRate() + .setUnit("triggered events"), + Field.ofString("type")); + } + + @Override + public void onEvent(com.google.gerrit.server.events.Event event) { + events.increment(event.getType()); + } +}
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 index c54fe26..6804816 100644 --- 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
@@ -77,7 +77,7 @@ */ public class BatchUpdate implements AutoCloseable { public interface Factory { - public BatchUpdate create(ReviewDb db, Project.NameKey project, + BatchUpdate create(ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java index 391ccd0..209cbe0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
@@ -20,5 +20,5 @@ import java.util.List; public interface ChangeCache { - public List<Change> get(Project.NameKey name); + List<Change> get(Project.NameKey name); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java index dee2df0..ab6d66c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -41,7 +41,7 @@ * repository. * @throws IOException the name cannot be read as a repository. */ - public abstract Repository openRepository(Project.NameKey name) + Repository openRepository(Project.NameKey name) throws RepositoryNotFoundException, IOException; /** @@ -58,7 +58,7 @@ * @throws RepositoryNotFoundException the name is invalid. * @throws IOException the repository cannot be created. */ - public abstract Repository createRepository(Project.NameKey name) + Repository createRepository(Project.NameKey name) throws RepositoryCaseMismatchException, RepositoryNotFoundException, IOException; @@ -76,11 +76,11 @@ * repository. * @throws IOException the name cannot be read as a repository. */ - public abstract Repository openMetadataRepository(Project.NameKey name) + Repository openMetadataRepository(Project.NameKey name) throws RepositoryNotFoundException, IOException; /** @return set of all known projects, sorted by natural NameKey order. */ - public abstract SortedSet<Project.NameKey> list(); + SortedSet<Project.NameKey> list(); /** * Read the {@code GIT_DIR/description} file for gitweb. @@ -94,7 +94,7 @@ * @throws IOException the description file exists, but is not readable by * this process. */ - public abstract String getProjectDescription(Project.NameKey name) + String getProjectDescription(Project.NameKey name) throws RepositoryNotFoundException, IOException; /** @@ -106,6 +106,6 @@ * @param name the repository name, relative to the base directory. * @param description new description text for the repository. */ - public abstract void setProjectDescription(Project.NameKey name, + void setProjectDescription(Project.NameKey name, final String description); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java index 0dc7aa9..097ab6e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
@@ -116,7 +116,7 @@ LabelTypes labelTypes = ctl.getLabelTypes(); for (PatchSetApproval psa : approvals) { Change.Id changeId = psa.getKey().getParentKey().getParentKey(); - checkArgument(changeId.equals(ctl.getChange().getId()), + checkArgument(changeId.equals(ctl.getId()), "Approval %s does not match change %s", psa.getKey(), ctl.getChange().getKey()); if (psa.isSubmit()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LazyPostReceiveHookChain.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LazyPostReceiveHookChain.java new file mode 100644 index 0000000..ebfaae7 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LazyPostReceiveHookChain.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.server.git; + +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.inject.Inject; + +import org.eclipse.jgit.transport.PostReceiveHook; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.ReceivePack; + +import java.util.Collection; + +class LazyPostReceiveHookChain implements PostReceiveHook { + private final DynamicSet<PostReceiveHook> hooks; + + @Inject + LazyPostReceiveHookChain(DynamicSet<PostReceiveHook> hooks) { + this.hooks = hooks; + } + + @Override + public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) { + for (PostReceiveHook h : hooks) { + h.onPostReceive(rp, commands); + } + } +}
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 fc2f672..e809e67 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
@@ -15,6 +15,7 @@ package com.google.gerrit.server.git; import static com.google.common.base.Preconditions.checkState; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import static org.eclipse.jgit.lib.RefDatabase.ALL; import com.google.common.base.Optional; @@ -859,6 +860,9 @@ // If mergeTip is null merge failed and mergeResultRev will not be read. ObjectId mergeResultRev = mergeTip != null ? mergeTip.getMergeResults().get(commit) : null; + // The change notes must be forcefully reloaded so that the SUBMIT + // approval that we added earlier is visible + commit.notes().reload(); try { ChangeMessage msg; switch (s) { @@ -1050,6 +1054,7 @@ logDebug("Add approval for " + cd + " from user " + user); ChangeUpdate update = updateFactory.create(control, timestamp); + update.putReviewer(user.getAccountId(), REVIEWER); List<SubmitRecord> record = records.get(cd.change().getId()); if (record != null) { update.merge(record); @@ -1063,6 +1068,11 @@ // If the submit strategy created a new revision (rebase, cherry-pick) // approve that as well if (!psIdNewRev.equals(psId)) { + update.setPatchSetId(psId); + update.commit(); + // Create a new ChangeUpdate instance because we need to store meta data + // on another patch set (psIdNewRev). + update = updateFactory.create(control, timestamp); batch = approve(control, psIdNewRev, user, update, timestamp); // Write update commit after all normalized label commits. @@ -1072,6 +1082,7 @@ } finally { db.rollback(); } + update.commit(); indexer.index(db, cd.change()); }
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 addbc1b..246647c 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
@@ -141,7 +141,7 @@ private static final String KEY_CAN_OVERRIDE = "canOverride"; private static final String KEY_Branch = "branch"; private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of( - "MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp"); + "MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp", "PatchSetLock"); private static final String PLUGIN = "plugin";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java index c7d925c..cd37c9e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
@@ -19,5 +19,5 @@ INTERACTIVE, BATCH } - public WorkQueue.Executor getQueue(QueueType type); + WorkQueue.Executor getQueue(QueueType type); }
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 5da4ec7..63b03b6 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
@@ -53,7 +53,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.gerrit.common.ChangeHookRunner.HookResult; import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.TimeUtil; @@ -65,6 +64,7 @@ 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.registration.DynamicSet; import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.reviewdb.client.Account; @@ -308,6 +308,7 @@ private final SshInfo sshInfo; private final AllProjectsName allProjectsName; private final ReceiveConfig receiveConfig; + private final DynamicSet<ReceivePackInitializer> initializers; private final ChangeKindCache changeKindCache; private final BatchUpdate.Factory batchUpdateFactory; private final SetHashtagsOp.Factory hashtagsFactory; @@ -378,7 +379,10 @@ final ChangeIndexer indexer, final SshInfo sshInfo, final AllProjectsName allProjectsName, - ReceiveConfig config, + ReceiveConfig receiveConfig, + TransferConfig transferConfig, + DynamicSet<ReceivePackInitializer> initializers, + Provider<LazyPostReceiveHookChain> lazyPostReceive, @Assisted final ProjectControl projectControl, @Assisted final Repository repo, final Provider<SubmoduleOp> subOpProvider, @@ -420,7 +424,8 @@ this.indexer = indexer; this.sshInfo = sshInfo; this.allProjectsName = allProjectsName; - this.receiveConfig = config; + this.receiveConfig = receiveConfig; + this.initializers = initializers; this.changeKindCache = changeKindCache; this.batchUpdateFactory = batchUpdateFactory; this.hashtagsFactory = hashtagsFactory; @@ -448,6 +453,10 @@ rp.setAllowCreates(true); rp.setAllowDeletes(true); rp.setAllowNonFastForwards(true); + rp.setRefLogIdent(user.newRefLogIdent()); + rp.setTimeout(transferConfig.getTimeout()); + rp.setMaxObjectSizeLimit(transferConfig.getEffectiveMaxObjectSizeLimit( + projectControl.getProjectState())); rp.setCheckReceivedObjects(ps.getConfig().getCheckReceivedObjects()); rp.setRefFilter(new RefFilter() { @Override @@ -465,7 +474,7 @@ }); if (!projectControl.allRefsAreVisible()) { - rp.setCheckReferencedObjectsAreReachable(config.checkReferencedObjectsAreReachable); + rp.setCheckReferencedObjectsAreReachable(receiveConfig.checkReferencedObjectsAreReachable); rp.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo, projectControl, db, false)); } List<AdvertiseRefsHook> advHooks = new ArrayList<>(3); @@ -497,6 +506,13 @@ db, queryProvider, projectControl.getProject().getNameKey())); advHooks.add(new HackPushNegotiateHook()); rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks)); + rp.setPostReceiveHook(lazyPostReceive.get()); + } + + public void init() { + for (ReceivePackInitializer i : initializers) { + i.init(projectControl.getProject().getNameKey(), rp); + } } /** Add reviewers for new (or updated) changes. */ @@ -880,19 +896,6 @@ continue; } - HookResult result = hooks.doRefUpdateHook(project, cmd.getRefName(), - user.getAccount(), cmd.getOldId(), - cmd.getNewId()); - - if (result != null) { - final String message = result.toString().trim(); - if (result.getExitValue() != 0) { - reject(cmd, message); - continue; - } - rp.sendMessage(message); - } - if (MagicBranch.isMagicBranch(cmd.getRefName())) { parseMagicBranch(cmd); continue; @@ -1966,7 +1969,7 @@ } } - boolean validate(boolean autoClose) throws IOException { + boolean validate(boolean autoClose) throws IOException, OrmException { if (!autoClose && inputCommand.getResult() != NOT_ATTEMPTED) { return false; } else if (change == null) { @@ -1989,8 +1992,12 @@ } changeCtl = projectControl.controlFor(change); - if (!changeCtl.canAddPatchSet()) { - reject(inputCommand, "cannot replace " + ontoChange); + if (!changeCtl.canAddPatchSet(db)) { + String locked = "."; + if (changeCtl.isPatchSetLocked(db)) { + locked = ". Change is patch set locked."; + } + reject(inputCommand, "cannot replace " + ontoChange + locked); return false; } else if (change.getStatus().isClosed()) { reject(inputCommand, "change " + ontoChange + " closed");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java index ec0bdaf..ee229d4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java
@@ -32,5 +32,5 @@ * @param project project for which the ReceivePack is created * @param receivePack the ReceivePack instance which is being initialized */ - public void init(Project.NameKey project, ReceivePack receivePack); + void init(Project.NameKey project, ReceivePack receivePack); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java index 3cbac3b..d77c7e2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TabFile.java
@@ -28,7 +28,7 @@ public class TabFile { public interface Parser { - public String parse(String str); + String parse(String str); } public static Parser TRIM = new Parser() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/UploadPackMetricsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/UploadPackMetricsHook.java new file mode 100644 index 0000000..fd4e7d3 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/UploadPackMetricsHook.java
@@ -0,0 +1,98 @@ +// 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 java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Histogram1; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer1; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.jgit.storage.pack.PackStatistics; +import org.eclipse.jgit.transport.PostUploadHook; + +@Singleton +public class UploadPackMetricsHook implements PostUploadHook { + enum Operation { + CLONE, + FETCH; + } + + private final Counter1<Operation> requestCount; + private final Timer1<Operation> counting; + private final Timer1<Operation> compressing; + private final Timer1<Operation> writing; + private final Histogram1<Operation> packBytes; + + @Inject + UploadPackMetricsHook(MetricMaker metricMaker) { + Field<Operation> operation = Field.ofEnum(Operation.class, "operation"); + requestCount = metricMaker.newCounter( + "git/upload-pack/request_count", + new Description("Total number of git-upload-pack requests") + .setRate() + .setUnit("requests"), + operation); + + counting = metricMaker.newTimer( + "git/upload-pack/phase_counting", + new Description("Time spenting in the 'Counting...' phase") + .setCumulative() + .setUnit(Units.MILLISECONDS), + operation); + + compressing = metricMaker.newTimer( + "git/upload-pack/phase_compressing", + new Description("Time spenting in the 'Compressing...' phase") + .setCumulative() + .setUnit(Units.MILLISECONDS), + operation); + + writing = metricMaker.newTimer( + "git/upload-pack/phase_writing", + new Description("Time spenting transferring bytes to client") + .setCumulative() + .setUnit(Units.MILLISECONDS), + operation); + + packBytes = metricMaker.newHistogram( + "git/upload-pack/pack_bytes", + new Description("Distribution of sizes of packs sent to clients") + .setCumulative() + .setUnit(Units.BYTES), + operation); + } + + @Override + public void onPostUpload(PackStatistics stats) { + Operation op = Operation.FETCH; + if (stats.getUninterestingObjects() == null + || stats.getUninterestingObjects().isEmpty()) { + op = Operation.CLONE; + } + + requestCount.increment(op); + counting.record(op, stats.getTimeCounting(), MILLISECONDS); + compressing.record(op, stats.getTimeCompressing(), MILLISECONDS); + writing.record(op, stats.getTimeWriting(), MILLISECONDS); + packBytes.record(op, stats.getTotalBytes()); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java index 4a8163b..954e412 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -246,7 +246,7 @@ /** Runnable needing to know it was canceled. */ public interface CancelableRunnable extends Runnable { /** Notifies the runnable it was canceled. */ - public void cancel(); + void cancel(); } /** A wrapper around a scheduled Runnable, as maintained in the queue. */
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 61b1b00..91c6601 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
@@ -176,6 +176,7 @@ // Merge conflict; don't update change. return; } + ctx.getChangeUpdate().setPatchSetId(psId); PatchSet ps = new PatchSet(psId); ps.setCreatedOn(ctx.getWhen()); ps.setUploader(args.caller.getAccountId()); @@ -191,6 +192,7 @@ for (PatchSetApproval a : args.approvalsUtil.byPatchSet( args.db, toMerge.getControl(), toMerge.getPatchsetId())) { approvals.add(new PatchSetApproval(ps.getId(), a)); + ctx.getChangeUpdate().putApproval(a.getLabel(), a.getValue()); } args.db.patchSetApprovals().insert(approvals);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java index 029096e..91c6a14 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java
@@ -23,7 +23,8 @@ private static final long serialVersionUID = 1L; private final List<CommitValidationMessage> messages; - public CommitValidationException(String reason, List<CommitValidationMessage> messages) { + public CommitValidationException(String reason, + List<CommitValidationMessage> messages) { super(reason); this.messages = messages; } @@ -41,4 +42,16 @@ public List<CommitValidationMessage> getMessages() { return messages; } + + /** @return the reason string along with all validation messages. */ + public String getFullMessage() { + StringBuilder sb = new StringBuilder(getMessage()); + if (!messages.isEmpty()) { + sb.append(':'); + for (CommitValidationMessage msg : messages) { + sb.append("\n ").append(msg.getMessage()); + } + } + return sb.toString(); + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationListener.java index e98c49b..9d7b3e6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationListener.java
@@ -34,6 +34,6 @@ * @return list of validation messages * @throws CommitValidationException if validation fails */ - public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) + List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) throws CommitValidationException; }
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 6ac5707..b069ca9 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
@@ -131,6 +131,7 @@ validators.add(new ConfigValidator(refControl, repo)); validators.add(new BannedCommitsValidator(rejectCommits)); validators.add(new PluginCommitValidationListener(commitValidationListeners)); + validators.add(new ChangeHookValidator(hooks)); List<CommitValidationMessage> messages = new LinkedList<>(); @@ -164,7 +165,7 @@ } validators.add(new ConfigValidator(refControl, repo)); validators.add(new PluginCommitValidationListener(commitValidationListeners)); - validators.add(new ChangeHookValidator(refControl, hooks)); + validators.add(new ChangeHookValidator(hooks)); List<CommitValidationMessage> messages = new LinkedList<>(); @@ -548,42 +549,37 @@ /** 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; + public ChangeHookValidator(ChangeHooks hooks) { this.hooks = hooks; } @Override public List<CommitValidationMessage> onCommitReceived( CommitReceivedEvent receiveEvent) throws CommitValidationException { + IdentifiedUser user = receiveEvent.user; + String refname = receiveEvent.refName; + ObjectId old = ObjectId.zeroId(); + if (receiveEvent.commit.getParentCount() > 0) { + old = receiveEvent.commit.getParent(0); + } - if (refControl.getUser().isIdentifiedUser()) { - IdentifiedUser user = refControl.getUser().asIdentifiedUser(); - - String refname = receiveEvent.refName; - ObjectId old = ObjectId.zeroId(); - if (receiveEvent.commit.getParentCount() > 0) { - 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()); - } + 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(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java index 0a8d245..6e98223 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java
@@ -39,7 +39,7 @@ * @param patchSetId the patch set ID * @throws MergeValidationException if the commit fails to validate */ - public void onPreMerge(Repository repo, + void onPreMerge(Repository repo, CodeReviewCommit commit, ProjectState destProject, Branch.NameKey destBranch,
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 2c032c4..e6923c1 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
@@ -51,7 +51,7 @@ * @throws ValidationException to block the upload and send a message * back to the end-user over the client's protocol connection. */ - public void onPreUpload(Repository repository, Project project, + void onPreUpload(Repository repository, Project project, String remoteHost, UploadPack up, Collection<? extends ObjectId> wants, Collection<? extends ObjectId> haves) throws ValidationException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java index 405cdf1..d623b31 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -14,10 +14,8 @@ package com.google.gerrit.server.group; -import com.google.common.collect.ComparisonChain; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Ordering; import com.google.gerrit.common.data.GroupDetail; import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.extensions.common.AccountInfo; @@ -30,13 +28,13 @@ import com.google.gerrit.server.account.AccountLoader; import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupDetailFactory; +import com.google.gerrit.server.api.accounts.AccountInfoComparator; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import org.kohsuke.args4j.Option; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -83,15 +81,7 @@ final Map<Account.Id, AccountInfo> members = getMembers(groupId, new HashSet<AccountGroup.UUID>()); final List<AccountInfo> memberInfos = Lists.newArrayList(members.values()); - Collections.sort(memberInfos, new Comparator<AccountInfo>() { - @Override - public int compare(AccountInfo a, AccountInfo b) { - return ComparisonChain.start() - .compare(a.name, b.name, Ordering.natural().nullsFirst()) - .compare(a.email, b.email, Ordering.natural().nullsFirst()) - .compare(a._accountId, b._accountId, Ordering.natural().nullsFirst()).result(); - } - }); + Collections.sort(memberInfos, AccountInfoComparator.ORDER_NULLS_FIRST); return memberInfos; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java index 02e737a..258f1e9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -35,10 +35,10 @@ */ public interface ChangeIndex { /** @return the schema version used by this index. */ - public Schema<ChangeData> getSchema(); + Schema<ChangeData> getSchema(); /** Close this index. */ - public void close(); + void close(); /** * Update a change document in the index. @@ -52,7 +52,7 @@ * * @throws IOException */ - public void replace(ChangeData cd) throws IOException; + void replace(ChangeData cd) throws IOException; /** * Delete a change document from the index by id. @@ -61,14 +61,14 @@ * * @throws IOException */ - public void delete(Change.Id id) throws IOException; + void delete(Change.Id id) throws IOException; /** * Delete all change documents from the index. * * @throws IOException */ - public void deleteAll() throws IOException; + void deleteAll() throws IOException; /** * Convert the given operator predicate into a source searching the index and @@ -90,7 +90,7 @@ * @throws QueryParseException if the predicate could not be converted to an * indexed data source. */ - public ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts) + ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts) throws QueryParseException; /** @@ -99,5 +99,5 @@ * @param ready whether the index is ready * @throws IOException */ - public void markReady(boolean ready) throws IOException; + void markReady(boolean ready) throws IOException; }
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 6c484d8..84edcbf 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
@@ -239,9 +239,7 @@ try { ChangeData cd = changeDataFactory.create( newCtx.getReviewDbProvider().get(), id); - for (ChangeIndex i : getWriteIndexes()) { - i.replace(cd); - } + index(cd); return null; } finally { context.setContext(oldCtx);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java index 683f8cf..28fa9f8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -50,7 +50,7 @@ int backendLimit = opts.config().maxLimit(); int limit = Ints.saturatedCast((long) opts.limit() + opts.start()); limit = Math.min(limit, backendLimit); - return QueryOptions.create(opts.config(), 0, limit); + return QueryOptions.create(opts.config(), 0, limit, opts.fields()); } private final ChangeIndex index;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java index 31fbb40..9d573e1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
@@ -69,6 +69,11 @@ @Override public void onGitReferenceUpdated(final Event event) { + if (event.getRefName().startsWith(RefNames.REFS_CHANGES) + || event.getRefName().startsWith(RefNames.REFS_DRAFT_COMMENTS) + || event.getRefName().startsWith(RefNames.REFS_USERS)) { + return; + } Futures.transformAsync( executor.submit(new GetChanges(event)), new AsyncFunction<List<Change>, List<Void>>() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java index df70292..a605461 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
@@ -61,6 +61,8 @@ } private final ImmutableMap<String, FieldDef<T, ?>> fields; + private final ImmutableMap<String, FieldDef<T, ?>> storedFields; + private int version; protected Schema(Iterable<FieldDef<T, ?>> fields) { @@ -71,10 +73,15 @@ public Schema(int version, Iterable<FieldDef<T, ?>> fields) { this.version = version; ImmutableMap.Builder<String, FieldDef<T, ?>> b = ImmutableMap.builder(); + ImmutableMap.Builder<String, FieldDef<T, ?>> sb = ImmutableMap.builder(); for (FieldDef<T, ?> f : fields) { b.put(f.getName(), f); + if (f.isStored()) { + sb.put(f.getName(), f); + } } this.fields = b.build(); + this.storedFields = sb.build(); } public final int getVersion() { @@ -95,6 +102,14 @@ } /** + * @return all fields in this schema where {@link FieldDef#isStored()} is + * true. + */ + public final ImmutableMap<String, FieldDef<T, ?>> getStoredFields() { + return storedFields; + } + + /** * Look up fields in this schema. * * @param first the preferred field to look up.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java index 0f1e86e..f825d1c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
@@ -25,9 +25,9 @@ public class AddKeySender extends OutgoingEmail { public interface Factory { - public AddKeySender create(IdentifiedUser user, AccountSshKey sshKey); + AddKeySender create(IdentifiedUser user, AccountSshKey sshKey); - public AddKeySender create(IdentifiedUser user, List<String> gpgKey); + AddKeySender create(IdentifiedUser user, List<String> gpgKey); } private final IdentifiedUser callingUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java index 8948ce3..43d02fb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -14,6 +14,8 @@ package com.google.gerrit.server.mail; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; + import com.google.gerrit.common.errors.EmailException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; @@ -26,7 +28,6 @@ import com.google.gerrit.reviewdb.client.StarredChange; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.mail.ProjectWatch.Watchers; -import com.google.gerrit.server.notedb.ReviewerState; import com.google.gerrit.server.patch.PatchList; import com.google.gerrit.server.patch.PatchListEntry; import com.google.gerrit.server.patch.PatchListNotAvailableException; @@ -329,7 +330,7 @@ /** Users who have non-zero approval codes on the change. */ protected void ccExistingReviewers() { try { - for (Account.Id id : changeData.reviewers().get(ReviewerState.REVIEWER)) { + for (Account.Id id : changeData.reviewers().get(REVIEWER)) { add(RecipientType.CC, id); } } catch (OrmException err) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java index 3d0041c..3e5e167 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -52,7 +52,7 @@ .getLogger(CommentSender.class); public static interface Factory { - public CommentSender create(NotifyHandling notify, Change.Id id); + CommentSender create(NotifyHandling notify, Change.Id id); } private final NotifyHandling notify;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java index 29895d9..e67b8ce 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -33,7 +33,7 @@ LoggerFactory.getLogger(CreateChangeSender.class); public static interface Factory { - public CreateChangeSender create(Change.Id id); + CreateChangeSender create(Change.Id id); } @Inject
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 58bdac1..b6fb006 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
@@ -28,7 +28,7 @@ * the string provides proof the user has the ability to read messages * sent to that address. Must not be null. */ - public String encode(Account.Id accountId, String emailAddress); + String encode(Account.Id accountId, String emailAddress); /** * Decode a token previously created. @@ -36,7 +36,7 @@ * @return a pair of account id and email address. * @throws InvalidTokenException the token is invalid, expired, malformed, etc. */ - public ParsedToken decode(String tokenString) throws InvalidTokenException; + ParsedToken decode(String tokenString) throws InvalidTokenException; /** Exception thrown when a token does not parse correctly. */ public static class InvalidTokenException extends Exception {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java index dec3d2c..9bcabc3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java
@@ -18,7 +18,7 @@ /** Constructs an address to send email from. */ public interface FromAddressGenerator { - public boolean isGenericAddress(Account.Id fromId); + boolean isGenericAddress(Account.Id fromId); - public Address from(Account.Id fromId); + Address from(Account.Id fromId); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java index c6e59eb..a3a048a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
@@ -14,13 +14,16 @@ package com.google.gerrit.server.mail; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; + import com.google.common.collect.Multimap; import com.google.gerrit.common.FooterConstants; import com.google.gerrit.common.errors.NoSuchAccountException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.server.account.AccountResolver; -import com.google.gerrit.server.notedb.ReviewerState; +import com.google.gerrit.server.notedb.ReviewerStateInternal; import com.google.gwtorm.server.OrmException; import org.eclipse.jgit.revwalk.FooterKey; @@ -56,10 +59,10 @@ } public static MailRecipients getRecipientsFromReviewers( - Multimap<ReviewerState, Account.Id> reviewers) { + Multimap<ReviewerStateInternal, Account.Id> reviewers) { MailRecipients recipients = new MailRecipients(); - recipients.reviewers.addAll(reviewers.get(ReviewerState.REVIEWER)); - recipients.cc.addAll(reviewers.get(ReviewerState.CC)); + recipients.reviewers.addAll(reviewers.get(REVIEWER)); + recipients.cc.addAll(reviewers.get(CC)); return recipients; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java index ba75723..f91b0fd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
@@ -23,7 +23,7 @@ /** Send notice about a change failing to merged. */ public class MergeFailSender extends ReplyToChangeSender { public static interface Factory { - public MergeFailSender create(Change.Id id); + MergeFailSender create(Change.Id id); } @Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java index 7fbcf8d..9725e7f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -31,7 +31,7 @@ /** Send notice about a change successfully merged. */ public class MergedSender extends ReplyToChangeSender { public static interface Factory { - public MergedSender create(Change.Id id); + MergedSender create(Change.Id id); } private final LabelTypes labelTypes;
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 c4d374f..c5d56b8 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
@@ -23,7 +23,7 @@ public class RegisterNewEmailSender extends OutgoingEmail { public interface Factory { - public RegisterNewEmailSender create(String address); + RegisterNewEmailSender create(String address); } private final EmailTokenVerifier tokenVerifier;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java index 05c7933..7980845 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -31,7 +31,7 @@ /** Send notice of new patch sets for reviewers. */ public class ReplacePatchSetSender extends ReplyToChangeSender { public static interface Factory { - public ReplacePatchSetSender create(Change.Id id); + ReplacePatchSetSender create(Change.Id id); } private final Set<Account.Id> reviewers = new HashSet<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java index 62a6c72..fbfbe61 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
@@ -22,7 +22,7 @@ /** Alert a user to a reply to a change, usually commentary made during review. */ public abstract class ReplyToChangeSender extends ChangeEmail { public static interface Factory<T extends ReplyToChangeSender> { - public T create(Change.Id id); + T create(Change.Id id); } protected ReplyToChangeSender(EmailArguments ea, String mc, ChangeData cd)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java index 43d53f0..96486e9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/mime/FileTypeRegistry.java
@@ -31,7 +31,7 @@ * or cannot be determined, {@link MimeUtil2#UNKNOWN_MIME_TYPE} which * is an alias for {@code application/octet-stream}. */ - public abstract MimeType getMimeType(final String path, final byte[] content); + MimeType getMimeType(final String path, final byte[] content); /** * Is this content type safe to transmit to a browser directly? @@ -42,6 +42,6 @@ * content type and wants it to be protected (typically by wrapping * the data in a ZIP archive). */ - public abstract boolean isSafeInline(final MimeType type); + boolean isSafeInline(final MimeType type); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java index b1ca9e7..d4555b4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -63,6 +63,11 @@ return self(); } + public T reload() throws OrmException { + loaded = false; + return load(); + } + public ObjectId loadRevision() throws OrmException { if (loaded) { return getRevision();
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 fb41027..22c8da4 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
@@ -86,8 +86,7 @@ } public void setPatchSetId(PatchSet.Id psId) { - checkArgument(psId == null - || psId.getParentKey().equals(getChange().getId())); + checkArgument(psId == null || psId.getParentKey().equals(ctl.getId())); this.psId = psId; }
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 bd8f797..acd2852 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
@@ -98,7 +98,7 @@ IdentifiedUser user = ctl.getUser().asIdentifiedUser(); this.accountId = user.getAccountId(); this.changeNotes = getChangeNotes().load(); - this.draftNotes = draftNotesFactory.create(ctl.getChange().getId(), + this.draftNotes = draftNotesFactory.create(ctl.getId(), user.getAccountId()); this.upsertComments = Lists.newArrayList(); @@ -273,7 +273,7 @@ @Override protected String getRefName() { - return RefNames.refsDraftComments(accountId, getChange().getId()); + return RefNames.refsDraftComments(accountId, ctl.getId()); } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java index 063ff5a..801f172 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -116,10 +116,11 @@ private final Change change; private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals; - private ImmutableSetMultimap<ReviewerState, Account.Id> reviewers; + private ImmutableSetMultimap<ReviewerStateInternal, Account.Id> reviewers; private ImmutableList<Account.Id> allPastReviewers; private ImmutableList<SubmitRecord> submitRecords; - private ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessages; + private ImmutableList<ChangeMessage> allChangeMessages; + private ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet; private ImmutableListMultimap<RevId, PatchLineComment> comments; private ImmutableSet<String> hashtags; NoteMap noteMap; @@ -143,7 +144,7 @@ return approvals; } - public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers() { + public ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers() { return reviewers; } @@ -170,9 +171,18 @@ return submitRecords; } - /** @return change messages by patch set, in chronological order. */ - public ImmutableListMultimap<PatchSet.Id, ChangeMessage> getChangeMessages() { - return changeMessages; + /** @return all change messages, in chronological order, oldest first. */ + public ImmutableList<ChangeMessage> getChangeMessages() { + return allChangeMessages; + } + + /** + * @return change messages by patch set, in chronological order, oldest + * first. + */ + public ImmutableListMultimap<PatchSet.Id, ChangeMessage> + getChangeMessagesByPatchSet() { + return changeMessagesByPatchSet; } /** @return inline comments on each revision. */ @@ -250,7 +260,8 @@ change.setStatus(parser.status); } approvals = parser.buildApprovals(); - changeMessages = parser.buildMessages(); + changeMessagesByPatchSet = parser.buildMessagesByPatchSet(); + allChangeMessages = parser.buildAllMessages(); comments = ImmutableListMultimap.copyOf(parser.comments); noteMap = parser.commentNoteMap; @@ -259,9 +270,9 @@ } else { hashtags = ImmutableSet.of(); } - ImmutableSetMultimap.Builder<ReviewerState, Account.Id> reviewers = + ImmutableSetMultimap.Builder<ReviewerStateInternal, Account.Id> reviewers = ImmutableSetMultimap.builder(); - for (Map.Entry<Account.Id, ReviewerState> e + for (Map.Entry<Account.Id, ReviewerStateInternal> e : parser.reviewers.entrySet()) { reviewers.put(e.getValue(), e.getKey()); } @@ -277,7 +288,8 @@ approvals = ImmutableListMultimap.of(); reviewers = ImmutableSetMultimap.of(); submitRecords = ImmutableList.of(); - changeMessages = ImmutableListMultimap.of(); + allChangeMessages = ImmutableList.of(); + changeMessagesByPatchSet = ImmutableListMultimap.of(); comments = ImmutableListMultimap.of(); hashtags = ImmutableSet.of(); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java index 6f8cb2b..b58548c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -26,6 +26,7 @@ import com.google.common.base.Splitter; import com.google.common.base.Supplier; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedListMultimap; @@ -70,7 +71,7 @@ import java.util.Set; class ChangeNotesParser implements AutoCloseable { - final Map<Account.Id, ReviewerState> reviewers; + final Map<Account.Id, ReviewerStateInternal> reviewers; final List<Account.Id> allPastReviewers; final List<SubmitRecord> submitRecords; final Multimap<RevId, PatchLineComment> comments; @@ -84,7 +85,8 @@ private final Repository repo; private final Map<PatchSet.Id, Table<Account.Id, String, Optional<PatchSetApproval>>> approvals; - private final Multimap<PatchSet.Id, ChangeMessage> changeMessages; + private final List<ChangeMessage> allChangeMessages; + private final Multimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet; ChangeNotesParser(Change change, ObjectId tip, RevWalk walk, GitRepositoryManager repoManager) throws RepositoryNotFoundException, @@ -98,7 +100,8 @@ reviewers = Maps.newLinkedHashMap(); allPastReviewers = Lists.newArrayList(); submitRecords = Lists.newArrayListWithExpectedSize(1); - changeMessages = LinkedListMultimap.create(); + allChangeMessages = Lists.newArrayList(); + changeMessagesByPatchSet = LinkedListMultimap.create(); comments = ArrayListMultimap.create(); } @@ -133,11 +136,16 @@ return ImmutableListMultimap.copyOf(result); } - ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessages() { - for (Collection<ChangeMessage> v : changeMessages.asMap().values()) { - Collections.sort((List<ChangeMessage>) v, ChangeNotes.MESSAGE_BY_TIME); + ImmutableList<ChangeMessage> buildAllMessages() { + return ImmutableList.copyOf(Lists.reverse(allChangeMessages)); + } + + ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessagesByPatchSet() { + for (Collection<ChangeMessage> v : + changeMessagesByPatchSet.asMap().values()) { + Collections.reverse((List<ChangeMessage>) v); } - return ImmutableListMultimap.copyOf(changeMessages); + return ImmutableListMultimap.copyOf(changeMessagesByPatchSet); } private void parse(RevCommit commit) throws ConfigInvalidException { @@ -160,7 +168,7 @@ parseApproval(psId, accountId, commit, line); } - for (ReviewerState state : ReviewerState.values()) { + for (ReviewerStateInternal state : ReviewerStateInternal.values()) { for (String line : commit.getFooterLines(state.getFooterKey())) { parseReviewer(state, line); } @@ -267,7 +275,8 @@ new Timestamp(commit.getCommitterIdent().getWhen().getTime()), psId); changeMessage.setMessage(changeMsgString); - changeMessages.put(psId, changeMessage); + changeMessagesByPatchSet.put(psId, changeMessage); + allChangeMessages.add(changeMessage); } private void parseComments() @@ -385,7 +394,7 @@ GERRIT_PLACEHOLDER_HOST, email); } - private void parseReviewer(ReviewerState state, String line) + private void parseReviewer(ReviewerStateInternal state, String line) throws ConfigInvalidException { PersonIdent ident = RawParseUtils.parsePersonIdent(line); if (ident == null) { @@ -398,11 +407,11 @@ } private void pruneReviewers() { - Iterator<Map.Entry<Account.Id, ReviewerState>> rit = + Iterator<Map.Entry<Account.Id, ReviewerStateInternal>> rit = reviewers.entrySet().iterator(); while (rit.hasNext()) { - Map.Entry<Account.Id, ReviewerState> e = rit.next(); - if (e.getValue() == ReviewerState.REMOVED) { + Map.Entry<Account.Id, ReviewerStateInternal> e = rit.next(); + if (e.getValue() == ReviewerStateInternal.REMOVED) { rit.remove(); for (Table<Account.Id, ?, ?> curr : approvals.values()) { curr.rowKeySet().remove(e.getKey());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java index cb35619..08632dd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -86,7 +86,7 @@ private final AccountCache accountCache; private final Map<String, Optional<Short>> approvals; - private final Map<Account.Id, ReviewerState> reviewers; + private final Map<Account.Id, ReviewerStateInternal> reviewers; private Change.Status status; private String subject; private List<SubmitRecord> submitRecords; @@ -165,7 +165,11 @@ public void setStatus(Change.Status status) { checkArgument(status != Change.Status.MERGED, - "use submit(Iterable<PatchSetApproval>)"); + "use merge(Iterable<SubmitRecord>)"); + this.status = status; + } + + public void fixStatus(Change.Status status) { this.status = status; } @@ -316,13 +320,13 @@ this.hashtags = hashtags; } - public void putReviewer(Account.Id reviewer, ReviewerState type) { - checkArgument(type != ReviewerState.REMOVED, "invalid ReviewerType"); + public void putReviewer(Account.Id reviewer, ReviewerStateInternal type) { + checkArgument(type != ReviewerStateInternal.REMOVED, "invalid ReviewerType"); reviewers.put(reviewer, type); } public void removeReviewer(Account.Id reviewer) { - reviewers.put(reviewer, ReviewerState.REMOVED); + reviewers.put(reviewer, ReviewerStateInternal.REMOVED); } /** @return the tree id for the updated tree */ @@ -383,7 +387,7 @@ @Override protected String getRefName() { - return ChangeNoteUtil.changeRefName(getChange().getId()); + return ChangeNoteUtil.changeRefName(ctl.getId()); } @Override @@ -418,7 +422,7 @@ addFooter(msg, FOOTER_HASHTAGS, Joiner.on(",").join(hashtags)); } - for (Map.Entry<Account.Id, ReviewerState> e : reviewers.entrySet()) { + for (Map.Entry<Account.Id, ReviewerStateInternal> e : reviewers.entrySet()) { Account account = accountCache.get(e.getKey()).getAccount(); PersonIdent ident = newIdent(account, when); addFooter(msg, e.getValue().getFooterKey())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java index e65f8e82..d0cb1df 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
@@ -243,9 +243,6 @@ } int startChar = RawParseUtils.parseBase10(note, ptr.value, ptr); - if (startChar == 0) { - return null; - } if (note[ptr.value] == '-') { range.setStartCharacter(startChar); ptr.value += 1;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java new file mode 100644 index 0000000..3bf2135 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java
@@ -0,0 +1,64 @@ +// 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.notedb; + +import com.google.gerrit.extensions.client.ReviewerState; + +import org.eclipse.jgit.revwalk.FooterKey; + +import java.util.Arrays; + +/** State of a reviewer on a change. */ +public enum ReviewerStateInternal { + /** The user has contributed at least one nonzero vote on the change. */ + REVIEWER(new FooterKey("Reviewer"), ReviewerState.REVIEWER), + + /** The reviewer was added to the change, but has not voted. */ + CC(new FooterKey("CC"), ReviewerState.CC), + + /** The user was previously a reviewer on the change, but was removed. */ + REMOVED(new FooterKey("Removed"), ReviewerState.REMOVED); + + static { + boolean ok = true; + if (ReviewerStateInternal.values().length != ReviewerState.values().length) { + ok = false; + } + for (ReviewerStateInternal s : ReviewerStateInternal.values()) { + ok &= s.name().equals(s.state.name()); + } + if (!ok) { + throw new IllegalStateException("Mismatched reviewer state mapping: " + + Arrays.asList(ReviewerStateInternal.values()) + " != " + + Arrays.asList(ReviewerState.values())); + } + } + + private final FooterKey footerKey; + private final ReviewerState state; + + private ReviewerStateInternal(FooterKey footerKey, ReviewerState state) { + this.footerKey = footerKey; + this.state = state; + } + + FooterKey getFooterKey() { + return footerKey; + } + + public ReviewerState asReviewerState() { + return state; + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java index a6332f0..3680dc3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -20,12 +20,12 @@ /** Provides a cached list of {@link PatchListEntry}. */ public interface PatchListCache { - public PatchList get(PatchListKey key, Project.NameKey project) + PatchList get(PatchListKey key, Project.NameKey project) throws PatchListNotAvailableException; - public PatchList get(Change change, PatchSet patchSet) + PatchList get(Change change, PatchSet patchSet) throws PatchListNotAvailableException; - public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key, + IntraLineDiff getIntraLineDiff(IntraLineDiffKey key, IntraLineDiffArgs args); }
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 5de28f3..d130175 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
@@ -51,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, 0); + fileName, EMPTY_HEADER, Collections.<Edit> emptyList(), 0, 0, 0, 0); } private final ChangeType changeType; @@ -62,11 +62,13 @@ private final List<Edit> edits; private final int insertions; private final int deletions; + private final long size; 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(FileHeader hdr, List<Edit> editList, long sizeDelta) { + PatchListEntry(FileHeader hdr, List<Edit> editList, long size, + long sizeDelta) { changeType = toChangeType(hdr); patchType = toPatchType(hdr); @@ -111,12 +113,13 @@ } insertions = ins; deletions = del; + this.size = size; this.sizeDelta = sizeDelta; } private PatchListEntry(ChangeType changeType, PatchType patchType, String oldName, String newName, byte[] header, List<Edit> edits, - int insertions, int deletions, long sizeDelta) { + int insertions, int deletions, long size, long sizeDelta) { this.changeType = changeType; this.patchType = patchType; this.oldName = oldName; @@ -125,6 +128,7 @@ this.edits = edits; this.insertions = insertions; this.deletions = deletions; + this.size = size; this.sizeDelta = sizeDelta; } @@ -172,6 +176,10 @@ return deletions; } + public long getSize() { + return size; + } + public long getSizeDelta() { return sizeDelta; } @@ -208,6 +216,7 @@ writeBytes(out, header); writeVarInt32(out, insertions); writeVarInt32(out, deletions); + writeFixInt64(out, size); writeFixInt64(out, sizeDelta); writeVarInt32(out, edits.size()); @@ -227,6 +236,7 @@ byte[] hdr = readBytes(in); int ins = readVarInt32(in); int del = readVarInt32(in); + long size = readFixInt64(in); long sizeDelta = readFixInt64(in); int editCount = readVarInt32(in); @@ -240,7 +250,7 @@ } return new PatchListEntry(changeType, patchType, oldName, newName, hdr, - toList(editArray), ins, del, sizeDelta); + toList(editArray), ins, del, size, 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 15277b2..0200fa5 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
@@ -34,7 +34,7 @@ import java.io.Serializable; public class PatchListKey implements Serializable { - static final long serialVersionUID = 18L; + static final long serialVersionUID = 19L; public static final BiMap<Whitespace, Character> WHITESPACE_TYPES = ImmutableBiMap.of( Whitespace.IGNORE_NONE, 'N',
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 aa2a8a8..10ea61c 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
@@ -206,7 +206,7 @@ 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)); + entries.add(newEntry(aTree, fh, newSize, newSize - oldSize)); } } return new PatchList(a, b, againstParent, @@ -301,37 +301,38 @@ byte[] rawHdr = hdr.toString().getBytes(UTF_8); byte[] aContent = aText.getContent(); byte[] bContent = bText.getContent(); + long size = bContent.length; 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, sizeDelta); + return new PatchListEntry(fh, edits, size, sizeDelta); } private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader, - long sizeDelta) { + long size, 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(), - sizeDelta); + size, sizeDelta); } if (aTree == null // want combined diff || fileHeader.getPatchType() != PatchType.UNIFIED || fileHeader.getHunks().isEmpty()) { return new PatchListEntry(fileHeader, Collections.<Edit> emptyList(), - sizeDelta); + size, sizeDelta); } List<Edit> edits = fileHeader.toEditList(); if (edits.isEmpty()) { return new PatchListEntry(fileHeader, Collections.<Edit> emptyList(), - sizeDelta); + size, sizeDelta); } else { - return new PatchListEntry(fileHeader, edits, sizeDelta); + return new PatchListEntry(fileHeader, edits, size, sizeDelta); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java index 6b458aa..5df364f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -34,6 +34,7 @@ import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle; import com.google.gerrit.extensions.systemstatus.ServerInformation; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.server.util.PluginRequestContext; import com.google.gerrit.server.util.RequestContext; import com.google.gerrit.server.util.ThreadLocalRequestContext; @@ -77,6 +78,7 @@ private final List<StartPluginListener> onStart; private final List<StopPluginListener> onStop; private final List<ReloadPluginListener> onReload; + private final MetricMaker serverMetrics; private Module sysModule; private Module sshModule; @@ -102,12 +104,14 @@ Injector sysInjector, ThreadLocalRequestContext local, ServerInformation srvInfo, - CopyConfigModule ccm) { + CopyConfigModule ccm, + MetricMaker serverMetrics) { this.sysInjector = sysInjector; this.srvInfo = srvInfo; this.local = local; this.copyConfigModule = ccm; this.copyConfigKeys = Guice.createInjector(ccm).getAllBindings().keySet(); + this.serverMetrics = serverMetrics; onStart = new CopyOnWriteArrayList<>(); onStart.addAll(listeners(sysInjector, StartPluginListener.class)); @@ -127,6 +131,10 @@ return srvInfo; } + MetricMaker getServerMetrics() { + return serverMetrics; + } + boolean hasDynamicItem(TypeLiteral<?> type) { return sysItems.containsKey(type) || (sshItems != null && sshItems.containsKey(type)) @@ -424,6 +432,7 @@ } } } + private void reattachItem( ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles, Map<TypeLiteral<?>, DynamicItem<?>> items, @@ -564,6 +573,9 @@ if (StopPluginListener.class.isAssignableFrom(type)) { return false; } + if (MetricMaker.class.isAssignableFrom(type)) { + return false; + } if (type.getName().startsWith("com.google.inject.")) { return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java new file mode 100644 index 0000000..724ebeb --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginMetricMaker.java
@@ -0,0 +1,204 @@ +// 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.plugins; + +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.extensions.registration.RegistrationHandle; +import com.google.gerrit.metrics.CallbackMetric; +import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Counter2; +import com.google.gerrit.metrics.Counter3; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.Histogram0; +import com.google.gerrit.metrics.Histogram1; +import com.google.gerrit.metrics.Histogram2; +import com.google.gerrit.metrics.Histogram3; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; +import com.google.gerrit.metrics.Timer1; +import com.google.gerrit.metrics.Timer2; +import com.google.gerrit.metrics.Timer3; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class PluginMetricMaker extends MetricMaker implements LifecycleListener { + private final MetricMaker root; + private final String prefix; + private final Set<RegistrationHandle> cleanup; + + public PluginMetricMaker(MetricMaker root, String pluginName) { + this.root = root; + this.prefix = String.format("plugins/%s/", pluginName); + cleanup = Collections.synchronizedSet(new HashSet<RegistrationHandle>()); + } + + @Override + public Counter0 newCounter(String name, Description desc) { + Counter0 m = root.newCounter(prefix + name, desc); + cleanup.add(m); + return m; + } + + @Override + public <F1> Counter1<F1> newCounter( + String name, Description desc, + Field<F1> field1) { + Counter1<F1> m = root.newCounter(prefix + name, desc, field1); + cleanup.add(m); + return m; + } + + @Override + public <F1, F2> Counter2<F1, F2> newCounter( + String name, Description desc, + Field<F1> field1, Field<F2> field2) { + Counter2<F1, F2> m = root.newCounter(prefix + name, desc, field1, field2); + cleanup.add(m); + return m; + } + + @Override + public <F1, F2, F3> Counter3<F1, F2, F3> newCounter( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3) { + Counter3<F1, F2, F3> m = + root.newCounter(prefix + name, desc, field1, field2, field3); + cleanup.add(m); + return m; + } + + @Override + public Timer0 newTimer(String name, Description desc) { + Timer0 m = root.newTimer(prefix + name, desc); + cleanup.add(m); + return m; + } + + @Override + public <F1> Timer1<F1> newTimer( + String name, Description desc, + Field<F1> field1) { + Timer1<F1> m = root.newTimer(prefix + name, desc, field1); + cleanup.add(m); + return m; + } + + @Override + public <F1, F2> Timer2<F1, F2> newTimer( + String name, Description desc, + Field<F1> field1, Field<F2> field2) { + Timer2<F1, F2> m = root.newTimer(prefix + name, desc, field1, field2); + cleanup.add(m); + return m; + } + + @Override + public <F1, F2, F3> Timer3<F1, F2, F3> newTimer( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3) { + Timer3<F1, F2, F3> m = + root.newTimer(prefix + name, desc, field1, field2, field3); + cleanup.add(m); + return m; + } + + @Override + public Histogram0 newHistogram(String name, Description desc) { + Histogram0 m = root.newHistogram(prefix + name, desc); + cleanup.add(m); + return m; + } + + @Override + public <F1> Histogram1<F1> newHistogram( + String name, Description desc, + Field<F1> field1) { + Histogram1<F1> m = root.newHistogram(prefix + name, desc, field1); + cleanup.add(m); + return m; + } + + @Override + public <F1, F2> Histogram2<F1, F2> newHistogram( + String name, Description desc, + Field<F1> field1, Field<F2> field2) { + Histogram2<F1, F2> m = root.newHistogram(prefix + name, desc, field1, field2); + cleanup.add(m); + return m; + } + + @Override + public <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram( + String name, Description desc, + Field<F1> field1, Field<F2> field2, Field<F3> field3) { + Histogram3<F1, F2, F3> m = + root.newHistogram(prefix + name, desc, field1, field2, field3); + cleanup.add(m); + return m; + } + + @Override + public <V> CallbackMetric0<V> newCallbackMetric( + String name, Class<V> valueClass, Description desc) { + CallbackMetric0<V> m = root.newCallbackMetric(prefix + name, valueClass, desc); + cleanup.add(m); + return m; + } + + @Override + public <F1, V> CallbackMetric1<F1, V> newCallbackMetric(String name, + Class<V> valueClass, Description desc, Field<F1> field1) { + CallbackMetric1<F1, V> m = + root.newCallbackMetric(prefix + name, valueClass, desc, field1); + cleanup.add(m); + return m; + } + + @Override + public RegistrationHandle newTrigger(Set<CallbackMetric<?>> metrics, + Runnable trigger) { + final RegistrationHandle handle = root.newTrigger(metrics, trigger); + cleanup.add(handle); + return new RegistrationHandle() { + @Override + public void remove() { + handle.remove(); + cleanup.remove(handle); + } + }; + } + + @Override + public void start() { + } + + @Override + public void stop() { + synchronized (cleanup) { + Iterator<RegistrationHandle> itr = cleanup.iterator(); + while (itr.hasNext()) { + itr.next().remove(); + itr.remove(); + } + } + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPluginListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPluginListener.java index 72a499e..73fb9c4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPluginListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPluginListener.java
@@ -16,5 +16,5 @@ /** Broadcasts event indicating a plugin was reloaded. */ public interface ReloadPluginListener { - public void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin); + void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java index 14c1185..ea96a56 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -236,7 +236,7 @@ if (getApiType() == ApiType.PLUGIN) { modules.add(env.getSysModule()); } - modules.add(new ServerPluginInfoModule(this)); + modules.add(new ServerPluginInfoModule(this, env.getServerMetrics())); return Guice.createInjector(modules); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java index b0e9453..a7f0087 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java
@@ -17,6 +17,8 @@ import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl; import com.google.gerrit.extensions.annotations.PluginData; import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.server.PluginUser; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -32,10 +34,12 @@ private final Path dataDir; private volatile boolean ready; + private final MetricMaker serverMetrics; - ServerPluginInfoModule(ServerPlugin plugin) { + ServerPluginInfoModule(ServerPlugin plugin, MetricMaker serverMetrics) { this.plugin = plugin; this.dataDir = plugin.getDataDir(); + this.serverMetrics = serverMetrics; } @Override @@ -47,6 +51,17 @@ bind(String.class) .annotatedWith(PluginCanonicalWebUrl.class) .toInstance(plugin.getPluginCanonicalWebUrl()); + + install(new LifecycleModule() { + @Override + public void configure() { + PluginMetricMaker metrics = new PluginMetricMaker( + serverMetrics, + plugin.getName()); + bind(MetricMaker.class).toInstance(metrics); + listener().toInstance(metrics); + } + }); } @Provides
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StartPluginListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StartPluginListener.java index aaad370..0d27c87 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StartPluginListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StartPluginListener.java
@@ -16,5 +16,5 @@ /** Broadcasts event indicating a plugin was loaded. */ public interface StartPluginListener { - public void onStartPlugin(Plugin plugin); + void onStartPlugin(Plugin plugin); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StopPluginListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StopPluginListener.java index 24bd655..7ce53a9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StopPluginListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/StopPluginListener.java
@@ -16,5 +16,5 @@ /** Broadcasts event indicating a plugin was unloaded. */ public interface StopPluginListener { - public void onStopPlugin(Plugin plugin); + void onStopPlugin(Plugin plugin); }
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 3ab6ff5..606ca78d 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
@@ -28,6 +28,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.query.change.ChangeData; @@ -89,7 +90,7 @@ throws NoSuchChangeException, OrmException { ChangeControl c = controlFor(change, user); if (!c.isVisible(db.get())) { - throw new NoSuchChangeException(c.getChange().getId()); + throw new NoSuchChangeException(c.getId()); } return c; } @@ -103,23 +104,27 @@ private final ChangeData.Factory changeDataFactory; private final RefControl refControl; private final ChangeNotes notes; + private final ApprovalsUtil approvalsUtil; @AssistedInject ChangeControl( ChangeData.Factory changeDataFactory, ChangeNotes.Factory notesFactory, + ApprovalsUtil approvalsUtil, @Assisted RefControl refControl, @Assisted Change change) { - this(changeDataFactory, refControl, + this(changeDataFactory, approvalsUtil, refControl, notesFactory.create(change)); } @AssistedInject ChangeControl( ChangeData.Factory changeDataFactory, + ApprovalsUtil approvalsUtil, @Assisted RefControl refControl, @Assisted ChangeNotes notes) { this.changeDataFactory = changeDataFactory; + this.approvalsUtil = approvalsUtil; this.refControl = refControl; this.notes = notes; } @@ -128,7 +133,7 @@ if (getUser().equals(who)) { return this; } - return new ChangeControl(changeDataFactory, + return new ChangeControl(changeDataFactory, approvalsUtil, getRefControl().forUser(who), notes); } @@ -148,6 +153,10 @@ return getProjectControl().getProject(); } + public Change.Id getId() { + return notes.getChangeId(); + } + public Change getChange() { return notes.getChange(); } @@ -190,13 +199,13 @@ } /** Can this user abandon this change? */ - public boolean canAbandon() { - return isOwner() // owner (aka creator) of the change can abandon + public boolean canAbandon(ReviewDb db) throws OrmException { + return (isOwner() // owner (aka creator) of the change can abandon || getRefControl().isOwner() // branch owner can abandon || getProjectControl().isOwner() // project owner can abandon || getUser().getCapabilities().canAdministrateServer() // site administers are god || getRefControl().canAbandon() // user can abandon a specific ref - ; + ) && !isPatchSetLocked(db); } /** Can this user publish this draft change or any draft patch set of this change? */ @@ -212,14 +221,14 @@ } /** Can this user rebase this change? */ - public boolean canRebase() { - return isOwner() || getRefControl().canSubmit() - || getRefControl().canRebase(); + public boolean canRebase(ReviewDb db) throws OrmException { + return (isOwner() || getRefControl().canSubmit() + || getRefControl().canRebase()) && !isPatchSetLocked(db); } /** Can this user restore this change? */ - public boolean canRestore() { - return canAbandon() // Anyone who can abandon the change can restore it back + public boolean canRestore(ReviewDb db) throws OrmException { + return canAbandon(db) // Anyone who can abandon the change can restore it back && getRefControl().canUpload(); // as long as you can upload too } @@ -258,8 +267,25 @@ } /** Can this user add a patch set to this change? */ - public boolean canAddPatchSet() { - return getRefControl().canUpload(); + public boolean canAddPatchSet(ReviewDb db) throws OrmException { + return getRefControl().canUpload() && !isPatchSetLocked(db); + } + + /** Is the current patch set locked against state changes? */ + public boolean isPatchSetLocked(ReviewDb db) throws OrmException { + if (getChange().getStatus() == Change.Status.MERGED) { + return false; + } + + for (PatchSetApproval ap : approvalsUtil.byPatchSet(db, this, + getChange().currentPatchSetId())) { + LabelType type = getLabelTypes().byLabel(ap.getLabel()); + if (type != null && ap.getValue() == 1 + && type.getFunctionName().equalsIgnoreCase("PatchSetLock")) { + return true; + } + } + return false; } /** Is this user the owner of the change? */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java index d451b46..67bdc88 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
@@ -23,10 +23,10 @@ /** Cache of project information, including access rights. */ public interface ProjectCache { /** @return the parent state for all projects on this server. */ - public ProjectState getAllProjects(); + ProjectState getAllProjects(); /** @return the project state of the project storing meta data for all users. */ - public ProjectState getAllUsers(); + ProjectState getAllUsers(); /** * Get the cached data for a project by its unique name. @@ -35,7 +35,7 @@ * @return the cached data; null if no such project exists or a error occurred. * @see #checkedGet(com.google.gerrit.reviewdb.client.Project.NameKey) */ - public ProjectState get(Project.NameKey projectName); + ProjectState get(Project.NameKey projectName); /** * Get the cached data for a project by its unique name. @@ -44,14 +44,14 @@ * @throws IOException when there was an error. * @return the cached data; null if no such project exists. */ - public ProjectState checkedGet(Project.NameKey projectName) + ProjectState checkedGet(Project.NameKey projectName) throws IOException; /** Invalidate the cached information about the given project. */ - public void evict(Project p); + void evict(Project p); /** Invalidate the cached information about the given project. */ - public void evict(Project.NameKey p); + void evict(Project.NameKey p); /** * Remove information about the given project from the cache. It will no @@ -60,14 +60,14 @@ void remove(Project p); /** @return sorted iteration of projects. */ - public abstract Iterable<Project.NameKey> all(); + Iterable<Project.NameKey> all(); /** * @return estimated set of relevant groups extracted from hot project access * rules. If the cache is cold or too small for the entire project set * of the server, this set may be incomplete. */ - public abstract Set<AccountGroup.UUID> guessRelevantGroupUUIDs(); + Set<AccountGroup.UUID> guessRelevantGroupUUIDs(); /** * Filter the set of registered project names by common prefix. @@ -75,8 +75,8 @@ * @param prefix common prefix. * @return sorted iteration of projects sharing the same prefix. */ - public abstract Iterable<Project.NameKey> byName(String prefix); + Iterable<Project.NameKey> byName(String prefix); /** Notify the cache that a new project was constructed. */ - public void onCreateProject(Project.NameKey newProjectName); + void onCreateProject(Project.NameKey newProjectName); }
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 76ad2f0..5f921b1 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
@@ -36,6 +36,7 @@ import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.config.ProjectConfigEntry; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; @@ -89,6 +90,7 @@ private final DynamicMap<RestView<ProjectResource>> views; private final Provider<CurrentUser> user; private final ChangeHooks hooks; + private final GitReferenceUpdated gitRefUpdated; @Inject PutConfig(@EnableSignedPush boolean serverEnableSignedPush, @@ -102,6 +104,7 @@ AllProjectsNameProvider allProjects, DynamicMap<RestView<ProjectResource>> views, ChangeHooks hooks, + GitReferenceUpdated gitRefUpdated, Provider<CurrentUser> user) { this.serverEnableSignedPush = serverEnableSignedPush; this.metaDataUpdateFactory = metaDataUpdateFactory; @@ -114,6 +117,7 @@ this.allProjects = allProjects; this.views = views; this.hooks = hooks; + this.gitRefUpdated = gitRefUpdated; this.user = user; } @@ -199,6 +203,8 @@ ObjectId commitRev = projectConfig.commit(md); // Only fire hook if project was actually changed. if (!Objects.equals(baseRev, commitRev)) { + gitRefUpdated.fire(projectName, RefNames.REFS_CONFIG, + baseRev, commitRev); hooks.doRefUpdatedHook( new Branch.NameKey(projectName, RefNames.REFS_CONFIG), baseRev, commitRev, user.get().asIdentifiedUser().getAccount());
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 d589865..2d407bd 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
@@ -27,6 +27,7 @@ import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; @@ -46,15 +47,18 @@ private final MetaDataUpdate.Server updateFactory; private final GitRepositoryManager gitMgr; private final ChangeHooks hooks; + private final GitReferenceUpdated gitRefUpdated; @Inject PutDescription(ProjectCache cache, MetaDataUpdate.Server updateFactory, ChangeHooks hooks, + GitReferenceUpdated gitRefUpdated, GitRepositoryManager gitMgr) { this.cache = cache; this.updateFactory = updateFactory; this.hooks = hooks; + this.gitRefUpdated = gitRefUpdated; this.gitMgr = gitMgr; } @@ -91,6 +95,8 @@ ObjectId commitRev = config.commit(md); // Only fire hook if project was actually changed. if (!Objects.equals(baseRev, commitRev)) { + gitRefUpdated.fire(resource.getNameKey(), RefNames.REFS_CONFIG, + baseRev, commitRev); hooks.doRefUpdatedHook( new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG), baseRev, commitRev, user.getAccount());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java index 83cdc80..8a1718d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java
@@ -19,8 +19,8 @@ public interface DataSource<T> { /** @return an estimate of the number of results from {@link #read()}. */ - public int getCardinality(); + int getCardinality(); /** @return read from the database and return the results. */ - public ResultSet<T> read() throws OrmException; + ResultSet<T> read() throws OrmException; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java index c8f9972..3a21ce4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
@@ -73,6 +73,11 @@ * @param <T> type of object the predicates can evaluate in memory. */ public abstract class QueryBuilder<T> { + /** Converts a value string passed to an operator into a {@link Predicate}. */ + public interface OperatorFactory<T, Q extends QueryBuilder<T>> { + Predicate<T> create(Q builder, String value) throws QueryParseException; + } + /** * Defines the operators known by a QueryBuilder. * @@ -162,7 +167,7 @@ protected final Definition<T, ? extends QueryBuilder<T>> builderDef; @SuppressWarnings("rawtypes") - private final Map<String, OperatorFactory> opFactories; + protected final Map<String, OperatorFactory> opFactories; @SuppressWarnings({"unchecked", "rawtypes"}) protected QueryBuilder(Definition<T, ? extends QueryBuilder<T>> def) { @@ -323,11 +328,6 @@ return new QueryParseException(msg, why); } - /** Converts a value string passed to an operator into a {@link Predicate}. */ - protected interface OperatorFactory<T, Q extends QueryBuilder<T>> { - Predicate<T> create(Q builder, String value) throws QueryParseException; - } - /** Denotes a method which is a query operator. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
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 9677f5f..a3fb523 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
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.gerrit.common.data.SubmitRecord; @@ -44,7 +45,7 @@ import com.google.gerrit.server.git.MergeUtil; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.NotesMigration; -import com.google.gerrit.server.notedb.ReviewerState; +import com.google.gerrit.server.notedb.ReviewerStateInternal; import com.google.gerrit.server.patch.PatchList; import com.google.gerrit.server.patch.PatchListCache; import com.google.gerrit.server.patch.PatchListEntry; @@ -415,7 +416,7 @@ this.patchListCache = patchListCache; this.notesMigration = notesMigration; this.mergeabilityCache = mergeabilityCache; - legacyId = c.getChange().getId(); + legacyId = c.getId(); change = c.getChange(); changeControl = c; notes = c.getNotes(); @@ -543,6 +544,23 @@ return changeControl; } + public ChangeControl changeControl(CurrentUser user) throws OrmException { + if (changeControl != null) { + throw new IllegalStateException( + "user already specified: " + changeControl.getUser()); + } + try { + if (change != null) { + changeControl = changeControlFactory.controlFor(change, user); + } else { + changeControl = changeControlFactory.controlFor(legacyId, user); + } + } catch (NoSuchChangeException e) { + throw new OrmException(e); + } + return changeControl; + } + void cacheVisibleTo(ChangeControl ctl) { visibleTo = ctl.getUser(); changeControl = ctl; @@ -706,7 +724,7 @@ return allApprovals; } - public SetMultimap<ReviewerState, Account.Id> reviewers() + public SetMultimap<ReviewerStateInternal, Account.Id> reviewers() throws OrmException { return approvalsUtil.getReviewers(notes(), approvals().values()); } @@ -808,10 +826,7 @@ events.add(ReviewedByEvent.create(msg)); } } - for (PatchSet ps : patchSets()) { - events.add(ReviewedByEvent.create(ps)); - } - Collections.sort(events, Collections.reverseOrder()); + events = Lists.reverse(events); reviewedBy = new LinkedHashSet<>(); Account.Id owner = c.getOwner(); for (ReviewedByEvent event : events) { @@ -829,12 +844,7 @@ } @AutoValue - abstract static class ReviewedByEvent implements Comparable<ReviewedByEvent> { - private static ReviewedByEvent create(PatchSet ps) { - return new AutoValue_ChangeData_ReviewedByEvent( - ps.getUploader(), ps.getCreatedOn()); - } - + abstract static class ReviewedByEvent { private static ReviewedByEvent create(ChangeMessage msg) { return new AutoValue_ChangeData_ReviewedByEvent( msg.getAuthor(), msg.getWrittenOn()); @@ -842,11 +852,6 @@ public abstract Account.Id author(); public abstract Timestamp ts(); - - @Override - public int compareTo(ReviewedByEvent other) { - return ts().compareTo(other.ts()); - } } @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java index 47bf82d..c32ff0d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
@@ -18,5 +18,5 @@ public interface ChangeDataSource extends DataSource<ChangeData> { /** @return true if all returned ChangeData.hasChange() will be true. */ - public boolean hasChange(); + boolean hasChange(); }
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 a4cff07..440731e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -25,6 +25,7 @@ import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.errors.NotSignedInException; import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Branch; @@ -86,6 +87,10 @@ * Parses a query string meant to be applied to change objects. */ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> { + public interface ChangeOperatorFactory + extends OperatorFactory<ChangeData, ChangeQueryBuilder> { + } + private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$"); private static final Pattern PAT_CHANGE_ID = Pattern.compile("^[iI][0-9a-f]{4,}.*$"); @@ -151,6 +156,7 @@ final Provider<ReviewDb> db; final Provider<InternalChangeQuery> queryProvider; final IndexRewriter rewriter; + final DynamicMap<ChangeOperatorFactory> opFactories; final IdentifiedUser.GenericFactory userFactory; final CapabilityControl.Factory capabilityControlFactory; final ChangeControl.GenericFactory changeControlGenericFactory; @@ -180,6 +186,7 @@ public Arguments(Provider<ReviewDb> db, Provider<InternalChangeQuery> queryProvider, IndexRewriter rewriter, + DynamicMap<ChangeOperatorFactory> opFactories, IdentifiedUser.GenericFactory userFactory, Provider<CurrentUser> self, CapabilityControl.Factory capabilityControlFactory, @@ -202,7 +209,7 @@ IndexConfig indexConfig, Provider<ListMembers> listMembers, @GerritServerConfig Config cfg) { - this(db, queryProvider, rewriter, userFactory, self, + this(db, queryProvider, rewriter, opFactories, userFactory, self, capabilityControlFactory, changeControlGenericFactory, changeDataFactory, fillArgs, plcUtil, accountResolver, groupBackend, allProjectsName, allUsersName, patchListCache, repoManager, @@ -217,6 +224,7 @@ Provider<ReviewDb> db, Provider<InternalChangeQuery> queryProvider, IndexRewriter rewriter, + DynamicMap<ChangeOperatorFactory> opFactories, IdentifiedUser.GenericFactory userFactory, Provider<CurrentUser> self, CapabilityControl.Factory capabilityControlFactory, @@ -242,6 +250,7 @@ this.db = db; this.queryProvider = queryProvider; this.rewriter = rewriter; + this.opFactories = opFactories; this.userFactory = userFactory; this.self = self; this.capabilityControlFactory = capabilityControlFactory; @@ -267,7 +276,7 @@ } Arguments asUser(CurrentUser otherUser) { - return new Arguments(db, queryProvider, rewriter, userFactory, + return new Arguments(db, queryProvider, rewriter, opFactories, userFactory, Providers.of(otherUser), capabilityControlFactory, changeControlGenericFactory, changeDataFactory, fillArgs, plcUtil, accountResolver, groupBackend, @@ -320,6 +329,7 @@ ChangeQueryBuilder(Arguments args) { super(mydef); this.args = args; + setupDynamicOperators(); } @VisibleForTesting @@ -330,6 +340,13 @@ this.args = args; } + private void setupDynamicOperators() { + for (DynamicMap.Entry<ChangeOperatorFactory> e : args.opFactories) { + String name = e.getExportName() + "_" + e.getPluginName(); + opFactories.put(name, e.getProvider().get()); + } + } + public ChangeQueryBuilder asUser(CurrentUser user) { return new ChangeQueryBuilder(builderDef, args.asUser(user)); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java index bf7a5dd..e8b2fef 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java
@@ -18,8 +18,8 @@ public interface ConflictsCache { - public void put(ConflictKey key, Boolean value); + void put(ConflictKey key, Boolean value); @Nullable - public Boolean getIfPresent(ConflictKey key); + Boolean getIfPresent(ConflictKey key); }
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 34a2bdf..ff6f2bc 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
@@ -106,6 +106,11 @@ return this; } + public InternalChangeQuery setRequestedFields(Set<String> fields) { + qp.setRequestedFields(fields); + return this; + } + public List<ChangeData> byKey(Change.Key key) throws OrmException { return byKeyPrefix(key.get()); }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryOptions.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryOptions.java index 1964fa5..a70f892 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryOptions.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryOptions.java
@@ -17,29 +17,36 @@ import static com.google.common.base.Preconditions.checkArgument; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableSet; import com.google.gerrit.server.index.IndexConfig; +import java.util.Set; + @AutoValue public abstract class QueryOptions { - public static QueryOptions create(IndexConfig config, int start, int limit) { + public static QueryOptions create(IndexConfig config, int start, int limit, + Set<String> fields) { checkArgument(start >= 0, "start must be nonnegative: %s", start); checkArgument(limit > 0, "limit must be positive: %s", limit); - return new AutoValue_QueryOptions(config, start, limit); + return new AutoValue_QueryOptions(config, start, limit, + ImmutableSet.copyOf(fields)); } public static QueryOptions oneResult() { - return create(IndexConfig.createDefault(), 0, 1); + return create(IndexConfig.createDefault(), 0, 1, + ImmutableSet.<String> of()); } public abstract IndexConfig config(); public abstract int start(); public abstract int limit(); + public abstract ImmutableSet<String> fields(); public QueryOptions withLimit(int newLimit) { - return create(config(), start(), newLimit); + return create(config(), start(), newLimit, fields()); } public QueryOptions withStart(int newStart) { - return create(config(), newStart, limit()); + return create(config(), newStart, limit(), fields()); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java index a2c8b81..1a6ae02 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -18,10 +18,16 @@ import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.index.ChangeIndex; +import com.google.gerrit.server.index.IndexCollection; import com.google.gerrit.server.index.IndexConfig; import com.google.gerrit.server.index.IndexPredicate; import com.google.gerrit.server.index.IndexRewriter; @@ -32,32 +38,41 @@ import com.google.gwtorm.server.ResultSet; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.Singleton; import java.util.ArrayList; import java.util.List; +import java.util.Set; public class QueryProcessor { private final Provider<ReviewDb> db; private final Provider<CurrentUser> userProvider; private final ChangeControl.GenericFactory changeControlFactory; + private final IndexCollection indexes; private final IndexRewriter rewriter; private final IndexConfig indexConfig; + private final Metrics metrics; private int limitFromCaller; private int start; private boolean enforceVisibility = true; + private Set<String> requestedFields; @Inject QueryProcessor(Provider<ReviewDb> db, Provider<CurrentUser> userProvider, ChangeControl.GenericFactory changeControlFactory, + IndexCollection indexes, IndexRewriter rewriter, - IndexConfig indexConfig) { + IndexConfig indexConfig, + Metrics metrics) { this.db = db; this.userProvider = userProvider; this.changeControlFactory = changeControlFactory; + this.indexes = indexes; this.rewriter = rewriter; this.indexConfig = indexConfig; + this.metrics = metrics; } public QueryProcessor enforceVisibility(boolean enforce) { @@ -75,6 +90,11 @@ return this; } + public QueryProcessor setRequestedFields(Set<String> fields) { + requestedFields = fields; + return this; + } + /** * Query for changes that match a structured query. * @@ -114,6 +134,9 @@ private List<QueryResult> queryChanges(List<String> queryStrings, List<Predicate<ChangeData>> queries) throws OrmException, QueryParseException { + @SuppressWarnings("resource") + Timer0.Context context = metrics.executionTime.start(); + Predicate<ChangeData> visibleToMe = enforceVisibility ? new IsVisibleToPredicate(db, changeControlFactory, userProvider.get()) : null; @@ -140,7 +163,8 @@ "Cannot go beyond page " + indexConfig.maxPages() + "of results"); } - QueryOptions opts = QueryOptions.create(indexConfig, start, limit + 1); + QueryOptions opts = QueryOptions.create( + indexConfig, start, limit + 1, getRequestedFields()); Predicate<ChangeData> s = rewriter.rewrite(q, opts); if (!(s instanceof ChangeDataSource)) { q = Predicate.and(open(), q); @@ -170,9 +194,20 @@ limits.get(i), matches.get(i).toList())); } + context.close(); // only measure successful queries return out; } + private Set<String> getRequestedFields() { + if (requestedFields != null) { + return requestedFields; + } + ChangeIndex index = indexes.getSearchIndex(); + return index != null + ? index.getSchema().getStoredFields().keySet() + : ImmutableSet.<String> of(); + } + boolean isDisabled() { return getPermittedLimit() <= 0; } @@ -203,4 +238,19 @@ } return Ordering.natural().min(possibleLimits); } + + @Singleton + static class Metrics { + final Timer0 executionTime; + + @Inject + Metrics(MetricMaker metricMaker) { + executionTime = metricMaker.newTimer( + "change/query/query_latency", + new Description("Successful change query latency," + + " accumulated over the life of the process") + .setCumulative() + .setUnit(Description.Units.MILLISECONDS)); + } + } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java index 30ebaa7..0c3bf67 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
@@ -20,9 +20,14 @@ import com.google.common.base.Strings; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.persistence.DataSourceInterceptor; +import com.google.gerrit.metrics.CallbackMetric1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.server.config.ConfigSection; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.config.ThreadSettingsConfig; import com.google.gwtorm.jdbc.SimpleDataSource; import com.google.inject.Inject; import com.google.inject.Provider; @@ -43,18 +48,22 @@ @Singleton public class DataSourceProvider implements Provider<DataSource>, LifecycleListener { - public static final int DEFAULT_POOL_LIMIT = 8; - private final Config cfg; + private final MetricMaker metrics; private final Context ctx; private final DataSourceType dst; + private final ThreadSettingsConfig threadSettingsConfig; private DataSource ds; @Inject protected DataSourceProvider(@GerritServerConfig Config cfg, + MetricMaker metrics, + ThreadSettingsConfig threadSettingsConfig, Context ctx, DataSourceType dst) { this.cfg = cfg; + this.metrics = metrics; + this.threadSettingsConfig = threadSettingsConfig; this.ctx = ctx; this.dst = dst; } @@ -120,12 +129,15 @@ if (password != null && !password.isEmpty()) { ds.setPassword(password); } - ds.setMaxActive(cfg.getInt("database", "poollimit", DEFAULT_POOL_LIMIT)); + int poolLimit = threadSettingsConfig.getDatabasePoolLimit(); + ds.setMaxActive(poolLimit); ds.setMinIdle(cfg.getInt("database", "poolminidle", 4)); - ds.setMaxIdle(cfg.getInt("database", "poolmaxidle", 4)); + ds.setMaxIdle( + cfg.getInt("database", "poolmaxidle", Math.min(poolLimit, 16))); ds.setMaxWait(ConfigUtil.getTimeUnit(cfg, "database", null, "poolmaxwait", MILLISECONDS.convert(30, SECONDS), MILLISECONDS)); ds.setInitialSize(ds.getMinIdle()); + exportPoolMetrics(ds); return intercept(interceptor, ds); } else { @@ -148,6 +160,25 @@ } } + private void exportPoolMetrics(final BasicDataSource pool) { + final CallbackMetric1<Boolean, Integer> cnt = metrics.newCallbackMetric( + "sql/connection_pool/connections", + Integer.class, + new Description("SQL database connections") + .setGauge() + .setUnit("connections"), + Field.ofBoolean("active")); + metrics.newTrigger(cnt, new Runnable() { + @Override + public void run() { + synchronized (pool) { + cnt.set(true, pool.getNumActive()); + cnt.set(false, pool.getNumIdle()); + } + } + }); + } + private DataSource intercept(String interceptor, DataSource ds) { if (interceptor == null) { return ds;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java index ecaaf5e..6eaa540 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
@@ -20,11 +20,11 @@ /** Abstraction of a supported database platform */ public interface DataSourceType { - public String getDriver(); + String getDriver(); - public String getUrl(); + String getUrl(); - public boolean usePool(); + boolean usePool(); /** * Return a ScriptRunner that runs the index script. Must not return @@ -32,5 +32,5 @@ * * @throws IOException */ - public ScriptRunner getIndexScript() throws IOException; + ScriptRunner getIndexScript() throws IOException; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java index 13c5703..a3c0a5a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java
@@ -19,8 +19,8 @@ /** Permits controlling the contents of the SSH key cache area. */ public interface SshKeyCache { - public void evict(String username); + void evict(String username); - public AccountSshKey create(AccountSshKey.Id id, String encoded) + AccountSshKey create(AccountSshKey.Id id, String encoded) throws InvalidSshKeyException; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/TreeFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/TreeFormatter.java index 1c68a75..cd559ee 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/util/TreeFormatter.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/TreeFormatter.java
@@ -20,9 +20,9 @@ public class TreeFormatter { public static interface TreeNode { - public String getDisplayName(); - public boolean isVisible(); - public SortedSet<? extends TreeNode> getChildren(); + String getDisplayName(); + boolean isVisible(); + SortedSet<? extends TreeNode> getChildren(); } public static final String NOT_VISIBLE_NODE = "(x)";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java index 11db3ee..03bdf37 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java
@@ -30,6 +30,6 @@ * @param args arguments for the group creation * @throws ValidationException if validation fails */ - public void validateNewGroup(CreateGroupArgs args) + void validateNewGroup(CreateGroupArgs args) throws ValidationException; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java index c1d509e..1baab7c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/HashtagValidationListener.java
@@ -32,6 +32,6 @@ * @param toRemove the hashtags to be removed * @throws ValidationException if validation fails */ - public void validateHashtags(Change change, Set<String> toAdd, + void validateHashtags(Change change, Set<String> toAdd, Set<String> toRemove) throws ValidationException; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java index 5b3f158..398a303 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
@@ -55,6 +55,6 @@ * @param args E-mail properties. Some are mutable. * @throws ValidationException if validation fails. */ - public void validateOutgoingEmail(OutgoingEmailValidationListener.Args args) + void validateOutgoingEmail(OutgoingEmailValidationListener.Args args) throws ValidationException; }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java index d3c69c1..6012328 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java
@@ -30,6 +30,6 @@ * @param args arguments for the project creation * @throws ValidationException if validation fails */ - public void validateNewProject(CreateProjectArgs args) + void validateNewProject(CreateProjectArgs args) throws ValidationException; }
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl index 9a4e77c..59c926f 100644 --- a/gerrit-server/src/main/prolog/gerrit_common.pl +++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -243,6 +243,7 @@ legacy_submit_rule('MaxNoBlock', Label, Min, Max, T) :- !, max_no_block(Label, Max, T). legacy_submit_rule('NoBlock', Label, Min, Max, T) :- !, T = may(_). legacy_submit_rule('NoOp', Label, Min, Max, T) :- !, T = may(_). +legacy_submit_rule('PatchSetLock', Label, Min, Max, T) :- !, T = may(_). legacy_submit_rule(Fun, Label, Min, Max, T) :- T = impossible(unsupported(Fun)). %% max_with_block: @@ -294,6 +295,10 @@ %% %% - At least one maximum is used. %% +max_no_block(Max, Label, label(Label, S)) :- + number(Max), atom(Label), + !, + max_no_block(Label, Max, S). max_no_block(Label, Max, ok(Who)) :- check_label_range_permission(Label, Max, ok(Who)), !
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg index 6988459..5370156 100644 --- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg +++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
@@ -63,6 +63,10 @@ AWK=/usr/xpg4/bin/awk fi + # Get core.commentChar from git config or use default symbol + commentChar=`git config --get core.commentChar` + commentChar=${commentChar:-#} + # How this works: # - parse the commit message as (textLine+ blankLine*)* # - assume textLine+ to be a footer until proven otherwise @@ -81,8 +85,8 @@ blankLines = 0 } - # Skip lines starting with "#" without any spaces before it. - /^#/ { next } + # Skip lines starting with commentChar without any spaces before it. + /^'"$commentChar"'/ { next } # Skip the line starting with the diff command and everything after it, # up to the end of the file, assuming it is only patch data.
diff --git a/gerrit-server/src/test/java/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java b/gerrit-server/src/test/java/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java new file mode 100644 index 0000000..aa4c4eb --- /dev/null +++ b/gerrit-server/src/test/java/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java
@@ -0,0 +1,201 @@ +// 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.metrics.proc; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gerrit.common.Version; +import com.google.gerrit.lifecycle.LifecycleManager; +import com.google.gerrit.metrics.CallbackMetric0; +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.FieldOrdering; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class ProcMetricModuleTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Inject + MetricMaker metrics; + + @Inject + MetricRegistry registry; + + @Test + public void testConstantBuildLabel() { + Gauge<String> buildLabel = gauge("build/label"); + assertThat(buildLabel.getValue()).isEqualTo(Version.getVersion()); + } + + @Test + public void testProcUptime() { + Gauge<Long> birth = gauge("proc/birth_timestamp"); + assertThat(birth.getValue()).isAtMost( + TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis())); + + Gauge<Long> uptime = gauge("proc/uptime"); + assertThat(uptime.getValue()).isAtLeast(1L); + } + + @Test + public void testCounter0() { + Counter0 cntr = metrics.newCounter( + "test/count", + new Description("simple test") + .setCumulative()); + + Counter raw = get("test/count", Counter.class); + assertThat(raw.getCount()).isEqualTo(0); + + cntr.increment(); + assertThat(raw.getCount()).isEqualTo(1); + + cntr.incrementBy(5); + assertThat(raw.getCount()).isEqualTo(6); + } + + @Test + public void testCounter1() { + Counter1<String> cntr = metrics.newCounter( + "test/count", + new Description("simple test") + .setCumulative(), + Field.ofString("action")); + + Counter total = get("test/count_total", Counter.class); + assertThat(total.getCount()).isEqualTo(0); + + cntr.increment("passed"); + Counter passed = get("test/count/passed", Counter.class); + assertThat(total.getCount()).isEqualTo(1); + assertThat(passed.getCount()).isEqualTo(1); + + cntr.incrementBy("failed", 5); + Counter failed = get("test/count/failed", Counter.class); + assertThat(total.getCount()).isEqualTo(6); + assertThat(passed.getCount()).isEqualTo(1); + assertThat(failed.getCount()).isEqualTo(5); + } + + @Test + public void testCounterPrefixFields() { + Counter1<String> cntr = metrics.newCounter( + "test/count", + new Description("simple test") + .setCumulative() + .setFieldOrdering(FieldOrdering.PREFIX_FIELDS_BASENAME), + Field.ofString("action")); + + Counter total = get("test/count_total", Counter.class); + assertThat(total.getCount()).isEqualTo(0); + + cntr.increment("passed"); + Counter passed = get("test/passed/count", Counter.class); + assertThat(total.getCount()).isEqualTo(1); + assertThat(passed.getCount()).isEqualTo(1); + + cntr.incrementBy("failed", 5); + Counter failed = get("test/failed/count", Counter.class); + assertThat(total.getCount()).isEqualTo(6); + assertThat(passed.getCount()).isEqualTo(1); + assertThat(failed.getCount()).isEqualTo(5); + } + + @Test + public void testCallbackMetric0() { + final CallbackMetric0<Long> cntr = metrics.newCallbackMetric( + "test/count", + Long.class, + new Description("simple test") + .setCumulative()); + + final AtomicInteger invocations = new AtomicInteger(0); + metrics.newTrigger(cntr, new Runnable() { + @Override + public void run() { + invocations.getAndIncrement(); + cntr.set(42L); + } + }); + + // Triggers run immediately with DropWizard binding. + assertThat(invocations.get()).isEqualTo(1); + + Gauge<Long> raw = gauge("test/count"); + assertThat(raw.getValue()).isEqualTo(42); + + // Triggers are debounced to avoid being fired too frequently. + assertThat(invocations.get()).isEqualTo(1); + } + + @Test + public void testInvalidName1() { + exception.expect(IllegalArgumentException.class); + metrics.newCounter("invalid name", new Description("fail")); + } + + @Test + public void testInvalidName2() { + exception.expect(IllegalArgumentException.class); + metrics.newCounter("invalid/ name", new Description("fail")); + } + + @SuppressWarnings({"unchecked", "cast"}) + private <V> Gauge<V> gauge(String name) { + return (Gauge<V>) get(name, Gauge.class); + } + + private <M extends Metric> M get(String name, Class<M> type) { + Metric m = registry.getMetrics().get(name); + assertThat(m).named(name).isNotNull(); + assertThat(m).named(name).isInstanceOf(type); + + @SuppressWarnings("unchecked") + M result = (M) m; + return result; + } + + @Before + public void setup() { + Injector injector = + Guice.createInjector(new DropWizardMetricMaker.ApiModule()); + + LifecycleManager mgr = new LifecycleManager(); + mgr.add(injector); + mgr.start(); + + injector.injectMembers(this); + } +}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java index d6a5c67..0245b89 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
@@ -40,9 +40,7 @@ import org.eclipse.jgit.lib.Config; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.io.PushbackReader; import java.io.StringReader; @@ -62,9 +60,6 @@ private ProjectConfig local; private Util util; - @Rule - public ExpectedException exception = ExpectedException.none(); - @Before public void setUp() throws Exception { util = new Util();
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 8956e8f..dc8004a 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
@@ -19,6 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.testutil.GerritBaseTests; import com.google.inject.Guice; import com.google.inject.Module; @@ -45,7 +46,7 @@ /** Base class for any tests written in Prolog. */ -public abstract class PrologTestCase { +public abstract class PrologTestCase extends GerritBaseTests { private static final SymbolTerm test_1 = SymbolTerm.intern("test", 1); private String pkg;
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 f5e6d74..5c3309c 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
@@ -67,8 +67,8 @@ import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ProjectCache; -import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.FakeAccountCache; +import com.google.gerrit.testutil.GerritServerTests; import com.google.gerrit.testutil.InMemoryRepositoryManager; import com.google.gerrit.testutil.TestChanges; import com.google.gwtorm.server.ListResultSet; @@ -87,10 +87,7 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.PersonIdent; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.sql.Timestamp; import java.util.Collections; @@ -98,23 +95,10 @@ import java.util.Map; import java.util.TimeZone; -@RunWith(ConfigSuite.class) -public class CommentsTest { +public class CommentsTest extends GerritServerTests { private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles"); - @ConfigSuite.Parameter - public Config config; - - @ConfigSuite.Config - @GerritServerConfig - public static Config noteDbEnabled() { - return NotesMigration.allEnabledConfig(); - } - - @Rule - public ExpectedException exception = ExpectedException.none(); - private Injector injector; private ReviewDb db; private Project.NameKey project;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java index 743e43d..8cdd42b 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
@@ -21,10 +21,9 @@ import static org.junit.Assert.assertTrue; import com.google.gerrit.server.util.HostPlatform; +import com.google.gerrit.testutil.GerritBaseTests; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.io.IOException; import java.nio.file.Files; @@ -32,10 +31,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class SitePathsTest { - @Rule - public ExpectedException exception = ExpectedException.none(); - +public class SitePathsTest extends GerritBaseTests { @Test public void testCreate_NotExisting() throws IOException { final Path root = random();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java index 63ae818..dd03bb0 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
@@ -26,8 +26,8 @@ new FakeQueryBuilder.Definition<>( FakeQueryBuilder.class), new ChangeQueryBuilder.Arguments(null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, indexes, null, null, null, null, null, null)); + null, null, null, null, null, null, null, null, null, null, null, + null, null, indexes, null, null, null, null, null, null)); } @Operator
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriterTest.java index 9ac83d5..f44d172 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriterTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriterTest.java
@@ -25,6 +25,7 @@ import static com.google.gerrit.server.query.Predicate.or; import static org.junit.Assert.assertEquals; +import com.google.common.collect.ImmutableSet; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.query.AndPredicate; import com.google.gerrit.server.query.Predicate; @@ -34,22 +35,18 @@ import com.google.gerrit.server.query.change.ChangeQueryBuilder; import com.google.gerrit.server.query.change.OrSource; import com.google.gerrit.server.query.change.QueryOptions; +import com.google.gerrit.testutil.GerritBaseTests; 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 IndexRewriterTest { +public class IndexRewriterTest extends GerritBaseTests { private static final IndexConfig CONFIG = IndexConfig.createDefault(); - @Rule - public ExpectedException exception = ExpectedException.none(); - private FakeIndex index; private IndexCollection indexes; private ChangeQueryBuilder queryBuilder; @@ -289,7 +286,8 @@ } private static QueryOptions options(int start, int limit) { - return QueryOptions.create(CONFIG, start, limit); + return QueryOptions.create(CONFIG, start, limit, + ImmutableSet.<String> of()); } private Set<Change.Status> status(String query) throws QueryParseException {
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 145042c..d5f3132 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
@@ -15,15 +15,13 @@ package com.google.gerrit.server.mail; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; -import org.junit.Rule; +import com.google.gerrit.testutil.GerritBaseTests; + import org.junit.Test; -import org.junit.rules.ExpectedException; -public class AddressTest { - @Rule - public ExpectedException exception = ExpectedException.none(); - +public class AddressTest extends GerritBaseTests { @Test public void testParse_NameEmail1() { final Address a = Address.parse("A U Thor <author@example.com>"); @@ -100,9 +98,12 @@ } private void assertInvalid(final String in) { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("Invalid email address: " + in); - Address.parse(in); + try { + Address.parse(in); + fail("Expected IllegalArgumentException for " + in); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage()).isEqualTo("Invalid email address: " + in); + } } @Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java index 8030f9f..6bdec00 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -49,6 +49,7 @@ import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.testutil.FakeAccountCache; +import com.google.gerrit.testutil.GerritBaseTests; import com.google.gerrit.testutil.InMemoryRepositoryManager; import com.google.gerrit.testutil.TestChanges; import com.google.gwtorm.client.KeyUtil; @@ -72,7 +73,7 @@ import java.util.TimeZone; import java.util.concurrent.atomic.AtomicLong; -public class AbstractChangeNotesTest { +public class AbstractChangeNotesTest extends GerritBaseTests { private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java index bac72f0..fc9563e 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -14,6 +14,8 @@ package com.google.gerrit.server.notedb; +import static org.junit.Assert.fail; + import com.google.gerrit.common.TimeUtil; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -27,17 +29,12 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; public class ChangeNotesParserTest extends AbstractChangeNotesTest { private TestRepository<InMemoryRepository> testRepo; private RevWalk walk; - @Rule - public ExpectedException exception = ExpectedException.none(); - @Before public void setUpTestRepo() throws Exception { testRepo = new TestRepository<>(repo); @@ -205,8 +202,10 @@ private void assertParseFails(RevCommit commit) throws Exception { try (ChangeNotesParser parser = newParser(commit)) { - exception.expect(ConfigInvalidException.class); parser.parseAll(); + fail("Expected parse to fail:\n" + commit.getFullMessage()); + } catch (ConfigInvalidException e) { + // Expected } }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java index f19987f..7debe94 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -15,8 +15,8 @@ package com.google.gerrit.server.notedb; import static com.google.common.truth.Truth.assertThat; -import static com.google.gerrit.server.notedb.ReviewerState.CC; -import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import static com.google.gerrit.testutil.TestChanges.incrementPatchSet; import static java.nio.charset.StandardCharsets.UTF_8; @@ -526,7 +526,7 @@ ChangeNotes notes = newNotes(c); ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = - notes.getChangeMessages(); + notes.getChangeMessagesByPatchSet(); assertThat(changeMessages.keySet()).containsExactly(ps1); ChangeMessage cm = Iterables.getOnlyElement(changeMessages.get(ps1)); @@ -557,7 +557,7 @@ ChangeNotes notes = newNotes(c); ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = - notes.getChangeMessages(); + notes.getChangeMessagesByPatchSet(); assertThat(changeMessages).hasSize(1); ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1)); @@ -579,7 +579,7 @@ ChangeNotes notes = newNotes(c); ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = - notes.getChangeMessages(); + notes.getChangeMessagesByPatchSet(); assertThat(changeMessages).hasSize(1); ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1)); @@ -609,7 +609,7 @@ ChangeNotes notes = newNotes(c); ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = - notes.getChangeMessages(); + notes.getChangeMessagesByPatchSet(); assertThat(changeMessages).hasSize(2); ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1)); @@ -642,7 +642,7 @@ ChangeNotes notes = newNotes(c); ListMultimap<PatchSet.Id, ChangeMessage> changeMessages = - notes.getChangeMessages(); + notes.getChangeMessagesByPatchSet(); assertThat(changeMessages.keySet()).hasSize(1); List<ChangeMessage> cm = changeMessages.get(ps1); @@ -690,7 +690,7 @@ update.commit(); update = newUpdate(c, otherUser); - CommentRange range3 = new CommentRange(3, 1, 4, 1); + CommentRange range3 = new CommentRange(3, 0, 4, 1); PatchLineComment comment3 = newPublishedComment(psId, "file2", uuid3, range3, range3.getEndLine(), otherUser, null, time3, message3, (short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234"); @@ -729,7 +729,7 @@ + "\n" + "File: file2\n" + "\n" - + "3:1-4:1\n" + + "3:0-4:1\n" + CommentsInNotesUtil.formatTime(serverIdent, time3) + "\n" + "Author: Other Account <2@gerrit>\n" + "UUID: uuid3\n"
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java index c206902..811cd8a 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -15,8 +15,8 @@ package com.google.gerrit.server.notedb; import static com.google.common.truth.Truth.assertThat; -import static com.google.gerrit.server.notedb.ReviewerState.CC; -import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; +import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import com.google.common.collect.ImmutableList; import com.google.gerrit.common.TimeUtil;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java index a671523..d60277f 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -96,6 +96,14 @@ value(-2, "This shall not be merged")); } + public static final LabelType patchSetLock() { + LabelType label = category("Patch-Set-Lock", + value(1, "Patch Set Locked"), + value(0, "Patch Set Unlocked")); + label.setFunctionName("PatchSetLock"); + return label; + } + public static LabelValue value(int value, String text) { return new LabelValue((short) value, text); }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java index 382610f..47df2db 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
@@ -21,33 +21,13 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; -import java.util.Collections; import java.util.List; public class AndPredicateTest extends PredicateTest { - private static final class TestPredicate extends OperatorPredicate<String> { - private TestPredicate(String name, String value) { - super(name, value); - } - - @Override - public boolean match(String object) { - return false; - } - - @Override - public int getCost() { - return 0; - } - } - - private static TestPredicate f(final String name, final String value) { - return new TestPredicate(name, value); - } - @Test public void testChildren() { final TestPredicate a = f("author", "alice"); @@ -64,17 +44,29 @@ final TestPredicate b = f("author", "bob"); final Predicate<String> n = and(a, b); - exception.expect(UnsupportedOperationException.class); - n.getChildren().clear(); + try { + n.getChildren().clear(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } assertChildren("clear", n, of(a, b)); - exception.expect(UnsupportedOperationException.class); - n.getChildren().remove(0); + try { + n.getChildren().remove(0); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } assertChildren("remove(0)", n, of(a, b)); - exception.expect(UnsupportedOperationException.class); - n.getChildren().iterator().remove(); - assertChildren("remove(0)", n, of(a, b)); + try { + n.getChildren().iterator().remove(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } + assertChildren("iterator().remove()", n, of(a, b)); } private static void assertChildren(String o, Predicate<String> p, @@ -129,11 +121,5 @@ assertNotSame(n2, n2.copy(s2)); assertEquals(s2, n2.copy(s2).getChildren()); assertEquals(s3, n2.copy(s3).getChildren()); - - try { - n2.copy(Collections.<Predicate<String>> emptyList()); - } catch (IllegalArgumentException e) { - assertEquals("Need at least two predicates", e.getMessage()); - } } }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java index e31caaf..8f16670 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
@@ -23,27 +23,7 @@ import java.util.Collections; -public class FieldPredicateTest { - private static final class TestPredicate extends OperatorPredicate<String> { - private TestPredicate(String name, String value) { - super(name, value); - } - - @Override - public boolean match(String object) { - return false; - } - - @Override - public int getCost() { - return 0; - } - } - - private static TestPredicate f(final String name, final String value) { - return new TestPredicate(name, value); - } - +public class FieldPredicateTest extends PredicateTest { @Test public void testToString() { assertEquals("author:bob", f("author", "bob").toString()); @@ -81,10 +61,8 @@ assertSame(f, f.copy(Collections.<Predicate<String>> emptyList())); assertSame(f, f.copy(f.getChildren())); - try { - f.copy(Collections.singleton(f("owner", "bob"))); - } catch (IllegalArgumentException e) { - assertEquals("Expected 0 children", e.getMessage()); - } + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Expected 0 children"); + f.copy(Collections.singleton(f("owner", "bob"))); } }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java index 45a747c..0256081 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; @@ -28,26 +29,6 @@ import java.util.List; public class NotPredicateTest extends PredicateTest { - private static final class TestPredicate extends OperatorPredicate<String> { - private TestPredicate(String name, String value) { - super(name, value); - } - - @Override - public boolean match(String object) { - return false; - } - - @Override - public int getCost() { - return 0; - } - } - - private static TestPredicate f(final String name, final String value) { - return new TestPredicate(name, value); - } - @Test public void testNotNot() { final TestPredicate p = f("author", "bob"); @@ -125,12 +106,14 @@ try { n.copy(Collections.<Predicate> emptyList()); + fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("Expected exactly one child", e.getMessage()); } try { n.copy(and(a, b).getChildren()); + fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertEquals("Expected exactly one child", e.getMessage()); }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java index ee5e0b0..5640d1b 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
@@ -21,33 +21,13 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; -import java.util.Collections; import java.util.List; public class OrPredicateTest extends PredicateTest { - private static final class TestPredicate extends OperatorPredicate<String> { - private TestPredicate(String name, String value) { - super(name, value); - } - - @Override - public boolean match(String object) { - return false; - } - - @Override - public int getCost() { - return 0; - } - } - - private static TestPredicate f(final String name, final String value) { - return new TestPredicate(name, value); - } - @Test public void testChildren() { final TestPredicate a = f("author", "alice"); @@ -64,17 +44,29 @@ final TestPredicate b = f("author", "bob"); final Predicate<String> n = or(a, b); - exception.expect(UnsupportedOperationException.class); - n.getChildren().clear(); + try { + n.getChildren().clear(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } assertChildren("clear", n, of(a, b)); - exception.expect(UnsupportedOperationException.class); - n.getChildren().remove(0); + try { + n.getChildren().remove(0); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } assertChildren("remove(0)", n, of(a, b)); - exception.expect(UnsupportedOperationException.class); - n.getChildren().iterator().remove(); - assertChildren("remove(0)", n, of(a, b)); + try { + n.getChildren().iterator().remove(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } + assertChildren("iterator().remove()", n, of(a, b)); } private static void assertChildren(String o, Predicate<String> p, @@ -129,11 +121,5 @@ assertNotSame(n2, n2.copy(s2)); assertEquals(s2, n2.copy(s2).getChildren()); assertEquals(s3, n2.copy(s3).getChildren()); - - try { - n2.copy(Collections.<Predicate<String>> emptyList()); - } catch (IllegalArgumentException e) { - assertEquals("Need at least two predicates", e.getMessage()); - } } }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java index 865841e..9a601eb 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java
@@ -14,10 +14,26 @@ package com.google.gerrit.server.query; -import org.junit.Rule; -import org.junit.rules.ExpectedException; +import com.google.gerrit.testutil.GerritBaseTests; -public class PredicateTest { - @Rule - public ExpectedException exception = ExpectedException.none(); +public class PredicateTest extends GerritBaseTests { + protected static final class TestPredicate extends OperatorPredicate<String> { + protected TestPredicate(String name, String value) { + super(name, value); + } + + @Override + public boolean match(String object) { + return false; + } + + @Override + public int getCost() { + return 0; + } + } + + protected static TestPredicate f(String name, String value) { + return new TestPredicate(name, value); + } }
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 3382264..fcaa738 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
@@ -21,6 +21,7 @@ import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.junit.Assert.fail; import com.google.common.base.Function; import com.google.common.base.MoreObjects; @@ -58,6 +59,7 @@ import com.google.gerrit.server.git.BatchUpdate; import com.google.gerrit.server.git.validators.CommitValidators; import com.google.gerrit.server.group.AddMembers; +import com.google.gerrit.server.index.ChangeField; import com.google.gerrit.server.index.IndexCollection; import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.project.ChangeControl; @@ -68,6 +70,7 @@ import com.google.gerrit.server.util.ThreadLocalRequestContext; import com.google.gerrit.testutil.ConfigSuite; import com.google.gerrit.testutil.DisabledReviewDb; +import com.google.gerrit.testutil.GerritServerTests; import com.google.gerrit.testutil.InMemoryDatabase; import com.google.gerrit.testutil.InMemoryRepositoryManager; import com.google.gerrit.testutil.InMemoryRepositoryManager.Repo; @@ -86,10 +89,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; @@ -97,27 +97,14 @@ import java.util.concurrent.atomic.AtomicLong; @Ignore -@RunWith(ConfigSuite.class) -public abstract class AbstractQueryChangesTest { +public abstract class AbstractQueryChangesTest extends GerritServerTests { @ConfigSuite.Default public static Config defaultConfig() { - return updateConfig(new Config()); - } - - @ConfigSuite.Config - public static Config noteDbEnabled() { - return updateConfig(NotesMigration.allEnabledConfig()); - } - - @Rule - public ExpectedException exception = ExpectedException.none(); - - private static Config updateConfig(Config cfg) { + Config cfg = new Config(); cfg.setInt("index", null, "maxPages", 10); return cfg; } - @ConfigSuite.Parameter public Config config; @Inject protected AccountManager accountManager; @Inject protected BatchUpdate.Factory updateFactory; @Inject protected ChangeInserter.Factory changeFactory; @@ -1325,6 +1312,30 @@ cd.messages(); } + @Test + public void prepopulateOnlyRequestedFields() throws Exception { + assume().that(notesMigration.enabled()).isFalse(); + TestRepository<Repo> repo = createProject("repo"); + Change change = insert(newChange(repo, null, null, null, null)); + + db = new DisabledReviewDb(); + requestContext.setContext(newRequestContext(userId)); + // Use QueryProcessor directly instead of API so we get ChangeDatas back. + List<ChangeData> cds = queryProcessor + .setRequestedFields(ImmutableSet.of( + ChangeField.PATCH_SET.getName(), + ChangeField.CHANGE.getName())) + .queryChanges(queryBuilder.parse(change.getId().toString())) + .changes(); + assertThat(cds).hasSize(1); + + ChangeData cd = cds.get(0); + cd.change(); + cd.patchSets(); + + exception.expect(DisabledReviewDb.Disabled.class); + cd.currentApprovals(); + } protected ChangeInserter newChange( TestRepository<Repo> repo, @@ -1406,8 +1417,12 @@ } protected void assertBadQuery(QueryRequest query) throws Exception { - exception.expect(BadRequestException.class); - query.get(); + try { + query.get(); + fail("expected BadRequestException for query: " + query); + } catch (BadRequestException e) { + // Expected. + } } protected TestRepository<Repo> createProject(String name) throws Exception {
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 7e7899b..53a9805 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
@@ -78,6 +78,13 @@ // Ignore. } + @Override + @Ignore + @Test + public void prepopulateOnlyRequestedFields() throws Exception { + // Ignore. + } + @Test public void isReviewed() throws Exception { clockStepMs = MILLISECONDS.convert(2, MINUTES);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java index c060aaf..3e3c13e 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
@@ -24,9 +24,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.junit.Rule; +import com.google.gerrit.testutil.GerritBaseTests; + import org.junit.Test; -import org.junit.rules.ExpectedException; import java.net.Inet4Address; import java.net.Inet6Address; @@ -34,10 +34,7 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; -public class SocketUtilTest { - @Rule - public ExpectedException exception = ExpectedException.none(); - +public class SocketUtilTest extends GerritBaseTests { @Test public void testIsIPv6() throws UnknownHostException { final InetAddress ipv6 = getByName("1:2:3:4:5:6:7:8");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritBaseTests.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritBaseTests.java new file mode 100644 index 0000000..c7eb899 --- /dev/null +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritBaseTests.java
@@ -0,0 +1,23 @@ +// 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.testutil; + +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +public class GerritBaseTests { + @Rule + public ExpectedException exception = ExpectedException.none(); +}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java new file mode 100644 index 0000000..39989a9 --- /dev/null +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java
@@ -0,0 +1,69 @@ +// 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.testutil; + +import com.google.gerrit.server.notedb.NotesMigration; + +import org.eclipse.jgit.lib.Config; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runners.model.Statement; + +import java.util.Arrays; + +@RunWith(ConfigSuite.class) +public class GerritServerTests extends GerritBaseTests { + @ConfigSuite.Parameter + public Config config; + + @ConfigSuite.Name + private String configName; + + public 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()); + } + + @Rule + public TestRule testRunner = new TestRule() { + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + beforeTest(); + try { + base.evaluate(); + } finally { + afterTest(); + } + } + }; + } + }; + + public void beforeTest() throws Exception { + if (isNoteDbTestEnabled()) { + NotesMigration.setAllEnabledConfig(config); + } + } + + public void afterTest() { + } +}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java index e5cd619..9d9ccb5 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -22,6 +22,8 @@ import com.google.gerrit.common.DisabledChangeHooks; import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.gpg.GpgModule; +import com.google.gerrit.metrics.DisabledMetricMaker; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.GerritPersonIdent; @@ -132,6 +134,7 @@ .toInstance(cfg); } }); + bind(MetricMaker.class).to(DisabledMetricMaker.class); install(cfgInjector.getInstance(GerritGlobalModule.class)); install(new ChangeCacheImplModule(false)); factory(GarbageCollection.Factory.class);
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 8b5e85a..f35dbea 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
@@ -102,12 +102,13 @@ GitRepositoryManager repoManager, NotesMigration migration, Change c, AllUsersNameProvider allUsers, IdentifiedUser user) throws OrmException { - ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class); + ChangeControl ctl = EasyMock.createMock(ChangeControl.class); expect(ctl.getChange()).andStubReturn(c); expect(ctl.getUser()).andStubReturn(user); ChangeNotes notes = new ChangeNotes(repoManager, migration, allUsers, c) .load(); expect(ctl.getNotes()).andStubReturn(notes); + expect(ctl.getId()).andStubReturn(c.getId()); EasyMock.replay(ctl); return ctl; }
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK index 5a7b539..260b557 100644 --- a/gerrit-sshd/BUCK +++ b/gerrit-sshd/BUCK
@@ -20,6 +20,7 @@ '//lib:jsch', '//lib/auto:auto-value', '//lib/commons:codec', + '//lib/dropwizard:dropwizard-core', '//lib/guice:guice', '//lib/guice:guice-assistedinject', '//lib/guice:guice-servlet', # SSH should not depend on servlet
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 5c897e4..ec49f5c 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
@@ -489,16 +489,16 @@ /** Runnable function which can throw an exception. */ public static interface CommandRunnable { - public void run() throws Exception; + void run() throws Exception; } /** Runnable function which can retrieve a project name related to the task */ public static interface ProjectCommandRunnable extends CommandRunnable { // execute parser command before running, in order to be able to retrieve // project name - public void executeParseCommand() throws Exception; + void executeParseCommand() throws Exception; - public Project.NameKey getProjectName(); + Project.NameKey getProjectName(); } /** Thrown from {@link CommandRunnable#run()} with client message and code. */
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java index f78aba4..88e1142 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
@@ -15,6 +15,7 @@ package com.google.gerrit.sshd; import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.config.ThreadSettingsConfig; import com.google.gerrit.server.git.QueueProvider; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; @@ -31,11 +32,13 @@ private final WorkQueue.Executor batchExecutor; @Inject - public CommandExecutorQueueProvider(@GerritServerConfig final Config config, - final WorkQueue queues) { - final int cores = Runtime.getRuntime().availableProcessors(); - poolSize = config.getInt("sshd", "threads", 3 * cores / 2); - batchThreads = config.getInt("sshd", "batchThreads", cores == 1 ? 1 : 2); + public CommandExecutorQueueProvider( + @GerritServerConfig Config config, + ThreadSettingsConfig threadsSettingsConfig, + WorkQueue queues) { + poolSize = threadsSettingsConfig.getSshdThreads(); + batchThreads = config.getInt("sshd", "batchThreads", + threadsSettingsConfig.getSshdBatchTreads()); if (batchThreads > poolSize) { poolSize += batchThreads; }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java index 6bd9c4c..ee4984b79 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -217,7 +217,7 @@ private void log(final int rc) { if (logged.compareAndSet(false, true)) { - log.onExecute(cmd, rc); + log.onExecute(cmd, rc, ctx.getSession()); } }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java index d6b3e5c..33347e7 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -19,10 +19,14 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.base.Strings; +import com.google.common.base.Supplier; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.gerrit.common.Version; import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.ssh.SshAdvertisedAddresses; @@ -128,6 +132,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** * SSH daemon to communicate with Gerrit. @@ -172,7 +177,8 @@ final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator, @GerritServerConfig final Config cfg, final SshLog sshLog, @SshListenAddresses final List<SocketAddress> listen, - @SshAdvertisedAddresses final List<String> advertised) { + @SshAdvertisedAddresses final List<String> advertised, + MetricMaker metricMaker) { setPort(IANA_SSH_PORT /* never used */); this.cfg = cfg; @@ -247,10 +253,39 @@ setKeyPairProvider(hostKeyProvider); setCommandFactory(commandFactory); setShellFactory(noShell); + + final AtomicInteger connected = new AtomicInteger(); + metricMaker.newCallbackMetric( + "sshd/sessions/connected", + Integer.class, + new Description("Currently connected SSH sessions") + .setGauge() + .setUnit("sessions"), + new Supplier<Integer>() { + @Override + public Integer get() { + return connected.get(); + } + }); + + final Counter0 sessionsCreated = metricMaker.newCounter( + "sshd/sessions/created", + new Description("Rate of new SSH sessions") + .setRate() + .setUnit("sessions")); + + final Counter0 authFailures = metricMaker.newCounter( + "sshd/sessions/authentication_failures", + new Description("Rate of SSH authentication failures") + .setRate() + .setUnit("failures")); + setSessionFactory(new SessionFactory() { @Override protected AbstractSession createSession(final IoSession io) throws Exception { + connected.incrementAndGet(); + sessionsCreated.increment(); if (io instanceof MinaSession) { if (((MinaSession) io).getSession() .getConfig() instanceof SocketSessionConfig) { @@ -271,7 +306,9 @@ s.addCloseSessionListener(new SshFutureListener<CloseFuture>() { @Override public void operationComplete(CloseFuture future) { + connected.decrementAndGet(); if (sd.isAuthenticationError()) { + authFailures.increment(); sshLog.onAuthFail(sd); } }
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 3a8a1f5..cec3249 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
@@ -47,6 +47,7 @@ private static final String P_WAIT = "queueWaitTime"; private static final String P_EXEC = "executionTime"; private static final String P_STATUS = "status"; + private static final String P_AGENT = "agent"; private final Provider<SshSession> session; private final Provider<Context> context; @@ -115,7 +116,7 @@ audit(null, "FAIL", "AUTH"); } - void onExecute(DispatchCommand dcmd, int exitValue) { + void onExecute(DispatchCommand dcmd, int exitValue, SshSession sshSession) { final Context ctx = context.get(); ctx.finished = TimeUtil.nowMs(); @@ -144,6 +145,10 @@ break; } event.setProperty(P_STATUS, status); + String peerAgent = sshSession.getPeerAgent(); + if (peerAgent != null) { + event.setProperty(P_AGENT, peerAgent); + } if (async != null) { async.append(event);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java index 2622fbd..541081e 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java
@@ -30,6 +30,7 @@ private static final String P_WAIT = "queueWaitTime"; private static final String P_EXEC = "executionTime"; private static final String P_STATUS = "status"; + private static final String P_AGENT = "agent"; private final Calendar calendar; private long lastTimeMillis; @@ -63,6 +64,7 @@ opt(P_WAIT, buf, event); opt(P_EXEC, buf, event); opt(P_STATUS, buf, event); + opt(P_AGENT, buf, event); buf.append('\n'); return buf.toString();
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 ff160e0..6285b20 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
@@ -35,6 +35,7 @@ private volatile CurrentUser identity; private volatile String username; private volatile String authError; + private volatile String peerAgent; SshSession(final int sessionId, SocketAddress peer) { this.sessionId = sessionId; @@ -72,6 +73,14 @@ return remoteAsString; } + public String getPeerAgent() { + return peerAgent; + } + + public void setPeerAgent(String agent) { + peerAgent = agent; + } + String getUsername() { return username; }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java index abb788d..0b12aa6 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -14,18 +14,15 @@ package com.google.gerrit.sshd.commands; -import com.google.common.collect.Lists; import com.google.gerrit.common.data.Capable; -import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.git.AsyncReceiveCommits; import com.google.gerrit.server.git.ReceiveCommits; -import com.google.gerrit.server.git.ReceivePackInitializer; -import com.google.gerrit.server.git.TransferConfig; import com.google.gerrit.server.git.VisibleRefFilter; import com.google.gerrit.sshd.AbstractGitCommand; import com.google.gerrit.sshd.CommandMetaData; +import com.google.gerrit.sshd.SshSession; import com.google.inject.Inject; import org.eclipse.jgit.errors.TooLargeObjectInPackException; @@ -33,8 +30,6 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.transport.AdvertiseRefsHook; -import org.eclipse.jgit.transport.PostReceiveHook; -import org.eclipse.jgit.transport.PostReceiveHookChain; import org.eclipse.jgit.transport.ReceivePack; import org.kohsuke.args4j.Option; import org.slf4j.Logger; @@ -62,13 +57,7 @@ private IdentifiedUser.GenericFactory identifiedUserFactory; @Inject - private TransferConfig config; - - @Inject - private DynamicSet<ReceivePackInitializer> receivePackInitializers; - - @Inject - private DynamicSet<PostReceiveHook> postReceiveHooks; + private SshSession session; private final Set<Account.Id> reviewerId = new HashSet<>(); private final Set<Account.Id> ccId = new HashSet<>(); @@ -100,19 +89,13 @@ verifyProjectVisible("reviewer", reviewerId); verifyProjectVisible("CC", ccId); + receive.init(); receive.addReviewers(reviewerId); receive.addExtraCC(ccId); - - final ReceivePack rp = receive.getReceivePack(); - rp.setRefLogIdent(currentUser.newRefLogIdent()); - rp.setTimeout(config.getTimeout()); - rp.setMaxObjectSizeLimit(config.getEffectiveMaxObjectSizeLimit( - projectControl.getProjectState())); - init(rp); - rp.setPostReceiveHook(PostReceiveHookChain.newChain( - Lists.newArrayList(postReceiveHooks))); + ReceivePack rp = receive.getReceivePack(); try { rp.receive(in, out, err); + session.setPeerAgent(rp.getPeerUserAgent()); } catch (UnpackException badStream) { // In case this was caused by the user pushing an object whose size // is larger than the receive.maxObjectSizeLimit gerrit.config parameter @@ -177,12 +160,6 @@ } } - private void init(ReceivePack rp) { - for (ReceivePackInitializer initializer : receivePackInitializers) { - initializer.init(projectControl.getProject().getNameKey(), rp); - } - } - private void verifyProjectVisible(final String type, final Set<Account.Id> who) throws UnloggedFailure { for (final Account.Id id : who) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java index e9043f7..db32b3f 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -18,9 +18,8 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.change.ChangeResource; import com.google.gerrit.server.change.ChangesCollection; @@ -28,14 +27,10 @@ import com.google.gerrit.server.change.PostReviewers; import com.google.gerrit.server.change.ReviewerResource; import com.google.gerrit.server.project.ChangeControl; -import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.ProjectControl; -import com.google.gerrit.server.query.change.ChangeData; -import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.SshCommand; import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.ResultSet; import com.google.inject.Inject; import com.google.inject.Provider; @@ -47,7 +42,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; @CommandMetaData(name = "set-reviewers", description = "Add or remove reviewers on a change") @@ -69,7 +66,7 @@ @Argument(index = 0, required = true, multiValued = true, metaVar = "COMMIT", usage = "changes to modify") void addChange(String token) { try { - changes.addAll(parseChangeId(token)); + addChangeImpl(token); } catch (UnloggedFailure e) { throw new IllegalArgumentException(e.getMessage(), e); } catch (OrmException e) { @@ -81,9 +78,6 @@ private ReviewDb db; @Inject - private Provider<InternalChangeQuery> queryProvider; - - @Inject private ReviewerResource.Factory reviewerFactory; @Inject @@ -96,24 +90,25 @@ private Provider<CurrentUser> userProvider; @Inject - private ChangeControl.GenericFactory changeControlFactory; - - @Inject private ChangesCollection changesCollection; + @Inject + private ChangeUtil changeUtil; + private Set<Account.Id> toRemove = new HashSet<>(); - private Set<Change.Id> changes = new HashSet<>(); + + private Map<Change.Id, ChangeResource> changes = new LinkedHashMap<>(); @Override protected void run() throws UnloggedFailure { boolean ok = true; - for (Change.Id changeId : changes) { + for (ChangeResource rsrc : changes.values()) { try { - ok &= modifyOne(changeId); + ok &= modifyOne(rsrc); } catch (Exception err) { ok = false; - log.error("Error updating reviewers on change " + changeId, err); - writeError("fatal", "internal error while updating " + changeId); + log.error("Error updating reviewers on change " + rsrc.getId(), err); + writeError("fatal", "internal error while updating " + rsrc.getId()); } } @@ -122,8 +117,7 @@ } } - private boolean modifyOne(Change.Id changeId) throws Exception { - ChangeResource changeRsrc = changesCollection.parse(changeId); + private boolean modifyOne(ChangeResource changeRsrc) throws Exception { boolean ok = true; // Remove reviewers @@ -168,92 +162,28 @@ return ok; } - private Set<Change.Id> parseChangeId(String idstr) - throws UnloggedFailure, OrmException { - Set<Change.Id> matched = new HashSet<>(4); - boolean isCommit = idstr.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$"); - - // By newer style changeKey? - // - boolean changeKeyParses = idstr.matches("^I[0-9a-f]*$"); - if (changeKeyParses) { - for (ChangeData cd : queryProvider.get().byKeyPrefix(idstr)) { - matchChange(matched, cd.change()); + private void addChangeImpl(String id) throws UnloggedFailure, OrmException { + List<ChangeControl> matched = + changeUtil.findChanges(id, userProvider.get()); + List<ChangeControl> toAdd = new ArrayList<>(changes.size()); + for (ChangeControl ctl : matched) { + Change c = ctl.getChange(); + if (!changes.containsKey(c.getId()) && inProject(c) + && ctl.isVisible(db)) { + toAdd.add(ctl); } } - - // By commit? - // - if (isCommit) { - RevId id = new RevId(idstr); - ResultSet<PatchSet> patches; - if (id.isComplete()) { - patches = db.patchSets().byRevision(id); - } else { - patches = db.patchSets().byRevisionRange(id, id.max()); - } - - for (PatchSet ps : patches) { - matchChange(matched, ps.getId().getParentKey()); - } - } - - // By older style changeId? - // - boolean changeIdParses = false; - if (idstr.matches("^[1-9][0-9]*$")) { - Change.Id id; - try { - id = Change.Id.parse(idstr); - changeIdParses = true; - } catch (IllegalArgumentException e) { - id = null; - changeIdParses = false; - } - - if (changeIdParses) { - matchChange(matched, id); - } - } - - if (!changeKeyParses && !isCommit && !changeIdParses) { - throw error("\"" + idstr + "\" is not a valid change"); - } - - switch (matched.size()) { + switch (toAdd.size()) { case 0: - throw error("\"" + idstr + "\" no such change"); + throw error("\"" + id + "\" no such change"); case 1: - return matched; + ChangeControl ctl = toAdd.get(0); + changes.put(ctl.getId(), changesCollection.parse(ctl)); + break; default: - throw error("\"" + idstr + "\" matches multiple changes"); - } - } - - private void matchChange(Set<Change.Id> matched, Change.Id changeId) { - if (changeId != null && !matched.contains(changeId)) { - try { - matchChange(matched, db.changes().get(changeId)); - } catch (OrmException e) { - log.warn("Error reading change " + changeId, e); - } - } - } - - private void matchChange(Set<Change.Id> matched, Change change) { - try { - if (change != null - && inProject(change) - && changeControlFactory.controlFor(change, - userProvider.get()).isVisible(db)) { - matched.add(change.getId()); - } - } catch (NoSuchChangeException e) { - // Ignore this change. - } catch (OrmException e) { - log.warn("Error reading change " + change.getId(), e); + throw error("\"" + id + "\" matches multiple changes"); } }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java index 34f7107..d278f4b 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -20,6 +20,7 @@ import com.google.gerrit.server.git.ChangeCache; import com.google.gerrit.server.git.TagCache; import com.google.gerrit.server.git.TransferConfig; +import com.google.gerrit.server.git.UploadPackMetricsHook; import com.google.gerrit.server.git.VisibleRefFilter; import com.google.gerrit.server.git.validators.UploadValidationException; import com.google.gerrit.server.git.validators.UploadValidators; @@ -58,6 +59,9 @@ @Inject private SshSession session; + @Inject + private UploadPackMetricsHook uploadMetrics; + @Override protected void runImpl() throws IOException, Failure { if (!projectControl.canRunUploadPack()) { @@ -71,6 +75,7 @@ } up.setPackConfig(config.getPackConfig()); up.setTimeout(config.getTimeout()); + up.setPostUploadHook(uploadMetrics); List<PreUploadHook> allPreUploadHooks = Lists.newArrayList(preUploadHooks); allPreUploadHooks.add(uploadValidatorsFactory.create(project, repo, @@ -78,6 +83,7 @@ up.setPreUploadHook(PreUploadHookChain.newChain(allPreUploadHooks)); try { up.upload(in, out, err); + session.setPeerAgent(up.getPeerUserAgent()); } catch (UploadValidationException e) { // UploadValidationException is used by the UploadValidators to // stop the uploadPack. We do not want this exception to go beyond this
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml index dab19a5..f0430db 100644 --- a/gerrit-war/pom.xml +++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-war</artifactId> - <version>2.12</version> + <version>2.13-SNAPSHOT</version> <packaging>war</packaging> <name>Gerrit Code Review - WAR</name> <description>Gerrit WAR</description>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java index 5e36318..a2947af 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
@@ -23,9 +23,11 @@ import com.google.gerrit.httpd.auth.oauth.OAuthModule; import com.google.gerrit.httpd.auth.openid.OpenIdModule; import com.google.gerrit.httpd.plugins.HttpPluginModule; +import com.google.gerrit.httpd.raw.StaticModule; import com.google.gerrit.lifecycle.LifecycleManager; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lucene.LuceneIndexModule; +import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.server.account.InternalAccountDirectory; import com.google.gerrit.server.cache.h2.DefaultCacheFactory; @@ -260,6 +262,7 @@ }); } modules.add(new DatabaseModule()); + modules.add(new DropWizardMetricMaker.ApiModule()); return Guice.createInjector(PRODUCTION, modules); } @@ -288,6 +291,7 @@ private Injector createSysInjector() { final List<Module> modules = new ArrayList<>(); + modules.add(new DropWizardMetricMaker.RestModule()); modules.add(new WorkQueue.Module()); modules.add(new ChangeHookRunner.Module()); modules.add(new ReceiveCommitsExecutorModule()); @@ -319,7 +323,8 @@ modules.add(new AbstractModule() { @Override protected void configure() { - bind(GerritOptions.class).toInstance(new GerritOptions(false, false)); + bind(GerritOptions.class) + .toInstance(new GerritOptions(config, false, false, false)); } }); modules.add(new GarbageCollectionModule()); @@ -347,8 +352,11 @@ final List<Module> modules = new ArrayList<>(); modules.add(RequestContextFilter.module()); modules.add(AllRequestFilter.module()); + modules.add(RequestMetricsFilter.module()); modules.add(sysInjector.getInstance(GitOverHttpModule.class)); modules.add(sysInjector.getInstance(WebModule.class)); + modules.add(sysInjector.getInstance(StaticModule.class)); + modules.add(sysInjector.getInstance(RequireSslFilter.Module.class)); if (sshInjector != null) { modules.add(sshInjector.getInstance(WebSshGlueModule.class)); } else {
diff --git a/lib/BUCK b/lib/BUCK index a92f910..4d06afa 100644 --- a/lib/BUCK +++ b/lib/BUCK
@@ -1,14 +1,12 @@ include_defs('//lib/maven.defs') +define_license(name = 'antlr') define_license(name = 'Apache1.1') define_license(name = 'Apache2.0') -define_license(name = 'CC-BY3.0') -define_license(name = 'MPL1.1') -define_license(name = 'PublicDomain') -define_license(name = 'antlr') define_license(name = 'args4j') define_license(name = 'automaton') define_license(name = 'bouncycastle') +define_license(name = 'CC-BY3.0') define_license(name = 'clippy') define_license(name = 'codemirror') define_license(name = 'diffy') @@ -17,12 +15,18 @@ define_license(name = 'h2') define_license(name = 'jgit') define_license(name = 'jsch') +define_license(name = 'MPL1.1') define_license(name = 'ow2') +define_license(name = 'page.js') +define_license(name = 'polymer') define_license(name = 'postgresql') define_license(name = 'prologcafe') +define_license(name = 'promise-polyfill') define_license(name = 'protobuf') +define_license(name = 'PublicDomain') define_license(name = 'slf4j') define_license(name = 'xz') + define_license(name = 'DO_NOT_DISTRIBUTE') maven_jar(
diff --git a/lib/LICENSE-page.js b/lib/LICENSE-page.js new file mode 100644 index 0000000..78152a9 --- /dev/null +++ b/lib/LICENSE-page.js
@@ -0,0 +1,20 @@ +(The MIT License) + +Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/LICENSE-polymer b/lib/LICENSE-polymer new file mode 100644 index 0000000..322c5a8 --- /dev/null +++ b/lib/LICENSE-polymer
@@ -0,0 +1,27 @@ +Copyright (c) 2014 The Polymer Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-promise-polyfill b/lib/LICENSE-promise-polyfill new file mode 100644 index 0000000..6f7c0123 --- /dev/null +++ b/lib/LICENSE-promise-polyfill
@@ -0,0 +1,20 @@ +Copyright (c) 2014 Taylor Hakes +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
diff --git a/lib/dropwizard/BUCK b/lib/dropwizard/BUCK new file mode 100644 index 0000000..de73e13 --- /dev/null +++ b/lib/dropwizard/BUCK
@@ -0,0 +1,8 @@ +include_defs('//lib/maven.defs') + +maven_jar( + name = 'dropwizard-core', + id = 'io.dropwizard.metrics:metrics-core:3.1.2', + sha1 = '224f03afd2521c6c94632f566beb1bb5ee32cf07', + license = 'Apache2.0', +)
diff --git a/lib/js.defs b/lib/js.defs new file mode 100644 index 0000000..57ccce8 --- /dev/null +++ b/lib/js.defs
@@ -0,0 +1,157 @@ +# 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. + +NPMJS = 'NPMJS' +GERRIT = 'GERRIT' + +# NOTE: npm_binary rules do not get their licenses checked by gen_licenses.py, +# as we would have to cut too many edges. DO NOT include these binaries in +# build outputs. Using them in the build _process_ is ok. +def npm_binary( + name, + version, + sha1 = '', + repository = NPMJS, + visibility = ['PUBLIC']): + + dir = '%s-%s' % (name, version) + filename = '%s.tgz' % dir + dest = '%s@%s.npm_binary.tgz' % (name, version) + if repository == GERRIT: + url = 'http://gerrit-maven.storage.googleapis.com/npm-packages/%s' % filename + elif repository == NPMJS: + url = 'http://registry.npmjs.org/%s/-/%s' % (name, filename) + else: + raise ValueError('invalid repository: %s' % repository) + cmd = ['$(exe //tools:download_file)', '-o', '$OUT', '-u', url] + if sha1: + cmd.extend(['-v', sha1]) + genrule( + name = name, + cmd = ' '.join(cmd), + out = dest, + visibility = visibility, + ) + + +def run_npm_binary(target): + return '$(location //tools/js:run_npm_binary) $(location %s)' % target + + +def bower_component( + name, + package, + version, + license, + deps = [], + semver = None, + sha1 = '', + visibility = ['PUBLIC']): + download_name = '%s__download_bower' % name + genrule( + name = download_name, + cmd = ' '.join([ + '$(exe //tools/js:download_bower)', + '-b', '"%s"' % run_npm_binary('//lib/js:bower'), + '-n', name, + '-p', package, + '-v', version, + '-s', sha1, + '-o', '$OUT', + ]), + out = '%s.zip' % download_name, + license = license, + visibility = [], + ) + + renamed_name = '%s__renamed' % name + genrule( + name = renamed_name, + cmd = ' && '.join([ + 'cd $TMP', + 'mkdir bower_components', + 'cd bower_components', + 'unzip $(location :%s)' % download_name, + 'cd ..', + 'zip -r $OUT bower_components', + ]), + out = '%s.zip' % renamed_name, + visibility = [], + ) + + genrule( + name = name, + cmd = _combine_components([':%s' % renamed_name] + deps), + out = '%s-%s.zip' % (name, version), + visibility = visibility, + ) + + version_name = '%s__bower_version' % name + dep_version = semver if semver is not None else version + deps_json = '{"%s": "%s#%s"}' % (name, package, dep_version) + genrule( + name = version_name, + cmd = "echo '%s' > $OUT" % deps_json, + out = version_name, + visibility = visibility, + ) + + +def bower_components( + name, + deps, + visibility = ['PUBLIC']): + genrule( + name = name, + cmd = _combine_components(deps), + out = '%s.bower_components.zip' % name, + visibility = visibility, + ) + + +def _combine_components(deps): + cmds = ['cd $TMP'] + for d in deps: + cmds.append('unzip -qo $(location %s)' % d) + cmds.append('zip -r $OUT bower_components') + return ' && '.join(cmds) + + +VULCANIZE_FLAGS = [ + '--inline-scripts', + '--inline-css', + '--strip-comments', +] + +def vulcanize( + name, + app, + srcs, + components, + extra_flags = [], + visibility = ['PUBLIC']): + genrule( + name = name, + cmd = ' '.join([ + 'unzip', '-qd', '$SRCDIR', + ] + ['$(location %s)' % c for c in components] + [ + '&&', run_npm_binary('//lib/js:vulcanize') + ] + VULCANIZE_FLAGS + extra_flags + [ + '--out-html', '$OUT', + '$SRCDIR/%s' % app, + ]), + srcs = srcs, + out = '%s.html' % name, + visibility = visibility, + )
diff --git a/lib/js/BUCK b/lib/js/BUCK new file mode 100644 index 0000000..d63df1f --- /dev/null +++ b/lib/js/BUCK
@@ -0,0 +1,188 @@ +include_defs('//lib/js.defs') + +# WHEN REVIEWING NEW NPM_BINARY RULES: +# +# You must check licenses in the transitive closure of dependencies to ensure +# they can be used by Gerrit. (npm binaries are not distributed with Gerrit +# releases, so we are less restrictive in our selection of licenses, but we +# still need to do a sanity check.) +# +# To do this: +# npm install -g license-checker +# mkdir /tmp/npmtmp +# cd /tmp/npmtmp +# npm install <package>@<version> +# license-checker +# (Piping to grep -o 'licenses:.*' and/or sort -u may make the output saner.) + +npm_binary( + name = 'bower', + version = '1.6.5', + sha1 = '59d457122a161e42cc1625bbab8179c214b7ac11', +) + +npm_binary( + name = 'vulcanize', + version = '1.14.0', + sha1 = '91eac280d031b5bbcafb5f86bb6ed30515fa2564', + repository = GERRIT, +) + +# ## Adding Bower component dependencies +# +# 1. Add a dummy bower_component rule to this file, specifying the semantic +# version you want to use. The actual version will be filled in by Bower, +# after evaluating the full dependency tree. +# +# bower_component( +# name = 'somepackage', +# package = 'someauthor/somepackage', +# version = 'TODO', +# semver = '~1.0.0', +# license = 'DO_NOT_DISTRIBUTE' +# ) +# +# 2. Add your bower_component as a dep to a bower_components rule. +# +# bower_components( +# name = 'polygerrit_components', +# deps = [ +# '//lib/js:foo', +# '//lib/js:somepackage', # NEW +# ], +# ) +# +# 3. Run bower2buck.py. +# +# buck run //tools/js:bower2buck -- -o /tmp/newbuck +# +# 4. Use your favorite diff tool to merge the output in newbuck with this file. +# bower2buck reevaluates semantic versions and may upgrade some packages, so +# you may need to make changes beyond the new component that was added. +# +# meld /tmp/newbuck lib/js/BUCK +# +# +# ## Updating Bower component dependencies +# +# Use the same procedure as for adding dependencies, except just change the +# version number of the existing bower_component rather than adding a new rule. + +bower_component( + name = 'iron-a11y-keys', + package = 'polymerelements/iron-a11y-keys', + version = '1.0.3', + deps = [ + ':iron-a11y-keys-behavior', + ':polymer', + ], + license = 'polymer', + sha1 = 'cf7fdf9ffa3349d28632fc3e86b84300d1439e29', +) + +bower_component( + name = 'iron-a11y-keys-behavior', + package = 'polymerelements/iron-a11y-keys-behavior', + version = '1.0.8', + deps = [':polymer'], + license = 'polymer', + sha1 = '2f1ea0b4542e2949de195dff5cbe02b7cb953eff', +) + +bower_component( + name = 'iron-ajax', + package = 'polymerelements/iron-ajax', + version = '1.1.0', + deps = [ + ':polymer', + ':promise-polyfill', + ], + license = 'polymer', + sha1 = 'f94a3a3d847842c49def41e27da42c7c94f8d7c7', +) + +bower_component( + name = 'iron-input', + package = 'polymerelements/iron-input', + version = '1.0.6', + deps = [ + ':iron-validatable-behavior', + ':polymer', + ], + license = 'polymer', + sha1 = '2d3eedf0a26046c0e828b1ce3d5b102ee1d0ab19', +) + +bower_component( + name = 'iron-meta', + package = 'polymerelements/iron-meta', + version = '1.1.1', + deps = [':polymer'], + license = 'polymer', + sha1 = 'e06281b6ddb3355ceca44975a167381b1fd72ce5', +) + +bower_component( + name = 'iron-test-helpers', + package = 'polymerelements/iron-test-helpers', + version = '1.0.6', + semver = '~1.0.6', + deps = [':polymer'], + license = 'DO_NOT_DISTRIBUTE', + sha1 = 'c0f7c7f010ca3c63fb08ae0d9462e400380cde2c', +) + +bower_component( + name = 'iron-validatable-behavior', + package = 'polymerelements/iron-validatable-behavior', + version = '1.0.5', + deps = [ + ':iron-meta', + ':polymer', + ], + license = 'polymer', + sha1 = '5a68250d6d9abcd576f116dc4fc7312426323883', +) + +bower_component( + name = 'page', + package = 'visionmedia/page.js', + version = '1.6.4', + license = 'page.js', + sha1 = 'cc442386d4e392be26c85873f463db76fafbaeaf', +) + +bower_component( + name = 'polymer', + package = 'polymer/polymer', + version = '1.2.2', + deps = [':webcomponentsjs'], + license = 'polymer', + sha1 = '7f4033438425584d8912a80614d1a4f754438e15', +) + +bower_component( + name = 'promise-polyfill', + package = 'polymerlabs/promise-polyfill', + version = '1.0.0', + deps = [':polymer'], + license = 'promise-polyfill', + sha1 = 'a3b598c06cbd7f441402e666ff748326030905d6', +) + +bower_component( + name = 'test-fixture', + package = 'polymerelements/test-fixture', + version = '1.0.3', + semver = '^1.0.0', + license = 'DO_NOT_DISTRIBUTE', + sha1 = '21192d554ff6ad7eea894ca751c73b6bc46867dc', +) + +bower_component( + name = 'webcomponentsjs', + package = 'webcomponentsjs', + version = '0.7.17', + license = 'polymer', + sha1 = '36e29cfe21caa71322a0b5026d7d423c33c0426f', +)
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK index c5107d5..7d0a16c 100644 --- a/lib/lucene/BUCK +++ b/lib/lucene/BUCK
@@ -1,6 +1,6 @@ include_defs('//lib/maven.defs') -VERSION = '5.3.0' +VERSION = '5.3.1' # 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 = '9e12bb7c39e964a544e3a23b9c8ffa9599d38f10', + sha1 = '36860653d7e09790ada96aeb1970b4ca396ac5d7', 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 = '1502beac94cf437baff848ffbbb8f76172befa6b', + sha1 = 'bd804dbc1b8f7941018926e940d20d1016b36c4c', 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 = 'f654901e55fe56bdbe4be202767296929c2f8d9e', + sha1 = '380603f537317a78f9d9b7421bc2ac87586cb9a1', license = 'Apache2.0', deps = [':core_jar'], exclude = [ @@ -53,7 +53,7 @@ maven_jar( name = 'misc', id = 'org.apache.lucene:lucene-misc:' + VERSION, - sha1 = 'd03ce6d1bb8ab3926b3acc717418c474a49ade69', + sha1 = '7891bbc18b372135c2a52b471075b0bdf5f110ec', 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 = '2c5e08580316c90b56a52e3cb686e1cf69db3f9e', + sha1 = 'bef0e2ac5b196dbab9d0b7c8cc8196b7ef5dd056', license = 'Apache2.0', deps = [':core-and-backward-codecs'], exclude = [
diff --git a/plugins/README b/plugins/README deleted file mode 100644 index 00df3c5..0000000 --- a/plugins/README +++ /dev/null
@@ -1,11 +0,0 @@ -If you are adding a directory here: - -- Search all pom.xml files for "CORE PLUGIN LIST". -- Add the new plugin to that location. -- (optional) Thank the Maven developers for making this easy. - -- Ensure the plugin's pom.xml <version> is the same as Gerrit's - own pom.xml(s). Gerrit will only embed a plugin that has the - same version as itself. - -- Register the plugin as a submodule with git submodule.
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin index 82eefc2..d14b1d6 160000 --- a/plugins/cookbook-plugin +++ b/plugins/cookbook-plugin
@@ -1 +1 @@ -Subproject commit 82eefc2048a4dd69ab589213190dd8403295fb7d +Subproject commit d14b1d6d23162f821ae505936032ffaf330b6808
diff --git a/plugins/replication b/plugins/replication index 675d9dd..4ab29b7 160000 --- a/plugins/replication +++ b/plugins/replication
@@ -1 +1 @@ -Subproject commit 675d9dd370948f0508dbe536dfc65cc6cd3bb00d +Subproject commit 4ab29b755a147a69d88bf000e276a7a2eaa6403b
diff --git a/polygerrit-ui/.gitattributes b/polygerrit-ui/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/polygerrit-ui/.gitattributes
@@ -0,0 +1 @@ +* text=auto \ No newline at end of file
diff --git a/polygerrit-ui/.gitignore b/polygerrit-ui/.gitignore new file mode 100644 index 0000000..af8e683 --- /dev/null +++ b/polygerrit-ui/.gitignore
@@ -0,0 +1,5 @@ +node_modules +npm-debug.log +dist +bower_components +.tmp
diff --git a/polygerrit-ui/BUCK b/polygerrit-ui/BUCK new file mode 100644 index 0000000..df82442 --- /dev/null +++ b/polygerrit-ui/BUCK
@@ -0,0 +1,12 @@ +include_defs('//lib/js.defs') + +bower_components( + name = 'polygerrit_components', + deps = [ + '//lib/js:polymer', + '//lib/js:page', + '//lib/js:iron-ajax', + '//lib/js:iron-a11y-keys', + '//lib/js:iron-input', + ], +)
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md new file mode 100644 index 0000000..c2cd4bd --- /dev/null +++ b/polygerrit-ui/README.md
@@ -0,0 +1,70 @@ +# PolyGerrit + +## Installing [Node.js](https://nodejs.org/en/download/) + +```sh +# Debian/Ubuntu +sudo apt-get install nodejs-legacy + +# OS X with Homebrew +brew install node +``` + +All other platforms: [download from +nodejs.org](https://nodejs.org/en/download/). + +## Local UI, Production Data + +To test the local UI against gerrit-review.googlesource.com: + +```sh +cd polygerrit-ui +npm install +bower install +go run server.go +``` + +Then visit http://localhost:8081 + +## Local UI, Test Data + +One-time setup: + +1. [Install Buck](https://gerrit-review.googlesource.com/Documentation/dev-buck.html#_installation) + for building Gerrit. +2. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-buck.html#_gerrit_development_war_file) + and set up a [local test site](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init). + +Run a test server: + +```sh +buck build polygerrit && \ +java -jar buck-out/gen/polygerrit/polygerrit.war daemon --polygerrit-dev -d ../gerrit_testsite --console-log --show-stack-trace +``` + +## Running Tests + +One-time setup: + +```sh +# Debian/Ubuntu +sudo apt-get install npm + +# OS X with Homebrew +brew install npm + +# All platforms (including those above) +sudo npm install -g web-component-tester +``` + +Run all web tests: + +```sh +buck test --include web +``` + +If you need to pass additional arguments to `wct`: + +```sh +WCT_ARGS='-p --some-flag="foo bar"' buck test --no-results-cache --include web +```
diff --git a/polygerrit-ui/app/BUCK b/polygerrit-ui/app/BUCK new file mode 100644 index 0000000..d56c25d --- /dev/null +++ b/polygerrit-ui/app/BUCK
@@ -0,0 +1,84 @@ +include_defs('//lib/js.defs') + +WCT_TEST_PATTERNS = ['test/**'] +PY_TEST_PATTERNS = ['polygerrit_wct_tests.py'] +APP_SRCS = glob( + ['**'], + excludes = [ + 'BUCK', + 'index.html', + ] + WCT_TEST_PATTERNS + PY_TEST_PATTERNS) + +WEBJS = 'bower_components/webcomponentsjs/webcomponents-lite.js' + +# TODO(dborowitz): Putting these rules in this package avoids having to handle +# the app/ prefix like we would have to if this were in the parent directory. +# The only reason for the app subdirectory in the first place was convenience +# when witing server.go; when that goes away, we can just move all the files and +# these rules up one directory. +genrule( + name = 'polygerrit_ui', + cmd = ' && '.join([ + 'mkdir $TMP/polygerrit_ui', + 'cd $TMP/polygerrit_ui', + 'cp $(location :processed_index) index.html', + 'mkdir -p {elements,bower_components/webcomponentsjs}', + 'cp $(location :polygerrit) elements/gr-app.vulcanized.html', + 'cp -rp $SRCDIR/* .', + 'unzip -p $(location //polygerrit-ui:polygerrit_components) %s>%s' % (WEBJS, WEBJS), + 'cd $TMP', + 'zip -9qr $OUT .', + ]), + srcs = glob([ + 'favicon.ico', + 'styles/**/*.css' + ]), + out = 'polygerrit_ui.zip', + visibility = ['PUBLIC'], +) + +genrule( + name = 'processed_index', + cmd = 'sed "s/gr-app.html/gr-app.vulcanized.html/g" $SRCS >$OUT', + srcs = ['index.html'], + out = 'index_processed.html', +) + +vulcanize( + name = 'polygerrit', + app = 'elements/gr-app.html', + srcs = APP_SRCS, + components = ['//polygerrit-ui:polygerrit_components'], +) + + +bower_components( + name = 'test_components', + deps = [ + '//polygerrit-ui:polygerrit_components', + '//lib/js:iron-test-helpers', + '//lib/js:test-fixture', + ], +) + +genrule( + name = 'test_resources', + cmd = ' && '.join([ + 'cd $TMP', + 'unzip -q $(location :test_components)', + 'cp -rL $SRCDIR/* .', + 'zip -r $OUT .', + ]), + srcs = APP_SRCS + glob(WCT_TEST_PATTERNS), + out = 'test_resources.zip', +) + +python_test( + name = 'polygerrit_tests', + srcs = glob(PY_TEST_PATTERNS), + resources = [':test_resources'], + labels = [ + 'manual', + 'web', + ], +)
diff --git a/polygerrit-ui/app/elements/gr-account-dropdown.html b/polygerrit-ui/app/elements/gr-account-dropdown.html new file mode 100644 index 0000000..fbc9a68 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-account-dropdown.html
@@ -0,0 +1,61 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> + +<dom-module id="gr-account-dropdown"> + <template> + <style> + :not(.loggedIn):not(.loggedOut) .loginButton, + :not(.loggedIn):not(.loggedOut) .logoutButton, + .loggedIn .loginButton, + .loggedOut .logoutButton { + display: none; + } + </style> + <div class$="[[_computeContainerClass(account)]]"> + <a class="loginButton" href="/login" on-tap="_loginTapHandler">Login</a> + <a class="logoutButton" href="/logout">Logout</a> + </div> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-account-dropdown', + + properties: { + account: Object, + }, + + _loginTapHandler: function(e) { + e.preventDefault(); + page('/login/' + encodeURIComponent( + window.location.pathname + window.location.hash)); + }, + + _computeContainerClass: function(account) { + if (Object.keys(account).length == 0) { + return 'loggedOut'; + } + return 'loggedIn'; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-ajax.html b/polygerrit-ui/app/elements/gr-ajax.html new file mode 100644 index 0000000..f2adc06 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-ajax.html
@@ -0,0 +1,70 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> + +<dom-module id="gr-ajax"> + <template> + <iron-ajax id="xhr" + auto="{{auto}}" + url="{{url}}" + params="{{params}}" + json-prefix=")]}'" + debounce-duration="300"></iron-ajax> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-ajax', + + hostAttributes: { + hidden: true + }, + + properties: { + auto: Boolean, + url: String, + params: { + type: Object, + value: function() { + return {}; + }, + }, + response: { + type: Object, + notify: true, + }, + }, + + listeners: { + 'xhr.response': '_handleResponse', + 'xhr.error': '_handleResponse', + }, + + _handleResponse: function(e, req) { + if (req.status >= 200 && req.status < 300) { + this.response = req.response; + } else { + this.response = {}; + } + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html new file mode 100644 index 0000000..87f0698 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-app.html
@@ -0,0 +1,178 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="../styles/app-theme.html"> +<link rel="import" href="gr-account-dropdown.html"> +<link rel="import" href="gr-ajax.html"> +<link rel="import" href="gr-change-list-view.html"> +<link rel="import" href="gr-change-view.html"> +<link rel="import" href="gr-dashboard-view.html"> +<link rel="import" href="gr-diff-view.html"> +<link rel="import" href="gr-search-bar.html"> + +<script src="../bower_components/page/page.js"></script> +<script src="../scripts/app.js"></script> +<script src="../scripts/changes.js"></script> +<script src="../scripts/util.js"></script> + +<dom-module id="gr-app"> + <template> + <style> + :host { + background-color: var(--secondary-color); + display: flex; + min-height: 100vh; + flex-direction: column; + } + :host([constrained]) main { + margin: 0 auto; + width: 100%; + max-width: 980px; + } + header, + footer { + background-color: var(--primary-color); + color: var(--primary-text-color); + padding: .5rem 1.25rem; + } + header { + display: flex; + align-items: center; + } + main { + flex: 1; + } + .bigTitle { + color: var(--primary-text-color); + font-size: 1.75em; + text-decoration: none; + } + .bigTitle:hover { + text-decoration: underline; + } + .headerRightItems { + display: flex; + flex: 1; + justify-content: flex-end; + } + gr-search-bar { + width: 500px; + } + gr-account-dropdown { + align-items: center; + display: flex; + margin-left: var(--default-horizontal-margin); + } + </style> + <gr-ajax auto url="/accounts/self/detail" response="{{account}}"></gr-ajax> + <gr-ajax auto url="/config/server/info" response="{{config}}"></gr-ajax> + <header role="banner"> + <a href="/" class="bigTitle">PolyGerrit</a> + <div class="headerRightItems"> + <gr-search-bar value="{{params.query}}" role="search"></gr-search-bar> + <gr-account-dropdown account="[[account]]"></gr-account-dropdown> + </div> + </header> + <main> + <template is="dom-if" if="{{_showChangeListView}}" restamp="true"> + <gr-change-list-view params="[[params]]"></gr-change-list-view> + </template> + <template is="dom-if" if="{{_showDashboardView}}" restamp="true"> + <gr-dashboard-view params="[[params]]"></gr-dashboard-view> + </template> + <template is="dom-if" if="{{_showChangeView}}" restamp="true"> + <gr-change-view params="[[params]]"></gr-change-view> + </template> + <template is="dom-if" if="{{_showDiffView}}" restamp="true"> + <gr-diff-view params="[[params]]"></gr-diff-view> + </template> + </main> + <footer role="contentinfo">Powered by PolyGerrit</footer> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-app', + + properties: { + account: { + type: Object, + observer: '_accountChanged', + }, + accountReady: { + type: Object, + readOnly: true, + notify: true, + value: function() { + return new Promise(function(resolve) { + this._resolveAccountReady = resolve; + }.bind(this)); + }, + }, + config: { + type: Object, + observer: '_configChanged', + }, + configReady: { + type: Object, + readOnly: true, + notify: true, + value: function() { + return new Promise(function(resolve) { + this._resolveConfigReady = resolve; + }.bind(this)); + }, + }, + constrained: { + type: Boolean, + value: false, + reflectToAttribute: true, + }, + params: Object, + route: { + type: Object, + value: {}, + observer: '_routeChanged', + }, + }, + + get loggedIn() { + return this.account && Object.keys(this.account).length > 0; + }, + + _accountChanged: function() { + this._resolveAccountReady(); + }, + + _configChanged: function(config) { + this._resolveConfigReady(config); + }, + + _routeChanged: function(route) { + this.set('_showChangeListView', route == 'gr-change-list-view'); + this.set('_showDashboardView', route == 'gr-dashboard-view'); + this.set('_showChangeView', route == 'gr-change-view'); + this.set('_showDiffView', route == 'gr-diff-view'); + this.constrained = route == 'gr-change-view'; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-change-list-item.html b/polygerrit-ui/app/elements/gr-change-list-item.html new file mode 100644 index 0000000..d646552 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-change-list-item.html
@@ -0,0 +1,215 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="gr-date-formatter.html"> + +<dom-module id="gr-change-list-item"> + <template> + <style> + :host { + display: table-row; + } + :host([selected]) { + background-color: #d8EdF9; + } + th, td { + border-bottom: 1px solid #eee; + padding: .3em .5em; + vertical-align: top; + } + th { + background: #ddd; + text-align: left; + } + a { + color: var(--default-text-color); + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + .positionIndicator { + visibility: hidden; + } + :host([selected]) .positionIndicator { + visibility: visible; + } + .avatarImage { + border-radius: 50%; + height: 1.3em; + vertical-align: -.3em; + width: 1.3em; + } + .u-monospace { + font-family: 'Source Code Pro'; + } + .u-green { + color: #388E3C; + } + .u-red { + color: #D32F2F; + } + </style> + <template is="dom-if" if="[[header]]"> + <th></th> <!-- keyboard position indicator --> + <th>Subject</th> + <th>Status</th> + <th>Owner</th> + <th>Project</th> + <th>Branch</th> + <th>Updated</th> + <th>Size</th> + <th title="Code-Review">CR</th> + <th title="Verified">V</th> + </template> + <template is="dom-if" if="[[!header]]"> + <td> + <span class="positionIndicator">▶</span> + </td> + <td> + <a href$="[[changeURL]]">[[change.subject]]</a> + </td> + <td>[[_computeChangeStatusString(change)]]</td> + <td> + <template is="dom-if" if="[[showAvatar]]"> + <img class="avatarImage" src$="[[_computeAvatarURL(change.owner)]]"> + </template> + <a href$="[[_computeOwnerLink(change.owner.email)]]" + title$="[[_computeOwnerTitle(change.owner)]]">[[change.owner.name]]</a> + </td> + <td> + <a href$="[[_computeProjectURL(change.project)]]">[[change.project]]</a> + </td> + <td> + <a href$="[[_computeProjectBranchURL(change.project, change.branch)]]">[[change.branch]]</a> + </td> + <td><gr-date-formatter date-str="[[change.updated]]"></gr-date-formatter></td> + <td class="u-monospace"> + <span class="u-green"><span>+</span>[[change.insertions]]</span>, + <span class="u-red"><span>-</span>[[change.deletions]]</span> + </td> + <td title="Code-Review" + class$="[[_computeCodeReviewClass(change.labels.Code-Review)]]">[[_computeCodeReviewLabel(change.labels.Code-Review)]]</td> + <td title="Verified" class="u-green">[[_computeVerifiedLabel(change.labels.Verified)]]</td> + </template> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-change-list-item', + + properties: { + header: { + type: Boolean, + reflectToAttribute: true, + value: false, + }, + selected: { + type: Boolean, + reflectToAttribute: true, + }, + change: Object, + changeURL: { + type: String, + computed: '_computeChangeURL(change._number)', + }, + showAvatar: Boolean, + }, + + ready: function() { + app.configReady.then(function(cfg) { + this.showAvatar = cfg && cfg.plugin && cfg.plugin.has_avatars; + }.bind(this)); + }, + + _computeChangeURL: function(changeNum) { + if (!changeNum) { return ''; } + return '/c/' + changeNum + '/'; + }, + + _computeChangeStatusString: function(change) { + if (!change.mergeable) { + return 'Merge Conflict'; + } + return ''; + }, + + _computeCodeReviewClass: function(codeReview) { + if (!codeReview) { return ''; } + if (codeReview.approved) { + return 'u-green'; + } + if (codeReview.value == 1) { + return 'u-monospace u-green'; + } + if (codeReview.value == -1) { + return 'u-monospace u-red'; + } + return ''; + }, + + _computeCodeReviewLabel: function(codeReview) { + if (!codeReview) { return ''; } + if (codeReview.approved) { + return '✓'; + } + if (codeReview.value == 1) { + return '+1'; + } + if (codeReview.value == -1) { + return '-1'; + } + return ''; + }, + + _computeVerifiedLabel: function(verified) { + if (verified && verified.approved) { + return '✓'; + } + return '' + }, + + _computeAvatarURL: function(owner) { + if (!owner) { return ''; } + return '/accounts/' + owner.email + '/avatar?s=32' + }, + + _computeOwnerLink: function(email) { + if (!email) { return ''; } + return '/q/owner:' + encodeURIComponent(email) + '+status:open'; + }, + + _computeOwnerTitle: function(owner) { + if (!owner) { return ''; } + // TODO: Is this safe from XSS attacks? + return owner.name + ' <' + owner.email + '>'; + }, + + _computeProjectURL: function(project) { + return '/projects/' + project + ',dashboards/default'; + }, + + _computeProjectBranchURL: function(project, branch) { + return '/q/status:open+project:' + project + '+branch:' + branch; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-change-list-view.html b/polygerrit-ui/app/elements/gr-change-list-view.html new file mode 100644 index 0000000..4d874dd --- /dev/null +++ b/polygerrit-ui/app/elements/gr-change-list-view.html
@@ -0,0 +1,130 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html"> +<link rel="import" href="gr-change-list.html"> + +<dom-module id="gr-change-list-view"> + <template> + <style> + :host { + background-color: var(--view-background-color); + display: block; + margin: 0 1.25rem; + } + gr-change-list { + margin-top: 1em; + width: 100%; + } + nav { + margin-bottom: 1em; + padding: .5em 0; + text-align: center; + } + nav a { + display: inline-block; + } + nav a:first-of-type { + margin-right: .5em; + } + [hidden] { + display: none !important; + } + </style> + <iron-ajax + auto + url="/changes/" + params="[[_computeQueryParams(query, offset)]]" + json-prefix=")]}'" + last-response="{{_changes}}" + debounce-duration="300"></iron-ajax> + <gr-change-list changes="{{_changes}}"></gr-change-list> + <nav> + <a href$="[[_computeNavLink(query, offset, -1)]]" + hidden$="[[_hidePrevArrow(offset)]]">← Prev</a> + <a href$="[[_computeNavLink(query, offset, 1)]]" + hidden$="[[_hideNextArrow(_changes.length)]]">Next →</a> + </nav> + </template> + <script> + (function() { + 'use strict'; + + var DEFAULT_NUM_CHANGES = 25; + + Polymer({ + is: 'gr-change-list-view', + + properties: { + /** + * URL params passed from the router. + */ + params: { + type: Object, + observer: '_paramsChanged', + }, + + /** + * Change objects loaded from the server. + */ + _changes: Array, + }, + + _paramsChanged: function(value) { + this.query = value.query; + this.offset = value.offset || 0; + }, + + _computeQueryParams: function(query, offset) { + var options = Changes.listChangesOptionsToHex( + Changes.ListChangesOption.LABELS, + Changes.ListChangesOption.DETAILED_ACCOUNTS + ); + var obj = { + n: DEFAULT_NUM_CHANGES, // Number of results to return. + O: options, + S: offset || 0, + }; + if (query && query.length > 0) { + obj.q = query; + } + return obj; + }, + + _computeNavLink: function(query, offset, direction) { + // Offset could be a string when passed from the router. + offset = +(offset || 0); + var newOffset = Math.max(0, offset + (25 * direction)); + var href = '/q/' + query; + if (newOffset > 0) { + href += ',' + newOffset; + } + return href; + }, + + _hidePrevArrow: function(offset) { + return offset == 0; + }, + + _hideNextArrow: function(changesLen) { + return changesLen < DEFAULT_NUM_CHANGES; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-change-list.html b/polygerrit-ui/app/elements/gr-change-list.html new file mode 100644 index 0000000..c9882aa --- /dev/null +++ b/polygerrit-ui/app/elements/gr-change-list.html
@@ -0,0 +1,117 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="../bower_components/iron-a11y-keys/iron-a11y-keys.html"> +<link rel="import" href="gr-change-list-item.html"> + +<dom-module id="gr-change-list"> + <template> + <style> + :host { + display: table; + border-collapse: collapse; + } + </style> + + <iron-a11y-keys + target="[[keyTarget]]" + keys="j k enter" + on-keys-pressed="_handleKey"></iron-a11y-keys> + + <gr-change-list-item header></gr-change-list-item> + <template is="dom-repeat" items="{{changes}}" as="change"> + <gr-change-list-item change="[[change]]" + selected="[[_isSelected(index)]]"></gr-change-list-item> + </template> + </template> + + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-change-list', + + hostAttributes: { + tabindex: 0, + }, + + properties: { + /** + * An array of ChangeInfo objects to render. + * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info + */ + changes: Array, + keyTarget: { + type: Object, + value: function() { + return document.body; + } + }, + selectedIndex: { + type: Number, + value: 0, + observer: '_selectedIndexChanged', + }, + }, + + _isSelected: function(index) { + return index == this.selectedIndex; + }, + + _selectedIndexChanged: function(value) { + // Don't re-render the entire list. + var changeEls = this._getNonHeaderListItems(); + for (var i = 0; i < changeEls.length; i++) { + changeEls[i].toggleAttribute('selected', i == value); + } + }, + + _handleKey: function(e) { + var len = (this.changes && this.changes.length) || 0; + switch(e.detail.combo) { + case 'j': + if (this.selectedIndex == len - 1) { return; } + this.selectedIndex += 1; + break; + case 'k': + if (this.selectedIndex == 0) { return; } + this.selectedIndex -= 1; + break; + case 'enter': + page(this._changeURLForIndex(this.selectedIndex)); + break; + } + }, + + _changeURLForIndex: function(index) { + var changeEls = this._getNonHeaderListItems(); + if (index < changeEls.length && changeEls[index]) { + return changeEls[index].changeURL; + } + return ''; + }, + + _getNonHeaderListItems: function() { + return Polymer.dom(this.root).querySelectorAll( + 'gr-change-list-item:not([header])'); + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-change-view.html b/polygerrit-ui/app/elements/gr-change-view.html new file mode 100644 index 0000000..211aefb --- /dev/null +++ b/polygerrit-ui/app/elements/gr-change-view.html
@@ -0,0 +1,203 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html"> +<link rel="import" href="gr-date-formatter.html"> +<link rel="import" href="gr-file-list.html"> +<link rel="import" href="gr-messages-list.html"> + +<dom-module id="gr-change-view"> + <template> + <style> + :host { + background-color: var(--view-background-color); + display: block; + } + .container { + margin: 1em 0; + padding-top: 1em; + } + h2 { + padding: 0 var(--default-horizontal-margin); + } + section { + margin: 10px 0; + padding: 10px var(--default-horizontal-margin); + } + table { + border-collapse: collapse; + } + td { + padding: 2px 5px; + vertical-align: top; + } + .changeInfo-label { + font-weight: bold; + text-align: right; + } + .summary { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + font-family: 'Source Code Pro', Menlo, 'Lucida Console', Monaco, monospace; + overflow-x: auto; + white-space: pre-wrap; + } + gr-file-list { + padding: 0 var(--default-horizontal-margin) 10px; + } + </style> + <iron-ajax id="detailXHR" + url="[[_computeDetailPath(changeNum)]]" + params="[[_computeDetailQueryParams()]]" + json-prefix=")]}'" + last-response="{{change}}"></iron-ajax> + <iron-ajax id="commentsXHR" + url="[[_computeCommentsPath(changeNum)]]" + json-prefix=")]}'" + last-response="{{comments}}"></iron-ajax> + + <div class="container"> + <h2> + <a href$="[[_computeChangePath(change._number)]]">[[change._number]]</a><span>:</span> + <span>[[change.subject]]</span> + </h2> + <section class="changeInfo"> + <table> + <tr> + <td class="changeInfo-label">Owner</td> + <td>[[change.owner.name]]</td> + </tr> + <tr> + <td class="changeInfo-label">Reviewers</td> + <td> + <template is="dom-repeat" + items="[[_computeReviewers(change.labels.Code-Review.all, change.owner)]]" + as="reviewer"> + <div>[[reviewer.name]]</div> + </template> + </td> + </tr> + <tr> + <td class="changeInfo-label">Project</td> + <td>[[change.project]]</td> + </tr> + <tr> + <td class="changeInfo-label">Branch</td> + <td>[[change.branch]]</td> + </tr> + <tr> + <td class="changeInfo-label">Topic</td> + <td>[[change.topic]]</td> + </tr> + <tr> + <td class="changeInfo-label">Strategy</td> + <td></td> + </tr> + <tr> + <td class="changeInfo-label">Updated</td> + <td> + <gr-date-formatter + date-str="[[change.updated]]"></gr-date-formatter> + </td> + </tr> + </table> + </section> + <section class="summary">[[_computeCurrentRevisionMessage(change)]]</section> + <gr-file-list change-num="[[changeNum]]" + patch-num="[[_computePatchNum(change.current_revision)]]" + revision="[[change.current_revision]]" + comments="[[comments]]"></gr-file-list> + <gr-messages-list change-num="[[changeNum]]" + messages="[[change.messages]]" + comments="[[comments]]"></gr-messages-list> + </div> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-change-view', + + properties: { + /** + * URL params passed from the router. + */ + params: { + type: Object, + observer: '_paramsChanged', + }, + + changeNum: Number, + }, + + _paramsChanged: function(value) { + this.changeNum = value.changeNum; + if (!this.changeNum) { + this.change = null; + this.comments = null; + return; + } + this.$.detailXHR.generateRequest(); + this.$.commentsXHR.generateRequest(); + }, + + _computeChangePath: function(changeNum) { + return '/c/' + changeNum; + }, + + _computeDetailPath: function(changeNum) { + return '/changes/' + changeNum + '/detail'; + }, + + _computeCommitInfoPath: function(changeNum, commitHash) { + return '/changes/' + changeNum + '/revisions/' + commitHash + '/commit'; + }, + + _computeCommentsPath: function(changeNum) { + return '/changes/' + changeNum + '/comments'; + }, + + _computePatchNum: function(revision) { + return this.change && this.change.revisions[revision]._number; + }, + + _computeDetailQueryParams: function() { + var options = Changes.listChangesOptionsToHex( + Changes.ListChangesOption.CURRENT_REVISION, + Changes.ListChangesOption.CURRENT_COMMIT, + Changes.ListChangesOption.CHANGE_ACTIONS + ); + return { O: options }; + }, + + _computeCurrentRevisionMessage: function(change) { + return change && + change.revisions[change.current_revision].commit.message; + }, + + _computeReviewers: function(reviewers, owner) { + if (reviewers.length == 1) { return reviewers; } + return reviewers.filter(function(reviewer) { + return reviewer._account_id != owner._account_id; + }); + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-dashboard-view.html b/polygerrit-ui/app/elements/gr-dashboard-view.html new file mode 100644 index 0000000..73de392 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-dashboard-view.html
@@ -0,0 +1,59 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> + +<dom-module id="gr-dashboard-view"> + <template> + <iron-ajax id="xhr" + auto + url="/changes/" + params="[[_computeQueryParams()]]" + last-response="{{_results}}" + json-prefix=")]}'" + debounce-duration="300"></iron-ajax> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-dashboard-view', + + properties: { + _results: Array, + }, + + _computeQueryParams: function() { + var options = Changes.listChangesOptionsToHex( + Changes.ListChangesOption.LABELS, + Changes.ListChangesOption.DETAILED_ACCOUNTS, + Changes.ListChangesOption.REVIEWED + ); + return { + O: options, + q: [ + 'is:open owner:self', + 'is:open reviewer:self -owner:self', + 'is:closed (owner:self OR reviewer:self) -age:4w limit:10', + ], + }; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-date-formatter.html b/polygerrit-ui/app/elements/gr-date-formatter.html new file mode 100644 index 0000000..55782f6 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-date-formatter.html
@@ -0,0 +1,90 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> + +<dom-module id="gr-date-formatter"> + <template> + <style> + :host { + display: inline; + } + </style> + <span>[[_computeDateStr(dateStr)]]</span> + </template> + <script> + (function() { + 'use strict'; + + var Duration = { + HOUR: 1000 * 60 * 60, + DAY: 1000 * 60 * 60 * 24, + }; + + var ShortMonthNames = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', + 'Nov', 'Dec' + ]; + + Polymer({ + is: 'gr-date-formatter', + + properties: { + dateStr: { + type: String, + value: null, + notify: true + } + }, + + _computeDateStr: function(dateStr) { + return this._dateStr(this._parseDateStr(dateStr), new Date()); + }, + + _parseDateStr: function(dateStr) { + if (!dateStr) { return null; } + return util.parseDate(dateStr); + }, + + _dateStr: function(t, now) { + if (!t) { return ''; } + var diff = now.getTime() - t.getTime(); + if (diff < Duration.DAY && t.getDay() == now.getDay()) { + // Within 24 hours and on the same day: + // '2:14 AM' + var pm = t.getHours() >= 12; + var hours = t.getHours() === 0 ? 12 : + pm ? t.getHours() - 12 : t.getHours(); + var minutes = t.getMinutes() < 10 ? '0' + t.getMinutes() : + t.getMinutes(); + return hours + ':' + minutes + (pm ? ' PM' : ' AM'); + } else if ((t.getDay() != now.getDay() || diff >= Duration.DAY) && + diff < 180 * Duration.DAY) { + // From one to six months: + // 'Aug 29' + return ShortMonthNames[t.getMonth()] + ' ' + t.getDate(); + } else if (diff >= 180 * Duration.DAY) { + // More than six months: + // 'Aug 29, 1997' + return ShortMonthNames[t.getMonth()] + ' ' + t.getDate() + ', ' + + t.getFullYear(); + } + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-diff-comment-thread.html b/polygerrit-ui/app/elements/gr-diff-comment-thread.html new file mode 100644 index 0000000..b908cf7 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-diff-comment-thread.html
@@ -0,0 +1,111 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="gr-diff-comment.html"> + +<dom-module id="gr-diff-comment-thread"> + <template> + <style> + :host { + display: block; + max-width: 50em; + white-space: normal; + } + </style> + <template id="commentList" is="dom-repeat" items="{{_orderedComments}}" as="comment"> + <gr-diff-comment comment="[[comment]]"></gr-diff-comment> + </template> + </template> + <script> + (function() { + 'use strict'; + + + Polymer({ + is: 'gr-diff-comment-thread', + + /** + * Fired when the height of the thread changes. + * + * @event gr-diff-comment-thread-height-changed + */ + + properties: { + comments: { + type: Array, + observer: '_commentsChanged', + }, + _orderedComments: Array, + }, + + ready: function() { + this.addEventListener('gr-diff-comment-height-changed', + this._handleCommentHeightChange.bind(this)); + }, + + _commentsChanged: function(comments) { + this._orderedComments = this._sortedComments(comments); + }, + + _sortedComments: function(comments) { + var comments = comments || []; + comments.sort(function(c1, c2) { + return util.parseDate(c1.updated) - util.parseDate(c2.updated); + }); + + var commentIDToReplies = {}; + var topLevelComments = []; + for (var i = 0; i < comments.length; i++) { + var c = comments[i]; + if (c.in_reply_to) { + if (commentIDToReplies[c.in_reply_to] == null) { + commentIDToReplies[c.in_reply_to] = []; + } + commentIDToReplies[c.in_reply_to].push(c); + } else { + topLevelComments.push(c); + } + } + var results = []; + for (var i = 0; i < topLevelComments.length; i++) { + this._visitComment(topLevelComments[i], commentIDToReplies, results); + } + return results; + }, + + _visitComment: function(parent, commentIDToReplies, results) { + results.push(parent); + + var replies = commentIDToReplies[parent.id]; + if (!replies) { return; } + for (var i = 0; i < replies.length; i++) { + this._visitComment(replies[i], commentIDToReplies, results); + } + }, + + _handleCommentHeightChange: function(e) { + // TODO: This fires for each comment on initialization. Optimize to only + // fire the top level "thread height has changed" event once during + // initial DOM stamp. + this.fire('gr-diff-comment-thread-height-changed', + {height: this.offsetHeight}); + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-diff-comment.html b/polygerrit-ui/app/elements/gr-diff-comment.html new file mode 100644 index 0000000..343f7d3 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-diff-comment.html
@@ -0,0 +1,113 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="gr-date-formatter.html"> + +<dom-module id="gr-diff-comment"> + <template> + <style> + :host { + border: 1px solid #ddd; + display: block; + } + .header, + .message, + .actions { + padding: .5em .7em; + } + .header { + background-color: #eee; + font-family: 'Open Sans', sans-serif; + } + gr-date-formatter { + float: right; + margin-left: 5px; + } + .authorName { + font-weight: bold; + } + .message { + white-space: pre-wrap; + } + .actions { + /** TODO: remove once the actions actually do something. **/ + display: none; + padding-top: 0; + } + </style> + <div class="header" id="header"> + <span class="authorName">[[comment.author.name]]</span> + <gr-date-formatter date-str="[[comment.updated]]"></gr-date-formatter> + </div> + <div class="message">[[comment.message]]</div> + <div class="actions"> + <a class="reply" href="#" on-tap="_handleReply">Reply</a> + <a class="done" href="#" on-tap="_handleDone">Done</a> + </div> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-diff-comment', + + /** + * Fired when the height of the comment changes. + * + * @event gr-diff-comment-height-changed + */ + + /** + * Fired when the Reply action is triggered. + * + * @event gr-diff-comment-reply + */ + + /** + * Fired when the Done action is triggered. + * + * @event gr-diff-comment-done + */ + + properties: { + comment: Object, + draft: { + type: Boolean, + value: false, + }, + }, + + attached: function() { + this.fire('gr-diff-comment-height-changed', + {height: this.offsetHeight}); + }, + + _handleReply: function(e) { + e.preventDefault(); + this.fire('gr-diff-comment-reply'); + }, + + _handleDone: function(e) { + e.preventDefault(); + this.fire('gr-diff-comment-done'); + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-diff-view.html b/polygerrit-ui/app/elements/gr-diff-view.html new file mode 100644 index 0000000..e194b13 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-diff-view.html
@@ -0,0 +1,543 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="gr-diff-comment-thread.html"> + +<dom-module id="gr-diff-view"> + <template> + <style> + :host { + background-color: var(--view-background-color); + display: block; + } + h3 { + margin-top: 1em; + padding: .75em var(--default-horizontal-margin); + } + .mainContainer { + border-bottom: 1px solid #eee; + border-collapse: collapse; + border-top: 1px solid #eee; + width: 100%; + } + .diffNumbers, + .diffContent { + vertical-align: top; + } + .diffContainer { + font-family: 'Source Code Pro', monospace; + white-space: pre; + } + .diffNumbers { + background-color: #eee; + color: #666; + padding: 0 .75em; + text-align: right; + } + .diffContent { + min-width: 80ch; + max-width: 120ch; + overflow: hidden; + } + .diffContainer.leftOnly .diffContent, + .diffContainer.rightOnly .diffContent { + overflow: visible; + } + .diffContainer.leftOnly .right, + .diffContainer.rightOnly .left { + display: none; + } + .ruler { + display: block; + background-color: #ddd; + height: 1.3em; + position: absolute; + top: 0; + width: 1px; + } + .lineNum:before, + .content:before { + /* To ensure the height is non-zero in these elements, a + zero-width space is set as its content. The character + itself doesn't matter. Just that there is something + there. */ + content: '\200B'; + } + .content { + position: relative; + } + .lineNum.blank { + border-right: 2px solid #F34D4D; + margin-right: 3px; + } + .lineNum:not(.blank) { + cursor: pointer; + } + .lineNum:hover { + text-decoration: underline; + } + .lightRed { + background-color: #ffecec; + } + .darkRed { + background-color: #faa; + } + .lightGreen { + background-color: #eaffea; + } + .darkGreen { + background-color: #9f9; + } + </style> + <iron-ajax id="changeDetailXHR" + auto + url="[[_computeChangeDetailPath(_changeNum)]]" + params="[[_computeChangeDetailQueryParams()]]" + json-prefix=")]}'" + last-response="{{_change}}" + debounce-duration="300"></iron-ajax> + <iron-ajax + id="diffXHR" + url="[[_computeDiffPath(_changeNum, _patchNum, _path)]]" + json-prefix=")]}'" + on-response="_handleDiffResponse"></iron-ajax> + <iron-ajax + id="leftCommentsXHR" + url="[[_computeCommentsPath(_changeNum, _basePatchNum)]]" + json-prefix=")]}'" + on-response="_handleLeftCommentsResponse"></iron-ajax> + <iron-ajax + id="rightCommentsXHR" + url="[[_computeCommentsPath(_changeNum, _patchNum)]]" + json-prefix=")]}'" + on-response="_handleRightCommentsResponse"></iron-ajax> + <h3> + <a href$="[[_computeChangePath(_changeNum)]]">[[_changeNum]]</a><span>:</span> + <span>[[_change.subject]]</span> — <span>[[params.path]]</span> + </h3> + <table class="mainContainer"> + <tr class="diffContainer" id="diffContainer"> + <td class="diffNumbers left" id="leftDiffNumbers"></td> + <td class="diffContent left" id="leftDiffContent"></td> + <td class="diffNumbers right" id="rightDiffNumbers"></td> + <td class="diffContent right" id="rightDiffContent"></td> + </tr> + </table> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-diff-view', + + properties: { + /** + * URL params passed from the router. + */ + params: { + type: Object, + observer: '_paramsChanged', + }, + rulerWidth: { + type: Number, + value: 80, + observer: '_rulerWidthChanged', + }, + _change: Object, + _changeNum: String, + _diff: Object, + _basePatchNum: String, + _patchNum: String, + _path: String, + _leftComments: Array, + _rightComments: Array, + _rendered: Boolean, + }, + + listeners: { + 'diffContainer.tap': '_diffContainerTapHandler', + }, + + _paramsChanged: function(value) { + this._changeNum = value.changeNum; + this._patchNum = value.patchNum; + this._basePatchNum = value.basePatchNum; + this._path = value.path; + if (!this._patchNum) { + this._change = null; + this._basePatchNum = null; + this._patchNum = null; + this._diff = null; + this._path = null; + this._leftComments = null; + this._rightComments = null; + this._rendered = false; + return; + } + // Assign the params here since a computed binding relying on + // `_basePatchNum` won't fire in the case where it's not defined. + this.$.diffXHR.params = this._diffQueryParams(this._basePatchNum); + this.$.diffXHR.generateRequest(); + + if (this._basePatchNum) { + this.$.leftCommentsXHR.generateRequest(); + } + this.$.rightCommentsXHR.generateRequest(); + }, + + _rulerWidthChanged: function(newValue, oldValue) { + if (newValue < 0) { + throw Error('ruler width must be greater than zero.'); + } + if (oldValue == 0) { + this._renderRulerElements(); + } + var remove = newValue == 0; + var rulerEls = Polymer.dom(this.root).querySelectorAll('.ruler'); + for (var i = 0; i < rulerEls.length; i++) { + if (remove) { + rulerEls[i].parentNode.removeChild(rulerEls[i]); + } else { + rulerEls[i].style.left = newValue + 'ch'; + } + } + }, + + _computeChangePath: function(changeNum) { + return '/c/' + changeNum; + }, + + _computeChangeDetailPath: function(changeNum) { + return '/changes/' + changeNum + '/detail'; + }, + + _computeChangeDetailQueryParams: function() { + var options = Changes.listChangesOptionsToHex( + Changes.ListChangesOption.ALL_REVISIONS + ); + return { O: options }; + }, + + _computeDiffPath: function(changeNum, patchNum, path) { + return '/changes/' + changeNum + '/revisions/' + patchNum + '/files/' + + encodeURIComponent(path) + '/diff'; + }, + + _computeCommentsPath: function(changeNum, patchNum) { + return '/changes/' + changeNum + '/revisions/' + patchNum + '/comments'; + }, + + _diffQueryParams: function(basePatchNum) { + var params = { + context: 'ALL', + intraline: null + }; + if (!!basePatchNum) { + params.base = basePatchNum; + } + return params; + }, + + _diffContainerTapHandler: function(e) { + var el = e.detail.sourceEvent.target; + if (el.classList.contains('lineNum')) { + // TODO: Implement adding draft comments. + } + }, + + _handleLeftCommentsResponse: function(e, req) { + this._leftComments = e.detail.response[this._path] || []; + this._maybeRenderDiff(this._diff, this._leftComments, + this._rightComments); + }, + + _handleRightCommentsResponse: function(e, req) { + this._rightComments = e.detail.response[this._path] || []; + this._maybeRenderDiff(this._diff, this._leftComments, + this._rightComments); + }, + + _handleDiffResponse: function(e, req) { + this._diff = e.detail.response; + this._maybeRenderDiff(this._diff, this._leftComments, + this._rightComments); + }, + + _threadID: function(patchNum, lineNum) { + return 'thread-' + patchNum + '-' + lineNum; + }, + + _renderComments: function(comments, patchNum) { + // Group the comments by line number. Absense of a line number indicates + // a top-level file comment. + var threads = {}; + + for (var i = 0; i < comments.length; i++) { + var line = comments[i].line || 'FILE'; + if (threads[line] == null) { + threads[line] = [] + } + threads[line].push(comments[i]); + } + for (var lineNum in threads) { + this._addThread(threads[lineNum], patchNum, lineNum); + } + }, + + _addThread: function(comments, patchNum, lineNum) { + var el = document.createElement('gr-diff-comment-thread'); + el.comments = comments; + var threadID = this._threadID(patchNum, lineNum); + el.setAttribute('data-thread-id', threadID); + + // Find the element that the thread should be appended after. In the + // case of a file comment, it will be appended after the first line. + // TODO: Show file comment above the file itself. + var fileComment = lineNum == 'FILE'; + if (fileComment) { + lineNum = 1; + } + var contentEl = this.$$('.content' + + '[data-patch-num="' + patchNum + '"]' + + '[data-line-num="' + lineNum + '"]'); + var rowNum = contentEl.getAttribute('data-row-num'); + el.addEventListener('gr-diff-comment-thread-height-changed', + this._handleCommentThreadHeightChange.bind(this, rowNum, threadID)); + Polymer.dom(contentEl.parentNode).insertBefore( + el, contentEl.nextSibling); + }, + + _handleCommentThreadHeightChange: function(rowNum, threadID, e) { + // Adjust the filler element heights if they're present in the DOM. + var els = Polymer.dom(this.root).querySelectorAll( + '.js-threadFiller[data-thread-id="' + threadID + '"]'); + if (els.length > 0) { + for (var i = 0; i < els.length; i++) { + els[i].style.height = e.detail.height + 'px'; + } + return; + } + + // Create the filler elements if they're not already present. + var els = Polymer.dom(this.root).querySelectorAll( + '[data-row-num="' + rowNum + '"]'); + for (var i = 0; i < els.length; i++) { + // Is this is the side with the comment? Skip if so. + if (els[i].nextSibling && + els[i].nextSibling.tagName == 'GR-DIFF-COMMENT-THREAD') { + continue; + } + var fillerEl = document.createElement('div'); + fillerEl.setAttribute('data-thread-id', threadID); + fillerEl.classList.add('js-threadFiller'); + fillerEl.style.height = e.detail.height + 'px'; + Polymer.dom(els[i].parentNode).insertBefore( + fillerEl, els[i].nextSibling); + } + }, + + _maybeRenderDiff: function(diff, leftComments, rightComments) { + if (this._rendered) { + throw Error('diff has already been rendered'); + } + if (!diff || !diff.content) { return; } + if (this._basePatchNum && leftComments == null) { return; } + if (rightComments == null) { return; } + + this.$.diffContainer.classList.toggle('rightOnly', + diff.change_type == Changes.DiffType.ADDED); + this.$.diffContainer.classList.toggle('leftOnly', + diff.change_type == Changes.DiffType.DELETED); + + var initialLineNum = 0 + (diff.content.skip || 0); + var ctx = { + rowNum: 0, + left: { + lineNum: initialLineNum, + content: '', + cssClass: '', + }, + right: { + lineNum: initialLineNum, + content: '', + cssClass: '', + } + }; + for (var i = 0; i < diff.content.length; i++) { + this._addDiffChunk(ctx, diff.content[i]); + } + + if (leftComments) { + this._renderComments(leftComments, this._basePatchNum); + } + if (rightComments) { + this._renderComments(rightComments, this._patchNum); + } + + if (this.rulerWidth) { + this._renderRulerElements(); + } + + this._rendered = true; + }, + + _addDiffChunk: function(ctx, diffChunk) { + // Simplest case where both sides have the same content. + if (diffChunk.ab) { + for (var i = 0; i < diffChunk.ab.length; i++) { + ctx.left.lineNum++; + ctx.right.lineNum++; + ctx.left.content = ctx.right.content = diffChunk.ab[i]; + ctx.left.cssClass = ctx.right.cssClass = null; + this._addRow(ctx); + } + return; + } + + if (diffChunk.a) { + ctx.left.cssClass = 'lightRed'; + } else { + delete(ctx.left.cssClass); + } + if (diffChunk.b) { + ctx.right.cssClass = 'lightGreen'; + } else { + delete(ctx.right.cssClass); + } + + var aLen = (diffChunk.a && diffChunk.a.length) || 0; + var bLen = (diffChunk.b && diffChunk.b.length) || 0; + var maxLen = Math.max(aLen, bLen); + for (var i = 0; i < maxLen; i++) { + if (diffChunk.a && i < diffChunk.a.length) { + ctx.left.lineNum++; + ctx.left.content = diffChunk.a[i]; + } else { + delete(ctx.left.content); + } + if (diffChunk.b && i < diffChunk.b.length) { + ctx.right.lineNum++; + ctx.right.content = diffChunk.b[i]; + } else { + delete(ctx.right.content); + } + this._addRow(ctx); + } + }, + + _addRow: function(ctx) { + var leftLineNumEl = this._createElement('div', 'lineNum'); + var leftColEl = this._createElement('div', 'content'); + var rightLineNumEl = this._createElement('div', 'lineNum'); + var rightColEl = this._createElement('div', 'content'); + + [leftColEl, + rightColEl, + leftLineNumEl, + rightLineNumEl].forEach(function(el) { + el.setAttribute('data-row-num', ctx.rowNum); + }); + + var self = this; + if (this._basePatchNum) { + [leftLineNumEl, leftColEl].forEach(function(el) { + el.setAttribute('data-patch-num', self._basePatchNum); + }); + } + [rightLineNumEl, rightColEl].forEach(function(el) { + el.setAttribute('data-patch-num', self._patchNum); + }); + + if (ctx.left.content != null) { + leftLineNumEl.textContent = ctx.left.lineNum; + [leftLineNumEl, leftColEl].forEach(function(el) { + el.setAttribute('data-line-num', ctx.left.lineNum); + }); + } else { + leftLineNumEl.classList.add('blank'); + } + if (ctx.right.content != null) { + rightLineNumEl.textContent = ctx.right.lineNum; + [rightLineNumEl, rightColEl].forEach(function(el) { + el.setAttribute('data-line-num', ctx.right.lineNum); + }); + } else { + rightLineNumEl.classList.add('blank'); + } + + // Content must be defined to prevent the HTML from showing 'undefined'. + // Additionally, a newline is used place of an empty string to ensure + // copy/paste works correctly. + ctx.left.content = ctx.left.content || '\n'; + ctx.right.content = ctx.right.content || '\n'; + + if (!!ctx.left.cssClass) { + leftColEl.classList.add(ctx.left.cssClass); + } + if (!!ctx.right.cssClass) { + rightColEl.classList.add(ctx.right.cssClass); + } + + var leftHTML = util.escapeHTML(ctx.left.content); + var rightHTML = util.escapeHTML(ctx.right.content); + + // If the html is equivalent to the text then it didn't get highlighted + // or escaped. Use textContent which is faster than innerHTML. + if (ctx.left.content == leftHTML) { + leftColEl.textContent = ctx.left.content; + } else { + leftColEl.innerHTML = leftHTML; + } + if (ctx.right.content == rightHTML) { + rightColEl.textContent = ctx.right.content; + } else { + rightColEl.innerHTML = rightHTML; + } + + this.$.leftDiffNumbers.appendChild(leftLineNumEl); + this.$.leftDiffContent.appendChild(leftColEl); + this.$.rightDiffNumbers.appendChild(rightLineNumEl); + this.$.rightDiffContent.appendChild(rightColEl); + + ctx.rowNum++; + }, + + _renderRulerElements: function() { + var contentEls = Polymer.dom(this.root).querySelectorAll('.content'); + for (var i = 0; i < contentEls.length; i++) { + var rulerEl = this._createElement('i', 'ruler'); + rulerEl.style.left = this.rulerWidth + 'ch'; + contentEls[i].appendChild(rulerEl); + } + }, + + _createElement: function(tagName, className) { + var el = document.createElement(tagName); + // When Shady DOM is being used, these classes are added to account for + // Polymer's polyfill behavior. In order to guarantee sufficient + // specificity within the CSS rules, these are added to every element. + // Since the Polymer DOM utility functions (which would do this + // automatically) are not being used for performance reasons, this is + // done manually. + el.className = 'style-scope gr-diff-view ' + className; + return el; + }, + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-file-list.html b/polygerrit-ui/app/elements/gr-file-list.html new file mode 100644 index 0000000..1b0e932 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-file-list.html
@@ -0,0 +1,128 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> + +<dom-module id="gr-file-list"> + <template> + <style> + :host { + display: block; + } + .tableContainer { + overflow-x: auto; + } + table { + border-collapse: collapse; + width: 100%; + } + table a { + display: block; + } + td { + padding: 2px 0; + white-space: nowrap; + } + th { + text-align: left; + } + .status { + width: 20px; + } + </style> + <iron-ajax id="xhr" + url="[[_computeFilesURL(changeNum, revision)]]" + json-prefix=")]}'" + on-response="_handleResponse"></iron-ajax> + <div class="tableContainer"> + <table> + <tr> + <th></th> + <th></th> + <th>Path</th> + <th>Stats</th> + </tr> + <template is="dom-repeat" items="{{files}}" as="file"> + <tr> + <td></td> + <td class="status">[[file.status]]</td> + <td class="path"> + <a class="file" + href$="[[_computeDiffURL(changeNum, patchNum, file.__path)]]">[[file.__path]]</a> + </td> + <td> + +<span>[[file.lines_inserted]]</span> lines, + -<span>[[file.lines_deleted]]</span> lines + </td> + </tr> + </template> + </table> + </div> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-file-list', + + properties: { + patchNum: Number, + changeNum: { + type: Number, + observer: '_changeNumOrRevisionChanged', + }, + revision: { + type: String, + observer: '_changeNumOrRevisionChanged', + }, + comments: { + type: Array, + value: [], + }, + }, + + _changeNumOrRevisionChanged: function() { + if (!!this.changeNum && !!this.revision) { + this.$.xhr.generateRequest(); + } + }, + + _computeFilesURL: function(changeNum, revision) { + return '/changes/' + changeNum + '/revisions/' + revision + '/files'; + }, + + _handleResponse: function(e, req) { + var result = e.detail.response; + var paths = Object.keys(result).sort(); + var files = []; + for (var i = 0; i < paths.length; i++) { + var info = result[paths[i]]; + info.__path = paths[i]; + info.lines_inserted = info.lines_inserted || 0; + info.lines_deleted = info.lines_deleted || 0; + files.push(info) + } + this.files = files; + }, + + _computeDiffURL: function(changeNum, patchNum, path) { + return '/c/' + changeNum + '/' + patchNum + '/' + path; + }, + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-message.html b/polygerrit-ui/app/elements/gr-message.html new file mode 100644 index 0000000..d3820c4 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-message.html
@@ -0,0 +1,231 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="gr-date-formatter.html"> + +<dom-module id="gr-message"> + <template> + <style> + :host { + border-top: 1px solid #ddd; + display: block; + position: relative; + } + :host(:not([expanded])) { + cursor: pointer; + } + .avatar { + border-radius: 50%; + position: absolute; + left: var(--default-horizontal-margin); + } + :not(.hideAvatar):not(.showAvatar) .avatar, + .hideAvatar .avatar { + display: none; + } + .collapsed .contentContainer { + color: #777; + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; + } + .showAvatar.expanded .contentContainer { + margin-left: calc(var(--default-horizontal-margin) + 2.5em); + padding: 10px 0; + } + .showAvatar.collapsed .contentContainer { + margin-left: calc(var(--default-horizontal-margin) + 1.75em); + padding: 10px 75px 10px 0; + } + .hideAvatar.collapsed .contentContainer, + .hideAvatar.expanded .contentContainer { + margin-left: 0; + padding: 10px 75px 10px 0; + } + .collapsed .avatar { + top: 8px; + } + .expanded .avatar { + top: 12px; + } + .collapsed .avatar { + height: 1.75em; + width: 1.75em; + } + .expanded .avatar { + height: 2.5em; + width: 2.5em; + } + .name { + font-weight: bold; + } + .collapsed .name, + .collapsed .content, + .collapsed .message { + display: inline; + } + .collapsed .comments { + display: none; + } + .collapsed .name, + .collapsed gr-date-formatter { + color: var(--default-text-color); + } + .expanded .name { + cursor: pointer; + } + .expanded .message, + .expanded .commentMessage { + white-space: pre-wrap; + } + gr-date-formatter { + position: absolute; + right: var(--default-horizontal-margin); + top: 10px; + } + .file { + border-top: 1px solid #ddd; + font-weight: bold; + margin: 10px 0 3px; + padding: 10px 0 5px; + } + .commentContainer { + display: flex; + margin: 5px 0; + } + .lineNum { + margin-right: 10px; + min-width: 75px; + } + .commentMessage { + flex: 1; + } + </style> + <div class$="[[_computeClass(expanded, showAvatar)]]"> + <img class="avatar" src$="[[_computeAvatarURL(message.author)]]"> + <div class="contentContainer"> + <div class="name" id="name">[[message.author.name]]</div> + <div class="content"> + <div class="message">[[message.message]]</div> + <div class="comments"> + <template is="dom-repeat" items="{{files}}" as="file"> + <div class="file"> + <a href$="[[_computeFileDiffURL(file, changeNum, message._revision_number)]]">[[file]]</a>: + </div> + <template is="dom-repeat" + items="[[_computeCommentsForFile(file)]]" as="comment"> + <div class="commentContainer"> + <a class="lineNum" + href$="[[_computeDiffLineURL(file, changeNum, message._revision_number, comment)]]"> + <template is="dom-if" if="[[comment.line]]"> + Line <span>[[comment.line]]</span>: + </template> + <template is="dom-if" if="[[!comment.line]]"> + File comment: + </template> + </a> + <div class="commentMessage">[[comment.message]]</div> + </div> + </template> + </template> + </div> + </div> + <gr-date-formatter date-str="[[message.date]]"></gr-date-formatter> + </div> + </div> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-message', + + listeners: { + 'tap': '_tapHandler', + 'name.tap': '_collapseHandler', + }, + + properties: { + changeNum: Number, + message: Object, + comments: { + type: Object, + observer: '_commentsChanged', + }, + expanded: { + type: Boolean, + value: true, + reflectToAttribute: true, + }, + showAvatar: Boolean, + }, + + ready: function() { + app.configReady.then(function(cfg) { + this.showAvatar = cfg && cfg.plugin && cfg.plugin.has_avatars; + }.bind(this)); + }, + + _commentsChanged: function(value) { + this.files = Object.keys(value || {}).sort(); + this.expanded = this.files.length > 0; + }, + + _computeFileDiffURL: function(file, changeNum, patchNum) { + return '/c/' + changeNum + '/' + patchNum + '/' + file; + }, + + _computeDiffLineURL: function(file, changeNum, patchNum, comment) { + var diffURL = this._computeFileDiffURL(file, changeNum, patchNum); + if (comment.line) { + diffURL += '#' + comment.line; + } + return diffURL; + }, + + _computeCommentsForFile: function(file) { + return this.comments[file]; + }, + + _tapHandler: function(e) { + if (this.expanded) { return; } + this.expanded = true; + }, + + _collapseHandler: function(e) { + if (!this.expanded) { return; } + e.stopPropagation(); + this.expanded = false; + }, + + _computeClass: function(expanded, showAvatar) { + var classes = []; + classes.push(expanded ? 'expanded' : 'collapsed'); + classes.push(showAvatar ? 'showAvatar' : 'hideAvatar'); + return classes.join(' '); + }, + + _computeAvatarURL: function(author) { + if (!author) { return '' } + return '/accounts/' + author.email + '/avatar?s=100'; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-messages-list.html b/polygerrit-ui/app/elements/gr-messages-list.html new file mode 100644 index 0000000..bdf1fe4 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-messages-list.html
@@ -0,0 +1,85 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="gr-message.html"> + +<dom-module id="gr-messages-list"> + <template> + <style> + :host { + display: block; + } + h3 { + margin-bottom: .35em; + } + h3, + gr-message { + padding: 0 var(--default-horizontal-margin); + } + </style> + <h3>Messages</h3> + <template is="dom-repeat" items="{{messages}}" as="message"> + <gr-message change-num="[[changeNum]]" + message="[[message]]" + comments="[[_computeCommentsForMessage(comments, message, index)]]"></gr-message> + </template> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-messages-list', + + properties: { + changeNum: Number, + messages: { + type: Array, + value: [], + }, + comments: Object, + }, + + _computeCommentsForMessage: function(comments, message, index) { + var comments = comments || {}; + var messages = this.messages || []; + var msgComments = {}; + var mDate = util.parseDate(message.date); + var nextMDate; + if (index < messages.length - 1) { + nextMDate = util.parseDate(messages[index + 1].date); + } + for (var file in comments) { + var fileComments = comments[file]; + for (var i = 0; i < fileComments.length; i++) { + var cDate = util.parseDate(fileComments[i].updated); + if (cDate >= mDate) { + if (nextMDate && cDate >= nextMDate) { + continue; + } + msgComments[file] = msgComments[file] || []; + msgComments[file].push(fileComments[i]); + } + } + } + return msgComments; + }, + + }); + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-search-bar.html b/polygerrit-ui/app/elements/gr-search-bar.html new file mode 100644 index 0000000..d9ad1a3 --- /dev/null +++ b/polygerrit-ui/app/elements/gr-search-bar.html
@@ -0,0 +1,93 @@ +<!-- +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. +--> + +<link rel="import" href="../bower_components/polymer/polymer.html"> +<link rel="import" href="../bower_components/iron-input/iron-input.html"> + +<dom-module id="gr-search-bar"> + <template> + <style> + :host { + display: block; + } + form { + display: flex; + margin-left: 3em; + } + input, + button { + border: 1px solid #aaa; + font: inherit; + padding: .2em .5em; + } + input { + flex: 1; + border-radius: 2px 0 0 2px; + } + button { + background-color: #f1f2f3; + border-radius: 0 2px 2px 0; + border-left-width: 0; + } + </style> + <form> + <input is="iron-input" id="searchInput" bind-value="{{_inputVal}}"> + <button type="submit" id="searchButton">Search</button> + </form> + </template> + <script> + (function() { + 'use strict'; + + Polymer({ + is: 'gr-search-bar', + + listeners: { + 'searchInput.keydown': '_inputKeyDownHandler', + 'searchButton.tap': '_preventDefaultAndNavigateToInputVal', + }, + + properties: { + value: { + type: String, + value: '', + notify: true, + observer: '_valueChanged', + }, + _inputVal: String, + }, + + _valueChanged: function(value) { + this._inputVal = value; + }, + + _inputKeyDownHandler: function(e) { + if (e.keyCode == 13) { + // Enter was pressed. + this._preventDefaultAndNavigateToInputVal(e); + } + }, + + _preventDefaultAndNavigateToInputVal: function(e) { + e.preventDefault(); + page.show('/q/' + this._inputVal); + }, + + }); + + })(); + </script> +</dom-module>
diff --git a/polygerrit-ui/app/favicon.ico b/polygerrit-ui/app/favicon.ico new file mode 100644 index 0000000..155217b --- /dev/null +++ b/polygerrit-ui/app/favicon.ico Binary files differ
diff --git a/polygerrit-ui/app/index.html b/polygerrit-ui/app/index.html new file mode 100644 index 0000000..9dc9b2b --- /dev/null +++ b/polygerrit-ui/app/index.html
@@ -0,0 +1,44 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<html lang="en"> +<meta charset="utf-8"> +<meta name="description" content="Gerrit Code Review"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>PolyGerrit</title> + +<!-- build:css /styles/main.css --> +<link rel="stylesheet" href="/styles/main.css"> +<!-- endbuild--> + +<!-- build:js /bower_components/webcomponentsjs/webcomponents-lite.min.js --> +<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script> +<!-- endbuild --> + +<!-- Use Shadow DOM where supported. + https://www.polymer-project.org/1.0/docs/devguide/settings.html --> +<!--<script> +window.Polymer = window.Polymer || {}; +window.Polymer.dom = 'shadow'; +</script>--> + +<!-- will be replaced with elements/gr-app.vulcanized.html --> +<link rel="import" href="/elements/gr-app.html"> +<!-- endreplace--> + +<body unresolved> +<gr-app id="app"></gr-app>
diff --git a/polygerrit-ui/app/polygerrit_wct_tests.py b/polygerrit-ui/app/polygerrit_wct_tests.py new file mode 100644 index 0000000..571dcb8 --- /dev/null +++ b/polygerrit-ui/app/polygerrit_wct_tests.py
@@ -0,0 +1,105 @@ +# 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. + +from __future__ import print_function + +import atexit +from distutils import spawn +import json +import os +import pkg_resources +import shlex +import shutil +import subprocess +import sys +import tempfile +import unittest +import zipfile + + +def _write_wct_conf(root, exports): + with open(os.path.join(root, 'wct.conf.js'), 'w') as f: + f.write('module.exports = %s;\n' % json.dumps(exports)) + + +def _wct_cmd(): + return ['wct'] + shlex.split(os.environ.get('WCT_ARGS', '')) + + +class PolyGerritWctTests(unittest.TestCase): + + # Should really be setUpClass/tearDownClass, but Buck's test runner doesn't + # produce sane stack traces from those methods. There's only one test method + # anyway, so just use setUp. + + def _check_wct(self): + self.assertTrue( + spawn.find_executable('wct'), + msg='wct not found; try `npm install -g web-component-tester`') + + def _extract_resources(self): + tmpdir = tempfile.mkdtemp() + atexit.register(lambda: shutil.rmtree(tmpdir)) + root = os.path.join(tmpdir, 'polygerrit') + os.mkdir(root) + + tr = 'test_resources.zip' + zip_path = os.path.join(tmpdir, tr) + s = pkg_resources.resource_stream(__name__, tr) + with open(zip_path, 'w') as f: + shutil.copyfileobj(s, f) + + with zipfile.ZipFile(zip_path, 'r') as z: + z.extractall(root) + + return tmpdir, root + + def test_wct(self): + self._check_wct() + tmpdir, root = self._extract_resources() + + cmd = _wct_cmd() + print('Running %s in %s' % (cmd, root), file=sys.stderr) + + _write_wct_conf(root, { + 'suites': ['test'], + 'webserver': { + 'pathMappings': [ + {'/components/bower_components': 'bower_components'}, + ], + }, + 'plugins': { + 'local': { + # For some reason wct tries to install selenium into its node_modules + # directory on first run. If you've installed into /usr/local and + # aren't running wct as root, you're screwed. Turning this option off + # seems to still work, so there's that. + 'skipSeleniumInstall': True, + }, + }, + }) + + p = subprocess.Popen(cmd, cwd=root, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + sys.stdout.write(out) + sys.stderr.write(err) + self.assertEquals(0, p.returncode) + + # Only remove tmpdir if successful, to allow debugging. + shutil.rmtree(tmpdir) + + +if __name__ == '__main__': + unittest.main()
diff --git a/polygerrit-ui/app/robots.txt b/polygerrit-ui/app/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/polygerrit-ui/app/robots.txt
@@ -0,0 +1,2 @@ +User-agent: * +Disallow:
diff --git a/polygerrit-ui/app/scripts/app.js b/polygerrit-ui/app/scripts/app.js new file mode 100644 index 0000000..b7c49cf --- /dev/null +++ b/polygerrit-ui/app/scripts/app.js
@@ -0,0 +1,91 @@ +// 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. + +'use strict'; + +// Polymer makes `app` intrinsically defined on the window by virtue of the +// custom element having the id "app", but it is made explicit here. +var app = document.querySelector('#app'); + +window.addEventListener('WebComponentsReady', function() { + // Middleware + page(function(ctx, next) { + document.body.scrollTop = 0; + next(); + }); + + function loadUser(ctx, next) { + app.accountReady.then(function() { + next(); + }); + } + + // Routes. + page('/', loadUser, function() { + if (app.loggedIn) { + page.redirect('/dashboard/self'); + } else { + page.redirect('/q/status:open'); + } + }); + + page('/dashboard/*', loadUser, function(data) { + if (app.loggedIn) { + app.route = 'gr-dashboard-view'; + app.params = data.params; + } else { + page.redirect('/login/' + encodeURIComponent(data.canonicalPath)); + } + }); + + function queryHandler(data) { + app.route = 'gr-change-list-view'; + app.params = data.params; + } + + page('/q/:query,:offset', queryHandler); + page('/q/:query', queryHandler); + + page(/^\/(\d+)\/?/, function(ctx) { + page.redirect('/c/' + ctx.params[0]); + }); + + page('/c/:changeNum', function(data) { + app.route = 'gr-change-view'; + app.params = data.params; + }); + + page(/^\/c\/(\d+)\/((\d+)(\.\.(\d+))?)\/(.+)/, function(ctx) { + app.route = 'gr-diff-view'; + var params = { + changeNum: ctx.params[0], + basePatchNum: ctx.params[2], + patchNum: ctx.params[4], + path: ctx.params[5] + }; + // Don't allow diffing the same patch number against itself because WHY? + if (params.basePatchNum == params.patchNum) { + page.redirect('/c/' + params.changeNum + '/' + params.patchNum + '/' + + params.path); + return; + } + if (!params.patchNum) { + params.patchNum = params.basePatchNum; + delete(params.basePatchNum); + } + app.params = params; + }); + + page.start(); +});
diff --git a/polygerrit-ui/app/scripts/changes.js b/polygerrit-ui/app/scripts/changes.js new file mode 100644 index 0000000..8917c68 --- /dev/null +++ b/polygerrit-ui/app/scripts/changes.js
@@ -0,0 +1,82 @@ +// 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. + +'use strict'; + +var Changes = Changes || {}; + +Changes.DiffType = { + ADDED: 'ADDED', + COPIED: 'COPIED', + DELETED: 'DELETED', + MODIFIED: 'MODIFIED', + RENAMED: 'RENAMED', + REWRITE: 'REWRITE', +}; + +// Must be kept in sync with the ListChangesOption enum and protobuf. +Changes.ListChangesOption = { + LABELS: 0, + DETAILED_LABELS: 8, + + // Return information on the current patch set of the change. + CURRENT_REVISION: 1, + ALL_REVISIONS: 2, + + // If revisions are included, parse the commit object. + CURRENT_COMMIT: 3, + ALL_COMMITS: 4, + + // If a patch set is included, include the files of the patch set. + CURRENT_FILES: 5, + ALL_FILES: 6, + + // If accounts are included, include detailed account info. + DETAILED_ACCOUNTS: 7, + + // Include messages associated with the change. + MESSAGES: 9, + + // Include allowed actions client could perform. + CURRENT_ACTIONS: 10, + + // Set the reviewed boolean for the caller. + REVIEWED: 11, + + // Include download commands for the caller. + DOWNLOAD_COMMANDS: 13, + + // Include patch set weblinks. + WEB_LINKS: 14, + + // Include consistency check results. + CHECK: 15, + + // Include allowed change actions client could perform. + CHANGE_ACTIONS: 16, + + // Include a copy of commit messages including review footers. + COMMIT_FOOTERS: 17, + + // Include push certificate information along with any patch sets. + PUSH_CERTIFICATES: 18 +}; + +Changes.listChangesOptionsToHex = function() { + var v = 0; + for (var i = 0; i < arguments.length; i++) { + v |= 1 << arguments[i]; + } + return v.toString(16); +};
diff --git a/polygerrit-ui/app/scripts/fake-app.js b/polygerrit-ui/app/scripts/fake-app.js new file mode 100644 index 0000000..87e2d04 --- /dev/null +++ b/polygerrit-ui/app/scripts/fake-app.js
@@ -0,0 +1,28 @@ +// 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. + +'use strict'; + +/** + * A stub of the global gr-app element. Use this for testing. + */ +var app = { + accountReady: { + then: function(cb) { cb(); }, + }, + configReady: { + then: function(cb) { cb(); }, + }, + loggedIn: false, +};
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js new file mode 100644 index 0000000..400ed86 --- /dev/null +++ b/polygerrit-ui/app/scripts/util.js
@@ -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. + +'use strict'; + +var util = util || {}; + +util.parseDate = function(dateStr) { + // Timestamps are given in UTC and have the format + // "'yyyy-mm-dd hh:mm:ss.fffffffff'" where "'ffffffffff'" represents + // nanoseconds. + // Munge the date into an ISO 8061 format and parse that. + return new Date(dateStr.replace(' ', 'T') + 'Z'); +}; + +util.htmlEntityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' +}; + +util.escapeHTML = function(str) { + return str.replace(/[&<>"'\/]/g, function(s) { + return util.htmlEntityMap[s]; + }); +};
diff --git a/polygerrit-ui/app/styles/app-theme.html b/polygerrit-ui/app/styles/app-theme.html new file mode 100644 index 0000000..dbd544c --- /dev/null +++ b/polygerrit-ui/app/styles/app-theme.html
@@ -0,0 +1,26 @@ +<!-- +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. +--> +<style is="custom-style"> +:root { + --primary-color: #fff; + --primary-text-color: #000; + --search-border-color: #ddd; + --secondary-color: #f1f2f3; + --default-text-color: #000; + --view-background-color: #fff; + --default-horizontal-margin: 1.25rem; +} +</style>
diff --git a/polygerrit-ui/app/styles/main.css b/polygerrit-ui/app/styles/main.css new file mode 100644 index 0000000..961fbe9 --- /dev/null +++ b/polygerrit-ui/app/styles/main.css
@@ -0,0 +1,50 @@ +/* +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. +*/ + +@import "//fonts.googleapis.com/css?family=Open+Sans:400,700"; +@import "//fonts.googleapis.com/css?family=Source+Code+Pro"; + +*, +*::after, +*::before { + box-sizing: border-box; + margin: 0; + padding: 0; +} +html, +body { + height: 100%; + transition: none; /* Override the default Polymer fade-in. */ +} +body { + font: 11px 'Open Sans', sans-serif; +} + +@media only screen and (min-width: 1240px) { + body { + font-size: 12px; + } +} +@media only screen and (min-width: 1340px) { + body { + font-size: 13px; + } +} +@media only screen and (min-width: 1450px) { + body { + font-size: 14px; + } +}
diff --git a/polygerrit-ui/app/test/gr-change-list-item-test.html b/polygerrit-ui/app/test/gr-change-list-item-test.html new file mode 100644 index 0000000..1e968b5 --- /dev/null +++ b/polygerrit-ui/app/test/gr-change-list-item-test.html
@@ -0,0 +1,90 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-change-list-item</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> +<script src="../scripts/fake-app.js"></script> + +<link rel="import" href="../elements/gr-change-list-item.html"> + +<test-fixture id="basic"> + <template> + <gr-change-list-item></gr-change-list-item> + </template> +</test-fixture> + +<script> + suite('gr-change-list-item tests', function() { + var element; + + setup(function() { + element = fixture('basic'); + }); + + test('computed fields', function() { + assert.equal(element._computeChangeStatusString({mergeable: true}), ''); + assert.equal(element._computeChangeStatusString({mergeable: false}), + 'Merge Conflict'); + + assert.equal(element._computeCodeReviewClass(), ''); + assert.equal(element._computeCodeReviewClass({}), ''); + assert.equal(element._computeCodeReviewClass({approved: true, value: 1}), + 'u-green'); + assert.equal(element._computeCodeReviewClass({value: 1}), + 'u-monospace u-green'); + assert.equal(element._computeCodeReviewClass({value: -1}), + 'u-monospace u-red'); + + assert.equal(element._computeCodeReviewLabel(), ''); + assert.equal(element._computeCodeReviewLabel({}), ''); + assert.equal(element._computeCodeReviewLabel({approved: true, value: 1}), + '✓'); + assert.equal(element._computeCodeReviewLabel({value: 1}), '+1'); + assert.equal(element._computeCodeReviewLabel({value: -1}), '-1'); + + assert.equal(element._computeVerifiedLabel(), ''); + assert.equal(element._computeVerifiedLabel({}), ''); + assert.equal(element._computeVerifiedLabel({approved: true}), '✓'); + + + assert.equal(element._computeOwnerLink('andybons+gerrit@gmail.com'), + '/q/owner:andybons%2Bgerrit%40gmail.com+status:open'); + + assert.equal(element._computeOwnerTitle( + { + name: 'Andrew Bonventre', + email: 'andybons+gerrit@gmail.com' + }), + 'Andrew Bonventre <andybons+gerrit@gmail.com>'); + + // TODO(andybons): _computeProjectURL once it's not a constant. + + assert.equal(element._computeProjectBranchURL( + 'combustible-stuff', 'lemons'), + '/q/status:open+project:combustible-stuff+branch:lemons'); + + element.change = { _number: 42 }; + assert.equal(element.changeURL, '/c/42/'); + element.change = { _number: 43 }; + assert.equal(element.changeURL, '/c/43/'); + }); + + }); +</script>
diff --git a/polygerrit-ui/app/test/gr-change-list-test.html b/polygerrit-ui/app/test/gr-change-list-test.html new file mode 100644 index 0000000..c029cfe --- /dev/null +++ b/polygerrit-ui/app/test/gr-change-list-test.html
@@ -0,0 +1,73 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-change-list</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> +<script src="../scripts/fake-app.js"></script> + +<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html"> +<link rel="import" href="../elements/gr-change-list.html"> + +<test-fixture id="basic"> + <template> + <gr-change-list></gr-change-list> + </template> +</test-fixture> + +<script> + suite('gr-change-list tests', function() { + var changeList; + + setup(function() { + changeList = fixture('basic'); + }); + + test('keyboard shortcuts', function() { + changeList.changes = [ + {_number: 0}, + {_number: 1}, + {_number: 2}, + ]; + flushAsynchronousOperations(); + var changeListItems = Polymer.dom(changeList.root).querySelectorAll( + 'gr-change-list-item:not([header])'); + assert.equal(changeListItems.length, 3); + assert.equal(changeList.selectedIndex, 0); + + MockInteractions.pressAndReleaseKeyOn(changeList, 74); // 'j' + flushAsynchronousOperations(); + assert.equal(changeList.selectedIndex, 1); + MockInteractions.pressAndReleaseKeyOn(changeList, 74); // 'j' + flushAsynchronousOperations(); + assert.equal(changeList.selectedIndex, 2); + MockInteractions.pressAndReleaseKeyOn(changeList, 75); // 'k' + flushAsynchronousOperations(); + assert.equal(changeList.selectedIndex, 1); + MockInteractions.pressAndReleaseKeyOn(changeList, 75); // 'k' + flushAsynchronousOperations(); + MockInteractions.pressAndReleaseKeyOn(changeList, 75); // 'k' + flushAsynchronousOperations(); + MockInteractions.pressAndReleaseKeyOn(changeList, 75); // 'k' + flushAsynchronousOperations(); + assert.equal(changeList.selectedIndex, 0); + }); + + }); +</script>
diff --git a/polygerrit-ui/app/test/gr-date-formatter-test.html b/polygerrit-ui/app/test/gr-date-formatter-test.html new file mode 100644 index 0000000..dca3e9d --- /dev/null +++ b/polygerrit-ui/app/test/gr-date-formatter-test.html
@@ -0,0 +1,81 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-date-formatter</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> +<script src="../scripts/util.js"></script> + +<link rel="import" href="../elements/gr-date-formatter.html"> + +<test-fixture id="basic"> + <template> + <gr-date-formatter date-str="2015-09-24 23:30:17.033000000"></gr-date-formatter> + </template> +</test-fixture> + +<script> + suite('gr-date-formatter tests', function() { + var element; + + setup(function() { + element = fixture('basic'); + }); + + test('date is parsed correctly', function() { + assert.notOk((new Date('foo')).valueOf()); + var d = element._parseDateStr(element.getAttribute('date-str')); + assert.isAbove(d.valueOf(), 0); + }); + + function normalizedDate(dateStr) { + var d = new Date(dateStr); + d.setMinutes(d.getMinutes() + d.getTimezoneOffset()); + return d; + } + + function testDates(nowStr, dateStr, expected) { + var now = normalizedDate(nowStr); + var t = normalizedDate(dateStr); + assert.equal(element._dateStr(t, now), expected); + } + + test('dates strings are correct', function() { + // Within 24 hours on same day. + testDates('2015-07-29T20:34:00.000Z', + '2015-07-29T15:34:00.000Z', + '3:34 PM'); + + // Within 24 hours on different days. + testDates('2015-07-29T03:34:00.000Z', + '2015-07-28T20:25:00.000Z', + 'Jul 28'); + + // More than 24 hours. Less than six months. + testDates('2015-07-29T20:34:00.000Z', + '2015-06-15T03:25:00.000Z', + 'Jun 15'); + + // More than six months. + testDates('2015-09-15T20:34:00.000Z', + '2015-01-15T03:25:00.000Z', + 'Jan 15, 2015'); + }); + }); +</script>
diff --git a/polygerrit-ui/app/test/gr-diff-comment-test.html b/polygerrit-ui/app/test/gr-diff-comment-test.html new file mode 100644 index 0000000..4c2b8c2 --- /dev/null +++ b/polygerrit-ui/app/test/gr-diff-comment-test.html
@@ -0,0 +1,55 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-diff-comment</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> + +<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html"> +<link rel="import" href="../elements/gr-diff-comment.html"> + +<test-fixture id="basic"> + <template> + <gr-diff-comment></gr-diff-comment> + </template> +</test-fixture> + +<script> + suite('gr-diff-comment tests', function() { + var element; + setup(function() { + element = fixture('basic'); + }); + + test('proper event fires on reply', function(done) { + element.addEventListener('gr-diff-comment-reply', function(e) { + done(); + }); + MockInteractions.tap(element.$$('.reply')); + }); + + test('proper event fires on done', function(done) { + element.addEventListener('gr-diff-comment-done', function(e) { + done(); + }); + MockInteractions.tap(element.$$('.done')); + }); + + }); +</script>
diff --git a/polygerrit-ui/app/test/gr-diff-comment-thread-test.html b/polygerrit-ui/app/test/gr-diff-comment-thread-test.html new file mode 100644 index 0000000..7032712 --- /dev/null +++ b/polygerrit-ui/app/test/gr-diff-comment-thread-test.html
@@ -0,0 +1,116 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-diff-comment-thread</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> +<script src="../scripts/util.js"></script> + +<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html"> +<link rel="import" href="../elements/gr-diff-comment-thread.html"> + +<test-fixture id="basic"> + <template> + <gr-diff-comment-thread></gr-diff-comment-thread> + </template> +</test-fixture> + +<script> + suite('gr-diff-comment-thread tests', function() { + var element; + setup(function() { + element = fixture('basic'); + }); + + test('comments are sorted correctly', function() { + var comments = [ + { + id: 'jacks_reply', + message: 'i like you, too', + in_reply_to: 'sallys_confession', + updated: '2015-12-25 15:00:20.396000000', + }, + { + id: 'sallys_confession', + message: 'i like you, jack', + updated: '2015-12-24 15:00:20.396000000', + }, + { + id: 'sally_to_dr_finklestein', + message: 'i’m running away', + updated: '2015-10-31 09:00:20.396000000', + }, + { + id: 'sallys_defiance', + in_reply_to: 'sally_to_dr_finklestein', + message: 'i will poison you so i can get away', + updated: '2015-10-31 15:00:20.396000000', + }, + { + id: 'dr_finklesteins_response', + in_reply_to: 'sally_to_dr_finklestein', + message: 'no i will pull a thread and your arm will fall off', + updated: '2015-10-31 11:00:20.396000000' + }, + { + id: 'sallys_mission', + message: 'i have to find santa', + updated: '2015-12-24 21:00:20.396000000' + } + ]; + var results = element._sortedComments(comments); + assert.deepEqual(results, [ + { + id: 'sally_to_dr_finklestein', + message: 'i’m running away', + updated: '2015-10-31 09:00:20.396000000', + }, + { + id: 'dr_finklesteins_response', + in_reply_to: 'sally_to_dr_finklestein', + message: 'no i will pull a thread and your arm will fall off', + updated: '2015-10-31 11:00:20.396000000' + }, + { + id: 'sallys_defiance', + in_reply_to: 'sally_to_dr_finklestein', + message: 'i will poison you so i can get away', + updated: '2015-10-31 15:00:20.396000000', + }, + { + id: 'sallys_confession', + message: 'i like you, jack', + updated: '2015-12-24 15:00:20.396000000', + }, + { + id: 'jacks_reply', + message: 'i like you, too', + in_reply_to: 'sallys_confession', + updated: '2015-12-25 15:00:20.396000000', + }, + { + id: 'sallys_mission', + message: 'i have to find santa', + updated: '2015-12-24 21:00:20.396000000' + } + ]); + }); + + }); +</script>
diff --git a/polygerrit-ui/app/test/gr-diff-view-test.html b/polygerrit-ui/app/test/gr-diff-view-test.html new file mode 100644 index 0000000..f7a5575 --- /dev/null +++ b/polygerrit-ui/app/test/gr-diff-view-test.html
@@ -0,0 +1,223 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-diff-view</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> +<script src="../scripts/changes.js"></script> +<script src="../scripts/util.js"></script> + +<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html"> +<link rel="import" href="../elements/gr-diff-view.html"> + +<test-fixture id="basic"> + <template> + <gr-diff-view ruler-width="0"></gr-diff-view> + </template> +</test-fixture> + +<test-fixture id="comments"> + <template> + <gr-diff-view></gr-diff-view> + </template> +</test-fixture> + + +<script> + suite('gr-diff-view tests', function() { + var element; + var elWithComments; + // Original diff: + // Left side (side A): + // 1: if i < 5 { // "comments" &= \'fun\' && true + // 2: println("i is less than five") + // 3: } + // 4: + // 5: + // 6: // Comment + // 7: foo + // 8: bar + // 9: Bad news: combustible lemons failed. + // + // Right side (side B): + // 1: if i < 5 { // "comments" &= \'fun\' && true + // 2: println("i is less than five") + // 3: } + // 4: + // 5: + // 6: // Comment + // 7: baz + // 8: Bad news: combustible lemons failed. + // + var diffContent = [ + { + ab: [ + 'if i < 5 { // "comments" &= \'fun\' && true', + ' println("i is less than five")', + '}', + '', + '', + '// Comment' + ] + }, + { + a: [ + 'foo', + 'bar' + ], + b: [ + 'baz', + ] + }, + { + ab: [ + 'Bad news: combustible lemons failed.' + ] + } + ]; + setup(function() { + element = fixture('basic'); + element._maybeRenderDiff({content: diffContent}, [], []); + flushAsynchronousOperations(); + + elWithComments = fixture('comments'); + elWithComments._patchNum = 1; + }); + + test('ab content is the same for left and right sides', function() { + for (var i = 0; i < diffContent[0].ab.length; i++) { + var leftEls = Polymer.dom(element.root).querySelectorAll( + '#leftDiffContent .content[data-line-num="' + (i + 1) + '"]'); + assert.equal(leftEls.length, 1); + var rightEls = Polymer.dom(element.root).querySelectorAll( + '#rightDiffContent .content[data-line-num="' + (i + 1) + '"]'); + assert.equal(rightEls.length, 1); + assert.equal(leftEls[0].textContent, rightEls[0].textContent); + } + }); + + test('all line number and content elements have same (non-zero) height', + function() { + var els = Polymer.dom(element.root).querySelectorAll('.lineNum, .content'); + assert.isAbove(els.length, 0); + var offsetHeight = els.length > 0 && els[0].offsetHeight; + assert.isAbove(offsetHeight, 0); + for (var i = 0; i < els.length; i++) { + assert.equal(offsetHeight, els[i].offsetHeight); + } + }); + + test('content is properly escaped', function() { + var firstLineEls = Polymer.dom(element.root).querySelectorAll( + '#leftDiffContent .content[data-line-num="1"], ' + + '#rightDiffContent .content[data-line-num="1"]'); + assert.equal(2, firstLineEls.length); + for (var i = 0; i < firstLineEls.length; i++) { + assert.equal(firstLineEls[i].innerHTML, + 'if i < 5 { // "comments" &= \'fun\' && true'); + } + }); + + test('content and line numbers are correct for a/b edit', function() { + assert.equal(element.$$( + '#leftDiffContent .content[data-line-num="7"]').textContent, 'foo'); + assert.equal(element.$$( + '#leftDiffContent .content[data-line-num="8"]').textContent, 'bar'); + assert.equal(element.$$( + '#rightDiffContent .content[data-line-num="7"]').textContent, 'baz'); + assert.equal(element.$$( + '#leftDiffContent .content[data-line-num="9"]').textContent, + element.$$( + '#rightDiffContent .content[data-line-num="8"]').textContent); + }); + + test('ruler width changes are applied correctly', function() { + assert.equal(element.rulerWidth, 0); + assert.equal(Polymer.dom(element.root).querySelectorAll('.ruler').length, + 0); + element.rulerWidth = 80; + flushAsynchronousOperations(); + var els = Polymer.dom(element.root).querySelectorAll('.ruler'); + assert.isAbove(els.length, 0); + for (var i = 0; i < els.length; i++) { + assert.equal(els[i].style.left, '80ch'); + } + element.rulerWidth = 0; + flushAsynchronousOperations(); + assert.equal(Polymer.dom(element.root).querySelectorAll('.ruler').length, + 0); + element.rulerWidth = 100; + flushAsynchronousOperations(); + els = Polymer.dom(element.root).querySelectorAll('.ruler'); + assert.isAbove(els.length, 0); + for (var i = 0; i < els.length; i++) { + assert.equal(els[i].style.left, '100ch'); + } + }); + + test('comment threads are rendered correctly', function(done) { + elWithComments._maybeRenderDiff({content: diffContent}, [], [ + { + id: 'file_comment', + message: 'this is a file comment about the meaninglessness of life', + author: { + name: 'GLaDOS' + } + }, + { + id: 'all_the_lemons', + line: 8, + message: 'MAKE LIFE TAKE THE LEMONS BACK', + author: { + name: 'Cave Johnson', + }, + } + ]); + + // On WebKit and Gecko, flushAsynchronousOperations isn't enough to allow + // the thread filler elements to properly render. Wait for the resize + // events that trigger their addition and check after the expected number + // come in. + var numEventsFired = 0; + elWithComments.addEventListener('gr-diff-comment-thread-height-changed', + function() { + numEventsFired++; + if (numEventsFired < 2) { return; } + assert.equal(numEventsFired, 2); + + var threadEls = Polymer.dom(elWithComments.root).querySelectorAll( + 'gr-diff-comment-thread[data-thread-id="thread-1-8"]'); + assert.equal(threadEls.length, 1); + var fillerEls = Polymer.dom(elWithComments.root).querySelectorAll( + '.js-threadFiller[data-thread-id="thread-1-8"]'); + assert.equal(fillerEls.length, 3); + + threadEls = Polymer.dom(elWithComments.root).querySelectorAll( + 'gr-diff-comment-thread[data-thread-id="thread-1-FILE"]'); + assert.equal(threadEls.length, 1); + fillerEls = Polymer.dom(elWithComments.root).querySelectorAll( + '.js-threadFiller[data-thread-id="thread-1-FILE"]'); + assert.equal(fillerEls.length, 3); + + done(); + }); + }); + + }); +</script>
diff --git a/polygerrit-ui/app/test/gr-search-bar-test.html b/polygerrit-ui/app/test/gr-search-bar-test.html new file mode 100644 index 0000000..d21235f --- /dev/null +++ b/polygerrit-ui/app/test/gr-search-bar-test.html
@@ -0,0 +1,63 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>gr-search-bar</title> + +<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> + +<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html"> +<link rel="import" href="../elements/gr-search-bar.html"> + +<test-fixture id="basic"> + <template> + <gr-search-bar></gr-search-bar> + </template> +</test-fixture> + +<script> + suite('gr-search-bar tests', function() { + var element; + + setup(function() { + element = fixture('basic'); + }); + + test('value is propagated to _inputVal', function() { + element.value = 'foo'; + assert.equal(element._inputVal, 'foo'); + }) + + test('tap on search button triggers nav', function(done) { + sinon.stub(element, '_preventDefaultAndNavigateToInputVal', function() { + element._preventDefaultAndNavigateToInputVal.restore(); + done(); + }); + MockInteractions.tap(element.$.searchButton); + }); + + test('enter in search input triggers nav', function(done) { + sinon.stub(element, '_preventDefaultAndNavigateToInputVal', function() { + element._preventDefaultAndNavigateToInputVal.restore(); + done(); + }); + MockInteractions.pressAndReleaseKeyOn(element.$.searchInput, 13); + }); + + }); +</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html new file mode 100644 index 0000000..5428c51 --- /dev/null +++ b/polygerrit-ui/app/test/index.html
@@ -0,0 +1,39 @@ +<!DOCTYPE html> +<!-- +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. +--> + +<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> +<title>Elements Test Runner</title> +<meta charset="utf-8"> +<script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> +<script src="../../bower_components/web-component-tester/browser.js"></script> +<script> + var testFiles = []; + + [ 'gr-change-list-item-test.html', + 'gr-change-list-test.html', + 'gr-date-formatter-test.html', + 'gr-diff-comment-test.html', + 'gr-diff-comment-thread-test.html', + 'gr-diff-view-test.html', + 'gr-search-bar-test.html', + ].forEach(function(file) { + testFiles.push(file); + testFiles.push(file + '?dom=shadow'); + }); + + WCT.loadSuites(testFiles); +</script>
diff --git a/polygerrit-ui/bower.json b/polygerrit-ui/bower.json new file mode 100644 index 0000000..de77154 --- /dev/null +++ b/polygerrit-ui/bower.json
@@ -0,0 +1,17 @@ +{ + "name": "polygerrit", + "version": "0.0.0", + "description": "Gerrit UI in Polymer; DO NOT PUBLISH, used only for dependency management", + "private": true, + "dependencies": { + "polymer": "Polymer/polymer#1.2.2", + "page": "visionmedia/page.js#1.6.4", + "iron-ajax": "PolymerElements/iron-ajax#1.1.0", + "iron-a11y-keys": "PolymerElements/iron-a11y-keys#1.0.3", + "iron-input": "PolymerElements/iron-input#1.0.6" + }, + "devDependencies": { + "web-component-tester": "*", + "iron-test-helpers": "PolymerElements/iron-test-helpers" + } +}
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go new file mode 100644 index 0000000..eec8cf9 --- /dev/null +++ b/polygerrit-ui/server.go
@@ -0,0 +1,130 @@ +// 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 main + +import ( + "bufio" + "compress/gzip" + "errors" + "flag" + "io" + "log" + "net" + "net/http" + "net/url" + "regexp" + "strings" +) + +var ( + restHost = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to") + port = flag.String("port", ":8081", "Port to serve HTTP requests on") + prod = flag.Bool("prod", false, "Serve production assets") +) + +func main() { + flag.Parse() + + if *prod { + http.Handle("/", http.FileServer(http.Dir("dist"))) + } else { + http.Handle("/bower_components/", + http.StripPrefix("/bower_components/", http.FileServer(http.Dir("bower_components")))) + http.Handle("/", http.FileServer(http.Dir("app"))) + } + + http.HandleFunc("/changes/", handleRESTProxy) + http.HandleFunc("/accounts/", handleRESTProxy) + http.HandleFunc("/config/", handleRESTProxy) + log.Println("Serving on port", *port) + log.Fatal(http.ListenAndServe(*port, &server{})) +} + +func handleRESTProxy(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + req := &http.Request{ + Method: "GET", + URL: &url.URL{ + Scheme: "https", + Host: *restHost, + Opaque: r.URL.EscapedPath(), + RawQuery: r.URL.RawQuery, + }, + } + res, err := http.DefaultClient.Do(req) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer res.Body.Close() + w.WriteHeader(res.StatusCode) + if _, err := io.Copy(w, res.Body); err != nil { + log.Println("Error copying response to ResponseWriter:", err) + return + } +} + +type gzipResponseWriter struct { + io.WriteCloser + http.ResponseWriter +} + +func newGzipResponseWriter(w http.ResponseWriter) *gzipResponseWriter { + gz := gzip.NewWriter(w) + return &gzipResponseWriter{WriteCloser: gz, ResponseWriter: w} +} + +func (w gzipResponseWriter) Write(b []byte) (int, error) { + return w.WriteCloser.Write(b) +} + +func (w gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + h, ok := w.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, errors.New("gzipResponseWriter: ResponseWriter does not satisfy http.Hijacker interface") + } + return h.Hijack() +} + +type server struct{} + +// Any path prefixes that should resolve to index.html. +var ( + fePaths = []string{"/q/", "/c/", "/dashboard/"} + issueNumRE = regexp.MustCompile(`^\/\d+\/?$`) +) + +func (_ *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s %s %s\n", r.Proto, r.Method, r.RemoteAddr, r.URL) + for _, prefix := range fePaths { + if strings.HasPrefix(r.URL.Path, prefix) { + r.URL.Path = "/" + log.Println("Redirecting to /") + break + } else if match := issueNumRE.Find([]byte(r.URL.Path)); match != nil { + r.URL.Path = "/" + log.Println("Redirecting to /") + break + } + } + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + http.DefaultServeMux.ServeHTTP(w, r) + return + } + w.Header().Set("Content-Encoding", "gzip") + gzw := newGzipResponseWriter(w) + defer gzw.Close() + http.DefaultServeMux.ServeHTTP(gzw, r) +}
diff --git a/polygerrit-ui/wct.conf.js b/polygerrit-ui/wct.conf.js new file mode 100644 index 0000000..b6a6251 --- /dev/null +++ b/polygerrit-ui/wct.conf.js
@@ -0,0 +1,17 @@ +var path = require('path'); + +var ret = { + suites: ['app/test'], + webserver: { + pathMappings: [] + } +}; + +var mapping = {}; +var rootPath = (__dirname).split(path.sep).slice(-1)[0]; + +mapping['/components/' + rootPath + '/app/bower_components'] = 'bower_components'; + +ret.webserver.pathMappings.push(mapping); + +module.exports = ret;
diff --git a/tools/build.defs b/tools/build.defs index 893abba..3ea506c 100644 --- a/tools/build.defs +++ b/tools/build.defs
@@ -61,15 +61,20 @@ ) def gerrit_war(name, ui = 'ui_optdbg', context = [], docs = False, visibility = []): + ui_deps = [] + if ui: + if ui == 'polygerrit' or ui == 'ui_optdbg' or ui == 'ui_optdbg_r': + ui_deps.append('//polygerrit-ui/app:polygerrit_ui') + if ui != 'polygerrit': + ui_deps.append('//gerrit-gwtui:%s' % ui) war( name = name, libs = LIBS + ['//gerrit-war:version'], pgmlibs = PGMLIBS, - context = [ + context = ui_deps + context + [ '//gerrit-main:main_bin', '//gerrit-war:webapp_assets', - ] + (['//gerrit-gwtui:' + ui] if ui else []) + - context, + ], docs = docs, visibility = visibility, )
diff --git a/tools/default.defs b/tools/default.defs index 90096b2..191dfe5 100644 --- a/tools/default.defs +++ b/tools/default.defs
@@ -27,7 +27,6 @@ # Set defaults on java rules: # - Add AutoValue annotation processing support. # - Treat source files as UTF-8. -# - std_out_log_level = info (the default is too spammy) _buck_java_library = java_library def java_library(*args, **kwargs): @@ -37,9 +36,9 @@ _buck_java_test = java_test def java_test(*args, **kwargs): _munge_args(kwargs) - _do_not_spam_std_out(kwargs) _buck_java_test(*args, **kwargs) + # Munge kwargs to set Gerrit-specific defaults. def _munge_args(kwargs): _set_auto_value(kwargs) @@ -57,11 +56,6 @@ extra_args.extend(['-encoding', 'UTF-8']) -def _do_not_spam_std_out(kwargs): - level = 'std_out_log_level' - if level not in kwargs: - kwargs[level] = 'INFO' - def _set_auto_value(kwargs): apk = 'annotation_processors' if apk not in kwargs: @@ -79,6 +73,18 @@ apds.extend(AUTO_VALUE_PROCESSOR_DEPS) +# Add 'license' argument to genrule. +_buck_genrule = genrule +def genrule(*args, **kwargs): + license = kwargs.pop('license', None) + if license: + license = '//lib:LICENSE-%s' % license + # genrule has no deps attribute, but locations listed in the command show + # up as deps of the target with buck audit. + kwargs['cmd'] = 'true $(location %s); %s' % (license, kwargs['cmd']) + _buck_genrule(*args, **kwargs) + + def genantlr( name, srcs,
diff --git a/tools/download_file.py b/tools/download_file.py index 97d982f..bd67b50 100755 --- a/tools/download_file.py +++ b/tools/download_file.py
@@ -21,7 +21,7 @@ import shutil from subprocess import check_call, CalledProcessError from sys import stderr -from util import resolve_url +from util import hash_file, resolve_url from zipfile import ZipFile, BadZipfile, LargeZipFile GERRIT_HOME = path.expanduser('~/.gerritcodereview') @@ -33,17 +33,6 @@ LOCAL_PROPERTIES = 'local.properties' -def hashfile(p): - d = sha1() - with open(p, 'rb') as f: - while True: - b = f.read(8192) - if not b: - break - d.update(b) - return d.hexdigest() - - def safe_mkdirs(d): if path.isdir(d): return @@ -148,7 +137,7 @@ exit(1) if args.v: - have = hashfile(cache_ent) + have = hash_file(sha1(), cache_ent).hexdigest() if args.v != have: print(( '%s:\n' +
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py index 67ab138..9fbede3 100755 --- a/tools/eclipse/project.py +++ b/tools/eclipse/project.py
@@ -76,7 +76,7 @@ if path.exists(path.join(root, 'src', 'test', 'java')): testpath = """ <classpathentry kind="src" path="src/test/java"\ - out="buck-out/eclipse/test"/>""" + out="eclipse-out/test"/>""" else: testpath = "" print("""\ @@ -85,7 +85,7 @@ <classpathentry kind="src" path="src/main/java"/>%(testpath)s <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry combineaccessrules="false" kind="src" path="/gerrit"/> - <classpathentry kind="output" path="buck-out/eclipse/classes"/> + <classpathentry kind="output" path="eclipse-out/classes"/> </classpath>""" % {"testpath": testpath}, file=fd) def gen_classpath(): @@ -119,7 +119,7 @@ gwt_lib.add(p) continue - if p.startswith('buck-out/gen/lib/gwt/'): + if 'buck-out/gen/lib/gwt/' in p: # gwt_module() depends on huge shaded GWT JARs that import # incorrect versions of classes for Gerrit. Collect into # a private grouping for later use. @@ -141,12 +141,12 @@ out = None if s.startswith('lib/'): - out = 'buck-out/eclipse/lib' + out = 'eclipse-out/lib' elif s.startswith('plugins/'): if args.plugins: plugins.add(s) continue - out = 'buck-out/eclipse/' + s + out = 'eclipse-out/' + s p = path.join(s, 'java') if path.exists(p): @@ -158,14 +158,14 @@ if out: o = out + '/' + env elif env == 'test': - o = 'buck-out/eclipse/test' + o = 'eclipse-out/test' for srctype in ['java', 'resources']: p = path.join(s, 'src', env, srctype) if path.exists(p): classpathentry('src', p, out=o) - for libs in [gwt_lib, lib]: + for libs in [lib, gwt_lib]: for j in sorted(libs): s = None if j.endswith('.jar'): @@ -179,10 +179,10 @@ for s in sorted(gwt_src): p = path.join(ROOT, s, 'src', 'main', 'java') if path.exists(p): - classpathentry('lib', p, out='buck-out/eclipse/gwtsrc') + classpathentry('lib', p, out='eclipse-out/gwtsrc') classpathentry('con', JRE) - classpathentry('output', 'buck-out/eclipse/classes') + classpathentry('output', 'eclipse-out/classes') p = path.join(ROOT, '.classpath') with open(p, 'w') as fd:
diff --git a/tools/js/BUCK b/tools/js/BUCK new file mode 100644 index 0000000..ba4f19c --- /dev/null +++ b/tools/js/BUCK
@@ -0,0 +1,20 @@ +python_binary( + name = 'bower2buck', + main = 'bower2buck.py', + deps = ['//tools:util'], + visibility = ['PUBLIC'], +) + +python_binary( + name = 'download_bower', + main = 'download_bower.py', + deps = ['//tools:util'], + visibility = ['PUBLIC'], +) + +python_binary( + name = 'run_npm_binary', + main = 'run_npm_binary.py', + deps = ['//tools:util'], + visibility = ['PUBLIC'], +)
diff --git a/tools/js/bower2buck.py b/tools/js/bower2buck.py new file mode 100755 index 0000000..16c5909 --- /dev/null +++ b/tools/js/bower2buck.py
@@ -0,0 +1,209 @@ +#!/usr/bin/env python +# 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. + +from __future__ import print_function + +import atexit +import collections +import json +import hashlib +import optparse +import os +import shutil +import subprocess +import sys +import tempfile + +from tools import util + + +# This script is run with `buck run`, but needs to shell out to buck; this is +# only possible if we avoid buckd. +BUCK_ENV = dict(os.environ) +BUCK_ENV['NO_BUCKD'] = '1' + +HEADER = """\ +include_defs('//lib/js.defs') + +# AUTOGENERATED BY BOWER2BUCK +# +# This file should be merged with an existing BUCK file containing these rules. +# +# This comment SHOULD NOT be copied to the existing BUCK file, and you should +# leave alone any non-bower_component contents of the file. +# +# Generally, the following attributes SHOULD be copied from this file to the +# existing BUCK file: +# - package: the normalized package name +# - version: the exact version number +# - deps: direct dependencies of the package +# - sha1: a hash of the package contents +# +# The following fields SHOULD NOT be copied to the existing BUCK file: +# - semver: manually-specified semantic version, not included in autogenerated +# output. +# +# The following fields require SPECIAL HANDLING: +# - license: all licenses in this file are specified as TODO. You must replace +# this text with one of the existing licenses defined in lib/BUCK, or +# define a new one if necessary. Leave existing licenses alone. + +""" + + +def usage(): + print(('Usage: %s -o <outfile> [//path/to:bower_components_rule...]' + % sys.argv[0]), + file=sys.stderr) + return 1 + + +class Rule(object): + def __init__(self, bower_json_path): + with open(bower_json_path) as f: + bower_json = json.load(f) + self.name = bower_json['name'] + self.version = bower_json['version'] + self.deps = bower_json.get('dependencies', {}) + self.license = bower_json['license'] + self.sha1 = util.hash_bower_component( + hashlib.sha1(), os.path.dirname(bower_json_path)).hexdigest() + + def to_rule(self, packages): + if self.name not in packages: + raise ValueError('No package name found for %s' % self.name) + + lines = [ + 'bower_component(', + " name = '%s'," % self.name, + " package = '%s'," % packages[self.name], + " version = '%s'," % self.version, + ] + if self.deps: + if len(self.deps) == 1: + lines.append(" deps = [':%s']," % next(self.deps.iterkeys())) + else: + lines.append(' deps = [') + lines.extend(" ':%s'," % d for d in sorted(self.deps.iterkeys())) + lines.append(' ],') + lines.extend([ + " license = 'TODO: %s'," % self.license, + " sha1 = '%s'," % self.sha1, + ')']) + return '\n'.join(lines) + + +def build_bower_json(targets, buck_out): + bower_json = collections.OrderedDict() + bower_json['name'] = 'bower2buck-output' + bower_json['version'] = '0.0.0' + bower_json['description'] = 'Auto-generated bower.json for dependency management' + bower_json['private'] = True + bower_json['dependencies'] = {} + + deps = subprocess.check_output( + ['buck', 'query', '-v', '0', + "filter('__download_bower', deps(%s))" % '+'.join(targets)], + env=BUCK_ENV) + deps = deps.replace('__download_bower', '__bower_version').split() + subprocess.check_call(['buck', 'build'] + deps, env=BUCK_ENV) + + for dep in deps: + dep = dep.replace(':', '/').lstrip('/') + depout = os.path.basename(dep) + version_json = os.path.join(buck_out, 'gen', dep, depout) + with open(version_json) as f: + bower_json['dependencies'].update(json.load(f)) + + tmpdir = tempfile.mkdtemp() + atexit.register(lambda: shutil.rmtree(tmpdir)) + ret = os.path.join(tmpdir, 'bower.json') + with open(ret, 'w') as f: + json.dump(bower_json, f, indent=2) + return ret + + +def get_package_name(name, package_version): + v = package_version.lower() + if '#' in v: + return v[:v.find('#')] + return name + + +def get_packages(path): + with open(path) as f: + bower_json = json.load(f) + return dict((n, get_package_name(n, v)) + for n, v in bower_json.get('dependencies', {}).iteritems()) + + +def collect_rules(packages): + # TODO(dborowitz): Use run_npm_binary instead of system bower. + rules = {} + subprocess.check_call(['bower', 'install']) + for dirpath, dirnames, filenames in os.walk('.', topdown=True): + if '.bower.json' not in filenames: + continue + del dirnames[:] + rule = Rule(os.path.join(dirpath, '.bower.json')) + rules[rule.name] = rule + + # Oddly, the package name referred to in the deps section of dependents, + # e.g. 'PolymerElements/iron-ajax', is not found anywhere in this + # bower.json, which only contains 'iron-ajax'. Build up a map of short name + # to package name so we can resolve them later. + for n, v in rule.deps.iteritems(): + p = get_package_name(n, v) + old = packages.get(n) + if old is not None and old != p: + raise ValueError('multiple packages named %s: %s != %s' % (n, p, old)) + packages[n] = p + + return rules + + +def find_buck_out(): + dir = os.getcwd() + while not os.path.isfile(os.path.join(dir, '.buckconfig')): + dir = os.path.dirname(dir) + return os.path.join(dir, 'buck-out') + + +def main(args): + opts = optparse.OptionParser() + opts.add_option('-o', help='output file location') + opts, args = opts.parse_args() + + if not opts.o or not all(a.startswith('//') for a in args): + return usage() + outfile = os.path.abspath(opts.o) + buck_out = find_buck_out() + + targets = args if args else ['//polygerrit-ui/...'] + bower_json_path = build_bower_json(targets, buck_out) + os.chdir(os.path.dirname(bower_json_path)) + packages = get_packages(bower_json_path) + rules = collect_rules(packages) + + with open(outfile, 'w') as f: + f.write(HEADER) + for _, r in sorted(rules.iteritems()): + f.write('\n\n%s' % r.to_rule(packages)) + + print('Wrote bower_components rules to:\n %s' % outfile) + + +if __name__ == '__main__': + main(sys.argv[1:])
diff --git a/tools/js/download_bower.py b/tools/js/download_bower.py new file mode 100644 index 0000000..80720d7 --- /dev/null +++ b/tools/js/download_bower.py
@@ -0,0 +1,116 @@ +#!/usr/bin/env python +# 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. + +from __future__ import print_function + +import hashlib +import json +import optparse +import os +import shutil +import subprocess +import sys + +from tools import util + + +CACHE_DIR = os.path.expanduser(os.path.join( + '~', '.gerritcodereview', 'buck-cache', 'downloaded-artifacts')) + + +def bower_cmd(bower, *args): + cmd = bower.split(' ') + cmd.extend(args) + return cmd + + +def bower_info(bower, name, package, version): + cmd = bower_cmd(bower, '-l=error', '-j', + 'info', '%s#%s' % (package, version)) + p = subprocess.Popen(cmd , stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + if p.returncode: + sys.stderr.write(err) + raise OSError('Command failed: %s' % cmd) + + try: + info = json.loads(out) + except ValueError: + raise ValueError('invalid JSON from %s:\n%s' % (cmd, out)) + info_name = info.get('name') + if info_name != name: + raise ValueError('expected package name %s, got: %s' % (name, info_name)) + return info + + +def ignore_deps(info): + # Tell bower to ignore dependencies so we just download this component. This + # is just an optimization, since we only pick out the component we need, but + # it's important when downloading sizable dependency trees. + # + # As of 1.6.5 I don't think ignoredDependencies can be specified on the + # command line with --config, so we have to create .bowerrc. + deps = info.get('dependencies') + if deps: + with open(os.path.join('.bowerrc'), 'w') as f: + json.dump({'ignoredDependencies': deps.keys()}, f) + + +def cache_entry(name, package, version, sha1): + if not sha1: + sha1 = hashlib.sha1('%s#%s' % (package, version)).hexdigest() + return os.path.join(CACHE_DIR, '%s-%s.zip-%s' % (name, version, sha1)) + + +def main(args): + opts = optparse.OptionParser() + opts.add_option('-n', help='short name of component') + opts.add_option('-b', help='bower command') + opts.add_option('-p', help='full package name of component') + opts.add_option('-v', help='version number') + opts.add_option('-s', help='expected content sha1') + opts.add_option('-o', help='output file location') + opts, _ = opts.parse_args() + + cwd = os.getcwd() + outzip = os.path.join(cwd, opts.o) + cached = cache_entry(opts.n, opts.p, opts.v, opts.s) + + if not os.path.exists(cached): + info = bower_info(opts.b, opts.n, opts.p, opts.v) + ignore_deps(info) + subprocess.check_call( + bower_cmd(opts.b, '--quiet', 'install', '%s#%s' % (opts.p, opts.v))) + bc = os.path.join(cwd, 'bower_components') + subprocess.check_call( + ['zip', '-q', '--exclude', '.bower.json', '-r', cached, opts.n], + cwd=bc) + + if opts.s: + path = os.path.join(bc, opts.n) + sha1 = util.hash_bower_component(hashlib.sha1(), path).hexdigest() + if opts.s != sha1: + print(( + '%s#%s:\n' + 'expected %s\n' + 'received %s\n') % (opts.p, opts.v, opts.s, sha1), file=sys.stderr) + return 1 + + shutil.copyfile(cached, outzip) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
diff --git a/tools/js/npm_pack.py b/tools/js/npm_pack.py new file mode 100755 index 0000000..9eb6e34 --- /dev/null +++ b/tools/js/npm_pack.py
@@ -0,0 +1,74 @@ +#!/usr/bin/env python +# 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. + +from __future__ import print_function + +import atexit +import json +import os +import shutil +import subprocess +import sys +import tarfile +import tempfile + + +def is_bundled(tar): + # No entries for directories, so scan for a matching prefix. + for entry in tar.getmembers(): + if entry.name.startswith('package/node_modules/'): + return True + return False + + +def bundle_dependencies(): + with open('package.json') as f: + package = json.load(f) + package['bundledDependencies'] = package['dependencies'].keys() + with open('package.json', 'w') as f: + json.dump(package, f) + + +def main(args): + if len(args) != 2: + print('Usage: %s <package> <version>' % sys.argv[0], file=sys.stderr) + return 1 + + name, version = args + filename = '%s-%s.tgz' % (name, version) + url = 'http://registry.npmjs.org/%s/-/%s' % (name, filename) + + tmpdir = tempfile.mkdtemp(); + tgz = os.path.join(tmpdir, filename) + atexit.register(lambda: shutil.rmtree(tmpdir)) + + subprocess.check_call(['curl', '--proxy-anyauth', '-ksfo', tgz, url]) + with tarfile.open(tgz, 'r:gz') as tar: + if is_bundled(tar): + print('%s already has bundled node_modules' % filename) + return 1 + tar.extractall(path=tmpdir) + + oldpwd = os.getcwd() + os.chdir(os.path.join(tmpdir, 'package')) + bundle_dependencies() + subprocess.check_call(['npm', 'install']) + subprocess.check_call(['npm', 'pack']) + shutil.copy(filename, os.path.join(oldpwd, filename)) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
diff --git a/tools/js/run_npm_binary.py b/tools/js/run_npm_binary.py new file mode 100644 index 0000000..d76eff5 --- /dev/null +++ b/tools/js/run_npm_binary.py
@@ -0,0 +1,91 @@ +#!/usr/bin/env python +# 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. + +from __future__ import print_function + +import atexit +from distutils import spawn +import hashlib +import os +import shutil +import subprocess +import sys +import tarfile +import tempfile + +from tools import util + + +def extract(path, outdir, bin): + if os.path.exists(os.path.join(outdir, bin)): + return # Another process finished extracting, ignore. + + # Use a temp directory adjacent to outdir so shutil.move can use the same + # device atomically. + tmpdir = tempfile.mkdtemp(dir=os.path.dirname(outdir)) + def cleanup(): + try: + shutil.rmtree(tmpdir) + except OSError: + pass # Too late now + atexit.register(cleanup) + + def extract_one(mem): + dest = os.path.join(outdir, mem.name) + tar.extract(mem, path=tmpdir) + try: + os.makedirs(os.path.dirname(dest)) + except OSError: + pass # Either exists, or will fail on the next line. + shutil.move(os.path.join(tmpdir, mem.name), dest) + + with tarfile.open(path, 'r:gz') as tar: + for mem in tar.getmembers(): + if mem.name != bin: + extract_one(mem) + # Extract bin last so other processes only short circuit when extraction is + # finished. + extract_one(tar.getmember(bin)) + + +def main(args): + path = args[0] + suffix = '.npm_binary.tgz' + tgz = os.path.basename(path) + parts = tgz[:-len(suffix)].split('@') + + if not tgz.endswith(suffix) or len(parts) != 2: + print('usage: %s <path/to/npm_binary>' % sys.argv[0], file=sys.stderr) + return 1 + + name, version = parts + sha1 = util.hash_file(hashlib.sha1(), path).hexdigest() + outdir = '%s-%s' % (path[:-len(suffix)], sha1) + rel_bin = os.path.join('package', 'bin', name) + bin = os.path.join(outdir, rel_bin) + if not os.path.isfile(bin): + extract(path, outdir, rel_bin) + + nodejs = spawn.find_executable('nodejs') + if nodejs: + # Debian installs Node.js as 'nodejs', due to a conflict with another + # package. + subprocess.check_call([nodejs, bin] + args[1:]) + else: + subprocess.check_call([bin] + args[1:]) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
diff --git a/tools/util.py b/tools/util.py index ec895dd..96f6047 100644 --- a/tools/util.py +++ b/tools/util.py
@@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os from os import path REPO_ROOTS = { @@ -49,3 +50,53 @@ root = root.rstrip('/') rest = rest.lstrip('/') return '/'.join([root, rest]) + + +def hash_file(hash_obj, path): + """Hash the contents of a file. + + Args: + hash_obj: an open hash object, e.g. hashlib.sha1(). + path: path to the file to hash. + + Returns: + The passed-in hash_obj. + """ + with open(path, 'rb') as f: + while True: + b = f.read(8192) + if not b: + break + hash_obj.update(b) + return hash_obj + + +def hash_bower_component(hash_obj, path): + """Hash the contents of a bower component directory. + + This is a stable hash of a directory downloaded with `bower install`, minus + the .bower.json file, which is autogenerated each time by bower. Used in lieu + of hashing a zipfile of the contents, since zipfiles are difficult to hash in + a stable manner. + + Args: + hash_obj: an open hash object, e.g. hashlib.sha1(). + path: path to the directory to hash. + + Returns: + The passed-in hash_obj. + """ + if not os.path.isdir(path): + raise ValueError('Not a directory: %s' % path) + + path = os.path.abspath(path) + for root, dirs, files in os.walk(path): + dirs.sort() + for f in sorted(files): + if f == '.bower.json': + continue + p = os.path.join(root, f) + hash_obj.update(p[len(path)+1:]) + hash_file(hash_obj, p) + + return hash_obj