summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.pgm
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2021-02-25 10:29:07 +0100
committerMatthias Sohn <matthias.sohn@sap.com>2021-02-28 00:58:04 +0100
commitf6597971991e3350df568b0cde05c014dcd69c47 (patch)
treecb61592af3f53da45174beed517b3284d7bd55c6 /org.eclipse.jgit.pgm
parent286ad23cb56ffeac77d4bfd03be575358fd5217c (diff)
parent789c0479a9294417db0375cce9f1949fe9052d8c (diff)
downloadjgit-f6597971991e3350df568b0cde05c014dcd69c47.tar.gz
jgit-f6597971991e3350df568b0cde05c014dcd69c47.zip
Merge branch 'master' into next
* master: (143 commits) Prepare 5.11.0-SNAPSHOT builds JGit v5.11.0.202102240950-m3 [releng] japicmp: update last release version IgnoreNode: include path to file for invalid .gitignore patterns FastIgnoreRule: include bad pattern in log message init: add config option to set default for the initial branch name init: allow specifying the initial branch name for the new repository Fail clone if initial branch doesn't exist in remote repository GPG: fix reading unprotected old-format secret keys Update Orbit to S20210216215844 Add missing bazel dependency for o.e.j.gpg.bc.test GPG: handle extended private key format dfs: handle short copies [GPG] Provide a factory for the BouncyCastleGpgSigner Fix boxing warnings GPG: compute the keygrip to find a secret key GPG signature verification via BouncyCastle Post commit hook failure should not cause commit failure Allow to define additional Hook classes outside JGit GitHook: use default charset for output and error streams ... Change-Id: I689f4070e79f4a0ac1c02b35698ccaab68ad2f34
Diffstat (limited to 'org.eclipse.jgit.pgm')
-rw-r--r--org.eclipse.jgit.pgm/META-INF/MANIFEST.MF3
-rw-r--r--org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin1
-rw-r--r--org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties19
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java11
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java46
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java8
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java44
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java2
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java56
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java107
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java4
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java54
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java140
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java3
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java56
15 files changed, 339 insertions, 215 deletions
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 6822784f77..c56224e3a9 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -8,6 +8,7 @@ Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.servlet;version="[3.1.0,4.0.0)",
+ org.apache.commons.logging;version="[1.2,2.0)",
org.eclipse.jetty.server;version="[9.4.5,10.0.0)",
org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)",
org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)",
@@ -22,12 +23,10 @@ Import-Package: javax.servlet;version="[3.1.0,4.0.0)",
org.eclipse.jgit.dircache;version="[6.0.0,6.1.0)",
org.eclipse.jgit.errors;version="[6.0.0,6.1.0)",
org.eclipse.jgit.gitrepo;version="[6.0.0,6.1.0)",
- org.eclipse.jgit.internal.ketch;version="[6.0.0,6.1.0)",
org.eclipse.jgit.internal.storage.file;version="[6.0.0,6.1.0)",
org.eclipse.jgit.internal.storage.io;version="[6.0.0,6.1.0)",
org.eclipse.jgit.internal.storage.pack;version="[6.0.0,6.1.0)",
org.eclipse.jgit.internal.storage.reftable;version="[6.0.0,6.1.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[6.0.0,6.1.0)",
org.eclipse.jgit.lfs;version="[6.0.0,6.1.0)",
org.eclipse.jgit.lfs.server;version="[6.0.0,6.1.0)",
org.eclipse.jgit.lfs.server.fs;version="[6.0.0,6.1.0)",
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index 062b9643a3..e645255e96 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -47,7 +47,6 @@ org.eclipse.jgit.pgm.debug.MakeCacheTree
org.eclipse.jgit.pgm.debug.ReadDirCache
org.eclipse.jgit.pgm.debug.ReadReftable
org.eclipse.jgit.pgm.debug.RebuildCommitGraph
-org.eclipse.jgit.pgm.debug.RebuildRefTree
org.eclipse.jgit.pgm.debug.ShowCacheTree
org.eclipse.jgit.pgm.debug.ShowCommands
org.eclipse.jgit.pgm.debug.ShowDirCache
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index 6112a272e4..83846ee8e9 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -77,14 +77,15 @@ invalidHttpProxyOnlyHttpSupported=Invalid http_proxy: {0}: Only http supported.
invalidRecurseSubmodulesMode=Invalid recurse submodules mode: {0}
invalidUntrackedFilesMode=Invalid untracked files mode ''{0}''
jgitVersion=jgit version {0}
-lineFormat={0}
-listeningOn=Listening on {0}
lfsNoAccessKey=No accessKey in {0}
lfsNoSecretKey=No secretKey in {0}
lfsProtocolUrl=LFS protocol URL: {0}
lfsStoreDirectory=LFS objects stored in: {0}
lfsStoreUrl=LFS store URL: {0}
lfsUnknownStoreType="Unknown LFS store type: {0}"
+lineFormat={0}
+listeningOn=Listening on {0}
+logNoSignatureVerifier="No signature verifier available"
mergeConflict=CONFLICT(content): Merge conflict in {0}
mergeCheckoutConflict=error: Your local changes to the following files would be overwritten by merge:
mergeFailed=Automatic merge failed; fix conflicts and then commit the result
@@ -118,7 +119,6 @@ metaVar_file=FILE
metaVar_filepattern=filepattern
metaVar_gitDir=GIT_DIR
metaVar_hostName=HOSTNAME
-metaVar_ketchServerType=SERVERTYPE
metaVar_lfsStorage=STORAGE
metaVar_linesOfContext=lines
metaVar_message=message
@@ -143,6 +143,7 @@ metaVar_s3Region=REGION
metaVar_s3StorageClass=STORAGE-CLASS
metaVar_seconds=SECONDS
metaVar_service=SERVICE
+metaVar_tagLocalUser=<GPG key ID>
metaVar_treeish=tree-ish
metaVar_uriish=uri-ish
metaVar_url=URL
@@ -246,7 +247,6 @@ usage_DisplayTheVersionOfJgit=Display the version of jgit
usage_Gc=Cleanup unnecessary files and optimize the local repository
usage_Glog=View commit history as a graph
usage_IndexPack=Build pack index file for an existing packed archive
-usage_ketchServerType=Ketch server type
usage_LFSDirectory=Directory to store large objects
usage_LFSPort=Server http port
usage_LFSRunStore=Store (fs | s3), store lfs objects in file system or Amazon S3
@@ -266,8 +266,6 @@ usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved
usage_PrunePreserved=Remove the preserved subdirectory containing previously preserved old pack files before repacking, and before preserving more old pack files
usage_ReadDirCache= Read the DirCache 100 times
usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
-usage_RebuildRefTree=Copy references into a RefTree
-usage_RebuildRefTreeEnable=set extensions.refStorage = reftree
usage_Remote=Manage set of tracked repositories
usage_RepositoryToReadFrom=Repository to read from
usage_RepositoryToReceiveInto=Repository to receive into
@@ -414,6 +412,7 @@ usage_show=Display one commit
usage_showRefNamesMatchingCommits=Show ref names matching commits
usage_showPatch=display patch
usage_showNotes=Add this ref to the list of note branches from which notes are displayed
+usage_showSignature=Verify signatures of signed commits in the log
usage_showTimeInMilliseconds=Show mtime in milliseconds
usage_squash=Squash commits as if a real merge happened, but do not make a commit or move the HEAD.
usage_srcPrefix=show the source prefix instead of "a/"
@@ -421,13 +420,19 @@ usage_sshDriver=Selects the built-in ssh library to use, JSch or Apache MINA ssh
usage_symbolicVersionForTheProject=Symbolic version for the project
usage_tags=fetch all tags
usage_notags=do not fetch tags
+usage_tagAnnotated=create an annotated tag, unsigned unless -s or -u are given, or config tag.gpgSign is true
usage_tagDelete=delete tag
-usage_tagMessage=tag message
+usage_tagLocalUser=create a signed annotated tag using the specified GPG key ID
+usage_tagMessage=create an annotated tag with the given message, unsigned unless -s or -u are given, or config tag.gpgSign is true, or tar.forceSignAnnotated is true and -a is not given
+usage_tagSign=create a signed annotated tag
+usage_tagNoSign=suppress signing the tag
+usage_tagVerify=Verify the GPG signature
usage_untrackedFilesMode=show untracked files
usage_updateRef=reference to update
usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository
usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream
usage_checkoutBranchAfterClone=check out named branch instead of remote's HEAD
+usage_initialBranch=initial branch of the newly created repository (default 'master', can be configured via config option init.defaultBranch)
usage_viewCommitHistory=View commit history
usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from other branches and commits.
usernameFor=Username for {0}:
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index 8f80d6d70e..f28915d3fa 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -18,6 +18,7 @@ import java.util.Collection;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
+import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.TextProgressMonitor;
@@ -50,6 +51,9 @@ class Clone extends AbstractFetchCommand implements CloneCommand.Callback {
@Option(name = "--recurse-submodules", usage = "usage_recurseSubmodules")
private boolean cloneSubmodules;
+ @Option(name = "--timeout", metaVar = "metaVar_seconds", usage = "usage_abortConnectionIfNoActivity")
+ int timeout = -1;
+
@Argument(index = 0, required = true, metaVar = "metaVar_uriish")
private String sourceUri;
@@ -90,9 +94,8 @@ class Clone extends AbstractFetchCommand implements CloneCommand.Callback {
CloneCommand command = Git.cloneRepository();
command.setURI(sourceUri).setRemote(remoteName).setBare(isBare)
- .setMirror(isMirror)
- .setNoCheckout(noCheckout).setBranch(branch)
- .setCloneSubmodules(cloneSubmodules);
+ .setMirror(isMirror).setNoCheckout(noCheckout).setBranch(branch)
+ .setCloneSubmodules(cloneSubmodules).setTimeout(timeout);
command.setGitDir(gitdir == null ? null : new File(gitdir));
command.setDirectory(localNameF);
@@ -108,6 +111,8 @@ class Clone extends AbstractFetchCommand implements CloneCommand.Callback {
db = command.call().getRepository();
if (msgs && db.resolve(Constants.HEAD) == null)
outw.println(CLIText.get().clonedEmptyRepository);
+ } catch (TransportException e) {
+ throw die(e.getMessage(), e);
} catch (InvalidRemoteException e) {
throw die(MessageFormat.format(CLIText.get().doesNotExist,
sourceUri), e);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index bf9102552c..f987f2c806 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -13,19 +13,12 @@ package org.eclipse.jgit.pgm;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
-import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.internal.ketch.KetchLeader;
-import org.eclipse.jgit.internal.ketch.KetchLeaderCache;
-import org.eclipse.jgit.internal.ketch.KetchPreReceive;
-import org.eclipse.jgit.internal.ketch.KetchSystem;
-import org.eclipse.jgit.internal.ketch.KetchText;
-import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -33,10 +26,7 @@ import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.DaemonClient;
import org.eclipse.jgit.transport.DaemonService;
-import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.resolver.FileResolver;
-import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.kohsuke.args4j.Argument;
@@ -71,13 +61,6 @@ class Daemon extends TextBuiltin {
@Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk")
boolean exportAll;
- @Option(name = "--ketch", metaVar = "metaVar_ketchServerType", usage = "usage_ketchServerType")
- KetchServerType ketchServerType;
-
- enum KetchServerType {
- LEADER;
- }
-
@Argument(required = true, metaVar = "metaVar_directory", usage = "usage_directoriesToExport")
List<File> directory = new ArrayList<>();
@@ -102,9 +85,9 @@ class Daemon extends TextBuiltin {
}
cfg = new FileBasedConfig(configFile, FS.DETECTED);
}
- cfg.load();
- new WindowCacheConfig().fromConfig(cfg).install();
- packConfig.fromConfig(cfg);
+ cfg.load();
+ new WindowCacheConfig().fromConfig(cfg).install();
+ packConfig.fromConfig(cfg);
int threads = packConfig.getThreads();
if (threads <= 0)
@@ -137,9 +120,6 @@ class Daemon extends TextBuiltin {
service(d, n).setOverridable(true);
for (String n : forbidOverride)
service(d, n).setOverridable(false);
- if (ketchServerType == KetchServerType.LEADER) {
- startKetchLeader(d);
- }
d.start();
outw.println(MessageFormat.format(CLIText.get().listeningOn, d.getAddress()));
}
@@ -162,24 +142,4 @@ class Daemon extends TextBuiltin {
throw die(MessageFormat.format(CLIText.get().serviceNotSupported, n));
return svc;
}
-
- private void startKetchLeader(org.eclipse.jgit.transport.Daemon daemon) {
- KetchSystem system = new KetchSystem();
- final KetchLeaderCache leaders = new KetchLeaderCache(system);
- final ReceivePackFactory<DaemonClient> factory;
-
- factory = daemon.getReceivePackFactory();
- daemon.setReceivePackFactory((DaemonClient req, Repository repo) -> {
- ReceivePack rp = factory.create(req, repo);
- KetchLeader leader;
- try {
- leader = leaders.get(repo);
- } catch (URISyntaxException err) {
- throw new ServiceNotEnabledException(
- KetchText.get().invalidFollowerUri, err);
- }
- rp.setPreReceiveHook(new KetchPreReceive(leader));
- return rp;
- });
- }
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java
index 7f59ef43dc..7a0d96d419 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java
@@ -24,6 +24,7 @@ import org.eclipse.jgit.api.InitCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.util.StringUtils;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -32,6 +33,10 @@ class Init extends TextBuiltin {
@Option(name = "--bare", usage = "usage_CreateABareRepository")
private boolean bare;
+ @Option(name = "--initial-branch", aliases = { "-b" },
+ metaVar = "metaVar_branchName", usage = "usage_initialBranch")
+ private String branch;
+
@Argument(index = 0, metaVar = "metaVar_directory")
private String directory;
@@ -54,6 +59,9 @@ class Init extends TextBuiltin {
}
Repository repository;
try {
+ if (!StringUtils.isEmptyOrNull(branch)) {
+ command.setInitialBranch(branch);
+ }
repository = command.call().getRepository();
outw.println(MessageFormat.format(
CLIText.get().initializedEmptyGitRepositoryIn,
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index 55efd23c6a..353b64b9be 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2010, Google Inc.
- * Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2006, 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, 2021, Shawn O. Pearce <spearce@spearce.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -31,12 +31,17 @@ import org.eclipse.jgit.diff.RenameDetector;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.GpgSignatureVerifier;
+import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
+import org.eclipse.jgit.lib.GpgSignatureVerifierFactory;
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.notes.NoteMap;
import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.internal.VerificationUtils;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.util.GitDateFormatter;
@@ -68,6 +73,9 @@ class Log extends RevWalkTextBuiltin {
additionalNoteRefs.add(notesRef);
}
+ @Option(name = "--show-signature", usage = "usage_showSignature")
+ private boolean showSignature;
+
@Option(name = "--date", usage = "usage_date")
void dateFormat(String date) {
if (date.toLowerCase(Locale.ROOT).equals(date))
@@ -147,6 +155,10 @@ class Log extends RevWalkTextBuiltin {
// END -- Options shared with Diff
+ private GpgSignatureVerifier verifier;
+
+ private GpgConfig config;
+
Log() {
dateFormatter = new GitDateFormatter(Format.DEFAULT);
}
@@ -161,6 +173,7 @@ class Log extends RevWalkTextBuiltin {
/** {@inheritDoc} */
@Override
protected void run() {
+ config = new GpgConfig(db.getConfig());
diffFmt.setRepository(db);
try {
diffFmt.setPathFilter(pathFilter);
@@ -197,6 +210,9 @@ class Log extends RevWalkTextBuiltin {
throw die(e.getMessage(), e);
} finally {
diffFmt.close();
+ if (verifier != null) {
+ verifier.clear();
+ }
}
}
@@ -229,6 +245,9 @@ class Log extends RevWalkTextBuiltin {
}
outw.println();
+ if (showSignature) {
+ showSignature(c);
+ }
final PersonIdent author = c.getAuthorIdent();
outw.println(MessageFormat.format(CLIText.get().authorInfo, author.getName(), author.getEmailAddress()));
outw.println(MessageFormat.format(CLIText.get().dateInfo,
@@ -252,6 +271,27 @@ class Log extends RevWalkTextBuiltin {
outw.flush();
}
+ private void showSignature(RevCommit c) throws IOException {
+ if (c.getRawGpgSignature() == null) {
+ return;
+ }
+ if (verifier == null) {
+ GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory
+ .getDefault();
+ if (factory == null) {
+ throw die(CLIText.get().logNoSignatureVerifier, null);
+ }
+ verifier = factory.getVerifier();
+ }
+ SignatureVerification verification = verifier.verifySignature(c,
+ config);
+ if (verification == null) {
+ return;
+ }
+ VerificationUtils.writeVerification(outw, verification,
+ verifier.getName(), c.getCommitterIdent());
+ }
+
/**
* @param c
* @return <code>true</code> if at least one note was printed,
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
index 055b48a157..83446ccd53 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
@@ -79,7 +79,7 @@ class LsRemote extends TextBuiltin {
private void show(Ref ref, String name)
throws IOException {
- outw.print("ref: ");
+ outw.print("ref: "); //$NON-NLS-1$
outw.print(ref.getName());
outw.print('\t');
outw.print(name);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
index 5f9551e529..3beab60a8b 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
@@ -29,10 +29,15 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.GpgSignatureVerifier;
+import org.eclipse.jgit.lib.GpgSignatureVerifierFactory;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.internal.VerificationUtils;
import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
@@ -41,6 +46,7 @@ import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.RawParseUtils;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -58,6 +64,9 @@ class Show extends TextBuiltin {
@Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class)
protected TreeFilter pathFilter = TreeFilter.ALL;
+ @Option(name = "--show-signature", usage = "usage_showSignature")
+ private boolean showSignature;
+
// BEGIN -- Options shared with Diff
@Option(name = "-p", usage = "usage_showPatch")
boolean showPatch;
@@ -219,13 +228,20 @@ class Show extends TextBuiltin {
}
outw.println();
- final String[] lines = tag.getFullMessage().split("\n"); //$NON-NLS-1$
- for (String s : lines) {
- outw.print(" "); //$NON-NLS-1$
- outw.print(s);
- outw.println();
+ String fullMessage = tag.getFullMessage();
+ if (!fullMessage.isEmpty()) {
+ String[] lines = tag.getFullMessage().split("\n"); //$NON-NLS-1$
+ for (String s : lines) {
+ outw.println(s);
+ }
+ }
+ byte[] rawSignature = tag.getRawGpgSignature();
+ if (rawSignature != null) {
+ String[] lines = RawParseUtils.decode(rawSignature).split("\n"); //$NON-NLS-1$
+ for (String s : lines) {
+ outw.println(s);
+ }
}
-
outw.println();
}
@@ -253,6 +269,10 @@ class Show extends TextBuiltin {
c.getId().copyTo(outbuffer, outw);
outw.println();
+ if (showSignature) {
+ showSignature(c);
+ }
+
final PersonIdent author = c.getAuthorIdent();
outw.println(MessageFormat.format(CLIText.get().authorInfo,
author.getName(), author.getEmailAddress()));
@@ -291,4 +311,28 @@ class Show extends TextBuiltin {
}
outw.println();
}
+
+ private void showSignature(RevCommit c) throws IOException {
+ if (c.getRawGpgSignature() == null) {
+ return;
+ }
+ GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory
+ .getDefault();
+ if (factory == null) {
+ throw die(CLIText.get().logNoSignatureVerifier, null);
+ }
+ GpgSignatureVerifier verifier = factory.getVerifier();
+ GpgConfig config = new GpgConfig(db.getConfig());
+ try {
+ SignatureVerification verification = verifier.verifySignature(c,
+ config);
+ if (verification == null) {
+ return;
+ }
+ VerificationUtils.writeVerification(outw, verification,
+ verifier.getName(), c.getCommitterIdent());
+ } finally {
+ verifier.clear();
+ }
+ }
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
index b408b78f3c..e2cd31d198 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
@@ -4,7 +4,7 @@
* Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg.lists@dewire.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2021 Shawn O. Pearce <spearce@spearce.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -22,26 +22,59 @@ import java.util.List;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListTagCommand;
import org.eclipse.jgit.api.TagCommand;
+import org.eclipse.jgit.api.VerificationResult;
+import org.eclipse.jgit.api.VerifySignatureCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.internal.VerificationUtils;
+import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@Command(common = true, usage = "usage_CreateATag")
class Tag extends TextBuiltin {
- @Option(name = "-f", usage = "usage_forceReplacingAnExistingTag")
+
+ @Option(name = "--force", aliases = { "-f" }, forbids = { "--delete",
+ "--verify" }, usage = "usage_forceReplacingAnExistingTag")
private boolean force;
- @Option(name = "-d", usage = "usage_tagDelete")
+ @Option(name = "--delete", aliases = { "-d" }, forbids = {
+ "--verify" }, usage = "usage_tagDelete")
private boolean delete;
- @Option(name = "-m", metaVar = "metaVar_message", usage = "usage_tagMessage")
- private String message = ""; //$NON-NLS-1$
+ @Option(name = "--annotate", aliases = {
+ "-a" }, forbids = { "--delete",
+ "--verify" }, usage = "usage_tagAnnotated")
+ private boolean annotated;
+
+ @Option(name = "-m", forbids = { "--delete",
+ "--verify" }, metaVar = "metaVar_message", usage = "usage_tagMessage")
+ private String message;
+
+ @Option(name = "--sign", aliases = { "-s" }, forbids = {
+ "--no-sign", "--delete", "--verify" }, usage = "usage_tagSign")
+ private boolean sign;
+
+ @Option(name = "--no-sign", usage = "usage_tagNoSign", forbids = {
+ "--sign", "--delete", "--verify" })
+ private boolean noSign;
+
+ @Option(name = "--local-user", aliases = {
+ "-u" }, forbids = { "--delete",
+ "--verify" }, metaVar = "metaVar_tagLocalUser", usage = "usage_tagLocalUser")
+ private String gpgKeyId;
+
+ @Option(name = "--verify", aliases = { "-v" }, forbids = { "--delete",
+ "--force", "--annotate", "-m", "--sign", "--no-sign",
+ "--local-user" }, usage = "usage_tagVerify")
+ private boolean verify;
@Argument(index = 0, metaVar = "metaVar_name")
private String tagName;
@@ -54,7 +87,25 @@ class Tag extends TextBuiltin {
protected void run() {
try (Git git = new Git(db)) {
if (tagName != null) {
- if (delete) {
+ if (verify) {
+ VerifySignatureCommand verifySig = git.verifySignature()
+ .setMode(VerifySignatureCommand.VerifyMode.TAGS)
+ .addName(tagName);
+
+ VerificationResult verification = verifySig.call()
+ .get(tagName);
+ if (verification == null) {
+ showUnsigned(git, tagName);
+ } else {
+ Throwable error = verification.getException();
+ if (error != null) {
+ throw die(error.getMessage(), error);
+ }
+ writeVerification(verifySig.getVerifier().getName(),
+ (RevTag) verification.getObject(),
+ verification.getVerification());
+ }
+ } else if (delete) {
List<String> deletedTags = git.tagDelete().setTags(tagName)
.call();
if (deletedTags.isEmpty()) {
@@ -70,6 +121,18 @@ class Tag extends TextBuiltin {
command.setObjectId(walk.parseAny(object));
}
}
+ if (noSign) {
+ command.setSigned(false);
+ } else if (sign) {
+ command.setSigned(true);
+ }
+ if (annotated) {
+ command.setAnnotated(true);
+ } else if (message == null && !sign && gpgKeyId == null) {
+ // None of -a, -m, -s, -u given
+ command.setAnnotated(false);
+ }
+ command.setSigningKey(gpgKeyId);
try {
command.call();
} catch (RefAlreadyExistsException e) {
@@ -88,4 +151,36 @@ class Tag extends TextBuiltin {
throw die(e.getMessage(), e);
}
}
+
+ private void showUnsigned(Git git, String wantedTag) throws IOException {
+ ObjectId id = git.getRepository().resolve(wantedTag);
+ if (id != null && !ObjectId.zeroId().equals(id)) {
+ try (RevWalk walk = new RevWalk(git.getRepository())) {
+ showTag(walk.parseTag(id));
+ }
+ } else {
+ throw die(
+ MessageFormat.format(CLIText.get().tagNotFound, wantedTag));
+ }
+ }
+
+ private void showTag(RevTag tag) throws IOException {
+ outw.println("object " + tag.getObject().name()); //$NON-NLS-1$
+ outw.println("type " + Constants.typeString(tag.getObject().getType())); //$NON-NLS-1$
+ outw.println("tag " + tag.getTagName()); //$NON-NLS-1$
+ outw.println("tagger " + tag.getTaggerIdent().toExternalString()); //$NON-NLS-1$
+ outw.println();
+ outw.print(tag.getFullMessage());
+ }
+
+ private void writeVerification(String name, RevTag tag,
+ SignatureVerification verification) throws IOException {
+ showTag(tag);
+ if (verification == null) {
+ outw.println();
+ return;
+ }
+ VerificationUtils.writeVerification(outw, verification, name,
+ tag.getTaggerIdent());
+ }
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
index 0b02dd148d..f70e72d434 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
@@ -63,7 +63,7 @@ public abstract class TextBuiltin {
private boolean help;
@Option(name = "--ssh", usage = "usage_sshDriver")
- private SshDriver sshDriver = SshDriver.JSCH;
+ private SshDriver sshDriver = SshDriver.APACHE;
/**
* Input stream, typically this is standard input.
@@ -220,7 +220,7 @@ public abstract class TextBuiltin {
SshdSessionFactory factory = new SshdSessionFactory(
new JGitKeyCache(), new DefaultProxyDataFactory());
Runtime.getRuntime()
- .addShutdownHook(new Thread(() -> factory.close()));
+ .addShutdownHook(new Thread(factory::close));
SshSessionFactory.setInstance(factory);
break;
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
index 630fac549e..f23f4cf0ea 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
@@ -23,7 +23,9 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import org.eclipse.jgit.internal.storage.file.FileReftableStack;
import org.eclipse.jgit.internal.storage.io.BlockSource;
@@ -47,6 +49,7 @@ class BenchmarkReftable extends TextBuiltin {
SEEK_COLD, SEEK_HOT,
BY_ID_COLD, BY_ID_HOT,
WRITE_STACK,
+ GET_REFS_EXCLUDING_REF
}
@Option(name = "--tries")
@@ -91,7 +94,11 @@ class BenchmarkReftable extends TextBuiltin {
case WRITE_STACK:
writeStack();
break;
- }
+ case GET_REFS_EXCLUDING_REF :
+ getRefsExcludingWithSeekPast(ref);
+ getRefsExcludingWithFilter(ref);
+ break;
+ }
}
private void printf(String fmt, Object... args) throws IOException {
@@ -315,4 +322,49 @@ class BenchmarkReftable extends TextBuiltin {
printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable",
tot / 1000, (((double) tot) / tries) / 1000, tries);
}
+
+ @SuppressWarnings({"nls", "boxing"})
+ private void getRefsExcludingWithFilter(String prefix) throws Exception {
+ long startTime = System.nanoTime();
+ List<Ref> allRefs = new ArrayList<>();
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.allRefs()) {
+ while (rc.next()) {
+ allRefs.add(rc.getRef());
+ }
+ }
+ }
+ int total = allRefs.size();
+ allRefs = allRefs.stream().filter(r -> r.getName().startsWith(prefix)).collect(Collectors.toList());
+ int notStartWithPrefix = allRefs.size();
+ int startWithPrefix = total - notStartWithPrefix;
+ long totalTime = System.nanoTime() - startTime;
+ printf("total time the action took using filter: %10d usec", totalTime / 1000);
+ printf("number of refs that start with prefix: %d", startWithPrefix);
+ printf("number of refs that don't start with prefix: %d", notStartWithPrefix);
+ }
+
+ @SuppressWarnings({"nls", "boxing"})
+ private void getRefsExcludingWithSeekPast(String prefix) throws Exception {
+ long start = System.nanoTime();
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.allRefs()) {
+ while (rc.next()) {
+ if (rc.getRef().getName().startsWith(prefix)) {
+ break;
+ }
+ }
+ rc.seekPastPrefix(prefix);
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ long tot = System.nanoTime() - start;
+ printf("total time the action took using seek: %10d usec", tot / 1000);
+ }
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
deleted file mode 100644
index 38951ba428..0000000000
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2015, Google Inc. and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-
-package org.eclipse.jgit.pgm.debug;
-
-import static org.eclipse.jgit.lib.Constants.HEAD;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jgit.internal.storage.reftree.RefTree;
-import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.ConfigConstants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.pgm.Command;
-import org.eclipse.jgit.pgm.TextBuiltin;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.kohsuke.args4j.Option;
-
-@Command(usage = "usage_RebuildRefTree")
-class RebuildRefTree extends TextBuiltin {
- @Option(name = "--enable", usage = "usage_RebuildRefTreeEnable")
- boolean enable;
-
- private String txnNamespace;
- private String txnCommitted;
-
- /** {@inheritDoc} */
- @Override
- protected void run() throws Exception {
- try (ObjectReader reader = db.newObjectReader();
- RevWalk rw = new RevWalk(reader);
- ObjectInserter inserter = db.newObjectInserter()) {
- RefDatabase refDb = db.getRefDatabase();
- if (refDb instanceof RefTreeDatabase) {
- RefTreeDatabase d = (RefTreeDatabase) refDb;
- refDb = d.getBootstrap();
- txnNamespace = d.getTxnNamespace();
- txnCommitted = d.getTxnCommitted();
- } else {
- RefTreeDatabase d = new RefTreeDatabase(db, refDb);
- txnNamespace = d.getTxnNamespace();
- txnCommitted = d.getTxnCommitted();
- }
-
- errw.format("Rebuilding %s from %s", //$NON-NLS-1$
- txnCommitted, refDb.getClass().getSimpleName());
- errw.println();
- errw.flush();
-
- CommitBuilder b = new CommitBuilder();
- Ref ref = refDb.exactRef(txnCommitted);
- RefUpdate update = refDb.newUpdate(txnCommitted, true);
- ObjectId oldTreeId;
-
- if (ref != null && ref.getObjectId() != null) {
- ObjectId oldId = ref.getObjectId();
- update.setExpectedOldObjectId(oldId);
- b.setParentId(oldId);
- oldTreeId = rw.parseCommit(oldId).getTree();
- } else {
- update.setExpectedOldObjectId(ObjectId.zeroId());
- oldTreeId = ObjectId.zeroId();
- }
-
- RefTree tree = rebuild(refDb);
- b.setTreeId(tree.writeTree(inserter));
- b.setAuthor(new PersonIdent(db));
- b.setCommitter(b.getAuthor());
- if (b.getTreeId().equals(oldTreeId)) {
- return;
- }
-
- update.setNewObjectId(inserter.insert(b));
- inserter.flush();
-
- RefUpdate.Result result = update.update(rw);
- switch (result) {
- case NEW:
- case FAST_FORWARD:
- break;
- default:
- throw die(String.format("%s: %s", update.getName(), result)); //$NON-NLS-1$
- }
-
- if (enable && !(db.getRefDatabase() instanceof RefTreeDatabase)) {
- StoredConfig cfg = db.getConfig();
- cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
- cfg.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
- ConfigConstants.CONFIG_KEY_REFSTORAGE,
- ConfigConstants.CONFIG_REFSTORAGE_REFTREE);
- cfg.save();
- errw.println("Enabled reftree."); //$NON-NLS-1$
- errw.flush();
- }
- }
- }
-
- private RefTree rebuild(RefDatabase refdb) throws IOException {
- RefTree tree = RefTree.newEmptyTree();
- List<org.eclipse.jgit.internal.storage.reftree.Command> cmds
- = new ArrayList<>();
-
- Ref head = refdb.exactRef(HEAD);
- if (head != null) {
- cmds.add(new org.eclipse.jgit.internal.storage.reftree.Command(
- null,
- head));
- }
-
- for (Ref r : refdb.getRefs()) {
- if (r.getName().equals(txnCommitted) || r.getName().equals(HEAD)
- || r.getName().startsWith(txnNamespace)) {
- continue;
- }
- cmds.add(new org.eclipse.jgit.internal.storage.reftree.Command(
- null,
- db.getRefDatabase().peel(r)));
- }
- tree.apply(cmds);
- return tree;
- }
-}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index c68019e5d0..991b3ba58a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
- * Copyright (C) 2013, Obeo and others
+ * Copyright (C) 2013, 2021 Obeo and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -163,6 +163,7 @@ public class CLIText extends TranslationBundle {
/***/ public String lfsUnknownStoreType;
/***/ public String lineFormat;
/***/ public String listeningOn;
+ /***/ public String logNoSignatureVerifier;
/***/ public String mergeCheckoutConflict;
/***/ public String mergeConflict;
/***/ public String mergeFailed;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java
new file mode 100644
index 0000000000..c1f8a86a8c
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.pgm.internal;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.util.GitDateFormatter;
+import org.eclipse.jgit.util.SignatureUtils;
+import org.eclipse.jgit.util.io.ThrowingPrintWriter;
+
+/**
+ * Utilities for signature verification.
+ */
+public final class VerificationUtils {
+
+ private VerificationUtils() {
+ // No instantiation
+ }
+
+ /**
+ * Writes information about a signature verification to the given writer.
+ *
+ * @param out
+ * to write to
+ * @param verification
+ * to show
+ * @param name
+ * of the verifier used
+ * @param creator
+ * of the object verified; used for time zone information
+ * @throws IOException
+ * if writing fails
+ */
+ public static void writeVerification(ThrowingPrintWriter out,
+ SignatureVerification verification, String name,
+ PersonIdent creator) throws IOException {
+ String[] text = SignatureUtils
+ .toString(verification, creator,
+ new GitDateFormatter(GitDateFormatter.Format.LOCALE))
+ .split("\n"); //$NON-NLS-1$
+ for (String line : text) {
+ out.print(name);
+ out.print(": "); //$NON-NLS-1$
+ out.println(line);
+ }
+ }
+}