Merge changes from topic 'group-audit-log'
* changes:
Add new group screen that shows the audit log of the group
Add integration test for listing group audit events
Allow to get audit log of a group through GroupApi
Add new REST endpoint that provides the audit log of a group
diff --git a/.buckconfig b/.buckconfig
index 20954d7..7b75225 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -27,4 +27,4 @@
[cache]
mode = dir
- dir = ~/.gerritcodereview/buck-cache/cache
+ dir = ~/.gerritcodereview/buck-cache/locally-built-artifacts
diff --git a/Documentation/cmd-index-activate.txt b/Documentation/cmd-index-activate.txt
new file mode 100644
index 0000000..0135cfe
--- /dev/null
+++ b/Documentation/cmd-index-activate.txt
@@ -0,0 +1,32 @@
+= gerrit index activate
+
+== NAME
+gerrit index activate - Activate the latest index version available
+
+== SYNOPSIS
+--
+'ssh' -p @SSH_PORT@ @SSH_HOST@ 'gerrit index activate'
+--
+
+== DESCRIPTION
+Gerrit supports online index schema upgrades. When starting Gerrit for the first
+time after an upgrade that requires an index schema upgrade, the online indexer
+will be started. If the schema upgrade is a success, the new index will be
+activated and if it fails, a statement in the logs will be printed with the
+number of successfully/failed indexed changes.
+
+This command allows to activate the latest index even if there were some
+failures.
+
+== ACCESS
+Caller must be a member of the privileged 'Administrators' group.
+
+== SCRIPTING
+This command is intended to be used in scripts.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-index-start.txt b/Documentation/cmd-index-start.txt
new file mode 100644
index 0000000..4148b24
--- /dev/null
+++ b/Documentation/cmd-index-start.txt
@@ -0,0 +1,33 @@
+= gerrit index start
+
+== NAME
+gerrit index start - Start the online indexer
+
+== SYNOPSIS
+--
+'ssh' -p @SSH_PORT@ @SSH_HOST@ 'gerrit index start'
+--
+
+== DESCRIPTION
+Gerrit supports online index schema upgrades. When starting Gerrit for the first
+time after an upgrade that requires an index schema upgrade, the online indexer
+will be started. If the schema upgrade is a success, the new index will be
+activated and if it fails, a statement in the logs will be printed with the
+number of successfully/failed indexed changes.
+
+This command allows restarting the online indexer without having to restart
+Gerrit. This command will not start the indexer if it is already running or if
+the active index is the latest.
+
+== ACCESS
+Caller must be a member of the privileged 'Administrators' group.
+
+== SCRIPTING
+This command is intended to be used in scripts.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index f3bff7d..ef653cc 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -120,6 +120,12 @@
link:cmd-gsql.html[gerrit gsql]::
Administrative interface to active database.
+link:cmd-index-index.html[gerrit index activate]::
+ Activate the latest index version available.
+
+link:cmd-index-start.html[gerrit index start]::
+ Start the online indexer.
+
link:cmd-logging-ls-level.html[gerrit logging ls-level]::
List loggers and their logging level.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index ac33778..f95d9d1 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1525,6 +1525,31 @@
If `download.scheme` is not specified, SSH, HTTP and Anonymous HTTP
downloads are allowed.
+[[download.checkForHiddenChangeRefs]]download.checkForHiddenChangeRefs::
++
+Whether the download commands should be adapted when the change refs
+are hidden.
++
+Git has a configuration option to hide refs from the initial
+advertisement (`uploadpack.hideRefs`). This option can be used to hide
+the change refs from the client. As consequence fetching changes by
+change ref does not work anymore. However by setting
+`uploadpack.allowTipSha1InWant` to `true` fetching changes by commit ID
+is possible. If `download.checkForHiddenChangeRefs` is set to `true`
+the git download commands use the commit ID instead of the change ref
+when a project is configured like this.
++
+Example git configuration on a project:
++
+----
+[uploadpack]
+ hideRefs = refs/changes/
+ hideRefs = refs/cache-automerge/
+ allowTipSha1InWant = true
+----
++
+By default `false`.
+
[[download.archive]]download.archive::
+
Specifies which archive formats, if any, should be offered on the change
@@ -1670,6 +1695,18 @@
by the system administrator, and might not even be running on the
same host as Gerrit.
+[[gerrit.docUrl]]gerrit.docUrl::
++
+Optional base URL for documentation, under which one can find
+"index.html", "rest-api.html", etc. Used as the base for the fixed set
+of links in the "Documentation" tab. A slash is implicitly appended.
+(For finer control over the top menu, consider writing a
+link:dev-plugins.html#top-menu-extensions[plugin].)
++
+If unset or empty, the documentation tab will only be shown if
+`/Documentation/index.html` can be reached by the browser at app load
+time.
+
[[gerrit.installCommitMsgHookCommand]]gerrit.installCommitMsgHookCommand::
+
Optional command to install the `commit-msg` hook. Typically of the
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index 220b579..aedc0b4 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -118,9 +118,12 @@
these plugins.
The Gerrit Project doesn't provide binaries for these plugins, but
-there are some public services, like the
-link:https://ci.gerritforge.com/[CI Server from GerritForge], that
-offer the download of ready plugin jars.
+there are some public services that offer the download of pre-built
+plugin jars:
+
+* link:https://ci.gerritforge.com/[CI Server from GerritForge]
+* link:http://builds.quelltextlich.at/gerrit/nightly/index.html[
+ CI Server from Quelltextlich]
The following list gives an overview about available plugins, but the
list may not be complete. You may discover more plugins on
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index e307391..de3e6de 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -510,7 +510,7 @@
=== Cleaning The Buck Cache
The cache for the Gerrit Code Review project is located in
-`~/.gerritcodereview/buck-cache/cache`.
+`~/.gerritcodereview/buck-cache/locally-built-artifacts`.
The Buck cache should never need to be manually deleted. If you find yourself
deleting the Buck cache regularly, then it is likely that there is something
@@ -519,11 +519,12 @@
If you really do need to clean the cache manually, then:
----
- rm -rf ~/.gerritcodereview/buck-cache/cache
+ rm -rf ~/.gerritcodereview/buck-cache/locally-built-artifacts
----
-Note that the root `buck-cache` folder should not be deleted as this is where
-downloaded artifacts are stored.
+Note that the root `buck-cache` folder should not be deleted as it also contains
+the `downloaded-artifacts` directory, which holds the artifacts that got
+downloaded (not built locally).
[[buck-daemon]]
=== Using Buck daemon
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 6abfb9e..3157214 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -222,10 +222,6 @@
link:https://oss.sonatype.org/[Sonatype Nexus Server].
* Verify the staging repository
-+
-How to do this is described in the
-link:https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide#SonatypeOSSMavenRepositoryUsageGuide-8.a.1.ClosingaStagingRepository[
-Sonatype OSS Maven Repository Usage Guide].
** Go to the link:https://oss.sonatype.org/[Sonatype Nexus Server] and
sign in with your Sonatype credentials.
@@ -354,13 +350,10 @@
gerrit-documentation] storage bucket.
[[update-links]]
-==== Update Google Code project links
+==== Update homepage links
-* Go to http://code.google.com/p/gerrit/admin
-* Update the documentation link in the `Resources` section of the
-Description text, and in the `Links` section.
-* Add a link to the new release notes in the `News` section of the
-Description text
+Upload a change on the link:https://gerrit-review.googlesource.com/#/admin/projects/homepage[
+homepage project] to change the version numbers to the new version.
[[update-issues]]
==== Update the Issues
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 8ccd03b..32fa472 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -43,9 +43,6 @@
DRAFT;; Change is a draft change that only consists of draft patchsets.
- SUBMITTED;; Change has been submitted and is in the merge queue.
- It may be waiting for one or more dependencies.
-
MERGED;; Change has been merged to its branch.
ABANDONED;; Change was abandoned by its owner or administrator.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 0384993..4224c76 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3756,8 +3756,7 @@
|`subject` ||
The subject of the change (header line of the commit message).
|`status` ||
-The status of the change (`NEW`, `SUBMITTED`, `MERGED`, `ABANDONED`,
-`DRAFT`).
+The status of the change (`NEW`, `MERGED`, `ABANDONED`, `DRAFT`).
|`created` ||
The link:rest-api.html#timestamp[timestamp] of when the change was
created.
@@ -4322,7 +4321,7 @@
|`_revision_number` |optional|The revision number.
|`_current_revision_number`|optional|The current revision number.
|`status` |optional|The status of the change. The status of
-the change is one of (`NEW`, `SUBMITTED`, `MERGED`, `ABANDONED`, `DRAFT`).
+the change is one of (`NEW`, `MERGED`, `ABANDONED`, `DRAFT`).
|===========================
[[related-changes-info]]
@@ -4530,8 +4529,8 @@
|==========================
|Field Name ||Description
|`status` ||
-The status of the change after submitting, can be `MERGED` or
-`SUBMITTED`.+
+The status of the change after submitting is `MERGED`.
++
As `wait_for_merge` in the link:#submit-input[SubmitInput] is deprecated and
the request always waits for the merge to be completed, you can expect
`MERGED` to be returned here.
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 5a6c032..6762411 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1163,6 +1163,10 @@
|`all_users_name` ||
Name of the link:config-gerrit.html#gerrit.allUsers[project in which
meta data of all users is stored].
+|`doc_url` |optional|
+Custom base URL where Gerrit server documentation is located.
+(Documentation may still be available at /Documentation relative to the
+Gerrit base path even if this value is unset.)
|`report_bug_url` |optional|
link:config-gerrit.html#gerrit.reportBugUrl[URL to report bugs].
|`report_bug_text` |optional, not set if default|
diff --git a/README.md b/README.md
index b693e01..573042d 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,6 @@
## Events
-Next developer conference is September 2015, Berlin.
-Next user conference is November 2015, MV, CA. Registration form is
-[here](http://goo.gl/forms/fifi2YQTc7).
+- November 7-8 2015: Gerrit User Conference, Mountain View. ([Register](http://goo.gl/forms/fifi2YQTc7)).
+- November 9-13 2015: Gerrit Hackathon, Mountain View. (Invitation Only).
+- March 2016: Gerrit Hackathon, Berlin. (Details to be confirmed).
diff --git a/ReleaseNotes/ReleaseNotes-2.11.2.txt b/ReleaseNotes/ReleaseNotes-2.11.2.txt
new file mode 100644
index 0000000..1e411e4
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.11.2.txt
@@ -0,0 +1,97 @@
+Release notes for Gerrit 2.11.2
+===============================
+
+Gerrit 2.11.2 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.2.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.11.2.war]
+
+Gerrit 2.11.2 includes the bug fixes done with
+link:ReleaseNotes-2.10.6.html[Gerrit 2.10.6]. These bug fixes are *not* listed
+in these release notes.
+
+There are no schema changes from link:ReleaseNotes-2.11.1.html[2.11.1].
+
+New Features
+------------
+
+New SSH commands:
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.2/cmd-index-start.html[
+`index start`]
++
+Allows to restart the online indexer without restarting the Gerrit server.
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.2/cmd-index-activate.html[
+`index activate`]
++
+Allows to activate the latest index version even if the indexing encountered
+problems.
+
+
+Bug Fixes
+---------
+
+* link:link:https://code.google.com/p/gerrit/issues/detail?id=3460[Issue 3460]:
+Fix regression in the search box auto-suggestions.
++
+A change introduced in version 2.11 caused the auto-suggestions to not work
+any more.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3355[Issue 3355]:
+Fix corruption of database when deleting draft change ref fails.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3426[Issue 3426]:
+Fix regression in the `%base` option.
++
+A change introduced in version 2.11 caused the `%base` option to not work
+any more, meaning it was not possible to push a commit, which is already merged
+into a branch, for review to another branch of the same project.
+
+* link:https://bugs.eclipse.org/bugs/show_bug.cgi?id=468024[JGit bug 468024]:
+Fix data loss if a pack is pushed to a JGit based server and gc runs
+concurrently on the same repository.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3371[Issue 3371]:
+Fix wrong date/time for commits in `refs/meta/config` branch.
++
+When the `refs/meta/config` branch was modified using the PutConfig REST endpoint
+(e.g. when changing the project configuration in the web UI) the commit date/time
+was wrong. Instead of the actual date/time the date/time of the last Gerrit server
+start was used.
+
+* Fix NullPointerException in the 'related changes' REST API endpoint.
+
+* Make sure `/a` is not in the project name for git-over-http requests.
++
+The `/a` prefix is used to trigger authentication but was not removed from the
+request. Therefore, it was included in the project name and hence the project
+wasn't found when performing, for example `git fetch http://server/a/project`.
+
+* Fix disabling of git ssh commands.
++
+The ssh commands were available even when ssh commands were disabled.
+
+* Fix native string handling in Plugin API.
++
+The results of REST API calls were incorrectly being converted from NativeString
+to String when called from Javascript.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3440[Issue 3440]:
+Include prettify source files in the documentation.
++
+The prettify source files were being loaded from `cdnjs.cloudflare.com`, which
+may cause trouble if the Gerrit instance is behind a firewall on a machine not
+allowed to access the Internet at large.
++
+Now those files are bundled with the documentation.
+
+* Print proper name for project indexer tasks in `show-queue` command.
+
+* Print proper name for reindex after update tasks in `show-queue` command.
+
+Updates
+-------
+
+* Update JGit to 4.0.1.201506240215-r.
+
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 773fdd2..735bc0d 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,6 +4,7 @@
[[2_11]]
Version 2.11.x
--------------
+* link:ReleaseNotes-2.11.2.html[2.11.2]
* link:ReleaseNotes-2.11.1.html[2.11.1]
* link:ReleaseNotes-2.11.html[2.11]
diff --git a/contrib/abandon_stale.py b/contrib/abandon_stale.py
index f118346..32edf84 100755
--- a/contrib/abandon_stale.py
+++ b/contrib/abandon_stale.py
@@ -101,7 +101,7 @@
logging.error("Gerrit URL is required")
return 1
- pattern = re.compile(r"^([\d]+)(months|years)")
+ pattern = re.compile(r"^([\d]+)(month[s]?|year[s]?|week[s]?)")
match = pattern.match(options.age)
if not match:
logging.error("Invalid age: %s", options.age)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 8ac8a88..a4cbe72 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -39,6 +39,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.GroupCache;
@@ -67,6 +68,7 @@
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.Transport;
import org.junit.AfterClass;
@@ -148,6 +150,10 @@
@Inject
private Provider<AnonymousUser> anonymousUser;
+ @Inject
+ @GerritPersonIdent
+ protected Provider<PersonIdent> serverIdent;
+
protected TestRepository<InMemoryRepository> testRepo;
protected GerritServer server;
protected TestAccount admin;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
index e7b5834..4a6d22d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -79,7 +79,7 @@
}
public PersonIdent getIdent() {
- return new PersonIdent(username, email);
+ return new PersonIdent(fullName, email);
}
public String getHttpUrl(GerritServer server) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 201d940..23e3685 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -123,7 +123,6 @@
.review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
in.onBehalfOf = admin2.email;
- in.waitForMerge = true;
gApi.changes()
.id(project.get() + "~master~" + r.getChangeId())
.current()
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 8bf4511..2dbbb16 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
@@ -29,6 +29,8 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
+import java.util.concurrent.atomic.AtomicInteger;
+
public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
protected TestRepository<?> createProjectWithPush(String name)
throws Exception {
@@ -38,25 +40,54 @@
return cloneProject(project);
}
- protected void createSubscription(
- TestRepository<?> repo, String branch, String subscribeToRepo,
- String subscribeToBranch) throws Exception {
- subscribeToRepo = name(subscribeToRepo);
+ private static AtomicInteger contentCounter = new AtomicInteger(0);
+ protected ObjectId pushChangeTo(TestRepository<?> repo, String ref,
+ String message, String topic) throws Exception {
+ ObjectId ret = repo.branch("HEAD").commit().insertChangeId()
+ .message(message)
+ .add("a.txt", "a contents: " + contentCounter.incrementAndGet())
+ .create();
+ String refspec = "HEAD:" + ref;
+ if (!topic.isEmpty()) {
+ refspec += "/" + topic;
+ }
+ repo.git().push().setRemote("origin").setRefSpecs(
+ new RefSpec(refspec)).call();
+ return ret;
+ }
+
+ protected ObjectId pushChangeTo(TestRepository<?> repo, String branch)
+ throws Exception {
+ return pushChangeTo(repo, "refs/heads/" + branch, "some change", "");
+ }
+
+ protected void createSubscription(TestRepository<?> repo, String branch,
+ String subscribeToRepo, String subscribeToBranch) throws Exception {
+ Config config = new Config();
+ prepareSubscriptionConfigEntry(config, subscribeToRepo, subscribeToBranch);
+ pushSubscriptionConfig(repo, branch, config);
+ }
+
+ protected void prepareSubscriptionConfigEntry(Config config,
+ String subscribeToRepo, String subscribeToBranch) {
+ subscribeToRepo = name(subscribeToRepo);
// The submodule subscription module checks for gerrit.canonicalWebUrl to
// detect if it's configured for automatic updates. It doesn't matter if
// it serves from that URL.
String url = cfg.getString("gerrit", null, "canonicalWebUrl") + "/"
+ subscribeToRepo;
+ config.setString("submodule", subscribeToRepo, "path", subscribeToRepo);
+ config.setString("submodule", subscribeToRepo, "url", url);
+ config.setString("submodule", subscribeToRepo, "branch", subscribeToBranch);
+ }
- Config cfg = new Config();
- cfg.setString("submodule", subscribeToRepo, "path", subscribeToRepo);
- cfg.setString("submodule", subscribeToRepo, "url", url);
- cfg.setString("submodule", subscribeToRepo, "branch", subscribeToBranch);
+ protected void pushSubscriptionConfig(TestRepository<?> repo,
+ String branch, Config config) throws Exception {
repo.branch("HEAD").commit().insertChangeId()
.message("subject: adding new subscription")
- .add(".gitmodules", cfg.toText().toString())
+ .add(".gitmodules", config.toText().toString())
.create();
repo.git().push().setRemote("origin").setRefSpecs(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index 1f0c601..9a8bb51 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -26,14 +26,12 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -53,10 +51,6 @@
@Inject
private ChangeNotes.Factory changeNotesFactory;
- @Inject
- @GerritPersonIdent
- private PersonIdent serverIdent;
-
@Test
public void submitOnPush() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
@@ -124,7 +118,7 @@
PushOneCommit.Result r =
push("refs/for/master%submit", "other change", "a.txt", "other content");
r.assertErrorStatus();
- r.assertChange(Change.Status.NEW, null, admin);
+ r.assertChange(Change.Status.NEW, null);
r.assertMessage(CommitMergeStatus.PATH_CONFLICT.getMessage());
}
@@ -257,7 +251,7 @@
assertThat(c.getShortMessage()).isEqualTo("Merge \"" + subject + "\"");
assertThat(c.getAuthorIdent().getEmailAddress()).isEqualTo(admin.email);
assertThat(c.getCommitterIdent().getEmailAddress()).isEqualTo(
- serverIdent.getEmailAddress());
+ serverIdent.get().getEmailAddress());
}
}
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 3ffe07c..ad63e3c 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
@@ -24,8 +24,6 @@
import org.eclipse.jgit.transport.RefSpec;
import org.junit.Test;
-import java.util.concurrent.atomic.AtomicInteger;
-
public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
@Test
@@ -52,6 +50,34 @@
}
@Test
+ public void testSubmoduleCommitMessage() throws Exception {
+ TestRepository<?> superRepo = createProjectWithPush("super-project");
+ TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+ pushChangeTo(subRepo, "master");
+ createSubscription(superRepo, "master", "subscribed-to-project", "master");
+ ObjectId subHEAD = pushChangeTo(subRepo, "master");
+
+ // The first update doesn't include the rev log
+ RevWalk rw = subRepo.getRevWalk();
+ RevCommit subCommitMsg = rw.parseCommit(subHEAD);
+ expectToHaveCommitMessage(superRepo, "master",
+ "Updated git submodules\n\n" +
+ "Project: " + name("subscribed-to-project")
+ + " master " + subHEAD.name() + "\n\n");
+
+ // The next commit should generate only its commit message,
+ // omitting previous commit logs
+ subHEAD = pushChangeTo(subRepo, "master");
+ subCommitMsg = rw.parseCommit(subHEAD);
+ expectToHaveCommitMessage(superRepo, "master",
+ "Updated git submodules\n\n" +
+ "Project: " + name("subscribed-to-project")
+ + " master " + subHEAD.name() + "\n\n" +
+ subCommitMsg.getFullMessage() + "\n\n");
+ }
+
+ @Test
public void testSubscriptionUnsubscribe() throws Exception {
TestRepository<?> superRepo = createProjectWithPush("super-project");
TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
@@ -66,8 +92,10 @@
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEADbeforeUnsubscribing);
- pushChangeTo(superRepo, "master", "commit after unsubscribe");
- pushChangeTo(subRepo, "master", "commit after unsubscribe");
+ pushChangeTo(superRepo, "refs/heads/master",
+ "commit after unsubscribe", "");
+ pushChangeTo(subRepo, "refs/heads/master",
+ "commit after unsubscribe", "");
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEADbeforeUnsubscribing);
}
@@ -87,8 +115,10 @@
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEADbeforeUnsubscribing);
- pushChangeTo(superRepo, "master", "commit after unsubscribe");
- pushChangeTo(subRepo, "master", "commit after unsubscribe");
+ pushChangeTo(superRepo, "refs/heads/master",
+ "commit after unsubscribe", "");
+ pushChangeTo(subRepo, "refs/heads/master",
+ "commit after unsubscribe", "");
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subHEADbeforeUnsubscribing);
}
@@ -124,27 +154,6 @@
assertThat(hasSubmodule(subRepo, "master", "super-project")).isFalse();
}
- private static AtomicInteger contentCounter = new AtomicInteger(0);
-
- private ObjectId pushChangeTo(TestRepository<?> repo, String branch, String message)
- throws Exception {
-
- ObjectId ret = repo.branch("HEAD").commit().insertChangeId()
- .message(message)
- .add("a.txt", "a contents: " + contentCounter.addAndGet(1))
- .create();
-
- repo.git().push().setRemote("origin").setRefSpecs(
- new RefSpec("HEAD:refs/heads/" + branch)).call();
-
- return ret;
- }
-
- private ObjectId pushChangeTo(TestRepository<?> repo, String branch)
- throws Exception {
- return pushChangeTo(repo, branch, "some change");
- }
-
private void deleteAllSubscriptions(TestRepository<?> repo, String branch)
throws Exception {
repo.git().fetch().setRemote("origin").call();
@@ -198,4 +207,14 @@
}
}
+ private void expectToHaveCommitMessage(TestRepository<?> repo,
+ String branch, String expectedMessage) throws Exception {
+
+ ObjectId commitId = repo.git().fetch().setRemote("origin").call()
+ .getAdvertisedRef("refs/heads/" + branch).getObjectId();
+
+ RevWalk rw = repo.getRevWalk();
+ RevCommit c = rw.parseCommit(commitId);
+ assertThat(c.getFullMessage()).isEqualTo(expectedMessage);
+ }
}
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 7823e7d..086c205 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
@@ -14,6 +14,7 @@
package com.google.gerrit.acceptance.git;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.getChangeId;
import com.google.gerrit.acceptance.NoHttpd;
@@ -87,4 +88,45 @@
expectToHaveSubmoduleState(superRepo, "master",
"subscribed-to-project", subRepoId);
}
+
+ @Test
+ public void testUpdateManySubmodules() throws Exception {
+ TestRepository<?> superRepo = createProjectWithPush("super-project");
+ TestRepository<?> sub1 = createProjectWithPush("sub1");
+ TestRepository<?> sub2 = createProjectWithPush("sub2");
+ TestRepository<?> sub3 = createProjectWithPush("sub3");
+
+ Config config = new Config();
+ prepareSubscriptionConfigEntry(config, "sub1", "master");
+ prepareSubscriptionConfigEntry(config, "sub2", "master");
+ prepareSubscriptionConfigEntry(config, "sub3", "master");
+ pushSubscriptionConfig(superRepo, "master", config);
+
+ ObjectId superPreviousId = pushChangeTo(superRepo, "master");
+
+ ObjectId sub1Id = pushChangeTo(sub1, "refs/for/master",
+ "some message", "same-topic");
+ ObjectId sub2Id = pushChangeTo(sub2, "refs/for/master",
+ "some message", "same-topic");
+ ObjectId sub3Id = pushChangeTo(sub3, "refs/for/master",
+ "some message", "same-topic");
+
+ approve(getChangeId(sub1, sub1Id).get());
+ approve(getChangeId(sub2, sub2Id).get());
+ approve(getChangeId(sub3, sub3Id).get());
+
+ gApi.changes().id(getChangeId(sub1, sub1Id).get()).current().submit();
+
+ expectToHaveSubmoduleState(superRepo, "master", "sub1", sub1Id);
+ expectToHaveSubmoduleState(superRepo, "master", "sub2", sub2Id);
+ expectToHaveSubmoduleState(superRepo, "master", "sub3", sub3Id);
+
+ superRepo.git().fetch().setRemote("origin").call()
+ .getAdvertisedRef("refs/heads/master").getObjectId();
+
+ assertWithMessage("submodule subscription update "
+ + "should have made one commit")
+ .that(superRepo.getRepository().resolve("origin/master^"))
+ .isEqualTo(superPreviousId);
+ }
}
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 666be73..6fcdbce 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
@@ -41,7 +41,6 @@
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
@@ -61,6 +60,7 @@
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -71,8 +71,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -145,9 +143,11 @@
change1.assertChange(Change.Status.MERGED, "test-topic", admin);
change2.assertChange(Change.Status.MERGED, "test-topic", admin);
change3.assertChange(Change.Status.MERGED, "test-topic", admin);
+ // Check for the exact change to have the correct submitter.
+ assertSubmitter(change3);
+ // Also check submitters for changes submitted via the topic relationship.
assertSubmitter(change1);
assertSubmitter(change2);
- assertSubmitter(change3);
}
private void assertSubmitter(PushOneCommit.Result change) throws Exception {
@@ -202,26 +202,9 @@
submit(changeId, HttpStatus.SC_CONFLICT);
}
- protected void submitStatusOnly(String changeId) throws Exception {
- approve(changeId);
- Change c = queryProvider.get().byKeyPrefix(changeId).get(0).change();
- c.setStatus(Change.Status.SUBMITTED);
- db.changes().update(Collections.singleton(c));
- db.patchSetApprovals().insert(Collections.singleton(
- new PatchSetApproval(
- new PatchSetApproval.Key(
- c.currentPatchSetId(),
- admin.id,
- LabelId.SUBMIT),
- (short) 1,
- new Timestamp(System.currentTimeMillis()))));
- indexer.index(db, c);
- }
-
private void submit(String changeId, int expectedStatus) throws Exception {
approve(changeId);
SubmitInput subm = new SubmitInput();
- subm.waitForMerge = false;
RestResponse r =
adminSession.post("/changes/" + changeId + "/submit", subm);
assertThat(r.getStatusCode()).isEqualTo(expectedStatus);
@@ -246,8 +229,10 @@
BranchInfo branch =
newGson().fromJson(b.getReader(),
new TypeToken<BranchInfo>() {}.getType());
- assertThat(branch.revision).isEqualTo(
- mergeResults.get(Integer.toString(change._number)));
+ assertThat(mergeResults).isNotEmpty();
+ String newRev = mergeResults.get(Integer.toString(change._number));
+ assertThat(newRev).isNotNull();
+ assertThat(branch.revision).isEqualTo(newRev);
}
b.consume();
}
@@ -266,6 +251,10 @@
}
}
+ protected void assertNew(String changeId) throws Exception {
+ assertThat(get(changeId).status).isEqualTo(ChangeStatus.NEW);
+ }
+
protected void assertApproved(String changeId) throws Exception {
ChangeInfo c = get(changeId, DETAILED_LABELS);
LabelInfo cr = c.labels.get("Code-Review");
@@ -274,6 +263,16 @@
assertThat(new Account.Id(cr.all.get(0)._accountId)).isEqualTo(admin.getId());
}
+ protected void assertPersonEquals(PersonIdent expected,
+ PersonIdent actual) {
+ assertThat(actual.getEmailAddress())
+ .isEqualTo(expected.getEmailAddress());
+ assertThat(actual.getName())
+ .isEqualTo(expected.getName());
+ assertThat(actual.getTimeZone())
+ .isEqualTo(expected.getTimeZone());
+ }
+
protected void assertSubmitter(String changeId, int psId)
throws OrmException {
ChangeNotes cn = notesFactory.create(
@@ -284,6 +283,15 @@
assertThat(submitter.getAccountId()).isEqualTo(admin.getId());
}
+ protected void assertNoSubmitter(String changeId, int psId)
+ throws OrmException {
+ ChangeNotes cn = notesFactory.create(
+ getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change());
+ PatchSetApproval submitter = approvalsUtil.getSubmitter(
+ db, cn, new PatchSet.Id(cn.getChangeId(), psId));
+ assertThat(submitter).isNull();
+ }
+
protected void assertCherryPick(TestRepository<?> testRepo,
boolean contentMerge) throws IOException {
assertRebase(testRepo, contentMerge);
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 ee17797..d0bcfd9 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
@@ -56,7 +56,7 @@
@Test
public void createEmptyChange_InvalidStatus() throws Exception {
- ChangeInfo ci = newChangeInfo(ChangeStatus.SUBMITTED);
+ ChangeInfo ci = newChangeInfo(ChangeStatus.MERGED);
assertCreateFails(ci, BadRequestException.class,
"unsupported change status");
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index b54c5d6..b99b30a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -65,6 +65,8 @@
assertCurrentRevision(change2.getChangeId(), 2, newHead);
assertSubmitter(change2.getChangeId(), 1);
assertSubmitter(change2.getChangeId(), 2);
+ assertPersonEquals(admin.getIdent(), newHead.getAuthorIdent());
+ assertPersonEquals(admin.getIdent(), newHead.getCommitterIdent());
}
@Test
@@ -106,7 +108,7 @@
submitWithConflict(change2.getChangeId());
assertThat(getRemoteHead()).isEqualTo(oldHead);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommitId());
- assertSubmitter(change2.getChangeId(), 1);
+ assertNoSubmitter(change2.getChangeId(), 1);
}
@Test
@@ -146,7 +148,7 @@
submitWithConflict(change3.getChangeId());
assertThat(getRemoteHead()).isEqualTo(oldHead);
assertCurrentRevision(change3.getChangeId(), 1, change3.getCommitId());
- assertSubmitter(change3.getChangeId(), 1);
+ assertNoSubmitter(change3.getChangeId(), 1);
}
@Test
@@ -162,24 +164,17 @@
testRepo.reset(initialHead);
PushOneCommit.Result change4 = createChange("Change 4", "d", "d");
- submitStatusOnly(change2.getChangeId());
- submitStatusOnly(change3.getChangeId());
+ approve(change2.getChangeId());
+ approve(change3.getChangeId());
submit(change4.getChangeId());
List<RevCommit> log = getRemoteLog();
assertThat(log.get(0).getShortMessage()).isEqualTo(
change4.getCommit().getShortMessage());
- assertThat(log.get(0).getParent(0)).isEqualTo(log.get(1));
+ assertThat(log.get(1).getId()).isEqualTo(initialHead.getId());
- assertThat(log.get(1).getShortMessage()).isEqualTo(
- change3.getCommit().getShortMessage());
- assertThat(log.get(1).getParent(0)).isEqualTo(log.get(2));
-
- assertThat(log.get(2).getShortMessage()).isEqualTo(
- change2.getCommit().getShortMessage());
- assertThat(log.get(2).getParent(0)).isEqualTo(log.get(3));
-
- assertThat(log.get(3).getId()).isEqualTo(initialHead.getId());
+ assertNew(change2.getChangeId());
+ assertNew(change3.getChangeId());
}
@Test
@@ -231,6 +226,7 @@
// Tip has not changed.
List<RevCommit> log = getRemoteLog();
assertThat(log.get(0)).isEqualTo(initialHead.getId());
+ assertNoSubmitter(change3.getChangeId(), 1);
}
@Test
@@ -238,57 +234,16 @@
RevCommit initialHead = getRemoteHead();
testRepo.reset(initialHead);
- createChange("Change 2", "b", "b");
+ PushOneCommit.Result change2 = createChange("Change 2", "b", "b");
PushOneCommit.Result change3 = createChange("Change 3", "c", "c");
- createChange("Change 4", "d", "d");
- PushOneCommit.Result change5 = createChange("Change 5", "e", "e");
+ PushOneCommit.Result change4 = createChange("Change 5", "e", "e");
- // Out of the above, only submit 3 and 5.
- submitStatusOnly(change3.getChangeId());
- submit(change5.getChangeId());
+ // Out of the above, only submit 4. 2,3 are not related to 4
+ // by topic or ancestor (due to cherrypicking!)
+ approve(change3.getChangeId());
+ submit(change4.getChangeId());
- ChangeInfo info3 = get(change3.getChangeId());
- assertThat(info3.status).isEqualTo(ChangeStatus.MERGED);
-
- List<RevCommit> log = getRemoteLog();
- assertThat(log.get(0).getShortMessage())
- .isEqualTo(change5.getCommit().getShortMessage());
- assertThat(log.get(1).getShortMessage())
- .isEqualTo(change3.getCommit().getShortMessage());
- assertThat(log.get(2).getShortMessage())
- .isEqualTo(initialHead.getShortMessage());
- }
-
- @Test
- public void submitChangeAfterParentFailsDueToConflict() throws Exception {
- RevCommit initialHead = getRemoteHead();
-
- testRepo.reset(initialHead);
- PushOneCommit.Result change2 = createChange("Change 2", "b", "b1");
- submit(change2.getChangeId());
-
- testRepo.reset(initialHead);
- PushOneCommit.Result change3 = createChange("Change 3", "b", "b2");
- assertThat(change3.getCommit().getParent(0)).isEqualTo(initialHead);
- PushOneCommit.Result change4 = createChange("Change 3", "c", "c3");
-
- submitStatusOnly(change3.getChangeId());
- submitStatusOnly(change4.getChangeId());
-
- // Merge fails; change3 contains the delta "b1" -> "b2", which cannot be
- // applied against tip.
- // As change4 sits on top of change 3 we need to trigger submission there
- // to include it into the mergeing
- submitWithConflict(change4.getChangeId());
-
- // change4 is a clean merge, so should succeed in the same run where change3
- // failed.
- ChangeInfo info4 = get(change4.getChangeId());
- assertThat(info4.status).isEqualTo(ChangeStatus.MERGED);
- List<RevCommit> log = getRemoteLog();
- assertThat(log.get(0).getShortMessage())
- .isEqualTo(change4.getCommit().getShortMessage());
- assertThat(log.get(1).getShortMessage())
- .isEqualTo(change2.getCommit().getShortMessage());
+ assertNew(change2.getChangeId());
+ assertNew(change3.getChangeId());
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 62a92b8..2655789 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -56,12 +56,14 @@
assertThat(head.getParent(0).getId()).isEqualTo(change.getCommitId());
assertSubmitter(change.getChangeId(), 1);
assertSubmitter(change2.getChangeId(), 1);
+ assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
+ assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
}
@Test
public void submitTwoChangesWithFastForward_missingDependency() throws Exception {
RevCommit oldHead = getRemoteHead();
- PushOneCommit.Result change = createChange();
+ createChange();
PushOneCommit.Result change2 = createChange();
submitWithConflict(change2.getChangeId());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
index ebd3d3c..eb1d16b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
@@ -41,6 +41,8 @@
assertThat(head.getParent(0)).isEqualTo(oldHead);
assertThat(head.getParent(1)).isEqualTo(change.getCommitId());
assertSubmitter(change.getChangeId(), 1);
+ assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
+ assertPersonEquals(serverIdent.get(), head.getCommitterIdent());
}
@Test
@@ -56,23 +58,22 @@
testRepo.reset(initialHead);
PushOneCommit.Result change4 = createChange("Change 4", "d", "d");
- submitStatusOnly(change2.getChangeId());
- submitStatusOnly(change3.getChangeId());
+ approve(change2.getChangeId());
+ approve(change3.getChangeId());
submit(change4.getChangeId());
List<RevCommit> log = getRemoteLog();
RevCommit tip = log.get(0);
assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
change4.getCommit().getShortMessage());
-
- tip = tip.getParent(0);
- assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
- change3.getCommit().getShortMessage());
-
- tip = tip.getParent(0);
- assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
- change2.getCommit().getShortMessage());
-
+ assertThat(tip.getParent(0).getShortMessage()).isEqualTo(
+ initialHead.getShortMessage());
assertThat(tip.getParent(0).getId()).isEqualTo(initialHead.getId());
+
+ assertPersonEquals(admin.getIdent(), tip.getAuthorIdent());
+ assertPersonEquals(serverIdent.get(), tip.getCommitterIdent());
+
+ assertNew(change2.getChangeId());
+ assertNew(change3.getChangeId());
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index a9beefe..032cb7d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -29,6 +29,8 @@
assertThat(head.getId()).isEqualTo(change.getCommitId());
assertThat(head.getParent(0)).isEqualTo(oldHead);
assertSubmitter(change.getChangeId(), 1);
+ assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
+ assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
}
@Test
@@ -44,23 +46,32 @@
testRepo.reset(initialHead);
PushOneCommit.Result change4 = createChange("Change 4", "d", "d");
- submitStatusOnly(change2.getChangeId());
- submitStatusOnly(change3.getChangeId());
- submit(change4.getChangeId());
+ // Change 2 stays untouched.
+ approve(change2.getChangeId());
+ // Change 3 is a fast-forward, no need to merge.
+ submit(change3.getChangeId());
RevCommit tip = getRemoteLog().get(0);
+ assertThat(tip.getShortMessage()).isEqualTo(
+ change3.getCommit().getShortMessage());
+ assertThat(tip.getParent(0).getId()).isEqualTo(
+ initialHead.getId());
+ assertPersonEquals(admin.getIdent(), tip.getAuthorIdent());
+ assertPersonEquals(admin.getIdent(), tip.getCommitterIdent());
+
+ // We need to merge change 4.
+ submit(change4.getChangeId());
+
+ tip = getRemoteLog().get(0);
assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
change4.getCommit().getShortMessage());
-
- tip = tip.getParent(0);
- assertThat(tip.getParent(1).getShortMessage()).isEqualTo(
+ assertThat(tip.getParent(0).getShortMessage()).isEqualTo(
change3.getCommit().getShortMessage());
- tip = tip.getParent(0);
- assertThat(tip.getShortMessage()).isEqualTo(
- change2.getCommit().getShortMessage());
+ assertPersonEquals(admin.getIdent(), tip.getAuthorIdent());
+ assertPersonEquals(serverIdent.get(), tip.getCommitterIdent());
- assertThat(tip.getParent(0).getId()).isEqualTo(initialHead.getId());
+ assertNew(change2.getChangeId());
}
@Test
@@ -184,6 +195,10 @@
initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(
change3Conflict.getCommit().getShortMessage());
+ assertNoSubmitter(change1a.getChangeId(), 1);
+ assertNoSubmitter(change2a.getChangeId(), 1);
+ assertNoSubmitter(change2b.getChangeId(), 1);
+ assertNoSubmitter(change3.getChangeId(), 1);
} else {
assertThat(tip1.getShortMessage()).isEqualTo(
change1b.getCommit().getShortMessage());
@@ -191,6 +206,9 @@
initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(
change3Conflict.getCommit().getShortMessage());
+ assertNoSubmitter(change2a.getChangeId(), 1);
+ assertNoSubmitter(change2b.getChangeId(), 1);
+ assertNoSubmitter(change3.getChangeId(), 1);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index bb26a30..d1d3bdf 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -43,6 +43,8 @@
assertApproved(change.getChangeId());
assertCurrentRevision(change.getChangeId(), 1, head);
assertSubmitter(change.getChangeId(), 1);
+ assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
+ assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
}
@Test
@@ -65,6 +67,8 @@
assertCurrentRevision(change2.getChangeId(), 2, head);
assertSubmitter(change2.getChangeId(), 1);
assertSubmitter(change2.getChangeId(), 2);
+ assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
+ assertPersonEquals(serverIdent.get(), head.getCommitterIdent());
}
@Test
@@ -107,6 +111,6 @@
RevCommit head = getRemoteHead();
assertThat(head).isEqualTo(oldHead);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommitId());
- assertSubmitter(change2.getChangeId(), 1);
+ assertNoSubmitter(change2.getChangeId(), 1);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
index d3677ab..7044ad0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -102,9 +102,9 @@
assertThat(info.subject).isEqualTo("test commit");
assertThat(info.message).isEqualTo(
"test commit\n\nChange-Id: " + r.getChangeId() + "\n");
- assertThat(info.author.name).isEqualTo("admin");
+ assertThat(info.author.name).isEqualTo("Administrator");
assertThat(info.author.email).isEqualTo("admin@example.com");
- assertThat(info.committer.name).isEqualTo("admin");
+ assertThat(info.committer.name).isEqualTo("Administrator");
assertThat(info.committer.email).isEqualTo("admin@example.com");
CommitInfo parent = Iterables.getOnlyElement(info.parents);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index c15c1085..342fe4c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -23,7 +23,6 @@
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.git.ProjectConfig;
import org.eclipse.jgit.lib.Repository;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index b804607..80ca561 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -135,7 +135,6 @@
case MERGED:
return "status:merged";
case NEW:
- case SUBMITTED:
default:
return "status:open";
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 3d1e3bd..cb0d5f3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -29,7 +29,6 @@
void delete() throws RestApiException;
void review(ReviewInput in) throws RestApiException;
- /** {@code submit} with {@link SubmitInput#waitForMerge} set to true. */
void submit() throws RestApiException;
void submit(SubmitInput in) throws RestApiException;
void publish() throws RestApiException;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java
index f3fc887..56ebf9d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ChangeStatus.java
@@ -28,40 +28,13 @@
* <p>
* Changes in the NEW state can be moved to:
* <ul>
- * <li>{@link #SUBMITTED} - when the Submit Patch Set action is used;
+ * <li>{@link #MERGED} - when the Submit Patch Set action is used;
* <li>{@link #ABANDONED} - when the Abandon action is used.
* </ul>
*/
NEW,
/**
- * Change is open, but has been submitted to the merge queue.
- *
- * <p>
- * A change enters the SUBMITTED state when an authorized user presses the
- * "submit" action through the web UI, requesting that Gerrit merge the
- * change's current patch set into the destination branch.
- *
- * <p>
- * Typically a change resides in the SUBMITTED for only a brief sub-second
- * period while the merge queue fires and the destination branch is updated.
- * However, if a dependency commit (directly or transitively) is not yet
- * merged into the branch, the change will hang in the SUBMITTED state
- * indefinitely.
- *
- * <p>
- * Changes in the SUBMITTED state can be moved to:
- * <ul>
- * <li>{@link #NEW} - when a replacement patch set is supplied, OR when a
- * merge conflict is detected;
- * <li>{@link #MERGED} - when the change has been successfully merged into
- * the destination branch;
- * <li>{@link #ABANDONED} - when the Abandon action is used.
- * </ul>
- */
- SUBMITTED,
-
- /**
* Change is a draft change that only consists of draft patchsets.
*
* <p>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 8a2d645..070a868 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -113,6 +113,7 @@
private static String myHost;
private static ServerInfo myServerInfo;
private static boolean hasDocumentation;
+ private static String docUrl;
private static HostPageData.Theme myTheme;
private static Account myAccount;
private static String defaultScreenToken;
@@ -434,12 +435,18 @@
@Override
public void onSuccess(DocInfo indexInfo) {
hasDocumentation = indexInfo != null;
+ docUrl = selfRedirect("/Documentation/");
}
}));
ConfigServerApi.serverInfo(cbg.add(new GerritCallback<ServerInfo>() {
@Override
public void onSuccess(ServerInfo info) {
myServerInfo = info;
+ String du = info.gerrit().docUrl();
+ if (du != null && !du.isEmpty()) {
+ hasDocumentation = true;
+ docUrl = du;
+ }
}
}));
HostPageDataService hpd = GWT.create(HostPageDataService.class);
@@ -822,10 +829,15 @@
req.setCallback(new RequestCallback() {
@Override
public void onResponseReceived(Request req, Response resp) {
- if (resp.getStatusCode() == Response.SC_OK) {
- cb.onSuccess(DocInfo.create());
- } else {
- cb.onSuccess(null);
+ switch (resp.getStatusCode()) {
+ case Response.SC_OK:
+ case Response.SC_MOVED_PERMANENTLY:
+ case Response.SC_MOVED_TEMPORARILY:
+ cb.onSuccess(DocInfo.create());
+ break;
+ default:
+ cb.onSuccess(null);
+ break;
}
}
@@ -996,7 +1008,7 @@
private static void addDocLink(final LinkMenuBar m, final String text,
final String href) {
- final Anchor atag = anchor(text, selfRedirect("/Documentation/" + href));
+ final Anchor atag = anchor(text, docUrl + href);
atag.setTarget("_blank");
m.add(atag);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
index 9092508..de83354 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
@@ -38,6 +38,7 @@
@Override
public void onSend() {
ChangeApi.createChange(project, getDestinationBranch(),
+ getDestinationTopic(),
message.getText(), null,
new GerritCallback<ChangeInfo>() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
index 0fac957..b5f87cf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/EditConfigAction.java
@@ -27,7 +27,7 @@
static void call(final Button b, final String project) {
b.setEnabled(false);
- ChangeApi.createChange(project, RefNames.REFS_CONFIG,
+ ChangeApi.createChange(project, RefNames.REFS_CONFIG, null,
Util.C.editConfigMessage(), null, new GerritCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index e6f262c..4edcfa65 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -690,7 +690,7 @@
return pluginConfigValues;
}
- public class ProjectDownloadPanel extends DownloadPanel {
+ public static class ProjectDownloadPanel extends DownloadPanel {
public ProjectDownloadPanel(String project, boolean isAllowsAnonymous) {
super(project, isAllowsAnonymous);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
index b7307be..ec5c4a6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
@@ -166,6 +166,14 @@
api.get(wrap(cb));
}
+ /**
+ * The same as {@link #get(RestApi, JavaScriptObject)} but without converting
+ * a {@link NativeString} result to String.
+ */
+ static final void getRaw(RestApi api, final JavaScriptObject cb) {
+ api.get(wrapRaw(cb));
+ }
+
static final void post(RestApi api, JavaScriptObject in, JavaScriptObject cb) {
if (NativeString.is(in)) {
post(api, ((NativeString) in).asString(), cb);
@@ -174,14 +182,42 @@
}
}
+ /**
+ * The same as {@link #post(RestApi, JavaScriptObject, JavaScriptObject)} but
+ * without converting a {@link NativeString} result to String.
+ */
+ static final void postRaw(RestApi api, JavaScriptObject in, JavaScriptObject cb) {
+ if (NativeString.is(in)) {
+ postRaw(api, ((NativeString) in).asString(), cb);
+ } else {
+ api.post(in, wrapRaw(cb));
+ }
+ }
+
static final void post(RestApi api, String in, JavaScriptObject cb) {
api.post(in, wrap(cb));
}
+ /**
+ * The same as {@link #post(RestApi, String, JavaScriptObject)} but without
+ * converting a {@link NativeString} result to String.
+ */
+ static final void postRaw(RestApi api, String in, JavaScriptObject cb) {
+ api.post(in, wrapRaw(cb));
+ }
+
static final void put(RestApi api, JavaScriptObject cb) {
api.put(wrap(cb));
}
+ /**
+ * The same as {@link #put(RestApi, JavaScriptObject)} but without converting
+ * a {@link NativeString} result to String.
+ */
+ static final void putRaw(RestApi api, JavaScriptObject cb) {
+ api.put(wrapRaw(cb));
+ }
+
static final void put(RestApi api, JavaScriptObject in, JavaScriptObject cb) {
if (NativeString.is(in)) {
put(api, ((NativeString) in).asString(), cb);
@@ -190,14 +226,42 @@
}
}
+ /**
+ * The same as {@link #put(RestApi, JavaScriptObject, JavaScriptObject)} but
+ * without converting a {@link NativeString} result to String.
+ */
+ static final void putRaw(RestApi api, JavaScriptObject in, JavaScriptObject cb) {
+ if (NativeString.is(in)) {
+ putRaw(api, ((NativeString) in).asString(), cb);
+ } else {
+ api.put(in, wrapRaw(cb));
+ }
+ }
+
static final void put(RestApi api, String in, JavaScriptObject cb) {
api.put(in, wrap(cb));
}
+ /**
+ * The same as {@link #put(RestApi, String, JavaScriptObject)} but without
+ * converting a {@link NativeString} result to String.
+ */
+ static final void putRaw(RestApi api, String in, JavaScriptObject cb) {
+ api.put(in, wrapRaw(cb));
+ }
+
static final void delete(RestApi api, JavaScriptObject cb) {
api.delete(wrap(cb));
}
+ /**
+ * The same as {@link #delete(RestApi, JavaScriptObject)} but without
+ * converting a {@link NativeString} result to String.
+ */
+ static final void deleteRaw(RestApi api, JavaScriptObject cb) {
+ api.delete(wrapRaw(cb));
+ }
+
private static GerritCallback<JavaScriptObject> wrap(final JavaScriptObject cb) {
return new GerritCallback<JavaScriptObject>() {
@Override
@@ -211,4 +275,13 @@
}
};
}
+
+ private static GerritCallback<JavaScriptObject> wrapRaw(final JavaScriptObject cb) {
+ return new GerritCallback<JavaScriptObject>() {
+ @Override
+ public void onSuccess(JavaScriptObject result) {
+ ApiGlue.invoke(cb, result);
+ }
+ };
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
index b52efe8..e4a5446 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
@@ -102,6 +102,12 @@
Lcom/google/gwt/core/client/JavaScriptObject;)
(this._api(u), b);
},
+ get_raw: function(u,b) {
+ @com.google.gerrit.client.api.ActionContext::getRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), b);
+ },
post: function(u,i,b) {
if (typeof i == 'string') {
@com.google.gerrit.client.api.ActionContext::post(
@@ -117,6 +123,21 @@
(this._api(u), i, b);
}
},
+ post_raw: function(u,i,b) {
+ if (typeof i == 'string') {
+ @com.google.gerrit.client.api.ActionContext::postRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Ljava/lang/String;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), i, b);
+ } else {
+ @com.google.gerrit.client.api.ActionContext::postRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Lcom/google/gwt/core/client/JavaScriptObject;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), i, b);
+ }
+ },
put: function(u,i,b) {
if (b) {
if (typeof i == 'string') {
@@ -139,6 +160,28 @@
(this._api(u), i);
}
},
+ put_raw: function(u,i,b) {
+ if (b) {
+ if (typeof i == 'string') {
+ @com.google.gerrit.client.api.ActionContext::putRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Ljava/lang/String;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), i, b);
+ } else {
+ @com.google.gerrit.client.api.ActionContext::putRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Lcom/google/gwt/core/client/JavaScriptObject;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), i, b);
+ }
+ } else {
+ @com.google.gerrit.client.api.ActionContext::putRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), i);
+ }
+ },
'delete': function(u,b) {
@com.google.gerrit.client.api.ActionContext::delete(
Lcom/google/gerrit/client/rpc/RestApi;
@@ -151,6 +194,12 @@
Lcom/google/gwt/core/client/JavaScriptObject;)
(this._api(u), b);
},
+ del_raw: function(u,b) {
+ @com.google.gerrit.client.api.ActionContext::deleteRaw(
+ Lcom/google/gerrit/client/rpc/RestApi;
+ Lcom/google/gwt/core/client/JavaScriptObject;)
+ (this._api(u), b);
+ },
};
}-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
index 10d77cb..3df0b2d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -60,6 +60,7 @@
private ChangeInfo changeInfo;
private String revision;
private String project;
+ private String topic;
private String subject;
private String message;
private String branch;
@@ -80,6 +81,7 @@
CommitInfo commit = revInfo.commit();
changeId = info.legacyId();
project = info.project();
+ topic = info.topic();
subject = commit.subject();
message = commit.message();
branch = info.branch();
@@ -160,7 +162,7 @@
void onFollowUp(@SuppressWarnings("unused") ClickEvent e) {
if (followUpAction == null) {
followUpAction = new FollowUpAction(followUp, project,
- branch, key);
+ branch, topic, key);
}
followUpAction.show();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
index 30394d6..8f31010 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
@@ -24,18 +24,20 @@
class FollowUpAction extends ActionMessageBox {
private final String project;
private final String branch;
+ private final String topic;
private final String base;
- FollowUpAction(Button b, String project, String branch, String key) {
+ FollowUpAction(Button b, String project, String branch, String topic, String key) {
super(b);
this.project = project;
this.branch = branch;
+ this.topic = topic;
this.base = project + "~" + branch + "~" + key;
}
@Override
void send(String message) {
- ChangeApi.createChange(project, branch, message, base,
+ ChangeApi.createChange(project, branch, topic, message, base,
new GerritCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index 7b899ba..9f7e515 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -23,7 +23,6 @@
import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.rpc.Natives;
-import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 0dd85b1..46d5067 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -45,11 +45,12 @@
* new change is created as NEW.
*
*/
- public static void createChange(String project, String branch,
+ public static void createChange(String project, String branch, String topic,
String subject, String base, AsyncCallback<ChangeInfo> cb) {
CreateChangeInput input = CreateChangeInput.create();
input.project(emptyToNull(project));
input.branch(emptyToNull(branch));
+ input.topic(emptyToNull(topic));
input.subject(emptyToNull(subject));
input.baseChange(emptyToNull(base));
@@ -249,6 +250,7 @@
}
public final native void branch(String b) /*-{ if(b)this.branch=b; }-*/;
+ public final native void topic(String t) /*-{ if(t)this.topic=t; }-*/;
public final native void project(String p) /*-{ if(p)this.project=p; }-*/;
public final native void subject(String s) /*-{ if(s)this.subject=s; }-*/;
public final native void baseChange(String b) /*-{ if(b)this.base_change=b; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 2727ae2..759e722 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -18,7 +18,6 @@
public interface ChangeConstants extends Constants {
String statusLongNew();
- String statusLongSubmitted();
String statusLongMerged();
String statusLongAbandoned();
String statusLongDraft();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 8db9319..ca29773 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -1,5 +1,4 @@
statusLongNew = Review in Progress
-statusLongSubmitted = Submitted, Merge Pending
statusLongMerged = Merged
statusLongAbandoned = Abandoned
statusLongDraft = Draft
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index d34492c..e6d3dbe 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -34,8 +34,6 @@
return C.statusLongDraft();
case NEW:
return C.statusLongNew();
- case SUBMITTED:
- return C.statusLongSubmitted();
case MERGED:
return C.statusLongMerged();
case ABANDONED:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java
index 66e4154..83c1ba9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java
@@ -36,6 +36,7 @@
public final native String allProjects() /*-{ return this.all_projects; }-*/;
public final native String allUsers() /*-{ return this.all_users; }-*/;
+ public final native String docUrl() /*-{ return this.doc_url; }-*/;
public final native String reportBugUrl() /*-{ return this.report_bug_url; }-*/;
public final native String reportBugText() /*-{ return this.report_bug_text; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java
index a2b4aa8..e398e78 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CreateChangeDialog.java
@@ -24,6 +24,7 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.TextBox;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
@@ -33,6 +34,7 @@
public abstract class CreateChangeDialog extends TextAreaActionDialog {
private SuggestBox newChange;
private List<BranchInfo> branches;
+ private TextBox topic;
public CreateChangeDialog(Project.NameKey project) {
super(Util.C.dialogCreateChangeTitle(),
@@ -45,6 +47,15 @@
}
});
+ topic = new TextBox();
+ topic.setWidth("100%");
+ topic.getElement().getStyle().setProperty("boxSizing", "border-box");
+ FlowPanel newTopicPanel = new FlowPanel();
+ newTopicPanel.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
+ newTopicPanel.add(topic);
+ panel.insert(newTopicPanel, 0);
+ panel.insert(new SmallHeading(Util.C.newChangeTopicSuggestion()), 0);
+
newChange = new SuggestBox(new HighlightSuggestOracle() {
@Override
protected void onRequestSuggestions(Request request, Callback done) {
@@ -60,14 +71,13 @@
newChange.setWidth("100%");
newChange.getElement().getStyle().setProperty("boxSizing", "border-box");
- message.setCharacterWidth(70);
-
- FlowPanel mwrap = new FlowPanel();
- mwrap.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
- mwrap.add(newChange);
-
- panel.insert(mwrap, 0);
+ FlowPanel newChangePanel = new FlowPanel();
+ newChangePanel.setStyleName(Gerrit.RESOURCES.css().commentedActionMessage());
+ newChangePanel.add(newChange);
+ panel.insert(newChangePanel, 0);
panel.insert(new SmallHeading(Util.C.newChangeBranchSuggestion()), 0);
+
+ message.setCharacterWidth(70);
}
@Override
@@ -81,6 +91,10 @@
return newChange.getText();
}
+ public String getDestinationTopic() {
+ return topic.getText();
+ }
+
static class BranchSuggestion implements Suggestion {
private BranchInfo branch;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
index 0f5e12a..a220ea0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
@@ -29,4 +29,5 @@
String dialogCreateChangeTitle();
String dialogCreateChangeHeading();
String newChangeBranchSuggestion();
+ String newChangeTopicSuggestion();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
index a0845d9..736e210 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
@@ -10,3 +10,4 @@
dialogCreateChangeTitle = Create Change
dialogCreateChangeHeading = Description
newChangeBranchSuggestion = Select branch for new change
+newChangeTopicSuggestion = Enter topic for new change
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
index bb20875..65f42e5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -249,7 +249,6 @@
CacheHeaders.setNotCacheable(rsp);
OutputStream out;
- @SuppressWarnings("resource")
ZipOutputStream zo;
final MimeType contentType = registry.getMimeType(path, raw);
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 35d6636..5af7cc5 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -48,6 +48,7 @@
@Override
protected void configure() {
factory(LuceneChangeIndex.Factory.class);
+ factory(OnlineReindexer.Factory.class);
install(new IndexModule(threads));
if (singleVersion == null && base == null) {
install(new MultiVersionModule());
@@ -65,7 +66,6 @@
private static class MultiVersionModule extends LifecycleModule {
@Override
public void configure() {
- factory(OnlineReindexer.Factory.class);
listener().to(LuceneVersionManager.class);
}
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
index 44b1ea0..407f5a8 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -46,7 +46,7 @@
import java.util.TreeMap;
@Singleton
-class LuceneVersionManager implements LifecycleListener {
+public class LuceneVersionManager implements LifecycleListener {
private static final Logger log = LoggerFactory
.getLogger(LuceneVersionManager.class);
@@ -95,6 +95,7 @@
private final IndexCollection indexes;
private final OnlineReindexer.Factory reindexerFactory;
private final boolean onlineUpgrade;
+ private OnlineReindexer reindexer;
@Inject
LuceneVersionManager(
@@ -165,7 +166,53 @@
int latest = write.get(0).version;
if (onlineUpgrade && latest != search.version) {
- reindexerFactory.create(latest).start();
+ reindexer = reindexerFactory.create(latest);
+ reindexer.start();
+ }
+ }
+
+ /**
+ * Start the online reindexer if the current index is not already the latest.
+ *
+ * @return true if started, otherwise false.
+ * @throws ReindexerAlreadyRunningException
+ */
+ public synchronized boolean startReindexer()
+ throws ReindexerAlreadyRunningException {
+ validateReindexerNotRunning();
+ if (!isCurrentIndexVersionLatest()) {
+ reindexer.start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Activate the latest index if the current index is not already the latest.
+ *
+ * @return true if index was activate, otherwise false.
+ * @throws ReindexerAlreadyRunningException
+ */
+ public synchronized boolean activateLatestIndex()
+ throws ReindexerAlreadyRunningException {
+ validateReindexerNotRunning();
+ if (!isCurrentIndexVersionLatest()) {
+ reindexer.activateIndex();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isCurrentIndexVersionLatest() {
+ return reindexer == null
+ || reindexer.getVersion() == indexes.getSearchIndex().getSchema()
+ .getVersion();
+ }
+
+ private void validateReindexerNotRunning()
+ throws ReindexerAlreadyRunningException {
+ if (reindexer != null && reindexer.isRunning()) {
+ throw new ReindexerAlreadyRunningException();
}
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
index edded44..1dbc427 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
@@ -29,6 +29,7 @@
import java.io.IOException;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
public class OnlineReindexer {
private static final Logger log = LoggerFactory
@@ -42,6 +43,8 @@
private final SiteIndexer batchIndexer;
private final ProjectCache projectCache;
private final int version;
+ private ChangeIndex index;
+ private final AtomicBoolean running = new AtomicBoolean();
@Inject
OnlineReindexer(
@@ -56,15 +59,29 @@
}
public void start() {
- Thread t = new Thread() {
- @Override
- public void run() {
- reindex();
- }
- };
- t.setName(String.format("Reindex v%d-v%d",
- version(indexes.getSearchIndex()), version));
- t.start();
+ if (running.compareAndSet(false, true)) {
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ try {
+ reindex();
+ } finally {
+ running.set(false);
+ }
+ }
+ };
+ t.setName(String.format("Reindex v%d-v%d",
+ version(indexes.getSearchIndex()), version));
+ t.start();
+ }
+ }
+
+ public boolean isRunning() {
+ return running.get();
+ }
+
+ public int getVersion() {
+ return version;
}
private static int version(ChangeIndex i) {
@@ -72,7 +89,7 @@
}
private void reindex() {
- ChangeIndex index = checkNotNull(indexes.getWriteIndex(version),
+ index = checkNotNull(indexes.getWriteIndex(version),
"not an active write schema version: %s", version);
log.info("Starting online reindex from schema version {} to {}",
version(indexes.getSearchIndex()), version(index));
@@ -84,9 +101,13 @@
version(index), result.doneCount(), result.failedCount());
return;
}
+ log.info("Reindex to version {} complete", version(index));
+ activateIndex();
+ }
+ void activateIndex() {
indexes.setSearchIndex(index);
- log.info("Reindex complete, using schema version {}", version(index));
+ log.info("Using schema version {}", version(index));
try {
index.markReady(true);
} catch (IOException e) {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ReindexerAlreadyRunningException.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ReindexerAlreadyRunningException.java
new file mode 100644
index 0000000..0ca632b
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ReindexerAlreadyRunningException.java
@@ -0,0 +1,24 @@
+// 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.lucene;
+
+public class ReindexerAlreadyRunningException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public ReindexerAlreadyRunningException() {
+ super("Reindexer is already running.");
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index dcb6c85..139af9e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -49,6 +49,7 @@
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.DownloadConfig;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.RestCacheAdminModule;
@@ -402,8 +403,8 @@
if (!test) {
modules.add(new SshHostKeyModule());
}
- modules.add(new DefaultCommandModule(slave));
-
+ modules.add(new DefaultCommandModule(slave,
+ sysInjector.getInstance(DownloadConfig.class)));
return sysInjector.createChildInjector(modules);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java
index 4397661..948182e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtobufImport.java
@@ -136,7 +136,7 @@
}
@AutoValue
- static abstract class Relation {
+ abstract static class Relation {
private static Relation create(RelationModel model, ReviewDb db)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException, ClassNotFoundException {
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
index 8b2a6b8..8d408fb 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
@@ -110,7 +110,7 @@
}
private static native void get(String p, JavaScriptObject r)
- /*-{ $wnd.Gerrit.get(p, r) }-*/;
+ /*-{ $wnd.Gerrit.get_raw(p, r) }-*/;
public <T extends JavaScriptObject>
void put(AsyncCallback<T> cb) {
@@ -118,7 +118,7 @@
}
private static native void put(String p, JavaScriptObject r)
- /*-{ $wnd.Gerrit.put(p, r) }-*/;
+ /*-{ $wnd.Gerrit.put_raw(p, r) }-*/;
public <T extends JavaScriptObject>
void put(String content, AsyncCallback<T> cb) {
@@ -127,7 +127,7 @@
private static native
void put(String p, String c, JavaScriptObject r)
- /*-{ $wnd.Gerrit.put(p, c, r) }-*/;
+ /*-{ $wnd.Gerrit.put_raw(p, c, r) }-*/;
public <T extends JavaScriptObject>
void put(JavaScriptObject content, AsyncCallback<T> cb) {
@@ -136,7 +136,7 @@
private static native
void put(String p, JavaScriptObject c, JavaScriptObject r)
- /*-{ $wnd.Gerrit.put(p, c, r) }-*/;
+ /*-{ $wnd.Gerrit.put_raw(p, c, r) }-*/;
public <T extends JavaScriptObject>
void post(String content, AsyncCallback<T> cb) {
@@ -145,7 +145,7 @@
private static native
void post(String p, String c, JavaScriptObject r)
- /*-{ $wnd.Gerrit.post(p, c, r) }-*/;
+ /*-{ $wnd.Gerrit.post_raw(p, c, r) }-*/;
public <T extends JavaScriptObject>
void post(JavaScriptObject content, AsyncCallback<T> cb) {
@@ -154,14 +154,14 @@
private static native
void post(String p, JavaScriptObject c, JavaScriptObject r)
- /*-{ $wnd.Gerrit.post(p, c, r) }-*/;
+ /*-{ $wnd.Gerrit.post_raw(p, c, r) }-*/;
public void delete(AsyncCallback<NoContent> cb) {
delete(path(), wrap(cb));
}
private static native void delete(String p, JavaScriptObject r)
- /*-{ $wnd.Gerrit.del(p, r) }-*/;
+ /*-{ $wnd.Gerrit.del_raw(p, r) }-*/;
private static native <T extends JavaScriptObject>
JavaScriptObject wrap(AsyncCallback<T> b) /*-{
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 97fde9d..0701771 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -251,8 +251,6 @@
private static final char MIN_OPEN = 'a';
/** Database constant for {@link Status#NEW}. */
public static final char STATUS_NEW = 'n';
- /** Database constant for {@link Status#SUBMITTED}. */
- public static final char STATUS_SUBMITTED = 's';
/** Database constant for {@link Status#DRAFT}. */
public static final char STATUS_DRAFT = 'd';
/** Maximum database status constant for an open change. */
@@ -285,40 +283,13 @@
* <p>
* Changes in the NEW state can be moved to:
* <ul>
- * <li>{@link #SUBMITTED} - when the Submit Patch Set action is used;
+ * <li>{@link #MERGED} - when the Submit Patch Set action is used;
* <li>{@link #ABANDONED} - when the Abandon action is used.
* </ul>
*/
NEW(STATUS_NEW, ChangeStatus.NEW),
/**
- * Change is open, but has been submitted to the merge queue.
- *
- * <p>
- * A change enters the SUBMITTED state when an authorized user presses the
- * "submit" action through the web UI, requesting that Gerrit merge the
- * change's current patch set into the destination branch.
- *
- * <p>
- * Typically a change resides in the SUBMITTED for only a brief sub-second
- * period while the merge queue fires and the destination branch is updated.
- * However, if a dependency commit (a {@link PatchSetAncestor}, directly or
- * transitively) is not yet merged into the branch, the change will hang in
- * the SUBMITTED state indefinitely.
- *
- * <p>
- * Changes in the SUBMITTED state can be moved to:
- * <ul>
- * <li>{@link #NEW} - when a replacement patch set is supplied, OR when a
- * merge conflict is detected;
- * <li>{@link #MERGED} - when the change has been successfully merged into
- * the destination branch;
- * <li>{@link #ABANDONED} - when the Abandon action is used.
- * </ul>
- */
- SUBMITTED(STATUS_SUBMITTED, ChangeStatus.SUBMITTED),
-
- /**
* Change is a draft change that only consists of draft patchsets.
*
* <p>
@@ -475,7 +446,7 @@
/**
* First line of first patch set's commit message.
- *
+ * <p>
* Unlike {@link #subject}, this string does not change if future patch sets
* change the first line.
*/
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index a36d716..707664f 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -59,9 +59,9 @@
/**
* Special ref for GPG public keys used by {@link
- * com.google.gerrit.server.git.SignedPushPreReceiveHook}.
+ * com.google.gerrit.server.git.gpg.SignedPushPreReceiveHook}.
*/
- public static final String REFS_GPG_KEYS = REFS + "gpg-keys";
+ public static final String REFS_GPG_KEYS = "refs/meta/gpg-keys";
public static String fullName(String ref) {
return ref.startsWith(REFS) ? ref : REFS_HEADS + ref;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
index 465c9ba..93a3814 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -252,7 +252,7 @@
}
}
- public class AccessSectionInfo {
+ public static class AccessSectionInfo {
public Map<String, PermissionInfo> permissions;
public AccessSectionInfo(AccessSection section) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 1b420f7..1f8aa0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -152,7 +152,6 @@
@Override
public void submit() throws RestApiException {
SubmitInput in = new SubmitInput();
- in.waitForMerge = true;
submit(in);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index ab1cecb1..39c0712 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -210,7 +210,11 @@
validateCommit(git, refControl, c, me, ins);
updateRef(git, rw, c, change, ins.getPatchSet());
- change.setTopic(input.topic);
+ String topic = input.topic;
+ if (topic != null) {
+ topic = Strings.emptyToNull(topic.trim());
+ }
+ change.setTopic(topic);
ins.setDraft(input.status != null && input.status == ChangeStatus.DRAFT);
ins.setGroups(groups);
ins.insert();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index a840651..14aa5b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -244,7 +244,8 @@
null /*inserter*/,
canMerge,
accepted,
- key.load.dest).dryRun(tip, rev);
+ key.load.dest,
+ null).dryRun(tip, rev);
}
} finally {
key.load = null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 92bf3bf..b9e9b3e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -18,17 +18,12 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
-import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Table;
-import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.api.changes.SubmitInput;
@@ -38,34 +33,24 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.ChangeSet;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.git.MergeOp;
-import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.OrmRuntimeException;
import com.google.inject.Inject;
@@ -73,15 +58,12 @@
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -106,31 +88,19 @@
private static final String CLICK_FAILURE_TOOLTIP =
"Clicking the button would fail.";
- public enum Status {
- SUBMITTED, MERGED
- }
-
public static class Output {
- public Status status;
transient Change change;
- private Output(Status s, Change c) {
- status = s;
+ private Output(Change c) {
change = c;
}
}
- private final PersonIdent serverIdent;
private final Provider<ReviewDb> dbProvider;
private final GitRepositoryManager repoManager;
- private final IdentifiedUser.GenericFactory userFactory;
private final ChangeData.Factory changeDataFactory;
- private final ChangeUpdate.Factory updateFactory;
- private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
- private final MergeOp.Factory mergeOpFactory;
- private final ChangeIndexer indexer;
- private final LabelNormalizer labelNormalizer;
+ private final Provider<MergeOp> mergeOpProvider;
private final AccountsCollection accounts;
private final ChangesCollection changes;
private final String label;
@@ -141,34 +111,22 @@
private final Provider<InternalChangeQuery> queryProvider;
@Inject
- Submit(@GerritPersonIdent PersonIdent serverIdent,
- Provider<ReviewDb> dbProvider,
+ Submit(Provider<ReviewDb> dbProvider,
GitRepositoryManager repoManager,
- IdentifiedUser.GenericFactory userFactory,
ChangeData.Factory changeDataFactory,
- ChangeUpdate.Factory updateFactory,
- ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
- MergeOp.Factory mergeOpFactory,
+ Provider<MergeOp> mergeOpProvider,
AccountsCollection accounts,
ChangesCollection changes,
- ChangeIndexer indexer,
- LabelNormalizer labelNormalizer,
@GerritServerConfig Config cfg,
Provider<InternalChangeQuery> queryProvider) {
- this.serverIdent = serverIdent;
this.dbProvider = dbProvider;
this.repoManager = repoManager;
- this.userFactory = userFactory;
this.changeDataFactory = changeDataFactory;
- this.updateFactory = updateFactory;
- this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
- this.mergeOpFactory = mergeOpFactory;
+ this.mergeOpProvider = mergeOpProvider;
this.accounts = accounts;
this.changes = changes;
- this.indexer = indexer;
- this.labelNormalizer = labelNormalizer;
this.label = MoreObjects.firstNonNull(
Strings.emptyToNull(cfg.getString("change", null, "submitLabel")),
"Submit");
@@ -212,10 +170,19 @@
rsrc.getPatchSet().getRevision().get()));
}
- ChangeSet submittedChanges = ChangeSet.create(submit(rsrc, caller, false));
+ List<Change> changes;
+ if (submitWholeTopic && !Strings.isNullOrEmpty(change.getTopic())) {
+ changes = new ArrayList<>();
+ for (ChangeData cd : getChangesByTopic(change.getTopic())) {
+ changes.add(cd.change());
+ }
+ } else {
+ changes = Arrays.asList(change);
+ }
+ ChangeSet submittedChanges = ChangeSet.create(changes);
try {
- mergeOpFactory.create(submittedChanges, caller).merge(true);
+ mergeOpProvider.get().merge(submittedChanges, caller, true);
change = dbProvider.get().changes().get(change.getId());
} catch (NoSuchChangeException e) {
throw new OrmException("Submission failed", e);
@@ -225,10 +192,8 @@
throw new ResourceConflictException("change is deleted");
}
switch (change.getStatus()) {
- case SUBMITTED:
- return new Output(Status.SUBMITTED, change);
case MERGED:
- return new Output(Status.MERGED, change);
+ return new Output(change);
case NEW:
ChangeMessage msg = getConflictMessage(rsrc);
if (msg != null) {
@@ -374,203 +339,6 @@
.orNull();
}
- private Change submitToDatabase(final ReviewDb db, final Change.Id changeId,
- final Timestamp timestamp) throws OrmException,
- ResourceConflictException {
- Change ret = db.changes().atomicUpdate(changeId,
- new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isOpen()) {
- change.setStatus(Change.Status.SUBMITTED);
- change.setLastUpdatedOn(timestamp);
- return change;
- }
- return null;
- }
- });
- if (ret != null) {
- return ret;
- } else {
- throw new ResourceConflictException("change " + changeId + " is "
- + status(db.changes().get(changeId)));
- }
- }
-
- private Change submitThisChange(RevisionResource rsrc, IdentifiedUser caller,
- boolean force) throws ResourceConflictException, OrmException,
- IOException {
- ReviewDb db = dbProvider.get();
- ChangeData cd = changeDataFactory.create(db, rsrc.getControl());
- List<SubmitRecord> submitRecords = checkSubmitRule(cd,
- rsrc.getPatchSet(), force);
-
- final Timestamp timestamp = TimeUtil.nowTs();
- Change change = rsrc.getChange();
- ChangeUpdate update = updateFactory.create(rsrc.getControl(), timestamp);
- update.submit(submitRecords);
-
- db.changes().beginTransaction(change.getId());
- try {
- BatchMetaDataUpdate batch = approve(rsrc.getPatchSet().getId(),
- cd.changeControl(), update, caller, timestamp);
- // Write update commit after all normalized label commits.
- batch.write(update, new CommitBuilder());
- change = submitToDatabase(db, change.getId(), timestamp);
- db.commit();
- } finally {
- db.rollback();
- }
- indexer.index(db, change);
- return change;
- }
-
- private List<Change> submitWholeTopic(RevisionResource rsrc, IdentifiedUser caller,
- boolean force, String topic) throws ResourceConflictException, OrmException,
- IOException {
- Preconditions.checkNotNull(topic);
- final Timestamp timestamp = TimeUtil.nowTs();
-
- ReviewDb db = dbProvider.get();
- ChangeData cd = changeDataFactory.create(db, rsrc.getControl());
-
- List<ChangeData> changesByTopic = getChangesByTopic(topic);
- String problems = problemsForSubmittingChanges(changesByTopic, caller);
- if (problems != null) {
- throw new ResourceConflictException(problems);
- }
-
- Change change = rsrc.getChange();
- ChangeUpdate update = updateFactory.create(rsrc.getControl(), timestamp);
-
- List<SubmitRecord> submitRecords = checkSubmitRule(cd,
- rsrc.getPatchSet(), force);
- update.submit(submitRecords);
-
- db.changes().beginTransaction(change.getId());
- try {
- for (ChangeData c : changesByTopic) {
- BatchMetaDataUpdate batch = approve(c.currentPatchSet().getId(),
- c.changeControl(), update, caller, timestamp);
- // Write update commit after all normalized label commits.
- batch.write(update, new CommitBuilder());
- submitToDatabase(db, c.getId(), timestamp);
- }
- db.commit();
- } finally {
- db.rollback();
- }
- List<Change.Id> ids = new ArrayList<>(changesByTopic.size());
- List<Change> ret = new ArrayList<>(changesByTopic.size());
- for (ChangeData c : changesByTopic) {
- ids.add(c.getId());
- ret.add(c.change());
- }
- indexer.indexAsync(ids).checkedGet();
-
- return ret;
- }
-
- public List<Change> submit(RevisionResource rsrc, IdentifiedUser caller,
- boolean force) throws ResourceConflictException, OrmException,
- IOException {
- String topic = rsrc.getChange().getTopic();
- if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
- return submitWholeTopic(rsrc, caller, force, topic);
- } else {
- return Arrays.asList(submitThisChange(rsrc, caller, force));
- }
- }
-
- private BatchMetaDataUpdate approve(PatchSet.Id psId, ChangeControl control,
- ChangeUpdate update, IdentifiedUser caller, Timestamp timestamp)
- throws OrmException {
- Map<PatchSetApproval.Key, PatchSetApproval> byKey = Maps.newHashMap();
- for (PatchSetApproval psa :
- approvalsUtil.byPatchSet(dbProvider.get(), control, psId)) {
- if (!byKey.containsKey(psa.getKey())) {
- byKey.put(psa.getKey(), psa);
- }
- }
-
- PatchSetApproval submit = ApprovalsUtil.getSubmitter(psId, byKey.values());
- if (submit == null
- || !submit.getAccountId().equals(caller.getAccountId())) {
- submit = new PatchSetApproval(
- new PatchSetApproval.Key(
- psId,
- caller.getAccountId(),
- LabelId.SUBMIT),
- (short) 1, TimeUtil.nowTs());
- byKey.put(submit.getKey(), submit);
- }
- submit.setValue((short) 1);
- submit.setGranted(timestamp);
-
- // Flatten out existing approvals for this patch set based upon the current
- // permissions. Once the change is closed the approvals are not updated at
- // presentation view time, except for zero votes used to indicate a reviewer
- // was added. So we need to make sure votes are accurate now. This way if
- // permissions get modified in the future, historical records stay accurate.
- LabelNormalizer.Result normalized =
- labelNormalizer.normalize(control, byKey.values());
-
- // TODO(dborowitz): Don't use a label in notedb; just check when status
- // change happened.
- update.putApproval(submit.getLabel(), submit.getValue());
-
- dbProvider.get().patchSetApprovals().upsert(normalized.getNormalized());
- dbProvider.get().patchSetApprovals().delete(normalized.deleted());
-
- try {
- return saveToBatch(control, update, normalized, timestamp);
- } catch (IOException e) {
- throw new OrmException(e);
- }
- }
-
- private BatchMetaDataUpdate saveToBatch(ChangeControl ctl,
- ChangeUpdate callerUpdate, LabelNormalizer.Result normalized,
- Timestamp timestamp) throws IOException {
- Table<Account.Id, String, Optional<Short>> byUser = HashBasedTable.create();
- for (PatchSetApproval psa : normalized.updated()) {
- byUser.put(psa.getAccountId(), psa.getLabel(),
- Optional.of(psa.getValue()));
- }
- for (PatchSetApproval psa : normalized.deleted()) {
- byUser.put(psa.getAccountId(), psa.getLabel(), Optional.<Short> absent());
- }
-
- BatchMetaDataUpdate batch = callerUpdate.openUpdate();
- for (Account.Id accountId : byUser.rowKeySet()) {
- if (!accountId.equals(callerUpdate.getUser().getAccountId())) {
- ChangeUpdate update = updateFactory.create(
- ctl.forUser(userFactory.create(dbProvider, accountId)), timestamp);
- update.setSubject("Finalize approvals at submit");
- putApprovals(update, byUser.row(accountId));
-
- CommitBuilder commit = new CommitBuilder();
- commit.setCommitter(new PersonIdent(serverIdent, timestamp));
- batch.write(update, commit);
- }
- }
-
- putApprovals(callerUpdate,
- byUser.row(callerUpdate.getUser().getAccountId()));
- return batch;
- }
-
- private static void putApprovals(ChangeUpdate update,
- Map<String, Optional<Short>> approvals) {
- for (Map.Entry<String, Optional<Short>> e : approvals.entrySet()) {
- if (e.getValue().isPresent()) {
- update.putApproval(e.getKey(), e.getValue().get());
- } else {
- update.removeApproval(e.getKey());
- }
- }
- }
-
private List<SubmitRecord> checkSubmitRule(ChangeData cd,
PatchSet patchSet, boolean force)
throws ResourceConflictException, OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java
index 2dfd2de..d31805d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java
@@ -266,7 +266,7 @@
}
@AutoValue
- static abstract class PatchSetData {
+ abstract static class PatchSetData {
@VisibleForTesting
static PatchSetData create(ChangeData cd, PatchSet ps, RevCommit commit) {
return new AutoValue_WalkSorter_PatchSetData(cd, ps, commit);
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 82372cc..c8360a9 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
@@ -74,14 +74,12 @@
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitModule;
-import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.NotesBranchUtil;
import com.google.gerrit.server.git.ReceivePackInitializer;
-import com.google.gerrit.server.git.SignedPushModule;
-import com.google.gerrit.server.git.SubmoduleOp;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.git.gpg.SignedPushModule;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.git.validators.MergeValidationListener;
@@ -233,8 +231,6 @@
bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class)
.toProvider(DisableReverseDnsLookupProvider.class).in(SINGLETON);
- factory(MergeOp.Factory.class);
- factory(SubmoduleOp.Factory.class);
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
bind(ChangeControl.GenericFactory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index e4a8c34..5934759 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -14,8 +14,10 @@
package com.google.gerrit.server.config;
+import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Optional;
+import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GitwebType;
@@ -30,7 +32,7 @@
import com.google.gerrit.server.change.ArchiveFormat;
import com.google.gerrit.server.change.GetArchive;
import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.git.SignedPushModule;
+import com.google.gerrit.server.git.gpg.SignedPushModule;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -230,9 +232,18 @@
info.allUsers = allUsersName.get();
info.reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
info.reportBugText = cfg.getString("gerrit", null, "reportBugText");
+ info.docUrl = getDocUrl(cfg);
return info;
}
+ private String getDocUrl(Config cfg) {
+ String docUrl = cfg.getString("gerrit", null, "docUrl");
+ if (Strings.isNullOrEmpty(docUrl)) {
+ return null;
+ }
+ return CharMatcher.is('/').trimTrailingFrom(docUrl) + '/';
+ }
+
private GitwebInfo getGitwebInfo(GitwebConfig cfg) {
if (cfg.getUrl() == null || cfg.getGitwebType() == null) {
return null;
@@ -336,6 +347,7 @@
public static class GerritInfo {
public String allProjects;
public String allUsers;
+ public String docUrl;
public String reportBugUrl;
public String reportBugText;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index 3deab11..b907866 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -215,6 +215,34 @@
}
/**
+ * Called after reading the project config value. To modify the value before
+ * returning it to the client, override this method and return the modified
+ * value. Default implementation returns the same value.
+ *
+ * @param project the project.
+ * @param value the actual value of the config entry (computed out of the
+ * configured value, the inherited value and the default value).
+ * @return the modified value.
+ */
+ public String onRead(ProjectState project, String value) {
+ return value;
+ }
+
+ /**
+ * Called after reading the project config value of type ARRAY. To modify the
+ * values before returning it to the client, override this method and return
+ * the modified values. Default implementation returns the same values.
+ *
+ * @param project the project.
+ * @param values the actual values of the config entry (computed out of the
+ * configured value, the inherited value and the default value).
+ * @return the modified values.
+ */
+ public List<String> onRead(ProjectState project, List<String> values) {
+ return values;
+ }
+
+ /**
* Called after a project config is updated.
*
* @param project project name.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
index b906ba7..6c3b499 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
@@ -33,18 +33,22 @@
ImmutableSetMultimap.builder();
ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb =
ImmutableSetMultimap.builder();
+ ImmutableSetMultimap.Builder<Branch.NameKey, Change.Id> cbb =
+ ImmutableSetMultimap.builder();
for (Change c : changes) {
- Project.NameKey project = c.getDest().getParentKey();
+ Branch.NameKey branch = c.getDest();
+ Project.NameKey project = branch.getParentKey();
pb.add(project);
- bb.add(c.getDest());
+ bb.add(branch);
ib.add(c.getId());
- pbb.put(project, c.getDest());
+ pbb.put(project, branch);
pcb.put(project, c.getId());
+ cbb.put(branch, c.getId());
}
return new AutoValue_ChangeSet(pb.build(), bb.build(),
- ib.build(), pbb.build(), pcb.build());
+ ib.build(), pbb.build(), pcb.build(), cbb.build());
}
public static ChangeSet create(Change change) {
@@ -58,6 +62,8 @@
branchesByProject();
public abstract ImmutableSetMultimap<Project.NameKey, Change.Id>
changesByProject();
+ public abstract ImmutableSetMultimap<Branch.NameKey, Change.Id>
+ changesByBranch();
@Override
public int hashCode() {
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 ef2cef3..cc2ded6 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
@@ -20,10 +20,12 @@
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Table;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.SubmitRecord;
@@ -34,6 +36,7 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
@@ -43,15 +46,14 @@
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.GerritRequestModule;
import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
import com.google.gerrit.server.git.strategy.SubmitStrategy;
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
import com.google.gerrit.server.git.validators.MergeValidationException;
@@ -82,13 +84,13 @@
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Provides;
-import com.google.inject.assistedinject.Assisted;
import com.google.inject.servlet.RequestScoped;
import com.jcraft.jsch.HostKey;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -105,6 +107,7 @@
import java.io.IOException;
import java.net.SocketAddress;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -131,10 +134,6 @@
* be merged cleanly.
*/
public class MergeOp {
- public interface Factory {
- MergeOp create(ChangeSet changes, IdentifiedUser caller);
- }
-
private static final Logger log = LoggerFactory.getLogger(MergeOp.class);
private final AccountCache accountCache;
@@ -148,25 +147,24 @@
private final GitReferenceUpdated gitRefUpdated;
private final GitRepositoryManager repoManager;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
+ private final LabelNormalizer labelNormalizer;
private final MergedSender.Factory mergedSenderFactory;
private final MergeSuperSet mergeSuperSet;
private final MergeValidators.Factory mergeValidatorsFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ProjectCache projectCache;
- private final Provider<InternalChangeQuery> queryProvider;
+ private final InternalChangeQuery internalChangeQuery;
private final SchemaFactory<ReviewDb> schemaFactory;
- private final Submit submit;
+ private final PersonIdent serverIdent;
private final SubmitStrategyFactory submitStrategyFactory;
- private final SubmoduleOp.Factory subOpFactory;
+ private final Provider<SubmoduleOp> subOpProvider;
private final TagCache tagCache;
private final WorkQueue workQueue;
+ private final Map<Change.Id, List<SubmitRecord>> records;
private final Map<Change.Id, CodeReviewCommit> commits;
- private final List<Change> toUpdate;
private final PerThreadRequestScope.Scoper threadScoper;
- private final ChangeSet changes;
- private final IdentifiedUser caller;
- private final String logPrefix;
+ private String logPrefix;
private ProjectState destProject;
private ReviewDb db;
@@ -192,20 +190,19 @@
GitReferenceUpdated gitRefUpdated,
GitRepositoryManager repoManager,
IdentifiedUser.GenericFactory identifiedUserFactory,
+ LabelNormalizer labelNormalizer,
MergedSender.Factory mergedSenderFactory,
MergeSuperSet mergeSuperSet,
MergeValidators.Factory mergeValidatorsFactory,
PatchSetInfoFactory patchSetInfoFactory,
ProjectCache projectCache,
- Provider<InternalChangeQuery> queryProvider,
+ InternalChangeQuery internalChangeQuery,
SchemaFactory<ReviewDb> schemaFactory,
- Submit submit,
+ @GerritPersonIdent PersonIdent serverIdent,
SubmitStrategyFactory submitStrategyFactory,
- SubmoduleOp.Factory subOpFactory,
+ Provider<SubmoduleOp> subOpProvider,
TagCache tagCache,
- WorkQueue workQueue,
- @Assisted ChangeSet changes,
- @Assisted IdentifiedUser caller) {
+ WorkQueue workQueue) {
this.accountCache = accountCache;
this.approvalsUtil = approvalsUtil;
this.changeControlFactory = changeControlFactory;
@@ -217,26 +214,24 @@
this.gitRefUpdated = gitRefUpdated;
this.repoManager = repoManager;
this.identifiedUserFactory = identifiedUserFactory;
+ this.labelNormalizer = labelNormalizer;
this.mergedSenderFactory = mergedSenderFactory;
this.mergeSuperSet = mergeSuperSet;
this.mergeValidatorsFactory = mergeValidatorsFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.projectCache = projectCache;
- this.queryProvider = queryProvider;
+ this.internalChangeQuery = internalChangeQuery;
this.schemaFactory = schemaFactory;
- this.submit = submit;
+ this.serverIdent = serverIdent;
this.submitStrategyFactory = submitStrategyFactory;
- this.subOpFactory = subOpFactory;
+ this.subOpProvider = subOpProvider;
this.tagCache = tagCache;
this.workQueue = workQueue;
- this.changes = changes;
- this.caller = caller;
commits = new HashMap<>();
- toUpdate = Lists.newArrayList();
- logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
-
pendingRefUpdates = new HashMap<>();
openBranches = new HashMap<>();
+ pendingRefUpdates = new HashMap<>();
+ records = new HashMap<>();
mergeTips = new HashMap<>();
Injector child = injector.createChildInjector(new AbstractModule() {
@@ -393,45 +388,19 @@
throws ResourceConflictException, OrmException {
for (Change.Id id : cs.ids()) {
ChangeData cd = changeDataFactory.create(db, id);
- if (cd.change().getStatus() != Change.Status.NEW
- && cd.change().getStatus() != Change.Status.SUBMITTED){
+ if (cd.change().getStatus() != Change.Status.NEW){
throw new OrmException("Change " + cd.change().getChangeId()
+ " is in state " + cd.change().getStatus());
} else {
- checkSubmitRule(cd);
+ records.put(cd.change().getId(), checkSubmitRule(cd));
}
}
}
- // For historic reasons we will first go into the submitted state
- // TODO(sbeller): remove this when we get rid of Change.Status.SUBMITTED
- private void submitAllChanges(ChangeSet cs, boolean force)
- throws OrmException, ResourceConflictException, IOException {
- for (Change.Id id : cs.ids()) {
- ChangeData cd = changeDataFactory.create(db, id);
- switch (cd.change().getStatus()) {
- case ABANDONED:
- throw new ResourceConflictException("Change " + cd.getId() +
- " was abandoned while processing this change set");
- case DRAFT:
- throw new ResourceConflictException("Cannot submit draft " + cd.getId());
- case NEW:
- RevisionResource rsrc =
- new RevisionResource(new ChangeResource(cd.changeControl(), null),
- cd.currentPatchSet());
- logDebug("Submitting change id {}", cd.change().getId());
- submit.submit(rsrc, caller, force);
- break;
- case MERGED:
- // we're racing here, but having it already merged is fine.
- case SUBMITTED:
- // ok
- }
- }
- }
-
- public void merge(boolean checkPermissions) throws NoSuchChangeException,
+ public void merge(ChangeSet changes, IdentifiedUser caller,
+ boolean checkPermissions) throws NoSuchChangeException,
OrmException, ResourceConflictException {
+ logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
logDebug("Beginning merge of {}", changes);
try {
openSchema();
@@ -439,17 +408,11 @@
ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes);
logDebug("Calculated to merge {}", cs);
if (checkPermissions) {
- logDebug("Submitting all calculated changes while "
- + "enforcing submit rules");
- submitAllChanges(cs, false);
logDebug("Checking permissions");
checkPermissions(cs);
- } else {
- logDebug("Submitting all calculated changes ignoring submit rules");
- submitAllChanges(cs, true);
}
try {
- integrateIntoHistory(cs);
+ integrateIntoHistory(cs, caller);
} catch (MergeException e) {
logError("Merge Conflict", e);
throw new ResourceConflictException("Merge Conflict", e);
@@ -464,10 +427,10 @@
}
}
- private void integrateIntoHistory(ChangeSet cs)
+ private void integrateIntoHistory(ChangeSet cs, IdentifiedUser caller)
throws MergeException, NoSuchChangeException, ResourceConflictException {
- logDebug("Beginning merge attempt on {}", changes);
- Map<Branch.NameKey, ListMultimap<SubmitType, Change>> toSubmit =
+ logDebug("Beginning merge attempt on {}", cs);
+ Map<Branch.NameKey, ListMultimap<SubmitType, ChangeData>> toSubmit =
new HashMap<>();
try {
openSchema();
@@ -476,30 +439,32 @@
openRepository(project);
for (Branch.NameKey branch : cs.branchesByProject().get(project)) {
setDestProject(branch);
- ListMultimap<SubmitType, Change> submitting =
- validateChangeList(queryProvider.get().submitted(branch));
+
+ List<ChangeData> cds = new ArrayList<>();
+ for (Change.Id id : cs.changesByBranch().get(branch)) {
+ cds.add(changeDataFactory.create(db, id));
+ }
+ ListMultimap<SubmitType, ChangeData> submitting =
+ validateChangeList(cds);
toSubmit.put(branch, submitting);
Set<SubmitType> submitTypes = new HashSet<>(submitting.keySet());
for (SubmitType submitType : submitTypes) {
SubmitStrategy strategy = createStrategy(branch, submitType,
- getBranchTip(branch));
+ getBranchTip(branch), caller);
MergeTip mergeTip = preMerge(strategy, submitting.get(submitType),
getBranchTip(branch));
mergeTips.put(branch, mergeTip);
- if (submitType != SubmitType.CHERRY_PICK) {
- // For cherry picking we have relaxed atomic guarantees
- // as traditionally Gerrit kept going cherry picking if one
- // failed. We want to keep it for now.
- updateChangeStatus(submitting.get(submitType), branch, true);
- }
+ updateChangeStatus(submitting.get(submitType), branch,
+ true, caller);
}
inserter.flush();
}
closeRepository();
}
logDebug("Write out the new branch tips");
+ SubmoduleOp subOp = subOpProvider.get();
for (Project.NameKey project : cs.projects()) {
openRepository(project);
for (Branch.NameKey branch : cs.branchesByProject().get(project)) {
@@ -508,11 +473,11 @@
pendingRefUpdates.remove(branch);
setDestProject(branch);
- ListMultimap<SubmitType, Change> submitting = toSubmit.get(branch);
+ ListMultimap<SubmitType, ChangeData> submitting = toSubmit.get(branch);
for (SubmitType submitType : submitting.keySet()) {
- updateChangeStatus(submitting.get(submitType), branch, false);
- updateSubscriptions(branch, submitting.get(submitType),
- getBranchTip(branch));
+ updateChangeStatus(submitting.get(submitType), branch,
+ false, caller);
+ updateSubmoduleSubscriptions(subOp, branch, getBranchTip(branch));
}
if (update != null) {
fireRefUpdated(branch, update);
@@ -520,6 +485,7 @@
}
closeRepository();
}
+ updateSuperProjects(subOp, cs.branches());
checkState(pendingRefUpdates.isEmpty(), "programmer error: "
+ "pending ref update list not emptied");
} catch (NoSuchProjectException noProject) {
@@ -536,15 +502,15 @@
}
private MergeTip preMerge(SubmitStrategy strategy,
- List<Change> submitted, CodeReviewCommit branchTip)
- throws MergeException {
+ List<ChangeData> submitted, CodeReviewCommit branchTip)
+ throws MergeException, OrmException {
logDebug("Running submit strategy {} for {} commits {}",
strategy.getClass().getSimpleName(), submitted.size(), submitted);
List<CodeReviewCommit> toMerge = new ArrayList<>(submitted.size());
- for (Change c : submitted) {
- CodeReviewCommit commit = commits.get(c.getId());
+ for (ChangeData cd : submitted) {
+ CodeReviewCommit commit = commits.get(cd.change().getId());
checkState(commit != null,
- "commit for %s not found by validateChangeList", c.getId());
+ "commit for %s not found by validateChangeList", cd.change().getId());
toMerge.add(commit);
}
MergeTip mergeTip = strategy.run(branchTip, toMerge);
@@ -555,10 +521,10 @@
}
private SubmitStrategy createStrategy(Branch.NameKey destBranch,
- SubmitType submitType, CodeReviewCommit branchTip)
+ SubmitType submitType, CodeReviewCommit branchTip, IdentifiedUser caller)
throws MergeException, NoSuchProjectException {
return submitStrategyFactory.create(submitType, db, repo, rw, inserter,
- canMergeFlag, getAlreadyAccepted(branchTip), destBranch);
+ canMergeFlag, getAlreadyAccepted(branchTip), destBranch, caller);
}
private void openRepository(Project.NameKey name)
@@ -659,10 +625,10 @@
return alreadyAccepted;
}
- private ListMultimap<SubmitType, Change> validateChangeList(
+ private ListMultimap<SubmitType, ChangeData> validateChangeList(
List<ChangeData> submitted) throws MergeException {
logDebug("Validating {} changes", submitted.size());
- ListMultimap<SubmitType, Change> toSubmit = ArrayListMultimap.create();
+ ListMultimap<SubmitType, ChangeData> toSubmit = ArrayListMultimap.create();
Map<String, Ref> allRefs;
try {
@@ -687,14 +653,13 @@
throw new MergeException("Failed to validate changes", e);
}
Change.Id changeId = cd.getId();
- if (chg.getStatus() != Change.Status.SUBMITTED) {
- logDebug("Change {} is not submitted: {}", changeId, chg.getStatus());
+ if (chg.getStatus() != Change.Status.NEW) {
+ logDebug("Change {} is not new: {}", changeId, chg.getStatus());
continue;
}
if (chg.currentPatchSetId() == null) {
logError("Missing current patch set on change " + changeId);
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
- toUpdate.add(chg);
continue;
}
@@ -709,7 +674,6 @@
|| ps.getRevision().get() == null) {
logError("Missing patch set or revision on change " + changeId);
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
- toUpdate.add(chg);
continue;
}
@@ -720,7 +684,6 @@
} catch (IllegalArgumentException iae) {
logError("Invalid revision on patch set " + ps.getId());
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
- toUpdate.add(chg);
continue;
}
@@ -737,7 +700,6 @@
logError("Revision " + idstr + " of patch set " + ps.getId()
+ " is not contained in any ref");
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
- toUpdate.add(chg);
continue;
}
@@ -747,7 +709,6 @@
} catch (IOException e) {
logError("Invalid commit " + idstr + " on patch set " + ps.getId(), e);
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
- toUpdate.add(chg);
continue;
}
@@ -764,7 +725,6 @@
logDebug("Revision {} of patch set {} failed validation: {}",
idstr, ps.getId(), mve.getStatus());
commit.setStatusCode(mve.getStatus());
- toUpdate.add(chg);
continue;
}
@@ -774,12 +734,11 @@
logError("No submit type for revision " + idstr + " of patch set "
+ ps.getId());
commit.setStatusCode(CommitMergeStatus.NO_SUBMIT_TYPE);
- toUpdate.add(chg);
continue;
}
commit.add(canMergeFlag);
- toSubmit.put(submitType, chg);
+ toSubmit.put(submitType, cd);
}
logDebug("Submitting on this run: {}", toSubmit);
return toSubmit;
@@ -898,9 +857,10 @@
return "";
}
- private void updateChangeStatus(List<Change> submitted,
- Branch.NameKey destBranch, boolean dryRun)
- throws NoSuchChangeException, MergeException, ResourceConflictException {
+ private void updateChangeStatus(List<ChangeData> submitted,
+ Branch.NameKey destBranch, boolean dryRun, IdentifiedUser caller)
+ throws NoSuchChangeException, MergeException, ResourceConflictException,
+ OrmException {
if (!dryRun) {
logDebug("Updating change status for {} changes", submitted.size());
} else {
@@ -908,7 +868,8 @@
submitted.size());
}
MergeTip mergeTip = mergeTips.get(destBranch);
- for (Change c : submitted) {
+ for (ChangeData cd : submitted) {
+ Change c = cd.change();
CodeReviewCommit commit = commits.get(c.getId());
CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
if (s == null) {
@@ -920,6 +881,14 @@
continue;
}
+ if (!dryRun) {
+ try {
+ setApproval(cd, caller);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
String txt = s.getMessage();
logDebug("Status of change {} ({}) on {}: {}", c.getId(), commit.name(),
c.getDest(), s);
@@ -993,26 +962,32 @@
}
}
- private void updateSubscriptions(Branch.NameKey destBranch,
- List<Change> submitted, CodeReviewCommit branchTip) {
+ private void updateSubmoduleSubscriptions(SubmoduleOp subOp,
+ Branch.NameKey destBranch, CodeReviewCommit branchTip) {
MergeTip mergeTip = mergeTips.get(destBranch);
if (mergeTip != null
&& (branchTip == null || branchTip != mergeTip.getCurrentTip())) {
- logDebug("Updating submodule subscriptions for {} changes",
- submitted.size());
- SubmoduleOp subOp =
- subOpFactory.create(destBranch, mergeTip.getCurrentTip(), rw, repo,
- destProject.getProject(), submitted, commits,
- getAccount(mergeTip.getCurrentTip()));
+ logDebug("Updating submodule subscriptions for branch {}", destBranch);
try {
- subOp.update();
+ subOp.updateSubmoduleSubscriptions(db, destBranch);
} catch (SubmoduleException e) {
- logError("The gitLinks were not updated according to the subscriptions",
- e);
+ logError("The submodule subscriptions were not updated according"
+ + "to the .gitmodules files", e);
}
}
}
+ private void updateSuperProjects(SubmoduleOp subOp,
+ Set<Branch.NameKey> branches) {
+ logDebug("Updating superprojects");
+ try {
+ subOp.updateSuperProjects(db, branches);
+ } catch (SubmoduleException e) {
+ logError("The gitlinks were not updated according to the "
+ + "subscriptions", e);
+ }
+ }
+
private ChangeMessage message(Change c, String body) {
String uuid;
try {
@@ -1104,19 +1079,135 @@
});
}
+ private void setApproval(ChangeData cd, IdentifiedUser user)
+ throws OrmException, IOException {
+ Timestamp timestamp = TimeUtil.nowTs();
+ ChangeControl control = cd.changeControl();
+ PatchSet.Id psId = cd.currentPatchSet().getId();
+ PatchSet.Id psIdNewRev = commits.get(cd.change().getId())
+ .change().currentPatchSetId();
+
+ logDebug("Add approval for " + cd + " from user " + user);
+ ChangeUpdate update = updateFactory.create(control, timestamp);
+ List<SubmitRecord> record = records.get(cd.change().getId());
+ if (record != null) {
+ update.merge(record);
+ }
+ db.changes().beginTransaction(cd.change().getId());
+ try {
+ BatchMetaDataUpdate batch = approve(control, psId, user,
+ update, timestamp);
+ batch.write(update, new CommitBuilder());
+
+ // If the submit strategy created a new revision (rebase, cherry-pick)
+ // approve that as well
+ if (!psIdNewRev.equals(psId)) {
+ batch = approve(control, psIdNewRev, user,
+ update, timestamp);
+ // Write update commit after all normalized label commits.
+ batch.write(update, new CommitBuilder());
+ }
+ db.commit();
+ } finally {
+ db.rollback();
+ }
+ indexer.index(db, cd.change());
+ }
+
+ private BatchMetaDataUpdate approve(ChangeControl control, PatchSet.Id psId,
+ IdentifiedUser user, ChangeUpdate update, Timestamp timestamp)
+ throws OrmException {
+ Map<PatchSetApproval.Key, PatchSetApproval> byKey = Maps.newHashMap();
+ for (PatchSetApproval psa :
+ approvalsUtil.byPatchSet(db, control, psId)) {
+ if (!byKey.containsKey(psa.getKey())) {
+ byKey.put(psa.getKey(), psa);
+ }
+ }
+
+ PatchSetApproval submit = new PatchSetApproval(
+ new PatchSetApproval.Key(
+ psId,
+ user.getAccountId(),
+ LabelId.SUBMIT),
+ (short) 1, TimeUtil.nowTs());
+ byKey.put(submit.getKey(), submit);
+ submit.setValue((short) 1);
+ submit.setGranted(timestamp);
+
+ // Flatten out existing approvals for this patch set based upon the current
+ // permissions. Once the change is closed the approvals are not updated at
+ // presentation view time, except for zero votes used to indicate a reviewer
+ // was added. So we need to make sure votes are accurate now. This way if
+ // permissions get modified in the future, historical records stay accurate.
+ LabelNormalizer.Result normalized =
+ labelNormalizer.normalize(control, byKey.values());
+
+ // TODO(dborowitz): Don't use a label in notedb; just check when status
+ // change happened.
+ update.putApproval(submit.getLabel(), submit.getValue());
+ logDebug("Adding submit label " + submit);
+
+ db.patchSetApprovals().upsert(normalized.getNormalized());
+ db.patchSetApprovals().delete(normalized.deleted());
+
+ try {
+ return saveToBatch(control, update, normalized, timestamp);
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ private BatchMetaDataUpdate saveToBatch(ChangeControl ctl,
+ ChangeUpdate callerUpdate, LabelNormalizer.Result normalized,
+ Timestamp timestamp) throws IOException {
+ Table<Account.Id, String, Optional<Short>> byUser = HashBasedTable.create();
+ for (PatchSetApproval psa : normalized.updated()) {
+ byUser.put(psa.getAccountId(), psa.getLabel(),
+ Optional.of(psa.getValue()));
+ }
+ for (PatchSetApproval psa : normalized.deleted()) {
+ byUser.put(psa.getAccountId(), psa.getLabel(), Optional.<Short> absent());
+ }
+
+ BatchMetaDataUpdate batch = callerUpdate.openUpdate();
+ for (Account.Id accountId : byUser.rowKeySet()) {
+ if (!accountId.equals(callerUpdate.getUser().getAccountId())) {
+ ChangeUpdate update = updateFactory.create(
+ ctl.forUser(identifiedUserFactory.create(accountId)), timestamp);
+ update.setSubject("Finalize approvals at submit");
+ putApprovals(update, byUser.row(accountId));
+
+ CommitBuilder commit = new CommitBuilder();
+ commit.setCommitter(new PersonIdent(serverIdent, timestamp));
+ batch.write(update, commit);
+ }
+ }
+
+ putApprovals(callerUpdate,
+ byUser.row(callerUpdate.getUser().getAccountId()));
+ return batch;
+ }
+
+ private static void putApprovals(ChangeUpdate update,
+ Map<String, Optional<Short>> approvals) {
+ for (Map.Entry<String, Optional<Short>> e : approvals.entrySet()) {
+ if (e.getValue().isPresent()) {
+ update.putApproval(e.getKey(), e.getValue().get());
+ } else {
+ update.removeApproval(e.getKey());
+ }
+ }
+ }
+
private void sendMergedEmail(final Change c, final PatchSetApproval from) {
workQueue.getDefaultQueue()
.submit(new Runnable() {
@Override
public void run() {
PatchSet patchSet;
- try {
- ReviewDb reviewDb = schemaFactory.open();
- try {
- patchSet = reviewDb.patchSets().get(c.currentPatchSetId());
- } finally {
- reviewDb.close();
- }
+ try (ReviewDb reviewDb = schemaFactory.open()) {
+ patchSet = reviewDb.patchSets().get(c.currentPatchSetId());
} catch (Exception e) {
logError("Cannot send email for submitted patch set " + c.getId(), e);
return;
@@ -1208,8 +1299,7 @@
throws NoSuchChangeException {
try {
openSchema();
- for (ChangeData cd
- : queryProvider.get().byProjectOpen(destProject)) {
+ for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
abandonOneChange(cd.change());
}
db.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index da38a58..c5ead54 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -71,16 +71,13 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
-import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
-import java.util.TimeZone;
public class MergeUtil {
private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
@@ -167,10 +164,6 @@
return result;
}
- public PatchSetApproval getSubmitter(CodeReviewCommit c) {
- return approvalsUtil.getSubmitter(db.get(), c.notes(), c.getPatchsetId());
- }
-
public RevCommit createCherryPickFromCommit(Repository repo,
ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
PersonIdent cherryPickCommitterIdent, String commitMsg, RevWalk rw)
@@ -347,52 +340,6 @@
return false;
}
- public PersonIdent computeMergeCommitAuthor(final PersonIdent myIdent,
- final RevWalk rw, final List<CodeReviewCommit> codeReviewCommits) {
- PatchSetApproval submitter = null;
- for (final CodeReviewCommit c : codeReviewCommits) {
- PatchSetApproval s = getSubmitter(c);
- if (submitter == null
- || (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
- submitter = s;
- }
- }
-
- // Try to use the submitter's identity for the merge commit author.
- // If all of the commits being merged are created by the submitter,
- // prefer the identity line they used in the commits rather than the
- // preferred identity stored in the user account. This way the Git
- // commit records are more consistent internally.
- //
- PersonIdent authorIdent;
- if (submitter != null) {
- IdentifiedUser who =
- identifiedUserFactory.create(submitter.getAccountId());
- Set<String> emails = new HashSet<>();
- for (RevCommit c : codeReviewCommits) {
- try {
- rw.parseBody(c);
- } catch (IOException e) {
- log.warn("Cannot parse commit " + c.name(), e);
- continue;
- }
- emails.add(c.getAuthorIdent().getEmailAddress());
- }
-
- final Timestamp dt = submitter.getGranted();
- final TimeZone tz = myIdent.getTimeZone();
- if (emails.size() == 1 && who.hasEmailAddress(emails.iterator().next())) {
- authorIdent =
- new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
- } else {
- authorIdent = who.newCommitterIdent(dt, tz);
- }
- } else {
- authorIdent = myIdent;
- }
- return authorIdent;
- }
-
public boolean canMerge(final MergeSorter mergeSorter,
final Repository repo, final CodeReviewCommit mergeTip,
final CodeReviewCommit toMerge)
@@ -497,16 +444,15 @@
};
}
- public CodeReviewCommit mergeOneCommit(final PersonIdent myIdent,
- final Repository repo, final RevWalk rw, final ObjectInserter inserter,
- final RevFlag canMergeFlag, final Branch.NameKey destBranch,
- final CodeReviewCommit mergeTip, final CodeReviewCommit n)
- throws MergeException {
+ public CodeReviewCommit mergeOneCommit(PersonIdent author,
+ PersonIdent committer, Repository repo, RevWalk rw,
+ ObjectInserter inserter, RevFlag canMergeFlag, Branch.NameKey destBranch,
+ CodeReviewCommit mergeTip, CodeReviewCommit n) throws MergeException {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
- return writeMergeCommit(myIdent, rw, inserter, canMergeFlag, destBranch,
- mergeTip, m.getResultTreeId(), n);
+ return writeMergeCommit(author, committer, rw, inserter, canMergeFlag,
+ destBranch, mergeTip, m.getResultTreeId(), n);
} else {
failed(rw, canMergeFlag, mergeTip, n, CommitMergeStatus.PATH_CONFLICT);
}
@@ -549,12 +495,12 @@
return failed;
}
- public CodeReviewCommit writeMergeCommit(final PersonIdent myIdent,
- final RevWalk rw, final ObjectInserter inserter,
- final RevFlag canMergeFlag, final Branch.NameKey destBranch,
- final CodeReviewCommit mergeTip, final ObjectId treeId,
- final CodeReviewCommit n) throws IOException,
- MissingObjectException, IncorrectObjectTypeException {
+ public CodeReviewCommit writeMergeCommit(PersonIdent author,
+ PersonIdent committer, RevWalk rw, ObjectInserter inserter,
+ RevFlag canMergeFlag, Branch.NameKey destBranch,
+ CodeReviewCommit mergeTip, ObjectId treeId, CodeReviewCommit n)
+ throws IOException, MissingObjectException,
+ IncorrectObjectTypeException {
final List<CodeReviewCommit> merged = new ArrayList<>();
rw.resetRetain(canMergeFlag);
rw.markStart(n);
@@ -582,13 +528,11 @@
}
}
- PersonIdent authorIdent = computeMergeCommitAuthor(myIdent, rw, merged);
-
final CommitBuilder mergeCommit = new CommitBuilder();
mergeCommit.setTreeId(treeId);
mergeCommit.setParentIds(mergeTip, n);
- mergeCommit.setAuthor(authorIdent);
- mergeCommit.setCommitter(myIdent);
+ mergeCommit.setAuthor(author);
+ mergeCommit.setCommitter(committer);
mergeCommit.setMessage(msgbuf.toString());
CodeReviewCommit mergeResult =
@@ -691,7 +635,7 @@
return id;
}
- public PatchSetApproval markCleanMerges(final RevWalk rw,
+ public void markCleanMerges(final RevWalk rw,
final RevFlag canMergeFlag, final CodeReviewCommit mergeTip,
final Set<RevCommit> alreadyAccepted) throws MergeException {
if (mergeTip == null) {
@@ -699,12 +643,10 @@
// at the start of the merge process. We also elected to merge nothing,
// probably due to missing dependencies. Nothing was cleanly merged.
//
- return null;
+ return;
}
try {
- PatchSetApproval submitApproval = null;
-
rw.resetRetain(canMergeFlag);
rw.sort(RevSort.TOPO);
rw.sort(RevSort.REVERSE, true);
@@ -717,13 +659,8 @@
while ((c = (CodeReviewCommit) rw.next()) != null) {
if (c.getPatchsetId() != null) {
c.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
- if (submitApproval == null) {
- submitApproval = getSubmitter(c);
- }
}
}
-
- return submitApproval;
} catch (IOException e) {
throw new MergeException("Cannot mark clean merges", e);
}
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 f1f89a5..c21e8c0 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
@@ -325,9 +325,9 @@
private SetMultimap<ObjectId, Ref> refsById;
private Map<String, Ref> allRefs;
- private final SubmoduleOp.Factory subOpFactory;
+ private final Provider<SubmoduleOp> subOpProvider;
private final Provider<Submit> submitProvider;
- private final MergeOp.Factory mergeFactory;
+ private final Provider<MergeOp> mergeOpProvider;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final NotesMigration notesMigration;
private final ChangeEditUtil editUtil;
@@ -375,9 +375,9 @@
ReceiveConfig config,
@Assisted final ProjectControl projectControl,
@Assisted final Repository repo,
- final SubmoduleOp.Factory subOpFactory,
+ final Provider<SubmoduleOp> subOpProvider,
final Provider<Submit> submitProvider,
- final MergeOp.Factory mergeFactory,
+ final Provider<MergeOp> mergeOpProvider,
final ChangeKindCache changeKindCache,
final DynamicMap<ProjectConfigEntry> pluginConfigEntries,
final NotesMigration notesMigration,
@@ -422,9 +422,9 @@
this.rp = new ReceivePack(repo);
this.rejectCommits = BanCommit.loadRejectCommitsMap(repo, rp.getRevWalk());
- this.subOpFactory = subOpFactory;
+ this.subOpProvider = subOpProvider;
this.submitProvider = submitProvider;
- this.mergeFactory = mergeFactory;
+ this.mergeOpProvider = mergeOpProvider;
this.pluginConfigEntries = pluginConfigEntries;
this.notesMigration = notesMigration;
@@ -598,6 +598,7 @@
rp.sendMessage(COMMAND_REJECTION_MESSAGE_FOOTER);
}
+ Set<Branch.NameKey> branches = Sets.newHashSet();
for (final ReceiveCommand c : commands) {
if (c.getResult() == OK) {
if (c.getType() == ReceiveCommand.Type.UPDATE) { // aka fast-forward
@@ -613,6 +614,8 @@
case UPDATE:
case UPDATE_NONFASTFORWARD:
autoCloseChanges(c);
+ branches.add(new Branch.NameKey(project.getNameKey(),
+ c.getRefName()));
break;
case DELETE:
@@ -651,6 +654,16 @@
}
}
}
+ // Update superproject gitlinks if required.
+ SubmoduleOp op = subOpProvider.get();
+ try {
+ op.updateSubmoduleSubscriptions(db, branches);
+ op.updateSuperProjects(db, branches);
+ } catch (SubmoduleException e) {
+ log.error("Can't update submodule subscriptions "
+ + "or update the superprojects", e);
+ }
+
closeProgress.end();
commandProgress.end();
progress.end();
@@ -785,7 +798,7 @@
try {
List<CheckedFuture<?, RestApiException>> futures = Lists.newArrayList();
for (ReplaceRequest replace : replaceByChange.values()) {
- if (magicBranch != null && replace.inputCommand == magicBranch.cmd) {
+ if (replace.inputCommand == magicBranch.cmd) {
futures.add(replace.insertPatchSet());
}
}
@@ -1769,19 +1782,13 @@
}
private void submit(ChangeControl changeCtl, PatchSet ps)
- throws OrmException, IOException, ResourceConflictException {
+ throws OrmException, ResourceConflictException {
Submit submit = submitProvider.get();
RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
- List<Change> changes;
+ List<Change> changes = Lists.newArrayList(rsrc.getChange());
try {
- // Force submit even if submit rule evaluation fails.
- changes = submit.submit(rsrc, currentUser, true);
- } catch (ResourceConflictException e) {
- throw new IOException(e);
- }
- try {
- mergeFactory.create(ChangeSet.create(changes),
- (IdentifiedUser) changeCtl.getCurrentUser()).merge(false);
+ mergeOpProvider.get().merge(ChangeSet.create(changes),
+ (IdentifiedUser) changeCtl.getCurrentUser(), false);
} catch (NoSuchChangeException e) {
throw new OrmException(e);
}
@@ -1789,9 +1796,6 @@
for (Change c : changes) {
c = db.changes().get(c.getId());
switch (c.getStatus()) {
- case SUBMITTED:
- addMessage("Change " + c.getChangeId() + " submitted.");
- break;
case MERGED:
addMessage("Change " + c.getChangeId() + " merged.");
break;
@@ -2592,19 +2596,10 @@
closeProgress.update(1);
}
}
-
- // Update superproject gitlinks if required.
- subOpFactory.create(
- branch, newTip, rw, repo, project,
- new ArrayList<Change>(),
- new HashMap<Change.Id, CodeReviewCommit>(),
- currentUser.getAccount()).update();
} catch (RestApiException e) {
log.error("Can't insert patchset", e);
} catch (IOException | OrmException e) {
log.error("Can't scan for changes to close", e);
- } catch (SubmoduleException e) {
- log.error("Can't complete git links check", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushPreReceiveHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushPreReceiveHook.java
deleted file mode 100644
index 671c109..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushPreReceiveHook.java
+++ /dev/null
@@ -1,314 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.git;
-
-import static org.bouncycastle.openpgp.PGPSignature.CERTIFICATION_REVOCATION;
-import static org.bouncycastle.openpgp.PGPSignature.DEFAULT_CERTIFICATION;
-import static org.bouncycastle.openpgp.PGPSignature.POSITIVE_CERTIFICATION;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import org.bouncycastle.bcpg.ArmoredInputStream;
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPObjectFactory;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.bouncycastle.openpgp.PGPSignatureList;
-import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
-import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.Note;
-import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.PreReceiveHook;
-import org.eclipse.jgit.transport.PushCertificate;
-import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
-import org.eclipse.jgit.transport.PushCertificateIdent;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceivePack;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-
-/**
- * Pre-receive hook to validate signed pushes.
- * <p>
- * If configured, prior to processing any push using {@link ReceiveCommits},
- * requires that any push certificate present must be valid.
- */
-@Singleton
-public class SignedPushPreReceiveHook implements PreReceiveHook {
- private static final Logger log =
- LoggerFactory.getLogger(SignedPushPreReceiveHook.class);
-
- private final GitRepositoryManager repoManager;
- private final AllUsersName allUsers;
-
- @Inject
- public SignedPushPreReceiveHook(
- GitRepositoryManager repoManager,
- AllUsersName allUsers) {
- this.repoManager = repoManager;
- this.allUsers = allUsers;
- }
-
- @Override
- public void onPreReceive(ReceivePack rp,
- Collection<ReceiveCommand> commands) {
- try (Writer msgOut = new OutputStreamWriter(rp.getMessageOutputStream())) {
- PushCertificate cert = rp.getPushCertificate();
- if (cert == null) {
- return;
- }
- if (cert.getNonceStatus() != NonceStatus.OK) {
- rejectInvalid(commands);
- return;
- }
- verifySignature(cert, commands, msgOut);
- } catch (IOException e) {
- log.error("Error verifying push certificate", e);
- reject(commands, "push cert error");
- }
- }
-
- private void verifySignature(PushCertificate cert,
- Collection<ReceiveCommand> commands, Writer msgOut) throws IOException {
- PGPSignature sig = readSignature(cert);
- if (sig == null) {
- msgOut.write("Invalid signature format\n");
- rejectInvalid(commands);
- return;
- }
- PGPPublicKey key = readPublicKey(sig.getKeyID(), cert.getPusherIdent());
- if (key == null) {
- msgOut.write("No valid public key found for ID "
- + keyIdToString(sig.getKeyID()) + "\n");
- rejectInvalid(commands);
- return;
- }
- try {
- sig.init(new BcPGPContentVerifierBuilderProvider(), key);
- sig.update(Constants.encode(cert.toText()));
- if (!sig.verify()) {
- msgOut.write("Push certificate signature does not match\n");
- rejectInvalid(commands);
- }
- return;
- } catch (PGPException e) {
- msgOut.write(
- "Push certificate verification error: " + e.getMessage() + "\n");
- rejectInvalid(commands);
- return;
- }
- }
-
- private PGPSignature readSignature(PushCertificate cert) throws IOException {
- ArmoredInputStream in = new ArmoredInputStream(
- new ByteArrayInputStream(Constants.encode(cert.getSignature())));
- PGPObjectFactory factory = new BcPGPObjectFactory(in);
- PGPSignature sig = null;
-
- Object obj;
- while ((obj = factory.nextObject()) != null) {
- if (!(obj instanceof PGPSignatureList)) {
- log.error("Unexpected packet in push cert: {}",
- obj.getClass().getSimpleName());
- return null;
- }
- if (sig != null) {
- log.error("Multiple signature packets found in push cert");
- return null;
- }
- PGPSignatureList sigs = (PGPSignatureList) obj;
- if (sigs.size() != 1) {
- log.error("Expected 1 signature in push cert, found {}", sigs.size());
- return null;
- }
- sig = sigs.get(0);
- }
- return sig;
- }
-
- private PGPPublicKey readPublicKey(long keyId,
- PushCertificateIdent expectedIdent) throws IOException {
- try (Repository repo = repoManager.openRepository(allUsers);
- RevWalk rw = new RevWalk(repo)) {
- Ref ref = repo.getRefDatabase().exactRef(RefNames.REFS_GPG_KEYS);
- if (ref == null) {
- return null;
- }
- NoteMap notes = NoteMap.read(
- rw.getObjectReader(), rw.parseCommit(ref.getObjectId()));
- Note note = notes.getNote(keyObjectId(keyId));
- if (note == null) {
- return null;
- }
-
- try (InputStream objIn =
- rw.getObjectReader().open(note.getData(), OBJ_BLOB).openStream();
- ArmoredInputStream in = new ArmoredInputStream(objIn)) {
- PGPObjectFactory factory = new BcPGPObjectFactory(in);
- PGPPublicKey matched = null;
- Object obj;
- while ((obj = factory.nextObject()) != null) {
- if (!(obj instanceof PGPPublicKeyRing)) {
- // TODO(dborowitz): Support assertions signed by a trusted key.
- log.info("Ignoring {} packet in {}",
- obj.getClass().getSimpleName(), note.getName());
- continue;
- }
- PGPPublicKeyRing keyRing = (PGPPublicKeyRing) obj;
- PGPPublicKey key = keyRing.getPublicKey(keyId);
- if (key == null) {
- log.warn("Public key ring in {} does not contain key ID {}",
- note.getName(), keyObjectId(keyId));
- continue;
- }
- if (matched != null) {
- // TODO(dborowitz): Try all keys.
- log.warn("Ignoring key with duplicate ID: {}", toString(key));
- continue;
- }
- if (!verifyPublicKey(key, expectedIdent)) {
- continue;
- }
- matched = key;
- }
- return matched;
- }
- }
- }
-
- private boolean verifyPublicKey(PGPPublicKey key,
- PushCertificateIdent ident) {
- if (key.isRevoked()) {
- // TODO(dborowitz): isRevoked is overeager:
- // http://www.bouncycastle.org/jira/browse/BJB-45
- log.warn("Key is revoked: {}", toString(key));
- return false;
- } else if (key.getValidSeconds() == 0) {
- log.warn("Key is expired: {}", toString(key));
- return false;
- }
- return verifyPublicKeyCertifications(key, ident);
- }
-
- private boolean verifyPublicKeyCertifications(PGPPublicKey key,
- PushCertificateIdent ident) {
- @SuppressWarnings("unchecked")
- Iterator<PGPSignature> sigs = key.getSignaturesForID(ident.getUserId());
- if (sigs == null) {
- sigs = Collections.emptyIterator();
- }
- boolean valid = false;
- boolean revoked = false;
- try {
- while (sigs.hasNext()) {
- PGPSignature sig = sigs.next();
- if (sig.getKeyID() != key.getKeyID()) {
- // TODO(dborowitz): Support certifications by other trusted keys?
- continue;
- } else if (sig.getSignatureType() != DEFAULT_CERTIFICATION
- && sig.getSignatureType() != POSITIVE_CERTIFICATION
- && sig.getSignatureType() != CERTIFICATION_REVOCATION) {
- continue;
- }
- sig.init(new BcPGPContentVerifierBuilderProvider(), key);
- if (sig.verifyCertification(ident.getUserId(), key)) {
- if (sig.getSignatureType() == CERTIFICATION_REVOCATION) {
- revoked = true;
- } else {
- valid = true;
- }
- } else {
- log.warn("Invalid signature for pusher identity {} in key: {}",
- ident.getUserId(), toString(key));
- }
- }
- } catch (PGPException e) {
- log.warn("Error in signature verification for public key", e);
- }
-
- if (revoked) {
- log.warn("Pusher identity {} is revoked in key {}",
- ident.getUserId(), toString(key));
- return false;
- } else if (!valid) {
- log.warn(
- "Key does not contain valid certification for pusher identity {}: {}",
- ident.getUserId(), toString(key));
- return false;
- }
- return true;
- }
-
- static ObjectId keyObjectId(long keyId) {
- // Right-pad key IDs in network byte order to ObjectId length. This allows
- // us to reuse the fanout code in NoteMap for free. (If we ever fix the
- // fanout code to work with variable-length byte strings, we will need to
- // fall back to this key format during a transition period.)
- ByteBuffer buf = ByteBuffer.wrap(new byte[Constants.OBJECT_ID_LENGTH]);
- buf.putLong(keyId);
- return ObjectId.fromRaw(buf.array());
- }
-
- static String toString(PGPPublicKey key) {
- @SuppressWarnings("unchecked")
- Iterator<String> it = key.getUserIDs();
- ByteBuffer buf = ByteBuffer.wrap(key.getFingerprint());
- return String.format(
- "%s %s(%04X %04X %04X %04X %04X %04X %04X %04X %04X %04X)",
- keyIdToString(key.getKeyID()),
- it.hasNext() ? it.next() + " " : "",
- buf.getShort(), buf.getShort(), buf.getShort(), buf.getShort(),
- buf.getShort(), buf.getShort(), buf.getShort(), buf.getShort(),
- buf.getShort(), buf.getShort());
- }
-
- private static void reject(Collection<ReceiveCommand> commands,
- String reason) {
- for (ReceiveCommand cmd : commands) {
- if (cmd.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
- cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, reason);
- }
- }
- }
-
- static String keyIdToString(long keyId) {
- // Match key ID format from gpg --list-keys.
- return String.format("%08X", (int) keyId);
- }
-
- private static void rejectInvalid(Collection<ReceiveCommand> commands) {
- reject(commands, "invalid push cert");
- }
-}
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 0f66da4..34370f9 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
@@ -14,15 +14,13 @@
package com.google.gerrit.server.git;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
+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.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@@ -30,14 +28,13 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.util.SubmoduleSectionParser;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -62,67 +59,34 @@
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
public class SubmoduleOp {
- public interface Factory {
- SubmoduleOp create(Branch.NameKey destBranch, RevCommit mergeTip,
- RevWalk rw, Repository db, Project destProject, List<Change> submitted,
- Map<Change.Id, CodeReviewCommit> commits, Account account);
- }
-
private static final Logger log = LoggerFactory.getLogger(SubmoduleOp.class);
private static final String GIT_MODULES = ".gitmodules";
- private final Branch.NameKey destBranch;
- private RevCommit mergeTip;
- private RevWalk rw;
private final Provider<String> urlProvider;
- private ReviewDb schema;
- private Repository db;
- private Project destProject;
- private List<Change> submitted;
- private final Map<Change.Id, CodeReviewCommit> commits;
private final PersonIdent myIdent;
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated;
- private final SchemaFactory<ReviewDb> schemaFactory;
private final Set<Branch.NameKey> updatedSubscribers;
private final Account account;
private final ChangeHooks changeHooks;
private final SubmoduleSectionParser.Factory subSecParserFactory;
@Inject
- public SubmoduleOp(@Assisted Branch.NameKey destBranch,
- @Assisted RevCommit mergeTip,
- @Assisted RevWalk rw,
+ public SubmoduleOp(
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
- SchemaFactory<ReviewDb> sf,
- @Assisted Repository db,
- @Assisted Project destProject,
- @Assisted List<Change> submitted,
- @Assisted Map<Change.Id,
- CodeReviewCommit> commits,
@GerritPersonIdent PersonIdent myIdent,
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
- @Nullable @Assisted Account account,
+ @Nullable Account account,
ChangeHooks changeHooks,
SubmoduleSectionParser.Factory subSecParserFactory) {
- this.destBranch = destBranch;
- this.mergeTip = mergeTip;
- this.rw = rw;
this.urlProvider = urlProvider;
- this.schemaFactory = sf;
- this.db = db;
- this.destProject = destProject;
- this.submitted = submitted;
- this.commits = commits;
this.myIdent = myIdent;
this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated;
@@ -133,49 +97,42 @@
updatedSubscribers = new HashSet<>();
}
- public void update() throws SubmoduleException {
- try {
- schema = schemaFactory.open();
-
- updateSubmoduleSubscriptions();
- updateSuperProjects(destBranch, rw, mergeTip.getId().toObjectId(), null);
- } catch (OrmException | IOException e) {
- throw new SubmoduleException("Cannot open database", e);
- } finally {
- if (schema != null) {
- schema.close();
- schema = null;
- }
+ void updateSubmoduleSubscriptions(ReviewDb db, Set<Branch.NameKey> branches)
+ throws SubmoduleException {
+ for (Branch.NameKey branch : branches) {
+ updateSubmoduleSubscriptions(db, branch);
}
}
- private void updateSubmoduleSubscriptions() throws SubmoduleException {
+ void updateSubmoduleSubscriptions(ReviewDb db, Branch.NameKey destBranch)
+ throws SubmoduleException {
if (urlProvider.get() == null) {
- logAndThrowSubmoduleException("Cannot establish canonical web url used to access gerrit."
- + " It should be provided in gerrit.config file.");
+ logAndThrowSubmoduleException("Cannot establish canonical web url used "
+ + "to access gerrit. It should be provided in gerrit.config file.");
}
+ try (Repository repo = repoManager.openRepository(
+ destBranch.getParentKey());
+ RevWalk rw = new RevWalk(repo)) {
- try {
+ ObjectId id = repo.resolve(destBranch.get());
+ RevCommit commit = rw.parseCommit(id);
+
Set<SubmoduleSubscription> oldSubscriptions =
- Sets.newHashSet(schema.submoduleSubscriptions()
+ Sets.newHashSet(db.submoduleSubscriptions()
.bySuperProject(destBranch));
Set<SubmoduleSubscription> newSubscriptions;
- TreeWalk tw = TreeWalk.forPath(db, GIT_MODULES, mergeTip.getTree());
+ TreeWalk tw = TreeWalk.forPath(repo, GIT_MODULES, commit.getTree());
if (tw != null
&& (FileMode.REGULAR_FILE.equals(tw.getRawMode(0)) ||
FileMode.EXECUTABLE_FILE.equals(tw.getRawMode(0)))) {
BlobBasedConfig bbc =
- new BlobBasedConfig(null, db, mergeTip, GIT_MODULES);
+ new BlobBasedConfig(null, repo, commit, GIT_MODULES);
String thisServer = new URI(urlProvider.get()).getHost();
- Branch.NameKey target =
- new Branch.NameKey(new Project.NameKey(destProject.getName()),
- destBranch.get());
-
- newSubscriptions = subSecParserFactory.create(bbc, thisServer, target)
- .parseAllSections();
+ newSubscriptions = subSecParserFactory.create(bbc, thisServer,
+ destBranch).parseAllSections();
} else {
newSubscriptions = Collections.emptySet();
}
@@ -191,10 +148,10 @@
newSubscriptions.removeAll(alreadySubscribeds);
if (!oldSubscriptions.isEmpty()) {
- schema.submoduleSubscriptions().delete(oldSubscriptions);
+ db.submoduleSubscriptions().delete(oldSubscriptions);
}
if (!newSubscriptions.isEmpty()) {
- schema.submoduleSubscriptions().insert(newSubscriptions);
+ db.submoduleSubscriptions().insert(newSubscriptions);
}
} catch (OrmException e) {
@@ -216,73 +173,31 @@
}
}
- private void updateSuperProjects(final Branch.NameKey updatedBranch, RevWalk myRw,
- final ObjectId mergedCommit, final String msg) throws SubmoduleException,
- IOException {
+ protected void updateSuperProjects(ReviewDb db,
+ Set<Branch.NameKey> updatedBranches) throws SubmoduleException {
try {
- final List<SubmoduleSubscription> subscribers =
- schema.submoduleSubscriptions().bySubmodule(updatedBranch).toList();
+ // These (repo/branch) will be updated later with all the given
+ // individual submodule subscriptions
+ Multimap<Branch.NameKey, SubmoduleSubscription> targets =
+ HashMultimap.create();
- if (!subscribers.isEmpty()) {
- // Initialize the message buffer
- StringBuilder sb = new StringBuilder();
- if (msg != null) {
- sb.append(msg);
- } else {
- // The first updatedBranch on a cascade event of automatic
- // updates of repos is added to updatedSubscribers set so
- // if we face a situation having
- // submodule-a(master)-->super(master)-->submodule-a(master),
- // it will be detected we have a circular subscription
- // when updateSuperProjects is called having as updatedBranch
- // the super(master) value.
- updatedSubscribers.add(updatedBranch);
-
- for (final Change chg : submitted) {
- final CodeReviewCommit c = commits.get(chg.getId());
- if (c != null
- && (c.getStatusCode() == CommitMergeStatus.CLEAN_MERGE
- || c.getStatusCode() == CommitMergeStatus.CLEAN_PICK
- || c.getStatusCode() == CommitMergeStatus.CLEAN_REBASE)) {
- myRw.parseBody(c);
- sb.append("\n")
- .append(c.getFullMessage());
- }
- }
+ for (Branch.NameKey updatedBranch : updatedBranches) {
+ for (SubmoduleSubscription sub : db.submoduleSubscriptions()
+ .bySubmodule(updatedBranch)) {
+ targets.put(sub.getSuperProject(), sub);
}
-
- // update subscribers of this module
- List<SubmoduleSubscription> incorrectSubscriptions = Lists.newLinkedList();
- for (final SubmoduleSubscription s : subscribers) {
- try {
- if (!updatedSubscribers.add(s.getSuperProject())) {
- log.error("Possible circular subscription involving " + s);
- } else {
-
- Map<Branch.NameKey, ObjectId> modules = new HashMap<>(1);
- modules.put(updatedBranch, mergedCommit);
-
- Map<Branch.NameKey, String> paths = new HashMap<>(1);
- paths.put(updatedBranch, s.getPath());
- updateGitlinks(s.getSuperProject(), myRw, modules, paths, sb.toString());
- }
- } catch (SubmoduleException e) {
- log.warn("Cannot update gitlinks for " + s + " due to " + e.getMessage());
- incorrectSubscriptions.add(s);
- } catch (Exception e) {
- log.error("Cannot update gitlinks for " + s, e);
+ }
+ updatedSubscribers.addAll(updatedBranches);
+ // Update subscribers.
+ for (Branch.NameKey dest : targets.keySet()) {
+ try {
+ if (!updatedSubscribers.add(dest)) {
+ log.error("Possible circular subscription involving " + dest);
+ } else {
+ updateGitlinks(db, dest, targets.get(dest));
}
- }
-
- if (!incorrectSubscriptions.isEmpty()) {
- try {
- schema.submoduleSubscriptions().delete(incorrectSubscriptions);
- log.info("Deleted incorrect submodule subscription(s) "
- + incorrectSubscriptions);
- } catch (OrmException e) {
- log.error("Cannot delete submodule subscription(s) "
- + incorrectSubscriptions, e);
- }
+ } catch (SubmoduleException e) {
+ log.warn("Cannot update gitlinks for " + dest, e);
}
}
} catch (OrmException e) {
@@ -290,78 +205,109 @@
}
}
- private void updateGitlinks(final Branch.NameKey subscriber, RevWalk myRw,
- final Map<Branch.NameKey, ObjectId> modules,
- final Map<Branch.NameKey, String> paths, final String msg)
- throws SubmoduleException {
+ /**
+ * Update the submodules in one branch of one repository.
+ *
+ * @param subscriber the branch of the repository which should be changed.
+ * @param updates submodule updates which should be updated to.
+ * @throws SubmoduleException
+ */
+ private void updateGitlinks(ReviewDb db, Branch.NameKey subscriber,
+ Collection<SubmoduleSubscription> updates) throws SubmoduleException {
PersonIdent author = null;
- final StringBuilder msgbuf = new StringBuilder("Updated git submodules\n");
Repository pdb = null;
RevWalk recRw = null;
+ StringBuilder msgbuf = new StringBuilder("Updated git submodules\n\n");
try {
boolean sameAuthorForAll = true;
- for (final Map.Entry<Branch.NameKey, ObjectId> me : modules.entrySet()) {
- RevCommit c = myRw.parseCommit(me.getValue());
- if (c == null) {
- continue;
- }
-
- msgbuf.append("\nProject: ");
- msgbuf.append(me.getKey().getParentKey().get());
- msgbuf.append(" ").append(me.getValue().getName());
- msgbuf.append("\n");
- if (modules.size() == 1) {
- if (!Strings.isNullOrEmpty(msg)) {
- msgbuf.append(msg);
- } else {
- msgbuf.append("\n");
- msgbuf.append(c.getFullMessage());
- }
- } else {
- msgbuf.append(c.getShortMessage());
- }
- msgbuf.append("\n");
-
- if (author == null) {
- author = c.getAuthorIdent();
- } else if (!author.equals(c.getAuthorIdent())) {
- sameAuthorForAll = false;
- }
- }
-
- if (!sameAuthorForAll || author == null) {
- author = myIdent;
- }
-
pdb = repoManager.openRepository(subscriber.getParentKey());
if (pdb.getRef(subscriber.get()) == null) {
throw new SubmoduleException(
"The branch was probably deleted from the subscriber repository");
}
- final ObjectId currentCommitId =
- pdb.getRef(subscriber.get()).getObjectId();
-
DirCache dc = readTree(pdb, pdb.getRef(subscriber.get()));
DirCacheEditor ed = dc.editor();
- for (final Map.Entry<Branch.NameKey, ObjectId> me : modules.entrySet()) {
- ed.add(new PathEdit(paths.get(me.getKey())) {
- @Override
- public void apply(DirCacheEntry ent) {
- ent.setFileMode(FileMode.GITLINK);
- ent.setObjectId(me.getValue().copy());
+
+ for (SubmoduleSubscription s : updates) {
+ try (Repository subrepo = repoManager.openRepository(
+ s.getSubmodule().getParentKey());
+ RevWalk rw = CodeReviewCommit.newRevWalk(subrepo)) {
+ Ref ref = subrepo.getRefDatabase().exactRef(s.getSubmodule().get());
+ if (ref == null) {
+ ed.add(new DeletePath(s.getPath()));
+ continue;
}
- });
+
+ final ObjectId updateTo = ref.getObjectId();
+ RevCommit newCommit = rw.parseCommit(updateTo);
+
+ if (author == null) {
+ author = newCommit.getAuthorIdent();
+ } else if (!author.equals(newCommit.getAuthorIdent())) {
+ sameAuthorForAll = false;
+ }
+
+ DirCacheEntry dce = dc.getEntry(s.getPath());
+ ObjectId oldId = null;
+ if (dce != null) {
+ if (!dce.getFileMode().equals(FileMode.GITLINK)) {
+ log.error("Requested to update gitlink " + s.getPath() + " in "
+ + s.getSubmodule().getParentKey().get() + " but entry "
+ + "doesn't have gitlink file mode.");
+ continue;
+ }
+ oldId = dce.getObjectId();
+ } else {
+ // This submodule did not exist before. We do not want to add
+ // the full submodule history to the commit message, so omit it.
+ oldId = updateTo;
+ }
+
+ ed.add(new PathEdit(s.getPath()) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(updateTo);
+ }
+ });
+
+ msgbuf.append("Project: " + s.getSubmodule().getParentKey().get());
+ msgbuf.append(" " + s.getSubmodule().getShortName());
+ msgbuf.append(" " + updateTo.getName());
+ msgbuf.append("\n\n");
+
+ try {
+ rw.markStart(newCommit);
+
+ if (oldId != null) {
+ rw.markUninteresting(rw.parseCommit(oldId));
+ }
+ for (RevCommit c : rw) {
+ msgbuf.append(c.getFullMessage() + "\n\n");
+ }
+ } catch (IOException e) {
+ logAndThrowSubmoduleException("Could not perform a revwalk to "
+ + "create superproject commit message", e);
+ }
+ }
}
ed.finish();
+ if (!sameAuthorForAll || author == null) {
+ author = myIdent;
+ }
+
ObjectInserter oi = pdb.newObjectInserter();
ObjectId tree = dc.writeTree(oi);
- final CommitBuilder commit = new CommitBuilder();
+ ObjectId currentCommitId =
+ pdb.getRef(subscriber.get()).getObjectId();
+
+ CommitBuilder commit = new CommitBuilder();
commit.setTreeId(tree);
commit.setParentIds(new ObjectId[] {currentCommitId});
commit.setAuthor(author);
@@ -390,14 +336,12 @@
default:
throw new IOException(rfu.getResult().name());
}
-
recRw = new RevWalk(pdb);
-
// Recursive call: update subscribers of the subscriber
- updateSuperProjects(subscriber, recRw, commitId, msgbuf.toString());
+ updateSuperProjects(db, Sets.newHashSet(subscriber));
} catch (IOException e) {
- throw new SubmoduleException("Cannot update gitlinks for "
- + subscriber.get(), e);
+ throw new SubmoduleException("Cannot update gitlinks for "
+ + subscriber.get(), e);
} finally {
if (recRw != null) {
recRw.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/CheckResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/CheckResult.java
new file mode 100644
index 0000000..71321ba
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/CheckResult.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Result of checking an object like a key or signature. */
+public class CheckResult {
+ private final List<String> problems;
+
+ CheckResult(String... problems) {
+ this(Arrays.asList(problems));
+ }
+
+ CheckResult(List<String> problems) {
+ this.problems = Collections.unmodifiableList(new ArrayList<>(problems));
+ }
+
+ /**
+ * @return whether the result is entirely ok, i.e. has passed any verification
+ * or validation checks.
+ */
+ public boolean isOk() {
+ return problems.isEmpty();
+ }
+
+ /** @return any problems encountered during checking. */
+ public List<String> getProblems() {
+ return problems;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(getClass().getSimpleName())
+ .append('[');
+ for (int i = 0; i < problems.size(); i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(problems.get(i));
+ }
+ return sb.append(']').toString();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PublicKeyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PublicKeyChecker.java
new file mode 100644
index 0000000..5806e8e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PublicKeyChecker.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyIdToString;
+
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Checker for GPG public keys for use in a push certificate. */
+public class PublicKeyChecker {
+ /**
+ * Check a public key.
+ *
+ * @param key the public key.
+ * @param expectedKeyId the key ID that the caller expects.
+ */
+ public final CheckResult check(PGPPublicKey key, long expectedKeyId) {
+ List<String> problems = new ArrayList<>();
+ if (key.getKeyID() != expectedKeyId) {
+ problems.add(
+ "Public key does not match ID " + keyIdToString(expectedKeyId));
+ }
+ if (key.isRevoked()) {
+ // TODO(dborowitz): isRevoked is overeager:
+ // http://www.bouncycastle.org/jira/browse/BJB-45
+ problems.add("Key is revoked");
+ }
+
+ long validSecs = key.getValidSeconds();
+ if (validSecs != 0) {
+ long createdSecs = key.getCreationTime().getTime() / 1000;
+ long nowSecs = System.currentTimeMillis() / 1000;
+ if (nowSecs - createdSecs > validSecs) {
+ problems.add("Key is expired");
+ }
+ }
+ checkCustom(key, expectedKeyId, problems);
+ return new CheckResult(problems);
+ }
+
+ /**
+ * Perform custom checks.
+ * <p>
+ * Default implementation does nothing, but may be overridden by subclasses.
+ *
+ * @param key the public key.
+ * @param expectedKeyId the key ID that the caller expects.
+ * @param problems list to which any problems should be added.
+ */
+ public void checkCustom(PGPPublicKey key, long expectedKeyId,
+ List<String> problems) {
+ // Default implementation does nothing.
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PublicKeyStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PublicKeyStore.java
new file mode 100644
index 0000000..7327c87
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PublicKeyStore.java
@@ -0,0 +1,170 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.gerrit.reviewdb.client.RefNames;
+
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Store of GPG public keys in git notes.
+ * <p>
+ * Keys are stored in filenames based on their hex key ID, padded out to 40
+ * characters to match the length of a SHA-1. (This is to easily reuse existing
+ * fanout code in {@link NoteMap}, and may be changed later after an appropriate
+ * transition.)
+ * <p>
+ * The contents of each file is an ASCII armored stream containing one or more
+ * public key rings matching the ID. Multiple keys are supported because forging
+ * a key ID is possible, but such a key cannot be used to verify signatures
+ * produced with the correct key.
+ * <p>
+ * No additional checks are performed on the key after reading; callers should
+ * only trust keys after checking with a {@link PublicKeyChecker}.
+ */
+public class PublicKeyStore implements AutoCloseable {
+ private final Repository repo;
+ private ObjectReader reader;
+ private NoteMap notes;
+
+ /** @param repo repository to read keys from. */
+ public PublicKeyStore(Repository repo) {
+ this.repo = repo;
+ }
+
+ @Override
+ public void close() {
+ if (reader != null) {
+ reader.close();
+ reader = null;
+ notes = null;
+ }
+ }
+
+ private void load() throws IOException {
+ close();
+ reader = repo.newObjectReader();
+
+ Ref ref = repo.getRefDatabase().exactRef(RefNames.REFS_GPG_KEYS);
+ if (ref == null) {
+ return;
+ }
+ try (RevWalk rw = new RevWalk(reader)) {
+ notes = NoteMap.read(reader, rw.parseCommit(ref.getObjectId()));
+ }
+ }
+
+ /**
+ * Read public keys with the given key ID.
+ * <p>
+ * Keys should not be trusted unless checked with {@link PublicKeyChecker}.
+ * <p>
+ * Multiple calls to this method use the same state of the key ref; to reread
+ * the ref, call {@link #close()} first.
+ *
+ * @param keyId key ID.
+ * @return any keys found that could be successfully parsed.
+ * @throws PGPException if an error occurred parsing the key data.
+ * @throws IOException if an error occurred reading the repository data.
+ */
+ public PGPPublicKeyRingCollection get(long keyId)
+ throws PGPException, IOException {
+ if (reader == null) {
+ load();
+ }
+ if (notes == null) {
+ return empty();
+ }
+ Note note = notes.getNote(keyObjectId(keyId));
+ if (note == null) {
+ return empty();
+ }
+
+ List<PGPPublicKeyRing> keys = new ArrayList<>();
+ try (InputStream in = reader.open(note.getData(), OBJ_BLOB).openStream()) {
+ while (true) {
+ @SuppressWarnings("unchecked")
+ Iterator<Object> it =
+ new BcPGPObjectFactory(new ArmoredInputStream(in)).iterator();
+ if (!it.hasNext()) {
+ break;
+ }
+ Object obj = it.next();
+ if (obj instanceof PGPPublicKeyRing) {
+ keys.add((PGPPublicKeyRing) obj);
+ }
+ checkState(!it.hasNext(),
+ "expected one PGP object per ArmoredInputStream");
+ }
+ return new PGPPublicKeyRingCollection(keys);
+ }
+ }
+
+ // TODO(dborowitz): put method.
+
+ private static PGPPublicKeyRingCollection empty()
+ throws PGPException, IOException {
+ return new PGPPublicKeyRingCollection(
+ Collections.<PGPPublicKeyRing> emptyList());
+ }
+
+ static String keyToString(PGPPublicKey key) {
+ @SuppressWarnings("unchecked")
+ Iterator<String> it = key.getUserIDs();
+ ByteBuffer buf = ByteBuffer.wrap(key.getFingerprint());
+ return String.format(
+ "%s %s(%04X %04X %04X %04X %04X %04X %04X %04X %04X %04X)",
+ keyIdToString(key.getKeyID()),
+ it.hasNext() ? it.next() + " " : "",
+ buf.getShort(), buf.getShort(), buf.getShort(), buf.getShort(),
+ buf.getShort(), buf.getShort(), buf.getShort(), buf.getShort(),
+ buf.getShort(), buf.getShort());
+ }
+
+ static String keyIdToString(long keyId) {
+ // Match key ID format from gpg --list-keys.
+ return String.format("%08X", (int) keyId);
+ }
+
+ static ObjectId keyObjectId(long keyId) {
+ ByteBuffer buf = ByteBuffer.wrap(new byte[Constants.OBJECT_ID_LENGTH]);
+ buf.putLong(keyId);
+ return ObjectId.fromRaw(buf.array());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PushCertificateChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PushCertificateChecker.java
new file mode 100644
index 0000000..fcef3a3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/PushCertificateChecker.java
@@ -0,0 +1,164 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyIdToString;
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyToString;
+
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Checker for push certificates. */
+public abstract class PushCertificateChecker {
+ private final PublicKeyChecker publicKeyChecker;
+
+ protected PushCertificateChecker(PublicKeyChecker publicKeyChecker) {
+ this.publicKeyChecker = publicKeyChecker;
+ }
+
+ /**
+ * Check a push certificate.
+ *
+ * @return result of the check.
+ * @throws PGPException if an error occurred during GPG checks.
+ * @throws IOException if an error occurred reading from the repository.
+ */
+ public final CheckResult check(PushCertificate cert) throws PGPException, IOException {
+ if (cert.getNonceStatus() != NonceStatus.OK) {
+ return new CheckResult("Invalid nonce");
+ }
+ PGPSignature sig = readSignature(cert);
+ if (sig == null) {
+ return new CheckResult("Invalid signature format");
+ }
+ Repository repo = getRepository();
+ List<String> problems = new ArrayList<>();
+ try (PublicKeyStore store = new PublicKeyStore(repo)) {
+ checkSignature(sig, cert, store.get(sig.getKeyID()), problems);
+ checkCustom(repo, problems);
+ return new CheckResult(problems);
+ } finally {
+ if (shouldClose(repo)) {
+ repo.close();
+ }
+ }
+ }
+
+ /**
+ * Get the repository that this checker should operate on.
+ * <p>
+ * This method is called once per call to {@link #check(PushCertificate)}.
+ *
+ * @return the repository.
+ * @throws IOException if an error occurred reading the repository.
+ */
+ protected abstract Repository getRepository() throws IOException;
+
+ /**
+ * @param repo a repository previously returned by {@link #getRepository()}.
+ * @return whether this repository should be closed before returning from
+ * {@link #check(PushCertificate)}.
+ */
+ protected abstract boolean shouldClose(Repository repo);
+
+ /**
+ * Perform custom checks.
+ * <p>
+ * Default implementation does nothing, but may be overridden by subclasses.
+ *
+ * @param repo a repository previously returned by {@link #getRepository()}.
+ * @param problems list to which any problems should be added.
+ */
+ protected void checkCustom(Repository repo, List<String> problems) {
+ // Default implementation does nothing.
+ }
+
+ private PGPSignature readSignature(PushCertificate cert) throws IOException {
+ ArmoredInputStream in = new ArmoredInputStream(
+ new ByteArrayInputStream(Constants.encode(cert.getSignature())));
+ PGPObjectFactory factory = new BcPGPObjectFactory(in);
+ Object obj;
+ while ((obj = factory.nextObject()) != null) {
+ if (obj instanceof PGPSignatureList) {
+ PGPSignatureList sigs = (PGPSignatureList) obj;
+ if (!sigs.isEmpty()) {
+ return sigs.get(0);
+ }
+ }
+ }
+ return null;
+ }
+
+ private void checkSignature(PGPSignature sig,
+ PushCertificate cert, PGPPublicKeyRingCollection keys,
+ List<String> problems) {
+ List<String> deferredProblems = new ArrayList<>();
+ boolean anyKeys = false;
+ for (PGPPublicKeyRing kr : keys) {
+ PGPPublicKey k = kr.getPublicKey();
+ anyKeys = true;
+ try {
+ sig.init(new BcPGPContentVerifierBuilderProvider(), k);
+ sig.update(Constants.encode(cert.toText()));
+ if (!sig.verify()) {
+ // TODO(dborowitz): Privacy issues with exposing fingerprint/user ID
+ // of keys having the same ID as the pusher's key?
+ deferredProblems.add(
+ "Signature not valid with public key: " + keyToString(k));
+ continue;
+ }
+ CheckResult result = publicKeyChecker.check(k, sig.getKeyID());
+ if (result.isOk()) {
+ return;
+ }
+ StringBuilder err = new StringBuilder("Invalid public key (")
+ .append(keyToString(k))
+ .append("):");
+ for (int i = 0; i < result.getProblems().size(); i++) {
+ err.append('\n').append(" ").append(result.getProblems().get(i));
+ }
+ problems.add(err.toString());
+ return;
+ } catch (PGPException e) {
+ deferredProblems.add(
+ "Error checking signature with public key (" + keyToString(k)
+ + ": " + e.getMessage());
+ }
+ }
+ if (!anyKeys) {
+ problems.add(
+ "No public keys found for Key ID " + keyIdToString(sig.getKeyID()));
+ } else {
+ problems.addAll(deferredProblems);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/SignedPushModule.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/SignedPushModule.java
index 88a918d..6e7cc5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SignedPushModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/SignedPushModule.java
@@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.gpg;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.ReceivePackInitializer;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.BouncyCastleUtil;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/SignedPushPreReceiveHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/SignedPushPreReceiveHook.java
new file mode 100644
index 0000000..4da91d3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/gpg/SignedPushPreReceiveHook.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.bouncycastle.openpgp.PGPException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PreReceiveHook;
+import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * Pre-receive hook to check signed pushes.
+ * <p>
+ * If configured, prior to processing any push using {@link ReceiveCommits},
+ * requires that any push certificate present must be valid.
+ */
+@Singleton
+public class SignedPushPreReceiveHook implements PreReceiveHook {
+ private static final Logger log =
+ LoggerFactory.getLogger(SignedPushPreReceiveHook.class);
+
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+
+ @Inject
+ public SignedPushPreReceiveHook(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsers;
+ }
+
+ @Override
+ public void onPreReceive(ReceivePack rp,
+ Collection<ReceiveCommand> commands) {
+ try {
+ PushCertificate cert = rp.getPushCertificate();
+ if (cert == null) {
+ return;
+ }
+ PushCertificateChecker checker = new PushCertificateChecker(
+ new PublicKeyChecker()) {
+ @Override
+ protected Repository getRepository() throws IOException {
+ return repoManager.openRepository(allUsers);
+ }
+
+ @Override
+ protected boolean shouldClose(Repository repo) {
+ return true;
+ }
+ };
+ CheckResult result = checker.check(cert);
+ if (!result.isOk()) {
+ for (String problem : result.getProblems()) {
+ rp.sendMessage(problem);
+ }
+ reject(commands, "invalid push cert");
+ }
+ } catch (PGPException | IOException e) {
+ log.error("Error checking push certificate", e);
+ reject(commands, "push cert error");
+ }
+ }
+
+ private static void reject(Collection<ReceiveCommand> commands,
+ String reason) {
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
+ cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, reason);
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 2d229a9..0e740f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -23,7 +23,6 @@
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
@@ -132,14 +131,15 @@
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), n)) {
mergeTip.moveTipTo(n, n);
} else {
- CodeReviewCommit result = args.mergeUtil.mergeOneCommit(
- args.serverIdent.get(), args.repo, args.rw, args.inserter,
+ PersonIdent myIdent = args.serverIdent.get();
+ CodeReviewCommit result = args.mergeUtil.mergeOneCommit(myIdent,
+ myIdent, args.repo, args.rw, args.inserter,
args.canMergeFlag, args.destBranch, mergeTip.getCurrentTip(), n);
mergeTip.moveTipTo(result, n);
}
- PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(args.rw,
- args.canMergeFlag, mergeTip.getCurrentTip(), args.alreadyAccepted);
- setRefLogIdent(submitApproval);
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ mergeTip.getCurrentTip(), args.alreadyAccepted);
+ setRefLogIdent();
} else {
// One or more dependencies were not met. The status was already marked on
// the commit so we have nothing further to perform at this time.
@@ -153,33 +153,19 @@
args.rw.parseBody(n);
- PatchSetApproval submitAudit = args.mergeUtil.getSubmitter(n);
-
- IdentifiedUser cherryPickUser;
- PersonIdent serverNow = args.serverIdent.get();
- PersonIdent cherryPickCommitterIdent;
- if (submitAudit != null) {
- cherryPickUser =
- args.identifiedUserFactory.create(submitAudit.getAccountId());
- cherryPickCommitterIdent = cherryPickUser.newCommitterIdent(
- serverNow.getWhen(), serverNow.getTimeZone());
- } else {
- cherryPickUser = args.identifiedUserFactory.create(n.change().getOwner());
- cherryPickCommitterIdent = serverNow;
- }
-
String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
+ PersonIdent committer = args.caller.newCommitterIdent(
+ TimeUtil.nowTs(), args.serverIdent.get().getTimeZone());
CodeReviewCommit newCommit =
(CodeReviewCommit) args.mergeUtil.createCherryPickFromCommit(args.repo,
- args.inserter, mergeTip, n, cherryPickCommitterIdent,
- cherryPickCmtMsg, args.rw);
+ args.inserter, mergeTip, n, committer, cherryPickCmtMsg, args.rw);
PatchSet.Id id =
ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
PatchSet ps = new PatchSet(id);
ps.setCreatedOn(TimeUtil.nowTs());
- ps.setUploader(cherryPickUser.getAccountId());
+ ps.setUploader(args.caller.getAccountId());
ps.setRevision(new RevId(newCommit.getId().getName()));
RefUpdate ru;
@@ -220,9 +206,9 @@
newCommit.copyFrom(n);
newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
newCommit.setControl(
- args.changeControlFactory.controlFor(n.change(), cherryPickUser));
+ args.changeControlFactory.controlFor(n.change(), args.caller));
newCommits.put(newCommit.getPatchsetId().getParentKey(), newCommit);
- setRefLogIdent(submitAudit);
+ setRefLogIdent();
return newCommit;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
index 7ff2107..f7d8ab1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.git.strategy;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.git.MergeException;
@@ -43,10 +42,9 @@
n.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
}
- PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, newMergeTipCommit,
- args.alreadyAccepted);
- setRefLogIdent(submitApproval);
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ newMergeTipCommit, args.alreadyAccepted);
+ setRefLogIdent();
return mergeTip;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
index 3c13af9..d3a72e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.git.strategy;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeTip;
+import org.eclipse.jgit.lib.PersonIdent;
+
import java.util.Collection;
import java.util.List;
@@ -42,17 +43,19 @@
}
while (!sorted.isEmpty()) {
CodeReviewCommit mergedFrom = sorted.remove(0);
+ PersonIdent serverIdent = args.serverIdent.get();
+ PersonIdent caller = args.caller.newCommitterIdent(
+ serverIdent.getWhen(), serverIdent.getTimeZone());
CodeReviewCommit newTip =
- args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo, args.rw,
- args.inserter, args.canMergeFlag, args.destBranch, mergeTip.getCurrentTip(),
- mergedFrom);
+ args.mergeUtil.mergeOneCommit(caller, serverIdent,
+ args.repo, args.rw, args.inserter, args.canMergeFlag,
+ args.destBranch, mergeTip.getCurrentTip(), mergedFrom);
mergeTip.moveTipTo(newTip, mergedFrom);
}
- final PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, mergeTip.getCurrentTip(),
- args.alreadyAccepted);
- setRefLogIdent(submitApproval);
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ mergeTip.getCurrentTip(), args.alreadyAccepted);
+ setRefLogIdent();
return mergeTip;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index b49cb0a..688fa3e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.git.strategy;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeTip;
+import org.eclipse.jgit.lib.PersonIdent;
+
import java.util.Collection;
import java.util.List;
@@ -48,18 +49,19 @@
// For every other commit do a pair-wise merge.
while (!sorted.isEmpty()) {
CodeReviewCommit mergedFrom = sorted.remove(0);
+ PersonIdent serverIdent = args.serverIdent.get();
+ PersonIdent caller = args.caller.newCommitterIdent(
+ serverIdent.getWhen(), serverIdent.getTimeZone());
branchTip =
- args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo,
- args.rw, args.inserter, args.canMergeFlag, args.destBranch,
- branchTip, mergedFrom);
+ args.mergeUtil.mergeOneCommit(caller, serverIdent,
+ args.repo, args.rw, args.inserter, args.canMergeFlag,
+ args.destBranch, branchTip, mergedFrom);
mergeTip.moveTipTo(branchTip, mergedFrom);
}
- final PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, branchTip,
- args.alreadyAccepted);
- setRefLogIdent(submitApproval);
-
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag, branchTip,
+ args.alreadyAccepted);
+ setRefLogIdent();
return mergeTip;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index dd981ad..f9102ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -18,7 +18,6 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
import com.google.gerrit.server.change.RebaseChange;
import com.google.gerrit.server.git.CodeReviewCommit;
@@ -33,6 +32,7 @@
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
import java.io.IOException;
import java.util.Collection;
@@ -84,15 +84,11 @@
} else {
try {
- IdentifiedUser uploader =
- args.identifiedUserFactory.create(args.mergeUtil
- .getSubmitter(n).getAccountId());
PatchSet newPatchSet =
rebaseChange.rebase(args.repo, args.rw, args.inserter,
- n.change(), n.getPatchsetId(), uploader,
+ n.change(), n.getPatchsetId(), args.caller,
mergeTip.getCurrentTip(), args.mergeUtil,
args.serverIdent.get(), false, ValidatePolicy.NONE);
-
List<PatchSetApproval> approvals = Lists.newArrayList();
for (PatchSetApproval a : args.approvalsUtil.byPatchSet(args.db,
n.getControl(), n.getPatchsetId())) {
@@ -109,13 +105,13 @@
newPatchSet.getId()));
mergeTip.getCurrentTip().copyFrom(n);
mergeTip.getCurrentTip().setControl(
- args.changeControlFactory.controlFor(n.change(), uploader));
+ args.changeControlFactory.controlFor(n.change(), args.caller));
mergeTip.getCurrentTip().setPatchsetId(newPatchSet.getId());
mergeTip.getCurrentTip().setStatusCode(
CommitMergeStatus.CLEAN_REBASE);
newCommits.put(newPatchSet.getId().getParentKey(),
mergeTip.getCurrentTip());
- setRefLogIdent(args.mergeUtil.getSubmitter(n));
+ setRefLogIdent();
} catch (MergeConflictException e) {
n.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
} catch (NoSuchChangeException | OrmException | IOException
@@ -135,15 +131,15 @@
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), n)) {
mergeTip.moveTipTo(n, n);
} else {
+ PersonIdent myIdent = args.serverIdent.get();
mergeTip.moveTipTo(
- args.mergeUtil.mergeOneCommit(args.serverIdent.get(),
+ args.mergeUtil.mergeOneCommit(myIdent, myIdent,
args.repo, args.rw, args.inserter, args.canMergeFlag,
args.destBranch, mergeTip.getCurrentTip(), n), n);
}
- PatchSetApproval submitApproval =
- args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
- mergeTip.getCurrentTip(), args.alreadyAccepted);
- setRefLogIdent(submitApproval);
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ mergeTip.getCurrentTip(), args.alreadyAccepted);
+ setRefLogIdent();
} catch (IOException e) {
throw new MergeException("Cannot merge " + n.name(), e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index b25b17e..4034abd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.git.strategy;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -66,6 +67,7 @@
protected final MergeUtil mergeUtil;
protected final ChangeIndexer indexer;
protected final MergeSorter mergeSorter;
+ protected final IdentifiedUser caller;
Arguments(IdentifiedUser.GenericFactory identifiedUserFactory,
Provider<PersonIdent> serverIdent, ReviewDb db,
@@ -73,7 +75,7 @@
RevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag,
Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch,
ApprovalsUtil approvalsUtil, MergeUtil mergeUtil,
- ChangeIndexer indexer) {
+ ChangeIndexer indexer, IdentifiedUser caller) {
this.identifiedUserFactory = identifiedUserFactory;
this.serverIdent = serverIdent;
this.db = db;
@@ -89,6 +91,7 @@
this.mergeUtil = mergeUtil;
this.indexer = indexer;
this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
+ this.caller = caller;
}
}
@@ -115,6 +118,7 @@
public final MergeTip run(final CodeReviewCommit currentTip,
final Collection<CodeReviewCommit> toMerge) throws MergeException {
refLogIdent = null;
+ checkState(args.caller != null);
return _run(currentTip, toMerge);
}
@@ -124,7 +128,7 @@
/**
* Checks whether the given commit can be merged.
- *
+ * <p>
* Implementations must ensure that invoking this method modifies neither the
* git repository nor the Gerrit database.
*
@@ -156,7 +160,7 @@
* <p>
* By default this method returns an empty map, but subclasses may override
* this method to provide any newly created commits.
- *
+ * <p>
* This method may only be called after {@link #run(CodeReviewCommit,
* Collection)}.
*
@@ -182,13 +186,11 @@
/**
* Set the ref log identity if it wasn't set yet.
- *
- * @param submitApproval the approval that submitted the patch set
*/
- protected final void setRefLogIdent(PatchSetApproval submitApproval) {
- if (refLogIdent == null && submitApproval != null) {
+ protected final void setRefLogIdent() {
+ if (refLogIdent == null) {
refLogIdent = args.identifiedUserFactory.create(
- submitApproval.getAccountId()) .newRefLogIdent();
+ args.caller.getAccountId()).newRefLogIdent();
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index ffe351b..b87499a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -88,14 +88,14 @@
public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
final Repository repo, final RevWalk rw, final ObjectInserter inserter,
final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
- final Branch.NameKey destBranch)
+ final Branch.NameKey destBranch, final IdentifiedUser caller)
throws MergeException, NoSuchProjectException {
ProjectState project = getProject(destBranch);
final SubmitStrategy.Arguments args =
new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db,
changeControlFactory, repo, rw, inserter, canMergeFlag,
alreadyAccepted, destBranch,approvalsUtil,
- mergeUtilFactory.create(project), indexer);
+ mergeUtilFactory.create(project), indexer, caller);
switch (submitType) {
case CHERRY_PICK:
return new CherryPick(args, patchSetInfoFactory, gitRefUpdated);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
index 59ca272..32a051b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -101,30 +101,27 @@
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
final GroupReference ref = getGroup(uuid);
- if (ref != null) {
- return new GroupDescription.Basic() {
- @Override
- public String getName() {
- return ref.getName();
- }
+ return new GroupDescription.Basic() {
+ @Override
+ public String getName() {
+ return ref.getName();
+ }
- @Override
- public AccountGroup.UUID getGroupUUID() {
- return ref.getUUID();
- }
+ @Override
+ public AccountGroup.UUID getGroupUUID() {
+ return ref.getUUID();
+ }
- @Override
- public String getUrl() {
- return null;
- }
+ @Override
+ public String getUrl() {
+ return null;
+ }
- @Override
- public String getEmailAddress() {
- return null;
- }
- };
- }
- return null;
+ @Override
+ public String getEmailAddress() {
+ return null;
+ }
+ };
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 43c232b..cb35619 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -164,7 +164,7 @@
}
public void setStatus(Change.Status status) {
- checkArgument(status != Change.Status.SUBMITTED,
+ checkArgument(status != Change.Status.MERGED,
"use submit(Iterable<PatchSetApproval>)");
this.status = status;
}
@@ -177,8 +177,8 @@
approvals.put(label, Optional.<Short> absent());
}
- public void submit(Iterable<SubmitRecord> submitRecords) {
- status = Change.Status.SUBMITTED;
+ public void merge(Iterable<SubmitRecord> submitRecords) {
+ this.status = Change.Status.MERGED;
this.submitRecords = ImmutableList.copyOf(submitRecords);
checkArgument(!this.submitRecords.isEmpty(),
"no submit records specified at submit time");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
index 8da8cc1..ea81f17 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
@@ -58,7 +58,7 @@
}
@Override
- void stop(PluginGuiceEnvironment env) {
+ protected void stop(PluginGuiceEnvironment env) {
if (manager != null) {
manager.stop();
httpInjector = null;
@@ -83,7 +83,7 @@
}
@Override
- boolean canReload() {
+ protected boolean canReload() {
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index 6b84c21..c2b28cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -130,9 +130,9 @@
return disabled;
}
- abstract void start(PluginGuiceEnvironment env) throws Exception;
+ protected abstract void start(PluginGuiceEnvironment env) throws Exception;
- abstract void stop(PluginGuiceEnvironment env);
+ protected abstract void stop(PluginGuiceEnvironment env);
public abstract PluginContentScanner getContentScanner();
@@ -168,7 +168,7 @@
return "Plugin [" + name + "]";
}
- abstract boolean canReload();
+ protected abstract boolean canReload();
boolean isModified(Path jar) {
return snapshot.lastModified() != lastModified(jar);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index 2887a00..6b458aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -145,7 +145,7 @@
|| (httpMaps != null && httpMaps.containsKey(type));
}
- Module getSysModule() {
+ public Module getSysModule() {
return sysModule;
}
@@ -210,15 +210,15 @@
return httpGen.get();
}
- RequestContext enter(Plugin plugin) {
+ public RequestContext enter(Plugin plugin) {
return local.setContext(new PluginRequestContext(plugin.getPluginUser()));
}
- void exit(RequestContext old) {
+ public void exit(RequestContext old) {
local.setContext(old);
}
- void onStartPlugin(Plugin plugin) {
+ public void onStartPlugin(Plugin plugin) {
RequestContext oldContext = enter(plugin);
try {
attachItem(sysItems, plugin.getSysInjector(), plugin);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index 28d57b2..14c1185 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -151,7 +151,7 @@
}
@Override
- boolean canReload() {
+ protected boolean canReload() {
Attributes main = manifest.getMainAttributes();
String v = main.getValue("Gerrit-ReloadMode");
if (Strings.isNullOrEmpty(v) || "reload".equalsIgnoreCase(v)) {
@@ -167,7 +167,7 @@
}
@Override
- void start(PluginGuiceEnvironment env) throws Exception {
+ protected void start(PluginGuiceEnvironment env) throws Exception {
RequestContext oldContext = env.enter(this);
try {
startPlugin(env);
@@ -241,7 +241,7 @@
}
@Override
- void stop(PluginGuiceEnvironment env) {
+ protected void stop(PluginGuiceEnvironment env) {
if (serverManager != null) {
RequestContext oldContext = env.enter(this);
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index 28700b3..1801712 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -30,8 +30,8 @@
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
-import com.google.gerrit.server.git.SignedPushModule;
import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.git.gpg.SignedPushModule;
import com.google.inject.util.Providers;
import org.eclipse.jgit.lib.Config;
@@ -169,14 +169,19 @@
cfgFactory.getFromProjectConfigWithInheritance(project,
e.getPluginName());
p.inheritable = true;
- p.value = cfgWithInheritance.getString(e.getExportName(), configEntry.getDefaultValue());
+ p.value = configEntry.onRead(project,
+ cfgWithInheritance.getString(e.getExportName(),
+ configEntry.getDefaultValue()));
p.configuredValue = configuredValue;
p.inheritedValue = getInheritedValue(project, cfgFactory, e);
} else {
if (configEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
- p.values = Arrays.asList(cfg.getStringList(e.getExportName()));
+ p.values = configEntry.onRead(project,
+ Arrays.asList(cfg.getStringList(e.getExportName())));
} else {
- p.value = configuredValue != null ? configuredValue : configEntry.getDefaultValue();
+ p.value = configEntry.onRead(project, configuredValue != null
+ ? configuredValue
+ : configEntry.getDefaultValue());
}
}
Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName());
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 da7df8c..d73a95d 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
@@ -79,7 +79,7 @@
}
private final Config gerritConfig;
- private final MetaDataUpdate.User metaDataUpdateFactory;
+ private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final ProjectCache projectCache;
private final GitRepositoryManager gitMgr;
private final ProjectState.Factory projectStateFactory;
@@ -93,7 +93,7 @@
@Inject
PutConfig(@GerritServerConfig Config gerritConfig,
- MetaDataUpdate.User metaDataUpdateFactory,
+ Provider<MetaDataUpdate.User> metaDataUpdateFactory,
ProjectCache projectCache,
GitRepositoryManager gitMgr,
ProjectState.Factory projectStateFactory,
@@ -138,7 +138,7 @@
final MetaDataUpdate md;
try {
- md = metaDataUpdateFactory.create(projectName);
+ md = metaDataUpdateFactory.get().create(projectName);
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(projectName.get());
} catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 2070746..e63b05c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -117,7 +117,7 @@
args.submitStrategyFactory.create(submitType,
db.get(), repo, rw, null, canMergeFlag,
getAlreadyAccepted(repo, rw, commit),
- otherChange.getDest());
+ otherChange.getDest(), null);
CodeReviewCommit otherCommit =
(CodeReviewCommit) rw.parseCommit(other);
otherCommit.add(canMergeFlag);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index a4d4467..77a24cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -108,17 +108,6 @@
return query(project(project));
}
- public List<ChangeData> submitted(Branch.NameKey branch) throws OrmException {
- return query(and(
- ref(branch),
- project(branch.getParentKey()),
- status(Change.Status.SUBMITTED)));
- }
-
- public List<ChangeData> allSubmitted() throws OrmException {
- return query(status(Change.Status.SUBMITTED));
- }
-
public List<ChangeData> byBranchOpen(Branch.NameKey branch)
throws OrmException {
return query(and(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index c1be4f8..038da50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_108> C = Schema_108.class;
+ public static final Class<Schema_109> C = Schema_109.class;
public static int getBinaryVersion() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java
new file mode 100644
index 0000000..fa8af3e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_109.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_109 extends SchemaVersion {
+
+ @Inject
+ Schema_109(Provider<Schema_108> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
+ String cmd = "UPDATE changes SET status = 'n' WHERE status = 's';";
+ ui.message("Running " + cmd);
+ try (StatementExecutor e = newExecutor(db)) {
+ e.execute(cmd);
+ }
+ ui.message("done");
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/SignedPushPreReceiveHookTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/SignedPushPreReceiveHookTest.java
deleted file mode 100644
index f0cfe18..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/SignedPushPreReceiveHookTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.git;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.git.SignedPushPreReceiveHook.keyIdToString;
-
-import org.bouncycastle.bcpg.ArmoredInputStream;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
-import org.eclipse.jgit.lib.Constants;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.ByteArrayInputStream;
-
-public class SignedPushPreReceiveHookTest {
- // ./pubring.gpg
- // -------------
- // pub 1024R/30A5A053 2015-06-16 [expires: 2015-06-17]
- // Key fingerprint = 96D6 DE78 E6D8 DA49 9387 1F31 FA09 A0C4 30A5 A053
- // uid A U. Thor <a_u_thor@example.com>
- // sub 1024R/D6831DC8 2015-06-16 [expires: 2015-06-17]
- private static final String PUBKEY =
- "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
- + "Version: GnuPG v1\n"
- + "\n"
- + "mI0EVYCBUQEEALCKzuY6M68RRRm6PS1F322lpHSHTdW9PIURm5B//tbfS32EN6lM\n"
- + "ISwJxhanpZanv2o4mbV3V8oLT3jMVDPJ3dqmOZJdJs37l+dxCVJ3ycFe1LHtT2oT\n"
- + "eRyC5PxD7UY5PdDe97mjp7yrp/bx1hE6XqGV0nDGrkJXc8A35u3WzIF5ABEBAAG0\n"
- + "IEEgVS4gVGhvciA8YV91X3Rob3JAZXhhbXBsZS5jb20+iL4EEwECACgFAlWAgVEC\n"
- + "GwMFCQABUYAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEPoJoMQwpaBTjhoD\n"
- + "/0MRCX1zBjEKIfzFYeSEg/OcSLbAkUD7un5YTfpgds3oUNIKlIgovWO24TQxrCCu\n"
- + "5pSzN/WfRSzPFhj9HahY/5yh+EGd6HmIU2v/k5I3LwTPEOcZUi1SzOScSv6JOO9Q\n"
- + "3srVilCu3h6TNW1UGBNjfOr1NdmkWfsUZcjsEc/XrfBGuI0EVYCBUQEEAL0UP9jJ\n"
- + "eLj3klCCa2tmwdgyFiSf9T+Yoed4I3v3ag2F0/CWrCJr3e1ogSs4Bdts0WptI+Nu\n"
- + "QIq40AYszewq55dTcB4lbNAYE4svVYQ5AGz78iKzljaBFhyT6ePdZ5wfb+8Jqu1l\n"
- + "7wRwzRI5Jn3OXCmdGm/dmoUNG136EA9A4ZLLABEBAAGIpQQYAQIADwUCVYCBUQIb\n"
- + "DAUJAAFRgAAKCRD6CaDEMKWgU5JTA/9XjwPFZ5NseNROMhYZMmje1/ixISb2jaVc\n"
- + "9m9RLCl8Y3RCY9NNdU5FinTIX9LsRTrJlW6FSG5sin8mwx9jq0eGE1TBEKND5klT\n"
- + "TmsG0jx1dZG9kWDy6lPnIWw2/4W+N0fK/Cw6WEL1Xg7RLi4NQ9Bi2WoxJii9bWMv\n"
- + "yy35U6UfPQ==\n"
- + "=0GL9\n"
- + "-----END PGP PUBLIC KEY BLOCK-----\n";
-
- private PGPPublicKey key;
-
- @Before
- public void setUp() throws Exception {
- ArmoredInputStream in = new ArmoredInputStream(
- new ByteArrayInputStream(Constants.encode(PUBKEY)));
- PGPPublicKeyRing keyRing =
- new PGPPublicKeyRing(in, new BcKeyFingerprintCalculator());
- key = keyRing.getPublicKey();
- }
-
- @Test
- public void testKeyIdToString() throws Exception {
- assertThat(keyIdToString(key.getKeyID()))
- .isEqualTo("30A5A053");
- }
-
- @Test
- public void testKeyToString() throws Exception {
- assertThat(SignedPushPreReceiveHook.toString(key))
- .isEqualTo("30A5A053 A U. Thor <a_u_thor@example.com>"
- + " (96D6 DE78 E6D8 DA49 9387 1F31 FA09 A0C4 30A5 A053)");
- }
-
- @Test
- public void testKeyObjectId() throws Exception {
- String objId = SignedPushPreReceiveHook.keyObjectId(key.getKeyID()).name();
- assertThat(objId).isEqualTo("fa09a0c430a5a053000000000000000000000000");
- assertThat(objId.substring(8, 16))
- .isEqualTo(keyIdToString(key.getKeyID()).toLowerCase());
- }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PublicKeyCheckerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PublicKeyCheckerTest.java
new file mode 100644
index 0000000..a82619c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PublicKeyCheckerTest.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class PublicKeyCheckerTest {
+ private PublicKeyChecker checker;
+
+ @Before
+ public void setUp() {
+ checker = new PublicKeyChecker();
+ }
+
+ @Test
+ public void validKey() throws Exception {
+ assertProblems(TestKey.key1());
+ }
+
+ @Test
+ public void wrongKeyId() throws Exception {
+ TestKey k = TestKey.key1();
+ long badId = k.getKeyId() + 1;
+ CheckResult result = checker.check(k.getPublicKey(), badId);
+ assertEquals(
+ Arrays.asList("Public key does not match ID 46328A8D"),
+ result.getProblems());
+ }
+
+ @Test
+ public void keyExpiringInFuture() throws Exception {
+ assertProblems(TestKey.key2());
+ }
+
+ @Test
+ public void expiredKey() throws Exception {
+ assertProblems(TestKey.key3(), "Key is expired");
+ }
+
+ @Test
+ public void selfRevokedKey() throws Exception {
+ assertProblems(TestKey.key4(), "Key is revoked");
+ }
+
+ private void assertProblems(TestKey tk, String... expected) throws Exception {
+ CheckResult result = checker.check(tk.getPublicKey(), tk.getKeyId());
+ assertEquals(Arrays.asList(expected), result.getProblems());
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PublicKeyStoreTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PublicKeyStoreTest.java
new file mode 100644
index 0000000..f42e8b3
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PublicKeyStoreTest.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyIdToString;
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyObjectId;
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyToString;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.reviewdb.client.RefNames;
+
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class PublicKeyStoreTest {
+ private TestRepository<?> tr;
+ private PublicKeyStore store;
+
+ @Before
+ public void setUp() throws Exception {
+ tr = new TestRepository<>(new InMemoryRepository(
+ new DfsRepositoryDescription("pubkeys")));
+ store = new PublicKeyStore(tr.getRepository());
+ }
+
+ @Test
+ public void testKeyIdToString() throws Exception {
+ PGPPublicKey key = TestKey.key1().getPublicKey();
+ assertEquals("46328A8C", keyIdToString(key.getKeyID()));
+ }
+
+ @Test
+ public void testKeyToString() throws Exception {
+ PGPPublicKey key = TestKey.key1().getPublicKey();
+ assertEquals("46328A8C Testuser One <test1@example.com>"
+ + " (04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C)",
+ keyToString(key));
+ }
+
+ @Test
+ public void testKeyObjectId() throws Exception {
+ PGPPublicKey key = TestKey.key1().getPublicKey();
+ String objId = keyObjectId(key.getKeyID()).name();
+ assertEquals("ed0625dc46328a8c000000000000000000000000", objId);
+ assertEquals(keyIdToString(key.getKeyID()).toLowerCase(),
+ objId.substring(8, 16));
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ TestKey key1 = TestKey.key1();
+ tr.branch(RefNames.REFS_GPG_KEYS)
+ .commit()
+ .add(keyObjectId(key1.getKeyId()).name(),
+ key1.getPublicKeyArmored())
+ .create();
+ TestKey key2 = TestKey.key2();
+ tr.branch(RefNames.REFS_GPG_KEYS)
+ .commit()
+ .add(keyObjectId(key2.getKeyId()).name(),
+ key2.getPublicKeyArmored())
+ .create();
+
+ assertKeys(key1.getKeyId(), key1);
+ assertKeys(key2.getKeyId(), key2);
+ }
+
+ @Test
+ public void testGetMultiple() throws Exception {
+ TestKey key1 = TestKey.key1();
+ TestKey key2 = TestKey.key2();
+ tr.branch(RefNames.REFS_GPG_KEYS)
+ .commit()
+ .add(keyObjectId(key1.getKeyId()).name(),
+ key1.getPublicKeyArmored()
+ // Mismatched for this key ID, but we can still read it out.
+ + key2.getPublicKeyArmored())
+ .create();
+ assertKeys(key1.getKeyId(), key1, key2);
+ }
+
+ private void assertKeys(long keyId, TestKey... expected)
+ throws Exception {
+ Set<String> expectedStrings = new TreeSet<>();
+ for (TestKey k : expected) {
+ expectedStrings.add(keyToString(k.getPublicKey()));
+ }
+ PGPPublicKeyRingCollection actual = store.get(keyId);
+ Set<String> actualStrings = new TreeSet<>();
+ for (PGPPublicKeyRing k : actual) {
+ actualStrings.add(keyToString(k.getPublicKey()));
+ }
+ assertEquals(expectedStrings, actualStrings);
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PushCertificateCheckerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PushCertificateCheckerTest.java
new file mode 100644
index 0000000..aa2a2c7
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/PushCertificateCheckerTest.java
@@ -0,0 +1,153 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyIdToString;
+import static com.google.gerrit.server.git.gpg.PublicKeyStore.keyToString;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.reviewdb.client.RefNames;
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.PushCertificateIdent;
+import org.eclipse.jgit.transport.PushCertificateParser;
+import org.eclipse.jgit.transport.SignedPushConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Arrays;
+
+public class PushCertificateCheckerTest {
+ private TestRepository<?> tr;
+ private SignedPushConfig signedPushConfig;
+ private PushCertificateChecker checker;
+
+ @Before
+ public void setUp() throws Exception {
+ TestKey key1 = TestKey.key1();
+ TestKey key3 = TestKey.key3();
+ tr = new TestRepository<>(new InMemoryRepository(
+ new DfsRepositoryDescription("repo")));
+ tr.branch(RefNames.REFS_GPG_KEYS).commit()
+ .add(PublicKeyStore.keyObjectId(key1.getPublicKey().getKeyID()).name(),
+ key1.getPublicKeyArmored())
+ .add(PublicKeyStore.keyObjectId(key3.getPublicKey().getKeyID()).name(),
+ key3.getPublicKeyArmored())
+ .create();
+ signedPushConfig = new SignedPushConfig();
+ signedPushConfig.setCertNonceSeed("sekret");
+ signedPushConfig.setCertNonceSlopLimit(60 * 24);
+
+ checker = new PushCertificateChecker(new PublicKeyChecker()) {
+ @Override
+ protected Repository getRepository() {
+ return tr.getRepository();
+ }
+
+ @Override
+ protected boolean shouldClose(Repository repo) {
+ return false;
+ }
+ };
+ }
+
+ @Test
+ public void validCert() throws Exception {
+ PushCertificate cert = newSignedCert(validNonce(), TestKey.key1());
+ assertProblems(cert);
+ }
+
+ @Test
+ public void invalidNonce() throws Exception {
+ PushCertificate cert = newSignedCert("invalid-nonce", TestKey.key1());
+ assertProblems(cert, "Invalid nonce");
+ }
+
+ @Test
+ public void missingKey() throws Exception {
+ TestKey key2 = TestKey.key2();
+ PushCertificate cert = newSignedCert(validNonce(), key2);
+ assertProblems(cert,
+ "No public keys found for Key ID " + keyIdToString(key2.getKeyId()));
+ }
+
+ @Test
+ public void invalidKey() throws Exception {
+ TestKey key3 = TestKey.key3();
+ PushCertificate cert = newSignedCert(validNonce(), key3);
+ assertProblems(cert,
+ "Invalid public key (" + keyToString(key3.getPublicKey())
+ + "):\n Key is expired");
+ }
+
+ private String validNonce() {
+ return signedPushConfig.getNonceGenerator()
+ .createNonce(tr.getRepository(), System.currentTimeMillis() / 1000);
+ }
+
+ private PushCertificate newSignedCert(String nonce, TestKey signingKey)
+ throws Exception {
+ PushCertificateIdent ident = new PushCertificateIdent(
+ signingKey.getFirstUserId(), System.currentTimeMillis(), -7 * 60);
+ String payload = "certificate version 0.1\n"
+ + "pusher " + ident.getRaw() + "\n"
+ + "pushee test://localhost/repo.git\n"
+ + "nonce " + nonce + "\n"
+ + "\n"
+ + "0000000000000000000000000000000000000000"
+ + " deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+ + " refs/heads/master\n";
+ PGPSignatureGenerator gen = new PGPSignatureGenerator(
+ new BcPGPContentSignerBuilder(
+ signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1));
+ gen.init(PGPSignature.BINARY_DOCUMENT, signingKey.getPrivateKey());
+ gen.update(payload.getBytes(UTF_8));
+ PGPSignature sig = gen.generate();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ try (BCPGOutputStream out = new BCPGOutputStream(
+ new ArmoredOutputStream(bout))) {
+ sig.encode(out);
+ }
+
+ String cert = payload + new String(bout.toByteArray(), UTF_8);
+ Reader reader =
+ new InputStreamReader(new ByteArrayInputStream(cert.getBytes(UTF_8)));
+ PushCertificateParser parser =
+ new PushCertificateParser(tr.getRepository(), signedPushConfig);
+ return parser.parse(reader);
+ }
+
+ private void assertProblems(PushCertificate cert, String... expected)
+ throws Exception {
+ CheckResult result = checker.check(cert);
+ assertEquals(Arrays.asList(expected), result.getProblems());
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/TestKey.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/TestKey.java
new file mode 100644
index 0000000..69362ab
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/gpg/TestKey.java
@@ -0,0 +1,496 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.gpg;
+
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.eclipse.jgit.lib.Constants;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+class TestKey {
+ /**
+ * A valid key with no expiration.
+ *
+ * <pre>
+ * pub 2048R/46328A8C 2015-07-08
+ * Key fingerprint = 04AE A7ED 2F82 1133 E5B1 28D1 ED06 25DC 4632 8A8C
+ * uid Testuser One <test1@example.com>
+ * sub 2048R/F0AF69C0 2015-07-08
+ * </pre>
+ */
+ static TestKey key1() throws PGPException, IOException {
+ return new TestKey("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBFWdTIkBCADOaygDKjLuRX6LXAvBAYB91cmTf1MSlmEy+qsG3c9ijjQixPkr\n"
+ + "atdYkocrrT2S0R9UGjksTOI2WN5S0lQfLA1RSk63KURQE+OF+IfNqdD6nQdLBs1w\n"
+ + "va+GDj/uvuI05I0oXf/M7POdFphutrS4EUDBnFPj6ns/0C2sTRTxliD+Y9Y9a84V\n"
+ + "DfVVUbJB6wc3LP3L6ImT+cSM7dLq3hZHya+9FNeYPmPYnBrkJyqf2NDd38Sddsro\n"
+ + "7smw/GgCZHnnuVNS4C7NsHr6900VKC+JDtdx+fqptixcAEJWiGoQfWqU+hYmia3p\n"
+ + "9+Xw02+3FcjOT6ONUCmHX+xlz0pXW4iIYlPpABEBAAG0IFRlc3R1c2VyIE9uZSA8\n"
+ + "dGVzdDFAZXhhbXBsZS5jb20+iQE4BBMBAgAiBQJVnUyJAhsDBgsJCAcDAgYVCAIJ\n"
+ + "CgsEFgIDAQIeAQIXgAAKCRDtBiXcRjKKjHblB/9RaFO5+GTDIphAL/aVj2u+d8Lq\n"
+ + "yUpBrDp3P06QDGpKGFMAovBuh+NLH76VKNIzQLQC8rdTj651fLcLMuJ1enQ3Rblg\n"
+ + "RKr1oc+wqqtFHr4QyOQjE/N3C9GQjEzfqn4qnp5KtZxYFnlvU5NGehid7M1HTZMx\n"
+ + "jRcHbM9KQnsE5Z4fh4wmN5ynG+5nbaF4O9otPOpFzYRvIhxFmHscWyOgRaMZiYEX\n"
+ + "7Qkzze+scAlc9E/EWRJQIFcxnxV/SYIT4qCTT1g2aKA8OCBO/ZTOleH8SzvTODjy\n"
+ + "W0lGHnh/ZqH6XGVcGUaJZZ2uHTck1+czuVVShNcXPW1W20T6E9UqzHbJHN0guQEN\n"
+ + "BFWdTIkBCACoLVdPr3gpQwzI+2NGXjdtoyqYoPlgfeyI2M1XQD/7+rLZTbi14ZjN\n"
+ + "vYkS/+/oGtVEmiYOiAVTwmkjCYkKGDgNcCiJVekiPAN6JryVv488wRc999b5LpFE\n"
+ + "fhLGwI0YxjcS4KFFnpMC3wSb6tJUnHRLVoE5d8icdiaOpgYdp7uqWkSx2oxqHgIb\n"
+ + "nuyrk3ydEcS4ZeGD+w+taIxMc9F1DS9kiXALD7xWgUkmqZLEQoNgF6KlwCHXRd3m\n"
+ + "rBCo97sE95yKcq98ZMIWuQtTcEccZsN/6jlsei+9RI0tqs+FbZnIFm/go9zk11Vl\n"
+ + "IQ9QFSj6ruqoKrYvNZuDDLD1lHvZPD4/ABEBAAGJAR8EGAECAAkFAlWdTIkCGwwA\n"
+ + "CgkQ7QYl3EYyiox+HAf/Z/OCQO3jxALAcn3oUb1g/IlHm6qZv7RJOFUsj/16fGiF\n"
+ + "rRTP15zMXzyqV+L/LGV/owvOsdD/o7boZz4C/U98COx0Nl1jOrmPATOl+xqsgpEj\n"
+ + "Fhk+eAR7exO2XxW+u2g4cYoSMosIOX5w1GrdsxQeaZDwiSJMEOR2cVLs3YI19Ci/\n"
+ + "FuzActZ0wJNk0nlNF6l8CAbzwN6pM9OIc/iBIwDjz92KUco0NF8XKZnxqhH4wfHB\n"
+ + "PGkTx8RwOvELUTDMtvYnG5R0QtND0RbOnmp4ZVZmeOjKSLo1mZliUZB1H2PPSxrA\n"
+ + "0oLr8+wLntz1SU7uS4ddvhSQW+j2M/0pa352KUwmrw==\n"
+ + "=o/aU\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=MuAn\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A valid key expiring in 2065.
+ *
+ * <pre>
+ * pub 2048R/378A0AED 2015-07-08 [expires: 2065-06-25]
+ * Key fingerprint = C378 369A CBCD 34CC 138D 90B1 4531 1A6F 378A 0AED
+ * uid Testuser Two <test2@example.com>
+ * sub 2048R/46D4F204 2015-07-08 [expires: 2065-06-25]
+ * </pre>
+ */
+ static final TestKey key2() throws PGPException, IOException {
+ return new TestKey(
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBFWdTP8BCADRxNpasIv0jtNXTK6VYIS2VJ2Xk0ZD6gtxeoXCpjQ+TsB9fxh3\n"
+ + "vAMPt2Zu5LqoGwygKOJj1zquG8xk7GUCCHJk3+qG8xxB1xGtSz2vLyfRm7fOZmHj\n"
+ + "3W/C/25lynSPDrfvcwvwA4PN8iP5EWbWU10L6WOZGMwwwtVDUSEouSOw2LEepxLV\n"
+ + "rkKuZcyHaivheDbUlZliwe9rGXd4hh1h4qyNQWG3q+ytlL28sVkOzUh6IMBTvqhe\n"
+ + "IRsvxvaVSLV8jRVKfUTqw0g57ft4ZD2/L46yUTXzr9aUCBjTNxvWLlyboqql/D8P\n"
+ + "inp51h3cvAg7NW5RdG1GEYmylH8SygT5utPxABEBAAG0IFRlc3R1c2VyIFR3byA8\n"
+ + "dGVzdDJAZXhhbXBsZS5jb20+iQE+BBMBAgAoBQJVnUz/AhsDBQld/A8ABgsJCAcD\n"
+ + "AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBFMRpvN4oK7UZqCACWwQL/YvBK4b0m+R0d\n"
+ + "UdvAXeBx7DwOAnAodis9ZVqChb7RxcZQxF1Ti9mtCBPPQGuEs5wE2Ocrrq+L13r6\n"
+ + "bgW+1WOB1tZSDVxwL1PnZFw/SyADRIDCZrOHiAkp82UnZwWAkk39GzNJtt1wTYDZ\n"
+ + "FMTFUr2SPscXk1k7muS+ZfEFwNPD4tODo/poJKDYEJ80Z5UXXFQLDtsfdeIXMFIT\n"
+ + "449CYoq8XBMBfvyWl/LLpw0r3JI6pV/YdH3Oeuz8XkkEVzRxaxB6Zmeo5jSwjR/T\n"
+ + "8TKDGwwiuwiiT3SfkFSVdcjKulRuXSRNs1Ouf7/UC3cq4bG2WXWa85X1+HQRm7iu\n"
+ + "RHSOuQENBFWdTP8BCADhhGxAA0pX5yBHwIgM1j0gw2h5nSsopDrO6t/sbRUcNxnR\n"
+ + "tBScgKZnP0sjRTYEUIwmZuseHMBohtVCuMaDt06qyZDvDk/98j3AeE5t2dgFnOIe\n"
+ + "qCrm/6aejbFcQOpxe6U29KJRCAxuwNtB15X1VH1Kj7B0gRSTu13n/5sUsi2lunoZ\n"
+ + "oIvpIe9tZH4aXitCY2MCQH+hTyCyNBzlEa44kWz6LxUsPdo7I6rXkTr6Ot7wQh+9\n"
+ + "7HCe042GIq65h0apgujyjhJidjch5ur1mngaSNSEyvbji2MGC+cd3wAIstG5a7xP\n"
+ + "d9MncY5Q/eH+hn96694k5bckottSyGm/3f2Ihfj1ABEBAAGJASUEGAECAA8FAlWd\n"
+ + "TP8CGwwFCV38DwAACgkQRTEabzeKCu1FNwgAif4eK2v7R3QubL2S6wmb1nsgRMgV\n"
+ + "YoxGBeUk2EK6WZ5IPor93ySd0ixRVNMRmJ8BLH3EMjZQTzkDG+BH6zFyxo6lLHw9\n"
+ + "NxQjI06tqQWgyyK0mEweVwB/zqtxiB4lNUpsNbqOZWnBJ3d6o1SsnD2Q3uwvP5fb\n"
+ + "fSIgdmUk3c0VMdgA+KzWjPD/PJIPujE+ckHhjn5cbDNw35/FuyhkLJfqlOG7SPvM\n"
+ + "NmCdJ1Pcqju9t7sf6b0BGPDOCL4gpuWKK7HJz9WxngNb3FSziLbyPLk13ynADO+v\n"
+ + "EOR44LPyXE9kVxPusazsXlt9ayTOhELhwzw7sGFFu8E17Cpn7GnVj3tN9A==\n"
+ + "=1e/A\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "9A==\n"
+ + "=qbV3\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A key that expired in 2006.
+ *
+ * <pre>
+ * pub 2048R/17DE1ACD 2005-07-08 [expired: 2006-07-08]
+ * Key fingerprint = 1D9E EB79 DD38 B049 939D 9CAF 3CEC 781B 17DE 1ACD
+ * uid Testuser Three <test3@example.com>
+ * </pre>
+ */
+ static final TestKey key3() throws PGPException, IOException {
+ return new TestKey(
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "mQENBELOp0UBCACxholOPWuKhK+TYb88nvLUSCMvTLIFEpb5u3Eavr0wiluEzq6H\n"
+ + "55nswAD3dQm8DWxA7yUlEYjPr5btpw7V9441bb1+qtgZMJ10RTdEb/WjyctdGA99\n"
+ + "uOKBEarWbt8W+w6lyJ9NXy5bS/x5EwHHfoTFp4ff6ffHI5hbx1a00K8oxmitgd0X\n"
+ + "Mx86UmauFNJYupZOZG9gEcP4RbRp7e2pm4Jy1WLEOeg9Fdgm5e5Hj2nMkCSZ9BKV\n"
+ + "cxuOllSVzM/Zp0/4+RS9R57jKo3/V74Whwh9yQNgL9UxdNk7L0eGqvaT3EjXxjOc\n"
+ + "RCeJiucGN/0W2iq+V01/QGspp4SKtAogWBozABEBAAG0IlRlc3R1c2VyIFRocmVl\n"
+ + "IDx0ZXN0M0BleGFtcGxlLmNvbT6JAT4EEwECACgFAkLOp0UCGwMFCQHhM4AGCwkI\n"
+ + "BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEDzseBsX3hrNYg0H/2CMm5/JDQNSuRFC\n"
+ + "ECWLrcOeimuvwbmkonNzOkvKbGXl73GStISAksRWAHBQED1rEPC0NkFCDeVZO7df\n"
+ + "SYLlsqKwV6uSh05Ra0F5XeniC12YpAyzoQyCGRS2wLaS822j0zUPXA8XLaO2blCu\n"
+ + "R+8sNu/oecMRcFK4S9NaApi3vdqBNhLiN1/Lpqn1LfB8uIO+eaUf4PmCWbaPgzSk\n"
+ + "qcPfKZmocNXdgLV5Q80n3hc2y2nrl+vDW2M+eVZuDHAok2BOD9uGKFfLAbaXLbX5\n"
+ + "btBW2L0UHtoEyiqkRfD6lX2laSLQmA6+eup7e4GS+s0vXBuVh8XEYddV6Yjt8H7/\n"
+ + "2thO41K5AQ0EQs6nRQEIAM/833UHK1DuFlOm7/n18dRMvs7BkXvg+hPquKWMG3be\n"
+ + "eE4sh1NG5DbRCdo6iacZLarWr3FDz7J9+wswRhtHCh3pGHEuaJk52vRjQxlkNh5F\n"
+ + "p5u2R4WF546bWqX45xPdLfHVTPyWB9q7aVxE+6Q+MHa6lMoyTVnTVCOy3nshiihw\n"
+ + "dxLsxaga+QmaL0bAR+dRcO6ucj7TDQXz1AJAVp26c0LXV9iErhFuuybUZKT0a9Aj\n"
+ + "FoumMZ6l+k30sSdjSjpBMsNvPos0dTPPRXUMu77o5sj+pHa4o8WctgB3o7BHQELp\n"
+ + "KgujZ2sKC9Nm395u6Q4cqUWihzb/Y7rIRuNHJarI7vUAEQEAAYkBJQQYAQIADwUC\n"
+ + "Qs6nRQIbDAUJAeEzgAAKCRA87HgbF94azRiBB/4vAyOOjUjK3lDWjHGs7mvEWJI/\n"
+ + "1MeLlGPswCSInJBa+HMiMI4tzq+hu5ejGThojNbmnL96GdzfDkMlP4Feyxb2rjtb\n"
+ + "NrD/R5tlXHmjX/QLzep4LCeMziP80fu8qUeiOej/Ecdny0w365PlMdt10RaYR8VE\n"
+ + "ZX/DAie6JfElnfQcG5q8TIOH3i71qxV+kIoPqKWfQ0MXrNEJ3BYFfDGdUt8U1Kq9\n"
+ + "OuIHVRgGS7mMSyjgNqqp7MBeMY+PFFZaZel5yoYVjb9d3L8XvVv2eoa/jPj5FUEU\n"
+ + "kE9uxNmwaD1PiV8DvBTYI+eQL4qzfu+3NTG2SfgQYtj5oiGHw8aL3U6QHDJb\n"
+ + "=d/Xp\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "HDJb\n"
+ + "=RrXv\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ /**
+ * A self-revoked key with no expiration.
+ *
+ * <pre>
+ * pub 2048R/7CA87821 2015-07-08 [revoked: 2015-07-08]
+ * Key fingerprint = E328 CAB1 1F7E B1BC 1451 ABA5 0855 2A17 7CA8 7821
+ * uid Testuser Four <test4@example.com>
+ * </pre>
+ */
+ static final TestKey key4() throws PGPException, IOException {
+ return new TestKey(
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "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\n"
+ + "=477N\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\n",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"
+ + "Version: GnuPG v1\n"
+ + "\n"
+ + "lQOYBFWdTZwBCAC1jukp5mlitfq2sAmdtx1s1VbWh+buDbBY2kWcxbbssczozFUP\n"
+ + "Ii67wPwjRbn3GM5+jY3GMsqKIrdyDlxeTxGWoU/qa2YkCQzgFGD/XJBqkVpP6osm\n"
+ + "qFYSP0xST1iBkatkMHq5KMjrX2q2bGVLlchLF9eHrWSefMcfff1Vs/Y8F2RCo38y\n"
+ + "gH88mbcvgyC+zq6Q2T3h5RiLK2IaZDNsn3uUoIMYHxI6oYtXXMSXRJlLJvamXVrB\n"
+ + "7QAq8L8cNikJjZMz+bHtLtGDyVVp9tqo4yvMrHe6BYmBUte3tPYQlDVdEo7UqepR\n"
+ + "uT7JbBOGBoD+9ngDrDggPUBAoa0h3VNOTyoDABEBAAEAB/4jqeZoOiACaV/Nygeh\n"
+ + "iOpJSiDsNDbrFRpKYdnhwT69APIQ2q5sshi+/dopbZVpkeBiIJk0UR7TAp3JVEPV\n"
+ + "rK92SMqjcCRYuMRkMeyZzMt7e4DjiN17ov6BSBjMZFSs4vnpTNKWk4ngHlaebe15\n"
+ + "6vq0sYK/XpKQxU7yAzQjxR190P/F+QEL98zVG/9uqM8PupfdSm4Smp2cIpfta+JD\n"
+ + "mO23HC6jAEm2RFwklovzgK3rbIjyiMuowIkAKx5xxRvpxMHf1l566b9zJrRi0xau\n"
+ + "vp4J/lnBJtTMzCbsaaFxhrj23xvTXaWR+UkaGPCv7wheXQ9K7NAHwmH8YrR+cZx7\n"
+ + "KbDlBADUTHZ+OhNslx/rkjRWrFuK9p49x7qxQc26kcqlGPbW6KOAMdUpwneQbhCG\n"
+ + "a36E/GAZgsgQ4SUqn37EVCtd2Y9Dp0inPAujcZXSwgDHev6ea7fzbxT9KLtEgvQN\n"
+ + "0vrFJDCPIt0wzGqNDw4wgFjF2rAafBO//Wu5K5QLW4hfzSguRQQA2u6DpVja/FYY\n"
+ + "UHVh2HLiB8th4T+qogOsBe5mKEsGRPXtAh7QzJu36C4PJyHeNlmlMx+15cCFnovj\n"
+ + "6cLpGn6ZP4okLyq2+VsW7wh/Vir+UZHoAO/cZRlOc1PsaQconcxxq30SsbaRQrAd\n"
+ + "YargKlXU7HMFiK34nkidBV6vVW0+P6cD/jYRInM983KXqX5bYvqsM1Zyvvlu6otD\n"
+ + "nG0F/nQYT7oaKKR46quDa+xHMxK8/Vu1+TabzY8XapnoYFaFvrl/d2rUBEZSoury\n"
+ + "z2yfTyeomft9MGGQsCGAJ95bVDT+jBoohnYwfwdC7HG3qk0aK/TxFyUqvMOX7SFe\n"
+ + "YT55n3HlD9InST+0IVRlc3R1c2VyIEZvdXIgPHRlc3Q0QGV4YW1wbGUuY29tPokB\n"
+ + "OAQTAQIAIgUCVZ1NnAIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQCFUq\n"
+ + "F3yoeCF7BwgAkpvUSHccaomwGsYfp5usOReWuh47uXHZpJPWpWw6EGIdnjPBOTXQ\n"
+ + "6vq9Kkkntvrow2kOAveQ2TSUBEryHBxx2/QBAqwX0vGJPOq1/IcVXmEHMzJAfS3Q\n"
+ + "R8Ypk0qDq/C/3gMQRP1Vw8LrLehAHukNQGt7ZU07wn3JsOD4qb7T+xelMxWqvkFO\n"
+ + "n7wEaQQEznL1XNiVOCUDq8bjRENjyq0rYu+UuZp1VptKUXmzPhKK1gbZQNHjgHiz\n"
+ + "agqfO1MC3b1Mjk0WU+AyedXk8X6xQA16yPf3o7N/2MQj2MWj4nAJXJXXbrmo1P7P\n"
+ + "vYn61J9DaYNiC5fJkJDc6nnq5QXnnFFeBJ0DmARVnU2cAQgAxDrCvphUP9rnbPc5\n"
+ + "5JgKq5NuMGlQacjxq0017V19HSobUALIekJGRpIy05ysRLGvbMqlXJMPtSbQoeOE\n"
+ + "j9YI6g1KEoaQc+cCLpLGe6veh8fhdE1Rv540uhq1r8CoS9i2/YWH3Za2YmLTjotI\n"
+ + "ojs7nduR/zLNeZAvLbkJuzVrQDSSReEsaAcaK+i/xeM7pxJEy9C/f3aeKla8Nt3y\n"
+ + "VShN/HEncYwdLtakg8y1f7tECapWMSQIhHIsEDdrlZ02Nf10qaLjMui722FOwdrl\n"
+ + "P1qITZh90Yjf30OoyZuMfnwmfPs6lzImZxtnz5MJscSvXJeuGJzRnE8LFbegTGwh\n"
+ + "5PYx8QARAQABAAf8CeTumd6jbN7USXXDyQdzjkguR6mfwN29dcY8YF4U52oOm3+w\n"
+ + "bR23XmqTvoDJXONatZEYOm093wP4hBktP3Vq2KZX5Ew9r2JoBUIoWOcHHvCQqSUW\n"
+ + "6KMJBJNBMv3zXnOscmcPvTgStS5HfYn/XRLAhEqkd2ov2x/OiS8p0vM0F7YYSOdu\n"
+ + "X6/nHeBCM5QSJl00kgcaeQYdIGL0bPv9DnoeAC2/yITEvtvs+MHZ7FjH8A45QjWn\n"
+ + "DwfVoLg7WOc3wJtqJ55/r/2pylrWz0YYM8s6I3gbDilCF+Wb8tEIOaWJEwY73J1/\n"
+ + "KQG5qlO3/hBlO80DtzNmi3ylRUuzGhTxQfvemwQA3EuZ+E48LJ3dwtdJhh5mFlWI\n"
+ + "Ket21e5v1mqMxuLhf5/2CYcifM08u3EsEUdIr7egF25Sea8otqmCYcG8FuB37VY/\n"
+ + "Hd4G/+YVVaaAB8EU6u64YfSswhzr0R2qWVLtkJr0EAephzdPdoUEtKDSdTxnXiDV\n"
+ + "3vSqLWtZekScLa979uMEAOQIodJwxSvveKQWILjK67ZJr56X8YQZWA6rFsr1xMY0\n"
+ + "N0GH+5k0k+tr4wT3H9uk9ZM1Z11G3c01mhzCNg5roFoKtftKUZRKxmbfjjDmAofl\n"
+ + "bA6EZ0WHLdOwDLLTuXK09IsjjSHq0YHOxIlgFzIreuoxtz27bEEGhVFQg7xb0Lgb\n"
+ + "A/9LP8i32L7/CHsuN0q4YjhJkkaB6JWUQMFqWwoAXALG3rnw/CGRYHmHpiAuSeHR\n"
+ + "dSlZzndVi5poNC/d27msTx7ZuWlN7nOyywHBCTWV/nstm2I9rDhrHK7Axgq0Vv0y\n"
+ + "bWAurUmEgDJHU3ZpsNVt4e30FooXIDLR4cnpRM7tILv39D4giQEfBBgBAgAJBQJV\n"
+ + "nU2cAhsMAAoJEAhVKhd8qHghjncH/05qMR96RQZvzA4WOQreRTtCr7IEJNmYA2uv\n"
+ + "B8kPI0GItRVArcg47MX+Sk38r86c3E6Uu3MjsHCTbU4+lIscAE5HIr72FmKDl4IQ\n"
+ + "LE6OH9fo/8ehAqtfp2BOjaXADe1USaWDJNlDtqpYRwnrEzxs7B3IS+5sImHYRq3Y\n"
+ + "lhk6XSiPVnzmA2WfWUsP0CDSVMk0caw52rjr1W4WwF3O43SgpEUEWabDZRf/9UrY\n"
+ + "Op7LYxSe0q41XbDCTZYoU+nxubo5q7ctf+m9VQi5CEH1uXFr80c0wRcrau4uD1So\n"
+ + "d7uzs7bfZX/LOdM7drgRPwCjJMhOiCS7AVqST5O4hSpBxg8dOOE=\n"
+ + "=5aNq\n"
+ + "-----END PGP PRIVATE KEY BLOCK-----\n");
+ }
+
+ // TODO(dborowitz): Figure out how to get gpg to revoke a key for someone
+ // else.
+
+ private final String pubArmored;
+ private final String secArmored;
+ private final PGPPublicKey pub;
+ private final PGPSecretKey sec;
+
+ private TestKey(String pubArmored, String secArmored)
+ throws PGPException, IOException {
+ this.pubArmored = pubArmored;
+ this.secArmored = secArmored;
+ BcKeyFingerprintCalculator fc = new BcKeyFingerprintCalculator();
+ this.pub = new PGPPublicKeyRing(newStream(pubArmored), fc).getPublicKey();
+ this.sec = new PGPSecretKeyRing(newStream(secArmored), fc).getSecretKey();
+ }
+
+ String getPublicKeyArmored() {
+ return pubArmored;
+ }
+
+ String getSecretKeyArmored() {
+ return secArmored;
+ }
+
+ PGPPublicKey getPublicKey() {
+ return pub;
+ }
+
+ PGPSecretKey getSecretKey() {
+ return sec;
+ }
+
+ long getKeyId() {
+ return pub.getKeyID();
+ }
+
+ String getFirstUserId() {
+ return (String) pub.getUserIDs().next();
+ }
+
+ PGPPrivateKey getPrivateKey() throws PGPException {
+ return sec.extractPrivateKey(
+ new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider())
+ // All test keys have no passphrase.
+ .build(new char[0]));
+ }
+
+ private static ArmoredInputStream newStream(String armored)
+ throws IOException {
+ return new ArmoredInputStream(
+ new ByteArrayInputStream(Constants.encode(armored)));
+ }
+
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
index 8d7866d..ac6d805 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
@@ -19,7 +19,6 @@
import static com.google.gerrit.reviewdb.client.Change.Status.DRAFT;
import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
-import static com.google.gerrit.reviewdb.client.Change.Status.SUBMITTED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -183,17 +182,17 @@
public void testGetPossibleStatus() throws Exception {
assertEquals(EnumSet.allOf(Change.Status.class), status("file:a"));
assertEquals(EnumSet.of(NEW), status("is:new"));
- assertEquals(EnumSet.of(SUBMITTED, DRAFT, MERGED, ABANDONED),
+ assertEquals(EnumSet.of(DRAFT, MERGED, ABANDONED),
status("-is:new"));
assertEquals(EnumSet.of(NEW, MERGED), status("is:new OR is:merged"));
EnumSet<Change.Status> none = EnumSet.noneOf(Change.Status.class);
assertEquals(none, status("is:new is:merged"));
- assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)"));
- assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)"));
+ assertEquals(none, status("(is:new is:draft) (is:merged)"));
+ assertEquals(none, status("(is:new is:draft) (is:merged)"));
- assertEquals(EnumSet.of(MERGED, SUBMITTED),
- status("(is:new is:draft) OR (is:merged OR is:submitted)"));
+ assertEquals(EnumSet.of(MERGED),
+ status("(is:new is:draft) OR (is:merged)"));
}
@Test
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 01f964b..f19987f 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
@@ -287,7 +287,7 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
- update.submit(ImmutableList.of(
+ update.merge(ImmutableList.of(
submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Code-Review", "NEED", null)),
@@ -314,7 +314,7 @@
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
- update.submit(ImmutableList.of(
+ update.merge(ImmutableList.of(
submitRecord("OK", null,
submitLabel("Code-Review", "OK", otherUser.getAccountId()))));
update.commit();
@@ -322,7 +322,7 @@
incrementPatchSet(c);
update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 2");
- update.submit(ImmutableList.of(
+ update.merge(ImmutableList.of(
submitRecord("OK", null,
submitLabel("Code-Review", "OK", changeOwner.getAccountId()))));
update.commit();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 49b61cc..c206902 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -108,7 +108,7 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
- update.submit(ImmutableList.of(
+ update.merge(ImmutableList.of(
submitRecord("NOT_READY", null,
submitLabel("Verified", "OK", changeOwner.getAccountId()),
submitLabel("Code-Review", "NEED", null)),
@@ -121,7 +121,7 @@
assertBodyEquals("Submit patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
- + "Status: submitted\n"
+ + "Status: merged\n"
+ "Submitted-with: NOT_READY\n"
+ "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+ "Submitted-with: NEED: Code-Review\n"
@@ -173,14 +173,14 @@
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubject("Submit patch set 1");
- update.submit(ImmutableList.of(
+ update.merge(ImmutableList.of(
submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
update.commit();
assertBodyEquals("Submit patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
- + "Status: submitted\n"
+ + "Status: merged\n"
+ "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
update.getRevision());
}
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 2bef633..dcff98e 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -8,6 +8,7 @@
'//gerrit-cache-h2:cache-h2',
'//gerrit-common:annotations',
'//gerrit-common:server',
+ '//gerrit-lucene:lucene',
'//gerrit-patch-jgit:server',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index c44dd88..06b1cf5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
+import com.google.gerrit.server.config.DownloadConfig;
import com.google.gerrit.sshd.CommandModule;
import com.google.gerrit.sshd.CommandName;
import com.google.gerrit.sshd.Commands;
@@ -23,14 +25,18 @@
/** Register the commands a Gerrit server supports. */
public class DefaultCommandModule extends CommandModule {
- public DefaultCommandModule(boolean slave) {
+ private final DownloadConfig downloadConfig;
+
+ public DefaultCommandModule(boolean slave, DownloadConfig downloadCfg) {
slaveMode = slave;
+ downloadConfig = downloadCfg;
}
@Override
protected void configure() {
final CommandName git = Commands.named("git");
final CommandName gerrit = Commands.named("gerrit");
+ CommandName index = Commands.named(gerrit, "index");
final CommandName logging = Commands.named(gerrit, "logging");
final CommandName plugin = Commands.named(gerrit, "plugin");
final CommandName testSubmit = Commands.named(gerrit, "test-submit");
@@ -51,8 +57,12 @@
command(gerrit, StreamEvents.class);
command(gerrit, VersionCommand.class);
command(gerrit, GarbageCollectionCommand.class);
- command(gerrit, "plugin").toProvider(new DispatchCommandProvider(plugin));
+ command(index).toProvider(new DispatchCommandProvider(index));
+ command(index, IndexActivateCommand.class);
+ command(index, IndexStartCommand.class);
+
+ command(gerrit, "plugin").toProvider(new DispatchCommandProvider(plugin));
command(plugin, PluginLsCommand.class);
command(plugin, PluginEnableCommand.class);
command(plugin, PluginInstallCommand.class);
@@ -68,10 +78,12 @@
command("scp").to(ScpCommand.class);
// Honor the legacy hyphenated forms as aliases for the non-hyphenated forms
- command("git-upload-pack").to(Commands.key(git, "upload-pack"));
- command(git, "upload-pack").to(Upload.class);
- command("git-upload-archive").to(Commands.key(git, "upload-archive"));
- command(git, "upload-archive").to(UploadArchive.class);
+ if (sshEnabled()) {
+ command("git-upload-pack").to(Commands.key(git, "upload-pack"));
+ command(git, "upload-pack").to(Upload.class);
+ command("git-upload-archive").to(Commands.key(git, "upload-archive"));
+ command(git, "upload-archive").to(UploadArchive.class);
+ }
command("suexec").to(SuExec.class);
listener().to(ShowCaches.StartupListener.class);
@@ -87,9 +99,11 @@
command(git, "receive-pack").to(NotSupportedInSlaveModeFailureCommand.class);
command(gerrit, "test-submit").to(NotSupportedInSlaveModeFailureCommand.class);
} else {
- command("git-receive-pack").to(Commands.key(git, "receive-pack"));
- command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
- command(git, "receive-pack").to(Commands.key(gerrit, "receive-pack"));
+ if (sshEnabled()) {
+ command("git-receive-pack").to(Commands.key(git, "receive-pack"));
+ command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
+ command(git, "receive-pack").to(Commands.key(gerrit, "receive-pack"));
+ }
command(gerrit, "test-submit").toProvider(
new DispatchCommandProvider(testSubmit));
}
@@ -114,4 +128,10 @@
alias(logging, "ls", ListLoggingLevelCommand.class);
alias(logging, "set", SetLoggingLevelCommand.class);
}
+
+ private boolean sshEnabled() {
+ return downloadConfig.getDownloadSchemes().contains(DownloadScheme.SSH)
+ || downloadConfig.getDownloadSchemes().contains(
+ DownloadScheme.DEFAULT_DOWNLOADS);
+ }
}
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
new file mode 100644
index 0000000..dc67ac3
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexActivateCommand.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import 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;
+import com.google.gerrit.lucene.ReindexerAlreadyRunningException;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "activate",
+ description = "Activate the latest index version available",
+ runsAt = MASTER)
+public class IndexActivateCommand extends SshCommand {
+
+ @Inject
+ private LuceneVersionManager luceneVersionManager;
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ try {
+ if (luceneVersionManager.activateLatestIndex()) {
+ stdout.println("Activated latest index version");
+ } else {
+ stdout.println("Not activating index, already using latest version");
+ }
+ } catch (ReindexerAlreadyRunningException e) {
+ throw new UnloggedFailure("Failed to activate latest index: "
+ + e.getMessage());
+ }
+ }
+}
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
new file mode 100644
index 0000000..1b3b819
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexStartCommand.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.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;
+import com.google.gerrit.lucene.ReindexerAlreadyRunningException;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "start", description = "Start the online reindexer",
+ runsAt = MASTER)
+public class IndexStartCommand extends SshCommand {
+
+ @Inject
+ private LuceneVersionManager luceneVersionManager;
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ try {
+ if (luceneVersionManager.startReindexer()) {
+ stdout.println("Reindexer started");
+ } else {
+ stdout.println("Nothing to reindex, index is already the latest version");
+ }
+ } catch (ReindexerAlreadyRunningException e) {
+ throw new UnloggedFailure("Failed to start reindexer: " + e.getMessage());
+ }
+ }
+}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 8968c81..22f3f0b 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
+import com.google.gerrit.server.config.DownloadConfig;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GerritServerConfigModule;
@@ -330,7 +331,8 @@
final List<Module> modules = new ArrayList<>();
modules.add(sysInjector.getInstance(SshModule.class));
modules.add(new SshHostKeyModule());
- modules.add(new DefaultCommandModule(false));
+ modules.add(new DefaultCommandModule(false,
+ sysInjector.getInstance(DownloadConfig.class)));
return sysInjector.createChildInjector(modules);
}
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index a84197f..1c2d6a3 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -1,13 +1,13 @@
include_defs('//lib/maven.defs')
REPO = GERRIT # Leave here even if set to MAVEN_CENTRAL.
-VERS = '4.0.1.201506240215-r.65-g3c33d09'
+VERS = '4.0.1.201506240215-r.78-g19b45da'
maven_jar(
name = 'jgit',
id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
- bin_sha1 = '44b6233933768ed82ccab7dddf620b1c57c0af75',
- src_sha1 = '1b50482ca2cf525592e4c5bd4024c58ddaf9e56f',
+ bin_sha1 = '05f061b49f595036cde5154cf2fdaa48f6685091',
+ src_sha1 = '9f32cdda73404326546ad853b942e7e4f596c873',
license = 'jgit',
repository = REPO,
unsign = True,
@@ -22,7 +22,7 @@
maven_jar(
name = 'jgit-servlet',
id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
- sha1 = '44efd7bc2c4bf9054bd37a28d28f20f15fa32e6a',
+ sha1 = '0a9da2e634f2c741627207773ce45ba15effeeec',
license = 'jgit',
repository = REPO,
deps = [':jgit'],
@@ -36,7 +36,7 @@
maven_jar(
name = 'jgit-archive',
id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
- sha1 = '8096326e060578bfa74435e9d758ca0b1f1e384b',
+ sha1 = '6f37fa68ba81e9e669f20383d248f4098a23fb19',
license = 'jgit',
repository = REPO,
deps = [':jgit',
@@ -53,7 +53,7 @@
maven_jar(
name = 'junit',
id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
- sha1 = 'e4442bdcc60672257a825d326bfe457218014ec3',
+ sha1 = 'eab0158a98c9bbe966a1d2f2d8ec830dc23b2d9e',
license = 'DO_NOT_DISTRIBUTE',
repository = REPO,
unsign = True,
diff --git a/plugins/download-commands b/plugins/download-commands
index de482c4..334f725 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit de482c42d46bc6254370ccb33fe9700697c05e26
+Subproject commit 334f7253855551bea40f0f27b68e3d7aa71fe4af
diff --git a/plugins/replication b/plugins/replication
index 852dc51..7a3a89f 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 852dc51d1a9d41f69f3263631767de41f1bc81dd
+Subproject commit 7a3a89fc983b9bcb5b2c965affd7a83bb6b10bb2
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 5d6522a..d81e2d6 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 5d6522a4adf13dc34a9b9fe573c0a7370d9cf94e
+Subproject commit d81e2d6d3edc27c7aceea47518cbb03fb5590f11
diff --git a/tools/download_file.py b/tools/download_file.py
index 061d67c..97d982f 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -25,7 +25,11 @@
from zipfile import ZipFile, BadZipfile, LargeZipFile
GERRIT_HOME = path.expanduser('~/.gerritcodereview')
-CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache')
+CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache', 'downloaded-artifacts')
+# LEGACY_CACHE_DIR is only used to allow existing workspaces to move already
+# downloaded files to the new cache directory.
+# Please remove after 3 months (2015-10-07).
+LEGACY_CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache')
LOCAL_PROPERTIES = 'local.properties'
@@ -85,6 +89,15 @@
name = '%s-%s' % (path.basename(args.o), h)
return path.join(CACHE_DIR, name)
+# Please remove after 3 months (2015-10-07). See LEGACY_CACHE_DIR above.
+def legacy_cache_entry(args):
+ if args.v:
+ h = args.v
+ else:
+ h = sha1(args.u.encode('utf-8')).hexdigest()
+ name = '%s-%s' % (path.basename(args.o), h)
+ return path.join(LEGACY_CACHE_DIR, name)
+
opts = OptionParser()
opts.add_option('-o', help='local output file')
@@ -103,8 +116,19 @@
redirects = download_properties(root_dir)
cache_ent = cache_entry(args)
+legacy_cache_ent = legacy_cache_entry(args)
src_url = resolve_url(args.u, redirects)
+# Please remove after 3 months (2015-10-07). See LEGACY_CACHE_DIR above.
+if not path.exists(cache_ent) and path.exists(legacy_cache_ent):
+ try:
+ safe_mkdirs(path.dirname(cache_ent))
+ except OSError as err:
+ print('error creating directory %s: %s' %
+ (path.dirname(cache_ent), err), file=stderr)
+ exit(1)
+ shutil.move(legacy_cache_ent, cache_ent)
+
if not path.exists(cache_ent):
try:
safe_mkdirs(path.dirname(cache_ent))
diff --git a/tools/version.py b/tools/version.py
index eb1e076..e2d9ead 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -19,12 +19,6 @@
import re
import sys
-version_text = """# Maven style API version (e.g. '2.x-SNAPSHOT').
-# Used by :api_install and :api_deploy targets
-# when talking to the destination repository.
-#
-GERRIT_VERSION = '%s'
-"""
parser = OptionParser()
opts, args = parser.parse_args()
@@ -33,31 +27,34 @@
elif len(args) > 1:
parser.error('too many arguments')
-new_version = args[0]
-pattern = re.compile(r'(\s*)<version>[-.\w]+</version>')
+DEST_PATTERN = r'\g<1>%s\g<3>' % args[0]
+
+def replace_in_file(filename, src_pattern):
+ try:
+ f = open(filename, "r")
+ s = f.read()
+ f.close()
+ s = re.sub(src_pattern, DEST_PATTERN, s)
+ f = open(filename, "w")
+ f.write(s)
+ f.close()
+ except IOError as err:
+ print('error updating %s: %s' % (filename, err), file=sys.stderr)
+
+
+src_pattern = re.compile(r'^(\s*<version>)([-.\w]+)(</version>\s*)$',
+ re.MULTILINE)
for project in ['gerrit-extension-api', 'gerrit-plugin-api',
'gerrit-plugin-archetype', 'gerrit-plugin-gwt-archetype',
'gerrit-plugin-gwtui', 'gerrit-plugin-js-archetype',
'gerrit-war']:
pom = os.path.join(project, 'pom.xml')
- try:
- outxml = ""
- found = False
- for line in open(pom, "r"):
- m = pattern.match(line)
- if m and not found:
- outxml += "%s<version>%s</version>\n" % (m.group(1), new_version)
- found = True
- else:
- outxml += line
- with open(pom, "w") as outfile:
- outfile.write(outxml)
- except IOError as err:
- print('error updating %s: %s' % (pom, err), file=sys.stderr)
+ replace_in_file(pom, src_pattern)
-try:
- with open('VERSION', "w") as version_file:
- version_file.write(version_text % new_version)
-except IOError as err:
- print('error updating VERSION: %s' % err, file=sys.stderr)
+src_pattern = re.compile(r"^(GERRIT_VERSION = ')([-.\w]+)(')$", re.MULTILINE)
+replace_in_file('VERSION', src_pattern)
+
+src_pattern = re.compile(r'^(\s*-DarchetypeVersion=)([-.\w]+)(\s*\\)$',
+ re.MULTILINE)
+replace_in_file(os.path.join('Documentation', 'dev-plugins.txt'), src_pattern)