Merge changes from topic 'query-with-change-id-triplet'
* changes:
QueryScreen: Add support for Change-Id triplet in single query
QueryScreen: Use precompiled regex patterns to test for single query
diff --git a/Documentation/cmd-index-changes.txt b/Documentation/cmd-index-changes.txt
new file mode 100644
index 0000000..8566827
--- /dev/null
+++ b/Documentation/cmd-index-changes.txt
@@ -0,0 +1,40 @@
+= gerrit index changes
+
+== NAME
+gerrit index changes - Index one or more changes.
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit index changes' <CHANGE> [<CHANGE> ...]
+--
+
+== DESCRIPTION
+Indexes one or more changes.
+
+Changes can be specified in the link:rest-api-changes.html#change-id[same format]
+supported by the REST API.
+
+== ACCESS
+Caller must have the 'Maintain Server' capability, or be the owner of the change
+to be indexed.
+
+== SCRIPTING
+This command is intended to be used in scripts.
+
+== OPTIONS
+--CHANGE::
+ Required; changes to be indexed.
+
+== EXAMPLES
+Index changes with legacy ID numbers 1 and 2.
+
+====
+ $ ssh -p 29418 user@review.example.com gerrit index changes 1 2
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 90212fb..e244228 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -126,6 +126,9 @@
link:cmd-index-start.html[gerrit index start]::
Start the online indexer.
+link:cmd-index-changes.html[gerrit index changes]::
+ Index one or more changes.
+
link:cmd-logging-ls-level.html[gerrit logging ls-level]::
List loggers and their logging level.
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index 415cb76..5dec21d 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -189,16 +189,6 @@
link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
Configuration]
-[[codenvy]]
-=== codenvy
-
-Plugin to allow to edit code on-line on either an existing branch or an
-active change using the link:http://codenvy.com[Codenvy] cloud
-development platform.
-
-link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codenvy[
-Project]
-
[[delete-project]]
=== delete-project
@@ -557,6 +547,18 @@
link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[
Documentation]
+[[scripts]]
+=== scripts
+
+Repository containing a collection of Gerrit scripting plugins that are intended
+to provide simple and useful extensions.
+
+Groovy and Scala scripts require the installation of the corresponding
+scripting/*-provider plugin in order to be loaded into Gerrit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripts[Project]
+link:https://gerrit.googlesource.com/plugins/scripts/+doc/master/README.md[Documentation]
+
[[server-config]]
=== server-config
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 8315776..dbaa3c1 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -94,7 +94,6 @@
Implementation-Title: Example plugin showing examples
Implementation-Version: 1.0
Implementation-Vendor: Example, Inc.
- Implementation-URL: http://example.com/opensource/plugin-foo/
====
=== ApiType
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index a8b7699..baf08e7 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# coding=utf-8
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 661abb0..345f759 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -936,6 +936,103 @@
]
----
+[[get-diff-preferences]]
+=== Get diff preferences
+
+--
+'GET /config/server/preferences.diff'
+--
+
+Returns the default diff preferences for the server.
+
+.Request
+----
+ GET /a/config/server/preferences.diff HTTP/1.0
+----
+
+As response a link:rest-api-accounts.html#diff-preferences-info[
+DiffPreferencesInfo] is returned.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "context": 10,
+ "tab_size": 8,
+ "line_length": 100,
+ "cursor_blink_rate": 0,
+ "intraline_difference": true,
+ "show_line_endings": true,
+ "show_tabs": true,
+ "show_whitespace_errors": true,
+ "syntax_highlighting": true,
+ "auto_hide_diff_table_header": true,
+ "theme": "DEFAULT",
+ "ignore_whitespace": "IGNORE_NONE"
+ }
+----
+
+[[set-diff-preferences]]
+=== Set Diff Preferences
+
+--
+'PUT /config/server/preferences.diff'
+--
+
+Sets the default diff preferences for the server. Default diff preferences can
+only be set by a Gerrit link:access-control.html#administrators[administrator].
+At least one field of alink:rest-api-accounts.html#diff-preferences-info[
+DiffPreferencesInfo] must be provided in the request body.
+
+.Request
+----
+ PUT /a/config/server/preferences.diff HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "context": 10,
+ "tab_size": 8,
+ "line_length": 80,
+ "cursor_blink_rate": 0,
+ "intraline_difference": true,
+ "show_line_endings": true,
+ "show_tabs": true,
+ "show_whitespace_errors": true,
+ "syntax_highlighting": true,
+ "auto_hide_diff_table_header": true,
+ "theme": "DEFAULT",
+ "ignore_whitespace": "IGNORE_NONE"
+ }
+----
+
+As response a link:rest-api-accounts.html#diff-preferences-info[
+DiffPreferencesInfo] is returned.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "context": 10,
+ "tab_size": 8,
+ "line_length": 80,
+ "cursor_blink_rate": 0,
+ "intraline_difference": true,
+ "show_line_endings": true,
+ "show_tabs": true,
+ "show_whitespace_errors": true,
+ "syntax_highlighting": true,
+ "auto_hide_diff_table_header": true,
+ "theme": "DEFAULT",
+ "ignore_whitespace": "IGNORE_NONE"
+ }
+----
+
[[ids]]
== IDs
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 736b1b9..36e7489 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -923,6 +923,143 @@
}
----
+[[get-access]]
+=== List Access Rights for Project
+--
+'GET /projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
+--
+
+Lists the access rights for a single project.
+
+As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
+
+.Request
+----
+ GET /projects/MyProject/access HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
+ "inherits_from": {
+ "id": "All-Projects",
+ "name": "All-Projects",
+ "description": "Access inherited by all other projects."
+ },
+ "local": {
+ "refs/*": {
+ "permissions": {
+ "read": {
+ "rules": {
+ "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+ "action": "ALLOW",
+ "force": false
+ },
+ "global:Anonymous-Users": {
+ "action": "ALLOW",
+ "force": false
+ }
+ }
+ }
+ }
+ }
+ },
+ "is_owner": true,
+ "owner_of": [
+ "refs/*"
+ ],
+ "can_upload": true,
+ "can_add": true,
+ "config_visible": true
+ }
+----
+
+[[set-access]]
+=== Add, Update and Delete Access Rights for Project
+--
+'POST /projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
+--
+
+Sets access rights for the project using the diff schema provided by
+link:#project-access-input[ProjectAccessInput]. Deductions are used to
+remove access sections, permissions or permission rules. The backend will remove
+the entity with the finest granularity in the request, meaning that if an
+access section without permissions is posted, the access section will be
+removed; if an access section with a permission but no permission rules is
+posted, the permission will be removed; if an access section with a permission
+and a permission rule is posted, the permission rule will be removed.
+
+Additionally, access sections and permissions will be cleaned up after applying
+the deductions by removing items that have no child elements.
+
+After removals have been applied, additions will be applied.
+
+As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
+
+.Request
+----
+ POST /projects/MyProject/access HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "remove": [
+ "refs/*": {
+ "permissions": {
+ "read": {
+ "rules": {
+ "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+ "action": "ALLOW"
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
+ "inherits_from": {
+ "id": "All-Projects",
+ "name": "All-Projects",
+ "description": "Access inherited by all other projects."
+ },
+ "local": {
+ "refs/*": {
+ "permissions": {
+ "read": {
+ "rules": {
+ "global:Anonymous-Users": {
+ "action": "ALLOW",
+ "force": false
+ }
+ }
+ }
+ }
+ }
+ },
+ "is_owner": true,
+ "owner_of": [
+ "refs/*"
+ ],
+ "can_upload": true,
+ "can_add": true,
+ "config_visible": true
+ }
+----
+
[[branch-endpoints]]
== Branch Endpoints
@@ -2293,6 +2430,27 @@
Not set if there is no global limit for the object size.
|===============================
+[[project-access-input]]
+=== ProjectAccessInput
+The `ProjectAccessInput` describes changes that should be applied to a project
+access config.
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name | |Description
+|`remove` |optional|
+A list of deductions to be applied to the project access as
+link:rest-api-access.html#project-access-info[ProjectAccessInfo] entities.
+|`add` |optional|
+A list of additions to be applied to the project access as
+link:rest-api-access.html#project-access-info[ProjectAccessInfo] entities.
+|`message` |optional|
+A commit message for this change.
+|`parent` |optional|
+A new parent for the project to inherit from. Changing the parent project
+requires administrative privileges.
+|=============================
+
[[project-description-input]]
=== ProjectDescriptionInput
The `ProjectDescriptionInput` entity contains information for setting a
@@ -2472,60 +2630,6 @@
The path to the `GerritSiteFooter.html` file.
|=============================
-[[get-access]]
-=== List Access Rights for Project
---
-'GET //projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
---
-
-Lists the access rights for a single project.
-
-As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
-
-.Request
-----
- GET /projects/MyProject/access HTTP/1.0
-----
-
-.Response
-----
- HTTP/1.1 200 OK
- Content-Type: application/json; charset=UTF-8
-
- )]}'
- {
- "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
- "inherits_from": {
- "id": "All-Projects",
- "name": "All-Projects",
- "description": "Access inherited by all other projects."
- },
- "local": {
- "refs/*": {
- "permissions": {
- "read": {
- "rules": {
- "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
- "action": "ALLOW",
- "force": false
- },
- "global:Anonymous-Users": {
- "action": "ALLOW",
- "force": false
- }
- }
- }
- }
- }
- },
- "is_owner": true,
- "owner_of": [
- "refs/*"
- ],
- "can_upload": true,
- "can_add": true,
- "config_visible": true
- }
----
GERRIT
diff --git a/Documentation/user-submodules.txt b/Documentation/user-submodules.txt
index 8f75bf1..af2b344 100644
--- a/Documentation/user-submodules.txt
+++ b/Documentation/user-submodules.txt
@@ -85,7 +85,7 @@
====
and add the following lines:
====
- [subscribe "<superproject>"]
+ [allowSuperproject "<superproject>"]
refs = <refspec>
====
where the 'superproject' should be the exact project name of the superproject.
@@ -100,6 +100,15 @@
====
After the change is integrated a superproject subscription is possible.
+The configuration is inherited from parent projects, such that you can have
+a configuration in the "All-Projects" project like:
+====
+ [allowSuperproject "my-only-superproject"]
+ refs = refs/heads/*:refs/heads/*
+====
+and then you don't have to worry about configuring the individual projects
+any more. Child projects cannot negate the parent's configuration.
+
=== Defining the submodule branch
Since Gerrit manages subscriptions in the branch scope, we could have
@@ -153,14 +162,14 @@
'stable':
====
[allowSuperproject "<superproject>"]
- refs/heads/*:refs/heads/*
+ refs = refs/heads/*:refs/heads/*
====
If you want to enable a branch to be subscribed to any other branch of
the superproject, omit the second part of the RefSpec:
====
[allowSuperproject "<superproject>"]
- refs/heads/<submodule-branch>
+ refs = refs/heads/<submodule-branch>
====
=== Subscription Limitations
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
new file mode 100644
index 0000000..c35f82c
--- /dev/null
+++ b/contrib/populate-fixture-data.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This script will populate an empty standard Gerrit instance with some
+data for local testing.
+
+This script requires 'requests'. If you do not have this module, run
+'pip3 install requests' to install it.
+
+TODO(hiesel): Make real git commits instead of empty changes
+TODO(hiesel): Add comments
+"""
+
+import atexit
+import json
+import os
+import random
+import shutil
+import subprocess
+import tempfile
+
+import requests
+import requests.auth
+
+DEFAULT_TMP_PATH = "/tmp"
+TMP_PATH = ""
+BASE_URL = "http://localhost:8080/a/"
+ACCESS_URL = BASE_URL + "access/"
+ACCOUNTS_URL = BASE_URL + "accounts/"
+CHANGES_URL = BASE_URL + "changes/"
+CONFIG_URL = BASE_URL + "config/"
+GROUPS_URL = BASE_URL + "groups/"
+PLUGINS_URL = BASE_URL + "plugins/"
+PROJECTS_URL = BASE_URL + "projects/"
+
+ADMIN_DIGEST = requests.auth.HTTPDigestAuth("admin", "secret")
+
+# GROUP_ADMIN stores a GroupInfo for the admin group (see Gerrit rest docs)
+# In addition, GROUP_ADMIN["name"] stores the admin group"s name.
+GROUP_ADMIN = {}
+
+HEADERS = {"Content-Type": "application/json", "charset": "UTF-8"}
+
+# Random names from US Census Data
+FIRST_NAMES = [
+ "Casey", "Yesenia", "Shirley", "Tara", "Wanda", "Sheryl", "Jaime", "Elaine",
+ "Charlotte", "Carly", "Bonnie", "Kirsten", "Kathryn", "Carla", "Katrina",
+ "Melody", "Suzanne", "Sandy", "Joann", "Kristie", "Sally", "Emma", "Susan",
+ "Amanda", "Alyssa", "Patty", "Angie", "Dominique", "Cynthia", "Jennifer",
+ "Theresa", "Desiree", "Kaylee", "Maureen", "Jeanne", "Kellie", "Valerie",
+ "Nina", "Judy", "Diamond", "Anita", "Rebekah", "Stefanie", "Kendra", "Erin",
+ "Tammie", "Tracey", "Bridget", "Krystal", "Jasmin", "Sonia", "Meghan",
+ "Rebecca", "Jeanette", "Meredith", "Beverly", "Natasha", "Chloe", "Selena",
+ "Teresa", "Sheena", "Cassandra", "Rhonda", "Tami", "Jodi", "Shelly", "Angela",
+ "Kimberly", "Terry", "Joanna", "Isabella", "Lindsey", "Loretta", "Dana",
+ "Veronica", "Carolyn", "Laura", "Karen", "Dawn", "Alejandra", "Cassie",
+ "Lorraine", "Yolanda", "Kerry", "Stephanie", "Caitlin", "Melanie", "Kerri",
+ "Doris", "Sandra", "Beth", "Carol", "Vicki", "Shelia", "Bethany", "Rachael",
+ "Donna", "Alexandra", "Barbara", "Ana", "Jillian", "Ann", "Rachel", "Lauren",
+ "Hayley", "Misty", "Brianna", "Tanya", "Danielle", "Courtney", "Jacqueline",
+ "Becky", "Christy", "Alisha", "Phyllis", "Faith", "Jocelyn", "Nancy",
+ "Gloria", "Kristen", "Evelyn", "Julie", "Julia", "Kara", "Chelsey", "Cassidy",
+ "Jean", "Chelsea", "Jenny", "Diana", "Haley", "Kristine", "Kristina", "Erika",
+ "Jenna", "Alison", "Deanna", "Abigail", "Melissa", "Sierra", "Linda",
+ "Monica", "Tasha", "Traci", "Yvonne", "Tracy", "Marie", "Maria", "Michaela",
+ "Stacie", "April", "Morgan", "Cathy", "Darlene", "Cristina", "Emily"
+ "Ian", "Russell", "Phillip", "Jay", "Barry", "Brad", "Frederick", "Fernando",
+ "Timothy", "Ricardo", "Bernard", "Daniel", "Ruben", "Alexis", "Kyle", "Malik",
+ "Norman", "Kent", "Melvin", "Stephen", "Daryl", "Kurt", "Greg", "Alex",
+ "Mario", "Riley", "Marvin", "Dan", "Steven", "Roberto", "Lucas", "Leroy",
+ "Preston", "Drew", "Fred", "Casey", "Wesley", "Elijah", "Reginald", "Joel",
+ "Christopher", "Jacob", "Luis", "Philip", "Mark", "Rickey", "Todd", "Scott",
+ "Terrence", "Jim", "Stanley", "Bobby", "Thomas", "Gabriel", "Tracy", "Marcus",
+ "Peter", "Michael", "Calvin", "Herbert", "Darryl", "Billy", "Ross", "Dustin",
+ "Jaime", "Adam", "Henry", "Xavier", "Dominic", "Lonnie", "Danny", "Victor",
+ "Glen", "Perry", "Jackson", "Grant", "Gerald", "Garrett", "Alejandro",
+ "Eddie", "Alan", "Ronnie", "Mathew", "Dave", "Wayne", "Joe", "Craig",
+ "Terry", "Chris", "Randall", "Parker", "Francis", "Keith", "Neil", "Caleb",
+ "Jon", "Earl", "Taylor", "Bryce", "Brady", "Max", "Sergio", "Leon", "Gene",
+ "Darin", "Bill", "Edgar", "Antonio", "Dalton", "Arthur", "Austin", "Cristian",
+ "Kevin", "Omar", "Kelly", "Aaron", "Ethan", "Tom", "Isaac", "Maurice",
+ "Gilbert", "Hunter", "Willie", "Harry", "Dale", "Darius", "Jerome", "Jason",
+ "Harold", "Kerry", "Clarence", "Gregg", "Shane", "Eduardo", "Micheal",
+ "Howard", "Vernon", "Rodney", "Anthony", "Levi", "Larry", "Franklin", "Jimmy",
+ "Jonathon", "Carl",
+]
+
+LAST_NAMES = [
+ "Savage", "Hendrix", "Moon", "Larsen", "Rocha", "Burgess", "Bailey", "Farley",
+ "Moses", "Schmidt", "Brown", "Hoover", "Klein", "Jennings", "Braun", "Rangel",
+ "Casey", "Dougherty", "Hancock", "Wolf", "Henry", "Thomas", "Bentley",
+ "Barnett", "Kline", "Pitts", "Rojas", "Sosa", "Paul", "Hess", "Chase",
+ "Mckay", "Bender", "Colins", "Montoya", "Townsend", "Potts", "Ayala", "Avery",
+ "Sherman", "Tapia", "Hamilton", "Ferguson", "Huang", "Hooper", "Zamora",
+ "Logan", "Lloyd", "Quinn", "Monroe", "Brock", "Ibarra", "Fowler", "Weiss",
+ "Montgomery", "Diaz", "Dixon", "Olson", "Robertson", "Arias", "Benjamin",
+ "Abbott", "Stein", "Schroeder", "Beck", "Velasquez", "Barber", "Nichols",
+ "Ortiz", "Burns", "Moody", "Stokes", "Wilcox", "Rush", "Michael", "Kidd",
+ "Rowland", "Mclean", "Saunders", "Chung", "Newton", "Potter", "Hickman",
+ "Ray", "Larson", "Figueroa", "Duncan", "Sparks", "Rose", "Hodge", "Huynh",
+ "Joseph", "Morales", "Beasley", "Mora", "Fry", "Ross", "Novak", "Hahn",
+ "Wise", "Knight", "Frederick", "Heath", "Pollard", "Vega", "Mcclain",
+ "Buckley", "Conrad", "Cantrell", "Bond", "Mejia", "Wang", "Lewis", "Johns",
+ "Mcknight", "Callahan", "Reynolds", "Norris", "Burnett", "Carey", "Jacobson",
+ "Oneill", "Oconnor", "Leonard", "Mckenzie", "Hale", "Delgado", "Spence",
+ "Brandt", "Obrien", "Bowman", "James", "Avila", "Roberts", "Barker", "Cohen",
+ "Bradley", "Prince", "Warren", "Summers", "Little", "Caldwell", "Garrett",
+ "Hughes", "Norton", "Burke", "Holden", "Merritt", "Lee", "Frank", "Wiley",
+ "Ho", "Weber", "Keith", "Winters", "Gray", "Watts", "Brady", "Aguilar",
+ "Nicholson", "David", "Pace", "Cervantes", "Davis", "Baxter", "Sanchez",
+ "Singleton", "Taylor", "Strickland", "Glenn", "Valentine", "Roy", "Cameron",
+ "Beard", "Norman", "Fritz", "Anthony", "Koch", "Parrish", "Herman", "Hines",
+ "Sutton", "Gallegos", "Stephenson", "Lozano", "Franklin", "Howe", "Bauer",
+ "Love", "Ali", "Ellison", "Lester", "Guzman", "Jarvis", "Espinoza",
+ "Fletcher", "Burton", "Woodard", "Peterson", "Barajas", "Richard", "Bryan",
+ "Goodman", "Cline", "Rowe", "Faulkner", "Crawford", "Mueller", "Patterson",
+ "Hull", "Walton", "Wu", "Flores", "York", "Dickson", "Barnes", "Fisher",
+ "Strong", "Juarez", "Fitzgerald", "Schmitt", "Blevins", "Villa", "Sullivan",
+ "Velazquez", "Horton", "Meadows", "Riley", "Barrera", "Neal", "Mendez",
+ "Mcdonald", "Floyd", "Lynch", "Mcdowell", "Benson", "Hebert", "Livingston",
+ "Davies", "Richardson", "Vincent", "Davenport", "Osborn", "Mckee", "Marshall",
+ "Ferrell", "Martinez", "Melton", "Mercer", "Yoder", "Jacobs", "Mcdaniel",
+ "Mcmillan", "Peters", "Atkinson", "Wood", "Briggs", "Valencia", "Chandler",
+ "Rios", "Hunter", "Bean", "Hicks", "Hays", "Lucero", "Malone", "Waller",
+ "Banks", "Myers", "Mitchell", "Grimes", "Houston", "Hampton", "Trujillo",
+ "Perkins", "Moran", "Welch", "Contreras", "Montes", "Ayers", "Hayden",
+ "Daniel", "Weeks", "Porter", "Gill", "Mullen", "Nolan", "Dorsey", "Crane",
+ "Estes", "Lam", "Wells", "Cisneros", "Giles", "Watson", "Vang", "Scott",
+ "Knox", "Hanna", "Fields",
+]
+
+
+def clean(json_string):
+ # Strip JSON XSS Tag
+ json_string = json_string.strip()
+ if json_string.startswith(")]}'"):
+ return json_string[5:]
+ return json_string
+
+
+def digest_auth(user):
+ return requests.auth.HTTPDigestAuth(user["username"], user["http_password"])
+
+
+def fetch_admin_group():
+ global GROUP_ADMIN
+ # Get admin group
+ r = json.loads(clean(requests.get(GROUPS_URL + "?suggest=ad&p=All-Projects",
+ headers=HEADERS,
+ auth=ADMIN_DIGEST).text))
+ admin_group_name = r.keys()[0]
+ GROUP_ADMIN = r[admin_group_name]
+ GROUP_ADMIN["name"] = admin_group_name
+
+
+def generate_random_text():
+ return " ".join([random.choice("lorem ipsum "
+ "doleret delendam "
+ "\n esse".split(" ")) for _ in xrange(1, 100)])
+
+
+def set_up():
+ global TMP_PATH
+ TMP_PATH = tempfile.mkdtemp()
+ atexit.register(clean_up)
+ os.makedirs(TMP_PATH + "/ssh")
+ os.makedirs(TMP_PATH + "/repos")
+ fetch_admin_group()
+
+
+def get_random_users(num_users):
+ users = [(f, l) for f in FIRST_NAMES for l in LAST_NAMES][:num_users]
+ names = []
+ for u in users:
+ names.append({"firstname": u[0],
+ "lastname": u[1],
+ "name": u[0] + " " + u[1],
+ "username": u[0] + u[1],
+ "email": u[0] + "." + u[1] + "@gmail.com",
+ "http_password": "secret",
+ "groups": []})
+ return names
+
+
+def generate_ssh_keys(gerrit_users):
+ for user in gerrit_users:
+ key_file = TMP_PATH + "/ssh/" + user["username"] + ".key"
+ subprocess.check_output(["ssh-keygen", "-f", key_file, "-N", ""])
+ with open(key_file + ".pub", "r") as f:
+ user["ssh_key"] = f.read()
+
+
+def create_gerrit_groups():
+ groups = [
+ {"name": "iOS-Maintainers", "description": "iOS Maintainers",
+ "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+ "owner_id": GROUP_ADMIN["id"]},
+ {"name": "Android-Maintainers", "description": "Android Maintainers",
+ "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+ "owner_id": GROUP_ADMIN["id"]},
+ {"name": "Backend-Maintainers", "description": "Backend Maintainers",
+ "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+ "owner_id": GROUP_ADMIN["id"]},
+ {"name": "Script-Maintainers", "description": "Script Maintainers",
+ "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+ "owner_id": GROUP_ADMIN["id"]},
+ {"name": "Security-Team", "description": "Sec Team",
+ "visible_to_all": False, "owner": GROUP_ADMIN["name"],
+ "owner_id": GROUP_ADMIN["id"]}]
+ for g in groups:
+ requests.put(GROUPS_URL + g["name"],
+ json.dumps(g),
+ headers=HEADERS,
+ auth=ADMIN_DIGEST)
+ return [g["name"] for g in groups]
+
+
+def create_gerrit_projects(owner_groups):
+ projects = [
+ {"id": "android", "name": "Android", "parent": "All-Projects",
+ "branches": ["master"], "description": "Our android app.",
+ "owners": [owner_groups[0]], "create_empty_commit": True},
+ {"id": "ios", "name": "iOS", "parent": "All-Projects",
+ "branches": ["master"], "description": "Our ios app.",
+ "owners": [owner_groups[1]], "create_empty_commit": True},
+ {"id": "backend", "name": "Backend", "parent": "All-Projects",
+ "branches": ["master"], "description": "Our awesome backend.",
+ "owners": [owner_groups[2]], "create_empty_commit": True},
+ {"id": "scripts", "name": "Scripts", "parent": "All-Projects",
+ "branches": ["master"], "description": "some small scripts.",
+ "owners": [owner_groups[3]], "create_empty_commit": True}]
+ for p in projects:
+ requests.put(PROJECTS_URL + p["name"],
+ json.dumps(p),
+ headers=HEADERS,
+ auth=ADMIN_DIGEST)
+ return [p["name"] for p in projects]
+
+
+def create_gerrit_users(gerrit_users):
+ for user in gerrit_users:
+ requests.put(ACCOUNTS_URL + user["username"],
+ json.dumps(user),
+ headers=HEADERS,
+ auth=ADMIN_DIGEST)
+
+
+def create_change(user, project_name):
+ random_commit_message = generate_random_text()
+ change = {
+ "project": project_name,
+ "subject": random_commit_message.split("\n")[0],
+ "branch": "master",
+ "status": "NEW",
+ }
+ requests.post(CHANGES_URL,
+ json.dumps(change),
+ headers=HEADERS,
+ auth=digest_auth(user))
+
+
+def clean_up():
+ shutil.rmtree(TMP_PATH)
+
+
+def main():
+ set_up()
+ gerrit_users = get_random_users(100)
+
+ group_names = create_gerrit_groups()
+ for idx, u in enumerate(gerrit_users):
+ u["groups"].append(group_names[idx % len(group_names)])
+ if idx % 5 == 0:
+ # Also add to security group
+ u["groups"].append(group_names[4])
+
+ generate_ssh_keys(gerrit_users)
+ create_gerrit_users(gerrit_users)
+
+ project_names = create_gerrit_projects(group_names)
+
+ for idx, u in enumerate(gerrit_users):
+ create_change(u, project_names[4 * idx / len(gerrit_users)])
+
+main()
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 1b3b9d9..f681fd5 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
@@ -81,7 +81,6 @@
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.util.Providers;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -532,7 +531,7 @@
private Context newRequestContext(TestAccount account) {
return atrScope.newContext(reviewDbProvider, new SshSession(server, account),
- identifiedUserFactory.create(Providers.of(db), account.getId()));
+ identifiedUserFactory.create(account.getId()));
}
protected Context setApiUser(TestAccount account) {
@@ -717,7 +716,7 @@
}
protected IdentifiedUser user(TestAccount testAccount) {
- return identifiedUserFactory.create(Providers.of(db), testAccount.getId());
+ return identifiedUserFactory.create(testAccount.getId());
}
protected RevisionResource parseCurrentRevisionResource(String changeId)
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index 84e7557..f2c701b 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -298,7 +298,7 @@
throws OrmException, NoSuchChangeException {
Iterable<Account.Id> actualIds = approvalsUtil
.getReviewers(db, notesFactory.createChecked(db, c))
- .values();
+ .all();
assertThat(actualIds).containsExactlyElementsIn(
Sets.newHashSet(TestAccount.ids(expectedReviewers)));
}
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 7dc4cda..5d038c4 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
@@ -629,7 +629,7 @@
.votes();
assertThat(m).hasSize(1);
- assertThat(m).containsEntry("Code-Review", new Short((short)2));
+ assertThat(m).containsEntry("Code-Review", Short.valueOf((short)2));
setApiUser(user);
gApi.changes()
@@ -643,7 +643,7 @@
.votes();
assertThat(m).hasSize(1);
- assertThat(m).containsEntry("Code-Review", new Short((short)-1));
+ assertThat(m).containsEntry("Code-Review", Short.valueOf((short)-1));
}
@Test
@@ -681,7 +681,7 @@
// 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));
+ assertThat(m).containsEntry("Code-Review", Short.valueOf((short)0));
}
ChangeInfo c = gApi.changes()
@@ -1090,7 +1090,7 @@
@Test
public void noteDbCommitsOnPatchSetCreation() throws Exception {
- assume().that(notesMigration.enabled()).isTrue();
+ assume().that(notesMigration.readChanges()).isTrue();
PushOneCommit.Result r = createChange();
pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
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 fd7b359..e63b28ba 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
@@ -456,8 +456,8 @@
@Test
public void testPushForMasterWithHashtagsNoteDbDisabled() throws Exception {
- // push with hashtags should fail when noteDb is disabled
- assume().that(notesMigration.enabled()).isFalse();
+ // Push with hashtags should fail when reading from NoteDb is disabled.
+ assume().that(notesMigration.readChanges()).isFalse();
PushOneCommit.Result r = pushTo("refs/for/master%hashtag=tag1");
r.assertErrorStatus("cannot add hashtags; noteDb is disabled");
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index bb1a656..b504d25 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.SubscribeSection;
import com.google.gerrit.reviewdb.client.Project;
@@ -35,14 +36,19 @@
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
- protected TestRepository<?> createProjectWithPush(String name)
- throws Exception {
- Project.NameKey project = createProject(name);
+ protected TestRepository<?> createProjectWithPush(String name,
+ @Nullable Project.NameKey parent) throws Exception {
+ Project.NameKey project = createProject(name, parent);
grant(Permission.PUSH, project, "refs/heads/*");
grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
return cloneProject(project);
}
+ protected TestRepository<?> createProjectWithPush(String name)
+ throws Exception {
+ return createProjectWithPush(name, null);
+ }
+
private static AtomicInteger contentCounter = new AtomicInteger(0);
protected ObjectId pushChangeTo(TestRepository<?> repo, String ref,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSectionParserIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSectionParserIT.java
new file mode 100644
index 0000000..6a4454f
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSectionParserIT.java
@@ -0,0 +1,370 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
+import com.google.gerrit.server.util.SubmoduleSectionParser;
+
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.util.Set;
+
+public class SubmoduleSectionParserIT extends AbstractDaemonTest {
+ private static final String THIS_SERVER = "http://localhost/";
+
+ @Test
+ public void testFollowMasterBranch() throws Exception {
+ Project.NameKey p = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = localpath-to-a\n"
+ + "url = ssh://localhost/" + p.get() + "\n"
+ + "branch = master\n");
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p, "master"), "localpath-to-a"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testFollowMatchingBranch() throws Exception {
+ Project.NameKey p = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = a\n"
+ + "url = ssh://localhost/" + p.get() + "\n"
+ + "branch = .\n");
+
+ Branch.NameKey targetBranch1 = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res1 = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch1).parseAllSections();
+
+ Set<SubmoduleSubscription> expected1 = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch1, new Branch.NameKey(
+ p, "master"), "a"));
+
+ assertThat(res1).containsExactlyElementsIn(expected1);
+
+ Branch.NameKey targetBranch2 = new Branch.NameKey(
+ new Project.NameKey("project"), "somebranch");
+
+ Set<SubmoduleSubscription> res2 = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch2).parseAllSections();
+
+ Set<SubmoduleSubscription> expected2 = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch2, new Branch.NameKey(
+ p, "somebranch"), "a"));
+
+ assertThat(res2).containsExactlyElementsIn(expected2);
+ }
+
+ @Test
+ public void testFollowAnotherBranch() throws Exception {
+ Project.NameKey p = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = a\n"
+ + "url = ssh://localhost/" + p.get() + "\n"
+ + "branch = anotherbranch\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p, "anotherbranch"), "a"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithAnotherURI() throws Exception {
+ Project.NameKey p = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = a\n"
+ + "url = http://localhost:80/" + p.get() + "\n"
+ + "branch = master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res =new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p, "master"), "a"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithSlashesInProjectName() throws Exception {
+ Project.NameKey p = createProject("project/with/slashes/a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"project/with/slashes/a\"]\n"
+ + "path = a\n"
+ + "url = http://localhost:80/" + p.get() + "\n"
+ + "branch = master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p, "master"), "a"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithSlashesInPath() throws Exception {
+ Project.NameKey p = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = a/b/c/d/e\n"
+ + "url = http://localhost:80/" + p.get() + "\n"
+ + "branch = master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p, "master"), "a/b/c/d/e"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithMoreSections() throws Exception {
+ Project.NameKey p1 = createProject("a");
+ Project.NameKey p2 = createProject("b");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + " path = a\n"
+ + " url = ssh://localhost/" + p1.get() + "\n"
+ + " branch = .\n"
+ + "[submodule \"b\"]\n"
+ + " path = b\n"
+ + " url = http://localhost:80/" + p2.get() + "\n"
+ + " branch = master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p1, "master"), "a"),
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p2, "master"), "b"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithSubProjectFound() throws Exception {
+ Project.NameKey p1 = createProject("a/b");
+ Project.NameKey p2 = createProject("b");
+ Config cfg = new Config();
+ cfg.fromText("\n"
+ + "[submodule \"a/b\"]\n"
+ + "path = a/b\n"
+ + "url = ssh://localhost/" + p1.get() + "\n"
+ + "branch = .\n"
+ + "[submodule \"b\"]\n"
+ + "path = b\n"
+ + "url = http://localhost/" + p2.get() + "\n"
+ + "branch = .\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p2, "master"), "b"),
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p1, "master"), "a/b"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithAnInvalidSection() throws Exception {
+ Project.NameKey p1 = createProject("a");
+ Project.NameKey p2 = createProject("b");
+ Project.NameKey p3 = createProject("d");
+ Project.NameKey p4 = createProject("e");
+ Config cfg = new Config();
+ cfg.fromText("\n"
+ + "[submodule \"a\"]\n"
+ + " path = a\n"
+ + " url = ssh://localhost/" + p1.get() + "\n"
+ + " branch = .\n"
+ + "[submodule \"b\"]\n"
+ // path missing
+ + " url = http://localhost:80/" + p2.get() + "\n"
+ + " branch = master\n"
+ + "[submodule \"c\"]\n"
+ + " path = c\n"
+ // url missing
+ + " branch = .\n"
+ + "[submodule \"d\"]\n"
+ + " path = d-parent/the-d-folder\n"
+ + " url = ssh://localhost/" + p3.get() + "\n"
+ // branch missing
+ + "[submodule \"e\"]\n"
+ + " path = e\n"
+ + " url = ssh://localhost/" + p4.get() + "\n"
+ + " branch = refs/heads/master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p1, "master"), "a"),
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p4, "master"), "e"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithSectionOfNonexistingProject() throws Exception {
+ Config cfg = new Config();
+ cfg.fromText("\n"
+ + "[submodule \"a\"]\n"
+ + "path = a\n"
+ + "url = ssh://non-localhost/a\n"
+ // Project "a" doesn't exist
+ + "branch = .\\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ assertThat(res).isEmpty();
+ }
+
+ @Test
+ public void testWithSectionToOtherServer() throws Exception {
+ Project.NameKey p1 = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]"
+ + "path = a"
+ + "url = ssh://non-localhost/" + p1.get() + "\n"
+ + "branch = .");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ assertThat(res).isEmpty();
+ }
+
+ @Test
+ public void testWithRelativeURI() throws Exception {
+ Project.NameKey p1 = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = a\n"
+ + "url = ../" + p1.get() + "\n"
+ + "branch = master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p1, "master"), "a"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void testWithDeepRelativeURI() throws Exception {
+ Project.NameKey p1 = createProject("a");
+ Config cfg = new Config();
+ cfg.fromText(""
+ + "[submodule \"a\"]\n"
+ + "path = a\n"
+ + "url = ../../" + p1.get() + "\n"
+ + "branch = master\n");
+
+ Branch.NameKey targetBranch = new Branch.NameKey(
+ new Project.NameKey("nested/project"), "master");
+
+ Set<SubmoduleSubscription> res = new SubmoduleSectionParser(
+ cfg, THIS_SERVER, targetBranch).parseAllSections();
+
+ Set<SubmoduleSubscription> expected = Sets.newHashSet(
+ new SubmoduleSubscription(targetBranch, new Branch.NameKey(
+ p1, "master"), "a"));
+
+ assertThat(res).containsExactlyElementsIn(expected);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index ccaf4ac..4af4a41 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -18,6 +18,7 @@
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.testutil.ConfigSuite;
import org.eclipse.jgit.junit.TestRepository;
@@ -42,9 +43,11 @@
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
- assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+ assertThat(hasSubmodule(superRepo, "master",
+ "subscribed-to-project")).isFalse();
}
@Test
@@ -54,7 +57,8 @@
allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
"super-project", "refs/heads/master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
expectToHaveSubmoduleState(superRepo, "master",
@@ -69,7 +73,8 @@
"super-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEAD);
@@ -86,8 +91,10 @@
pushChangeTo(superRepo, "branch");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
- createSubmoduleSubscription(superRepo, "branch", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "branch",
+ "subscribed-to-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
@@ -110,8 +117,10 @@
pushChangeTo(subRepo, "branch");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
- createSubmoduleSubscription(superRepo, "branch", "subscribed-to-project", "branch");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "branch",
+ "subscribed-to-project", "branch");
ObjectId subHEAD1 = pushChangeTo(subRepo, "master");
ObjectId subHEAD2 = pushChangeTo(subRepo, "branch");
@@ -122,7 +131,8 @@
"subscribed-to-project", subHEAD2);
// Now test that cross subscriptions do not work:
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "branch");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "branch");
ObjectId subHEAD3 = pushChangeTo(subRepo, "branch");
expectToHaveSubmoduleState(superRepo, "master",
@@ -140,7 +150,8 @@
"super-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
// The first update doesn't include any commit messages
ObjectId subRepoId = pushChangeTo(subRepo, "master");
@@ -165,7 +176,8 @@
"super-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
ObjectId subHEAD = pushChangeTo(subRepo, "master");
// The first update doesn't include the rev log
@@ -195,7 +207,8 @@
"super-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
@@ -213,14 +226,16 @@
}
@Test
- public void testSubscriptionUnsubscribeByDeletingGitModules() throws Exception {
+ public void testSubscriptionUnsubscribeByDeletingGitModules()
+ throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
"super-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
@@ -244,7 +259,8 @@
allowSubmoduleSubscription("subscribed-to-project", "refs/heads/foo",
"super-project", "refs/heads/master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "foo");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "foo");
ObjectId subFoo = pushChangeTo(subRepo, "foo");
pushChangeTo(subRepo, "master");
@@ -262,16 +278,18 @@
"subscribed-to-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ pushChangeTo(superRepo, "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
createSubmoduleSubscription(subRepo, "master", "super-project", "master");
- ObjectId subHEAD = pushChangeTo(subRepo, "master");
+ pushChangeTo(subRepo, "master");
pushChangeTo(superRepo, "master");
- expectToHaveSubmoduleState(superRepo, "master",
- "subscribed-to-project", subHEAD);
-
- assertThat(hasSubmodule(subRepo, "master", "super-project")).isFalse();
+ assertThat(hasSubmodule(subRepo, "master",
+ "super-project")).isFalse();
+ assertThat(hasSubmodule(superRepo, "master",
+ "subscribed-to-project")).isFalse();
}
@@ -281,9 +299,11 @@
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
- assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+ assertThat(hasSubmodule(superRepo, "master",
+ "subscribed-to-project")).isFalse();
}
@Test
@@ -294,9 +314,11 @@
"wrong-super-project", "refs/heads/master");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
- assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+ assertThat(hasSubmodule(superRepo, "master",
+ "subscribed-to-project")).isFalse();
}
@Test
@@ -307,9 +329,28 @@
"super-project", "refs/heads/wrong-branch");
pushChangeTo(subRepo, "master");
- createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
pushChangeTo(subRepo, "master");
- assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+ assertThat(hasSubmodule(superRepo, "master",
+ "subscribed-to-project")).isFalse();
+ }
+
+ @Test
+ public void testSubscriptionInheritACL() throws Exception {
+ createProjectWithPush("config-repo");
+ TestRepository<?> superRepo = createProjectWithPush("super-project",
+ new Project.NameKey(name("config-repo")));
+ TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+ allowSubmoduleSubscription("config-repo", "refs/heads/master",
+ "super-project", "refs/heads/wrong-branch");
+
+ pushChangeTo(subRepo, "master");
+ createSubmoduleSubscription(superRepo, "master",
+ "subscribed-to-project", "master");
+ pushChangeTo(subRepo, "master");
+ assertThat(hasSubmodule(superRepo, "master",
+ "subscribed-to-project")).isFalse();
}
private void deleteAllSubscriptions(TestRepository<?> repo, String branch)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 30482dd..0a067e9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -93,6 +93,71 @@
}
@Test
+ public void testSubscriptionUpdateIncludingChangeInSuperproject() throws Exception {
+ TestRepository<?> superRepo = createProjectWithPush("super-project");
+ TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+ allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
+ "super-project", "refs/heads/master");
+
+ createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+
+ ObjectId subHEAD = subRepo.branch("HEAD").commit().insertChangeId()
+ .message("some change")
+ .add("a.txt", "a contents ")
+ .create();
+ subRepo.git().push().setRemote("origin").setRefSpecs(
+ new RefSpec("HEAD:refs/heads/master")).call();
+
+ RevCommit c = subRepo.getRevWalk().parseCommit(subHEAD);
+
+ RevCommit c1 = subRepo.branch("HEAD").commit().insertChangeId()
+ .message("first change")
+ .add("asdf", "asdf\n")
+ .create();
+ subRepo.git().push().setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/for/master/" + name("topic-foo")))
+ .call();
+
+ subRepo.reset(c.getId());
+ RevCommit c2 = subRepo.branch("HEAD").commit().insertChangeId()
+ .message("qwerty")
+ .add("qwerty", "qwerty")
+ .create();
+
+ RevCommit c3 = subRepo.branch("HEAD").commit().insertChangeId()
+ .message("qwerty followup")
+ .add("qwerty", "qwerty\nqwerty\n")
+ .create();
+ subRepo.git().push().setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/for/master/" + name("topic-foo")))
+ .call();
+
+ RevCommit c4 = superRepo.branch("HEAD").commit().insertChangeId()
+ .message("new change on superproject")
+ .add("foo", "bar")
+ .create();
+ superRepo.git().push().setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/for/master/" + name("topic-foo")))
+ .call();
+
+ String id1 = getChangeId(subRepo, c1).get();
+ String id2 = getChangeId(subRepo, c2).get();
+ String id3 = getChangeId(subRepo, c3).get();
+ String id4 = getChangeId(superRepo, c4).get();
+ gApi.changes().id(id1).current().review(ReviewInput.approve());
+ gApi.changes().id(id2).current().review(ReviewInput.approve());
+ gApi.changes().id(id3).current().review(ReviewInput.approve());
+ gApi.changes().id(id4).current().review(ReviewInput.approve());
+
+ gApi.changes().id(id1).current().submit();
+ ObjectId subRepoId = subRepo.git().fetch().setRemote("origin").call()
+ .getAdvertisedRef("refs/heads/master").getObjectId();
+
+ expectToHaveSubmoduleState(superRepo, "master",
+ "subscribed-to-project", subRepoId);
+ }
+
+ @Test
public void testUpdateManySubmodules() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> sub1 = createProjectWithPush("sub1");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
index cfe04a2..3e2b9a6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -38,7 +38,6 @@
import com.google.gerrit.server.project.Util;
import com.google.gerrit.testutil.DisabledReviewDb;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -272,7 +271,7 @@
AcceptanceTestRequestScope.Context ctx = disableDb();
try (Repository repo = repoManager.openRepository(project)) {
ProjectControl ctl = projectControlFactory.controlFor(project,
- identifiedUserFactory.create(Providers.of(db), user.getId()));
+ identifiedUserFactory.create(user.getId()));
VisibleRefFilter filter = new VisibleRefFilter(
tagCache, changeCache, repo, ctl, new DisabledReviewDb(), true);
Map<String, Ref> all = repo.getAllRefs();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 5403e0d..7fb91f1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -95,7 +95,7 @@
return submitWholeTopicEnabledConfig();
}
- private Map<String, String> mergeResults;
+ private Map<String, String> changeMergedEvents;
@Inject
private ApprovalsUtil approvalsUtil;
@@ -127,7 +127,7 @@
@Before
public void setUp() throws Exception {
- mergeResults = new HashMap<>();
+ changeMergedEvents = new HashMap<>();
eventListenerRegistration =
eventListeners.add(new UserScopedEventListener() {
@Override
@@ -139,7 +139,7 @@
ChangeAttribute c = e.change.get();
PatchSetAttribute ps = e.patchSet.get();
log.debug("Merged {},{} as {}", ps.number, c.number, e.newRev);
- mergeResults.put(e.change.get().number, e.newRev);
+ changeMergedEvents.put(e.change.get().number, e.newRev);
}
@Override
@@ -305,8 +305,8 @@
// newRev of the ChangeMergedEvent.
BranchInfo branch = gApi.projects().name(change.project)
.branch(change.branch).get();
- assertThat(mergeResults).isNotEmpty();
- String newRev = mergeResults.get(Integer.toString(change._number));
+ assertThat(changeMergedEvents).isNotEmpty();
+ String newRev = changeMergedEvents.get(Integer.toString(change._number));
assertThat(newRev).isNotNull();
assertThat(branch.revision).isEqualTo(newRev);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
index b8f0ec9..6781ef1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.Util;
@@ -56,6 +57,18 @@
@Test
@TestProjectInput(cloneAs = "user")
public void updateProjectConfig() throws Exception {
+ String id = testUpdateProjectConfig();
+ assertThat(gApi.changes().id(id).get().revisions).hasSize(1);
+ }
+
+ @Test
+ @TestProjectInput(cloneAs = "user", submitType = SubmitType.CHERRY_PICK)
+ public void updateProjectConfigWithCherryPick() throws Exception {
+ String id = testUpdateProjectConfig();
+ assertThat(gApi.changes().id(id).get().revisions).hasSize(2);
+ }
+
+ private String testUpdateProjectConfig() throws Exception {
Config cfg = readProjectConfig();
assertThat(cfg.getString("project", null, "description")).isNull();
String desc = "new project description";
@@ -74,6 +87,11 @@
fetchRefsMetaConfig();
assertThat(readProjectConfig().getString("project", null, "description"))
.isEqualTo(desc);
+ String changeRev = gApi.changes().id(id).get().currentRevision;
+ String branchRev = gApi.projects().name(project.get())
+ .branch("refs/meta/config").get().revision;
+ assertThat(changeRev).isEqualTo(branchRev);
+ return id;
}
@Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 924eb4d..f8640bd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -116,7 +116,7 @@
@Test
public void noteDbCommit() throws Exception {
- assume().that(notesMigration.enabled()).isTrue();
+ assume().that(notesMigration.readChanges()).isTrue();
ChangeInfo c = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
try (Repository repo = repoManager.openRepository(project);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/DiffPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/DiffPreferencesIT.java
new file mode 100644
index 0000000..e68b4af
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/DiffPreferencesIT.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+public class DiffPreferencesIT extends AbstractDaemonTest {
+
+ @Test
+ public void GetDiffPreferences() throws Exception {
+ DiffPreferencesInfo result = get();
+ assertPrefsEqual(result, DiffPreferencesInfo.defaults());
+ }
+
+ @Test
+ public void SetDiffPreferences() throws Exception {
+ int newLineLength = DiffPreferencesInfo.defaults().lineLength + 10;
+ DiffPreferencesInfo update = new DiffPreferencesInfo();
+ update.lineLength = newLineLength;
+ DiffPreferencesInfo result = put(update);
+ assertThat(result.lineLength).named("lineLength").isEqualTo(newLineLength);
+
+ result = get();
+ DiffPreferencesInfo expected = DiffPreferencesInfo.defaults();
+ expected.lineLength = newLineLength;
+ assertPrefsEqual(result, expected);
+ }
+
+ private DiffPreferencesInfo get() throws Exception {
+ RestResponse r = adminRestSession.get("/config/server/preferences.diff");
+ r.assertOK();
+ return newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
+ }
+
+ private DiffPreferencesInfo put(DiffPreferencesInfo input) throws Exception {
+ RestResponse r = adminRestSession.put(
+ "/config/server/preferences.diff", input);
+ r.assertOK();
+ return newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
+ }
+
+ private void assertPrefsEqual(DiffPreferencesInfo actual,
+ DiffPreferencesInfo expected) throws Exception {
+ for (Field field : actual.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ Object actualField = field.get(actual);
+ Object expectedField = field.get(expected);
+ Class<?> type = field.getType();
+ if ((type == boolean.class || type == Boolean.class)
+ && actualField == null) {
+ continue;
+ }
+ assertThat(actualField).named(field.getName()).isEqualTo(expectedField);
+ }
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 9a5dfeb..255fe57 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -16,16 +16,407 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.access.AccessSectionInfo;
+import com.google.gerrit.extensions.api.access.PermissionInfo;
+import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import org.eclipse.jgit.lib.Constants;
+import org.junit.Before;
import org.junit.Test;
+import java.util.HashMap;
+
public class AccessIT extends AbstractDaemonTest {
+
+ private final String PROJECT_NAME = "newProject";
+
+ private final String REFS_ALL = Constants.R_REFS + "*";
+ private final String REFS_HEADS = Constants.R_HEADS + "*";
+
+ private final String LABEL_CODE_REVIEW = "Code-Review";
+
+ private String newProjectName;
+ private ProjectApi pApi;
+
+ @Before
+ public void setUp() throws Exception {
+ newProjectName = createProject(PROJECT_NAME).get();
+ pApi = gApi.projects().name(newProjectName);
+ }
+
@Test
- public void testGetDefaultInheritance() throws Exception {
- String newProjectName = createProject("newProjectAccess").get();
- String inheritedName = gApi.projects()
- .name(newProjectName).access().inheritsFrom.name;
+ public void getDefaultInheritance() throws Exception {
+ String inheritedName = pApi.access().inheritsFrom.name;
assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
}
+
+ @Test
+ public void addAccessSection() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi.access(accessInput);
+
+ assertThat(pApi.access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void removePermission() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi.access(accessInput);
+
+ // Remove specific permission
+ AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+ accessSectionToRemove.permissions
+ .put(Permission.LABEL + LABEL_CODE_REVIEW, newPermissionInfo());
+ ProjectAccessInput removal = newProjectAccessInput();
+ removal.remove.put(REFS_HEADS, accessSectionToRemove);
+ pApi.access(removal);
+
+ // Remove locally
+ accessInput.add.get(REFS_HEADS).permissions
+ .remove(Permission.LABEL + LABEL_CODE_REVIEW);
+
+ // Check
+ assertThat(pApi.access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void removePermissionRule() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi.access(accessInput);
+
+ // Remove specific permission rule
+ AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+ PermissionInfo codeReview = newPermissionInfo();
+ codeReview.label = LABEL_CODE_REVIEW;
+ PermissionRuleInfo pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(
+ SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionToRemove.permissions
+ .put(Permission.LABEL +LABEL_CODE_REVIEW, codeReview);
+ ProjectAccessInput removal = newProjectAccessInput();
+ removal.remove.put(REFS_HEADS, accessSectionToRemove);
+ pApi.access(removal);
+
+ // Remove locally
+ accessInput.add.get(REFS_HEADS).permissions
+ .get(Permission.LABEL + LABEL_CODE_REVIEW)
+ .rules.remove(SystemGroupBackend.REGISTERED_USERS.get());
+
+ // Check
+ assertThat(pApi.access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void removePermissionRulesAndCleanupEmptyEntries() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi.access(accessInput);
+
+ // Remove specific permission rules
+ AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+ PermissionInfo codeReview = newPermissionInfo();
+ codeReview.label = LABEL_CODE_REVIEW;
+ PermissionRuleInfo pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(
+ SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(
+ SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+ accessSectionToRemove.permissions
+ .put(Permission.LABEL +LABEL_CODE_REVIEW, codeReview);
+ ProjectAccessInput removal = newProjectAccessInput();
+ removal.remove.put(REFS_HEADS, accessSectionToRemove);
+ pApi.access(removal);
+
+ // Remove locally
+ accessInput.add.get(REFS_HEADS)
+ .permissions.remove(Permission.LABEL + LABEL_CODE_REVIEW);
+
+ // Check
+ assertThat(pApi.access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void getPermissionsWithDisallowedUser() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
+
+ // Disallow READ
+ accessInput.add.put(REFS_ALL, accessSectionInfo);
+ pApi.access(accessInput);
+
+ setApiUser(user);
+ exception.expect(ResourceNotFoundException.class);
+ gApi.projects().name(newProjectName).access();
+ }
+
+ @Test
+ public void setPermissionsWithDisallowedUser() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
+
+ // Disallow READ
+ accessInput.add.put(REFS_ALL, accessSectionInfo);
+ pApi.access(accessInput);
+
+ // Create a change to apply
+ ProjectAccessInput accessInfoToApply = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfoToApply =
+ createDefaultAccessSectionInfo();
+ accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply);
+
+ setApiUser(user);
+ exception.expect(ResourceNotFoundException.class);
+ gApi.projects().name(newProjectName).access();
+ }
+
+ @Test
+ public void updateParentAsUser() throws Exception {
+ // Create child
+ String newParentProjectName = createProject(PROJECT_NAME + "PA").get();
+
+ // Set new parent
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ accessInput.parent = newParentProjectName;
+
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ exception.expectMessage("not administrator");
+ gApi.projects().name(newProjectName).access(accessInput);
+ }
+
+ @Test
+ public void updateParentAsAdministrator() throws Exception {
+ // Create parent
+ String newParentProjectName = createProject(PROJECT_NAME + "PA").get();
+
+ // Set new parent
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ accessInput.parent = newParentProjectName;
+
+ gApi.projects().name(newProjectName).access(accessInput);
+
+ assertThat(pApi.access().inheritsFrom.name).isEqualTo(newParentProjectName);
+ }
+
+ @Test
+ public void addGlobalCapabilityAsUser() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo =
+ createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ }
+
+ @Test
+ public void addGlobalCapabilityAsAdmin() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo =
+ createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ ProjectAccessInfo updatedAccessSectionInfo =
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThat(updatedAccessSectionInfo.local.get(
+ AccessSection.GLOBAL_CAPABILITIES).permissions.keySet())
+ .containsAllIn(accessSectionInfo.permissions.keySet());
+ }
+
+ @Test
+ public void addGlobalCapabilityForNonRootProject() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo =
+ createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ exception.expect(BadRequestException.class);
+ pApi.access(accessInput);
+ }
+
+ @Test
+ public void addNonGlobalCapabilityToGlobalCapabilities() throws Exception {
+ AccountGroup adminGroup =
+ groupCache.get(new AccountGroup.NameKey("Administrators"));
+
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo permissionInfo = newPermissionInfo();
+ permissionInfo.rules.put(
+ adminGroup.getGroupUUID().get(), null);
+ accessSectionInfo.permissions.put(Permission.PUSH,
+ permissionInfo);
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ exception.expect(BadRequestException.class);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ }
+
+ @Test
+ public void removeGlobalCapabilityAsUser() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo =
+ createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ }
+
+ @Test
+ public void removeGlobalCapabilityAsAdmin() throws Exception {
+ AccountGroup adminGroup =
+ groupCache.get(new AccountGroup.NameKey("Administrators"));
+
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo permissionInfo = newPermissionInfo();
+ permissionInfo.rules.put(
+ adminGroup.getGroupUUID().get(), null);
+ accessSectionInfo.permissions.put(GlobalCapability.ACCESS_DATABASE,
+ permissionInfo);
+
+ // Add and validate first as removing existing privileges such as
+ // administrateServer would break upcoming tests
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ ProjectAccessInfo updatedProjectAccessInfo =
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThat(updatedProjectAccessInfo.local.get(
+ AccessSection.GLOBAL_CAPABILITIES).permissions.keySet())
+ .containsAllIn(accessSectionInfo.permissions.keySet());
+
+ // Remove
+ accessInput.add.clear();
+ accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES,
+ accessSectionInfo);
+
+ updatedProjectAccessInfo =
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThat(updatedProjectAccessInfo.local.get(
+ AccessSection.GLOBAL_CAPABILITIES).permissions.keySet())
+ .containsNoneIn(accessSectionInfo.permissions.keySet());
+ }
+
+ private ProjectAccessInput newProjectAccessInput() {
+ ProjectAccessInput p = new ProjectAccessInput();
+ p.add = new HashMap<>();
+ p.remove = new HashMap<>();
+ return p;
+ }
+
+ private PermissionInfo newPermissionInfo() {
+ PermissionInfo p = new PermissionInfo(null, null);
+ p.rules = new HashMap<>();
+ return p;
+ }
+
+ private AccessSectionInfo newAccessSectionInfo() {
+ AccessSectionInfo a = new AccessSectionInfo();
+ a.permissions = new HashMap<>();
+ return a;
+ }
+
+ private AccessSectionInfo createDefaultAccessSectionInfo() {
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo push = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.ALLOW, false);
+ push.rules.put(
+ SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(Permission.PUSH, push);
+
+ PermissionInfo codeReview = newPermissionInfo();
+ codeReview.label = LABEL_CODE_REVIEW;
+ pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(
+ SystemGroupBackend.REGISTERED_USERS.get(), pri);
+
+ pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.ALLOW, false);
+ pri.max = 1;
+ pri.min = -1;
+ codeReview.rules.put(
+ SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+ accessSection.permissions.put(Permission.LABEL
+ + LABEL_CODE_REVIEW, codeReview);
+
+ return accessSection;
+ }
+
+
+ private AccessSectionInfo createDefaultGlobalCapabilitiesAccessSectionInfo() {
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo email = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.ALLOW, false);
+ email.rules.put(
+ SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(GlobalCapability.EMAIL_REVIEWERS, email);
+
+ return accessSection;
+ }
+
+ private AccessSectionInfo createAccessSectionInfoDenyAll() {
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo read = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(
+ PermissionRuleInfo.Action.DENY, false);
+ read.rules.put(
+ SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
+ accessSection.permissions.put(Permission.READ, read);
+
+ return accessSection;
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 80a349b..ea4909f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -17,6 +17,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -35,7 +38,6 @@
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
@@ -57,7 +59,9 @@
import com.google.inject.Provider;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.Before;
@@ -298,14 +302,14 @@
putDraft(user, id, 1, "comment by user");
ObjectId userDraftsId = getMetaRef(
- allUsers, RefNames.refsDraftComments(id, user.getId()));
+ allUsers, refsDraftComments(id, user.getId()));
assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
changeMetaId.name()
+ "," + user.getId() + "=" + userDraftsId.name());
putDraft(admin, id, 2, "comment by admin");
ObjectId adminDraftsId = getMetaRef(
- allUsers, RefNames.refsDraftComments(id, admin.getId()));
+ allUsers, refsDraftComments(id, admin.getId()));
assertThat(admin.getId().get()).isLessThan(user.getId().get());
assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
changeMetaId.name()
@@ -314,7 +318,7 @@
putDraft(admin, id, 2, "revised comment by admin");
adminDraftsId = getMetaRef(
- allUsers, RefNames.refsDraftComments(id, admin.getId()));
+ allUsers, refsDraftComments(id, admin.getId()));
assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
changeMetaId.name()
+ "," + admin.getId() + "=" + adminDraftsId.name()
@@ -603,6 +607,32 @@
.isEqualTo(oldNotes.getChange().getTopic());
}
+ @Test
+ public void rebuildDeletesOldDraftRefs() throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change.Id id = r.getPatchSetId().getParentKey();
+ putDraft(user, id, 1, "comment");
+
+ Account.Id otherAccountId = new Account.Id(user.getId().get() + 1234);
+ String otherDraftRef = refsDraftComments(id, otherAccountId);
+
+ try (Repository repo = repoManager.openRepository(allUsers);
+ ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId sha = ins.insert(OBJ_BLOB, "garbage data".getBytes(UTF_8));
+ ins.flush();
+ RefUpdate ru = repo.updateRef(otherDraftRef);
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(sha);
+ assertThat(ru.update()).isEqualTo(RefUpdate.Result.NEW);
+ }
+
+ checker.rebuildAndCheckChanges(id);
+
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ assertThat(repo.exactRef(otherDraftRef)).isNull();
+ }
+ }
+
private void setInvalidNoteDbState(Change.Id id) throws Exception {
ReviewDb db = unwrapDb();
Change c = db.changes().get(id);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index f21f894..993820b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -75,6 +75,10 @@
}
}
+ public void addPermission(Permission p) {
+ getPermissions().add(p);
+ }
+
public void remove(Permission permission) {
if (permission != null) {
removePermission(permission.getName());
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java
index b05f335..6cee630 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java
@@ -68,4 +68,18 @@
public Collection<RefSpec> getRefSpecs() {
return Collections.unmodifiableCollection(refSpecs);
}
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ ret.append("[SubscribeSection, project=");
+ ret.append(project);
+ ret.append(", refs=[");
+ for (RefSpec r : refSpecs) {
+ ret.append(r.toString());
+ ret.append(", ");
+ }
+ ret.append("]");
+ return ret.toString();
+ }
}
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 67bfdc1..9b83c5a 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -38,6 +38,9 @@
java_library(
name = 'api',
srcs = glob([SRC + '**/*.java']),
+ deps = [
+ '//gerrit-common:annotations',
+ ],
provided_deps = [
'//lib/guice:guice',
'//lib/guice:guice-assistedinject',
@@ -72,6 +75,7 @@
'//lib/guice:javax-inject',
'//lib/guice:guice_library',
'//lib/guice:guice-assistedinject',
+ '//gerrit-common:annotations',
],
visibility = ['PUBLIC'],
)
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java
index b97fd3b..7c47ec5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java
@@ -14,7 +14,22 @@
package com.google.gerrit.extensions.api.access;
import java.util.Map;
+import java.util.Objects;
public class AccessSectionInfo {
+
public Map<String, PermissionInfo> permissions;
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AccessSectionInfo) {
+ return Objects.equals(permissions, ((AccessSectionInfo) obj).permissions);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(permissions);
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java
index c6d25b3..c4808a5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.api.access;
import java.util.Map;
+import java.util.Objects;
public class PermissionInfo {
public String label;
@@ -24,4 +25,20 @@
this.label = label;
this.exclusive = exclusive;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof PermissionInfo) {
+ PermissionInfo p = (PermissionInfo) obj;
+ return Objects.equals(label, p.label)
+ && Objects.equals(exclusive, p.exclusive)
+ && Objects.equals(rules, p.rules);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(label, exclusive, rules);
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java
index 93990dc..f979039 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.gerrit.extensions.api.access;
+import java.util.Objects;
+
public class PermissionRuleInfo {
public enum Action {
ALLOW,
@@ -31,4 +33,21 @@
this.action = action;
this.force = force;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof PermissionRuleInfo) {
+ PermissionRuleInfo p = (PermissionRuleInfo) obj;
+ return Objects.equals(action, p.action)
+ && Objects.equals(force, p.force)
+ && Objects.equals(min, p.min)
+ && Objects.equals(max, p.max);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(action, force, min, max);
+ }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInput.java
new file mode 100644
index 0000000..39a5209
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInput.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.extensions.api.access;
+
+import java.util.Map;
+
+public class ProjectAccessInput {
+ public Map<String, AccessSectionInfo> remove;
+ public Map<String, AccessSectionInfo> add;
+ public String parent;
+ public String message;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 75cebaa..260d7ce 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -15,6 +15,7 @@
package com.google.gerrit.extensions.api.projects;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -30,6 +31,7 @@
void description(PutDescriptionInput in) throws RestApiException;
ProjectAccessInfo access() throws RestApiException;
+ ProjectAccessInfo access(ProjectAccessInput p) throws RestApiException;
ListRefsRequest<BranchInfo> branches();
ListRefsRequest<TagInfo> tags();
@@ -138,6 +140,12 @@
}
@Override
+ public ProjectAccessInfo access(ProjectAccessInput p)
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void description(PutDescriptionInput in)
throws RestApiException {
throw new NotImplementedException();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java
index a838baf..c20c9e0 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java
@@ -14,7 +14,9 @@
package com.google.gerrit.extensions.events;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.AccountInfo;
/** Notified when one or more references are modified. */
@ExtensionPoint
@@ -28,6 +30,10 @@
boolean isCreate();
boolean isDelete();
boolean isNonFastForward();
+ /**
+ * The updater, could be null if it's the server.
+ */
+ @Nullable AccountInfo getUpdater();
}
void onGitReferenceUpdated(Event event);
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index 903bf2e..fe6d719 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -169,7 +169,7 @@
if (extId == null) {
return CheckResult.bad("Key is not associated with any users");
}
- IdentifiedUser user = userFactory.create(db, extId.getAccountId());
+ IdentifiedUser user = userFactory.create(extId.getAccountId());
Set<String> allowedUserIds = getAllowedUserIds(user);
if (allowedUserIds.isEmpty()) {
return CheckResult.bad("No identities found for user");
diff --git a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index ebe8105..749360c 100644
--- a/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/gerrit-gpg/src/test/java/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -151,12 +151,12 @@
private IdentifiedUser addUser(String name) throws Exception {
AuthRequest req = AuthRequest.forUser(name);
Account.Id id = accountManager.authenticate(req).getAccountId();
- return userFactory.create(Providers.of(db), id);
+ return userFactory.create(id);
}
private IdentifiedUser reloadUser() {
accountCache.evict(userId);
- user = userFactory.create(Providers.of(db), userId);
+ user = userFactory.create(userId);
return user;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
index 20dd883..1d198ec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
@@ -31,10 +31,11 @@
*/
abstract class CommentGroup extends Composite {
+ final DisplaySide side;
+ final int line;
+
private final CommentManager manager;
private final CodeMirror cm;
- private final DisplaySide side;
- private final int line;
private final FlowPanel comments;
private LineWidget lineWidget;
private Timer resizeTimer;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
index 216fbda..ae6d3c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
@@ -21,6 +21,8 @@
import net.codemirror.lib.CodeMirror;
+import java.util.PriorityQueue;
+
/**
* LineWidget attached to a CodeMirror container.
*
@@ -28,14 +30,15 @@
* The group tracks all comment boxes on that same line, and also includes an
* empty padding element to keep subsequent lines vertically aligned.
*/
-class SideBySideCommentGroup extends CommentGroup {
+class SideBySideCommentGroup extends CommentGroup
+ implements Comparable<SideBySideCommentGroup> {
static void pair(SideBySideCommentGroup a, SideBySideCommentGroup b) {
- a.peer = b;
- b.peer = a;
+ a.peers.add(b);
+ b.peers.add(a);
}
private final Element padding;
- private SideBySideCommentGroup peer;
+ private final PriorityQueue<SideBySideCommentGroup> peers;
SideBySideCommentGroup(SideBySideCommentManager manager, CodeMirror cm, DisplaySide side,
int line) {
@@ -45,29 +48,42 @@
padding.setClassName(SideBySideTable.style.padding());
SideBySideChunkManager.focusOnClick(padding, cm.side());
getElement().appendChild(padding);
+ peers = new PriorityQueue<>();
}
SideBySideCommentGroup getPeer() {
- return peer;
+ return peers.peek();
}
@Override
void remove(DraftBox box) {
super.remove(box);
- if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
- resize();
- } else {
+ if (getBoxCount() == 0 && peers.size() == 1
+ && peers.peek().peers.size() > 1) {
+ SideBySideCommentGroup peer = peers.peek();
+ peer.peers.remove(this);
detach();
- peer.detach();
+ if (peer.getBoxCount() == 0 && peer.peers.size() == 1
+ && peer.peers.peek().getBoxCount() == 0) {
+ peer.detach();
+ } else {
+ peer.resize();
+ }
+ } else {
+ resize();
}
}
@Override
void init(DiffTable parent) {
- if (getLineWidget() == null && peer.getLineWidget() == null) {
- this.attach(parent);
- peer.attach(parent);
+ if (getLineWidget() == null) {
+ attach(parent);
+ }
+ for (CommentGroup peer : peers) {
+ if (peer.getLineWidget() == null) {
+ peer.attach(parent);
+ }
}
}
@@ -76,20 +92,20 @@
getLineWidget().onRedraw(new Runnable() {
@Override
public void run() {
- if (canComputeHeight() && peer.canComputeHeight()) {
+ if (canComputeHeight() && peers.peek().canComputeHeight()) {
if (getResizeTimer() != null) {
getResizeTimer().cancel();
setResizeTimer(null);
}
- adjustPadding(SideBySideCommentGroup.this, peer);
+ adjustPadding(SideBySideCommentGroup.this, peers.peek());
} else if (getResizeTimer() == null) {
setResizeTimer(new Timer() {
@Override
public void run() {
- if (canComputeHeight() && peer.canComputeHeight()) {
+ if (canComputeHeight() && peers.peek().canComputeHeight()) {
cancel();
setResizeTimer(null);
- adjustPadding(SideBySideCommentGroup.this, peer);
+ adjustPadding(SideBySideCommentGroup.this, peers.peek());
}
}
});
@@ -102,7 +118,7 @@
@Override
void resize() {
if (getLineWidget() != null) {
- adjustPadding(this, peer);
+ adjustPadding(this, peers.peek());
}
}
@@ -117,6 +133,16 @@
private static void adjustPadding(SideBySideCommentGroup a, SideBySideCommentGroup b) {
int apx = a.computeHeight();
int bpx = b.computeHeight();
+ for (SideBySideCommentGroup otherPeer : a.peers) {
+ if (otherPeer != b) {
+ bpx += otherPeer.computeHeight();
+ }
+ }
+ for (SideBySideCommentGroup otherPeer : b.peers) {
+ if (otherPeer != a) {
+ apx += otherPeer.computeHeight();
+ }
+ }
int h = Math.max(apx, bpx);
a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX);
b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX);
@@ -125,4 +151,14 @@
a.updateSelection();
b.updateSelection();
}
+
+ @Override
+ public int compareTo(SideBySideCommentGroup o) {
+ if (side == o.side) {
+ return line - o.line;
+ } else {
+ throw new IllegalStateException(
+ "Cannot compare SideBySideCommentGroup with different sides");
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
index a2af3a1c..2b83b71 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
@@ -23,6 +23,7 @@
import net.codemirror.lib.TextMarker.FromTo;
import java.util.Collection;
+import java.util.Map;
import java.util.SortedMap;
/** Tracks comment widgets for {@link SideBySide}. */
@@ -94,36 +95,33 @@
@Override
CommentGroup group(DisplaySide side, int line) {
- SideBySideCommentGroup w = (SideBySideCommentGroup) map(side).get(line);
- if (w != null) {
- return w;
+ CommentGroup existing = map(side).get(line);
+ if (existing != null) {
+ return existing;
}
- int lineA;
- int lineB;
- if (line == 0) {
- lineA = lineB = 0;
- } else if (side == DisplaySide.A) {
- lineA = line;
- lineB = host.lineOnOther(side, line - 1).getLine() + 1;
+ SideBySideCommentGroup newGroup = newGroup(side, line);
+ Map<Integer, CommentGroup> map =
+ side == DisplaySide.A ? sideA : sideB;
+ Map<Integer, CommentGroup> otherMap =
+ side == DisplaySide.A ? sideB : sideA;
+ map.put(line, newGroup);
+ int otherLine = host.lineOnOther(side, line - 1).getLine() + 1;
+ existing = map(side.otherSide()).get(otherLine);
+ CommentGroup otherGroup;
+ if (existing != null) {
+ otherGroup = existing;
} else {
- lineA = host.lineOnOther(side, line - 1).getLine() + 1;
- lineB = line;
+ otherGroup = newGroup(side.otherSide(), otherLine);
+ otherMap.put(otherLine, otherGroup);
}
-
- SideBySideCommentGroup a = newGroup(DisplaySide.A, lineA);
- SideBySideCommentGroup b = newGroup(DisplaySide.B, lineB);
- SideBySideCommentGroup.pair(a, b);
-
- sideA.put(lineA, a);
- sideB.put(lineB, b);
+ SideBySideCommentGroup.pair(newGroup, (SideBySideCommentGroup) otherGroup);
if (isAttached()) {
- a.init(host.getDiffTable());
- b.handleRedraw();
+ newGroup.init(host.getDiffTable());
+ otherGroup.handleRedraw();
}
-
- return side == DisplaySide.A ? a : b;
+ return newGroup;
}
private SideBySideCommentGroup newGroup(DisplaySide side, int line) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 7d8d22c..fa2e0e3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -161,6 +161,7 @@
key = manager.createKey(id);
val = manager.createVal(key, id, rememberMe, identity, null, null);
saveCookie();
+ user = identified.create(val.getAccountId());
}
/** Set the user account for this current request only. */
@@ -178,6 +179,7 @@
key = null;
val = null;
saveCookie();
+ user = anonymousProvider.get();
}
}
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 45e5615..2c67182 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
@@ -67,6 +67,8 @@
serve("/").with(HostPageServlet.class);
serve("/Gerrit").with(LegacyGerritServlet.class);
serve("/Gerrit/*").with(legacyGerritScreen());
+ // Forward PolyGerrit URLs to their respective GWT equivalents.
+ serveRegex("^/(c|q|x|admin|dashboard|settings)/(.*)").with(gerritUrl());
}
serve("/cat/*").with(CatServlet.class);
@@ -87,9 +89,6 @@
serve("/watched").with(query("is:watched status:open"));
serve("/starred").with(query("is:starred"));
- // Forward PolyGerrit URLs to their respective GWT equivalents.
- serveRegex("^/(c|q|x|admin|dashboard|settings)/(.*)").with(gerritUrl());
-
serveRegex("^/settings/?$").with(screen(PageLinks.SETTINGS));
serveRegex("^/register/?$").with(screen(PageLinks.REGISTER + "/"));
serveRegex("^/([1-9][0-9]*)/?$").with(directChangeById());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index beb0139..2190fe0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -488,7 +488,6 @@
String t = main.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
String n = main.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
String v = main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
- String u = main.getValue(Attributes.Name.IMPLEMENTATION_URL);
String a = main.getValue("Gerrit-ApiVersion");
html.append("<table class=\"plugin_info\">");
@@ -507,11 +506,6 @@
.append(v)
.append("</td></tr>\n");
}
- if (!Strings.isNullOrEmpty(u)) {
- html.append("<tr><th>URL</th><td>")
- .append(String.format("<a href=\"%s\">%s</a>", u, u))
- .append("</td></tr>\n");
- }
if (!Strings.isNullOrEmpty(a)) {
html.append("<tr><th>API Version</th><td>")
.append(a)
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 32a80fe..e8efd72 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
@@ -114,7 +114,7 @@
site = sp;
refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
staticServlet = ss;
- isNoteDbEnabled = migration.enabled();
+ isNoteDbEnabled = migration.readChanges();
pluginsLoadTimeout = getPluginsLoadTimeout(cfg);
getDiff = diffPref;
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 223629d..da19b5e 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
@@ -87,7 +87,7 @@
RevCommit commit = config.commit(md);
gitRefUpdated.fire(config.getProject().getNameKey(), RefNames.REFS_CONFIG,
- base, commit.getId());
+ base, commit.getId(), user.asIdentifiedUser().getAccount());
hooks.doRefUpdatedHook(
new Branch.NameKey(config.getProject().getNameKey(), RefNames.REFS_CONFIG),
base, commit.getId(), user.asIdentifiedUser().getAccount());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index d701afa..be061c7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -33,6 +33,7 @@
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.CacheRemovalListener;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeKindCacheImpl;
import com.google.gerrit.server.change.MergeabilityCacheImpl;
import com.google.gerrit.server.change.PatchSetInserter;
@@ -139,5 +140,8 @@
factory(CapabilityControl.Factory.class);
factory(ChangeData.Factory.class);
factory(ProjectState.Factory.class);
+
+ bind(ChangeJson.Factory.class).toProvider(
+ Providers.<ChangeJson.Factory>of(null));
}
}
diff --git a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 270e15c..e32a0d6 100644
--- a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -31,9 +31,6 @@
<requiredProperty key="Implementation-Vendor">
<defaultValue>Gerrit Code Review</defaultValue>
</requiredProperty>
- <requiredProperty key="Implementation-Url">
- <defaultValue>https://www.gerritcodereview.com/</defaultValue>
- </requiredProperty>
<requiredProperty key="gerritApiType">
<defaultValue>plugin</defaultValue>
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
index a6103b1..026e21d 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
@@ -50,7 +50,6 @@
#end
<Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
- <Implementation-URL>${Implementation-Url}</Implementation-URL>
<Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 3c3508c..32a603b 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -21,9 +21,6 @@
<requiredProperty key="Implementation-Vendor">
<defaultValue>Gerrit Code Review</defaultValue>
</requiredProperty>
- <requiredProperty key="Implementation-Url">
- <defaultValue>https://www.gerritcodereview.com/</defaultValue>
- </requiredProperty>
<requiredProperty key="Gwt-Version">
<defaultValue>2.7.0</defaultValue>
</requiredProperty>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
index d67c7cb..2c7fe88 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
@@ -45,7 +45,6 @@
<Gerrit-Module>${package}.Module</Gerrit-Module>
<Gerrit-HttpModule>${package}.HttpModule</Gerrit-HttpModule>
<Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
- <Implementation-URL>${Implementation-Url}</Implementation-URL>
<Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index fbf1e46..ef0e96c 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -21,9 +21,6 @@
<requiredProperty key="Implementation-Vendor">
<defaultValue>Gerrit Code Review</defaultValue>
</requiredProperty>
- <requiredProperty key="Implementation-Url">
- <defaultValue>https://gerrit.googlesource.com/</defaultValue>
- </requiredProperty>
<requiredProperty key="gerritApiType">
<defaultValue>js</defaultValue>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
index f24d81e..8f4aadd 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
@@ -44,7 +44,6 @@
<manifestEntries>
<Gerrit-PluginName>${pluginName}</Gerrit-PluginName>
<Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
- <Implementation-URL>${Implementation-Url}</Implementation-URL>
<Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
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 dc3a6b1..847d559 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
@@ -14,8 +14,6 @@
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;
@@ -23,13 +21,10 @@
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
-import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
-import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
@@ -45,7 +40,6 @@
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.ReviewerStateInternal;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.server.OrmException;
@@ -115,15 +109,14 @@
*
* @param db review database.
* @param notes change notes.
- * @return multimap of reviewers keyed by state, where each account appears
- * exactly once in {@link SetMultimap#values()}, and
- * {@link ReviewerStateInternal#REMOVED} is not present.
+ * @return reviewers for the change.
* @throws OrmException if reviewers for the change could not be read.
*/
- public ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers(
- ReviewDb db, ChangeNotes notes) throws OrmException {
+ public ReviewerSet getReviewers(ReviewDb db, ChangeNotes notes)
+ throws OrmException {
if (!migration.readChanges()) {
- return getReviewers(db.patchSetApprovals().byChange(notes.getChangeId()));
+ return ReviewerSet.fromApprovals(
+ db.patchSetApprovals().byChange(notes.getChangeId()));
}
return notes.load().getReviewers();
}
@@ -133,44 +126,18 @@
*
* @param allApprovals all approvals to consider; must all belong to the same
* change.
- * @return multimap of reviewers keyed by state, where each account appears
- * exactly once in {@link SetMultimap#values()}, and
- * {@link ReviewerStateInternal#REMOVED} is not present.
+ * @return reviewers for the change.
+ * @throws OrmException if reviewers for the change could not be read.
*/
- public ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers(
- ChangeNotes notes, Iterable<PatchSetApproval> allApprovals)
+ public ReviewerSet getReviewers(ChangeNotes notes,
+ Iterable<PatchSetApproval> allApprovals)
throws OrmException {
if (!migration.readChanges()) {
- return getReviewers(allApprovals);
+ return ReviewerSet.fromApprovals(allApprovals);
}
return notes.load().getReviewers();
}
- private static ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers(
- Iterable<PatchSetApproval> allApprovals) {
- PatchSetApproval first = null;
- SetMultimap<ReviewerStateInternal, Account.Id> reviewers =
- LinkedHashMultimap.create();
- for (PatchSetApproval psa : allApprovals) {
- if (first == null) {
- first = psa;
- } else {
- checkArgument(
- first.getKey().getParentKey().getParentKey().equals(
- psa.getKey().getParentKey().getParentKey()),
- "multiple change IDs: %s, %s", first.getKey(), psa.getKey());
- }
- Account.Id id = psa.getAccountId();
- if (psa.getValue() != 0) {
- reviewers.put(REVIEWER, id);
- reviewers.remove(CC, id);
- } else if (!reviewers.containsEntry(REVIEWER, id)) {
- reviewers.put(CC, id);
- }
- }
- return ImmutableSetMultimap.copyOf(reviewers);
- }
-
public List<PatchSetApproval> addReviewers(ReviewDb db,
ChangeUpdate update, LabelTypes labelTypes, Change change, PatchSet ps,
PatchSetInfo info, Iterable<Account.Id> wantReviewers,
@@ -185,7 +152,7 @@
Iterable<Account.Id> wantReviewers) throws OrmException {
PatchSet.Id psId = change.currentPatchSetId();
return addReviewers(db, update, labelTypes, change, psId, false, null, null,
- wantReviewers, getReviewers(db, notes).values());
+ wantReviewers, getReviewers(db, notes).all());
}
private List<PatchSetApproval> addReviewers(ReviewDb db, ChangeUpdate update,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 0493df3..c7f4c4a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -20,7 +20,6 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.CapabilityControl;
@@ -90,29 +89,20 @@
this.disableReverseDnsLookup = disableReverseDnsLookup;
}
- public IdentifiedUser create(final Account.Id id) {
+ public IdentifiedUser create(Account.Id id) {
return create((SocketAddress) null, id);
}
- public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
- return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
- authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, disableReverseDnsLookup, null, db, id, null);
- }
-
public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
- return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
- authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, disableReverseDnsLookup, Providers.of(remotePeer), null,
- id, null);
+ return runAs(remotePeer, id, null);
}
- public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
+ public IdentifiedUser runAs(SocketAddress remotePeer, Account.Id id,
@Nullable CurrentUser caller) {
return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, disableReverseDnsLookup, Providers.of(remotePeer), null,
- id, caller);
+ groupBackend, disableReverseDnsLookup, Providers.of(remotePeer), id,
+ caller);
}
}
@@ -133,23 +123,20 @@
private final AccountCache accountCache;
private final GroupBackend groupBackend;
private final Boolean disableReverseDnsLookup;
-
private final Provider<SocketAddress> remotePeerProvider;
- private final Provider<ReviewDb> dbProvider;
@Inject
RequestFactory(
CapabilityControl.Factory capabilityControlFactory,
@Nullable StarredChangesUtil starredChangesUtil,
- final AuthConfig authConfig,
+ AuthConfig authConfig,
Realm realm,
- @AnonymousCowardName final String anonymousCowardName,
- @CanonicalWebUrl final Provider<String> canonicalUrl,
- final AccountCache accountCache,
- final GroupBackend groupBackend,
- @DisableReverseDnsLookup final Boolean disableReverseDnsLookup,
- @RemotePeer final Provider<SocketAddress> remotePeerProvider,
- final Provider<ReviewDb> dbProvider) {
+ @AnonymousCowardName String anonymousCowardName,
+ @CanonicalWebUrl Provider<String> canonicalUrl,
+ AccountCache accountCache,
+ GroupBackend groupBackend,
+ @DisableReverseDnsLookup Boolean disableReverseDnsLookup,
+ @RemotePeer Provider<SocketAddress> remotePeerProvider) {
this.capabilityControlFactory = capabilityControlFactory;
this.starredChangesUtil = starredChangesUtil;
this.authConfig = authConfig;
@@ -160,21 +147,19 @@
this.groupBackend = groupBackend;
this.disableReverseDnsLookup = disableReverseDnsLookup;
this.remotePeerProvider = remotePeerProvider;
- this.dbProvider = dbProvider;
}
public IdentifiedUser create(Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, disableReverseDnsLookup, remotePeerProvider, dbProvider,
- id, null);
+ groupBackend, disableReverseDnsLookup, remotePeerProvider, id, null);
}
public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
return new IdentifiedUser(capabilityControlFactory, starredChangesUtil,
authConfig, realm, anonymousCowardName, canonicalUrl, accountCache,
- groupBackend, disableReverseDnsLookup, remotePeerProvider, dbProvider,
- id, caller);
+ groupBackend, disableReverseDnsLookup, remotePeerProvider, id,
+ caller);
}
}
@@ -196,12 +181,7 @@
private final Set<String> validEmails =
Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER);
- @Nullable
private final Provider<SocketAddress> remotePeerProvider;
-
- @Nullable
- private final Provider<ReviewDb> dbProvider;
-
private final Account.Id accountId;
private AccountState state;
@@ -224,7 +204,6 @@
final GroupBackend groupBackend,
final Boolean disableReverseDnsLookup,
@Nullable final Provider<SocketAddress> remotePeerProvider,
- @Nullable final Provider<ReviewDb> dbProvider,
final Account.Id id,
@Nullable CurrentUser realUser) {
super(capabilityControlFactory);
@@ -237,7 +216,6 @@
this.anonymousCowardName = anonymousCowardName;
this.disableReverseDnsLookup = disableReverseDnsLookup;
this.remotePeerProvider = remotePeerProvider;
- this.dbProvider = dbProvider;
this.accountId = id;
this.realUser = realUser != null ? realUser : this;
}
@@ -386,14 +364,11 @@
user = user + "|" + "account-" + ua.getId().toString();
String host = null;
- if (remotePeerProvider != null) {
- final SocketAddress remotePeer = remotePeerProvider.get();
- if (remotePeer instanceof InetSocketAddress) {
- final InetSocketAddress sa = (InetSocketAddress) remotePeer;
- final InetAddress in = sa.getAddress();
-
- host = in != null ? getHost(in) : sa.getHostName();
- }
+ SocketAddress remotePeer = remotePeerProvider.get();
+ if (remotePeer instanceof InetSocketAddress) {
+ InetSocketAddress sa = (InetSocketAddress) remotePeer;
+ InetAddress in = sa.getAddress();
+ host = in != null ? getHost(in) : sa.getHostName();
}
if (host == null || host.isEmpty()) {
host = "unknown";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerSet.java
new file mode 100644
index 0000000..515cef7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerSet.java
@@ -0,0 +1,115 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+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.collect.HashBasedTable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableTable;
+import com.google.common.collect.Table;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.notedb.ReviewerStateInternal;
+
+import java.sql.Timestamp;
+
+/**
+ * Set of reviewers on a change.
+ * <p>
+ * A given account may appear in multiple states and at different timestamps. No
+ * reviewers with state {@link ReviewerStateInternal#REMOVED} are ever exposed
+ * by this interface.
+ */
+public class ReviewerSet {
+ private static final ReviewerSet EMPTY = new ReviewerSet(
+ ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>of());
+
+ public static ReviewerSet fromApprovals(
+ Iterable<PatchSetApproval> approvals) {
+ PatchSetApproval first = null;
+ Table<ReviewerStateInternal, Account.Id, Timestamp> reviewers =
+ HashBasedTable.create();
+ for (PatchSetApproval psa : approvals) {
+ if (first == null) {
+ first = psa;
+ } else {
+ checkArgument(
+ first.getKey().getParentKey().getParentKey().equals(
+ psa.getKey().getParentKey().getParentKey()),
+ "multiple change IDs: %s, %s", first.getKey(), psa.getKey());
+ }
+ Account.Id id = psa.getAccountId();
+ if (psa.getValue() != 0) {
+ reviewers.put(REVIEWER, id, psa.getGranted());
+ reviewers.remove(CC, id);
+ } else if (!reviewers.contains(REVIEWER, id)) {
+ reviewers.put(CC, id, psa.getGranted());
+ }
+ }
+ return new ReviewerSet(reviewers);
+ }
+
+ public static ReviewerSet fromTable(
+ Table<ReviewerStateInternal, Account.Id, Timestamp> table) {
+ return new ReviewerSet(table);
+ }
+
+ public static ReviewerSet empty() {
+ return EMPTY;
+ }
+
+ private final ImmutableTable<ReviewerStateInternal, Account.Id, Timestamp>
+ table;
+ private ImmutableSet<Account.Id> accounts;
+
+ private ReviewerSet(Table<ReviewerStateInternal, Account.Id, Timestamp> table) {
+ this.table = ImmutableTable.copyOf(table);
+ }
+
+ public ImmutableSet<Account.Id> all() {
+ if (accounts == null) {
+ // Idempotent and immutable, don't bother locking.
+ accounts = ImmutableSet.copyOf(table.columnKeySet());
+ }
+ return accounts;
+ }
+
+ public ImmutableSet<Account.Id> byState(ReviewerStateInternal state) {
+ return table.row(state).keySet();
+ }
+
+ public ImmutableTable<ReviewerStateInternal, Account.Id, Timestamp>
+ asTable() {
+ return table;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof ReviewerSet) && table.equals(((ReviewerSet) o).table);
+ }
+
+ @Override
+ public int hashCode() {
+ return table.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + table;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
index be87ae7..2c4a840 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -31,11 +32,17 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.lang.reflect.Field;
@Singleton
public class GetDiffPreferences implements RestReadView<AccountResource> {
+ private static final Logger log =
+ LoggerFactory.getLogger(GetDiffPreferences.class);
+
private final Provider<CurrentUser> self;
private final Provider<AllUsersName> allUsersName;
private final GitRepositoryManager gitMgr;
@@ -66,13 +73,41 @@
DiffPreferencesInfo in)
throws IOException, ConfigInvalidException, RepositoryNotFoundException {
try (Repository git = gitMgr.openRepository(allUsersName)) {
+ // Load all users prefs.
+ VersionedAccountPreferences dp =
+ VersionedAccountPreferences.forDefault();
+ dp.load(git);
+ DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
+ loadSection(dp.getConfig(), UserConfigSections.DIFF, null, allUserPrefs,
+ DiffPreferencesInfo.defaults(), in);
+
+ // Load user prefs
VersionedAccountPreferences p =
VersionedAccountPreferences.forUser(id);
p.load(git);
DiffPreferencesInfo prefs = new DiffPreferencesInfo();
loadSection(p.getConfig(), UserConfigSections.DIFF, null, prefs,
- DiffPreferencesInfo.defaults(), in);
+ updateDefaults(allUserPrefs), in);
return prefs;
}
}
+
+ private static DiffPreferencesInfo updateDefaults(DiffPreferencesInfo input) {
+ DiffPreferencesInfo result = DiffPreferencesInfo.defaults();
+ try {
+ for (Field field : input.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ Object newVal = field.get(input);
+ if (newVal != null) {
+ field.set(result, newVal);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ log.warn("Cannot get default diff preferences from All-Users", e);
+ return DiffPreferencesInfo.defaults();
+ }
+ return result;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index b8bd905..e8baefe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ChildProjectApi;
@@ -44,6 +45,7 @@
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gerrit.server.project.PutDescription;
+import com.google.gerrit.server.project.SetAccess;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -74,6 +76,7 @@
private final GetAccess getAccess;
private final ListBranches listBranches;
private final ListTags listTags;
+ private final SetAccess setAccess;
@AssistedInject
ProjectApiImpl(CurrentUser user,
@@ -88,12 +91,13 @@
BranchApiImpl.Factory branchApiFactory,
TagApiImpl.Factory tagApiFactory,
GetAccess getAccess,
+ SetAccess setAccess,
ListBranches listBranches,
ListTags listTags,
@Assisted ProjectResource project) {
this(user, createProjectFactory, projectApi, projects, getDescription,
putDescription, childApi, children, projectJson, branchApiFactory,
- tagApiFactory, getAccess, listBranches, listTags,
+ tagApiFactory, getAccess, setAccess, listBranches, listTags,
project, null);
}
@@ -110,12 +114,13 @@
BranchApiImpl.Factory branchApiFactory,
TagApiImpl.Factory tagApiFactory,
GetAccess getAccess,
+ SetAccess setAccess,
ListBranches listBranches,
ListTags listTags,
@Assisted String name) {
this(user, createProjectFactory, projectApi, projects, getDescription,
putDescription, childApi, children, projectJson, branchApiFactory,
- tagApiFactory, getAccess, listBranches, listTags,
+ tagApiFactory, getAccess, setAccess, listBranches, listTags,
null, name);
}
@@ -131,6 +136,7 @@
BranchApiImpl.Factory branchApiFactory,
TagApiImpl.Factory tagApiFactory,
GetAccess getAccess,
+ SetAccess setAccess,
ListBranches listBranches,
ListTags listTags,
ProjectResource project,
@@ -149,6 +155,7 @@
this.branchApi = branchApiFactory;
this.tagApi = tagApiFactory;
this.getAccess = getAccess;
+ this.setAccess = setAccess;
this.listBranches = listBranches;
this.listTags = listTags;
}
@@ -199,6 +206,16 @@
}
@Override
+ public ProjectAccessInfo access(ProjectAccessInput p)
+ throws RestApiException {
+ try {
+ return setAccess.apply(checkExists(), p);
+ } catch (IOException e) {
+ throw new RestApiException("Cannot put access rights", e);
+ }
+ }
+
+ @Override
public void description(PutDescriptionInput in)
throws RestApiException {
try {
@@ -260,7 +277,8 @@
}
@Override
- public List<ProjectInfo> children(boolean recursive) throws RestApiException {
+ public List<ProjectInfo> children(boolean recursive)
+ throws RestApiException {
ListChildProjects list = children.list();
list.setRecursive(recursive);
return list.apply(checkExists());
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 eaf51e4..f93bb72 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
@@ -442,10 +442,10 @@
out.removableReviewers = removableReviewers(ctl, out.labels.values());
out.reviewers = new HashMap<>();
- for (Map.Entry<ReviewerStateInternal, Collection<Account.Id>> e
- : cd.reviewers().asMap().entrySet()) {
+ for (Map.Entry<ReviewerStateInternal, Map<Account.Id, Timestamp>> e
+ : cd.reviewers().asTable().rowMap().entrySet()) {
out.reviewers.put(e.getKey().asReviewerState(),
- toAccountInfo(e.getValue()));
+ toAccountInfo(e.getValue().keySet()));
}
}
@@ -617,7 +617,7 @@
// - They are an explicit reviewer.
// - They ever voted on this change.
Set<Account.Id> allUsers = new HashSet<>();
- allUsers.addAll(cd.reviewers().values());
+ allUsers.addAll(cd.reviewers().all());
for (PatchSetApproval psa : cd.approvals().values()) {
allUsers.add(psa.getAccountId());
}
@@ -967,7 +967,7 @@
if (in.getPushCertificate() != null) {
out.pushCertificate = gpgApi.checkPushCertificate(
in.getPushCertificate(),
- userFactory.create(db, in.getUploader()));
+ userFactory.create(in.getUploader()));
} else {
out.pushCertificate = new PushCertificateInfo();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
index 1e3480f..9a0c691 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
@@ -51,7 +51,7 @@
Map<Account.Id, ReviewerResource> reviewers = new LinkedHashMap<>();
ReviewDb db = dbProvider.get();
for (Account.Id accountId
- : approvalsUtil.getReviewers(db, rsrc.getNotes()).values()) {
+ : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
if (!reviewers.containsKey(accountId)) {
reviewers.put(accountId, resourceFactory.create(rsrc, accountId));
}
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 3defdd7..ffbfc36 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
@@ -19,10 +19,8 @@
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;
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;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -33,6 +31,7 @@
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.BatchUpdate;
@@ -43,7 +42,6 @@
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.notedb.ChangeUpdate;
-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.RefControl;
@@ -106,7 +104,7 @@
private PatchSet patchSet;
private PatchSetInfo patchSetInfo;
private ChangeMessage changeMessage;
- private SetMultimap<ReviewerStateInternal, Account.Id> oldReviewers;
+ private ReviewerSet oldReviewers;
@AssistedInject
public PatchSetInserter(ChangeHooks hooks,
@@ -264,8 +262,8 @@
cm.setFrom(ctx.getUser().getAccountId());
cm.setPatchSet(patchSet, patchSetInfo);
cm.setChangeMessage(changeMessage);
- cm.addReviewers(oldReviewers.get(REVIEWER));
- cm.addExtraCC(oldReviewers.get(CC));
+ cm.addReviewers(oldReviewers.byState(REVIEWER));
+ cm.addExtraCC(oldReviewers.byState(CC));
cm.send();
} catch (Exception err) {
log.error("Cannot send email for new patch set on change "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index ff5185e..4304669 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -207,7 +207,7 @@
throws OrmException, IOException {
LabelTypes labelTypes = ctx.getControl().getLabelTypes();
Collection<Account.Id> oldReviewers = approvalsUtil.getReviewers(
- ctx.getDb(), ctx.getNotes()).values();
+ ctx.getDb(), ctx.getNotes()).all();
RevCommit commit = ctx.getRevWalk().parseCommit(
ObjectId.fromString(patchSet.getRevision().get()));
patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java
index 74a8866..d45d260 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java
@@ -83,6 +83,6 @@
private Collection<Account.Id> fetchAccountIds(ChangeResource rsrc)
throws OrmException {
return approvalsUtil.getReviewers(
- dbProvider.get(), rsrc.getNotes()).values();
+ dbProvider.get(), rsrc.getNotes()).all();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
index 5846400..4e6bd2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.git.BatchUpdate.Context;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.validators.HashtagValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
@@ -51,6 +52,7 @@
SetHashtagsOp create(HashtagsInput input);
}
+ private final NotesMigration notesMigration;
private final ChangeMessagesUtil cmUtil;
private final ChangeHooks hooks;
private final DynamicSet<HashtagValidationListener> validationListeners;
@@ -65,10 +67,12 @@
@AssistedInject
SetHashtagsOp(
+ NotesMigration notesMigration,
ChangeMessagesUtil cmUtil,
ChangeHooks hooks,
DynamicSet<HashtagValidationListener> validationListeners,
@Assisted @Nullable HashtagsInput input) {
+ this.notesMigration = notesMigration;
this.cmUtil = cmUtil;
this.hooks = hooks;
this.validationListeners = validationListeners;
@@ -83,6 +87,9 @@
@Override
public boolean updateChange(ChangeContext ctx)
throws AuthException, BadRequestException, OrmException, IOException {
+ if (!notesMigration.readChanges()) {
+ throw new BadRequestException("Cannot add hashtags; NoteDb is disabled");
+ }
if (input == null
|| (input.add == null && input.remove == null)) {
updatedHashtags = ImmutableSortedSet.of();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
index 8cbdf14..40df57c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -64,8 +64,7 @@
return new VisibilityControl() {
@Override
public boolean isVisibleTo(Account.Id account) throws OrmException {
- IdentifiedUser who =
- identifiedUserFactory.create(dbProvider, account);
+ IdentifiedUser who = identifiedUserFactory.create(account);
// we can't use changeControl directly as it won't suggest reviewers
// to drafts
return rsrc.getControl().forUser(who).isRefVisible();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index e124e48..eaeb850 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -382,16 +382,16 @@
return s;
}
+ public static boolean skipField(Field field) {
+ int modifiers = field.getModifiers();
+ return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
+ }
+
private static boolean isCollectionOrMap(Class<?> t) {
return Collection.class.isAssignableFrom(t)
|| Map.class.isAssignableFrom(t);
}
- private static boolean skipField(Field field) {
- int modifiers = field.getModifiers();
- return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
- }
-
private static boolean isString(Class<?> t) {
return String.class == t;
}
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 73954b5..5bbfd3d 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
@@ -142,7 +142,6 @@
import com.google.gerrit.server.ssh.SshAddressesModule;
import com.google.gerrit.server.tools.ToolsCatalog;
import com.google.gerrit.server.util.IdGenerator;
-import com.google.gerrit.server.util.SubmoduleSectionParser;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.validators.GroupCreationValidationListener;
import com.google.gerrit.server.validators.HashtagValidationListener;
@@ -332,7 +331,6 @@
factory(MergeValidators.Factory.class);
factory(ProjectConfigValidator.Factory.class);
factory(NotesBranchUtil.Factory.class);
- factory(SubmoduleSectionParser.Factory.class);
factory(ReplaceOp.Factory.class);
factory(GitModules.Factory.class);
factory(VersionedAuthorizedKeys.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetDiffPreferences.java
new file mode 100644
index 0000000..f0bab6e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetDiffPreferences.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.server.config;
+
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+
+@Singleton
+public class GetDiffPreferences implements RestReadView<ConfigResource> {
+
+ private final AllUsersName allUsersName;
+ private final GitRepositoryManager gitManager;
+
+ @Inject
+ GetDiffPreferences(GitRepositoryManager gitManager,
+ AllUsersName allUsersName) {
+ this.allUsersName = allUsersName;
+ this.gitManager = gitManager;
+ }
+
+ @Override
+ public DiffPreferencesInfo apply(ConfigResource configResource)
+ throws BadRequestException, ResourceConflictException, Exception {
+ return readFromGit(gitManager, allUsersName, null);
+ }
+
+ static DiffPreferencesInfo readFromGit(GitRepositoryManager gitMgr,
+ AllUsersName allUsersName, DiffPreferencesInfo in)
+ throws IOException, ConfigInvalidException, RepositoryNotFoundException {
+ try (Repository git = gitMgr.openRepository(allUsersName)) {
+ // Load all users prefs.
+ VersionedAccountPreferences dp =
+ VersionedAccountPreferences.forDefault();
+ dp.load(git);
+ DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
+ loadSection(dp.getConfig(), UserConfigSections.DIFF, null, allUserPrefs,
+ DiffPreferencesInfo.defaults(), in);
+ return allUserPrefs;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
index e909f17..a05058e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -38,6 +38,8 @@
get(CONFIG_KIND, "info").to(GetServerInfo.class);
get(CONFIG_KIND, "preferences").to(GetPreferences.class);
put(CONFIG_KIND, "preferences").to(SetPreferences.class);
+ get(CONFIG_KIND, "preferences.diff").to(GetDiffPreferences.class);
+ put(CONFIG_KIND, "preferences.diff").to(SetDiffPreferences.class);
put(CONFIG_KIND, "email.confirm").to(ConfirmEmail.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetDiffPreferences.java
new file mode 100644
index 0000000..155ffc2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetDiffPreferences.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.server.config;
+
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
+import static com.google.gerrit.server.config.ConfigUtil.storeSection;
+import static com.google.gerrit.server.config.GetDiffPreferences.readFromGit;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@Singleton
+public class SetDiffPreferences implements
+ RestModifyView<ConfigResource, DiffPreferencesInfo> {
+ private static final Logger log =
+ LoggerFactory.getLogger(SetDiffPreferences.class);
+
+ private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
+ private final AllUsersName allUsersName;
+ private final GitRepositoryManager gitManager;
+
+ @Inject
+ SetDiffPreferences(GitRepositoryManager gitManager,
+ Provider<MetaDataUpdate.User> metaDataUpdateFactory,
+ AllUsersName allUsersName) {
+ this.gitManager = gitManager;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.allUsersName = allUsersName;
+ }
+
+ @Override
+ public Object apply(ConfigResource configResource, DiffPreferencesInfo in)
+ throws BadRequestException, Exception {
+ if (in == null) {
+ throw new BadRequestException("input must be provided");
+ }
+ if (!hasSetFields(in)) {
+ throw new BadRequestException("unsupported option");
+ }
+ return writeToGit(readFromGit(gitManager, allUsersName, in));
+ }
+
+ private DiffPreferencesInfo writeToGit(DiffPreferencesInfo in)
+ throws RepositoryNotFoundException, IOException, ConfigInvalidException {
+ DiffPreferencesInfo out = new DiffPreferencesInfo();
+ try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
+ VersionedAccountPreferences prefs =
+ VersionedAccountPreferences.forDefault();
+ prefs.load(md);
+ DiffPreferencesInfo defaults = DiffPreferencesInfo.defaults();
+ storeSection(prefs.getConfig(), UserConfigSections.DIFF, null, in,
+ defaults);
+ prefs.commit(md);
+ loadSection(prefs.getConfig(), UserConfigSections.DIFF, null, out,
+ DiffPreferencesInfo.defaults(), null);
+ }
+ return out;
+ }
+
+ private static boolean hasSetFields(DiffPreferencesInfo in) {
+ try {
+ for (Field field : in.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ if (field.get(in) != null) {
+ return true;
+ }
+ }
+ } catch (IllegalAccessException e) {
+ log.warn("Unable to verify input", e);
+ }
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 5807c26..56daccc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -203,7 +203,7 @@
public void addAllReviewers(ReviewDb db, ChangeAttribute a, ChangeNotes notes)
throws OrmException {
Collection<Account.Id> reviewers =
- approvalsUtil.getReviewers(db, notes).values();
+ approvalsUtil.getReviewers(db, notes).all();
if (!reviewers.isEmpty()) {
a.allReviewers = Lists.newArrayListWithCapacity(reviewers.size());
for (Account.Id id : reviewers) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
new file mode 100644
index 0000000..cb30eea
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -0,0 +1,45 @@
+// 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.extensions.events;
+
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.inject.Inject;
+
+public class EventUtil {
+
+ private final AccountCache accountCache;
+
+ @Inject
+ EventUtil(AccountCache accountCache) {
+ this.accountCache = accountCache;
+ }
+
+ public AccountInfo accountInfo(Account a) {
+ if (a == null || a.getId() == null) {
+ return null;
+ }
+ AccountInfo ai = new AccountInfo(a.getId().get());
+ ai.email = a.getPreferredEmail();
+ ai.name = a.getFullName();
+ ai.username = a.getUserName();
+ return ai;
+ }
+
+ public AccountInfo accountInfo(Account.Id accountId) {
+ return accountInfo(accountCache.get(accountId).getAccount());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
index 7eef0ee..6eac07c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
@@ -14,8 +14,10 @@
package com.google.gerrit.server.extensions.events;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
@@ -26,42 +28,111 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Collections;
-
public class GitReferenceUpdated {
private static final Logger log = LoggerFactory
.getLogger(GitReferenceUpdated.class);
- public static final GitReferenceUpdated DISABLED = new GitReferenceUpdated(
- Collections.<GitReferenceUpdatedListener> emptyList());
+ public static final GitReferenceUpdated DISABLED = new GitReferenceUpdated() {
+ @Override
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ ReceiveCommand.Type type, Account updater) {}
+
+ @Override
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ ReceiveCommand.Type type, Account.Id updater) {}
+
+ @Override
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ Account updater) {}
+
+ @Override
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ AccountInfo updater) {}
+
+ @Override
+ public void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
+ ObjectId newObjectId, Account updater) {}
+
+ @Override
+ public void fire(Project.NameKey project, ReceiveCommand cmd,
+ Account updater) {}
+
+ @Override
+ public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate,
+ Account.Id updater) {}
+ };
private final Iterable<GitReferenceUpdatedListener> listeners;
+ private final EventUtil util;
@Inject
- GitReferenceUpdated(DynamicSet<GitReferenceUpdatedListener> listeners) {
+ GitReferenceUpdated(DynamicSet<GitReferenceUpdatedListener> listeners,
+ EventUtil util) {
this.listeners = listeners;
+ this.util = util;
}
- GitReferenceUpdated(Iterable<GitReferenceUpdatedListener> listeners) {
- this.listeners = listeners;
+ private GitReferenceUpdated() {
+ this.listeners = null;
+ this.util = null;
}
public void fire(Project.NameKey project, RefUpdate refUpdate,
- ReceiveCommand.Type type) {
+ ReceiveCommand.Type type, Account updater) {
fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
- refUpdate.getNewObjectId(), type);
+ refUpdate.getNewObjectId(), type, util.accountInfo(updater));
}
- public void fire(Project.NameKey project, RefUpdate refUpdate) {
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ ReceiveCommand.Type type, Account.Id updater) {
fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
- refUpdate.getNewObjectId(), ReceiveCommand.Type.UPDATE);
+ refUpdate.getNewObjectId(), type, util.accountInfo(updater));
+ }
+
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ Account updater) {
+ fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
+ refUpdate.getNewObjectId(), ReceiveCommand.Type.UPDATE,
+ util.accountInfo(updater));
+ }
+
+ public void fire(Project.NameKey project, RefUpdate refUpdate,
+ AccountInfo updater) {
+ fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
+ refUpdate.getNewObjectId(), ReceiveCommand.Type.UPDATE, updater);
}
public void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
- ObjectId newObjectId, ReceiveCommand.Type type) {
+ ObjectId newObjectId, Account updater) {
+ fire(project, ref, oldObjectId, newObjectId, ReceiveCommand.Type.UPDATE,
+ util.accountInfo(updater));
+ }
+
+ public void fire(Project.NameKey project, ReceiveCommand cmd, Account updater) {
+ fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId(), cmd.getType(),
+ util.accountInfo(updater));
+ }
+
+ public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate,
+ Account.Id updater) {
+ for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
+ if (cmd.getResult() == ReceiveCommand.Result.OK) {
+ fire(project, cmd, util.accountInfo(updater));
+ }
+ }
+ }
+
+ private void fire(Project.NameKey project, ReceiveCommand cmd,
+ AccountInfo updater) {
+ fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId(), cmd.getType(),
+ updater);
+ }
+
+ private void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
+ ObjectId newObjectId, ReceiveCommand.Type type, AccountInfo updater) {
ObjectId o = oldObjectId != null ? oldObjectId : ObjectId.zeroId();
ObjectId n = newObjectId != null ? newObjectId : ObjectId.zeroId();
- Event event = new Event(project, ref, o.name(), n.name(), type);
+ Event event = new Event(project, ref, o.name(), n.name(), type, updater);
for (GitReferenceUpdatedListener l : listeners) {
try {
l.onGitReferenceUpdated(event);
@@ -71,38 +142,24 @@
}
}
- public void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
- ObjectId newObjectId) {
- fire(project, ref, oldObjectId, newObjectId, ReceiveCommand.Type.UPDATE);
- }
-
- public void fire(Project.NameKey project, ReceiveCommand cmd) {
- fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId(), cmd.getType());
- }
-
- public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate) {
- for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
- if (cmd.getResult() == ReceiveCommand.Result.OK) {
- fire(project, cmd);
- }
- }
- }
-
private static class Event implements GitReferenceUpdatedListener.Event {
private final String projectName;
private final String ref;
private final String oldObjectId;
private final String newObjectId;
private final ReceiveCommand.Type type;
+ private final AccountInfo updater;
Event(Project.NameKey project, String ref,
String oldObjectId, String newObjectId,
- ReceiveCommand.Type type) {
+ ReceiveCommand.Type type,
+ AccountInfo updater) {
this.projectName = project.get();
this.ref = ref;
this.oldObjectId = oldObjectId;
this.newObjectId = newObjectId;
this.type = type;
+ this.updater = updater;
}
@Override
@@ -141,6 +198,11 @@
}
@Override
+ public AccountInfo getUpdater() {
+ return updater;
+ }
+
+ @Override
public String toString() {
return String.format("%s[%s,%s: %s -> %s]", getClass().getSimpleName(),
projectName, ref, oldObjectId, newObjectId);
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 982777d..3b3c78e 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
@@ -350,7 +350,10 @@
// callers may assume a patch set ref being created means the change
// was created, or a branch advancing meaning some changes were
// closed.
- u.gitRefUpdated.fire(u.project, u.batchRefUpdate);
+ u.gitRefUpdated.fire(
+ u.project,
+ u.batchRefUpdate,
+ u.getUser().isIdentifiedUser() ? u.getUser().getAccountId() : null);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
index d236682..f19c0aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
@@ -112,8 +112,7 @@
@Override
public CurrentUser getUser() {
if (submitter != null) {
- return identifiedUserFactory.create(
- getReviewDbProvider(), submitter).getRealUser();
+ return identifiedUserFactory.create(submitter).getRealUser();
}
throw new OutOfScopeException("No user on email thread");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java
index eb359e6..e6ce074 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitModules.java
@@ -35,8 +35,6 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
@@ -55,8 +53,7 @@
private static final String GIT_MODULES = ".gitmodules";
- private final String thisServer;
- private final SubmoduleSectionParser.Factory subSecParserFactory;
+ private final String canonicalWebUrl;
private final Branch.NameKey branch;
private final String submissionId;
private final MergeOpRepoManager orm;
@@ -66,20 +63,13 @@
@AssistedInject
GitModules(
@CanonicalWebUrl @Nullable String canonicalWebUrl,
- SubmoduleSectionParser.Factory subSecParserFactory,
@Assisted Branch.NameKey branch,
@Assisted String submissionId,
- @Assisted MergeOpRepoManager orm) throws SubmoduleException {
- this.subSecParserFactory = subSecParserFactory;
+ @Assisted MergeOpRepoManager orm) {
this.orm = orm;
this.branch = branch;
this.submissionId = submissionId;
- try {
- this.thisServer = new URI(canonicalWebUrl).getHost();
- } catch (URISyntaxException e) {
- throw new SubmoduleException("Incorrect Gerrit canonical web url " +
- "provided in gerrit.config file.", e);
- }
+ this.canonicalWebUrl = canonicalWebUrl;
}
void load() throws IOException {
@@ -106,7 +96,7 @@
try {
BlobBasedConfig bbc =
new BlobBasedConfig(null, or.repo, commit, GIT_MODULES);
- subscriptions = subSecParserFactory.create(bbc, thisServer,
+ subscriptions = new SubmoduleSectionParser(bbc, canonicalWebUrl,
branch).parseAllSections();
} catch (ConfigInvalidException e) {
throw new IOException(
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 26db045..30cf0a9 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
@@ -729,7 +729,7 @@
logDebug("Updating superprojects");
SubmoduleOp subOp = subOpProvider.get();
try {
- subOp.updateSuperProjects(db, branches, submissionId, orm);
+ subOp.updateSuperProjects(branches, submissionId, orm);
logDebug("Updating superprojects done");
} catch (SubmoduleException e) {
logError("The gitlinks were not updated according to the "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index 540d479..477da3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -125,8 +125,8 @@
public MetaDataUpdate create(Project.NameKey name, Repository repository,
IdentifiedUser user, BatchRefUpdate batch) {
MetaDataUpdate md = factory.create(name, repository, batch);
- md.getCommitBuilder().setAuthor(createPersonIdent(user));
md.getCommitBuilder().setCommitter(serverIdent);
+ md.setAuthor(user);
return md;
}
@@ -176,6 +176,7 @@
private final CommitBuilder commit;
private boolean allowEmpty;
private boolean insertChangeId;
+ private IdentifiedUser author;
@AssistedInject
public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
@@ -198,8 +199,9 @@
getCommitBuilder().setMessage(message);
}
- public void setAuthor(IdentifiedUser user) {
- getCommitBuilder().setAuthor(user.newCommitterIdent(
+ public void setAuthor(IdentifiedUser author) {
+ this.author = author;
+ getCommitBuilder().setAuthor(author.newCommitterIdent(
getCommitBuilder().getCommitter().getWhen(),
getCommitBuilder().getCommitter().getTimeZone()));
}
@@ -244,6 +246,7 @@
}
void fireGitRefUpdatedEvent(RefUpdate ru) {
- gitRefUpdated.fire(projectName, ru);
+ gitRefUpdated.fire(
+ projectName, ru, author == null ? null : author.getAccount());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
index 2c5e512..d7424c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -261,7 +262,7 @@
throw new IOException("Couldn't update " + notesBranch + ". "
+ result.name());
} else {
- gitRefUpdated.fire(project, refUpdate);
+ gitRefUpdated.fire(project, refUpdate, (AccountInfo) null);
break;
}
}
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 d07dea5..a0bc332 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
@@ -284,6 +284,41 @@
}
}
+ public void remove(AccessSection section, Permission permission) {
+ if (permission == null) {
+ remove(section);
+ } else if (section != null) {
+ AccessSection a = accessSections.get(section.getName());
+ a.remove(permission);
+ if (a.getPermissions().isEmpty()) {
+ remove(a);
+ }
+ }
+ }
+
+ public void remove(AccessSection section,
+ Permission permission, PermissionRule rule) {
+ if (rule == null) {
+ remove(section, permission);
+ } else if (section != null && permission != null) {
+ AccessSection a = accessSections.get(section.getName());
+ if (a == null) {
+ return;
+ }
+ Permission p = a.getPermission(permission.getName());
+ if (p == null) {
+ return;
+ }
+ p.remove(rule);
+ if (p.getRules().isEmpty()) {
+ a.remove(permission);
+ }
+ if (a.getPermissions().isEmpty()) {
+ remove(a);
+ }
+ }
+ }
+
public void replace(AccessSection section) {
for (Permission permission : section.getPermissions()) {
for (PermissionRule rule : permission.getRules()) {
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 9e1165f..b52844d 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
@@ -667,7 +667,7 @@
// We only fire gitRefUpdated for direct refs updates.
// Events for change refs are fired when they are created.
//
- gitRefUpdated.fire(project.getNameKey(), c);
+ gitRefUpdated.fire(project.getNameKey(), c, user.getAccount());
hooks.doRefUpdatedHook(
new Branch.NameKey(project.getNameKey(), c.getRefName()),
c.getOldId(),
@@ -680,7 +680,7 @@
SubmoduleOp op = subOpProvider.get();
try (MergeOpRepoManager orm = ormProvider.get()) {
orm.setContext(db, TimeUtil.nowTs(), user);
- op.updateSuperProjects(db, branches, "receiveID", orm);
+ op.updateSuperProjects(branches, "receiveID", orm);
} catch (SubmoduleException e) {
log.error("Can't update the superprojects", e);
}
@@ -937,7 +937,7 @@
case UPDATE_NONFASTFORWARD:
try {
ProjectConfig cfg = new ProjectConfig(project.getNameKey());
- cfg.load(repo, cmd.getNewId());
+ cfg.load(rp.getRevWalk(), cmd.getNewId());
if (!cfg.getValidationErrors().isEmpty()) {
addError("Invalid project configuration:");
for (ValidationError err : cfg.getValidationErrors()) {
@@ -1208,7 +1208,7 @@
@Option(name = "--hashtag", aliases = {"-t"}, metaVar = "HASHTAG",
usage = "add hashtag to changes")
void addHashtag(String token) throws CmdLineException {
- if (!notesMigration.enabled()) {
+ if (!notesMigration.readChanges()) {
throw clp.reject("cannot add hashtags; noteDb is disabled");
}
String hashtag = cleanupHashtag(token);
@@ -1787,10 +1787,12 @@
.setRequestScopePropagator(requestScopePropagator)
.setSendMail(true)
.setUpdateRef(true));
- bu.addOp(
- changeId,
- hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
- .setRunHooks(false));
+ if (!magicBranch.hashtags.isEmpty()) {
+ bu.addOp(
+ changeId,
+ hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
+ .setRunHooks(false));
+ }
if (!Strings.isNullOrEmpty(magicBranch.topic)) {
bu.addOp(
changeId,
@@ -2178,7 +2180,7 @@
PatchSet newPatchSet = replaceOp.getPatchSet();
gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
- ObjectId.zeroId(), newCommit);
+ ObjectId.zeroId(), newCommit, user.getAccount());
if (magicBranch != null && magicBranch.submit) {
submit(changeCtl, newPatchSet);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 63c71a1..0a9c839 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -16,7 +16,6 @@
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubscribeSection;
@@ -25,13 +24,13 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import org.eclipse.jgit.dircache.DirCache;
@@ -71,7 +70,7 @@
private final PersonIdent myIdent;
private final GitReferenceUpdated gitRefUpdated;
private final ProjectCache projectCache;
- private final Set<Branch.NameKey> updatedSubscribers;
+ private final ProjectState.Factory projectStateFactory;
private final Account account;
private final ChangeHooks changeHooks;
private final boolean verboseSuperProject;
@@ -85,19 +84,20 @@
@GerritServerConfig Config cfg,
GitReferenceUpdated gitRefUpdated,
ProjectCache projectCache,
+ ProjectState.Factory projectStateFactory,
@Nullable Account account,
ChangeHooks changeHooks) {
this.gitmodulesFactory = gitmodulesFactory;
this.myIdent = myIdent;
this.gitRefUpdated = gitRefUpdated;
this.projectCache = projectCache;
+ this.projectStateFactory = projectStateFactory;
this.account = account;
this.changeHooks = changeHooks;
this.verboseSuperProject = cfg.getBoolean("submodule",
"verboseSuperprojectUpdate", true);
this.enableSuperProjectSubscriptions = cfg.getBoolean("submodule",
"enableSuperProjectSubscriptions", true);
- updatedSubscribers = new HashSet<>();
}
public Collection<Branch.NameKey> getDestinationBranches(Branch.NameKey src,
@@ -143,20 +143,28 @@
Collection<SubmoduleSubscription> ret = new ArrayList<>();
Project.NameKey project = branch.getParentKey();
ProjectConfig cfg = projectCache.get(project).getConfig();
- for (SubscribeSection s : cfg.getSubscribeSections(branch)) {
+ for (SubscribeSection s : projectStateFactory.create(cfg)
+ .getSubscribeSections(branch)) {
+ logDebug("Checking subscribe section " + s);
Collection<Branch.NameKey> branches =
getDestinationBranches(branch, s, orm);
for (Branch.NameKey targetBranch : branches) {
GitModules m = gitmodulesFactory.create(targetBranch, updateId, orm);
m.load();
- ret.addAll(m.subscribedTo(branch));
+ for (SubmoduleSubscription ss : m.subscribedTo(branch)) {
+ logDebug("Checking SubmoduleSubscription " + ss);
+ if (projectCache.get(ss.getSubmodule().getParentKey()) != null) {
+ logDebug("Adding SubmoduleSubscription " + ss);
+ ret.add(ss);
+ }
+ }
}
}
logDebug("Calculated superprojects for " + branch + " are " + ret);
return ret;
}
- protected void updateSuperProjects(ReviewDb db,
+ protected void updateSuperProjects(
Collection<Branch.NameKey> updatedBranches, String updateId,
MergeOpRepoManager orm) throws SubmoduleException {
if (!enableSuperProjectSubscriptions) {
@@ -165,32 +173,50 @@
}
this.updateId = updateId;
logDebug("Updating superprojects");
- // These (repo/branch) will be updated later with all the given
- // individual submodule subscriptions
+
Multimap<Branch.NameKey, SubmoduleSubscription> targets =
HashMultimap.create();
- try {
- for (Branch.NameKey updatedBranch : updatedBranches) {
- for (SubmoduleSubscription sub :
- superProjectSubscriptionsForSubmoduleBranch(updatedBranch, orm)) {
- targets.put(sub.getSuperProject(), sub);
+ for (Branch.NameKey updatedBranch : updatedBranches) {
+ logDebug("Now processing " + updatedBranch);
+ Set<Branch.NameKey> checkedTargets = new HashSet<>();
+ Set<Branch.NameKey> targetsToProcess = new HashSet<>();
+ targetsToProcess.add(updatedBranch);
+
+ while (!targetsToProcess.isEmpty()) {
+ Set<Branch.NameKey> newTargets = new HashSet<>();
+ for (Branch.NameKey b : targetsToProcess) {
+ try {
+ Collection<SubmoduleSubscription> subs =
+ superProjectSubscriptionsForSubmoduleBranch(b, orm);
+ for (SubmoduleSubscription sub : subs) {
+ Branch.NameKey dst = sub.getSuperProject();
+ targets.put(dst, sub);
+ newTargets.add(dst);
+ }
+ } catch (IOException e) {
+ throw new SubmoduleException("Cannot find superprojects for " + b, e);
+ }
}
+ logDebug("adding to done " + targetsToProcess);
+ checkedTargets.addAll(targetsToProcess);
+ logDebug("completely done with " + checkedTargets);
+
+ Set<Branch.NameKey> intersection = new HashSet<>(checkedTargets);
+ intersection.retainAll(newTargets);
+ if (!intersection.isEmpty()) {
+ throw new SubmoduleException("Possible circular subscription involving " + updatedBranch);
+ }
+
+ targetsToProcess = newTargets;
}
- } catch (IOException e) {
- throw new SubmoduleException("Could not calculate all superprojects");
}
- updatedSubscribers.addAll(updatedBranches);
- // Update subscribers.
- for (Branch.NameKey dest : targets.keySet()) {
+
+ for (Branch.NameKey dst : targets.keySet()) {
try {
- if (!updatedSubscribers.add(dest)) {
- log.error("Possible circular subscription involving " + dest);
- } else {
- updateGitlinks(db, dest, targets.get(dest), orm);
- }
+ updateGitlinks(dst, targets.get(dst), orm);
} catch (SubmoduleException e) {
- log.warn("Cannot update gitlinks for " + dest, e);
+ throw new SubmoduleException("Cannot update gitlinks for " + dst, e);
}
}
}
@@ -202,7 +228,7 @@
* @param updates submodule updates which should be updated to.
* @throws SubmoduleException
*/
- private void updateGitlinks(ReviewDb db, Branch.NameKey subscriber,
+ private void updateGitlinks(Branch.NameKey subscriber,
Collection<SubmoduleSubscription> updates, MergeOpRepoManager orm)
throws SubmoduleException {
PersonIdent author = null;
@@ -324,7 +350,7 @@
switch (rfu.update()) {
case NEW:
case FAST_FORWARD:
- gitRefUpdated.fire(subscriber.getParentKey(), rfu);
+ gitRefUpdated.fire(subscriber.getParentKey(), rfu, account);
changeHooks.doRefUpdatedHook(subscriber, rfu, account);
// TODO since this is performed "in the background" no mail will be
// sent to inform users about the updated branch
@@ -340,8 +366,6 @@
default:
throw new IOException(rfu.getResult().name());
}
- // Recursive call: update subscribers of the subscriber
- updateSuperProjects(db, Sets.newHashSet(subscriber), updateId, orm);
} catch (IOException e) {
throw new SubmoduleException("Cannot update gitlinks for "
+ subscriber.get(), e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index ecba568..dc927a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -137,12 +137,36 @@
*/
public void load(Repository db, ObjectId id) throws IOException,
ConfigInvalidException {
- reader = db.newObjectReader();
+ try (RevWalk walk = new RevWalk(db)) {
+ load(walk, id);
+ }
+ }
+
+ /**
+ * Load a specific version from an open walk.
+ * <p>
+ * This method is primarily useful for applying updates to a specific revision
+ * that was shown to an end-user in the user interface. If there are conflicts
+ * with another user's concurrent changes, these will be automatically
+ * detected at commit time.
+ * <p>
+ * The caller retains ownership of the walk and is responsible for closing
+ * it. However, this instance does not hold a reference to the walk or the
+ * repository after the call completes, allowing the application to retain
+ * this object for long periods of time.
+ *
+ * @param walk open walk to access to access.
+ * @param id revision to load.
+ * @throws IOException
+ * @throws ConfigInvalidException
+ */
+ public void load(RevWalk walk, ObjectId id) throws IOException,
+ ConfigInvalidException {
+ this.reader = walk.getObjectReader();
try {
revision = id != null ? new RevWalk(reader).parseCommit(id) : null;
onLoad();
} finally {
- reader.close();
reader = null;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 01ae0b8..5e70b91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -145,7 +145,7 @@
logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
try {
ProjectConfig cfg = new ProjectConfig(getProject());
- cfg.load(ctx.getRepository(), commit);
+ cfg.load(ctx.getRevWalk(), commit);
} catch (Exception e) {
throw new IntegrationException("Submit would store invalid"
+ " project configuration " + commit.name() + " for "
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 367159d..ad543ea 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
@@ -111,7 +111,7 @@
appendText(velocifyFile("ChangeFooter.vm"));
try {
TreeSet<String> names = new TreeSet<>();
- for (Account.Id who : changeData.reviewers().values()) {
+ for (Account.Id who : changeData.reviewers().all()) {
names.add(getNameEmailFor(who));
}
for (String name : names) {
@@ -337,7 +337,7 @@
}
try {
- for (Account.Id id : changeData.reviewers().values()) {
+ for (Account.Id id : changeData.reviewers().all()) {
add(RecipientType.CC, id);
}
} catch (OrmException err) {
@@ -353,7 +353,7 @@
}
try {
- for (Account.Id id : changeData.reviewers().get(REVIEWER)) {
+ for (Account.Id id : changeData.reviewers().byState(REVIEWER)) {
add(RecipientType.CC, id);
}
} catch (OrmException err) {
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 af8a3f69..a1274c3 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
@@ -17,12 +17,11 @@
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.server.ReviewerSet;
import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.revwalk.FooterKey;
@@ -58,10 +57,10 @@
}
public static MailRecipients getRecipientsFromReviewers(
- Multimap<ReviewerStateInternal, Account.Id> reviewers) {
+ ReviewerSet reviewers) {
MailRecipients recipients = new MailRecipients();
- recipients.reviewers.addAll(reviewers.get(REVIEWER));
- recipients.cc.addAll(reviewers.get(CC));
+ recipients.reviewers.addAll(reviewers.byState(REVIEWER));
+ recipients.cc.addAll(reviewers.byState(CC));
return recipients;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 374b2e9..8a80bfe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -174,8 +174,7 @@
private boolean add(Watchers matching, AccountProjectWatch w, NotifyType type)
throws OrmException {
- IdentifiedUser user =
- args.identifiedUserFactory.create(args.db, w.getAccountId());
+ IdentifiedUser user = args.identifiedUserFactory.create(w.getAccountId());
try {
if (filterMatch(user, w.getFilter())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 0dfd8c9..1f40498 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -40,6 +40,7 @@
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -50,6 +51,7 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.server.OrmException;
@@ -59,6 +61,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -84,12 +87,15 @@
throws OrmException {
db.changes().beginTransaction(id);
try {
+ List<PatchSetApproval> approvals =
+ db.patchSetApprovals().byChange(id).toList();
return new ChangeBundle(
db.changes().get(id),
db.changeMessages().byChange(id),
db.patchSets().byChange(id),
- db.patchSetApprovals().byChange(id),
+ approvals,
db.patchComments().byChange(id),
+ ReviewerSet.fromApprovals(approvals),
Source.REVIEW_DB);
} finally {
db.rollback();
@@ -106,6 +112,7 @@
Iterables.concat(
plcUtil.draftByChange(null, notes),
plcUtil.publishedByChange(null, notes)),
+ notes.getReviewers(),
Source.NOTE_DB);
}
@@ -156,7 +163,6 @@
return CHANGE_MESSAGE_ORDER.immutableSortedCopy(in);
}
-
private static TreeMap<PatchSet.Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
TreeMap<PatchSet.Id, PatchSet> out = new TreeMap<>(
new Comparator<PatchSet.Id>() {
@@ -256,6 +262,7 @@
patchSetApprovals;
private final ImmutableMap<PatchLineComment.Key, PatchLineComment>
patchLineComments;
+ private final ReviewerSet reviewers;
private final Source source;
public ChangeBundle(
@@ -264,6 +271,7 @@
Iterable<PatchSet> patchSets,
Iterable<PatchSetApproval> patchSetApprovals,
Iterable<PatchLineComment> patchLineComments,
+ ReviewerSet reviewers,
Source source) {
this.change = checkNotNull(change);
this.changeMessages = changeMessageList(changeMessages);
@@ -272,6 +280,7 @@
ImmutableMap.copyOf(patchSetApprovalMap(patchSetApprovals));
this.patchLineComments =
ImmutableMap.copyOf(patchLineCommentMap(patchLineComments));
+ this.reviewers = checkNotNull(reviewers);
this.source = checkNotNull(source);
for (ChangeMessage m : this.changeMessages) {
@@ -309,6 +318,10 @@
return patchLineComments.values();
}
+ public ReviewerSet getReviewers() {
+ return reviewers;
+ }
+
public Source getSource() {
return source;
}
@@ -319,6 +332,7 @@
diffChangeMessages(diffs, this, o);
diffPatchSets(diffs, this, o);
diffPatchSetApprovals(diffs, this, o);
+ diffReviewers(diffs, this, o);
diffPatchLineComments(diffs, this, o);
return ImmutableList.copyOf(diffs);
}
@@ -661,6 +675,39 @@
}
}
+ @AutoValue
+ static abstract class ReviewerKey {
+ private static Map<ReviewerKey, Timestamp> toMap(ReviewerSet reviewers) {
+ Map<ReviewerKey, Timestamp> result = new HashMap<>();
+ for (Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> c :
+ reviewers.asTable().cellSet()) {
+ result.put(new AutoValue_ChangeBundle_ReviewerKey(
+ c.getRowKey(), c.getColumnKey()), c.getValue());
+ }
+ return result;
+ }
+
+ abstract ReviewerStateInternal state();
+ abstract Account.Id account();
+
+ @Override
+ public String toString() {
+ return state() + "," + account();
+ }
+ }
+
+ private static void diffReviewers(List<String> diffs,
+ ChangeBundle bundleA, ChangeBundle bundleB) {
+ Map<ReviewerKey, Timestamp> as = ReviewerKey.toMap(bundleA.reviewers);
+ Map<ReviewerKey, Timestamp> bs = ReviewerKey.toMap(bundleB.reviewers);
+ for (ReviewerKey k : diffKeySets(diffs, as, bs)) {
+ Timestamp a = as.get(k);
+ Timestamp b = bs.get(k);
+ String desc = describe(k);
+ diffTimestamps(diffs, desc, bundleA, a, bundleB, b, "timestamp");
+ }
+ }
+
private static void diffPatchLineComments(List<String> diffs,
ChangeBundle bundleA, ChangeBundle bundleB) {
Map<PatchLineComment.Key, PatchLineComment> as =
@@ -825,9 +872,14 @@
private static String keyClass(Object obj) {
Class<?> clazz = obj.getClass();
String name = clazz.getSimpleName();
- checkArgument(name.equals("Key") || name.equals("Id"),
+ checkArgument(name.endsWith("Key") || name.endsWith("Id"),
"not an Id/Key class: %s", name);
- return clazz.getEnclosingClass().getSimpleName() + "." + name;
+ if (name.equals("Key") || name.equals("Id")) {
+ return clazz.getEnclosingClass().getSimpleName() + "." + name;
+ } else if (name.startsWith("AutoValue_")) {
+ return name.substring(name.lastIndexOf('_') + 1);
+ }
+ return name;
}
@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 a6cd8fa..926194b 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
@@ -28,7 +28,6 @@
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
@@ -36,6 +35,7 @@
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
+import com.google.common.collect.Tables;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.Futures;
@@ -55,6 +55,7 @@
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.git.RefCache;
import com.google.gerrit.server.git.RepoRefCache;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -381,7 +382,7 @@
private ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets;
private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
- private ImmutableSetMultimap<ReviewerStateInternal, Account.Id> reviewers;
+ private ReviewerSet reviewers;
private ImmutableList<Account.Id> allPastReviewers;
private ImmutableList<SubmitRecord> submitRecords;
private ImmutableList<ChangeMessage> allChangeMessages;
@@ -420,7 +421,7 @@
return approvals;
}
- public ImmutableSetMultimap<ReviewerStateInternal, Account.Id> getReviewers() {
+ public ReviewerSet getReviewers() {
return reviewers;
}
@@ -583,13 +584,7 @@
} else {
hashtags = ImmutableSet.of();
}
- ImmutableSetMultimap.Builder<ReviewerStateInternal, Account.Id> reviewers =
- ImmutableSetMultimap.builder();
- for (Map.Entry<Account.Id, ReviewerStateInternal> e
- : parser.reviewers.entrySet()) {
- reviewers.put(e.getValue(), e.getKey());
- }
- this.reviewers = reviewers.build();
+ this.reviewers = ReviewerSet.fromTable(Tables.transpose(parser.reviewers));
this.allPastReviewers = ImmutableList.copyOf(parser.allPastReviewers);
submitRecords = ImmutableList.copyOf(parser.submitRecords);
@@ -598,7 +593,7 @@
@Override
protected void loadDefaults() {
approvals = ImmutableListMultimap.of();
- reviewers = ImmutableSetMultimap.of();
+ reviewers = ReviewerSet.empty();
submitRecords = ImmutableList.of();
allChangeMessages = ImmutableList.of();
changeMessagesByPatchSet = ImmutableListMultimap.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 9d9e180..02dd441 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
@@ -36,6 +36,7 @@
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
@@ -94,7 +95,7 @@
private static final RevId PARTIAL_PATCH_SET =
new RevId("INVALID PARTIAL PATCH SET");
- final Map<Account.Id, ReviewerStateInternal> reviewers;
+ final Table<Account.Id, ReviewerStateInternal, Timestamp> reviewers;
final List<Account.Id> allPastReviewers;
final List<SubmitRecord> submitRecords;
final Multimap<RevId, PatchLineComment> comments;
@@ -134,7 +135,7 @@
this.noteUtil = noteUtil;
this.metrics = metrics;
approvals = new HashMap<>();
- reviewers = new LinkedHashMap<>();
+ reviewers = HashBasedTable.create();
allPastReviewers = new ArrayList<>();
submitRecords = Lists.newArrayListWithExpectedSize(1);
allChangeMessages = new ArrayList<>();
@@ -156,7 +157,7 @@
parse(commit);
}
parseNotes();
- allPastReviewers.addAll(reviewers.keySet());
+ allPastReviewers.addAll(reviewers.rowKeySet());
pruneReviewers();
updatePatchSetStates();
checkMandatoryFooters();
@@ -262,7 +263,7 @@
for (ReviewerStateInternal state : ReviewerStateInternal.values()) {
for (String line : commit.getFooterLineValues(state.getFooterKey())) {
- parseReviewer(state, line);
+ parseReviewer(ts, state, line);
}
// Don't update timestamp when a reviewer was added, matching RevewDb
// behavior.
@@ -698,27 +699,27 @@
return noteUtil.parseIdent(commit.getAuthorIdent(), id);
}
- private void parseReviewer(ReviewerStateInternal state, String line)
- throws ConfigInvalidException {
+ private void parseReviewer(Timestamp ts, ReviewerStateInternal state,
+ String line) throws ConfigInvalidException {
PersonIdent ident = RawParseUtils.parsePersonIdent(line);
if (ident == null) {
throw invalidFooter(state.getFooterKey(), line);
}
Account.Id accountId = noteUtil.parseIdent(ident, id);
- if (!reviewers.containsKey(accountId)) {
- reviewers.put(accountId, state);
+ if (!reviewers.containsRow(accountId)) {
+ reviewers.put(accountId, state, ts);
}
}
private void pruneReviewers() {
- Iterator<Map.Entry<Account.Id, ReviewerStateInternal>> rit =
- reviewers.entrySet().iterator();
+ Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
+ reviewers.cellSet().iterator();
while (rit.hasNext()) {
- Map.Entry<Account.Id, ReviewerStateInternal> e = rit.next();
- if (e.getValue() == ReviewerStateInternal.REMOVED) {
+ Table.Cell<Account.Id, ReviewerStateInternal, Timestamp> e = rit.next();
+ if (e.getColumnKey() == ReviewerStateInternal.REMOVED) {
rit.remove();
for (Table<Account.Id, ?, ?> curr : approvals.values()) {
- curr.rowKeySet().remove(e.getKey());
+ curr.rowKeySet().remove(e.getRowKey());
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
index 6daf457..5f16eb4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
@@ -36,6 +36,7 @@
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.FormatUtil;
import com.google.gerrit.common.Nullable;
@@ -47,6 +48,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.GerritPersonIdent;
@@ -54,6 +56,7 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.git.ChainedReceiveCommands;
+import com.google.gerrit.server.notedb.NoteDbUpdateManager.OpenRepo;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
@@ -69,6 +72,7 @@
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -240,7 +244,8 @@
events.addAll(getHashtagsEvents(change, manager));
// Delete ref only after hashtags have been read
- deleteRef(change, manager.getChangeRepo().cmds);
+ deleteChangeMetaRef(change, manager.getChangeRepo().cmds);
+ deleteDraftRefs(change, manager.getAllUsersRepo());
Integer minPsNum = getMinPatchSetNum(bundle);
Set<PatchSet.Id> psIds =
@@ -273,6 +278,11 @@
}
}
+ for (Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> r :
+ bundle.getReviewers().asTable().cellSet()) {
+ events.add(new ReviewerEvent(r, change.getCreatedOn()));
+ }
+
Change noteDbChange = new Change(null, null, null, null, null);
for (ChangeMessage msg : bundle.getChangeMessages()) {
if (msg.getPatchSetId() == null || psIds.contains(msg.getPatchSetId())) {
@@ -473,7 +483,7 @@
return new PatchSet.Id(change.getId(), psId);
}
- private void deleteRef(Change change, ChainedReceiveCommands cmds)
+ private void deleteChangeMetaRef(Change change, ChainedReceiveCommands cmds)
throws IOException {
String refName = changeMetaRef(change.getId());
Optional<ObjectId> old = cmds.get(refName);
@@ -482,6 +492,15 @@
}
}
+ private void deleteDraftRefs(Change change, OpenRepo allUsersRepo)
+ throws IOException {
+ for (Ref r : allUsersRepo.repo.getRefDatabase()
+ .getRefs(RefNames.refsDraftCommentsPrefix(change.getId())).values()) {
+ allUsersRepo.cmds.add(
+ new ReceiveCommand(r.getObjectId(), ObjectId.zeroId(), r.getName()));
+ }
+ }
+
private static final Ordering<Event> EVENT_ORDER = new Ordering<Event>() {
@Override
public int compare(Event a, Event b) {
@@ -701,6 +720,33 @@
}
}
+ private static class ReviewerEvent extends Event {
+ private Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer;
+
+ ReviewerEvent(
+ Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer,
+ Timestamp changeCreatedOn) {
+ super(
+ // Reviewers aren't generally associated with a particular patch set
+ // (although as an implementation detail they were in ReviewDb). Just
+ // use the latest patch set at the time of the event.
+ null,
+ reviewer.getColumnKey(), reviewer.getValue(), changeCreatedOn, null);
+ this.reviewer = reviewer;
+ }
+
+ @Override
+ boolean uniquePerUpdate() {
+ return false;
+ }
+
+ @Override
+ void apply(ChangeUpdate update) throws IOException, OrmException {
+ checkUpdate(update);
+ update.putReviewer(reviewer.getColumnKey(), reviewer.getRowKey());
+ }
+ }
+
private static class PatchSetEvent extends Event {
private final Change change;
private final PatchSet ps;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java
index fd02042..d75a553 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -198,7 +198,6 @@
builder.finish();
treeId = dc.writeTree(ins);
}
- ins.flush();
return commit(repo, rw, ins, refName, treeId, merge);
}
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 4bda245..62c6d5e 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
@@ -340,7 +340,7 @@
public boolean isReviewer(ReviewDb db, @Nullable ChangeData cd)
throws OrmException {
if (getUser().isIdentifiedUser()) {
- Collection<Account.Id> results = changeData(db, cd).reviewers().values();
+ Collection<Account.Id> results = changeData(db, cd).reviewers().all();
return results.contains(getUser().getAccountId());
}
return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index bc4d8f3..e41acbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -148,7 +148,9 @@
case FAST_FORWARD:
case NEW:
case NO_CHANGE:
- referenceUpdated.fire(name.getParentKey(), u, ReceiveCommand.Type.CREATE);
+ referenceUpdated.fire(
+ name.getParentKey(), u, ReceiveCommand.Type.CREATE,
+ identifiedUser.get().getAccount());
hooks.doRefUpdatedHook(name, u, identifiedUser.get().getAccount());
break;
case LOCK_FAILURE:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index a73ed0e..ecc618e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -370,7 +370,8 @@
Result result = ru.update();
switch (result) {
case NEW:
- referenceUpdated.fire(project, ru, ReceiveCommand.Type.CREATE);
+ referenceUpdated.fire(project, ru, ReceiveCommand.Type.CREATE,
+ currentUser.get().getAccountId());
break;
case FAST_FORWARD:
case FORCED:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index b4c25b4..43e1422 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -108,7 +108,8 @@
case NO_CHANGE:
case FAST_FORWARD:
case FORCED:
- referenceUpdated.fire(rsrc.getNameKey(), u, ReceiveCommand.Type.DELETE);
+ referenceUpdated.fire(rsrc.getNameKey(), u, ReceiveCommand.Type.DELETE,
+ identifiedUser.get().getAccount());
hooks.doRefUpdatedHook(rsrc.getBranchKey(), u, identifiedUser.get().getAccount());
break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
index b851f9e..daecc1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -162,7 +162,8 @@
}
private void postDeletion(ProjectResource project, ReceiveCommand cmd) {
- referenceUpdated.fire(project.getNameKey(), cmd);
+ referenceUpdated.fire(project.getNameKey(), cmd,
+ identifiedUser.get().getAccount());
Branch.NameKey branchKey =
new Branch.NameKey(project.getNameKey(), cmd.getRefName());
hooks.doRefUpdatedHook(branchKey, cmd.getOldId(), cmd.getNewId(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
index b8c5fd8..be6f892 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.project;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
@@ -53,16 +53,14 @@
@Singleton
public class GetAccess implements RestReadView<ProjectResource> {
- private static final ImmutableMap<PermissionRule.Action, PermissionRuleInfo.Action> ACTION_TYPE =
- Maps.immutableEnumMap(
- new ImmutableMap.Builder<PermissionRule.Action, PermissionRuleInfo.Action>()
- .put(PermissionRule.Action.ALLOW, PermissionRuleInfo.Action.ALLOW)
- .put(PermissionRule.Action.BATCH, PermissionRuleInfo.Action.BATCH)
- .put(PermissionRule.Action.BLOCK, PermissionRuleInfo.Action.BLOCK)
- .put(PermissionRule.Action.DENY, PermissionRuleInfo.Action.DENY)
- .put(PermissionRule.Action.INTERACTIVE,
- PermissionRuleInfo.Action.INTERACTIVE)
- .build());
+ public static final BiMap<PermissionRule.Action,
+ PermissionRuleInfo.Action> ACTION_TYPE = ImmutableBiMap.of(
+ PermissionRule.Action.ALLOW, PermissionRuleInfo.Action.ALLOW,
+ PermissionRule.Action.BATCH, PermissionRuleInfo.Action.BATCH,
+ PermissionRule.Action.BLOCK, PermissionRuleInfo.Action.BLOCK,
+ PermissionRule.Action.DENY, PermissionRuleInfo.Action.DENY,
+ PermissionRule.Action.INTERACTIVE,
+ PermissionRuleInfo.Action.INTERACTIVE);
private final Provider<CurrentUser> self;
private final GroupControl.Factory groupControlFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 469312d..3d0ff05c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -42,10 +42,12 @@
put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
get(PROJECT_KIND, "description").to(GetDescription.class);
- get(PROJECT_KIND, "access").to(GetAccess.class);
put(PROJECT_KIND, "description").to(PutDescription.class);
delete(PROJECT_KIND, "description").to(PutDescription.class);
+ get(PROJECT_KIND, "access").to(GetAccess.class);
+ post(PROJECT_KIND, "access").to(SetAccess.class);
+
get(PROJECT_KIND, "parent").to(GetParent.class);
put(PROJECT_KIND, "parent").to(SetParent.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 29f97fb..28cb5b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -27,8 +27,10 @@
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.SubscribeSection;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.rules.PrologEnvironment;
@@ -475,6 +477,15 @@
return null;
}
+ public Collection<SubscribeSection> getSubscribeSections(
+ Branch.NameKey branch) {
+ Collection<SubscribeSection> ret = new ArrayList<>();
+ for (ProjectState s : tree()) {
+ ret.addAll(s.getConfig().getSubscribeSections(branch));
+ }
+ return ret;
+ }
+
public ThemeInfo getTheme() {
ThemeInfo theme = this.theme;
if (theme == null) {
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 86f98ff..4b0ecf0 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
@@ -196,7 +196,7 @@
// Only fire hook if project was actually changed.
if (!Objects.equals(baseRev, commitRev)) {
gitRefUpdated.fire(projectName, RefNames.REFS_CONFIG,
- baseRev, commitRev);
+ baseRev, commitRev, user.get().asIdentifiedUser().getAccount());
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 7cf426d..b136821 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
@@ -94,7 +94,7 @@
// Only fire hook if project was actually changed.
if (!Objects.equals(baseRev, commitRev)) {
gitRefUpdated.fire(resource.getNameKey(), RefNames.REFS_CONFIG,
- baseRev, commitRev);
+ baseRev, commitRev, user.getAccount());
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/project/SetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
new file mode 100644
index 0000000..258b386
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
@@ -0,0 +1,328 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.extensions.api.access.AccessSectionInfo;
+import com.google.gerrit.extensions.api.access.PermissionInfo;
+import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.AllProjectsName;
+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.group.GroupsCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+@Singleton
+public class SetAccess implements
+ RestModifyView<ProjectResource, ProjectAccessInput> {
+ protected final GroupBackend groupBackend;
+ private final GroupsCollection groupsCollection;
+ private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
+ private final AllProjectsName allProjects;
+ private final Provider<SetParent> setParent;
+ private final ChangeHooks hooks;
+ private final GitReferenceUpdated gitRefUpdated;
+ private final GetAccess getAccess;
+ private final ProjectCache projectCache;
+ private final Provider<IdentifiedUser> identifiedUser;
+
+ @Inject
+ private SetAccess(GroupBackend groupBackend,
+ Provider<MetaDataUpdate.User> metaDataUpdateFactory,
+ AllProjectsName allProjects,
+ Provider<SetParent> setParent,
+ ChangeHooks hooks,
+ GitReferenceUpdated gitRefUpdated,
+ GroupsCollection groupsCollection,
+ ProjectCache projectCache,
+ GetAccess getAccess,
+ Provider<IdentifiedUser> identifiedUser) {
+ this.groupBackend = groupBackend;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.allProjects = allProjects;
+ this.setParent = setParent;
+ this.groupsCollection = groupsCollection;
+ this.hooks = hooks;
+ this.gitRefUpdated = gitRefUpdated;
+ this.getAccess = getAccess;
+ this.projectCache = projectCache;
+ this.identifiedUser = identifiedUser;
+ }
+
+ @Override
+ public ProjectAccessInfo apply(ProjectResource rsrc,
+ ProjectAccessInput input)
+ throws ResourceNotFoundException, ResourceConflictException,
+ IOException, AuthException, BadRequestException,
+ UnprocessableEntityException{
+ List<AccessSection> removals = getAccessSections(input.remove);
+ List<AccessSection> additions = getAccessSections(input.add);
+ MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
+
+ ProjectControl projectControl = rsrc.getControl();
+ ProjectConfig config;
+ ObjectId base;
+
+ Project.NameKey newParentProjectName = input.parent == null ?
+ null : new Project.NameKey(input.parent);
+
+ try (MetaDataUpdate md = metaDataUpdateUser.create(rsrc.getNameKey())) {
+ config = ProjectConfig.read(md);
+ base = config.getRevision();
+
+ // Perform removal checks
+ for (AccessSection section : removals) {
+ boolean isGlobalCapabilities =
+ AccessSection.GLOBAL_CAPABILITIES.equals(section.getName());
+
+ if (isGlobalCapabilities) {
+ checkGlobalCapabilityPermissions(config.getName());
+ } else if (!projectControl.controlForRef(section.getName()).isOwner()) {
+ throw new AuthException("You are not allowed to edit permissions"
+ + "for ref: " + section.getName());
+ }
+ }
+ // Perform addition checks
+ for (AccessSection section : additions) {
+ String name = section.getName();
+ boolean isGlobalCapabilities =
+ AccessSection.GLOBAL_CAPABILITIES.equals(name);
+
+ if (isGlobalCapabilities) {
+ checkGlobalCapabilityPermissions(config.getName());
+ } else {
+ if (!AccessSection.isValid(name)) {
+ throw new BadRequestException("invalid section name");
+ }
+ if (!projectControl.controlForRef(name).isOwner()) {
+ throw new AuthException("You are not allowed to edit permissions"
+ + "for ref: " + name);
+ }
+ RefControl.validateRefPattern(name);
+ }
+
+ // Check all permissions for soundness
+ for (Permission p : section.getPermissions()) {
+ if (isGlobalCapabilities
+ && !GlobalCapability.isCapability(p.getName())) {
+ throw new BadRequestException("Cannot add non-global capability "
+ + p.getName() + " to global capabilities");
+ }
+ }
+ }
+
+ // Apply removals
+ for (AccessSection section : removals) {
+ if (section.getPermissions().isEmpty()) {
+ // Remove entire section
+ config.remove(config.getAccessSection(section.getName()));
+ }
+ // Remove specific permissions
+ for (Permission p : section.getPermissions()) {
+ if (p.getRules().isEmpty()) {
+ config.remove(config.getAccessSection(section.getName()), p);
+ } else {
+ for (PermissionRule r : p.getRules()) {
+ config.remove(config.getAccessSection(section.getName()), p, r);
+ }
+ }
+ }
+ }
+
+ // Apply additions
+ for (AccessSection section : additions) {
+ AccessSection currentAccessSection =
+ config.getAccessSection(section.getName());
+
+ if (currentAccessSection == null) {
+ // Add AccessSection
+ config.replace(section);
+ } else {
+ for (Permission p : section.getPermissions()) {
+ Permission currentPermission =
+ currentAccessSection.getPermission(p.getName());
+ if (currentPermission == null) {
+ // Add Permission
+ currentAccessSection.addPermission(p);
+ } else {
+ for (PermissionRule r : p.getRules()) {
+ // AddPermissionRule
+ currentPermission.add(r);
+ }
+ }
+ }
+ }
+ }
+
+ if (newParentProjectName != null &&
+ !config.getProject().getNameKey().equals(allProjects) &&
+ !config.getProject().getParent(allProjects)
+ .equals(newParentProjectName)) {
+ try {
+ setParent.get().validateParentUpdate(projectControl,
+ MoreObjects.firstNonNull(newParentProjectName, allProjects).get(),
+ true);
+ } catch (UnprocessableEntityException e) {
+ throw new ResourceConflictException(e.getMessage(), e);
+ }
+ config.getProject().setParentName(newParentProjectName);
+ }
+
+ if (!Strings.isNullOrEmpty(input.message)) {
+ if (!input.message.endsWith("\n")) {
+ input.message += "\n";
+ }
+ md.setMessage(input.message);
+ } else {
+ md.setMessage("Modify access rules\n");
+ }
+
+ updateProjectConfig(projectControl.getUser(), config, md, base);
+ } catch (InvalidNameException e) {
+ throw new BadRequestException(e.toString());
+ } catch (ConfigInvalidException e) {
+ throw new ResourceConflictException(rsrc.getName());
+ }
+
+ return getAccess.apply(rsrc.getNameKey());
+ }
+
+ private List<AccessSection> getAccessSections(
+ Map<String, AccessSectionInfo> sectionInfos)
+ throws UnprocessableEntityException {
+ List<AccessSection> sections = new LinkedList<>();
+ if (sectionInfos == null) {
+ return sections;
+ }
+
+ for (Map.Entry<String, AccessSectionInfo> entry :
+ sectionInfos.entrySet()) {
+ AccessSection accessSection = new AccessSection(entry.getKey());
+
+ if (entry.getValue().permissions == null) {
+ continue;
+ }
+
+ for (Map.Entry<String, PermissionInfo> permissionEntry : entry
+ .getValue().permissions
+ .entrySet()) {
+ Permission p = new Permission(permissionEntry.getKey());
+ if (permissionEntry.getValue().exclusive != null) {
+ p.setExclusiveGroup(permissionEntry.getValue().exclusive);
+ }
+
+ if (permissionEntry.getValue().rules == null) {
+ continue;
+ }
+ for (Map.Entry<String, PermissionRuleInfo> permissionRuleInfoEntry :
+ permissionEntry.getValue().rules.entrySet()) {
+ PermissionRuleInfo pri = permissionRuleInfoEntry.getValue();
+
+ GroupDescription.Basic group = groupsCollection
+ .parseId(permissionRuleInfoEntry.getKey());
+ if (group == null) {
+ throw new UnprocessableEntityException(
+ permissionRuleInfoEntry.getKey() + " is not a valid group ID");
+ }
+ PermissionRule r = new PermissionRule(
+ GroupReference.forGroup(group));
+ if (pri != null) {
+ if (pri.max != null) {
+ r.setMax(pri.max);
+ }
+ if (pri.min != null) {
+ r.setMin(pri.min);
+ }
+ r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action));
+ r.setForce(pri.force);
+ }
+ p.add(r);
+ }
+ accessSection.getPermissions().add(p);
+ }
+ sections.add(accessSection);
+ }
+ return sections;
+ }
+
+ private void updateProjectConfig(CurrentUser user,
+ ProjectConfig config, MetaDataUpdate md, ObjectId base)
+ throws IOException {
+ RevCommit commit = config.commit(md);
+
+ Account account = user.isIdentifiedUser()
+ ? user.asIdentifiedUser().getAccount()
+ : null;
+ gitRefUpdated.fire(config.getProject().getNameKey(), RefNames.REFS_CONFIG,
+ base, commit.getId(), account);
+
+ projectCache.evict(config.getProject());
+
+ hooks.doRefUpdatedHook(
+ new Branch.NameKey(config.getProject().getNameKey(),
+ RefNames.REFS_CONFIG),
+ base, commit.getId(), user.asIdentifiedUser().getAccount());
+ }
+
+ private void checkGlobalCapabilityPermissions(Project.NameKey projectName)
+ throws BadRequestException, AuthException {
+
+ if (!allProjects.equals(projectName)) {
+ throw new BadRequestException("Cannot edit global capabilities "
+ + "for projects other than " + allProjects.get());
+ }
+
+ if (!identifiedUser.get().getCapabilities().canAdministrateServer()) {
+ throw new AuthException("Editing global capabilities "
+ + "requires " + GlobalCapability.ADMINISTRATE_SERVER);
+ }
+ }
+}
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 ed7b134..93a16ad 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
@@ -30,7 +30,6 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
-import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord;
@@ -50,13 +49,13 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.change.MergeabilityCache;
import com.google.gerrit.server.git.GitRepositoryManager;
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.ReviewerStateInternal;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -905,8 +904,7 @@
return Optional.absent();
}
- public SetMultimap<ReviewerStateInternal, Account.Id> reviewers()
- throws OrmException {
+ public ReviewerSet reviewers() throws OrmException {
return approvalsUtil.getReviewers(notes(), approvals().values());
}
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 07c8c72..d92c2b2 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
@@ -301,7 +301,7 @@
} catch (ProvisionException e) {
// Doesn't match current user, continue.
}
- return asUser(userFactory.create(db, otherId));
+ return asUser(userFactory.create(otherId));
}
IdentifiedUser getIdentifiedUser() throws QueryParseException {
@@ -736,7 +736,7 @@
if (!m.isEmpty()) {
List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
for (Account.Id id : m) {
- return visibleto(args.userFactory.create(args.db, id));
+ return visibleto(args.userFactory.create(id));
}
return Predicate.or(p);
}
@@ -791,7 +791,7 @@
if (g == null) {
throw error("Group " + group + " not found");
}
- return new OwnerinPredicate(args.db, args.userFactory, g.getUUID());
+ return new OwnerinPredicate(args.userFactory, g.getUUID());
}
@Operator
@@ -818,7 +818,7 @@
if (g == null) {
throw error("Group " + group + " not found");
}
- return new ReviewerinPredicate(args.db, args.userFactory, g.getUUID());
+ return new ReviewerinPredicate(args.userFactory, g.getUUID());
}
@Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index ff9c853..b01fdbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -112,7 +112,7 @@
if (psVal == expVal) {
// Double check the value is still permitted for the user.
//
- IdentifiedUser reviewer = userFactory.create(dbProvider, approver);
+ IdentifiedUser reviewer = userFactory.create(approver);
try {
ChangeControl cc =
ccFactory.controlFor(dbProvider.get(), change, reviewer);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
index a0c1235..467e4c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
@@ -16,21 +16,17 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.query.OperatorPredicate;
import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
class OwnerinPredicate extends OperatorPredicate<ChangeData> {
- private final Provider<ReviewDb> dbProvider;
private final IdentifiedUser.GenericFactory userFactory;
private final AccountGroup.UUID uuid;
- OwnerinPredicate(Provider<ReviewDb> dbProvider,
- IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+ OwnerinPredicate(IdentifiedUser.GenericFactory userFactory,
+ AccountGroup.UUID uuid) {
super(ChangeQueryBuilder.FIELD_OWNERIN, uuid.toString());
- this.dbProvider = dbProvider;
this.userFactory = userFactory;
this.uuid = uuid;
}
@@ -45,8 +41,7 @@
if (change == null) {
return false;
}
- final IdentifiedUser owner = userFactory.create(dbProvider,
- change.getOwner());
+ final IdentifiedUser owner = userFactory.create(change.getOwner());
return owner.getEffectiveGroups().contains(uuid);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index eb07250..2da8c54 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -40,7 +40,7 @@
object.change().getStatus() == Change.Status.DRAFT) {
return false;
}
- for (Account.Id accountId : object.reviewers().values()) {
+ for (Account.Id accountId : object.reviewers().all()) {
if (id.equals(accountId)) {
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
index a29ac62..76a02432 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
@@ -16,21 +16,17 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.query.OperatorPredicate;
import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
class ReviewerinPredicate extends OperatorPredicate<ChangeData> {
- private final Provider<ReviewDb> dbProvider;
private final IdentifiedUser.GenericFactory userFactory;
private final AccountGroup.UUID uuid;
- ReviewerinPredicate(Provider<ReviewDb> dbProvider,
- IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+ ReviewerinPredicate(IdentifiedUser.GenericFactory userFactory,
+ AccountGroup.UUID uuid) {
super(ChangeQueryBuilder.FIELD_REVIEWERIN, uuid.toString());
- this.dbProvider = dbProvider;
this.userFactory = userFactory;
this.uuid = uuid;
}
@@ -41,8 +37,8 @@
@Override
public boolean match(final ChangeData object) throws OrmException {
- for (Account.Id accountId : object.reviewers().values()) {
- IdentifiedUser reviewer = userFactory.create(dbProvider, accountId);
+ for (Account.Id accountId : object.reviewers().all()) {
+ IdentifiedUser reviewer = userFactory.create(accountId);
if (reviewer.getEffectiveGroups().contains(uuid)) {
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index 907ef70..6b5c991 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -17,11 +17,8 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.lib.BlobBasedConfig;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import java.net.URI;
@@ -48,24 +45,15 @@
*/
public class SubmoduleSectionParser {
- public interface Factory {
- SubmoduleSectionParser create(BlobBasedConfig bbc, String thisServer,
- Branch.NameKey superProjectBranch);
- }
-
- private final ProjectCache projectCache;
- private final BlobBasedConfig bbc;
- private final String thisServer;
+ private final Config bbc;
+ private final String canonicalWebUrl;
private final Branch.NameKey superProjectBranch;
- @Inject
- public SubmoduleSectionParser(ProjectCache projectCache,
- @Assisted BlobBasedConfig bbc,
- @Assisted String thisServer,
- @Assisted Branch.NameKey superProjectBranch) {
- this.projectCache = projectCache;
+ public SubmoduleSectionParser(Config bbc,
+ String canonicalWebUrl,
+ Branch.NameKey superProjectBranch) {
this.bbc = bbc;
- this.thisServer = thisServer;
+ this.canonicalWebUrl = canonicalWebUrl;
this.superProjectBranch = superProjectBranch;
}
@@ -84,51 +72,74 @@
final String url = bbc.getString("submodule", id, "url");
final String path = bbc.getString("submodule", id, "path");
String branch = bbc.getString("submodule", id, "branch");
- SubmoduleSubscription ss = null;
try {
if (url != null && url.length() > 0 && path != null && path.length() > 0
&& branch != null && branch.length() > 0) {
// All required fields filled.
+ String project;
- boolean urlIsRelative = url.startsWith("../");
- String server = null;
- if (!urlIsRelative) {
+ if (branch.equals(".")) {
+ branch = superProjectBranch.get();
+ }
+
+ // relative URL
+ if (url.startsWith("../")) {
+ // prefix with a slash for easier relative path walks
+ project = '/' + superProjectBranch.getParentKey().get();
+ String hostPart = url;
+ while (hostPart.startsWith("../")) {
+ int lastSlash = project.lastIndexOf('/');
+ if (lastSlash < 0) {
+ // too many levels up, ignore for now
+ return null;
+ }
+ project = project.substring(0, lastSlash);
+ hostPart = hostPart.substring(3);
+ }
+ project = project + "/" + hostPart;
+
+ // remove leading '/'
+ project = project.substring(1);
+ } else {
// It is actually an URI. It could be ssh://localhost/project-a.
- server = new URI(url).getHost();
- }
- if ((urlIsRelative)
- || (server != null && server.equalsIgnoreCase(thisServer))) {
- // Subscription really related to this running server.
- if (branch.equals(".")) {
- branch = superProjectBranch.get();
+ URI targetServerURI = new URI(url);
+ URI thisServerURI = new URI(canonicalWebUrl);
+ String thisHost = thisServerURI.getHost();
+ String targetHost = targetServerURI.getHost();
+ if (thisHost == null || targetHost == null ||
+ !targetHost.equalsIgnoreCase(thisHost)) {
+ return null;
}
-
- final String urlExtractedPath = new URI(url).getPath();
- String projectName;
- int fromIndex = urlExtractedPath.length() - 1;
- while (fromIndex > 0) {
- fromIndex = urlExtractedPath.lastIndexOf('/', fromIndex - 1);
- projectName = urlExtractedPath.substring(fromIndex + 1);
-
- if (projectName.endsWith(Constants.DOT_GIT_EXT)) {
- projectName = projectName.substring(0, //
- projectName.length() - Constants.DOT_GIT_EXT.length());
- }
- Project.NameKey projectKey = new Project.NameKey(projectName);
- if (projectCache.get(projectKey) != null) {
- ss = new SubmoduleSubscription(
- superProjectBranch,
- new Branch.NameKey(new Project.NameKey(projectName), branch),
- path);
- }
+ String p1 = targetServerURI.getPath();
+ String p2 = thisServerURI.getPath();
+ if (!p1.startsWith(p2)) {
+ // When we are running the server at
+ // http://server/my-gerrit/ but the subscription is for
+ // http://server/other-teams-gerrit/
+ return null;
}
+ // skip common part
+ project = p1.substring(p2.length());
}
+
+ while (project.startsWith("/")) {
+ project = project.substring(1);
+ }
+
+ if (project.endsWith(Constants.DOT_GIT_EXT)) {
+ project = project.substring(0, //
+ project.length() - Constants.DOT_GIT_EXT.length());
+ }
+ Project.NameKey projectKey = new Project.NameKey(project);
+ return new SubmoduleSubscription(
+ superProjectBranch,
+ new Branch.NameKey(projectKey, branch),
+ path);
}
} catch (URISyntaxException e) {
// Error in url syntax (in fact it is uri syntax)
}
-
- return ss;
+ return null;
}
}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
index 3ee8d82..87c7138 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
@@ -17,12 +17,10 @@
import static com.googlecode.prolog_cafe.lang.SymbolTerm.intern;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.util.Providers;
import com.googlecode.prolog_cafe.exceptions.IllegalTypeException;
import com.googlecode.prolog_cafe.exceptions.PInstantiationException;
@@ -90,14 +88,8 @@
Account.Id accountId = new Account.Id(((IntegerTerm) idTerm).intValue());
user = cache.get(accountId);
if (user == null) {
- ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
IdentifiedUser.GenericFactory userFactory = userFactory(engine);
- IdentifiedUser who;
- if (db != null) {
- who = userFactory.create(Providers.of(db), accountId);
- } else {
- who = userFactory.create(accountId);
- }
+ IdentifiedUser who = userFactory.create(accountId);
cache.put(accountId, who);
user = who;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
index 6b6528e..aa23e50 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -90,7 +90,7 @@
schemaCreator.create(db);
userId = accountManager.authenticate(AuthRequest.forUser("user"))
.getAccountId();
- user = userFactory.create(Providers.of(db), userId);
+ user = userFactory.create(userId);
requestContext.setContext(new RequestContext() {
@Override
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index ea1120f..4306d74 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -14,14 +14,18 @@
package com.google.gerrit.server.notedb;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.common.TimeUtil.roundToSecond;
import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
+import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Table;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
@@ -33,6 +37,7 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.testutil.TestChanges;
import com.google.gerrit.testutil.TestTimeUtil;
import com.google.gwtorm.client.KeyUtil;
@@ -113,9 +118,9 @@
Change c2 = TestChanges.newChange(project, accountId);
int id2 = c2.getId().get();
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"changeId differs for Changes: {" + id1 + "} != {" + id2 + "}",
@@ -131,9 +136,9 @@
new Project.NameKey("project"), new Account.Id(100));
Change c2 = clone(c1);
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
@@ -153,9 +158,9 @@
// Both are ReviewDb, exact timestamp match is required.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"createdOn differs for Change.Id " + c1.getId() + ":"
+ " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:02.0}",
@@ -164,9 +169,9 @@
// One NoteDb, slop is allowed.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
@@ -175,9 +180,9 @@
Change c3 = clone(c1);
c3.setLastUpdatedOn(TimeUtil.nowTs());
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
ChangeBundle b3 = new ChangeBundle(c3, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
String msg = "effective last updated time differs for Change.Id "
+ c1.getId() + " in NoteDb vs. ReviewDb:"
+ " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:10.0}";
@@ -197,27 +202,27 @@
// Both ReviewDb, exact match required.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"originalSubject differs for Change.Id " + c1.getId() + ":"
+ " {Original A} != {Original B}");
// Both NoteDb, exact match required.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertDiffs(b1, b2,
"originalSubject differs for Change.Id " + c1.getId() + ":"
+ " {Original A} != {Original B}");
// One ReviewDb, one NoteDb, original subject is ignored.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
}
@@ -233,25 +238,25 @@
// Both ReviewDb, exact match required.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"topic differs for Change.Id " + c1.getId() + ":"
+ " {} != {null}");
// Topic ignored if ReviewDb is empty and NoteDb is null.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
// Exact match still required if NoteDb has empty value (not realistic).
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"topic differs for Change.Id " + c1.getId() + ":"
+ " {} != {null}");
@@ -260,9 +265,9 @@
Change c3 = clone(c1);
c3.setTopic("topic");
b1 = new ChangeBundle(c3, messages(), patchSets(), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertDiffs(b1, b2,
"topic differs for Change.Id " + c1.getId() + ":"
+ " {topic} != {null}");
@@ -284,16 +289,16 @@
// Both ReviewDb, exact match required.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(),
- approvals(a), comments(), REVIEW_DB);
+ approvals(a), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(),
- approvals(a), comments(), REVIEW_DB);
+ approvals(a), comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"effective last updated time differs for Change.Id " + c1.getId() + ":"
+ " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}");
// NoteDb allows latest timestamp from all entities in bundle.
b2 = new ChangeBundle(c2, messages(), patchSets(),
- approvals(a), comments(), NOTE_DB);
+ approvals(a), comments(), reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
}
@@ -314,18 +319,18 @@
// ReviewDb has later lastUpdatedOn timestamp than NoteDb, allowed since
// NoteDb matches the latest timestamp of a non-Change entity.
ChangeBundle b1 = new ChangeBundle(c2, messages(), patchSets(),
- approvals(a), comments(), REVIEW_DB);
+ approvals(a), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c1, messages(), patchSets(),
- approvals(a), comments(), NOTE_DB);
+ approvals(a), comments(), reviewers(), NOTE_DB);
assertThat(b1.getChange().getLastUpdatedOn())
.isGreaterThan(b2.getChange().getLastUpdatedOn());
assertNoDiffs(b1, b2);
// Timestamps must actually match if Change is the only entity.
b1 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertDiffs(b1, b2,
"effective last updated time differs for Change.Id " + c1.getId()
+ " in NoteDb vs. ReviewDb:"
@@ -344,25 +349,25 @@
// Both ReviewDb, exact match required.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"subject differs for Change.Id " + c1.getId() + ":"
+ " {Change subject} != {Change sub}");
// ReviewDb has shorter subject, allowed.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
// NoteDb has shorter subject, not allowed.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
assertDiffs(b1, b2,
"subject differs for Change.Id " + c1.getId() + ":"
+ " {Change subject} != {Change sub}");
@@ -379,18 +384,18 @@
// Both ReviewDb, exact match required.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"subject differs for Change.Id " + c1.getId() + ":"
+ " {Change subject} != { Change subject}");
// ReviewDb is missing leading spaces, allowed.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
}
@@ -406,18 +411,18 @@
// Both ReviewDb.
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"subject differs for Change.Id " + c1.getId() + ":"
+ " {Change subject} != {\tChange subject}");
// One NoteDb.
b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"subject differs for Change.Id " + c1.getId() + ":"
+ " {Change subject} != {\tChange subject}");
@@ -437,9 +442,9 @@
new ChangeMessage.Key(c.getId(), "uuid2"),
accountId, TimeUtil.nowTs(), c.currentPatchSetId());
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"ChangeMessage.Key sets differ:"
@@ -455,9 +460,9 @@
cm1.setMessage("message 1");
ChangeMessage cm2 = clone(cm1);
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
@@ -479,9 +484,9 @@
cm2.getKey().set("uuid2");
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
// Both are ReviewDb, exact UUID match is required.
assertDiffs(b1, b2,
"ChangeMessage.Key sets differ:"
@@ -489,9 +494,9 @@
// One NoteDb, UUIDs are ignored.
b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
}
@@ -510,18 +515,18 @@
// Both ReviewDb: Uses same keySet diff as other types.
ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm2), latest(c),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"ChangeMessage.Key sets differ: [" + id
+ ",uuid2] only in A; [] only in B");
// One NoteDb: UUIDs in keys can't be used for comparison, just diff counts.
b1 = new ChangeBundle(c, messages(cm1, cm2), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertDiffs(b1, b2,
"ChangeMessages differ for Change.Id " + id + "\n"
+ "Only in A:\n " + cm2);
@@ -544,9 +549,9 @@
cm3.getKey().set("uuid2"); // Differs only in UUID.
ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm3), latest(c),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm2, cm3), latest(c),
- approvals(), comments(), NOTE_DB);
+ approvals(), comments(), reviewers(), NOTE_DB);
// Implementation happens to pair up cm1 in b1 with cm3 in b2 because it
// depends on iteration order and doesn't care about UUIDs. The important
// thing is that there's some diff.
@@ -572,18 +577,18 @@
// Both are ReviewDb, exact timestamp match is required.
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"writtenOn differs for ChangeMessage.Key " + c.getId() + ",uuid1:"
+ " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
// One NoteDb, slop is allowed.
b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
@@ -592,9 +597,9 @@
ChangeMessage cm3 = clone(cm1);
cm3.setWrittenOn(TimeUtil.nowTs());
b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
ChangeBundle b3 = new ChangeBundle(c, messages(cm3), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
int id = c.getId().get();
assertDiffs(b1, b3,
"ChangeMessages differ for Change.Id " + id + "\n"
@@ -619,9 +624,9 @@
cm2.setPatchSetId(null);
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
// Both are ReviewDb, exact patch set ID match is required.
assertDiffs(b1, b2,
@@ -630,16 +635,16 @@
// Null patch set ID on ReviewDb is ignored.
b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
// Null patch set ID on NoteDb is not ignored (but is not realistic).
b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
assertDiffs(b1, b2,
"ChangeMessages differ for Change.Id " + id + "\n"
+ "Only in A:\n " + cm1 + "\n"
@@ -665,9 +670,9 @@
ps2.setCreatedOn(TimeUtil.nowTs());
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps2),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"PatchSet.Id sets differ:"
@@ -683,9 +688,9 @@
ps1.setCreatedOn(TimeUtil.nowTs());
PatchSet ps2 = clone(ps1);
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps2),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
@@ -709,18 +714,18 @@
// Both are ReviewDb, exact timestamp match is required.
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps2),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"createdOn differs for PatchSet.Id " + c.getId() + ",1:"
+ " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
// One NoteDb, slop is allowed.
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), patchSets(ps2), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
// But not too much slop.
@@ -728,9 +733,9 @@
PatchSet ps3 = clone(ps1);
ps3.setCreatedOn(TimeUtil.nowTs());
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
ChangeBundle b3 = new ChangeBundle(c, messages(), patchSets(ps3),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
String msg = "createdOn differs for PatchSet.Id " + c.getId()
+ ",1 in NoteDb vs. ReviewDb:"
+ " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}";
@@ -752,16 +757,16 @@
ps2.setPushCertificate(ps2.getPushCertificate() + "\n\n");
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
- approvals(), comments(), NOTE_DB);
+ approvals(), comments(), reviewers(), NOTE_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps2),
- approvals(), comments(), REVIEW_DB);
+ approvals(), comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
b2 = new ChangeBundle(c, messages(), patchSets(ps2), approvals(),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
}
@@ -793,24 +798,24 @@
// Both ReviewDb.
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
- approvals(a1), comments(), REVIEW_DB);
+ approvals(a1), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2),
- approvals(a1, a2), comments(), REVIEW_DB);
+ approvals(a1, a2), comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
// One NoteDb.
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(a1),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2), approvals(a1, a2),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
// Both NoteDb.
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(a1),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2), approvals(a1, a2),
- comments(), NOTE_DB);
+ comments(), reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
}
@@ -830,9 +835,9 @@
TimeUtil.nowTs());
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"PatchSetApproval.Key sets differ:"
@@ -850,9 +855,9 @@
TimeUtil.nowTs());
PatchSetApproval a2 = clone(a1);
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
@@ -877,9 +882,9 @@
// Both are ReviewDb, exact timestamp match is required.
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"granted differs for PatchSetApproval.Key "
+ c.getId() + "%2C1,100,Code-Review:"
@@ -887,9 +892,9 @@
// One NoteDb, slop is allowed.
b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
// But not too much slop.
@@ -897,9 +902,9 @@
PatchSetApproval a3 = clone(a1);
a3.setGranted(TimeUtil.nowTs());
b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
ChangeBundle b3 = new ChangeBundle(c, messages(), latest(c), approvals(a3),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
String msg = "granted differs for PatchSetApproval.Key "
+ c.getId() + "%2C1,100,Code-Review in NoteDb vs. ReviewDb:"
+ " {2009-09-30 17:00:07.0} != {2009-09-30 17:00:15.0}";
@@ -923,9 +928,9 @@
// Both are ReviewDb, exact match is required.
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
- comments(), REVIEW_DB);
+ comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"granted differs for PatchSetApproval.Key "
+ c.getId() + "%2C1,100,Code-Review:"
@@ -933,14 +938,91 @@
// Truncating NoteDb timestamp is allowed.
b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2), comments(),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
}
@Test
+ public void diffReviewerKeySets() throws Exception {
+ Change c = TestChanges.newChange(project, accountId);
+ Timestamp now = TimeUtil.nowTs();
+ ReviewerSet r1 = reviewers(REVIEWER, new Account.Id(1), now);
+ ReviewerSet r2 = reviewers(REVIEWER, new Account.Id(2), now);
+
+ ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r1, REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r2, REVIEW_DB);
+ assertNoDiffs(b1, b1);
+ assertNoDiffs(b2, b2);
+ assertDiffs(b1, b2,
+ "ReviewerKey sets differ:"
+ + " [REVIEWER,1] only in A;"
+ + " [REVIEWER,2] only in B");
+ }
+
+ @Test
+ public void diffReviewerTimestamps() throws Exception {
+ Change c = TestChanges.newChange(project, accountId);
+ ReviewerSet r1 = reviewers(REVIEWER, new Account.Id(1), TimeUtil.nowTs());
+ ReviewerSet r2 = reviewers(REVIEWER, new Account.Id(1), TimeUtil.nowTs());
+
+ ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r1, REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r2, REVIEW_DB);
+ assertDiffs(b1, b2,
+ "timestamp differs for ReviewerKey REVIEWER,1:"
+ + " {2009-09-30 17:00:06.0} != {2009-09-30 17:00:12.0}");
+
+ b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r1,
+ REVIEW_DB);
+ b2 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r2,
+ NOTE_DB);
+ assertDiffs(b1, b2,
+ "timestamp differs for ReviewerKey REVIEWER,1 in NoteDb vs. ReviewDb:"
+ + " {2009-09-30 17:00:12.0} != {2009-09-30 17:00:06.0}");
+ }
+
+ @Test
+ public void diffReviewerTimestampsAllowsSlop() throws Exception {
+ subWindowResolution();
+ Change c = TestChanges.newChange(project, accountId);
+ ReviewerSet r1 = reviewers(REVIEWER, new Account.Id(1), TimeUtil.nowTs());
+ ReviewerSet r2 = reviewers(REVIEWER, new Account.Id(1), TimeUtil.nowTs());
+
+ // Both are ReviewDb, exact timestamp match is required.
+ ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r1, REVIEW_DB);
+ ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r2, REVIEW_DB);
+ assertDiffs(b1, b2,
+ "timestamp differs for ReviewerKey REVIEWER,1:"
+ + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
+
+ // One NoteDb, slop is allowed
+ b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r1,
+ NOTE_DB);
+ b2 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r2,
+ REVIEW_DB);
+ assertNoDiffs(b1, b2);
+
+ // But not too much slop.
+ superWindowResolution();
+ ReviewerSet r3 = reviewers(REVIEWER, new Account.Id(1), TimeUtil.nowTs());
+ b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r1,
+ NOTE_DB);
+ ChangeBundle b3 = new ChangeBundle(c, messages(), latest(c), approvals(),
+ comments(), r3, REVIEW_DB);
+ assertDiffs(b1, b3,
+ "timestamp differs for ReviewerKey REVIEWER,1 in NoteDb vs. ReviewDb:"
+ + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}");
+ }
+
+ @Test
public void diffPatchLineCommentKeySets() throws Exception {
Change c = TestChanges.newChange(project, accountId);
int id = c.getId().get();
@@ -954,9 +1036,9 @@
5, accountId, null, TimeUtil.nowTs());
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c1), REVIEW_DB);
+ comments(c1), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c2), REVIEW_DB);
+ comments(c2), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"PatchLineComment.Key sets differ:"
@@ -973,9 +1055,9 @@
5, accountId, null, TimeUtil.nowTs());
PatchLineComment c2 = clone(c1);
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c1), REVIEW_DB);
+ comments(c1), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c2), REVIEW_DB);
+ comments(c2), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
@@ -999,9 +1081,9 @@
// Both are ReviewDb, exact timestamp match is required.
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c1), REVIEW_DB);
+ comments(c1), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c2), REVIEW_DB);
+ comments(c2), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"writtenOn differs for PatchLineComment.Key "
+ c.getId() + ",1,filename,uuid:"
@@ -1009,9 +1091,9 @@
// One NoteDb, slop is allowed.
b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(c1),
- NOTE_DB);
+ reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(c2),
- REVIEW_DB);
+ reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
// But not too much slop.
@@ -1019,9 +1101,9 @@
PatchLineComment c3 = clone(c1);
c3.setWrittenOn(TimeUtil.nowTs());
b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(c1),
- NOTE_DB);
+ reviewers(), NOTE_DB);
ChangeBundle b3 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c3), REVIEW_DB);
+ comments(c3), reviewers(), REVIEW_DB);
String msg = "writtenOn differs for PatchLineComment.Key " + c.getId()
+ ",1,filename,uuid in NoteDb vs. ReviewDb:"
+ " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}";
@@ -1043,9 +1125,9 @@
5, accountId, null, TimeUtil.nowTs());
ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c1, c2), REVIEW_DB);
+ comments(c1, c2), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
- comments(c1), REVIEW_DB);
+ comments(c1), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
}
@@ -1085,6 +1167,17 @@
return Arrays.asList(ents);
}
+ private static ReviewerSet reviewers(Object... ents) {
+ checkArgument(ents.length % 3 == 0);
+ Table<ReviewerStateInternal, Account.Id, Timestamp> t =
+ HashBasedTable.create();
+ for (int i = 0; i < ents.length; i += 3) {
+ t.put((ReviewerStateInternal) ents[i], (Account.Id) ents[i + 1],
+ (Timestamp) ents[i + 2]);
+ }
+ return ReviewerSet.fromTable(t);
+ }
+
private static List<PatchLineComment> comments(PatchLineComment... ents) {
return Arrays.asList(ents);
}
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 3f77498..1f54f55 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
@@ -28,7 +28,7 @@
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -46,6 +46,7 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
import com.google.gerrit.testutil.TestChanges;
import com.google.gwtorm.server.OrmException;
@@ -392,10 +393,12 @@
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getReviewers()).isEqualTo(
- ImmutableSetMultimap.of(
- REVIEWER, new Account.Id(1),
- REVIEWER, new Account.Id(2)));
+ Timestamp ts = new Timestamp(update.getWhen().getTime());
+ assertThat(notes.getReviewers()).isEqualTo(ReviewerSet.fromTable(
+ ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>builder()
+ .put(REVIEWER, new Account.Id(1), ts)
+ .put(REVIEWER, new Account.Id(2), ts)
+ .build()));
}
@Test
@@ -407,10 +410,12 @@
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getReviewers()).isEqualTo(
- ImmutableSetMultimap.of(
- REVIEWER, new Account.Id(1),
- CC, new Account.Id(2)));
+ Timestamp ts = new Timestamp(update.getWhen().getTime());
+ assertThat(notes.getReviewers()).isEqualTo(ReviewerSet.fromTable(
+ ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>builder()
+ .put(REVIEWER, new Account.Id(1), ts)
+ .put(CC, new Account.Id(2), ts)
+ .build()));
}
@Test
@@ -421,16 +426,18 @@
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getReviewers()).isEqualTo(
- ImmutableSetMultimap.of(REVIEWER, new Account.Id(2)));
+ Timestamp ts = new Timestamp(update.getWhen().getTime());
+ assertThat(notes.getReviewers()).isEqualTo(ReviewerSet.fromTable(
+ ImmutableTable.of(REVIEWER, new Account.Id(2), ts)));
update = newUpdate(c, otherUser);
update.putReviewer(otherUser.getAccount().getId(), CC);
update.commit();
notes = newNotes(c);
- assertThat(notes.getReviewers()).isEqualTo(
- ImmutableSetMultimap.of(CC, new Account.Id(2)));
+ ts = new Timestamp(update.getWhen().getTime());
+ assertThat(notes.getReviewers()).isEqualTo(ReviewerSet.fromTable(
+ ImmutableTable.of(CC, new Account.Id(2), ts)));
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
index f29a351..e3c382a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
@@ -79,7 +79,7 @@
schemaCreator.create(db);
Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user"))
.getAccountId();
- user = userFactory.create(Providers.of(db), userId);
+ user = userFactory.create(userId);
Project.NameKey name = new Project.NameKey("project");
InMemoryRepository inMemoryRepo = repoManager.createRepository(name);
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 bcc9b38..dad134b 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
@@ -66,6 +66,7 @@
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
@@ -119,6 +120,7 @@
@Inject protected GerritApi gApi;
@Inject protected IdentifiedUser.GenericFactory userFactory;
@Inject protected ChangeIndexCollection indexes;
+ @Inject protected ChangeIndexer indexer;
@Inject protected InMemoryDatabase schemaFactory;
@Inject protected InMemoryRepositoryManager repoManager;
@Inject protected InternalChangeQuery internalChangeQuery;
@@ -154,13 +156,13 @@
Account userAccount = db.accounts().get(userId);
userAccount.setPreferredEmail("user@example.com");
db.accounts().update(ImmutableList.of(userAccount));
- user = userFactory.create(Providers.of(db), userId);
+ user = userFactory.create(userId);
requestContext.setContext(newRequestContext(userAccount.getId()));
}
protected RequestContext newRequestContext(Account.Id requestUserId) {
final CurrentUser requestUser =
- userFactory.create(Providers.of(db), requestUserId);
+ userFactory.create(requestUserId);
return new RequestContext() {
@Override
public CurrentUser getUser() {
@@ -569,7 +571,7 @@
.reviewer(user.getAccountId().toString())
.votes();
assertThat(m).hasSize(1);
- assertThat(m).containsEntry("Code-Review", new Short((short)1));
+ assertThat(m).containsEntry("Code-Review", Short.valueOf((short) 1));
Map<Integer, Change> changes = new LinkedHashMap<>(5);
changes.put(2, reviewPlus2Change);
@@ -1087,8 +1089,24 @@
@Test
public void byHashtagWithoutNoteDb() throws Exception {
- assume().that(notesMigration.enabled()).isFalse();
- setUpHashtagChanges();
+ assume().that(notesMigration.readChanges()).isFalse();
+
+ notesMigration.setWriteChanges(true);
+ notesMigration.setReadChanges(true);
+ db.close();
+ db = schemaFactory.open();
+ List<Change> changes;
+ try {
+ changes = setUpHashtagChanges();
+ notesMigration.setWriteChanges(false);
+ notesMigration.setReadChanges(false);
+ } finally {
+ db.close();
+ }
+ db = schemaFactory.open();
+ for (Change c : changes) {
+ indexer.index(db, c); // Reindex without hashtag field.
+ }
assertQuery("hashtag:foo");
assertQuery("hashtag:bar");
assertQuery("hashtag:\" bar \"");
@@ -1415,7 +1433,7 @@
@Test
public void prepopulatedFields() throws Exception {
- assume().that(notesMigration.enabled()).isFalse();
+ assume().that(notesMigration.readChanges()).isFalse();
TestRepository<Repo> repo = createProject("repo");
Change change = insert(repo, newChange(repo));
@@ -1444,7 +1462,7 @@
@Test
public void prepopulateOnlyRequestedFields() throws Exception {
- assume().that(notesMigration.enabled()).isFalse();
+ assume().that(notesMigration.readChanges()).isFalse();
TestRepository<Repo> repo = createProject("repo");
Change change = insert(repo, newChange(repo));
@@ -1522,7 +1540,7 @@
Project.NameKey project = new Project.NameKey(
repo.getRepository().getDescription().getRepositoryName());
Account.Id ownerId = owner != null ? owner : userId;
- IdentifiedUser user = userFactory.create(Providers.of(db), ownerId);
+ IdentifiedUser user = userFactory.create(ownerId);
try (BatchUpdate bu =
updateFactory.create(db, project, user, TimeUtil.nowTs())) {
bu.insertChange(ins);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
deleted file mode 100644
index ba62cf7..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.util;
-
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
-
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.lib.BlobBasedConfig;
-import org.eclipse.jgit.lib.Constants;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-public class SubmoduleSectionParserTest extends LocalDiskRepositoryTestCase {
- private static final String THIS_SERVER = "localhost";
- private ProjectCache projectCache;
- private BlobBasedConfig bbc;
-
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- projectCache = createStrictMock(ProjectCache.class);
- bbc = createStrictMock(BlobBasedConfig.class);
- }
-
- private void doReplay() {
- replay(projectCache, bbc);
- }
-
- private void doVerify() {
- verify(projectCache, bbc);
- }
-
- @Test
- public void testSubmodulesParseWithCorrectSections() throws Exception {
- final Map<String, SubmoduleSection> sectionsToReturn = new TreeMap<>();
- sectionsToReturn.put("a", new SubmoduleSection("ssh://localhost/a", "a",
- "."));
- sectionsToReturn.put("b", new SubmoduleSection("ssh://localhost/b", "b",
- "."));
- sectionsToReturn.put("c", new SubmoduleSection("ssh://localhost/test/c",
- "c-path", "refs/heads/master"));
- sectionsToReturn.put("d", new SubmoduleSection("ssh://localhost/d",
- "d-parent/the-d-folder", "refs/heads/test"));
- sectionsToReturn.put("e", new SubmoduleSection("ssh://localhost/e.git", "e",
- "."));
-
- Map<String, String> reposToBeFound = new HashMap<>();
- reposToBeFound.put("a", "a");
- reposToBeFound.put("b", "b");
- reposToBeFound.put("c", "test/c");
- reposToBeFound.put("d", "d");
- reposToBeFound.put("e", "e");
-
- final Branch.NameKey superBranchNameKey =
- new Branch.NameKey(new Project.NameKey("super-project"),
- "refs/heads/master");
-
- Set<SubmoduleSubscription> expectedSubscriptions = new HashSet<>();
- expectedSubscriptions
- .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
- new Project.NameKey("a"), "refs/heads/master"), "a"));
- expectedSubscriptions
- .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
- new Project.NameKey("b"), "refs/heads/master"), "b"));
- expectedSubscriptions.add(new SubmoduleSubscription(superBranchNameKey,
- new Branch.NameKey(new Project.NameKey("test/c"), "refs/heads/master"),
- "c-path"));
- expectedSubscriptions.add(new SubmoduleSubscription(superBranchNameKey,
- new Branch.NameKey(new Project.NameKey("d"), "refs/heads/test"),
- "d-parent/the-d-folder"));
- expectedSubscriptions
- .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
- new Project.NameKey("e"), "refs/heads/master"), "e"));
-
- execute(superBranchNameKey, sectionsToReturn, reposToBeFound,
- expectedSubscriptions);
- }
-
- @Test
- public void testSubmodulesParseWithAnInvalidSection() throws Exception {
- final Map<String, SubmoduleSection> sectionsToReturn = new TreeMap<>();
- sectionsToReturn.put("a", new SubmoduleSection("ssh://localhost/a", "a",
- "."));
- // This one is invalid since "b" is not a recognized project
- sectionsToReturn.put("b", new SubmoduleSection("ssh://localhost/b", "b",
- "."));
- sectionsToReturn.put("c", new SubmoduleSection("ssh://localhost/test/c",
- "c-path", "refs/heads/master"));
- sectionsToReturn.put("d", new SubmoduleSection("ssh://localhost/d",
- "d-parent/the-d-folder", "refs/heads/test"));
- sectionsToReturn.put("e", new SubmoduleSection("ssh://localhost/e.git", "e",
- "."));
-
- // "b" will not be in this list
- Map<String, String> reposToBeFound = new HashMap<>();
- reposToBeFound.put("a", "a");
- reposToBeFound.put("c", "test/c");
- reposToBeFound.put("d", "d");
- reposToBeFound.put("e", "e");
-
- final Branch.NameKey superBranchNameKey =
- new Branch.NameKey(new Project.NameKey("super-project"),
- "refs/heads/master");
-
- Set<SubmoduleSubscription> expectedSubscriptions = new HashSet<>();
- expectedSubscriptions
- .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
- new Project.NameKey("a"), "refs/heads/master"), "a"));
- expectedSubscriptions.add(new SubmoduleSubscription(superBranchNameKey,
- new Branch.NameKey(new Project.NameKey("test/c"), "refs/heads/master"),
- "c-path"));
- expectedSubscriptions.add(new SubmoduleSubscription(superBranchNameKey,
- new Branch.NameKey(new Project.NameKey("d"), "refs/heads/test"),
- "d-parent/the-d-folder"));
- expectedSubscriptions
- .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
- new Project.NameKey("e"), "refs/heads/master"), "e"));
-
- execute(superBranchNameKey, sectionsToReturn, reposToBeFound,
- expectedSubscriptions);
- }
-
- @Test
- public void testSubmoduleSectionToOtherServer() throws Exception {
- Map<String, SubmoduleSection> sectionsToReturn = new HashMap<>();
- // The url is not to this server.
- sectionsToReturn.put("a", new SubmoduleSection("ssh://review.source.com/a",
- "a", "."));
-
- Set<SubmoduleSubscription> expectedSubscriptions = Collections.emptySet();
- execute(new Branch.NameKey(new Project.NameKey("super-project"),
- "refs/heads/master"), sectionsToReturn, new HashMap<String, String>(),
- expectedSubscriptions);
- }
-
- @Test
- public void testProjectNotFound() throws Exception {
- Map<String, SubmoduleSection> sectionsToReturn = new HashMap<>();
- sectionsToReturn.put("a", new SubmoduleSection("ssh://localhost/a", "a",
- "."));
-
- Set<SubmoduleSubscription> expectedSubscriptions = Collections.emptySet();
- execute(new Branch.NameKey(new Project.NameKey("super-project"),
- "refs/heads/master"), sectionsToReturn, new HashMap<String, String>(),
- expectedSubscriptions);
- }
-
- @Test
- public void testProjectWithSlashesNotFound() throws Exception {
- Map<String, SubmoduleSection> sectionsToReturn = new HashMap<>();
- sectionsToReturn.put("project", new SubmoduleSection(
- "ssh://localhost/company/tools/project", "project", "."));
-
- Set<SubmoduleSubscription> expectedSubscriptions = Collections.emptySet();
- execute(new Branch.NameKey(new Project.NameKey("super-project"),
- "refs/heads/master"), sectionsToReturn, new HashMap<String, String>(),
- expectedSubscriptions);
- }
-
- @Test
- public void testSubmodulesParseWithSubProjectFound() throws Exception {
- Map<String, SubmoduleSection> sectionsToReturn = new TreeMap<>();
- sectionsToReturn.put("a/b", new SubmoduleSection(
- "ssh://localhost/a/b", "a/b", "."));
-
- Map<String, String> reposToBeFound = new HashMap<>();
- reposToBeFound.put("a/b", "a/b");
- reposToBeFound.put("b", "b");
-
- Branch.NameKey superBranchNameKey =
- new Branch.NameKey(new Project.NameKey("super-project"),
- "refs/heads/master");
-
- Set<SubmoduleSubscription> expectedSubscriptions = new HashSet<>();
- expectedSubscriptions
- .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
- new Project.NameKey("a/b"), "refs/heads/master"), "a/b"));
- execute(superBranchNameKey, sectionsToReturn, reposToBeFound,
- expectedSubscriptions);
- }
-
- private void execute(final Branch.NameKey superProjectBranch,
- final Map<String, SubmoduleSection> sectionsToReturn,
- final Map<String, String> reposToBeFound,
- final Set<SubmoduleSubscription> expectedSubscriptions) throws Exception {
- expect(bbc.getSubsections("submodule"))
- .andReturn(sectionsToReturn.keySet());
-
- for (Map.Entry<String, SubmoduleSection> entry : sectionsToReturn.entrySet()) {
- String id = entry.getKey();
- final SubmoduleSection section = entry.getValue();
- expect(bbc.getString("submodule", id, "url")).andReturn(section.getUrl());
- expect(bbc.getString("submodule", id, "path")).andReturn(
- section.getPath());
- expect(bbc.getString("submodule", id, "branch")).andReturn(
- section.getBranch());
-
- if (THIS_SERVER.equals(new URI(section.getUrl()).getHost())) {
- String projectNameCandidate;
- final String urlExtractedPath = new URI(section.getUrl()).getPath();
- int fromIndex = urlExtractedPath.length() - 1;
- while (fromIndex > 0) {
- fromIndex = urlExtractedPath.lastIndexOf('/', fromIndex - 1);
- projectNameCandidate = urlExtractedPath.substring(fromIndex + 1);
- if (projectNameCandidate.endsWith(Constants.DOT_GIT_EXT)) {
- projectNameCandidate = projectNameCandidate.substring(0, //
- projectNameCandidate.length() - Constants.DOT_GIT_EXT.length());
- }
- if (reposToBeFound.containsValue(projectNameCandidate)) {
- expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
- .andReturn(createNiceMock(ProjectState.class));
- } else {
- expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
- .andReturn(null);
- }
- }
- }
- }
-
- doReplay();
-
- final SubmoduleSectionParser ssp =
- new SubmoduleSectionParser(projectCache, bbc, THIS_SERVER,
- superProjectBranch);
-
- Set<SubmoduleSubscription> returnedSubscriptions = ssp.parseAllSections();
-
- doVerify();
-
- assertEquals(expectedSubscriptions, returnedSubscriptions);
- }
-
- private static final class SubmoduleSection {
- private final String url;
- private final String path;
- private final String branch;
-
- SubmoduleSection(final String url, final String path,
- final String branch) {
- this.url = url;
- this.path = path;
- this.branch = branch;
- }
-
- public String getUrl() {
- return url;
- }
-
- public String getPath() {
- return path;
- }
-
- public String getBranch() {
- return branch;
- }
- }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
index bbad2be..fde3a66 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
@@ -63,19 +63,19 @@
for (String name : chain(command)) {
CommandProvider p = map.get(name);
if (p == null) {
- throw new UnloggedFailure(1, getName() + ": not found");
+ throw die(getName() + ": not found");
}
Command cmd = p.getProvider().get();
if (!(cmd instanceof DispatchCommand)) {
- throw new UnloggedFailure(1, getName() + ": not found");
+ throw die(getName() + ": not found");
}
map = ((DispatchCommand) cmd).getMap();
}
CommandProvider p = map.get(command.value());
if (p == null) {
- throw new UnloggedFailure(1, getName() + ": not found");
+ throw die(getName() + ": not found");
}
Command cmd = p.getProvider().get();
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 f296ef3..2873c37 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
@@ -377,6 +377,14 @@
return new UnloggedFailure(1, "fatal: " + why.getMessage(), why);
}
+ protected void writeError(String type, String msg) {
+ try {
+ err.write((type + ": " + msg + "\n").getBytes(ENC));
+ } catch (IOException e) {
+ // Ignored
+ }
+ }
+
public void checkExclusivity(final Object arg1, final String arg1name,
final Object arg2, final String arg2name) throws UnloggedFailure {
if (arg1 != null && arg2 != null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
new file mode 100644
index 0000000..aa361af
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeFinder;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.BaseCommand.UnloggedFailure;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class ChangeArgumentParser {
+ private final CurrentUser currentUser;
+ private final ChangesCollection changesCollection;
+ private final ChangeFinder changeFinder;
+ private final ReviewDb db;
+
+ @Inject
+ ChangeArgumentParser(CurrentUser currentUser,
+ ChangesCollection changesCollection,
+ ChangeFinder changeFinder,
+ ReviewDb db) {
+ this.currentUser = currentUser;
+ this.changesCollection = changesCollection;
+ this.changeFinder = changeFinder;
+ this.db = db;
+ }
+
+ public void addChange(String id, Map<Change.Id, ChangeResource> changes)
+ throws UnloggedFailure, OrmException {
+ addChange(id, changes, null);
+ }
+
+ public void addChange(String id, Map<Change.Id, ChangeResource> changes,
+ ProjectControl projectControl) throws UnloggedFailure, OrmException {
+ List<ChangeControl> matched = changeFinder.find(id, currentUser);
+ List<ChangeControl> toAdd = new ArrayList<>(changes.size());
+ for (ChangeControl ctl : matched) {
+ if (!changes.containsKey(ctl.getId())
+ && inProject(projectControl, ctl.getProject())
+ && ctl.isVisible(db)) {
+ toAdd.add(ctl);
+ }
+ }
+
+ if (toAdd.isEmpty()) {
+ throw new UnloggedFailure(1, "\"" + id + "\" no such change");
+ } else if (toAdd.size() > 1) {
+ throw new UnloggedFailure(1, "\"" + id + "\" matches multiple changes");
+ }
+ ChangeControl ctl = toAdd.get(0);
+ changes.put(ctl.getId(), changesCollection.parse(ctl));
+ }
+
+ private boolean inProject(ProjectControl projectControl, Project project) {
+ if (projectControl != null) {
+ return projectControl.getProject().getNameKey().equals(project.getNameKey());
+ } else {
+ // No --project option, so they want every project.
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index 8873be9..f2911dc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -73,7 +73,7 @@
if (Strings.isNullOrEmpty(commandName)) {
StringWriter msg = new StringWriter();
msg.write(usage());
- throw new UnloggedFailure(1, msg.toString());
+ throw die(msg.toString());
}
final CommandProvider p = commands.get(commandName);
@@ -81,7 +81,7 @@
String msg =
(getName().isEmpty() ? "Gerrit Code Review" : getName()) + ": "
+ commandName + ": not found";
- throw new UnloggedFailure(1, msg);
+ throw die(msg);
}
final Command cmd = p.getProvider().get();
@@ -96,7 +96,7 @@
bc.setArguments(args.toArray(new String[args.size()]));
} else if (!args.isEmpty()) {
- throw new UnloggedFailure(1, commandName + " does not take arguments");
+ throw die(commandName + " does not take arguments");
}
provideStateTo(cmd);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
index 4308db9..24bd8c2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
@@ -115,10 +115,9 @@
if (caller instanceof PeerDaemonUser) {
// OK.
} else if (!enableRunAs) {
- throw new UnloggedFailure(1,
- "fatal: suexec disabled by auth.enableRunAs = false");
+ throw die("suexec disabled by auth.enableRunAs = false");
} else if (!caller.getCapabilities().canRunAs()) {
- throw new UnloggedFailure(1, "fatal: suexec not permitted");
+ throw die("suexec not permitted");
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
index 850026b..237d844 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
@@ -47,7 +47,7 @@
try {
checkPermission();
} catch (PermissionDeniedException err) {
- throw new UnloggedFailure("fatal: " + err.getMessage());
+ throw die(err.getMessage());
}
QueryShell shell = factory.create(in, out);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index b4594d4..eb0d7b2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -84,12 +84,11 @@
@Override
protected void run() throws Failure {
if (oldParent == null && children.isEmpty()) {
- throw new UnloggedFailure(1, "fatal: child projects have to be specified as " +
- "arguments or the --children-of option has to be set");
+ throw die("child projects have to be specified as " +
+ "arguments or the --children-of option has to be set");
}
if (oldParent == null && !excludedChildren.isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --exclude can only be used together " +
- "with --children-of");
+ throw die("--exclude can only be used together with --children-of");
}
final StringBuilder err = new StringBuilder();
@@ -164,7 +163,7 @@
while (err.charAt(err.length() - 1) == '\n') {
err.setLength(err.length() - 1);
}
- throw new UnloggedFailure(1, err.toString());
+ throw die(err.toString());
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
index cc393ce..12f69ed 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
@@ -48,7 +48,7 @@
docResult.url));
}
} catch (DocQueryException dqe) {
- throw new UnloggedFailure(1, "fatal: " + dqe.getMessage());
+ throw die(dqe);
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
index 301bc0e..9f31ddc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java
@@ -76,7 +76,7 @@
OutputFormat.JSON.newGson().toJson(result, stdout);
stdout.print('\n');
} catch (Exception e) {
- throw new UnloggedFailure("Processing of prolog script failed: " + e);
+ throw die("Processing of prolog script failed: " + e);
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
index b1d09e9..d3ec69c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
@@ -48,7 +48,7 @@
gApi.projects().name(project.getProject().getNameKey().get())
.branch(name).create(in);
} catch (RestApiException e) {
- throw new UnloggedFailure(1, "fatal: " + e.getMessage(), e);
+ throw die(e);
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 3ad5156..4ebafb8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -134,7 +134,7 @@
try {
if (!suggestParent) {
if (projectName == null) {
- throw new UnloggedFailure(1, "fatal: Project name is required.");
+ throw die("Project name is required.");
}
ProjectInput input = new ProjectInput();
@@ -176,7 +176,7 @@
}
}
} catch (RestApiException | NoSuchProjectException err) {
- throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
+ throw die(err);
}
}
@@ -188,7 +188,7 @@
String[] s = pluginConfigValue.split("=");
String[] s2 = s[0].split("\\.");
if (s.length != 2 || s2.length != 2) {
- throw new UnloggedFailure(1, "Invalid plugin config value '"
+ throw die("Invalid plugin config value '"
+ pluginConfigValue
+ "', expected format '<plugin-name>.<parameter-name>=<value>'"
+ " or '<plugin-name>.<parameter-name>=<value1,value2,...>'");
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index fcf365c..1f03225 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -60,14 +60,14 @@
try {
if (list) {
if (all || caches.size() > 0) {
- throw error("error: cannot use --list with --all or --cache");
+ throw die("cannot use --list with --all or --cache");
}
doList();
return;
}
if (all && caches.size() > 0) {
- throw error("error: cannot combine --all and --cache");
+ throw die("cannot combine --all and --cache");
} else if (!all && caches.size() == 1 && caches.contains("all")) {
caches.clear();
all = true;
@@ -87,10 +87,6 @@
}
}
- private static UnloggedFailure error(String msg) {
- return new UnloggedFailure(1, msg);
- }
-
@SuppressWarnings("unchecked")
private void doList() {
for (String name : (List<String>) listCaches
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index a3fbcb2..520d194 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -68,11 +68,10 @@
private void verifyCommandLine() throws UnloggedFailure {
if (!all && projects.isEmpty()) {
- throw new UnloggedFailure(1,
- "needs projects as command arguments or --all option");
+ throw die("needs projects as command arguments or --all option");
}
if (all && !projects.isEmpty()) {
- throw new UnloggedFailure(1,
+ throw die(
"either specify projects as command arguments or use --all option");
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
index c508b1d..4991700 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
@@ -14,8 +14,6 @@
package com.google.gerrit.sshd.commands;
-import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
-
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.lucene.LuceneVersionManager;
@@ -28,8 +26,7 @@
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "activate",
- description = "Activate the latest index version available",
- runsAt = MASTER)
+ description = "Activate the latest index version available")
public class IndexActivateCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "INDEX",
@@ -48,8 +45,7 @@
stdout.println("Not activating index, already using latest version");
}
} catch (ReindexerAlreadyRunningException e) {
- throw new UnloggedFailure("Failed to activate latest index: "
- + e.getMessage());
+ throw die("Failed to activate latest index: " + e.getMessage());
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
new file mode 100644
index 0000000..f2c858e
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.Index;
+import com.google.gerrit.sshd.ChangeArgumentParser;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Argument;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@CommandMetaData(name = "changes", description = "Index changes")
+final class IndexChangesCommand extends SshCommand {
+ @Inject
+ private Index index;
+
+ @Inject
+ private ChangeArgumentParser changeArgumentParser;
+
+ @Argument(index = 0, required = true, multiValued = true, metaVar = "CHANGE",
+ usage = "changes to index")
+ void addChange(String token) {
+ try {
+ changeArgumentParser.addChange(token, changes);
+ } catch (UnloggedFailure e) {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ } catch (OrmException e) {
+ throw new IllegalArgumentException("database is down", e);
+ }
+ }
+
+ private Map<Change.Id, ChangeResource> changes = new LinkedHashMap<>();
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ boolean ok = true;
+ for (ChangeResource rsrc : changes.values()) {
+ try {
+ index.apply(rsrc, new Index.Input());
+ } catch (IOException | RestApiException e) {
+ ok = false;
+ writeError("error", String.format(
+ "failed to index change %s: %s", rsrc.getId(), e.getMessage()));
+ }
+ }
+ if (!ok) {
+ throw die("failed to index one or more changes");
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
index 3e7b293..633bca8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexCommandsModule.java
@@ -28,5 +28,6 @@
command(index).toProvider(new DispatchCommandProvider(index));
command(index, IndexActivateCommand.class);
command(index, IndexStartCommand.class);
+ command(index, IndexChangesCommand.class);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
index c2c565f..73e9f33 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
@@ -14,8 +14,6 @@
package com.google.gerrit.sshd.commands;
-import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
-
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.lucene.LuceneVersionManager;
@@ -27,8 +25,7 @@
import org.kohsuke.args4j.Argument;
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "start", description = "Start the online reindexer",
- runsAt = MASTER)
+@CommandMetaData(name = "start", description = "Start the online reindexer")
public class IndexStartCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "INDEX",
@@ -47,7 +44,7 @@
stdout.println("Nothing to reindex, index is already the latest version");
}
} catch (ReindexerAlreadyRunningException e) {
- throw new UnloggedFailure("Failed to start reindexer: " + e.getMessage());
+ throw die("Failed to start reindexer: " + e.getMessage());
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index bd97286..2e11ef9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -49,7 +49,7 @@
@Override
public void run() throws Exception {
if (impl.getUser() != null && !impl.getProjects().isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
+ throw die("--user and --project options are not compatible.");
}
impl.display(stdout);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index 134a719..d81c153 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -34,10 +34,10 @@
if (!impl.getFormat().isJson()) {
List<String> showBranch = impl.getShowBranch();
if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {
- throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
+ throw die("--tree and --show-branch options are not compatible.");
}
if (impl.isShowTree() && impl.isShowDescription()) {
- throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
+ throw die("--tree and --description options are not compatible.");
}
}
impl.display(out);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index a70d581..1ac347f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -109,11 +109,10 @@
+ projectControl.getProject().getNameKey(), e);
}
} catch (RepositoryNotFoundException e) {
- throw new UnloggedFailure("fatal: '"
- + projectControl.getProject().getNameKey() + "': not a git archive");
+ throw die("'" + projectControl.getProject().getNameKey()
+ + "': not a git archive");
} catch (IOException e) {
- throw new UnloggedFailure("fatal: Error opening: '"
- + projectControl.getProject().getNameKey());
+ throw die("Error opening: '" + projectControl.getProject().getNameKey());
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index f65a0c9..8bde743 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -102,7 +102,7 @@
super.parseCommandLine();
if (processor.getIncludeFiles() &&
!(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
- throw new UnloggedFailure(1, "--files option needs --patch-sets or --current-patch-set");
+ throw die("--files option needs --patch-sets or --current-patch-set");
}
}
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 0b12aa6..011cb91 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
@@ -83,7 +83,7 @@
Capable r = receive.canUpload();
if (r != Capable.OK) {
- throw new UnloggedFailure(1, "\nfatal: " + r.getMessage());
+ throw die(r.getMessage());
}
verifyProjectVisible("reviewer", reviewerId);
@@ -165,7 +165,7 @@
for (final Account.Id id : who) {
final IdentifiedUser user = identifiedUserFactory.create(id);
if (!projectControl.forUser(user).isVisible()) {
- throw new UnloggedFailure(1, type + " "
+ throw die(type + " "
+ user.getAccount().getFullName() + " cannot access the project");
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index dad8672..3c5d5a3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -153,68 +153,68 @@
protected void run() throws UnloggedFailure {
if (abandonChange) {
if (restoreChange) {
- throw error("abandon and restore actions are mutually exclusive");
+ throw die("abandon and restore actions are mutually exclusive");
}
if (submitChange) {
- throw error("abandon and submit actions are mutually exclusive");
+ throw die("abandon and submit actions are mutually exclusive");
}
if (publishPatchSet) {
- throw error("abandon and publish actions are mutually exclusive");
+ throw die("abandon and publish actions are mutually exclusive");
}
if (deleteDraftPatchSet) {
- throw error("abandon and delete actions are mutually exclusive");
+ throw die("abandon and delete actions are mutually exclusive");
}
if (rebaseChange) {
- throw error("abandon and rebase actions are mutually exclusive");
+ throw die("abandon and rebase actions are mutually exclusive");
}
}
if (publishPatchSet) {
if (restoreChange) {
- throw error("publish and restore actions are mutually exclusive");
+ throw die("publish and restore actions are mutually exclusive");
}
if (submitChange) {
- throw error("publish and submit actions are mutually exclusive");
+ throw die("publish and submit actions are mutually exclusive");
}
if (deleteDraftPatchSet) {
- throw error("publish and delete actions are mutually exclusive");
+ throw die("publish and delete actions are mutually exclusive");
}
}
if (json) {
if (restoreChange) {
- throw error("json and restore actions are mutually exclusive");
+ throw die("json and restore actions are mutually exclusive");
}
if (submitChange) {
- throw error("json and submit actions are mutually exclusive");
+ throw die("json and submit actions are mutually exclusive");
}
if (deleteDraftPatchSet) {
- throw error("json and delete actions are mutually exclusive");
+ throw die("json and delete actions are mutually exclusive");
}
if (publishPatchSet) {
- throw error("json and publish actions are mutually exclusive");
+ throw die("json and publish actions are mutually exclusive");
}
if (abandonChange) {
- throw error("json and abandon actions are mutually exclusive");
+ throw die("json and abandon actions are mutually exclusive");
}
if (changeComment != null) {
- throw error("json and message are mutually exclusive");
+ throw die("json and message are mutually exclusive");
}
if (rebaseChange) {
- throw error("json and rebase actions are mutually exclusive");
+ throw die("json and rebase actions are mutually exclusive");
}
if (changeTag != null) {
- throw error("json and tag actions are mutually exclusive");
+ throw die("json and tag actions are mutually exclusive");
}
}
if (rebaseChange) {
if (deleteDraftPatchSet) {
- throw error("rebase and delete actions are mutually exclusive");
+ throw die("rebase and delete actions are mutually exclusive");
}
if (submitChange) {
- throw error("rebase and submit actions are mutually exclusive");
+ throw die("rebase and submit actions are mutually exclusive");
}
}
if (deleteDraftPatchSet && submitChange) {
- throw error("delete and submit actions are mutually exclusive");
+ throw die("delete and submit actions are mutually exclusive");
}
boolean ok = true;
@@ -232,20 +232,21 @@
}
} catch (RestApiException | UnloggedFailure e) {
ok = false;
- writeError("error: " + e.getMessage() + "\n");
+ writeError("error", e.getMessage() + "\n");
} catch (NoSuchChangeException e) {
ok = false;
- writeError("no such change " + patchSet.getId().getParentKey().get());
+ writeError("error",
+ "no such change " + patchSet.getId().getParentKey().get());
} catch (Exception e) {
ok = false;
- writeError("fatal: internal server error while reviewing "
+ writeError("fatal", "internal server error while reviewing "
+ patchSet.getId() + "\n");
log.error("internal error while reviewing " + patchSet.getId(), e);
}
}
if (!ok) {
- throw error("one or more reviews failed; review output above");
+ throw die("one or more reviews failed; review output above");
}
}
@@ -262,8 +263,8 @@
return OutputFormat.JSON.newGson().
fromJson(CharStreams.toString(r), ReviewInput.class);
} catch (IOException | JsonSyntaxException e) {
- writeError(e.getMessage() + '\n');
- throw error("internal error while reading review input");
+ writeError("error", e.getMessage() + '\n');
+ throw die("internal error while reading review input");
}
}
@@ -321,7 +322,7 @@
revisionApi(patchSet).delete();
}
} catch (IllegalStateException | RestApiException e) {
- throw error(e.getMessage());
+ throw die(e);
}
}
@@ -342,7 +343,7 @@
try {
allProjectsControl = projectControlFactory.controlFor(allProjects);
} catch (NoSuchProjectException e) {
- throw new UnloggedFailure("missing " + allProjects.get());
+ throw die("missing " + allProjects.get());
}
for (LabelType type : allProjectsControl.getLabelTypes().getLabelTypes()) {
@@ -360,16 +361,4 @@
super.parseCommandLine();
}
-
- private void writeError(final String msg) {
- try {
- err.write(msg.getBytes(ENC));
- } catch (IOException e) {
- // Ignored
- }
- }
-
- private static UnloggedFailure error(final String msg) {
- return new UnloggedFailure(1, msg);
- }
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 6e03ac1..535f79a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -145,16 +145,14 @@
private void validate() throws UnloggedFailure {
if (active && inactive) {
- throw new UnloggedFailure(1,
- "--active and --inactive options are mutually exclusive.");
+ throw die("--active and --inactive options are mutually exclusive.");
}
if (clearHttpPassword && !Strings.isNullOrEmpty(httpPassword)) {
- throw new UnloggedFailure(1,
- "--http-password and --clear-http-password options are mutually " +
- "exclusive.");
+ throw die("--http-password and --clear-http-password options are "
+ + "mutually exclusive.");
}
if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) {
- throw new UnloggedFailure(1, "Only one option may use the stdin");
+ throw die("Only one option may use the stdin");
}
if (deleteSshKeys.contains("ALL")) {
deleteSshKeys = Collections.singletonList("ALL");
@@ -163,8 +161,7 @@
deleteEmails = Collections.singletonList("ALL");
}
if (deleteEmails.contains(preferredEmail)) {
- throw new UnloggedFailure(1,
- "--preferred-email and --delete-email options are mutually " +
+ throw die("--preferred-email and --delete-email options are mutually " +
"exclusive for the same email address.");
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
index b1d1605..4fef018 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
@@ -49,7 +49,7 @@
try {
setHead.apply(new ProjectResource(project), input);
} catch (UnprocessableEntityException e) {
- throw new UnloggedFailure("fatal: " + e.getMessage());
+ throw die(e);
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index 589fbf0..6328fb4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -155,7 +155,7 @@
md.setMessage("Project settings updated");
config.commit(md);
} catch (RepositoryNotFoundException notFound) {
- err.append("error: Project ").append(name).append(" not found\n");
+ err.append("Project ").append(name).append(" not found\n");
} catch (IOException | ConfigInvalidException e) {
final String msg = "Cannot update project " + name;
log.error(msg, e);
@@ -167,7 +167,7 @@
while (err.charAt(err.length() - 1) == '\n') {
err.setLength(err.length() - 1);
}
- throw new UnloggedFailure(1, err.toString());
+ throw die(err.toString());
}
}
}
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 63cb54f..ac64803 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,17 +18,12 @@
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.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeFinder;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.change.DeleteReviewer;
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.ProjectControl;
+import com.google.gerrit.sshd.ChangeArgumentParser;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
@@ -39,7 +34,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -63,10 +57,10 @@
toRemove.add(who);
}
- @Argument(index = 0, required = true, multiValued = true, metaVar = "COMMIT", usage = "changes to modify")
+ @Argument(index = 0, required = true, multiValued = true, metaVar = "CHANGE", usage = "changes to modify")
void addChange(String token) {
try {
- addChangeImpl(token);
+ changeArgumentParser.addChange(token, changes, projectControl);
} catch (UnloggedFailure e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (OrmException e) {
@@ -75,9 +69,6 @@
}
@Inject
- private ReviewDb db;
-
- @Inject
private ReviewerResource.Factory reviewerFactory;
@Inject
@@ -87,13 +78,7 @@
private DeleteReviewer deleteReviewer;
@Inject
- private CurrentUser currentUser;
-
- @Inject
- private ChangesCollection changesCollection;
-
- @Inject
- private ChangeFinder changeFinder;
+ private ChangeArgumentParser changeArgumentParser;
private Set<Account.Id> toRemove = new HashSet<>();
@@ -113,7 +98,7 @@
}
if (!ok) {
- throw error("fatal: one or more updates failed; review output above");
+ throw die("one or more updates failed; review output above");
}
}
@@ -159,48 +144,4 @@
return ok;
}
-
- private void addChangeImpl(String id) throws UnloggedFailure, OrmException {
- List<ChangeControl> matched = changeFinder.find(id, currentUser);
- List<ChangeControl> toAdd = new ArrayList<>(changes.size());
- for (ChangeControl ctl : matched) {
- if (!changes.containsKey(ctl.getId()) && inProject(ctl.getProject())
- && ctl.isVisible(db)) {
- toAdd.add(ctl);
- }
- }
- switch (toAdd.size()) {
- case 0:
- throw error("\"" + id + "\" no such change");
-
- case 1:
- ChangeControl ctl = toAdd.get(0);
- changes.put(ctl.getId(), changesCollection.parse(ctl));
- break;
-
- default:
- throw error("\"" + id + "\" matches multiple changes");
- }
- }
-
- private boolean inProject(Project project) {
- if (projectControl != null) {
- return projectControl.getProject().getNameKey().equals(project.getNameKey());
- } else {
- // No --project option, so they want every project.
- return true;
- }
- }
-
- private void writeError(String type, String msg) {
- try {
- err.write((type + ": " + msg + "\n").getBytes(ENC));
- } catch (IOException e) {
- // Ignored
- }
- }
-
- private static UnloggedFailure error(String msg) {
- return new UnloggedFailure(1, msg);
- }
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
index 2da1b6d..9e6630a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -14,7 +14,6 @@
package com.google.gerrit.sshd.commands;
-import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Supplier;
@@ -48,8 +47,7 @@
import java.util.concurrent.LinkedBlockingQueue;
@RequiresCapability(GlobalCapability.STREAM_EVENTS)
-@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time",
- runsAt = MASTER)
+@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time")
final class StreamEvents extends BaseCommand {
/** Maximum number of events that may be queued up for each connection. */
private static final int MAX_EVENTS = 128;
diff --git a/lib/BUCK b/lib/BUCK
index 0c424ad..65bdd5e 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -257,8 +257,8 @@
maven_jar(
name = 'javassist',
- id = 'org.javassist:javassist:3.18.1-GA',
- sha1 = 'd9a09f7732226af26bf99f19e2cffe0ae219db5b',
+ id = 'org.javassist:javassist:3.20.0-GA',
+ sha1 = 'a9cbcdfb7e9f86fbc74d3afae65f2248bfbf82a0',
license = 'DO_NOT_DISTRIBUTE',
)
diff --git a/lib/auto/BUCK b/lib/auto/BUCK
index c688ee4..149f2d1 100644
--- a/lib/auto/BUCK
+++ b/lib/auto/BUCK
@@ -2,12 +2,8 @@
maven_jar(
name = 'auto-value',
- id = 'com.google.auto.value:auto-value:1.1',
- sha1 = 'f6951c141ea3e89c0f8b01da16834880a1ebf162',
- # Exclude un-relocated dependencies and replace with our own versions; see
- # https://github.com/google/auto/blob/auto-value-1.1/value/pom.xml#L151
- exclude = ['org/apache/*'],
- deps = ['//lib:velocity'],
+ id = 'com.google.auto.value:auto-value:1.2',
+ sha1 = '6873fed014fe1de1051aae2af68ba266d2934471',
license = 'Apache2.0',
visibility = ['PUBLIC'],
)
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index 9b8a146..67779ab 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -1,14 +1,14 @@
include_defs('//lib/maven.defs')
include_defs('//lib/codemirror/cm.defs')
-VERSION = '5.14.2'
+VERSION = '5.15.2'
TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION
TOP_MINIFIED = 'META-INF/resources/webjars/codemirror-minified/%s' % VERSION
maven_jar(
name = 'codemirror-minified',
id = 'org.webjars.npm:codemirror-minified:' + VERSION,
- sha1 = 'c69056d2a0e07432326e67ea8fe2abb91a065030',
+ sha1 = '222152d6c4f9da6e812378499894e6f86688ac2a',
attach_source = False,
license = 'codemirror-minified',
visibility = [],
@@ -17,7 +17,7 @@
maven_jar(
name = 'codemirror-original',
id = 'org.webjars.npm:codemirror:' + VERSION,
- sha1 = '1ed9697531be85c85edb70fcdf58f10045563f7b',
+ sha1 = '2f0c3e94bb133df1f07800ff0e361da8ac791442',
attach_source = False,
license = 'codemirror-original',
visibility = [],
diff --git a/lib/easymock/BUCK b/lib/easymock/BUCK
index c0cb77b..93640a0 100644
--- a/lib/easymock/BUCK
+++ b/lib/easymock/BUCK
@@ -2,9 +2,9 @@
maven_jar(
name = 'easymock',
- id = 'org.easymock:easymock:3.3.1', # When bumping the version
+ id = 'org.easymock:easymock:3.4', # When bumping the version
# number, make sure to also move powermock to a compatible version
- sha1 = 'a497d7f00c9af78b72b6d8f24762d9210309148a',
+ sha1 = '9fdeea183a399f25c2469497612cad131e920fa3',
license = 'DO_NOT_DISTRIBUTE',
deps = [
':cglib-2_2',
@@ -22,8 +22,8 @@
maven_jar(
name = 'objenesis',
- id = 'org.objenesis:objenesis:2.1',
- sha1 = '87c0ea803b69252868d09308b4618f766f135a96',
+ id = 'org.objenesis:objenesis:2.2',
+ sha1 = '3fb533efdaa50a768c394aa4624144cf8df17845',
license = 'DO_NOT_DISTRIBUTE',
visibility = ['//lib/powermock:powermock-reflect'],
attach_source = False,
diff --git a/lib/js/BUCK b/lib/js/BUCK
index 36a3d19..275941c 100644
--- a/lib/js/BUCK
+++ b/lib/js/BUCK
@@ -17,8 +17,8 @@
npm_binary(
name = 'bower',
- version = '1.6.5',
- sha1 = '59d457122a161e42cc1625bbab8179c214b7ac11',
+ version = '1.7.9',
+ sha1 = 'b7296c2393e0d75edaa6ca39648132dd255812b0',
)
npm_binary(
@@ -102,17 +102,9 @@
bower_component(
name = 'fetch',
package = 'fetch',
- version = '0.11.0',
+ version = '1.0.0',
license = 'fetch',
- sha1 = 'a55d4e291821958d9d400bb3184c12bb367dc670',
-)
-
-bower_component(
- name = 'font-roboto',
- package = 'polymerelements/font-roboto',
- version = '1.0.1',
- license = 'polymer',
- sha1 = '735676217f67221903d6be10cc2fb1b336bed13f',
+ sha1 = '1b05a2bb40c73232c2909dc196de7519fe4db7a9',
)
bower_component(
@@ -127,10 +119,10 @@
bower_component(
name = 'iron-a11y-keys-behavior',
package = 'polymerelements/iron-a11y-keys-behavior',
- version = '1.1.1',
+ version = '1.1.2',
deps = [':polymer'],
license = 'polymer',
- sha1 = '6bb52b967a4fb242897520dad6c366135e3813ce',
+ sha1 = '57fd39ee153ce37ed719ba3f7a405afb987d54f9',
)
bower_component(
@@ -151,19 +143,19 @@
bower_component(
name = 'iron-behaviors',
package = 'polymerelements/iron-behaviors',
- version = '1.0.13',
+ version = '1.0.16',
deps = [
':iron-a11y-keys-behavior',
':polymer',
],
license = 'polymer',
- sha1 = 'e9bcdac5414cb8282b5f75eeb51c9154380045af',
+ sha1 = 'bd70636a2c0a78c50d1a76f9b8ca1ffd815478a3',
)
bower_component(
name = 'iron-dropdown',
package = 'polymerelements/iron-dropdown',
- version = '1.3.0',
+ version = '1.4.0',
deps = [
':iron-a11y-keys-behavior',
':iron-behaviors',
@@ -173,16 +165,16 @@
':polymer',
],
license = 'polymer',
- sha1 = '08ae9c9fa2f2c19a8ab330dfe8240292c8d161cf',
+ sha1 = '63e3d669a09edaa31c4f05afc76b53b919ef0595',
)
bower_component(
name = 'iron-fit-behavior',
package = 'polymerelements/iron-fit-behavior',
- version = '1.0.6',
+ version = '1.2.2',
deps = [':polymer'],
license = 'polymer',
- sha1 = '28df0349d3cb20ac5e4aeb40651ef7d84de75fb0',
+ sha1 = 'bc53e9bab36b21f086ab8fac8c53cc7214aa1890',
)
bower_component(
@@ -206,14 +198,14 @@
bower_component(
name = 'iron-input',
package = 'polymerelements/iron-input',
- version = '1.0.9',
+ version = '1.0.10',
deps = [
':iron-a11y-announcer',
':iron-validatable-behavior',
':polymer',
],
license = 'polymer',
- sha1 = '4e415c2511ec8ff6c8b17249ec8f02e8d8b1a0d9',
+ sha1 = '9bc0c8e81de2527125383cbcf74dd9f27e7fa9ac',
)
bower_component(
@@ -228,7 +220,7 @@
bower_component(
name = 'iron-overlay-behavior',
package = 'polymerelements/iron-overlay-behavior',
- version = '1.4.2',
+ version = '1.7.6',
deps = [
':iron-a11y-keys-behavior',
':iron-fit-behavior',
@@ -236,7 +228,7 @@
':polymer',
],
license = 'polymer',
- sha1 = 'babdd95d7efd63bf3f2969a8f1036e8f324979a9',
+ sha1 = '83181085fda59446ce74fd0d5ca30c223f38ee4a',
)
bower_component(
@@ -251,31 +243,31 @@
bower_component(
name = 'iron-selector',
package = 'polymerelements/iron-selector',
- version = '1.2.5',
+ version = '1.5.2',
deps = [':polymer'],
license = 'polymer',
- sha1 = '7728750bc9dfa858915dfd25397709bdbdaee2b1',
+ sha1 = 'c57235dfda7fbb987c20ad0e97aac70babf1a1bf',
)
bower_component(
name = 'iron-test-helpers',
package = 'polymerelements/iron-test-helpers',
- version = '1.1.5',
+ version = '1.2.5',
deps = [':polymer'],
license = 'DO_NOT_DISTRIBUTE',
- sha1 = '000e2256ae487e4d24edfb6d17dc98626bb8a8e2',
+ sha1 = '433b03b106f5ff32049b84150cd70938e18b67ac',
)
bower_component(
name = 'iron-validatable-behavior',
package = 'polymerelements/iron-validatable-behavior',
- version = '1.0.5',
+ version = '1.1.1',
deps = [
':iron-meta',
':polymer',
],
license = 'polymer',
- sha1 = '5a68250d6d9abcd576f116dc4fc7312426323883',
+ sha1 = '480423380be0536f948735d91bc472f6e7ced5b4',
)
bower_component(
@@ -289,23 +281,23 @@
bower_component(
name = 'mocha',
package = 'mocha',
- version = '2.4.5',
+ version = '2.5.1',
license = 'DO_NOT_DISTRIBUTE',
- sha1 = 'efbb1675710c0ba94a44eb7a4d27040229283197',
+ sha1 = 'cb29bdd1047cfd9304659ecf10ec263f9c888c99',
)
bower_component(
name = 'moment',
package = 'moment/moment',
- version = '2.12.0',
+ version = '2.13.0',
license = 'moment',
- sha1 = '508d53de8f49ab87f03e209e5073e339107ed3e6',
+ sha1 = 'fc8ce2c799bab21f6ced7aff928244f4ca8880aa',
)
bower_component(
name = 'neon-animation',
package = 'polymerelements/neon-animation',
- version = '1.1.1',
+ version = '1.2.3',
deps = [
':iron-meta',
':iron-resizable-behavior',
@@ -314,7 +306,7 @@
':web-animations-js',
],
license = 'polymer',
- sha1 = 'd6e1b45e5a936d0ec0b66b3520e230e9d8605642',
+ sha1 = '71cc0d3e0afdf8b8563e87d2ff03a6fa19183bd9',
)
bower_component(
@@ -326,19 +318,6 @@
)
bower_component(
- name = 'paper-styles',
- package = 'polymerelements/paper-styles',
- version = '1.1.4',
- deps = [
- ':font-roboto',
- ':iron-flex-layout',
- ':polymer',
- ],
- license = 'polymer',
- sha1 = '89276c5ec18b8927a704dda2bf14ff35c310401a',
-)
-
-bower_component(
name = 'polymer',
package = 'polymer/polymer',
version = '1.4.0',
@@ -383,17 +362,17 @@
bower_component(
name = 'test-fixture',
package = 'polymerelements/test-fixture',
- version = '1.1.0',
+ version = '1.1.1',
license = 'DO_NOT_DISTRIBUTE',
- sha1 = '4afc8998ae42b0421847906a7550b997c6fdc088',
+ sha1 = 'e373bd21c069163c3a754e234d52c07c77b20d3c',
)
bower_component(
name = 'web-animations-js',
package = 'web-animations/web-animations-js',
- version = '2.1.4',
+ version = '2.2.1',
license = 'Apache2.0',
- sha1 = '92f06d8417a51f1f75c94b7a19616e19695cc6db',
+ sha1 = '0e73b263a86aa6764ad35c273eb12055f83d7eda',
)
bower_component(
@@ -418,7 +397,8 @@
bower_component(
name = 'webcomponentsjs',
package = 'webcomponentsjs',
- version = '0.7.21',
+ version = '0.7.22',
license = 'polymer',
- sha1 = 'ceb96b01c8a86b17831a25d6ab9eca95226c408e',
+ sha1 = '8ba97a4a279ec6973a19b171c462a7b5cf454fb9',
)
+
diff --git a/lib/powermock/BUCK b/lib/powermock/BUCK
index 221a640..b642457 100644
--- a/lib/powermock/BUCK
+++ b/lib/powermock/BUCK
@@ -1,12 +1,12 @@
include_defs('//lib/maven.defs')
-VERSION = '1.6.2' # When bumping VERSION, make sure to also move
+VERSION = '1.6.4' # When bumping VERSION, make sure to also move
# easymock to a compatible version
maven_jar(
name = 'powermock-module-junit4',
id = 'org.powermock:powermock-module-junit4:' + VERSION,
- sha1 = 'dff58978da716e000463bc1b08013d6a7cf3d696',
+ sha1 = '8692eb1d9bb8eb1310ffe8a20c2da7ee6d1b5994',
license = 'DO_NOT_DISTRIBUTE',
deps = [
':powermock-module-junit4-common',
@@ -17,7 +17,7 @@
maven_jar(
name = 'powermock-module-junit4-common',
id = 'org.powermock:powermock-module-junit4-common:' + VERSION,
- sha1 = '48dd7406e11a14fe2ae4ab641e1f27510e896640',
+ sha1 = 'b0b578da443794ceb8224bd5f5f852aaf40f1b81',
license = 'DO_NOT_DISTRIBUTE',
deps = [
':powermock-reflect',
@@ -28,7 +28,7 @@
maven_jar(
name = 'powermock-reflect',
id = 'org.powermock:powermock-reflect:' + VERSION,
- sha1 = '1af1bbd1207c3ecdcf64973e6f9d57dcd17cc145',
+ sha1 = '5532f4e7c42db4bca4778bc9f1afcd4b0ee0b893',
license = 'DO_NOT_DISTRIBUTE',
deps = [
'//lib:junit',
@@ -39,7 +39,7 @@
maven_jar(
name = 'powermock-api-easymock',
id = 'org.powermock:powermock-api-easymock:' + VERSION,
- sha1 = 'addd25742ac9fe3e0491cbd68e2515e3b06c77fd',
+ sha1 = '5c385a0d8c13f84b731b75c6e90319c532f80b45',
license = 'DO_NOT_DISTRIBUTE',
deps = [
':powermock-api-support',
@@ -50,7 +50,7 @@
maven_jar(
name = 'powermock-api-support',
id = 'org.powermock:powermock-api-support:' + VERSION,
- sha1 = '93b21413b4ee99b7bc0dd34e1416fdca96866aaf',
+ sha1 = '314daafb761541293595630e10a3699ebc07881d',
license = 'DO_NOT_DISTRIBUTE',
deps = [
':powermock-core',
@@ -62,7 +62,7 @@
maven_jar(
name = 'powermock-core',
id = 'org.powermock:powermock-core:' + VERSION,
- sha1 = 'ea04e79244e19dcf0c3ccf6863c5b028b4b58c9c',
+ sha1 = '85fb32e9ccba748d569fc36aef92e0b9e7f40b87',
license = 'DO_NOT_DISTRIBUTE',
deps = [
':powermock-reflect',
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index c167df0..3f3d572 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit c167df08a8550d8c6c7ccf12b7df4fa6bfc6d432
+Subproject commit 3f3d572e9618f268b19cc54856deee4c96180e4c
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 9a7537b..3175517 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -225,7 +225,7 @@
<span class="header-actions">
<gr-button hidden
class="reply"
- primary$="[[_computeReplyButtonHighlighted(_diffDrafts)]]"
+ primary$="[[_computeReplyButtonHighlighted(_diffDrafts.*)]]"
hidden$="[[!_loggedIn]]"
on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
<gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 428d067..831fdb8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -55,7 +55,10 @@
},
_commitInfo: Object,
_changeNum: String,
- _diffDrafts: Object,
+ _diffDrafts: {
+ type: Object,
+ value: function() { return {}; },
+ },
_patchRange: Object,
_allPatchSets: {
type: Array,
@@ -69,14 +72,10 @@
_headerContainerEl: Object,
_headerEl: Object,
_projectConfig: Object,
- _boundScrollHandler: {
- type: Function,
- value: function() { return this._handleBodyScroll.bind(this); },
- },
_replyButtonLabel: {
type: String,
value: 'Reply',
- computed: '_computeReplyButtonLabel(_diffDrafts)',
+ computed: '_computeReplyButtonLabel(_diffDrafts.*)',
},
},
@@ -94,11 +93,14 @@
this._loggedIn = loggedIn;
}.bind(this));
- window.addEventListener('scroll', this._boundScrollHandler);
+ this.addEventListener('comment-save', this._handleCommentSave.bind(this));
+ this.addEventListener('comment-discard',
+ this._handleCommentDiscard.bind(this));
+ this.listen(window, 'scroll', '_handleBodyScroll');
},
detached: function() {
- window.removeEventListener('scroll', this._boundScrollHandler);
+ this.unlisten(window, 'scroll', '_handleBodyScroll');
},
_handleBodyScroll: function(e) {
@@ -124,6 +126,67 @@
el.classList.remove('pinned');
},
+ _handleCommentSave: function(e) {
+ if (!e.target.comment.__draft) { return; }
+
+ var draft = e.target.comment;
+ draft.patch_set = draft.patch_set || this._patchRange.patchNum;
+
+ // The use of path-based notification helpers (set, push) can’t be used
+ // because the paths could contain dots in them. A new object must be
+ // created to satisfy Polymer’s dirty checking.
+ // https://github.com/Polymer/polymer/issues/3127
+ // TODO(andybons): Polyfill for Object.assign in IE.
+ var diffDrafts = Object.assign({}, this._diffDrafts);
+ if (!diffDrafts[draft.path]) {
+ diffDrafts[draft.path] = [draft];
+ this._diffDrafts = diffDrafts;
+ return;
+ }
+ for (var i = 0; i < this._diffDrafts[draft.path].length; i++) {
+ if (this._diffDrafts[draft.path][i].id === draft.id) {
+ diffDrafts[draft.path][i] = draft;
+ this._diffDrafts = diffDrafts;
+ return;
+ }
+ }
+ diffDrafts[draft.path].push(draft);
+ this._diffDrafts = diffDrafts;
+ },
+
+ _handleCommentDiscard: function(e) {
+ if (!e.target.comment.__draft) { return; }
+
+ var draft = e.target.comment;
+ if (!this._diffDrafts[draft.path]) {
+ return;
+ }
+ var index = -1;
+ for (var i = 0; i < this._diffDrafts[draft.path].length; i++) {
+ if (this._diffDrafts[draft.path][i].id === draft.id) {
+ index = i;
+ break;
+ }
+ }
+ if (index === -1) {
+ throw Error('Unable to find draft with id ' + draft.id);
+ }
+
+ draft.patch_set = draft.patch_set || this._patchRange.patchNum;
+
+ // The use of path-based notification helpers (set, push) can’t be used
+ // because the paths could contain dots in them. A new object must be
+ // created to satisfy Polymer’s dirty checking.
+ // https://github.com/Polymer/polymer/issues/3127
+ // TODO(andybons): Polyfill for Object.assign in IE.
+ var diffDrafts = Object.assign({}, this._diffDrafts);
+ diffDrafts[draft.path].splice(index, 1);
+ if (diffDrafts[draft.path].length === 0) {
+ delete diffDrafts[draft.path];
+ }
+ this._diffDrafts = diffDrafts;
+ },
+
_handlePatchChange: function(e) {
var patchNum = e.target.value;
var currentPatchNum =
@@ -220,6 +283,7 @@
if (this.viewState.showReplyDialog) {
this.$.replyOverlay.open();
+ this.async(function() { this.$.replyOverlay.center(); }, 1);
this.set('viewState.showReplyDialog', false);
}
}.bind(this));
@@ -313,12 +377,13 @@
return result;
},
- _computeReplyButtonHighlighted: function(drafts) {
- return Object.keys(drafts || {}).length > 0;
+ _computeReplyButtonHighlighted: function(changeRecord) {
+ var drafts = (changeRecord && changeRecord.base) || {};
+ return Object.keys(drafts).length > 0;
},
- _computeReplyButtonLabel: function(drafts) {
- drafts = drafts || {};
+ _computeReplyButtonLabel: function(changeRecord) {
+ var drafts = (changeRecord && changeRecord.base) || {};
var draftCount = Object.keys(drafts).reduce(function(count, file) {
return count + drafts[file].length;
}, 0);
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 7fcbd32..f4a0a52 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -81,6 +81,42 @@
assert.equal(replyButton.textContent, 'Reply (3)');
});
+ test('comment events properly update diff drafts', function() {
+ element._patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 2,
+ };
+ var draft = {
+ __draft: true,
+ id: 'id1',
+ path: '/foo/bar.txt',
+ text: 'hello',
+ };
+ element._handleCommentSave({target: {comment: draft}});
+ draft.patch_set = 2;
+ assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft]});
+ draft.patch_set = null;
+ draft.text = 'hello, there';
+ element._handleCommentSave({target: {comment: draft}});
+ draft.patch_set = 2;
+ assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft]});
+ var draft2 = {
+ __draft: true,
+ id: 'id2',
+ path: '/foo/bar.txt',
+ text: 'hola',
+ };
+ element._handleCommentSave({target: {comment: draft2}});
+ draft2.patch_set = 2;
+ assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft, draft2]});
+ draft.patch_set = null;
+ element._handleCommentDiscard({target: {comment: draft}});
+ draft.patch_set = 2;
+ assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft2]});
+ element._handleCommentDiscard({target: {comment: draft2}});
+ assert.deepEqual(element._diffDrafts, {});
+ });
+
test('patch num change', function(done) {
element._changeNum = '42';
element._patchRange = {
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index 263fb28..da15406 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -42,12 +42,12 @@
word-wrap: break-word;
}
</style>
- <template is="dom-repeat" items="{{_files}}" as="file">
+ <template is="dom-repeat" items="[[_files]]" as="file">
<div class="file">
<a href$="[[_computeFileDiffURL(file, changeNum, patchNum)]]">[[file]]</a>:
</div>
<template is="dom-repeat"
- items="[[_computeCommentsForFile(file)]]" as="comment">
+ items="[[_computeCommentsForFile(comments, file)]]" as="comment">
<div class="container">
<a class="lineNum"
href$="[[_computeDiffLineURL(file, changeNum, comment.patch_set, comment)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index b40c18e..c23c373 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -19,17 +19,17 @@
properties: {
changeNum: Number,
- comments: {
- type: Object,
- observer: '_commentsChanged',
- },
+ comments: Object,
patchNum: Number,
- _files: Array,
+ _files: {
+ type: Array,
+ computed: '_computeFiles(comments)',
+ },
},
- _commentsChanged: function(value) {
- this._files = Object.keys(value || {}).sort();
+ _computeFiles: function(comments) {
+ return Object.keys(comments || {}).sort();
},
_computeFileDiffURL: function(file, changeNum, patchNum) {
@@ -45,8 +45,8 @@
return diffURL;
},
- _computeCommentsForFile: function(file) {
- return this.comments[file];
+ _computeCommentsForFile: function(comments, file) {
+ return comments[file];
},
_computePatchDisplayName: function(comment) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 97e5bdc..976cf9b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -17,6 +17,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
<link rel="import" href="../../diff/gr-diff/gr-diff.html">
+<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -177,6 +178,9 @@
view-mode="[[_userPrefs.diff_view]]"></gr-diff>
</template>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-diff-cursor
+ id="cursor"
+ fold-offset-top="[[topMargin]]"></gr-diff-cursor>
</template>
<script src="gr-file-list.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 3a6930e..3257469 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -37,7 +37,10 @@
value: function() { return document.body; },
},
- _files: Array,
+ _files: {
+ type: Array,
+ observer: '_filesChanged',
+ },
_loggedIn: {
type: Boolean,
value: false,
@@ -124,19 +127,26 @@
}
},
- _expandAllDiffs: function() {
+ _expandAllDiffs: function(e) {
this._showInlineDiffs = true;
this._forEachDiff(function(diff) {
diff.hidden = false;
diff.reload();
});
+ if (e && e.target) {
+ e.target.blur();
+ }
},
- _collapseAllDiffs: function() {
+ _collapseAllDiffs: function(e) {
this._showInlineDiffs = false;
this._forEachDiff(function(diff) {
diff.hidden = true;
});
+ this.$.cursor.handleDiffUpdate();
+ if (e && e.target) {
+ e.target.blur();
+ }
},
_computeCommentsString: function(comments, patchNum, path) {
@@ -202,21 +212,49 @@
if (this.shouldSupressKeyboardShortcut(e)) { return; }
switch (e.keyCode) {
+ case 37: // left
+ if (e.shiftKey && this._showInlineDiffs) {
+ e.preventDefault();
+ this.$.cursor.moveLeft();
+ }
+ break;
+ case 39: // right
+ if (e.shiftKey && this._showInlineDiffs) {
+ e.preventDefault();
+ this.$.cursor.moveRight();
+ }
+ break;
case 73: // 'i'
if (!e.shiftKey) { return; }
e.preventDefault();
this._toggleInlineDiffs();
break;
+ case 40: // down
case 74: // 'j'
e.preventDefault();
- this.selectedIndex =
- Math.min(this._files.length - 1, this.selectedIndex + 1);
- this._scrollToSelectedFile();
+ if (this._showInlineDiffs) {
+ this.$.cursor.moveDown();
+ } else {
+ this.selectedIndex =
+ Math.min(this._files.length - 1, this.selectedIndex + 1);
+ this._scrollToSelectedFile();
+ }
break;
+ case 38: // up
case 75: // 'k'
e.preventDefault();
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
- this._scrollToSelectedFile();
+ if (this._showInlineDiffs) {
+ this.$.cursor.moveUp();
+ } else {
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
+ this._scrollToSelectedFile();
+ }
+ break;
+ case 67: // 'c'
+ if (this._showInlineDiffs) {
+ e.preventDefault();
+ this._addDraftAtTarget();
+ }
break;
case 219: // '['
e.preventDefault();
@@ -229,7 +267,31 @@
case 13: // <enter>
case 79: // 'o'
e.preventDefault();
- this._openSelectedFile();
+ if (this._showInlineDiffs) {
+ this._openCursorFile();
+ } else {
+ this._openSelectedFile();
+ }
+ break;
+ case 78: // 'n'
+ if (this._showInlineDiffs) {
+ e.preventDefault();
+ if (e.shiftKey) {
+ this.$.cursor.moveToNextCommentThread();
+ } else {
+ this.$.cursor.moveToNextChunk();
+ }
+ }
+ break;
+ case 80: // 'p'
+ if (this._showInlineDiffs) {
+ e.preventDefault();
+ if (e.shiftKey) {
+ this.$.cursor.moveToPreviousCommentThread();
+ } else {
+ this.$.cursor.moveToPreviousChunk();
+ }
+ }
break;
}
},
@@ -242,6 +304,12 @@
}
},
+ _openCursorFile: function() {
+ var diff = this.$.cursor.getTargetDiffElement();
+ page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
+ diff.path));
+ },
+
_openSelectedFile: function(opt_index) {
if (opt_index != null) {
this.selectedIndex = opt_index;
@@ -250,6 +318,14 @@
this._files[this.selectedIndex].__path));
},
+ _addDraftAtTarget: function() {
+ var diff = this.$.cursor.getTargetDiffElement();
+ var target = this.$.cursor.getTargetLineElement();
+ if (diff && target) {
+ diff.addDraftAtLine(target);
+ }
+ },
+
_scrollToSelectedFile: function() {
var el = this.$$('.row[selected]');
var top = 0;
@@ -297,5 +373,15 @@
}
return classes.join(' ');
},
+
+ _filesChanged: function() {
+ this.async(function() {
+ var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
+
+ // Overwrite the cursor's list of diffs:
+ this.$.cursor.splice.apply(this.$.cursor,
+ ['diffs', 0, this.$.cursor.diffs.length].concat(diffElements));
+ }.bind(this), 1);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index ae57be3..c89cb93 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -146,6 +146,10 @@
};
for (var label in this.permittedLabels) {
var selectorEl = this.$$('iron-selector[data-label="' + label + '"]');
+
+ // The selector may not be present if it’s not at the latest patch set.
+ if (!selectorEl) { continue; }
+
var selectedVal = selectorEl.selectedItem.getAttribute('data-value');
selectedVal = parseInt(selectedVal, 10);
obj.labels[label] = selectedVal;
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
index 32de08a..5b70769 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -141,7 +141,10 @@
<td>Show previous change</td>
</tr>
<tr>
- <td><span class="key">Enter</span> or <span class="key">o</span></td>
+ <td>
+ <span class="key">Enter</span> or
+ <span class="key">o</span>
+ </td>
<td>Show selected change</td>
</tr>
</tbody>
@@ -158,17 +161,70 @@
<td></td><td class="header">File list</td>
</tr>
<tr>
- <td><span class="key">j</span></td>
+ <td><span class="key">j</span> or <span class="key">↓</span></td>
<td>Select next file</td>
</tr>
<tr>
- <td><span class="key">k</span></td>
+ <td><span class="key">k</span> or <span class="key">↑</span></td>
<td>Select previous file</td>
</tr>
<tr>
<td><span class="key">Enter</span> or <span class="key">o</span></td>
<td>Show selected file</td>
</tr>
+ <tr>
+ <td></td><td class="header">Diffs</td>
+ </tr>
+ <tr>
+ <td><span class="key">j</span> or <span class="key">↓</span></td>
+ <td>Go to next line</td>
+ </tr>
+ <tr>
+ <td><span class="key">k</span> or <span class="key">↑</span></td>
+ <td>Go to previous line</td>
+ </tr>
+ <tr>
+ <td><span class="key">n</span></td>
+ <td>Go to next diff chunk</td>
+ </tr>
+ <tr>
+ <td><span class="key">p</span></td>
+ <td>Go to previous diff chunk</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">n</span>
+ </td>
+ <td>Go to next comment thread</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">p</span>
+ </td>
+ <td>Go to previous comment thread</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">←</span>
+ </td>
+ <td>Select left pane</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">→</span>
+ </td>
+ <td>Select right pane</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key">c</span>
+ </td>
+ <td>Draft new comment</td>
+ </tr>
</tbody>
<!-- Diff View -->
<tbody hidden$="[[!_computeInView(view, 'gr-diff-view')]]" hidden>
@@ -176,6 +232,14 @@
<td></td><td class="header">Actions</td>
</tr>
<tr>
+ <td><span class="key">j</span> or <span class="key">↓</span></td>
+ <td>Show next line</td>
+ </tr>
+ <tr>
+ <td><span class="key">k</span> or <span class="key">↑</span></td>
+ <td>Show previous line</td>
+ </tr>
+ <tr>
<td><span class="key">n</span></td>
<td>Show next diff chunk</td>
</tr>
@@ -198,6 +262,26 @@
<td>Show previous comment thread</td>
</tr>
<tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">←</span>
+ </td>
+ <td>Select left pane</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">→</span>
+ </td>
+ <td>Select right pane</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="key">c</span>
+ </td>
+ <td>Draft new comment</td>
+ </tr>
+ <tr>
<td><span class="key">a</span></td>
<td>Review and publish comments</td>
</tr>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index c6d8dbc..930c8cf 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -148,7 +148,7 @@
<div class="rightItems">
<gr-search-bar value="{{searchQuery}}" role="search"></gr-search-bar>
<div class="accountContainer" id="accountContainer">
- <a class="loginButton" href$="[[_computeRelativeURL('/login')]]" on-tap="_loginTapHandler">Sign in</a>
+ <a class="loginButton" href$="[[_loginURL]]" on-tap="_loginTapHandler">Sign in</a>
<gr-account-dropdown account="[[_account]]"></gr-account-dropdown>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index 186932a..6fc3cc1 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -56,6 +56,10 @@
type: Array,
computed: '_computeLinks(_defaultLinks, _userLinks)',
},
+ _loginURL: {
+ type: String,
+ value: '/login',
+ },
_userLinks: {
type: Array,
value: function() { return []; },
@@ -68,6 +72,18 @@
attached: function() {
this._loadAccount();
+ this.listen(window, 'location-change', '_handleLocationChange');
+ },
+
+ detached: function() {
+ this.unlisten(window, 'location-change', '_handleLocationChange');
+ },
+
+ _handleLocationChange: function(e) {
+ this._loginURL = '/login/' + encodeURIComponent(
+ window.location.pathname +
+ window.location.search +
+ window.location.hash);
},
_computeRelativeURL: function(path) {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 9145ba1..dea0d1d 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -23,6 +23,12 @@
// Middleware
page(function(ctx, next) {
document.body.scrollTop = 0;
+
+ // Fire asynchronously so that the URL is changed by the time the event
+ // is processed.
+ app.async(function() {
+ app.fire('location-change');
+ }, 1);
next();
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index b827d26..aa5d8bd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -41,10 +41,6 @@
_orderedComments: Array,
},
- get naturalHeight() {
- return this.$.container.offsetHeight;
- },
-
observers: [
'_commentsChanged(comments.splices)',
],
@@ -64,7 +60,11 @@
return;
}
- this.push('comments', this._newDraft(opt_lineNum));
+ var draft = this._newDraft(opt_lineNum);
+ this.push('comments', draft);
+ this.async(function() {
+ this._commentElWithDraftID(draft.__draftID).editing = true;
+ }.bind(this), 1);
},
_getLoggedIn: function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index fd35ad7..6e7a68a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -19,6 +19,7 @@
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
+<link rel="import" href="../../shared/gr-storage/gr-storage.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-diff-comment">
@@ -128,7 +129,7 @@
class="editMessage"
disabled="{{disabled}}"
rows="4"
- bind-value="{{_editDraft}}"
+ bind-value="{{_messageText}}"
on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
<gr-linked-text class="message"
pre
@@ -140,7 +141,7 @@
<gr-button class="action done" on-tap="_handleDone">Done</gr-button>
<gr-button class="action edit" on-tap="_handleEdit">Edit</gr-button>
<gr-button class="action save" on-tap="_handleSave"
- disabled$="[[_computeSaveDisabled(_editDraft)]]">Save</gr-button>
+ disabled$="[[_computeSaveDisabled(_messageText)]]">Save</gr-button>
<gr-button class="action cancel" on-tap="_handleCancel" hidden>Cancel</gr-button>
<div class="danger">
<gr-button class="action discard" on-tap="_handleDiscard">Discard</gr-button>
@@ -148,6 +149,7 @@
</div>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-storage id="storage"></gr-storage>
</template>
<script src="gr-diff-comment.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index ded5108..525bac0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -14,6 +14,8 @@
(function() {
'use strict';
+ var STORAGE_DEBOUNCE_INTERVAL = 400;
+
Polymer({
is: 'gr-diff-comment',
@@ -35,6 +37,12 @@
* @event comment-discard
*/
+ /**
+ * Fired when this comment is saved.
+ *
+ * @event comment-save
+ */
+
properties: {
changeNum: String,
comment: {
@@ -61,17 +69,29 @@
projectConfig: Object,
_xhrPromise: Object, // Used for testing.
- _editDraft: String,
+ _messageText: {
+ type: String,
+ value: '',
+ observer: '_messageTextChanged',
+ },
},
- ready: function() {
- this._editDraft = (this.comment && this.comment.message) || '';
- this.editing = this._editDraft.length == 0;
- },
+ observers: [
+ '_commentMessageChanged(comment.message)',
+ '_loadLocalDraft(changeNum, patchNum, comment)',
+ ],
save: function() {
- this.comment.message = this._editDraft;
+ this.comment.message = this._messageText;
this.disabled = true;
+
+ this.$.storage.eraseDraftComment({
+ changeNum: this.changeNum,
+ patchNum: this.patchNum,
+ path: this.comment.path,
+ line: this.comment.line,
+ });
+
this._xhrPromise = this._saveDraft(this.comment).then(function(response) {
this.disabled = false;
if (!response.ok) { return response; }
@@ -86,6 +106,7 @@
}
this.comment = comment;
this.editing = false;
+ this.fire('comment-save');
return obj;
}.bind(this));
@@ -129,6 +150,33 @@
}
},
+ _commentMessageChanged: function(message) {
+ this._messageText = message || '';
+ },
+
+ _messageTextChanged: function(newValue, oldValue) {
+ if (!this.comment || (this.comment && this.comment.id)) { return; }
+
+ this.debounce('store', function() {
+ var message = this._messageText;
+
+ var commentLocation = {
+ changeNum: this.changeNum,
+ patchNum: this.patchNum,
+ path: this.comment.path,
+ line: this.comment.line,
+ };
+
+ if ((!this._messageText || !this._messageText.length) && oldValue) {
+ // If the draft has been modified to be empty, then erase the storage
+ // entry.
+ this.$.storage.eraseDraftComment(commentLocation);
+ } else {
+ this.$.storage.setDraftComment(commentLocation, message);
+ }
+ }, STORAGE_DEBOUNCE_INTERVAL);
+ },
+
_handleLinkTap: function(e) {
e.preventDefault();
var hash = this._computeLinkToComment(this.comment);
@@ -158,7 +206,7 @@
_handleEdit: function(e) {
this._preventDefaultAndBlur(e);
- this._editDraft = this.comment.message;
+ this._messageText = this.comment.message;
this.editing = true;
},
@@ -173,7 +221,7 @@
this.fire('comment-discard');
return;
}
- this._editDraft = this.comment.message;
+ this._messageText = this.comment.message;
this.editing = false;
},
@@ -182,8 +230,10 @@
if (!this.comment.__draft) {
throw Error('Cannot discard a non-draft comment.');
}
+ this.editing = false;
this.disabled = true;
if (!this.comment.id) {
+ this.disabled = false;
this.fire('comment-discard');
return;
}
@@ -213,5 +263,24 @@
return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
draft);
},
+
+ _loadLocalDraft: function(changeNum, patchNum, comment) {
+ // Only apply local drafts to comments that haven't been saved
+ // remotely, and haven't been given a default message already.
+ if (!comment || comment.id || comment.message) {
+ return;
+ }
+
+ var draft = this.$.storage.getDraftComment({
+ changeNum: changeNum,
+ patchNum: patchNum,
+ path: comment.path,
+ line: comment.line,
+ });
+
+ if (draft) {
+ this.set('comment.message', draft.message);
+ }
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index a333e14..8ef222e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -183,11 +183,11 @@
MockInteractions.tap(element.$$('.edit'));
assert.isTrue(element.editing);
- element._editDraft = '';
+ element._messageText = '';
// Save should be disabled on an empty message.
var disabled = element.$$('.save').hasAttribute('disabled');
assert.isTrue(disabled, 'save button should be disabled.');
- element._editDraft = ' ';
+ element._messageText = ' ';
disabled = element.$$('.save').hasAttribute('disabled');
assert.isTrue(disabled, 'save button should be disabled.');
@@ -206,7 +206,7 @@
test('draft saving/editing', function(done) {
element.draft = true;
MockInteractions.tap(element.$$('.edit'));
- element._editDraft = 'good news, everyone!';
+ element._messageText = 'good news, everyone!';
MockInteractions.tap(element.$$('.save'));
assert.isTrue(element.disabled,
'Element should be disabled when creating draft.');
@@ -218,7 +218,7 @@
assert.isFalse(element.editing);
}).then(function() {
MockInteractions.tap(element.$$('.edit'));
- element._editDraft = 'You’ll be delivering a package to Chapek 9, a ' +
+ element._messageText = 'You’ll be delivering a package to Chapek 9, a ' +
'world where humans are killed on sight.';
MockInteractions.tap(element.$$('.save'));
assert.isTrue(element.disabled,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
new file mode 100644
index 0000000..5a41709
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -0,0 +1,30 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
+
+<dom-module id="gr-diff-cursor">
+ <template>
+ <gr-cursor-manager
+ id="cursorManager"
+ scroll="keep-visible"
+ cursor-target-class="target-row"
+ fold-offset-top="[[foldOffsetTop]]"
+ target="{{diffRow}}"></gr-cursor-manager>
+ </template>
+ <script src="gr-diff-cursor.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
new file mode 100644
index 0000000..3f71892
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -0,0 +1,288 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ var DiffSides = {
+ LEFT: 'left',
+ RIGHT: 'right',
+ };
+
+ var DiffViewMode = {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ };
+
+ var LEFT_SIDE_CLASS = 'target-side-left';
+ var RIGHT_SIDE_CLASS = 'target-side-right';
+
+ Polymer({
+ is: 'gr-diff-cursor',
+
+ properties: {
+ /**
+ * Either DiffSides.LEFT or DiffSides.RIGHT.
+ */
+ side: {
+ type: String,
+ value: DiffSides.RIGHT,
+ },
+ diffRow: {
+ type: Object,
+ notify: true,
+ observer: '_rowChanged',
+ },
+
+ /**
+ * The diff views to cursor through and listen to.
+ */
+ diffs: {
+ type: Array,
+ value: function() {
+ return [];
+ },
+ },
+
+ foldOffsetTop: {
+ type: Number,
+ value: 0,
+ },
+ },
+
+ observers: [
+ '_updateSideClass(side)',
+ '_diffsChanged(diffs.splices)',
+ ],
+
+ moveLeft: function() {
+ this.side = DiffSides.LEFT;
+ if (this._isTargetBlank()) {
+ this.moveUp()
+ }
+ },
+
+ moveRight: function() {
+ this.side = DiffSides.RIGHT;
+ if (this._isTargetBlank()) {
+ this.moveUp()
+ }
+ },
+
+ moveDown: function() {
+ if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+ this.$.cursorManager.next(this._rowHasSide.bind(this));
+ } else {
+ this.$.cursorManager.next();
+ }
+ },
+
+ moveUp: function() {
+ if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+ this.$.cursorManager.previous(this._rowHasSide.bind(this));
+ } else {
+ this.$.cursorManager.previous();
+ }
+ },
+
+ moveToNextChunk: function() {
+ this.$.cursorManager.next(this._isFirstRowOfChunk.bind(this));
+ this._fixSide();
+ },
+
+ moveToPreviousChunk: function() {
+ this.$.cursorManager.previous(this._isFirstRowOfChunk.bind(this));
+ this._fixSide();
+ },
+
+ moveToNextCommentThread: function() {
+ this.$.cursorManager.next(this._rowHasThread.bind(this));
+ this._fixSide();
+ },
+
+ moveToPreviousCommentThread: function() {
+ this.$.cursorManager.previous(this._rowHasThread.bind(this));
+ this._fixSide();
+ },
+
+ /**
+ * Get the line number element targeted by the cursor row and side.
+ * @return {DOMElement}
+ */
+ getTargetLineElement: function() {
+ var lineElSelector = '.lineNum';
+
+ if (!this.diffRow) {
+ return;
+ }
+
+ if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+ lineElSelector += this.side === DiffSides.LEFT ? '.left' : '.right';
+ }
+
+ return this.diffRow.querySelector(lineElSelector);
+ },
+
+ getTargetDiffElement: function() {
+ // Find the parent diff element of the cursor row.
+ for (var diff = this.diffRow; diff; diff = diff.parentElement) {
+ if (diff.tagName === 'GR-DIFF') { return diff; }
+ }
+ return null;
+ },
+
+ moveToFirstChunk: function() {
+ this.$.cursorManager.moveToStart();
+ this.moveToNextChunk();
+ },
+
+ reInitCursor: function() {
+ this._updateStops();
+ this.moveToFirstChunk();
+ },
+
+ handleDiffUpdate: function() {
+ this._updateStops();
+
+ if (!this.diffRow) {
+ this.reInitCursor();
+ }
+ },
+
+ _getViewMode: function() {
+ if (!this.diffRow) {
+ return null;
+ }
+
+ if (this.diffRow.classList.contains('side-by-side')) {
+ return DiffViewMode.SIDE_BY_SIDE;
+ } else {
+ return DiffViewMode.UNIFIED;
+ }
+ },
+
+ _rowHasSide: function(row) {
+ var selector = '.content';
+ selector += this.side === DiffSides.LEFT ? '.left' : '.right';
+ return row.querySelector(selector);
+ },
+
+ _isFirstRowOfChunk: function(row) {
+ var parentClassList = row.parentNode.classList;
+ return parentClassList.contains('section') &&
+ parentClassList.contains('delta') &&
+ !row.previousSibling;
+ },
+
+ _rowHasThread: function(row) {
+ return row.querySelector('gr-diff-comment-thread');
+ },
+
+ /**
+ * If we jumped to a row where there is no content on the current side then
+ * switch to the alternate side.
+ */
+ _fixSide: function() {
+ if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE &&
+ this._isTargetBlank()) {
+ this.side = this.side === DiffSides.LEFT ?
+ DiffSides.RIGHT : DiffSides.LEFT;
+ }
+ },
+
+ _isTargetBlank: function() {
+ if (!this.diffRow) {
+ return false;
+ }
+
+ var actions = this._getActionsForRow();
+ return (this.side === DiffSides.LEFT && !actions.left) ||
+ (this.side === DiffSides.RIGHT && !actions.right);
+ },
+
+ _rowChanged: function(newRow, oldRow) {
+ if (oldRow) {
+ oldRow.classList.remove(LEFT_SIDE_CLASS, RIGHT_SIDE_CLASS);
+ }
+ this._updateSideClass();
+ },
+
+ _updateSideClass: function() {
+ if (!this.diffRow) {
+ return;
+ }
+ this.toggleClass(LEFT_SIDE_CLASS, this.side === DiffSides.LEFT,
+ this.diffRow);
+ this.toggleClass(RIGHT_SIDE_CLASS, this.side === DiffSides.RIGHT,
+ this.diffRow);
+ },
+
+ _isActionType: function(type) {
+ return type !== 'blank' && type !== 'contextControl';
+ },
+
+ _getActionsForRow: function() {
+ var actions = {left: false, right: false};
+ if (this.diffRow) {
+ actions.left = this._isActionType(
+ this.diffRow.getAttribute('left-type'));
+ actions.right = this._isActionType(
+ this.diffRow.getAttribute('right-type'));
+ }
+ return actions;
+ },
+
+ _getStops: function() {
+ return this.diffs.reduce(
+ function(stops, diff) {
+ return stops.concat(diff.getCursorStops());
+ }, []);
+ },
+
+ _updateStops: function() {
+ this.$.cursorManager.stops = this._getStops();
+ },
+
+ /**
+ * Setup and tear down on-render listeners for any diffs that are added or
+ * removed from the cursor.
+ * @private
+ */
+ _diffsChanged: function(changeRecord) {
+ if (!changeRecord) { return; }
+
+ this._updateStops();
+
+ var splice;
+ var i;
+ for (var spliceIdx = 0;
+ changeRecord.indexSplices &&
+ spliceIdx < changeRecord.indexSplices.length;
+ spliceIdx++) {
+ splice = changeRecord.indexSplices[spliceIdx];
+
+ for (i = splice.index;
+ i < splice.index + splice.addedCount;
+ i++) {
+ this.listen(this.diffs[i], 'render', 'handleDiffUpdate');
+ }
+
+ for (i = 0;
+ i < splice.removed && splicee.removed.length;
+ i++) {
+ this.unlisten(splice.removed[i], 'render', 'handleDiffUpdate');
+ }
+ }
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
new file mode 100644
index 0000000..f3c9f95
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-diff-cursor</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.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="../gr-diff/gr-diff.html">
+<link rel="import" href="./gr-diff-cursor.html">
+<link rel="import" href="./mock-diff-response_test.html">
+
+<test-fixture id="basic">
+ <template>
+ <mock-diff-response></mock-diff-response>
+ <gr-diff></gr-diff>
+ <gr-diff-cursor></gr-diff-cursor>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-diff-cursor tests', function() {
+ var cursorElement;
+ var diffElement;
+ var mockDiffResponse;
+
+ setup(function(done) {
+ stub('gr-rest-api-interface', {
+ getLoggedIn: function() { return Promise.resolve(false); },
+ });
+
+ var fixtureElems = fixture('basic');
+ mockDiffResponse = fixtureElems[0];
+ diffElement = fixtureElems[1];
+ cursorElement = fixtureElems[2];
+
+ // Register the diff with the cursor.
+ cursorElement.push('diffs', diffElement);
+
+ diffElement.$.restAPI.getDiffPreferences().then(function(prefs) {
+ diffElement.prefs = prefs;
+ });
+
+ sinon.stub(diffElement, '_getDiff', function() {
+ return Promise.resolve(mockDiffResponse.diffResponse);
+ });
+
+ sinon.stub(diffElement, '_getDiffComments', function() {
+ return Promise.resolve({baseComments: [], comments: []});
+ });
+
+ sinon.stub(diffElement, '_getDiffDrafts', function() {
+ return Promise.resolve({baseComments: [], comments: []});
+ });
+
+ var setupDone = function() {
+ cursorElement.moveToFirstChunk();
+ done();
+ diffElement.removeEventListener('render', setupDone);
+ };
+ diffElement.addEventListener('render', setupDone);
+
+ diffElement.reload();
+ });
+
+ test('diff cursor functionality (side-by-side)', function() {
+ // The cursor has been initialized to the first delta.
+ assert.isOk(cursorElement.diffRow);
+
+ var firstDeltaRow = diffElement.$$('.section.delta .diff-row');
+ assert.equal(cursorElement.diffRow, firstDeltaRow);
+
+ cursorElement.moveDown()
+
+ assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+ assert.equal(cursorElement.diffRow, firstDeltaRow.nextSibling);
+
+ cursorElement.moveUp();
+
+ assert.notEqual(cursorElement.diffRow, firstDeltaRow.nextSibling);
+ assert.equal(cursorElement.diffRow, firstDeltaRow);
+ });
+
+ test('diff cursor functionality (unified)', function() {
+ diffElement.viewMode = 'UNIFIED_DIFF';
+ cursorElement.reInitCursor();
+
+ // The cursor has been initialized to the first delta.
+ assert.isOk(cursorElement.diffRow);
+
+ var firstDeltaRow = diffElement.$$('.section.delta .diff-row');
+ assert.equal(cursorElement.diffRow, firstDeltaRow);
+
+ firstDeltaRow = diffElement.$$('.section.delta .diff-row');
+ assert.equal(cursorElement.diffRow, firstDeltaRow);
+
+ cursorElement.moveDown();
+
+ assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+ assert.equal(cursorElement.diffRow, firstDeltaRow.nextSibling);
+
+ cursorElement.moveUp();
+
+ assert.notEqual(cursorElement.diffRow, firstDeltaRow.nextSibling);
+ assert.equal(cursorElement.diffRow, firstDeltaRow);
+ });
+
+ test('cursor side functionality', function() {
+ // The side only applies to side-by-side mode, which should be the default
+ // mode.
+ assert.equal(diffElement.viewMode, 'SIDE_BY_SIDE');
+
+ var firstDeltaSection = diffElement.$$('.section.delta');
+ var firstDeltaRow = firstDeltaSection.querySelector('.diff-row');
+
+ // Because the first delta in this diff is on the right, it should be set
+ // to the right side.
+ assert.equal(cursorElement.side, 'right');
+ assert.equal(cursorElement.diffRow, firstDeltaRow);
+ var firstIndex = cursorElement.$.cursorManager.index;
+
+ // Move the side to the left. Because this delta only has a right side, we
+ // should be moved up to the previous line where there is content on the
+ // right. The previous row is part of the previous section.
+ cursorElement.moveLeft();
+
+ assert.equal(cursorElement.side, 'left');
+ assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+ assert.equal(cursorElement.$.cursorManager.index, firstIndex - 1);
+ assert.equal(cursorElement.diffRow.parentElement,
+ firstDeltaSection.previousSibling);
+
+ // If we move down, we should skip everything in the first delta because
+ // we are on the left side and the first delta has no content on the left.
+ cursorElement.moveDown();
+
+ assert.equal(cursorElement.side, 'left');
+ assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+ assert.isTrue(cursorElement.$.cursorManager.index > firstIndex);
+ assert.equal(cursorElement.diffRow.parentElement,
+ firstDeltaSection.nextSibling);
+ });
+
+ test('chunk skip functionality', function() {
+ var chunks = Polymer.dom(diffElement.root).querySelectorAll(
+ '.section.delta');
+ var indexOfChunk = function(chunk) {
+ return Array.prototype.indexOf.call(chunks, chunk);
+ };
+
+ // We should be initialized to the first chunk. Since this chunk only has
+ // content on the right side, our side should be right.
+ var currentIndex = indexOfChunk(cursorElement.diffRow.parentElement);
+ assert.equal(currentIndex, 0);
+ assert.equal(cursorElement.side, 'right');
+
+ // Move to the next chunk.
+ cursorElement.moveToNextChunk();
+
+ // Since this chunk only has content on the left side. we should have been
+ // automatically mvoed over.
+ var previousIndex = currentIndex;
+ currentIndex = indexOfChunk(cursorElement.diffRow.parentElement);
+ assert.equal(currentIndex, previousIndex + 1);
+ assert.equal(cursorElement.side, 'left');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/mock-diff-response_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/mock-diff-response_test.html
new file mode 100644
index 0000000..ee4bd51
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/mock-diff-response_test.html
@@ -0,0 +1,163 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<dom-module id="mock-diff-response">
+ <template></template>
+ <script>
+ (function() {
+ 'use strict';
+
+ var RESPONSE = {
+ "meta_a": {
+ "name": "lorem-ipsum.txt",
+ "content_type": "text/plain",
+ "lines": 45,
+ },
+ "meta_b": {
+ "name": "lorem-ipsum.txt",
+ "content_type": "text/plain",
+ "lines": 48,
+ },
+ "intraline_status": "OK",
+ "change_type": "MODIFIED",
+ "diff_header": [
+ "diff --git a/lorem-ipsum.txt b/lorem-ipsum.txt",
+ "index b2adcf4..554ae49 100644",
+ "--- a/lorem-ipsum.txt",
+ "+++ b/lorem-ipsum.txt",
+ ],
+ "content": [
+ {
+ "ab": [
+ "Lorem ipsum dolor sit amet, suspendisse inceptos vehicula, nulla phasellus.",
+ "Mattis lectus.",
+ "Sodales duis.",
+ "Orci a faucibus.",
+ ]
+ },
+ {
+ "b": [
+ "Nullam neque, ligula ac, id blandit.",
+ "Sagittis tincidunt torquent, tempor nunc amet.",
+ "At rhoncus id.",
+ ],
+ },
+ {
+ "ab": [
+ "Sem nascetur, erat ut, non in.",
+ "A donec, venenatis pellentesque dis.",
+ "Mauris mauris.",
+ "Quisque nisl duis, facilisis viverra.",
+ "Justo purus, semper eget et.",
+ ],
+ },
+ { "a": [
+ "Est amet, vestibulum pellentesque.",
+ "Erat ligula.",
+ "Justo eros.",
+ "Fringilla quisque.",
+ ],
+ },
+ {
+ "ab": [
+ "Arcu eget, rhoncus amet cursus, ipsum elementum.",
+ "Eros suspendisse.",
+ ],
+ },
+ {
+ "a": [
+ "Rhoncus tempor, ultricies aliquam ipsum.",
+ ],
+ "b": [
+ "Rhoncus tempor, ultricies praesent ipsum.",
+ ],
+ "edit_a": [
+ [
+ 26,
+ 7,
+ ],
+ ],
+ "edit_b": [
+ [
+ 26,
+ 8,
+ ],
+ ],
+ },
+ {
+ "ab": [
+ "Sollicitudin duis.",
+ "Blandit blandit, ante nisl fusce.",
+ "Felis ac at, tellus consectetuer.",
+ "Sociis ligula sapien, egestas leo.",
+ "Cum pulvinar, sed mauris, cursus neque velit.",
+ "Augue porta lobortis.",
+ "Nibh lorem, amet fermentum turpis, vel pulvinar diam.",
+ "Id quam ipsum, id urna et, massa suspendisse.",
+ "Ac nec, nibh praesent.",
+ "Rutrum vestibulum.",
+ "Est tellus, bibendum habitasse.",
+ "Justo facilisis, vel nulla.",
+ "Donec eu, vulputate neque aliquam, nulla dui.",
+ "Risus adipiscing in.",
+ "Lacus arcu arcu.",
+ "Urna velit.",
+ "Urna a dolor.",
+ "Lectus magna augue, convallis mattis tortor, sed tellus consequat.",
+ "Etiam dui, blandit wisi.",
+ "Mi nec.",
+ "Vitae eget vestibulum.",
+ "Ullamcorper nunc ante, nec imperdiet felis, consectetur in.",
+ "Ac eget.",
+ "Vel fringilla, interdum pellentesque placerat, proin ante.",
+ ],
+ },
+ {
+ "b": [
+ "Eu congue risus.",
+ "Enim ac, quis elementum.",
+ "Non et elit.",
+ "Etiam aliquam, diam vel nunc.",
+ ],
+ },
+ {
+ "ab": [
+ "Nec at.",
+ "Arcu mauris, venenatis lacus fermentum, praesent duis.",
+ "Pellentesque amet et, tellus duis.",
+ "Ipsum arcu vitae, justo elit, sed libero tellus.",
+ "Metus rutrum euismod, vivamus sodales, vel arcu nisl.",
+ ],
+ },
+ ],
+ };
+
+ Polymer({
+ is: 'mock-diff-response',
+ properties: {
+ diffResponse: {
+ type: Object,
+ value: function() {
+ return RESPONSE;
+ },
+ },
+ },
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index c52289f..0e99a15 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -21,6 +21,7 @@
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-diff/gr-diff.html">
+<link rel="import" href="../gr-diff-cursor/gr-diff-cursor.html">
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
@@ -96,6 +97,7 @@
color: #666;
}
.header {
+ align-items: center;
display: flex;
justify-content: space-between;
margin: 0 var(--default-horizontal-margin) .75em;
@@ -201,6 +203,7 @@
</gr-diff>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-diff-cursor id="cursor"></gr-diff-cursor>
</template>
<script src="gr-diff-view.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 26c1030..c75cb72 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -99,6 +99,8 @@
this.fire('title-change',
{title: this._computeFileDisplayName(this._path)});
}
+
+ this.$.cursor.push('diffs', this.$.diff);
},
detached: function() {
@@ -162,6 +164,35 @@
if (this.shouldSupressKeyboardShortcut(e)) { return; }
switch (e.keyCode) {
+ case 37: // left
+ if (e.shiftKey) {
+ e.preventDefault();
+ this.$.cursor.moveLeft();
+ }
+ break;
+ case 39: // right
+ if (e.shiftKey) {
+ e.preventDefault();
+ this.$.cursor.moveRight();
+ }
+ break;
+ case 40: // down
+ case 74: // 'j'
+ e.preventDefault();
+ this.$.cursor.moveDown();
+ break;
+ case 38: // up
+ case 75: // 'k'
+ e.preventDefault();
+ this.$.cursor.moveUp();
+ break;
+ case 67: // 'c'
+ e.preventDefault();
+ var line = this.$.cursor.getTargetLineElement();
+ if (line) {
+ this.$.diff.addDraftAtLine(line);
+ }
+ break;
case 219: // '['
e.preventDefault();
this._navToFile(this._fileList, -1);
@@ -173,17 +204,17 @@
case 78: // 'n'
e.preventDefault();
if (e.shiftKey) {
- this.$.diff.scrollToNextCommentThread();
+ this.$.cursor.moveToNextCommentThread();
} else {
- this.$.diff.scrollToNextDiffChunk();
+ this.$.cursor.moveToNextChunk();
}
break;
case 80: // 'p'
e.preventDefault();
if (e.shiftKey) {
- this.$.diff.scrollToPreviousCommentThread();
+ this.$.cursor.moveToPreviousCommentThread();
} else {
- this.$.diff.scrollToPreviousDiffChunk();
+ this.$.cursor.moveToPreviousChunk();
}
break;
case 65: // 'a'
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index ce7cdd6..5fb3126 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -94,22 +94,22 @@
MockInteractions.pressAndReleaseKeyOn(element, 188); // ','
assert(showPrefsStub.calledOnce);
- var scrollStub = sinon.stub(element.$.diff, 'scrollToNextDiffChunk');
+ var scrollStub = sinon.stub(element.$.cursor, 'moveToNextChunk');
MockInteractions.pressAndReleaseKeyOn(element, 78); // 'n'
assert(scrollStub.calledOnce);
scrollStub.restore();
- scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousDiffChunk');
+ scrollStub = sinon.stub(element.$.cursor, 'moveToPreviousChunk');
MockInteractions.pressAndReleaseKeyOn(element, 80); // 'p'
assert(scrollStub.calledOnce);
scrollStub.restore();
- scrollStub = sinon.stub(element.$.diff, 'scrollToNextCommentThread');
+ scrollStub = sinon.stub(element.$.cursor, 'moveToNextCommentThread');
MockInteractions.pressAndReleaseKeyOn(element, 78, ['shift']); // 'N'
assert(scrollStub.calledOnce);
scrollStub.restore();
- scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousCommentThread');
+ scrollStub = sinon.stub(element.$.cursor, 'moveToPreviousCommentThread');
MockInteractions.pressAndReleaseKeyOn(element, 80, ['shift']); // 'P'
assert(scrollStub.calledOnce);
scrollStub.restore();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js
index 061ed4f..bf93112 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js
@@ -35,6 +35,10 @@
GrDiffBuilderSideBySide.prototype._createRow = function(section, leftLine,
rightLine) {
var row = this._createElement('tr');
+ row.classList.add('diff-row', 'side-by-side');
+ row.setAttribute('left-type', leftLine.type);
+ row.setAttribute('right-type', rightLine.type);
+
this._appendPair(section, row, leftLine, leftLine.beforeNumber,
GrDiffBuilder.Side.LEFT);
this._appendPair(section, row, rightLine, rightLine.afterNumber,
@@ -44,7 +48,7 @@
GrDiffBuilderSideBySide.prototype._appendPair = function(section, row, line,
lineNumber, side) {
- row.appendChild(this._createLineEl(line, lineNumber, line.type));
+ row.appendChild(this._createLineEl(line, lineNumber, line.type, side));
var action = this._createContextControl(section, line);
if (action) {
row.appendChild(action);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js
index d9517d3..86340bd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js
@@ -23,6 +23,7 @@
GrDiffBuilderUnified.prototype.emitGroup = function(group,
opt_beforeSection) {
var sectionEl = this._createElement('tbody', 'section');
+ sectionEl.classList.add(group.type);
for (var i = 0; i < group.lines.length; ++i) {
sectionEl.appendChild(this._createRow(sectionEl, group.lines[i]));
@@ -36,6 +37,7 @@
GrDiffLine.Type.REMOVE));
row.appendChild(this._createLineEl(line, line.afterNumber,
GrDiffLine.Type.ADD));
+ row.classList.add('diff-row', 'unified');
var action = this._createContextControl(section, line);
if (action) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js
index ce4515c..c2197eb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js
@@ -401,8 +401,12 @@
return threadEl;
};
- GrDiffBuilder.prototype._createLineEl = function(line, number, type) {
+ GrDiffBuilder.prototype._createLineEl = function(line, number, type,
+ opt_class) {
var td = this._createElement('td');
+ if (opt_class) {
+ td.classList.add(opt_class);
+ }
if (line.type === GrDiffLine.Type.BLANK) {
return td;
} else if (line.type === GrDiffLine.Type.CONTEXT_CONTROL) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 7b1ce00..e4603b8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -43,6 +43,16 @@
.section {
background-color: #eee;
}
+ .diff-row.target-row.target-side-left .lineNum.left,
+ .diff-row.target-row.target-side-right .lineNum.right,
+ .diff-row.target-row.unified .lineNum {
+ background-color: #BBDEFB;
+ }
+ .diff-row.target-row.target-side-left .lineNum.left:before,
+ .diff-row.target-row.target-side-right .lineNum.right:before,
+ .diff-row.target-row.unified .lineNum:before {
+ color: #000;
+ }
.blank,
.content {
background-color: #fff;
@@ -64,9 +74,6 @@
.canComment .lineNum[data-value] {
cursor: pointer;
}
- .canComment .lineNum[data-value]:before {
- text-decoration: underline;
- }
.canComment .lineNum[data-value]:hover:before {
background-color: #ccc;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 447d307..660b86f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -58,14 +58,6 @@
observer: '_selectionSideChanged',
},
_comments: Object,
- _focusedSection: {
- type: Number,
- value: -1,
- },
- _focusedThread: {
- type: Number,
- value: -1,
- },
},
observers: [
@@ -114,24 +106,29 @@
this._scrollToElement(el);
},
- scrollToNextDiffChunk: function() {
- this._focusedSection = this._advanceElementWithinNodeList(
- this._getDeltaSections(), this._focusedSection, 1);
+ getCursorStops: function() {
+ if (this.hidden) {
+ return [];
+ }
+
+ return Polymer.dom(this.root).querySelectorAll('.diff-row');
},
- scrollToPreviousDiffChunk: function() {
- this._focusedSection = this._advanceElementWithinNodeList(
- this._getDeltaSections(), this._focusedSection, -1);
- },
+ addDraftAtLine: function(el) {
+ this._getLoggedIn().then(function(loggedIn) {
+ if (!loggedIn) { return; }
- scrollToNextCommentThread: function() {
- this._focusedThread = this._advanceElementWithinNodeList(
- this._getCommentThreads(), this._focusedThread, 1);
- },
-
- scrollToPreviousCommentThread: function() {
- this._focusedThread = this._advanceElementWithinNodeList(
- this._getCommentThreads(), this._focusedThread, -1);
+ var value = el.getAttribute('data-value');
+ if (value === GrDiffLine.FILE) {
+ this._addDraft(el);
+ return;
+ }
+ var lineNum = parseInt(value, 10);
+ if (isNaN(lineNum)) {
+ throw Error('Invalid line number: ' + value);
+ }
+ this._addDraft(el, lineNum);
+ }.bind(this));
},
_advanceElementWithinNodeList: function(els, curIndex, direction) {
@@ -147,10 +144,6 @@
return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
},
- _getDeltaSections: function() {
- return Polymer.dom(this.root).querySelectorAll('.section.delta');
- },
-
_scrollToElement: function(el) {
if (!el) { return; }
@@ -194,27 +187,10 @@
if (el.classList.contains('showContext')) {
this._showContext(e.detail.group, e.detail.section);
} else if (el.classList.contains('lineNum')) {
- this._handleLineTap(el);
+ this.addDraftAtLine(el);
}
},
- _handleLineTap: function(el) {
- this._getLoggedIn().then(function(loggedIn) {
- if (!loggedIn) { return; }
-
- var value = el.getAttribute('data-value');
- if (value === GrDiffLine.FILE) {
- this._addDraft(el);
- return;
- }
- var lineNum = parseInt(value, 10);
- if (isNaN(lineNum)) {
- throw Error('Invalid line number: ' + value);
- }
- this._addDraft(el, lineNum);
- }.bind(this));
- },
-
_addDraft: function(lineEl, opt_lineNum) {
var threadEl;
@@ -338,6 +314,10 @@
_showContext: function(group, sectionEl) {
this._builder.emitGroup(group, sectionEl);
sectionEl.parentNode.removeChild(sectionEl);
+
+ this.async(function() {
+ this.fire('render', null, {bubbles: false});
+ }.bind(this), 1);
},
_prefsChanged: function(prefsChangeRecord) {
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 684120e..affcd44 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -17,6 +17,12 @@
Polymer({
is: 'gr-app',
+ /**
+ * Fired when the URL location changes.
+ *
+ * @event location-change
+ */
+
properties: {
params: Object,
keyEventTarget: {
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index fe1605c..e1990c4 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -14,6 +14,12 @@
(function() {
'use strict';
+ var ScrollBehavior = {
+ ALWAYS: 'always',
+ NEVER: 'never',
+ KEEP_VISIBLE: 'keep-visible',
+ };
+
Polymer({
is: 'gr-cursor-manager',
@@ -46,9 +52,24 @@
type: String,
value: null,
},
+
+ /**
+ * The scroll behavior for the cursor. Values are 'never', 'always' and
+ * 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
+ * the viewport.
+ */
scroll: {
- type: Boolean,
- value: false,
+ type: String,
+ value: ScrollBehavior.NEVER,
+ },
+
+ /**
+ * When using the 'keep-visible' scroll behavior, set an offset to the top
+ * of the window for what is considered above the upper fold.
+ */
+ foldOffsetTop: {
+ type: Number,
+ value: 0,
},
},
@@ -152,13 +173,17 @@
var newIndex = this.index;
do {
newIndex = newIndex + delta;
- } while(newIndex !== 0 &&
- newIndex !== this.stops.length - 1 &&
- opt_condition &&
- !opt_condition(this.stops[newIndex]));
+ } while(newIndex > 0 &&
+ newIndex < this.stops.length - 1 &&
+ opt_condition && !opt_condition(this.stops[newIndex]));
newIndex = Math.max(0, Math.min(this.stops.length - 1, newIndex));
+ // If we failed to satisfy the condition:
+ if (opt_condition && !opt_condition(this.stops[newIndex])) {
+ return this.index;
+ }
+
return newIndex;
},
@@ -177,7 +202,7 @@
},
_scrollToTarget: function() {
- if (!this.target || !this.scroll) { return; }
+ if (!this.target || this.scroll === ScrollBehavior.NEVER) { return; }
// Calculate where the element is relative to the window.
var top = this.target.offsetTop;
@@ -187,6 +212,10 @@
top += offsetParent.offsetTop;
}
+ if (this.scroll === ScrollBehavior.KEEP_VISIBLE &&
+ top > window.pageYOffset + this.foldOffsetTop &&
+ top < window.pageYOffset + window.innerHeight) { return; }
+
// Scroll the element to the middle of the window. Dividing by a third
// instead of half the inner height feels a bit better otherwise the
// element appears to be below the center of the window even when it
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 3795aa8..e681bf9 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -589,6 +589,7 @@
function onlyParent(c) { return c.side == PARENT_PATCH_NUM; }
function withoutParent(c) { return c.side != PARENT_PATCH_NUM; }
+ function setPath(c) { c.path = opt_path; }
var promises = [];
var comments;
@@ -599,8 +600,11 @@
comments = response[opt_path] || [];
if (opt_basePatchNum == PARENT_PATCH_NUM) {
baseComments = comments.filter(onlyParent);
+ baseComments.forEach(setPath);
}
comments = comments.filter(withoutParent);
+
+ comments.forEach(setPath);
}.bind(this)));
if (opt_basePatchNum != PARENT_PATCH_NUM) {
@@ -608,6 +612,7 @@
opt_basePatchNum);
promises.push(this.fetchJSON(baseURL).then(function(response) {
baseComments = (response[opt_path] || []).filter(withoutParent);
+ baseComments.forEach(setPath);
}));
}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index e7f7a21..ef0741a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -140,10 +140,12 @@
assert.deepEqual(obj.baseComments[0], {
side: 'PARENT',
message: 'how did this work in the first place?',
+ path: 'sieve.go',
});
assert.equal(obj.comments.length, 1);
assert.deepEqual(obj.comments[0], {
message: 'this isn’t quite right',
+ path: 'sieve.go',
});
fetchJSONStub.restore();
done();
@@ -188,13 +190,16 @@
assert.equal(obj.baseComments.length, 1);
assert.deepEqual(obj.baseComments[0], {
message: 'this isn’t quite right',
+ path: 'sieve.go',
});
assert.equal(obj.comments.length, 2);
assert.deepEqual(obj.comments[0], {
message: 'What on earth are you thinking, here?',
+ path: 'sieve.go',
});
assert.deepEqual(obj.comments[1], {
message: '¯\\_(ツ)_/¯',
+ path: 'sieve.go',
});
fetchJSONStub.restore();
done();
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
new file mode 100644
index 0000000..74bfcdf
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
@@ -0,0 +1,19 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<dom-module id="gr-storage">
+ <script src="gr-storage.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
new file mode 100644
index 0000000..ef3a6c0
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -0,0 +1,85 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ // Date cutoff is one day:
+ var DRAFT_MAX_AGE = 24*60*60*1000;
+
+ // Clean up old entries no more frequently than one day.
+ var CLEANUP_THROTTLE_INTERVAL = 24*60*60*1000;
+
+ Polymer({
+ is: 'gr-storage',
+
+ properties: {
+ _lastCleanup: Number,
+ _storage: {
+ type: Object,
+ value: function() {
+ return window.localStorage;
+ },
+ },
+ },
+
+ getDraftComment: function(location) {
+ this._cleanupDrafts();
+ return this._getObject(this._getDraftKey(location));
+ },
+
+ setDraftComment: function(location, message) {
+ var key = this._getDraftKey(location);
+ this._setObject(key, {message: message, updated: Date.now()});
+ },
+
+ eraseDraftComment: function(location) {
+ var key = this._getDraftKey(location);
+ this._storage.removeItem(key);
+ },
+
+ _getDraftKey: function(location) {
+ return ['draft', location.changeNum, location.patchNum, location.path,
+ location.line].join(':');
+ },
+
+ _cleanupDrafts: function() {
+ // Throttle cleanup to the throttle interval.
+ if (this._lastCleanup &&
+ Date.now() - this._lastCleanup < CLEANUP_THROTTLE_INTERVAL) {
+ return;
+ }
+ this._lastCleanup = Date.now();
+
+ var draft;
+ for (var key in this._storage) {
+ if (key.indexOf('draft:') === 0) {
+ draft = this._getObject(key);
+ if (Date.now() - draft.updated > DRAFT_MAX_AGE) {
+ this._storage.removeItem(key);
+ }
+ }
+ }
+ },
+
+ _getObject: function(key) {
+ var serial = this._storage.getItem(key);
+ if (!serial) { return null; }
+ return JSON.parse(serial);
+ },
+
+ _setObject: function(key, obj) {
+ this._storage.setItem(key, JSON.stringify(obj));
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
new file mode 100644
index 0000000..4e17cb6
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-storage</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="gr-storage.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-storage></gr-storage>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-storage tests', function() {
+ var element;
+ var storage;
+
+ setup(function() {
+ element = fixture('basic');
+ storage = element._storage;
+ cleanupStorage();
+ });
+
+ function cleanupStorage() {
+ // Make sure there are no entries in storage.
+ for (var key in window.localStorage) {
+ window.localStorage.removeItem(key);
+ }
+ }
+
+ test('storing, retrieving and erasing drafts', function() {
+ var changeNum = 1234;
+ var patchNum = 5;
+ var path = 'my_source_file.js';
+ var line = 123;
+ var location = {
+ changeNum: changeNum,
+ patchNum: patchNum,
+ path: path,
+ line: line,
+ };
+
+ // The key is in the expected format.
+ var key = element._getDraftKey(location);
+ assert.equal(key, ['draft', changeNum, patchNum, path, line].join(':'));
+
+ // There should be no draft initially.
+ var draft = element.getDraftComment(location);
+ assert.isNotOk(draft);
+
+ // Setting the draft stores it under the expected key.
+ element.setDraftComment(location, 'my comment');
+ assert.isOk(storage.getItem(key));
+ assert.equal(JSON.parse(storage.getItem(key)).message, 'my comment');
+ assert.isOk(JSON.parse(storage.getItem(key)).updated);
+
+ // Erasing the draft removes the key.
+ element.eraseDraftComment(location);
+ assert.isNotOk(storage.getItem(key));
+
+ cleanupStorage();
+ });
+
+ test('automatically removes old drafts', function() {
+ var changeNum = 1234;
+ var patchNum = 5;
+ var path = 'my_source_file.js';
+ var line = 123;
+ var location = {
+ changeNum: changeNum,
+ patchNum: patchNum,
+ path: path,
+ line: line,
+ };
+ var key = element._getDraftKey(location);
+
+ // Make sure that the call to cleanup doesn't get throttled.
+ element._lastCleanup = 0;
+
+ var cleanupSpy = sinon.spy(element, '_cleanupDrafts');
+
+ // Create a message with a timestamp that is a second behind the max age.
+ storage.setItem(key, JSON.stringify({
+ message: 'old message',
+ updated: Date.now() - 24*60*60*1000 - 1000,
+ }));
+
+ // Getting the draft should cause it to be removed.
+ var draft = element.getDraftComment(location);
+
+ assert.isTrue(cleanupSpy.called);
+ assert.isNotOk(draft);
+ assert.isNotOk(storage.getItem(key));
+
+ cleanupSpy.restore();
+ cleanupStorage();
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index da2072b..7c87037 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -46,6 +46,7 @@
'../elements/diff/gr-diff/gr-diff_test.html',
'../elements/diff/gr-diff-comment/gr-diff-comment_test.html',
'../elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
+ '../elements/diff/gr-diff-cursor/gr-diff-cursor_test.html',
'../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
'../elements/diff/gr-diff-view/gr-diff-view_test.html',
'../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
@@ -61,6 +62,7 @@
'../elements/shared/gr-js-api-interface/gr-js-api-interface_test.html',
'../elements/shared/gr-linked-text/gr-linked-text_test.html',
'../elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
+ '../elements/shared/gr-storage/gr-storage_test.html',
].forEach(function(file) {
testFiles.push(file);
testFiles.push(file + '?dom=shadow');