diff --git a/release-tools/application-local.template b/release-tools/application-local.template
index 065ebdf..277a68c 100644
--- a/release-tools/application-local.template
+++ b/release-tools/application-local.template
@@ -20,9 +20,9 @@ jira.password=
jira.url=https://jira.spring.io
# GPG
-deployment.gpg.keyname=
-deployment.gpg.password=
-# deployment.gpg.executable=/usr/local/bin/gpg2
+gpg.keyname=
+gpg.password=
+# gpg.executable=/usr/local/bin/gpg2
# A GitHub token with user:email, read:user and read:org scopes.
# User needs to be part of the Spring team on GitHub as well.
diff --git a/release-tools/pom.xml b/release-tools/pom.xml
index 30eef71..d93dc5f 100644
--- a/release-tools/pom.xml
+++ b/release-tools/pom.xml
@@ -96,7 +96,7 @@
org.eclipse.jgit
org.eclipse.jgit
- 4.2.0.201601211800-r
+ 5.7.0.202003110725-r
diff --git a/release-tools/readme.md b/release-tools/readme.md
index f12270b..4bd1ebf 100644
--- a/release-tools/readme.md
+++ b/release-tools/readme.md
@@ -16,6 +16,10 @@ Add an `application-local.properties` to the project root and add the following
- `maven.mavenHome` - Pointing to the location of your Maven installation.
- `deployment.api-key` - The API key to use for artifact promotion.
- `deployment.password` - The password of the deployment user (buildmaster).
+- `gpg.keyname` - The GPG key name.
+- `gpg.password` - The password of your GPG key.
+- `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2` or `/usr/local/bin/gpg`.
+
See `application-local.template` for details.
diff --git a/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java b/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java
index 09367fc..11d020d 100644
--- a/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java
+++ b/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java
@@ -34,9 +34,9 @@ import org.springframework.data.release.build.Pom.Artifact;
import org.springframework.data.release.deployment.DefaultDeploymentInformation;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentProperties;
-import org.springframework.data.release.deployment.DeploymentProperties.Gpg;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
+import org.springframework.data.release.model.Gpg;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
@@ -66,6 +66,7 @@ class MavenBuildSystem implements BuildSystem {
Logger logger;
MavenRuntime mvn;
DeploymentProperties properties;
+ Gpg gpg;
/*
* (non-Javadoc)
@@ -331,7 +332,7 @@ class MavenBuildSystem implements BuildSystem {
logger.log(module, "Deploying artifacts to Sonatype OSS Nexus…");
- Gpg gpg = properties.getGpg();
+ Gpg gpg = this.gpg.isGpgAvailable() ? this.gpg : properties.getGpg();
CommandLine arguments = CommandLine.of(Goal.DEPLOY, //
profile("ci,release,central"), //
diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java
index 6b5f54a..0492cb4 100644
--- a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java
+++ b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java
@@ -20,6 +20,8 @@ import lombok.Data;
import java.net.URI;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
+import org.springframework.data.release.model.Gpg;
import org.springframework.data.release.model.Password;
import org.springframework.data.release.utils.HttpBasicCredentials;
import org.springframework.stereotype.Component;
@@ -74,6 +76,11 @@ public class DeploymentProperties {
return repositoryPrefix.concat(distributionRepository);
}
+ @DeprecatedConfigurationProperty(reason = "Moved to gpg.", replacement = "gpg")
+ public Gpg getGpg() {
+ return gpg;
+ }
+
/**
* Returns the URI of the staging repository.
*
@@ -119,9 +126,4 @@ public class DeploymentProperties {
}
}
- @Data
- public static class Gpg {
- private String keyname, executable;
- private Password password;
- }
}
diff --git a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java
index 6b821f9..0f66d36 100644
--- a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java
+++ b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java
@@ -21,8 +21,10 @@ import lombok.experimental.FieldDefaults;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -32,22 +34,31 @@ import java.util.stream.Stream;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CherryPickResult;
import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
+import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialItem.CharArrayType;
+import org.eclipse.jgit.transport.CredentialItem.InformationalMessage;
+import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.TagOpt;
+import org.eclipse.jgit.transport.URIish;
+
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.model.ArtifactVersion;
+import org.springframework.data.release.model.Gpg;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
@@ -63,6 +74,7 @@ import org.springframework.util.Assert;
* Component to execute Git related operations.
*
* @author Oliver Gierke
+ * @author Mark Paluch
*/
@Component
@RequiredArgsConstructor
@@ -78,6 +90,7 @@ public class GitOperations {
Logger logger;
PluginRegistry issueTracker;
GitProperties gitProperties;
+ Gpg gpg;
/**
* Returns the {@link GitProject} for the given {@link Project}.
@@ -93,7 +106,6 @@ public class GitOperations {
* Resets the repositories for all modules of the given {@link Train}.
*
* @param train must not be {@literal null}.
- * @throws Exception
*/
public void reset(TrainIteration train) {
@@ -437,16 +449,25 @@ public class GitOperations {
String author = gitProperties.getAuthor();
String email = gitProperties.getEmail();
- logger.log(module, "git commit -m \"%s\" --author=\"%s <%s>\"", commit, author, email);
+ logger.log(module, "git commit -m \"%s\" %s --author=\"%s <%s>\"", commit,
+ gpg.isGpgAvailable() ? "-S" + gpg.getKeyname() : "", author, email);
doWithGit(project, git -> {
- git.commit()//
+ CommitCommand commitCommand = git.commit()//
.setMessage(commit.toString())//
.setAuthor(author, email)//
.setCommitter(author, email)//
- .setAll(true)//
- .call();
+ .setAll(true);
+
+ if (gpg.isGpgAvailable()) {
+ commitCommand.setSign(true).setSigningKey(gpg.getKeyname())
+ .setCredentialsProvider(new GpgPassphraseProvider(gpg));
+ } else {
+ commitCommand.setSign(false);
+ }
+
+ commitCommand.call();
});
}
@@ -783,4 +804,54 @@ public class GitOperations {
private static interface VoidGitCallback {
void doWithGit(Git git) throws Exception;
}
+
+ /**
+ * {@link CredentialsProvider} for GPG Keys used with JGit Commit Signing.
+ */
+ private static class GpgPassphraseProvider extends CredentialsProvider {
+
+ private final Gpg gpg;
+
+ private GpgPassphraseProvider(Gpg gpg) {
+ this.gpg = gpg;
+ }
+
+ @Override
+ public boolean isInteractive() {
+ return false;
+ }
+
+ @Override
+ public boolean supports(CredentialItem... items) {
+
+ boolean matchesKey = matchesKey(items);
+ boolean hasSettableCharArray = Arrays.stream(items).anyMatch(CharArrayType.class::isInstance);
+
+ return matchesKey && hasSettableCharArray;
+ }
+
+ private boolean matchesKey(CredentialItem[] items) {
+ return Arrays.stream(items).filter(InformationalMessage.class::isInstance) //
+ .map(CredentialItem::getPromptText) //
+ .map(it -> it.toLowerCase(Locale.US)) //
+ .anyMatch(it -> it.contains(gpg.getKeyname().toLowerCase(Locale.US)));
+ }
+
+ @Override
+ public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem {
+
+ if (!matchesKey(items)) {
+ return false;
+ }
+
+ for (CredentialItem item : items) {
+ if (item instanceof CharArrayType) {
+ ((CharArrayType) item).setValueNoCopy(gpg.getPassword().toString().toCharArray());
+
+ return true;
+ }
+ }
+ return false;
+ }
+ }
}
diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Gpg.java b/release-tools/src/main/java/org/springframework/data/release/model/Gpg.java
new file mode 100644
index 0000000..3d7bad1
--- /dev/null
+++ b/release-tools/src/main/java/org/springframework/data/release/model/Gpg.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * 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 org.springframework.data.release.model;
+
+import lombok.Data;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Oliver Gierke
+ * @author Mark Paluch
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "gpg")
+public class Gpg {
+ private String keyname, executable;
+ private Password password;
+
+ public boolean isGpgAvailable() {
+ return this.password != null && StringUtils.hasText(keyname);
+ }
+}
diff --git a/release-tools/src/main/resources/application.properties b/release-tools/src/main/resources/application.properties
index fa3b02e..f4b5380 100644
--- a/release-tools/src/main/resources/application.properties
+++ b/release-tools/src/main/resources/application.properties
@@ -1,6 +1,7 @@
spring.main.banner-mode=off
io.work-dir=~/temp/spring-data-shell/workspace
io.logs=logs
+
# Maven setup
maven.local-repository=~/temp/spring-data-shell/repository
maven.plugins.versions=org.codehaus.mojo:versions-maven-plugin:2.2
@@ -10,12 +11,15 @@ deployment.staging-repository=libs-staging-local
deployment.distribution-repository=temp-private-local
deployment.username=buildmaster
#deployment.password <- local
+
# GPG setup
-deployment.gpg.executable=/usr/local/bin/gpg2
-# deployment.gpg.keyname
-# deployment.gpg.password
+gpg.executable=/usr/local/bin/gpg2
+# gpg.keyname
+# gpg.password
+
# JIRA
jira.api-url=https://jira.spring.io
+
# GitHub
github.api-url=https://api.github.com
logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(%-20.20logger{19}){cyan}%clr(:){faint} %m%n%wEx