() {
-
- public String apply(Branch from) {
- return Util.fixNull(from.getName());
- }
- }));
+ s.append(branches.stream().map(Branch::getName).map(Util::fixNull).collect(joining(", ")));
}
s.append(')');
return s.toString();
}
- /** {@inheritDoc} */
@Override
public Revision clone() {
Revision clone;
@@ -143,13 +126,11 @@ public Revision clone() {
return clone;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return sha1 != null ? 31 + sha1.hashCode() : 1;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Revision)) {
diff --git a/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasySSLProtocolSocketFactory.java b/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasySSLProtocolSocketFactory.java
deleted file mode 100644
index 2bf07daf3c..0000000000
--- a/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasySSLProtocolSocketFactory.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * $HeadURL$
- * $Revision$
- * $Date$
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
-
-package org.apache.commons.httpclient.contrib.ssl;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.UnknownHostException;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-
-import org.apache.commons.httpclient.ConnectTimeoutException;
-import org.apache.commons.httpclient.HttpClientError;
-import org.apache.commons.httpclient.params.HttpConnectionParams;
-import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- *
- * EasySSLProtocolSocketFactory can be used to create SSL {@link java.net.Socket}s
- * that accept self-signed certificates.
- *
- *
- * This socket factory SHOULD NOT be used for productive systems
- * due to security reasons, unless it is a concious decision and
- * you are perfectly aware of security implications of accepting
- * self-signed certificates
- *
- *
- *
- * Example of using custom protocol socket factory for a specific host:
- *
- * Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
- *
- * URI uri = new URI("https://localhost/", true);
- * // use relative url only
- * GetMethod httpget = new GetMethod(uri.getPathQuery());
- * HostConfiguration hc = new HostConfiguration();
- * hc.setHost(uri.getHost(), uri.getPort(), easyhttps);
- * HttpClient client = new HttpClient();
- * client.executeMethod(hc, httpget);
- *
- *
- * Example of using custom protocol socket factory per default instead of the standard one:
- *
- * Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
- * Protocol.registerProtocol("https", easyhttps);
- *
- * HttpClient client = new HttpClient();
- * GetMethod httpget = new GetMethod("https://localhost/");
- * client.executeMethod(httpget);
- *
- *
- * @author Oleg Kalnichevski
- *
- *
- * DISCLAIMER: HttpClient developers DO NOT actively support this component.
- * The component is provided as a reference material, which may be inappropriate
- * for use without additional customization.
- *
- */
-public class EasySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
-
- /** Log object for this class. */
- private static final Log LOG = LogFactory.getLog(EasySSLProtocolSocketFactory.class);
-
- private SSLContext sslcontext = null;
-
- /**
- * Constructor for EasySSLProtocolSocketFactory.
- */
- public EasySSLProtocolSocketFactory() {
- super();
- }
-
- private static SSLContext createEasySSLContext() {
- try {
- SSLContext context = SSLContext.getInstance("SSL");
- context.init(
- null,
- new TrustManager[] {new EasyX509TrustManager(null)},
- null);
- return context;
- } catch (Exception e) {
- LOG.error(e.getMessage(), e);
- throw new HttpClientError(e.toString());
- }
- }
-
- private SSLContext getSSLContext() {
- if (this.sslcontext == null) {
- this.sslcontext = createEasySSLContext();
- }
- return this.sslcontext;
- }
-
- /** {@inheritDoc} */
- public Socket createSocket(
- String host,
- int port,
- InetAddress clientHost,
- int clientPort)
- throws IOException, UnknownHostException {
-
- return getSSLContext().getSocketFactory().createSocket(
- host,
- port,
- clientHost,
- clientPort
- );
- }
-
- /**
- * {@inheritDoc}
- *
- * Attempts to get a new socket connection to the given host within the given time limit.
- *
- * To circumvent the limitations of older JREs that do not support connect timeout a
- * controller thread is executed. The controller thread attempts to create a new socket
- * within the given limit of time. If socket constructor does not return until the
- * timeout expires, the controller terminates and throws an {@link ConnectTimeoutException}
- *
- */
- public Socket createSocket(
- final String host,
- final int port,
- final InetAddress localAddress,
- final int localPort,
- final HttpConnectionParams params
- ) throws IOException, UnknownHostException, ConnectTimeoutException {
- if (params == null) {
- throw new IllegalArgumentException("Parameters may not be null");
- }
- int timeout = params.getConnectionTimeout();
- SocketFactory socketfactory = getSSLContext().getSocketFactory();
- if (timeout == 0) {
- return socketfactory.createSocket(host, port, localAddress, localPort);
- } else {
- Socket socket = socketfactory.createSocket();
- SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
- SocketAddress remoteaddr = new InetSocketAddress(host, port);
- socket.bind(localaddr);
- socket.connect(remoteaddr, timeout);
- return socket;
- }
- }
-
- /** {@inheritDoc} */
- public Socket createSocket(String host, int port)
- throws IOException, UnknownHostException {
- return getSSLContext().getSocketFactory().createSocket(
- host,
- port
- );
- }
-
- /** {@inheritDoc} */
- public Socket createSocket(
- Socket socket,
- String host,
- int port,
- boolean autoClose)
- throws IOException, UnknownHostException {
- return getSSLContext().getSocketFactory().createSocket(
- socket,
- host,
- port,
- autoClose
- );
- }
-
- /** {@inheritDoc} */
- @SuppressFBWarnings(value = "EQ_GETCLASS_AND_CLASS_CONSTANT",
- justification = "Implementation provided by Apache, never inherited")
- public boolean equals(Object obj) {
- return ((obj != null) && obj.getClass().equals(EasySSLProtocolSocketFactory.class));
- }
-
- /**
- * hashCode.
- *
- * @return a int.
- */
- public int hashCode() {
- return EasySSLProtocolSocketFactory.class.hashCode();
- }
-
-}
diff --git a/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasyX509TrustManager.java b/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasyX509TrustManager.java
deleted file mode 100644
index 63e640a911..0000000000
--- a/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasyX509TrustManager.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
-
-package org.apache.commons.httpclient.contrib.ssl;
-
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- *
- * EasyX509TrustManager unlike default {@link javax.net.ssl.X509TrustManager} accepts
- * self-signed certificates.
- *
- * This trust manager SHOULD NOT be used for productive systems
- * due to security reasons, unless it is a concious decision and
- * you are perfectly aware of security implications of accepting
- * self-signed certificates
- *
- *
- * DISCLAIMER: HttpClient developers DO NOT actively support this component.
- * The component is provided as a reference material, which may be inappropriate
- * for use without additional customization.
- *
- * @author Adrian Sutton
- * @author Oleg Kalnichevski
- */
-public class EasyX509TrustManager implements X509TrustManager
-{
- private X509TrustManager standardTrustManager = null;
-
- /** Log object for this class. */
- private static final Log LOG = LogFactory.getLog(EasyX509TrustManager.class);
-
- /**
- * Constructor for EasyX509TrustManager.
- *
- * @param keystore a {@link java.security.KeyStore} object.
- * @throws java.security.NoSuchAlgorithmException if requested algorithm is not available
- * @throws java.security.KeyStoreException if KeyStore operations fail
- */
- public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {
- super();
- TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- factory.init(keystore);
- TrustManager[] trustmanagers = factory.getTrustManagers();
- if (trustmanagers.length == 0) {
- throw new NoSuchAlgorithmException("no trust manager found");
- }
- this.standardTrustManager = (X509TrustManager)trustmanagers[0];
- }
-
- /** {@inheritDoc} */
- public void checkClientTrusted(X509Certificate[] certificates,String authType) throws CertificateException {
- standardTrustManager.checkClientTrusted(certificates,authType);
- }
-
- /** {@inheritDoc} */
- public void checkServerTrusted(X509Certificate[] certificates,String authType) throws CertificateException {
- if ((certificates != null) && LOG.isDebugEnabled()) {
- LOG.debug("Server certificate chain:");
- for (int i = 0; i < certificates.length; i++) {
- LOG.debug("X509Certificate[" + i + "]=" + certificates[i]);
- }
- }
- if ((certificates != null) && (certificates.length == 1)) {
- certificates[0].checkValidity();
- } else {
- standardTrustManager.checkServerTrusted(certificates,authType);
- }
- }
-
- /**
- * getAcceptedIssuers.
- *
- * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
- * @return an array of {@link java.security.cert.X509Certificate} objects.
- */
- public X509Certificate[] getAcceptedIssuers() {
- return this.standardTrustManager.getAcceptedIssuers();
- }
-}
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/AbstractGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/AbstractGitAPIImpl.java
index b102d6244d..5f8d307b71 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/AbstractGitAPIImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/AbstractGitAPIImpl.java
@@ -1,11 +1,11 @@
package org.jenkinsci.plugins.gitclient;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import hudson.FilePath;
import hudson.ProxyConfiguration;
import hudson.plugins.git.GitException;
import hudson.remoting.Channel;
-import jenkins.model.Jenkins.MasterComputer;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
@@ -26,7 +26,7 @@ abstract class AbstractGitAPIImpl implements GitClient, Serializable {
/** {@inheritDoc} */
public T withRepository(RepositoryCallback callable) throws IOException, InterruptedException {
try (Repository repo = getRepository()) {
- return callable.invoke(repo, MasterComputer.localChannel);
+ return callable.invoke(repo, FilePath.localChannel);
}
}
@@ -90,9 +90,13 @@ public void merge(ObjectId rev) throws GitException, InterruptedException {
* When sent to remote, switch to the proxy.
*
* @return a {@link java.lang.Object} object.
+ * @throws java.io.ObjectStreamException if current channel is null
*/
- protected Object writeReplace() {
- return remoteProxyFor(Channel.current().export(GitClient.class, this));
+ protected Object writeReplace() throws java.io.ObjectStreamException {
+ Channel currentChannel = Channel.current();
+ if (currentChannel == null)
+ throw new java.io.WriteAbortedException("No current channel", new java.lang.NullPointerException());
+ return remoteProxyFor(currentChannel.export(GitClient.class, this));
}
/**
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java b/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java
index 96c2a0ee67..3b12b938dc 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/ChangelogCommand.java
@@ -9,7 +9,7 @@
* Command builder for generating changelog in the format {@code GitSCM} expects.
*
*
- * The output format is that of git-whatchanged, which looks something like this:
+ * The output format is that of git-whatchanged, which looks something like this:
*
*
* commit dadaf808d99c4c23c53476b0c48e25a181016300
@@ -121,4 +121,11 @@ public interface ChangelogCommand extends GitCommand {
* ChangelogCommand instance or files will be left open.
*/
void abort();
+
+ /**
+ * Include merge commits in the changelog
+ * @param flag true if merge commits should be listed
+ * @return a {@link org.jenkinsci.plugins.gitclient.ChangelogCommand} object.
+ */
+ ChangelogCommand listMerges(boolean flag);
}
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
index 68e8e5eb1c..e1325e211e 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
@@ -25,8 +25,10 @@
import hudson.plugins.git.Revision;
import hudson.util.ArgumentListBuilder;
import hudson.util.Secret;
+import hudson.Proc;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -45,19 +47,28 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@@ -124,6 +135,29 @@ public class CliGitAPIImpl extends LegacyCompatibleGitAPIImpl {
* ssh configurations).
*/
private static final boolean CALL_SETSID;
+
+ /**
+ * Needed file permission for OpenSSH client that is made by Windows,
+ * this will remove unwanted users and inherited permissions
+ * which is required when the git client is using the SSH to clone
+ *
+ * The ssh client that the git client ships ignores file permission on Windows
+ * Which the PowerShell team at Microsoft decided to fix in their port of OpenSSH
+ */
+ static final EnumSet ACL_ENTRY_PERMISSIONS = EnumSet.of(
+ AclEntryPermission.READ_DATA,
+ AclEntryPermission.WRITE_DATA,
+ AclEntryPermission.APPEND_DATA,
+ AclEntryPermission.READ_NAMED_ATTRS,
+ AclEntryPermission.WRITE_NAMED_ATTRS,
+ AclEntryPermission.EXECUTE,
+ AclEntryPermission.READ_ATTRIBUTES,
+ AclEntryPermission.WRITE_ATTRIBUTES,
+ AclEntryPermission.DELETE,
+ AclEntryPermission.READ_ACL,
+ AclEntryPermission.SYNCHRONIZE
+ );
+
static {
acceptSelfSignedCertificates = Boolean.getBoolean(GitClient.class.getName() + ".untrustedSSL");
CALL_SETSID = setsidExists() && USE_SETSID;
@@ -141,6 +175,7 @@ public class CliGitAPIImpl extends LegacyCompatibleGitAPIImpl {
private Map credentials = new HashMap<>();
private StandardCredentials defaultCredentials;
private StandardCredentials lfsCredentials;
+ private final String encoding;
/* git config --get-regex applies the regex to match keys, and returns all matches (including substring matches).
* Thus, a config call:
@@ -243,14 +278,19 @@ private void getGitVersion() {
* @param listener a {@link hudson.model.TaskListener} object.
* @param environment a {@link hudson.EnvVars} object.
*/
- protected CliGitAPIImpl(String gitExe, File workspace,
- TaskListener listener, EnvVars environment) {
+ protected CliGitAPIImpl(String gitExe, File workspace, TaskListener listener, EnvVars environment) {
super(workspace);
this.listener = listener;
this.gitExe = gitExe;
this.environment = environment;
+
+ if( isZos() && System.getProperty("ibm.system.encoding") != null ) {
+ this.encoding = Charset.forName(System.getProperty("ibm.system.encoding")).toString();
+ } else {
+ this.encoding = Charset.defaultCharset().toString();
+ }
- launcher = new LocalLauncher(IGitAPI.verbose?listener:TaskListener.NULL);
+ launcher = new LocalLauncher(IGitAPI.verbose ? listener : TaskListener.NULL);
}
/** {@inheritDoc} */
@@ -329,49 +369,57 @@ public List getSubmodules( String treeIsh ) throws GitException, Int
*/
public FetchCommand fetch_() {
return new FetchCommand() {
- public URIish url;
- public List refspecs;
- public boolean prune;
- public boolean shallow;
- public Integer timeout;
- public boolean tags = true;
- public Integer depth = 1;
+ private URIish url;
+ private List refspecs;
+ private boolean prune;
+ private boolean shallow;
+ private Integer timeout;
+ private boolean tags = true;
+ private Integer depth = 1;
+ @Override
public FetchCommand from(URIish remote, List refspecs) {
this.url = remote;
this.refspecs = refspecs;
return this;
}
+ @Override
public FetchCommand tags(boolean tags) {
this.tags = tags;
return this;
}
+ @Override
public FetchCommand prune() {
return prune(true);
}
+ @Override
public FetchCommand prune(boolean prune) {
this.prune = prune;
return this;
}
+ @Override
public FetchCommand shallow(boolean shallow) {
this.shallow = shallow;
return this;
}
+ @Override
public FetchCommand timeout(Integer timeout) {
this.timeout = timeout;
return this;
}
+ @Override
public FetchCommand depth(Integer depth) {
this.depth = depth;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
listener.getLogger().println(
"Fetching upstream changes from " + url);
@@ -394,7 +442,7 @@ public void execute() throws GitException, InterruptedException {
if (prune) args.add("--prune");
if (shallow) {
- if (depth == null){
+ if (depth == null) {
depth = 1;
}
args.add("--depth=" + depth);
@@ -402,7 +450,22 @@ public void execute() throws GitException, InterruptedException {
warnIfWindowsTemporaryDirNameHasSpaces();
- launchCommandWithCredentials(args, workspace, cred, url, timeout);
+ /* If url looks like a remote name reference, convert to remote URL for authentication */
+ /* See JENKINS-50573 for more details */
+ /* "git remote add" rejects remote names with ':' (and it is a common character in remote URLs) */
+ /* "git remote add" allows remote names with '@' but internal git parsing problems seem likely (and it is a common character in remote URLs) */
+ /* "git remote add" allows remote names with '/' but git client plugin parsing problems will occur (and it is a common character in remote URLs) */
+ /* "git remote add" allows remote names with '\' but git client plugin parsing problems will occur */
+ URIish remoteUrl = url;
+ if (!url.isRemote() && !StringUtils.containsAny(url.toString(), ":@/\\")) {
+ try {
+ remoteUrl = new URIish(getRemoteUrl(url.toString()));
+ } catch (URISyntaxException e) {
+ listener.getLogger().println("Unexpected remote name or URL: '" + url + "'");
+ }
+ }
+
+ launchCommandWithCredentials(args, workspace, cred, remoteUrl, timeout);
}
};
}
@@ -470,25 +533,28 @@ public void reset(boolean hard) throws GitException, InterruptedException {
*/
public CloneCommand clone_() {
return new CloneCommand() {
- String url;
- String origin = "origin";
- String reference;
- boolean shallow,shared;
- Integer timeout;
- boolean tags = true;
- List refspecs;
- Integer depth = 1;
+ private String url;
+ private String origin = "origin";
+ private String reference;
+ private boolean shallow,shared;
+ private Integer timeout;
+ private boolean tags = true;
+ private List refspecs;
+ private Integer depth = 1;
+ @Override
public CloneCommand url(String url) {
this.url = url;
return this;
}
+ @Override
public CloneCommand repositoryName(String name) {
this.origin = name;
return this;
}
+ @Override
public CloneCommand shared() {
return shared(true);
}
@@ -499,6 +565,7 @@ public CloneCommand shared(boolean shared) {
return this;
}
+ @Override
public CloneCommand shallow() {
return shallow(true);
}
@@ -509,36 +576,43 @@ public CloneCommand shallow(boolean shallow) {
return this;
}
+ @Override
public CloneCommand noCheckout() {
//this.noCheckout = true; Since the "clone" command has been replaced with init + fetch, the --no-checkout option is always satisfied
return this;
}
+ @Override
public CloneCommand tags(boolean tags) {
this.tags = tags;
return this;
}
+ @Override
public CloneCommand reference(String reference) {
this.reference = reference;
return this;
}
+ @Override
public CloneCommand timeout(Integer timeout) {
this.timeout = timeout;
return this;
}
+ @Override
public CloneCommand depth(Integer depth) {
this.depth = depth;
return this;
}
+ @Override
public CloneCommand refspecs(List refspecs) {
this.refspecs = new ArrayList<>(refspecs);
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
URIish urIish = null;
@@ -628,43 +702,50 @@ else if (!referencePath.isDirectory())
*/
public MergeCommand merge() {
return new MergeCommand() {
- public ObjectId rev;
- public String comment;
- public String strategy;
- public String fastForwardMode;
- public boolean squash;
- public boolean commit = true;
+ private ObjectId rev;
+ private String comment;
+ private String strategy;
+ private String fastForwardMode;
+ private boolean squash;
+ private boolean commit = true;
+ @Override
public MergeCommand setRevisionToMerge(ObjectId rev) {
this.rev = rev;
return this;
}
+ @Override
public MergeCommand setStrategy(MergeCommand.Strategy strategy) {
this.strategy = strategy.toString();
return this;
}
+ @Override
public MergeCommand setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode fastForwardMode) {
this.fastForwardMode = fastForwardMode.toString();
return this;
}
+ @Override
public MergeCommand setSquash(boolean squash) {
this.squash = squash;
return this;
}
+ @Override
public MergeCommand setMessage(String comment) {
this.comment = comment;
return this;
}
+ @Override
public MergeCommand setCommit(boolean commit) {
this.commit = commit;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
ArgumentListBuilder args = new ArgumentListBuilder();
args.add("merge");
@@ -708,11 +789,13 @@ public RebaseCommand rebase() {
return new RebaseCommand() {
private String upstream;
+ @Override
public RebaseCommand setUpstream(String upstream) {
this.upstream = upstream;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
try {
ArgumentListBuilder args = new ArgumentListBuilder();
@@ -735,19 +818,22 @@ public void execute() throws GitException, InterruptedException {
public InitCommand init_() {
return new InitCommand() {
- public String workspace;
- public boolean bare;
+ private String workspace;
+ private boolean bare;
+ @Override
public InitCommand workspace(String workspace) {
this.workspace = workspace;
return this;
}
+ @Override
public InitCommand bare(boolean bare) {
this.bare = bare;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
/* Match JGit - create directory if it does not exist */
/* Multi-branch pipeline assumes init() creates directory */
@@ -779,12 +865,27 @@ public void execute() throws GitException, InterruptedException {
* Remove untracked files and directories, including files listed
* in the ignore rules.
*
+ * @param cleanSubmodule flag to add extra -f
* @throws hudson.plugins.git.GitException if underlying git operation fails.
* @throws java.lang.InterruptedException if interrupted.
*/
- public void clean() throws GitException, InterruptedException {
+ public void clean(boolean cleanSubmodule) throws GitException, InterruptedException {
reset(true);
- launchCommand("clean", "-fdx");
+ String cmd = "-fdx";
+ if (cleanSubmodule) cmd = "-ffdx";
+
+ launchCommand("clean", cmd);
+ }
+
+ /**
+ * Remove untracked files and directories, including files listed
+ * in the ignore rules.
+ *
+ * @throws hudson.plugins.git.GitException if underlying git operation fails.
+ * @throws java.lang.InterruptedException if interrupted.
+ */
+ public void clean() throws GitException, InterruptedException {
+ this.clean(false);
}
/** {@inheritDoc} */
@@ -907,11 +1008,12 @@ public ChangelogCommand changelog() {
return new ChangelogCommand() {
/** Equivalent to the git-log raw format but using ISO 8601 date format - also prevent to depend on git CLI future changes */
- public static final String RAW = "commit %H%ntree %T%nparent %P%nauthor %aN <%aE> %ai%ncommitter %cN <%cE> %ci%n%n%w(76,4,4)%s%n%n%b";
- final List revs = new ArrayList<>();
+ public static final String RAW = "commit %H%ntree %T%nparent %P%nauthor %aN <%aE> %ai%ncommitter %cN <%cE> %ci%n%n%w(0,4,4)%B";
+ private final List revs = new ArrayList<>();
- Integer n = null;
- Writer out = null;
+ private Integer n = null;
+ private Writer out = null;
+ private boolean listMerges = false;
@Override
public ChangelogCommand excludes(String rev) {
@@ -958,6 +1060,8 @@ public void execute() throws GitException, InterruptedException {
args.add("--format="+RAW);
if (n!=null)
args.add("-n").add(n);
+ if (listMerges)
+ args.add("-m");
for (String rev : this.revs)
args.add(rev);
@@ -973,6 +1077,11 @@ public void execute() throws GitException, InterruptedException {
throw new GitException("Error: " + args + " in " + workspace, e);
}
}
+
+ public ChangelogCommand listMerges(boolean flag) {
+ listMerges = flag;
+ return this;
+ }
};
}
@@ -1034,47 +1143,75 @@ public void submoduleSync() throws GitException, InterruptedException {
*/
public SubmoduleUpdateCommand submoduleUpdate() {
return new SubmoduleUpdateCommand() {
- boolean recursive = false;
- boolean remoteTracking = false;
- boolean parentCredentials = false;
- String ref = null;
- Map submodBranch = new HashMap<>();
- public Integer timeout;
+ private boolean recursive = false;
+ private boolean remoteTracking = false;
+ private boolean parentCredentials = false;
+ private boolean shallow = false;
+ private String ref = null;
+ private Map submodBranch = new HashMap<>();
+ private Integer timeout;
+ private Integer depth = 1;
+ private Integer threads = 1;
+ @Override
public SubmoduleUpdateCommand recursive(boolean recursive) {
this.recursive = recursive;
return this;
}
+ @Override
public SubmoduleUpdateCommand remoteTracking(boolean remoteTracking) {
this.remoteTracking = remoteTracking;
return this;
}
+ @Override
public SubmoduleUpdateCommand parentCredentials(boolean parentCredentials) {
this.parentCredentials = parentCredentials;
return this;
}
+ @Override
public SubmoduleUpdateCommand ref(String ref) {
this.ref = ref;
return this;
}
+ @Override
public SubmoduleUpdateCommand useBranch(String submodule, String branchname) {
this.submodBranch.put(submodule, branchname);
return this;
}
+ @Override
public SubmoduleUpdateCommand timeout(Integer timeout) {
this.timeout = timeout;
return this;
}
+ @Override
+ public SubmoduleUpdateCommand shallow(boolean shallow) {
+ this.shallow = shallow;
+ return this;
+ }
+
+ @Override
+ public SubmoduleUpdateCommand depth(Integer depth) {
+ this.depth = depth;
+ return this;
+ }
+
+ @Override
+ public SubmoduleUpdateCommand threads(Integer threads) {
+ this.threads = threads;
+ return this;
+ }
+
/**
* @throws GitException if executing the Git command fails
* @throws InterruptedException if called methods throw same exception
*/
+ @Override
public void execute() throws GitException, InterruptedException {
// Initialize the submodules to ensure that the git config
// contains the URLs from .gitmodules.
@@ -1101,7 +1238,16 @@ else if (!referencePath.isDirectory())
else
args.add("--reference", ref);
}
-
+ if (shallow) {
+ if (depth == null) {
+ depth = 1;
+ }
+ if (isAtLeastVersion(1, 8, 4, 0)) {
+ args.add("--depth=" + depth);
+ } else {
+ listener.getLogger().println("[WARNING] Git client older than 1.8.4 doesn't support shallow submodule updates. This flag is ignored.");
+ }
+ }
// We need to call submodule update for each configured
// submodule. Note that we can't reliably depend on the
@@ -1125,6 +1271,14 @@ else if (!referencePath.isDirectory())
// path.
Pattern pattern = Pattern.compile(SUBMODULE_REMOTE_PATTERN_STRING, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(cfgOutput);
+
+ ExecutorService executorService;
+ if (threads > 1) {
+ executorService = Executors.newFixedThreadPool(threads);
+ } else {
+ executorService = Executors.newSingleThreadExecutor();
+ }
+
while (matcher.find()) {
ArgumentListBuilder perModuleArgs = args.clone();
String sModuleName = matcher.group(1);
@@ -1158,8 +1312,19 @@ else if (!referencePath.isDirectory())
String sModulePath = getSubmodulePath(sModuleName);
perModuleArgs.add(sModulePath);
- launchCommandWithCredentials(perModuleArgs, workspace, cred, urIish, timeout);
+ StandardCredentials finalCred = cred;
+ URIish finalUrIish = urIish;
+ executorService.submit(() -> {
+ try {
+ launchCommandWithCredentials(perModuleArgs, workspace, finalCred, finalUrIish, timeout);
+ } catch (InterruptedException e) {
+ throw new GitException("Interrupted while updating submodule for " + sModuleName);
+ }
+ });
}
+
+ executorService.shutdown();
+ executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
};
}
@@ -1526,12 +1691,19 @@ private File createTempFileInSystemDir(String prefix, String suffix) throws IOEx
*
* Package protected for testing. Not to be used outside this class
*
- * @param prefix file name prefix for the generated temporary file
+ * @param prefix file name prefix for the generated temporary file (will be preceeded by "jenkins-gitclient-")
* @param suffix file name suffix for the generated temporary file
* @return temporary file
* @throws IOException on error
*/
File createTempFile(String prefix, String suffix) throws IOException {
+ String common_prefix = "jenkins-gitclient-";
+ if (prefix == null) {
+ prefix = common_prefix;
+ } else {
+ prefix = common_prefix + prefix;
+ }
+
if (workspace == null) {
return createTempFileInSystemDir(prefix, suffix);
}
@@ -1555,6 +1727,9 @@ File createTempFile(String prefix, String suffix) throws IOException {
return createTempFileInSystemDir(prefix, suffix);
}
return Files.createTempFile(tmpPath, prefix, suffix).toFile();
+ } else if (workspaceTmp.getAbsolutePath().contains("%")) {
+ /* Avoid Linux expansion of % in ssh arguments */
+ return createTempFileInSystemDir(prefix, suffix);
}
// Unix specific
if (workspaceTmp.getAbsolutePath().contains("`")) {
@@ -1631,14 +1806,16 @@ private String launchCommandWithCredentials(ArgumentListBuilder args, File workD
File key = null;
File ssh = null;
- File pass = null;
File askpass = null;
+ File usernameFile = null;
+ File passwordFile = null;
+ File passphrase = null;
EnvVars env = environment;
if (!PROMPT_FOR_AUTHENTICATION && isAtLeastVersion(2, 3, 0, 0)) {
env = new EnvVars(env);
- env.put("GIT_TERMINAL_PROMPT", "0"); // Don't prompt for auth from command line git
+ env.put("GIT_TERMINAL_PROMPT", "false"); // Don't prompt for auth from command line git
if (isWindows()) {
- env.put("GCM_INTERACTIVE", "never"); // Don't prompt for auth from git credentials manager for windows
+ env.put("GCM_INTERACTIVE", "false"); // Don't prompt for auth from git credentials manager for windows
}
}
try {
@@ -1647,18 +1824,25 @@ private String launchCommandWithCredentials(ArgumentListBuilder args, File workD
listener.getLogger().println("using GIT_SSH to set credentials " + sshUser.getDescription());
key = createSshKeyFile(sshUser);
+ // Prefer url username if set, OpenSSH 7.7 argument precedence change
+ // See JENKINS-50573 for details
+ String userName = url.getUser();
+ if (userName == null) {
+ userName = sshUser.getUsername();
+ }
+ passphrase = createPassphraseFile(sshUser);
if (launcher.isUnix()) {
- ssh = createUnixGitSSH(key, sshUser.getUsername());
- pass = createUnixSshAskpass(sshUser);
+ ssh = createUnixGitSSH(key, userName);
+ askpass = createUnixSshAskpass(sshUser, passphrase);
} else {
- ssh = createWindowsGitSSH(key, sshUser.getUsername());
- pass = createWindowsSshAskpass(sshUser);
+ ssh = createWindowsGitSSH(key, userName);
+ askpass = createWindowsSshAskpass(sshUser, passphrase);
}
env = new EnvVars(env);
env.put("GIT_SSH", ssh.getAbsolutePath());
env.put("GIT_SSH_VARIANT", "ssh");
- env.put("SSH_ASKPASS", pass.getAbsolutePath());
+ env.put("SSH_ASKPASS", askpass.getAbsolutePath());
// supply a dummy value for DISPLAY if not already present
// or else ssh will not invoke SSH_ASKPASS
@@ -1670,15 +1854,16 @@ private String launchCommandWithCredentials(ArgumentListBuilder args, File workD
StandardUsernamePasswordCredentials userPass = (StandardUsernamePasswordCredentials) credentials;
listener.getLogger().println("using GIT_ASKPASS to set credentials " + userPass.getDescription());
+ usernameFile = createUsernameFile(userPass);
+ passwordFile = createPasswordFile(userPass);
if (launcher.isUnix()) {
- askpass = createUnixStandardAskpass(userPass);
+ askpass = createUnixStandardAskpass(userPass, usernameFile, passwordFile);
} else {
- askpass = createWindowsStandardAskpass(userPass);
+ askpass = createWindowsStandardAskpass(userPass, usernameFile, passwordFile);
}
env = new EnvVars(env);
env.put("GIT_ASKPASS", askpass.getAbsolutePath());
- // SSH binary does not recognize GIT_ASKPASS, so set SSH_ASKPASS also, in the case we have an ssh:// URL
env.put("SSH_ASKPASS", askpass.getAbsolutePath());
}
@@ -1716,101 +1901,169 @@ private String launchCommandWithCredentials(ArgumentListBuilder args, File workD
} catch (IOException e) {
throw new GitException("Failed to setup credentials", e);
} finally {
- deleteTempFile(pass);
deleteTempFile(key);
deleteTempFile(ssh);
deleteTempFile(askpass);
+ deleteTempFile(passphrase);
+ deleteTempFile(usernameFile);
+ deleteTempFile(passwordFile);
}
}
private File createSshKeyFile(SSHUserPrivateKey sshUser) throws IOException, InterruptedException {
File key = createTempFile("ssh", ".key");
- try (PrintWriter w = new PrintWriter(key, Charset.defaultCharset().toString())) {
+ try (PrintWriter w = new PrintWriter(key, encoding)) {
List privateKeys = sshUser.getPrivateKeys();
for (String s : privateKeys) {
w.println(s);
}
}
- new FilePath(key).chmod(0400);
+ if (launcher.isUnix()) {
+ new FilePath(key).chmod(0400);
+ } else {
+ fixSshKeyOnWindows(key);
+ }
+
return key;
}
/* package protected for testability */
- String escapeWindowsCharsForUnquotedString(String str) {
- // Quote special characters for Windows Batch Files
- // See: http://stackoverflow.com/questions/562038/escaping-double-quotes-in-batch-script
- // See: http://ss64.com/nt/syntax-esc.html
- String quoted = str.replace("%", "%%")
- .replace("^", "^^")
- .replace(" ", "^ ")
- .replace("\t", "^\t")
- .replace("\\", "^\\")
- .replace("&", "^&")
- .replace("|", "^|")
- .replace("\"", "^\"")
- .replace(">", "^>")
- .replace("<", "^<");
- return quoted;
- }
-
- private String quoteUnixCredentials(String str) {
- // Assumes string will be used inside of single quotes, as it will
- // only replace "'" substrings.
- return str.replace("'", "'\\''");
- }
-
- private File createWindowsSshAskpass(SSHUserPrivateKey sshUser) throws IOException {
- File ssh = createTempFile("pass", ".bat");
- try (PrintWriter w = new PrintWriter(ssh, Charset.defaultCharset().toString())) {
+ void fixSshKeyOnWindows(File key) throws GitException {
+ if (launcher.isUnix()) return;
+
+ Path file = Paths.get(key.toURI());
+
+ AclFileAttributeView fileAttributeView = Files.getFileAttributeView(file, AclFileAttributeView.class);
+ if (fileAttributeView == null) return;
+
+ String username = getWindowsUserName(fileAttributeView);
+ if (StringUtils.isBlank(username)) return;
+
+ try {
+ UserPrincipalLookupService userPrincipalLookupService = file.getFileSystem().getUserPrincipalLookupService();
+ UserPrincipal userPrincipal = userPrincipalLookupService.lookupPrincipalByName(username);
+ AclEntry aclEntry = AclEntry.newBuilder()
+ .setType(AclEntryType.ALLOW)
+ .setPrincipal(userPrincipal)
+ .setPermissions(ACL_ENTRY_PERMISSIONS)
+ .build();
+ fileAttributeView.setAcl(Collections.singletonList(aclEntry));
+ } catch (IOException | UnsupportedOperationException e) {
+ throw new GitException("Error updating file permission for \"" + key.getAbsolutePath() + "\"");
+ }
+ }
+
+ /* package protected for testability */
+ String getWindowsUserName(AclFileAttributeView aclFileAttributeView) {
+ if (launcher.isUnix()) return "";
+
+ try {
+ return aclFileAttributeView.getOwner().getName();
+ } catch (IOException ignored) {
+ String username = System.getenv("USERNAME");
+ if (StringUtils.isBlank(username)) return "";
+
+ String domain = System.getenv("USERDOMAIN");
+ if (StringUtils.isNotBlank(domain) && !username.endsWith("$")) {
+ username = domain + "\\" + username;
+ } else if (username.endsWith("$")) {
+ username = "BUILTIN\\Administrators";
+ }
+
+ return username;
+ }
+ }
+
+ /* Escape all double quotes in filename, then surround filename in double quotes.
+ * Only useful to prepare filename for reference from a DOS batch file.
+ */
+ private String windowsArgEncodeFileName(String filename) {
+ if (filename.contains("\"")) {
+ filename = filename.replaceAll("\"", "^\"");
+ }
+ return "\"" + filename + "\"";
+ }
+
+ private File createWindowsSshAskpass(SSHUserPrivateKey sshUser, @NonNull File passphrase) throws IOException {
+ File ssh = File.createTempFile("pass", ".bat");
+ try (PrintWriter w = new PrintWriter(ssh, encoding)) {
// avoid echoing command as part of the password
w.println("@echo off");
- // no surrounding double quotes on windows echo -- they are echoed too
- w.println("echo " + escapeWindowsCharsForUnquotedString(Secret.toString(sshUser.getPassphrase())));
+ w.println("type " + windowsArgEncodeFileName(passphrase.getAbsolutePath()));
w.flush();
}
ssh.setExecutable(true, true);
return ssh;
}
- private File createUnixSshAskpass(SSHUserPrivateKey sshUser) throws IOException {
+ /* Escape all single quotes in filename, then surround filename in single quotes.
+ * Only useful to prepare filename for reference from a shell script.
+ */
+ private String unixArgEncodeFileName(String filename) {
+ if (filename.contains("'")) {
+ filename = filename.replaceAll("'", "\\'");
+ }
+ return "'" + filename + "'";
+ }
+
+ private File createUnixSshAskpass(SSHUserPrivateKey sshUser, @NonNull File passphrase) throws IOException {
File ssh = createTempFile("pass", ".sh");
- try (PrintWriter w = new PrintWriter(ssh, Charset.defaultCharset().toString())) {
+ try (PrintWriter w = new PrintWriter(ssh, encoding)) {
w.println("#!/bin/sh");
- w.println("echo '" + quoteUnixCredentials(Secret.toString(sshUser.getPassphrase())) + "'");
+ w.println("cat " + unixArgEncodeFileName(passphrase.getAbsolutePath()));
}
ssh.setExecutable(true, true);
return ssh;
}
- /* Package protected for testability */
- File createWindowsBatFile(String userName, String password) throws IOException {
+ private File createWindowsStandardAskpass(StandardUsernamePasswordCredentials creds, File usernameFile, File passwordFile) throws IOException {
File askpass = createTempFile("pass", ".bat");
- try (PrintWriter w = new PrintWriter(askpass, Charset.defaultCharset().toString())) {
+ try (PrintWriter w = new PrintWriter(askpass, encoding)) {
w.println("@set arg=%~1");
- w.println("@if (%arg:~0,8%)==(Username) echo " + escapeWindowsCharsForUnquotedString(userName));
- w.println("@if (%arg:~0,8%)==(Password) echo " + escapeWindowsCharsForUnquotedString(password));
+ w.println("@if (%arg:~0,8%)==(Username) type " + windowsArgEncodeFileName(usernameFile.getAbsolutePath()));
+ w.println("@if (%arg:~0,8%)==(Password) type " + windowsArgEncodeFileName(passwordFile.getAbsolutePath()));
}
askpass.setExecutable(true, true);
return askpass;
}
- private File createWindowsStandardAskpass(StandardUsernamePasswordCredentials creds) throws IOException {
- return createWindowsBatFile(creds.getUsername(), Secret.toString(creds.getPassword()));
- }
-
- private File createUnixStandardAskpass(StandardUsernamePasswordCredentials creds) throws IOException {
+ private File createUnixStandardAskpass(StandardUsernamePasswordCredentials creds, File usernameFile, File passwordFile) throws IOException {
File askpass = createTempFile("pass", ".sh");
- try (PrintWriter w = new PrintWriter(askpass, Charset.defaultCharset().toString())) {
+ try (PrintWriter w = new PrintWriter(askpass, encoding)) {
w.println("#!/bin/sh");
w.println("case \"$1\" in");
- w.println("Username*) echo '" + quoteUnixCredentials(creds.getUsername()) + "' ;;");
- w.println("Password*) echo '" + quoteUnixCredentials(Secret.toString(creds.getPassword())) + "' ;;");
+ w.println("Username*) cat " + unixArgEncodeFileName(usernameFile.getAbsolutePath()) + " ;;");
+ w.println("Password*) cat " + unixArgEncodeFileName(passwordFile.getAbsolutePath()) + " ;;");
w.println("esac");
}
askpass.setExecutable(true, true);
return askpass;
}
+ private File createPassphraseFile(SSHUserPrivateKey sshUser) throws IOException {
+ File passphraseFile = createTempFile("phrase", ".txt");
+ try (PrintWriter w = new PrintWriter(passphraseFile, "UTF-8")) {
+ w.println(Secret.toString(sshUser.getPassphrase()));
+ }
+ return passphraseFile;
+ }
+
+ private File createUsernameFile(StandardUsernamePasswordCredentials userPass) throws IOException {
+ File usernameFile = createTempFile("username", ".txt");
+ try (PrintWriter w = new PrintWriter(usernameFile, "UTF-8")) {
+ w.println(userPass.getUsername());
+ }
+ return usernameFile;
+ }
+
+ private File createPasswordFile(StandardUsernamePasswordCredentials userPass) throws IOException {
+ File passwordFile = createTempFile("password", ".txt");
+ try (PrintWriter w = new PrintWriter(passwordFile, "UTF-8")) {
+ w.println(Secret.toString(userPass.getPassword()));
+ }
+ return passwordFile;
+ }
+
private String getPathToExe(String userGitExe) {
userGitExe = userGitExe.toLowerCase();
@@ -1933,7 +2186,7 @@ private File createWindowsGitSSH(File key, String user) throws IOException {
File sshexe = getSSHExecutable();
- try (PrintWriter w = new PrintWriter(ssh, Charset.defaultCharset().toString())) {
+ try (PrintWriter w = new PrintWriter(ssh, encoding)) {
w.println("@echo off");
w.println("\"" + sshexe.getAbsolutePath() + "\" -i \"" + key.getAbsolutePath() +"\" -l \"" + user + "\" -o StrictHostKeyChecking=no %* ");
}
@@ -1943,7 +2196,9 @@ private File createWindowsGitSSH(File key, String user) throws IOException {
private File createUnixGitSSH(File key, String user) throws IOException {
File ssh = createTempFile("ssh", ".sh");
- try (PrintWriter w = new PrintWriter(ssh, Charset.defaultCharset().toString())) {
+ File ssh_copy = new File(ssh.toString() + "-copy");
+ boolean isCopied = false;
+ try (PrintWriter w = new PrintWriter(ssh, encoding)) {
w.println("#!/bin/sh");
// ${SSH_ASKPASS} might be ignored if ${DISPLAY} is not set
w.println("if [ -z \"${DISPLAY}\" ]; then");
@@ -1953,7 +2208,31 @@ private File createUnixGitSSH(File key, String user) throws IOException {
w.println("ssh -i \"" + key.getAbsolutePath() + "\" -l \"" + user + "\" -o StrictHostKeyChecking=no \"$@\"");
}
ssh.setExecutable(true, true);
- return ssh;
+ //JENKINS-48258 git client plugin occasionally fails with "text file busy" error
+ //The following creates a copy of the generated file and deletes the original
+ //In case of a failure return the original and delete the copy
+ String fromLocation = ssh.toString();
+ String toLocation = ssh_copy.toString();
+ //Copying ssh file
+ try {
+ new ProcessBuilder("cp", fromLocation, toLocation).start().waitFor();
+ isCopied = true;
+ ssh_copy.setExecutable(true,true);
+ //Deleting original file
+ deleteTempFile(ssh);
+ }
+ catch(InterruptedException ie)
+ {
+ //Delete the copied file in case of failure
+ if(isCopied)
+ {
+ deleteTempFile(ssh_copy);
+ }
+ //Previous operation failed. Return original file
+ return ssh;
+ }
+
+ return ssh_copy;
}
private String launchCommandIn(ArgumentListBuilder args, File workDir) throws GitException, InterruptedException {
@@ -1964,10 +2243,18 @@ private String launchCommandIn(ArgumentListBuilder args, File workDir, EnvVars e
return launchCommandIn(args, workDir, environment, TIMEOUT);
}
+ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "earlier readStderr()/readStdout() call prevents null return")
+ private String readProcessIntoString(Proc process, String encoding, boolean useStderr)
+ throws IOException, UnsupportedEncodingException {
+ if (useStderr) {
+ /* process.getStderr reference is the findbugs warning to be suppressed */
+ return IOUtils.toString(process.getStderr(), encoding);
+ }
+ /* process.getStdout reference is the findbugs warning to be suppressed */
+ return IOUtils.toString(process.getStdout(), encoding);
+ }
+
private String launchCommandIn(ArgumentListBuilder args, File workDir, EnvVars env, Integer timeout) throws GitException, InterruptedException {
- ByteArrayOutputStream fos = new ByteArrayOutputStream();
- // JENKINS-13356: capture the output of stderr separately
- ByteArrayOutputStream err = new ByteArrayOutputStream();
EnvVars freshEnv = new EnvVars(env);
// If we don't have credentials, but the requested URL requires them,
@@ -1985,26 +2272,53 @@ private String launchCommandIn(ArgumentListBuilder args, File workDir, EnvVars e
/* GIT_SSH won't call the passphrase prompt script unless detached from controlling terminal */
args.prepend("setsid");
}
- listener.getLogger().println(" > " + command + (timeout != null ? TIMEOUT_LOG_PREFIX + timeout : ""));
- Launcher.ProcStarter p = launcher.launch().cmds(args.toCommandArray()).
- envs(freshEnv).stdout(fos).stderr(err);
- if (workDir != null) p.pwd(workDir);
- int status = p.start().joinWithTimeout(timeout != null ? timeout : TIMEOUT, TimeUnit.MINUTES, listener);
+ int usedTimeout = timeout == null ? TIMEOUT : timeout;
+ listener.getLogger().println(" > " + command + TIMEOUT_LOG_PREFIX + usedTimeout);
+
+ Launcher.ProcStarter p = launcher.launch().cmds(args.toCommandArray()).envs(freshEnv);
+
+ if (workDir != null) {
+ p.pwd(workDir);
+ }
+
+ int status;
+ String stdout;
+ String stderr;
+
+ if (isZos()) {
+ // Another behavior on z/OS required due to the race condition happening during transcoding of charset in
+ // EBCDIC code page if CopyThread is used on IBM z/OS Java. For unclear reason, if we rely on Proc class consumption
+ // of stdout and stderr with StreamCopyThread, then first several chars of a stream aren't get transcoded.
+ // Also, there is a need to pass a EBCDIC codepage conversion charset into input stream.
+ p.readStdout().readStderr();
+ Proc process = p.start();
+
+ status = process.joinWithTimeout(usedTimeout, TimeUnit.MINUTES, listener);
+
+ stdout = readProcessIntoString(process, encoding, false);
+ stderr = readProcessIntoString(process, encoding, true);
+ } else {
+ // JENKINS-13356: capture stdout and stderr separately
+ ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream stderrStream = new ByteArrayOutputStream();
+
+ p.stdout(stdoutStream).stderr(stderrStream);
+ status = p.start().joinWithTimeout(usedTimeout, TimeUnit.MINUTES, listener);
+
+ stdout = stdoutStream.toString(encoding);
+ stderr = stderrStream.toString(encoding);
+ }
- String result = fos.toString(Charset.defaultCharset().toString());
if (status != 0) {
- throw new GitException("Command \""+command+"\" returned status code " + status + ":\nstdout: " + result + "\nstderr: "+ err.toString(Charset.defaultCharset().toString()));
+ throw new GitException("Command \"" + command + "\" returned status code " + status + ":\nstdout: " + stdout + "\nstderr: "+ stderr);
}
- return result;
+ return stdout;
} catch (GitException | InterruptedException e) {
throw e;
- } catch (IOException e) {
- throw new GitException("Error performing command: " + command, e);
- } catch (Throwable t) {
- throw new GitException("Error performing git command", t);
+ } catch (Throwable e) {
+ throw new GitException("Error performing git command: " + command, e);
}
-
}
/**
@@ -2014,22 +2328,25 @@ private String launchCommandIn(ArgumentListBuilder args, File workDir, EnvVars e
*/
public PushCommand push() {
return new PushCommand() {
- public URIish remote;
- public String refspec;
- public boolean force;
- public boolean tags;
- public Integer timeout;
+ private URIish remote;
+ private String refspec;
+ private boolean force;
+ private boolean tags;
+ private Integer timeout;
+ @Override
public PushCommand to(URIish remote) {
this.remote = remote;
return this;
}
+ @Override
public PushCommand ref(String refspec) {
this.refspec = refspec;
return this;
}
+ @Override
public PushCommand force() {
return force(true);
}
@@ -2040,16 +2357,19 @@ public PushCommand force(boolean force) {
return this;
}
+ @Override
public PushCommand tags(boolean tags) {
this.tags = tags;
return this;
}
+ @Override
public PushCommand timeout(Integer timeout) {
this.timeout = timeout;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
ArgumentListBuilder args = new ArgumentListBuilder();
args.add("push", remote.toPrivateASCIIString());
@@ -2188,39 +2508,45 @@ public Set getRemoteBranches() throws GitException, InterruptedException
public CheckoutCommand checkout() {
return new CheckoutCommand() {
- public String ref;
- public String branch;
- public boolean deleteBranch;
- public List sparseCheckoutPaths = Collections.emptyList();
- public Integer timeout;
- public String lfsRemote;
- public StandardCredentials lfsCredentials;
+ private String ref;
+ private String branch;
+ private boolean deleteBranch;
+ private List sparseCheckoutPaths = Collections.emptyList();
+ private Integer timeout;
+ private String lfsRemote;
+ private StandardCredentials lfsCredentials;
+ @Override
public CheckoutCommand ref(String ref) {
this.ref = ref;
return this;
}
+ @Override
public CheckoutCommand branch(String branch) {
this.branch = branch;
return this;
}
+ @Override
public CheckoutCommand deleteBranchIfExist(boolean deleteBranch) {
this.deleteBranch = deleteBranch;
return this;
}
+ @Override
public CheckoutCommand sparseCheckoutPaths(List sparseCheckoutPaths) {
this.sparseCheckoutPaths = sparseCheckoutPaths == null ? Collections.emptyList() : sparseCheckoutPaths;
return this;
}
+ @Override
public CheckoutCommand timeout(Integer timeout) {
this.timeout = timeout;
return this;
}
+ @Override
public CheckoutCommand lfsRemote(String lfsRemote) {
this.lfsRemote = lfsRemote;
return this;
@@ -2244,6 +2570,7 @@ private void interruptThisCheckout() throws InterruptedException {
throw new InterruptedException(interruptMessage);
}
+ @Override
public void execute() throws GitException, InterruptedException {
/* File.lastModified() limited by file system time, several
* popular Linux file systems only have 1 second granularity.
@@ -2441,12 +2768,13 @@ public List lsTree(String treeIsh, boolean recursive) throws GitExce
*/
public RevListCommand revList_() {
return new RevListCommand() {
- public boolean all;
- public boolean nowalk;
- public boolean firstParent;
- public String refspec;
- public List out;
+ private boolean all;
+ private boolean nowalk;
+ private boolean firstParent;
+ private String refspec;
+ private List out;
+ @Override
public RevListCommand all() {
return all(true);
}
@@ -2456,6 +2784,8 @@ public RevListCommand all(boolean all) {
this.all = all;
return this;
}
+
+ @Override
public RevListCommand nowalk(boolean nowalk) {
// --no-walk wasn't introduced until v1.5.3
if (isAtLeastVersion(1, 5, 3, 0)) {
@@ -2464,6 +2794,7 @@ public RevListCommand nowalk(boolean nowalk) {
return this;
}
+ @Override
public RevListCommand firstParent() {
return firstParent(true);
}
@@ -2474,16 +2805,19 @@ public RevListCommand firstParent(boolean firstParent) {
return this;
}
+ @Override
public RevListCommand to(List revs){
this.out = revs;
return this;
}
+ @Override
public RevListCommand reference(String reference){
this.refspec = reference;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
ArgumentListBuilder args = new ArgumentListBuilder("rev-list");
@@ -2985,7 +3319,11 @@ public String getAllLogEntries(String branch) throws InterruptedException {
/** inline ${@link hudson.Functions#isWindows()} to prevent a transient remote classloader issue */
private boolean isWindows() {
- return File.pathSeparatorChar==';';
+ return File.pathSeparatorChar == ';';
+ }
+
+ private boolean isZos() {
+ return File.pathSeparatorChar == ':' && System.getProperty("os.name").equals("z/OS");
}
/* Return true if setsid program exists */
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/CloneCommand.java b/src/main/java/org/jenkinsci/plugins/gitclient/CloneCommand.java
index 6c357cd8a7..3f2a0caa91 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/CloneCommand.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/CloneCommand.java
@@ -109,7 +109,7 @@ public interface CloneCommand extends GitCommand {
/**
* When shallow cloning, allow for a depth to be set in cases where you need more than the immediate last commit.
- * Has no effect if shallow is set to false (default)
+ * Has no effect if shallow is set to false (default).
*
* @param depth number of revisions to be included in shallow clone
* @return a {@link org.jenkinsci.plugins.gitclient.CloneCommand} object.
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/Git.java b/src/main/java/org/jenkinsci/plugins/gitclient/Git.java
index 3d8c616441..ead40c530a 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/Git.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/Git.java
@@ -117,23 +117,7 @@ public Git using(String exe) {
* @throws java.lang.InterruptedException if interrupted.
*/
public GitClient getClient() throws IOException, InterruptedException {
- jenkins.MasterToSlaveFileCallable callable = new jenkins.MasterToSlaveFileCallable() {
- public GitClient invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- if (listener == null) listener = TaskListener.NULL;
- if (env == null) env = new EnvVars();
-
- if (exe == null || JGitTool.MAGIC_EXENAME.equalsIgnoreCase(exe)) {
- return new JGitAPIImpl(f, listener);
- }
-
- if (JGitApacheTool.MAGIC_EXENAME.equalsIgnoreCase(exe)) {
- final PreemptiveAuthHttpClientConnectionFactory factory = new PreemptiveAuthHttpClientConnectionFactory();
- return new JGitAPIImpl(f, listener, factory);
- }
- // Ensure we return a backward compatible GitAPI, even API only claim to provide a GitClient
- return new GitAPI(exe, f, listener, env);
- }
- };
+ jenkins.MasterToSlaveFileCallable callable = new GitAPIMasterToSlaveFileCallable();
GitClient git = (repository!=null ? repository.act(callable) : callable.invoke(null,null));
Jenkins jenkinsInstance = Jenkins.getInstance();
if (jenkinsInstance != null && git != null)
@@ -151,4 +135,22 @@ public GitClient invoke(File f, VirtualChannel channel) throws IOException, Inte
public static final boolean USE_CLI = Boolean.valueOf(System.getProperty(Git.class.getName() + ".useCLI", "true"));
private static final long serialVersionUID = 1L;
+
+ private class GitAPIMasterToSlaveFileCallable extends jenkins.MasterToSlaveFileCallable {
+ public GitClient invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ if (listener == null) listener = TaskListener.NULL;
+ if (env == null) env = new EnvVars();
+
+ if (exe == null || JGitTool.MAGIC_EXENAME.equalsIgnoreCase(exe)) {
+ return new JGitAPIImpl(f, listener);
+ }
+
+ if (JGitApacheTool.MAGIC_EXENAME.equalsIgnoreCase(exe)) {
+ final PreemptiveAuthHttpClientConnectionFactory factory = new PreemptiveAuthHttpClientConnectionFactory();
+ return new JGitAPIImpl(f, listener, factory);
+ }
+ // Ensure we return a backward compatible GitAPI, even API only claim to provide a GitClient
+ return new GitAPI(exe, f, listener, env);
+ }
+ }
}
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/GitClient.java b/src/main/java/org/jenkinsci/plugins/gitclient/GitClient.java
index f4650e79d3..f3a412bd5e 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/GitClient.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/GitClient.java
@@ -49,7 +49,6 @@ public interface GitClient {
CredentialsMatcher CREDENTIALS_MATCHER = CredentialsMatchers.anyOf(
CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class),
CredentialsMatchers.instanceOf(SSHUserPrivateKey.class)
- // TODO does anyone use SSL client certificates with GIT?
);
/**
@@ -232,7 +231,7 @@ public interface GitClient {
/**
* Checks out the specified commit/tag/branch into the workspace.
- * (equivalent of git checkout branch.)
+ * (equivalent of git checkout branch.)
*
* @param ref A git object references expression (either a sha1, tag or branch)
* @deprecated use {@link #checkout()} and {@link org.jenkinsci.plugins.gitclient.CheckoutCommand}
@@ -247,7 +246,7 @@ public interface GitClient {
*
* This will fail if the branch already exists.
*
- * @param ref A git object references expression. For backward compatibility, null will checkout current HEAD
+ * @param ref A git object references expression. For backward compatibility, null will checkout current HEAD
* @param branch name of the branch to create from reference
* @deprecated use {@link #checkout()} and {@link org.jenkinsci.plugins.gitclient.CheckoutCommand}
* @throws hudson.plugins.git.GitException if underlying git operation fails.
@@ -269,7 +268,7 @@ public interface GitClient {
*
*
* - The branch of the specified name branch exists and points to the specified ref
- *
- HEAD points to branch. IOW, the workspace is on the specified branch.
+ *
HEAD points to branch. In other words, the workspace is on the specified branch.
* - Both index and workspace are the same tree with ref.
* (no dirty files and no staged changes, although this method will not touch untracked files
* in the workspace.)
@@ -277,7 +276,7 @@ public interface GitClient {
*
*
* This method is preferred over the {@link #checkout(String, String)} family of methods, as
- * this method is affected far less by the current state of the repository. The checkout
+ * this method is affected far less by the current state of the repository. The checkout
* methods, in their attempt to emulate the "git checkout" command line behaviour, have too many
* side effects. In Jenkins, where you care a lot less about throwing away local changes and
* care a lot more about resetting the workspace into a known state, methods like this is more useful.
@@ -298,7 +297,7 @@ public interface GitClient {
* Clone a remote repository
*
* @param url URL for remote repository to clone
- * @param origin upstream track name, defaults to origin by convention
+ * @param origin upstream track name, defaults to origin by convention
* @param useShallowClone option to create a shallow clone, that has some restriction but will make clone operation
* @param reference (optional) reference to a local clone for faster clone operations (reduce network and local storage costs)
* @throws hudson.plugins.git.GitException if underlying git operation fails.
@@ -315,7 +314,7 @@ public interface GitClient {
/**
* Fetch commits from url which match any of the passed in
- * refspecs. Assumes remote.remoteName.url has been set.
+ * refspecs. Assumes remote.remoteName.url has been set.
*
* @deprecated use {@link #fetch_()} and configure a {@link org.jenkinsci.plugins.gitclient.FetchCommand}
* @param url a {@link org.eclipse.jgit.transport.URIish} object.
@@ -435,6 +434,18 @@ public interface GitClient {
*/
void clean() throws GitException, InterruptedException;
+ /**
+ * Fully revert working copy to a clean state, i.e. run both
+ * git-reset(1) --hard then
+ * git-clean(1) for working copy to
+ * match a fresh clone.
+ *
+ * @param cleanSubmodule flag to add extra -f
+ * @throws hudson.plugins.git.GitException if underlying git operation fails.
+ * @throws java.lang.InterruptedException if interrupted.
+ */
+ void clean(boolean cleanSubmodule) throws GitException, InterruptedException;
+
// --- manage branches
@@ -479,7 +490,7 @@ public interface GitClient {
// --- manage tags
/**
- * Create (or update) a tag. If tag already exist it gets updated (equivalent to git tag --force)
+ * Create (or update) a tag. If tag already exist it gets updated (equivalent to git tag --force)
*
* @param tagName a {@link java.lang.String} object.
* @param comment a {@link java.lang.String} object.
@@ -540,7 +551,7 @@ public interface GitClient {
// --- manage refs
/**
- * Create (or update) a ref. The ref will reference HEAD (equivalent to git update-ref ... HEAD).
+ * Create (or update) a ref. The ref will reference HEAD (equivalent to git update-ref ... HEAD).
*
* @param refName the full name of the ref (e.g. "refs/myref"). Spaces will be replaced with underscores.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
@@ -549,7 +560,7 @@ public interface GitClient {
void ref(String refName) throws GitException, InterruptedException;
/**
- * Check if a ref exists. Equivalent to comparing the return code of git show-ref to zero.
+ * Check if a ref exists. Equivalent to comparing the return code of git show-ref to zero.
*
* @param refName the full name of the ref (e.g. "refs/myref"). Spaces will be replaced with underscores.
* @return True if the ref exists, false otherwse.
@@ -559,7 +570,7 @@ public interface GitClient {
boolean refExists(String refName) throws GitException, InterruptedException;
/**
- * Deletes a ref. Has no effect if the ref does not exist, equivalent to git update-ref -d.
+ * Deletes a ref. Has no effect if the ref does not exist, equivalent to git update-ref -d.
*
* @param refName the full name of the ref (e.g. "refs/myref"). Spaces will be replaced with underscores.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
@@ -568,7 +579,7 @@ public interface GitClient {
void deleteRef(String refName) throws GitException, InterruptedException;
/**
- * List refs with the given prefix. Equivalent to git for-each-ref --format="%(refname)".
+ * List refs with the given prefix. Equivalent to git for-each-ref --format="%(refname)".
*
* @param refPrefix the literal prefix any ref returned will have. The empty string implies all.
* @return a set of refs, each beginning with the given prefix. Empty if none.
@@ -601,7 +612,7 @@ public interface GitClient {
ObjectId getHeadRev(String remoteRepoUrl, String branch) throws GitException, InterruptedException;
/**
- * List references in a remote repository. Equivalent to git ls-remote [--heads] [--tags] <repository> [<refs>].
+ * List references in a remote repository. Equivalent to git ls-remote [--heads] [--tags] <repository> [<refs>].
*
* @param remoteRepoUrl
* Remote repository URL.
@@ -620,8 +631,8 @@ public interface GitClient {
Map getRemoteReferences(String remoteRepoUrl, String pattern, boolean headsOnly, boolean tagsOnly) throws GitException, InterruptedException;
/**
- * List symbolic references in a remote repository. Equivalent to git ls-remote --symref <repository>
- * [<refs>]. Note: the response may be empty for multiple reasons
+ * List symbolic references in a remote repository. Equivalent to git ls-remote --symref <repository>
+ * [<refs>]. Note: the response may be empty for multiple reasons
*
* @param remoteRepoUrl Remote repository URL.
* @param pattern Only references matching the given pattern are displayed.
@@ -634,7 +645,7 @@ public interface GitClient {
Map getRemoteSymbolicReferences(String remoteRepoUrl, String pattern) throws GitException, InterruptedException;
/**
- * Retrieve commit object that is direct child for revName revision reference.
+ * Retrieve commit object that is direct child for revName revision reference.
*
* @param revName a commit sha1 or tag/branch refname
* @throws hudson.plugins.git.GitException when no such commit / revName is found in repository.
@@ -711,7 +722,7 @@ public interface GitClient {
/**
* Run submodule update optionally recursively on all submodules
- * (equivalent of git submodule update --recursive.)
+ * (equivalent of git submodule update --recursive.)
*
* @deprecated use {@link #submoduleUpdate()} and {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand}
* @param recursive a boolean.
@@ -723,7 +734,7 @@ public interface GitClient {
/**
* Run submodule update optionally recursively on all submodules, with a specific
* reference passed to git clone if needing to --init.
- * (equivalent of git submodule update --recursive --reference 'reference'.)
+ * (equivalent of git submodule update --recursive --reference 'reference'.)
*
* @deprecated use {@link #submoduleUpdate()} and {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand}
* @param recursive a boolean.
@@ -735,7 +746,7 @@ public interface GitClient {
/**
* Run submodule update optionally recursively on all submodules, optionally with remoteTracking submodules
- * (equivalent of git submodule update --recursive --remote.)
+ * (equivalent of git submodule update --recursive --remote.)
*
* @deprecated use {@link #submoduleUpdate()} and {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand}
* @param recursive a boolean.
@@ -747,7 +758,7 @@ public interface GitClient {
/**
* Run submodule update optionally recursively on all submodules, optionally with remoteTracking, with a specific
* reference passed to git clone if needing to --init.
- * (equivalent of git submodule update --recursive --remote --reference 'reference'.)
+ * (equivalent of git submodule update --recursive --remote --reference 'reference'.)
*
* @deprecated use {@link #submoduleUpdate()} and {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand}
* @param recursive a boolean.
@@ -876,7 +887,7 @@ public interface GitClient {
* For merge commit, this method reports one diff per each parent. This makes this method
* behave differently from {@link #changelog()}.
*
- * @return The git show output, in raw format.
+ * @return The git show output, in raw format.
* @param from a {@link org.eclipse.jgit.lib.ObjectId} object.
* @param to a {@link org.eclipse.jgit.lib.ObjectId} object.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
@@ -900,7 +911,7 @@ public interface GitClient {
* For merge commit, this method reports one diff per each parent. This makes this method
* behave differently from {@link #changelog()}.
*
- * @return The git show output, in raw format.
+ * @return The git show output, in raw format.
* @param from a {@link org.eclipse.jgit.lib.ObjectId} object.
* @param to a {@link org.eclipse.jgit.lib.ObjectId} object.
* @param useRawOutput a {java.lang.Boolean} object.
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
index a9f0038e25..3feda708eb 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
@@ -21,7 +21,6 @@
import hudson.plugins.git.GitLockFailedException;
import hudson.plugins.git.IndexEntry;
import hudson.plugins.git.Revision;
-import hudson.util.IOUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -30,15 +29,12 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -50,6 +46,7 @@
import javax.annotation.Nullable;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.eclipse.jgit.api.AddNoteCommand;
import org.eclipse.jgit.api.CommitCommand;
@@ -175,16 +172,19 @@ public class JGitAPIImpl extends LegacyCompatibleGitAPIImpl {
/**
* clearCredentials.
*/
+ @Override
public void clearCredentials() {
asSmartCredentialsProvider().clearCredentials();
}
/** {@inheritDoc} */
+ @Override
public void addCredentials(String url, StandardCredentials credentials) {
asSmartCredentialsProvider().addCredentials(url, credentials);
}
/** {@inheritDoc} */
+ @Override
public void addDefaultCredentials(StandardCredentials credentials) {
asSmartCredentialsProvider().addDefaultCredentials(credentials);
}
@@ -210,16 +210,19 @@ private synchronized CredentialsProvider getProvider() {
}
/** {@inheritDoc} */
+ @Override
public GitClient subGit(String subdir) {
return new JGitAPIImpl(new File(workspace, subdir), listener);
}
/** {@inheritDoc} */
+ @Override
public void setAuthor(String name, String email) throws GitException {
author = new PersonIdent(name,email);
}
/** {@inheritDoc} */
+ @Override
public void setCommitter(String name, String email) throws GitException {
committer = new PersonIdent(name,email);
}
@@ -230,6 +233,7 @@ public void setCommitter(String name, String email) throws GitException {
* @throws hudson.plugins.git.GitException if underlying git operation fails.
* @throws java.lang.InterruptedException if interrupted.
*/
+ @Override
public void init() throws GitException, InterruptedException {
init_().workspace(workspace.getAbsolutePath()).execute();
}
@@ -247,34 +251,39 @@ private void doInit(String workspace, boolean bare) throws GitException {
*
* @return a {@link org.jenkinsci.plugins.gitclient.CheckoutCommand} object.
*/
+ @Override
public CheckoutCommand checkout() {
return new CheckoutCommand() {
+ private String ref;
+ private String branch;
+ private boolean deleteBranch;
+ private List sparseCheckoutPaths = Collections.emptyList();
- public String ref;
- public String branch;
- public boolean deleteBranch;
- public List sparseCheckoutPaths = Collections.emptyList();
-
+ @Override
public CheckoutCommand ref(String ref) {
this.ref = ref;
return this;
}
+ @Override
public CheckoutCommand branch(String branch) {
this.branch = branch;
return this;
}
+ @Override
public CheckoutCommand deleteBranchIfExist(boolean deleteBranch) {
this.deleteBranch = deleteBranch;
return this;
}
+ @Override
public CheckoutCommand sparseCheckoutPaths(List sparseCheckoutPaths) {
this.sparseCheckoutPaths = sparseCheckoutPaths == null ? Collections.emptyList() : sparseCheckoutPaths;
return this;
}
+ @Override
public CheckoutCommand timeout(Integer timeout) {
// noop in jgit
return this;
@@ -295,6 +304,7 @@ public CheckoutCommand lfsCredentials(StandardCredentials lfsCredentials) {
return lfsCheckoutIsNotSupported();
}
+ @Override
public void execute() throws GitException, InterruptedException {
if(! sparseCheckoutPaths.isEmpty()) {
@@ -303,16 +313,16 @@ public void execute() throws GitException, InterruptedException {
}
if (branch == null)
- doCheckout(ref);
+ doCheckoutWithResetAndRetry(ref);
else if (deleteBranch)
- doCheckoutCleanBranch(branch, ref);
+ doCheckoutWithResetAndRetryAndCleanBranch(branch, ref);
else
doCheckout(ref, branch);
}
};
}
- private void doCheckout(String ref) throws GitException {
+ private void doCheckoutWithResetAndRetry(String ref) throws GitException {
boolean retried = false;
Repository repo = null;
while (true) {
@@ -344,7 +354,7 @@ private void doCheckout(String ref) throws GitException {
for (String remote : repo.getRemoteNames()) {
// look for exactly ONE remote tracking branch
String matchingRemoteBranch = Constants.R_REMOTES + remote + "/" + ref;
- if (repo.getRef(matchingRemoteBranch) != null) {
+ if (repo.exactRef(matchingRemoteBranch) != null) {
remoteTrackingBranches.add(matchingRemoteBranch);
}
}
@@ -400,14 +410,13 @@ private void doCheckout(String ref) throws GitException {
private void doCheckout(String ref, String branch) throws GitException {
try (Repository repo = getRepository()) {
- if (ref == null) ref = repo.resolve(HEAD).name();
git(repo).checkout().setName(branch).setCreateBranch(true).setForce(true).setStartPoint(ref).call();
- } catch (IOException | GitAPIException e) {
+ } catch (GitAPIException e) {
throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
}
}
- private void doCheckoutCleanBranch(String branch, String ref) throws GitException {
+ private void doCheckoutWithResetAndRetryAndCleanBranch(String branch, String ref) throws GitException {
try (Repository repo = getRepository()) {
RefUpdate refUpdate = repo.updateRef(R_HEADS + branch);
refUpdate.setNewObjectId(repo.resolve(ref));
@@ -421,7 +430,7 @@ private void doCheckoutCleanBranch(String branch, String ref) throws GitExceptio
throw new GitException("Could not update " + branch + " to " + ref);
}
- doCheckout(branch);
+ doCheckoutWithResetAndRetry(branch);
} catch (IOException e) {
throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
}
@@ -429,6 +438,7 @@ private void doCheckoutCleanBranch(String branch, String ref) throws GitExceptio
/** {@inheritDoc} */
+ @Override
public void add(String filePattern) throws GitException {
try (Repository repo = getRepository()) {
git(repo).add().addFilepattern(filePattern).call();
@@ -442,11 +452,10 @@ private Git git(Repository repo) {
}
/** {@inheritDoc} */
+ @Override
public void commit(String message) throws GitException {
try (Repository repo = getRepository()) {
- CommitCommand cmd = git(repo).commit().setMessage(message);
- if (author!=null)
- cmd.setAuthor(author);
+ CommitCommand cmd = git(repo).commit().setMessage(message).setAuthor(author);
if (committer!=null)
cmd.setCommitter(new PersonIdent(committer,new Date()));
cmd.call();
@@ -456,6 +465,7 @@ public void commit(String message) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void branch(String name) throws GitException {
try (Repository repo = getRepository()) {
git(repo).branchCreate().setName(name).call();
@@ -465,6 +475,7 @@ public void branch(String name) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void deleteBranch(String name) throws GitException {
try (Repository repo = getRepository()) {
git(repo).branchDelete().setForce(true).setBranchNames(name).call();
@@ -479,17 +490,9 @@ public void deleteBranch(String name) throws GitException {
* @return a {@link java.util.Set} object.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
*/
+ @Override
public Set getBranches() throws GitException {
- try (Repository repo = getRepository()) {
- List[ refs = git(repo).branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
- Set branches = new HashSet<>(refs.size());
- for (Ref ref : refs) {
- branches.add(new Branch(ref));
- }
- return branches;
- } catch (GitAPIException e) {
- throw new GitException(e);
- }
+ return getBranchesInternal(ListBranchCommand.ListMode.ALL);
}
/**
@@ -498,9 +501,14 @@ public Set getBranches() throws GitException {
* @return a {@link java.util.Set} object.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
*/
+ @Override
public Set getRemoteBranches() throws GitException {
+ return getBranchesInternal(ListBranchCommand.ListMode.REMOTE);
+ }
+
+ public Set getBranchesInternal(ListBranchCommand.ListMode mode) throws GitException {
try (Repository repo = getRepository()) {
- List][ refs = git(repo).branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
+ List][ refs = git(repo).branchList().setListMode(mode).call();
Set branches = new HashSet<>(refs.size());
for (Ref ref : refs) {
branches.add(new Branch(ref));
@@ -512,6 +520,7 @@ public Set getRemoteBranches() throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void tag(String name, String message) throws GitException {
try (Repository repo = getRepository()) {
git(repo).tag().setName(name).setMessage(message).setForceUpdate(true).call();
@@ -521,9 +530,10 @@ public void tag(String name, String message) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public boolean tagExists(String tagName) throws GitException {
try (Repository repo = getRepository()) {
- Ref tag = repo.getRefDatabase().getRef(R_TAGS + tagName);
+ Ref tag = repo.exactRef(R_TAGS + tagName);
return tag != null;
} catch (IOException e) {
throw new GitException(e);
@@ -535,21 +545,22 @@ public boolean tagExists(String tagName) throws GitException {
*
* @return a {@link org.jenkinsci.plugins.gitclient.FetchCommand} object.
*/
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand fetch_() {
return new org.jenkinsci.plugins.gitclient.FetchCommand() {
- public URIish url;
- public List refspecs;
- // JGit 3.3.0 thru 3.6.0 prune more branches than expected
- // Refer to GitAPITestCase.test_fetch_with_prune()
+ private URIish url;
+ private List refspecs;
private boolean shouldPrune = false;
- public boolean tags = true;
+ private boolean tags = true;
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand from(URIish remote, List refspecs) {
this.url = remote;
this.refspecs = refspecs;
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand prune() {
return prune(true);
}
@@ -560,6 +571,7 @@ public org.jenkinsci.plugins.gitclient.FetchCommand prune(boolean prune) {
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand shallow(boolean shallow) {
if (shallow) {
listener.getLogger().println("[WARNING] JGit doesn't support shallow clone. This flag is ignored");
@@ -567,67 +579,50 @@ public org.jenkinsci.plugins.gitclient.FetchCommand shallow(boolean shallow) {
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand timeout(Integer timeout) {
// noop in jgit
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand tags(boolean tags) {
this.tags = tags;
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.FetchCommand depth(Integer depth) {
listener.getLogger().println("[WARNING] JGit doesn't support shallow clone and therefore depth is meaningless. This flag is ignored");
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
Git git = git(repo);
List allRefSpecs = new ArrayList<>();
- if (tags) {
- // see http://stackoverflow.com/questions/14876321/jgit-fetch-dont-update-tag
- allRefSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
- }
if (refspecs != null)
for (RefSpec rs: refspecs)
if (rs != null)
allRefSpecs.add(rs);
- if (shouldPrune) {
- // since prune is broken in JGit, we go the trivial way:
- // delete all refs matching the right side of the refspecs
- // then fetch and let git recreate them.
- List][ refs = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
-
- List toDelete = new ArrayList<>(refs.size());
-
- for (ListIterator][ it = refs.listIterator(); it.hasNext(); ) {
- Ref branchRef = it.next();
- if (!branchRef.isSymbolic()) { // Don't delete HEAD and other symbolic refs
- for (RefSpec rs : allRefSpecs) {
- if (rs.matchDestination(branchRef)) {
- toDelete.add(branchRef.getName());
- break;
- }
- }
- }
- }
- if (!toDelete.isEmpty()) {
- // we need force = true because usually not all remote branches will be merged into the current branch.
- git.branchDelete().setForce(true).setBranchNames(toDelete.toArray(new String[toDelete.size()])).call();
- }
- }
-
FetchCommand fetch = git.fetch();
fetch.setTagOpt(tags ? TagOpt.FETCH_TAGS : TagOpt.NO_TAGS);
+ /* JGit 4.5 required a work around that the tags refspec had to be passed in addition to setting
+ * the FETCH_TAGS tagOpt. JGit 4.9.0 fixed that bug.
+ * However, JGit 4.9 and later will not accept an empty refspec.
+ * If the refspec is empty and tag fetch is requested, must add the tags refspec to fetch.
+ */
+ if (allRefSpecs.isEmpty() && tags) {
+ allRefSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
+ }
fetch.setRemote(url.toString());
fetch.setCredentialsProvider(getProvider());
fetch.setRefSpecs(allRefSpecs);
- // fetch.setRemoveDeletedRefs(shouldPrune);
+ fetch.setRemoveDeletedRefs(shouldPrune);
fetch.call();
} catch (GitAPIException e) {
@@ -645,20 +640,20 @@ public void execute() throws GitException, InterruptedException {
* @throws hudson.plugins.git.GitException if any.
* @throws java.lang.InterruptedException if any.
*/
+ @Override
public void fetch(URIish url, List refspecs) throws GitException, InterruptedException {
fetch_().from(url, refspecs).execute();
}
/** {@inheritDoc} */
+ @Override
public void fetch(String remoteName, RefSpec... refspec) throws GitException {
try (Repository repo = getRepository()) {
FetchCommand fetch = git(repo).fetch().setTagOpt(TagOpt.FETCH_TAGS);
if (remoteName != null) fetch.setRemote(remoteName);
fetch.setCredentialsProvider(getProvider());
- // see http://stackoverflow.com/questions/14876321/jgit-fetch-dont-update-tag
List refSpecs = new ArrayList<>();
- refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
if (refspec != null && refspec.length > 0)
for (RefSpec rs: refspec)
if (rs != null)
@@ -672,16 +667,18 @@ public void fetch(String remoteName, RefSpec... refspec) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void fetch(String remoteName, RefSpec refspec) throws GitException {
fetch(remoteName, new RefSpec[] {refspec});
}
/** {@inheritDoc} */
+ @Override
public void ref(String refName) throws GitException, InterruptedException {
refName = refName.replace(' ', '_');
try (Repository repo = getRepository()) {
RefUpdate refUpdate = repo.updateRef(refName);
- refUpdate.setNewObjectId(repo.getRef(Constants.HEAD).getObjectId());
+ refUpdate.setNewObjectId(repo.exactRef(Constants.HEAD).getObjectId());
switch (refUpdate.forceUpdate()) {
case NOT_ATTEMPTED:
case LOCK_FAILURE:
@@ -697,10 +694,11 @@ public void ref(String refName) throws GitException, InterruptedException {
}
/** {@inheritDoc} */
+ @Override
public boolean refExists(String refName) throws GitException, InterruptedException {
refName = refName.replace(' ', '_');
try (Repository repo = getRepository()) {
- Ref ref = repo.getRefDatabase().getRef(refName);
+ Ref ref = repo.findRef(refName);
return ref != null;
} catch (IOException e) {
throw new GitException("Error checking ref " + refName, e);
@@ -708,12 +706,13 @@ public boolean refExists(String refName) throws GitException, InterruptedExcepti
}
/** {@inheritDoc} */
+ @Override
public void deleteRef(String refName) throws GitException, InterruptedException {
refName = refName.replace(' ', '_');
try (Repository repo = getRepository()) {
RefUpdate refUpdate = repo.updateRef(refName);
// Required, even though this is a forced delete.
- refUpdate.setNewObjectId(repo.getRef(Constants.HEAD).getObjectId());
+ refUpdate.setNewObjectId(repo.exactRef(Constants.HEAD).getObjectId());
refUpdate.setForceUpdate(true);
switch (refUpdate.delete()) {
case NOT_ATTEMPTED:
@@ -730,6 +729,7 @@ public void deleteRef(String refName) throws GitException, InterruptedException
}
/** {@inheritDoc} */
+ @Override
public Set getRefNames(String refPrefix) throws GitException, InterruptedException {
if (refPrefix.isEmpty()) {
refPrefix = RefDatabase.ALL;
@@ -737,10 +737,9 @@ public Set getRefNames(String refPrefix) throws GitException, Interrupte
refPrefix = refPrefix.replace(' ', '_');
}
try (Repository repo = getRepository()) {
- Map refList = repo.getRefDatabase().getRefs(refPrefix);
- // The key set for refList will have refPrefix removed, so to recover it we just grab the full name.
+ List][ refList = repo.getRefDatabase().getRefsByPrefix(refPrefix);
Set refs = new HashSet<>(refList.size());
- for (Ref ref : refList.values()) {
+ for (Ref ref : refList) {
refs.add(ref.getName());
}
return refs;
@@ -750,23 +749,13 @@ public Set getRefNames(String refPrefix) throws GitException, Interrupte
}
/** {@inheritDoc} */
+ @Override
public Map getHeadRev(String url) throws GitException, InterruptedException {
- Map heads = new HashMap<>();
- try (Repository repo = openDummyRepository();
- final Transport tn = Transport.open(repo, new URIish(url))) {
- tn.setCredentialsProvider(getProvider());
- try (FetchConnection c = tn.openFetch()) {
- for (final Ref r : c.getRefs()) {
- heads.put(r.getName(), r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId());
- }
- }
- } catch (IOException | URISyntaxException e) {
- throw new GitException(e);
- }
- return heads;
+ return getRemoteReferences(url, null, true, false);
}
/** {@inheritDoc} */
+ @Override
public Map getRemoteReferences(String url, String pattern, boolean headsOnly, boolean tagsOnly)
throws GitException, InterruptedException {
Map references = new HashMap<>();
@@ -797,7 +786,7 @@ public Map getRemoteReferences(String url, String pattern, boo
references.put(refName, refObjectId);
}
}
- } catch (GitAPIException | IOException e) {
+ } catch (JGitInternalException | GitAPIException | IOException e) {
throw new GitException(e);
}
return references;
@@ -902,6 +891,7 @@ private String replaceGlobCharsWithRegExChars(String glob)
}
/** {@inheritDoc} */
+ @Override
public ObjectId getHeadRev(String remoteRepoUrl, String branchSpec) throws GitException {
try (Repository repo = openDummyRepository();
final Transport tn = Transport.open(repo, new URIish(remoteRepoUrl))) {
@@ -942,6 +932,7 @@ public void close() {
}
/** {@inheritDoc} */
+ @Override
public String getRemoteUrl(String name) throws GitException {
try (Repository repo = getRepository()) {
return repo.getConfig().getString("remote",name,"url");
@@ -955,6 +946,7 @@ public String getRemoteUrl(String name) throws GitException {
* @throws hudson.plugins.git.GitException if underlying git operation fails.
*/
@NonNull
+ @Override
public Repository getRepository() throws GitException {
try {
return new RepositoryBuilder().setWorkTree(workspace).build();
@@ -968,11 +960,13 @@ public Repository getRepository() throws GitException {
*
* @return a {@link hudson.FilePath} object.
*/
+ @Override
public FilePath getWorkTree() {
return new FilePath(workspace);
}
/** {@inheritDoc} */
+ @Override
public void setRemoteUrl(String name, String url) throws GitException {
try (Repository repo = getRepository()) {
StoredConfig config = repo.getConfig();
@@ -984,6 +978,7 @@ public void setRemoteUrl(String name, String url) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void addRemoteUrl(String name, String url) throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
StoredConfig config = repo.getConfig();
@@ -1000,6 +995,7 @@ public void addRemoteUrl(String name, String url) throws GitException, Interrupt
}
/** {@inheritDoc} */
+ @Override
public void addNote(String note, String namespace) throws GitException {
try (Repository repo = getRepository()) {
ObjectId head = repo.resolve(HEAD); // commit to put a note on
@@ -1035,6 +1031,7 @@ private String qualifyNotesNamespace(String namespace) {
}
/** {@inheritDoc} */
+ @Override
public void appendNote(String note, String namespace) throws GitException {
try (Repository repo = getRepository()) {
ObjectId head = repo.resolve(HEAD); // commit to put a note on
@@ -1069,11 +1066,12 @@ public void appendNote(String note, String namespace) throws GitException {
@Override
public ChangelogCommand changelog() {
return new ChangelogCommand() {
- Repository repo = getRepository();
- ObjectReader or = repo.newObjectReader();
- RevWalk walk = new RevWalk(or);
- Writer out;
- boolean hasIncludedRev = false;
+ private Repository repo = getRepository();
+ private ObjectReader or = repo.newObjectReader();
+ private RevWalk walk = new RevWalk(or);
+ private Writer out;
+ private boolean hasIncludedRev = false;
+ private boolean listMerges = false;
@Override
public ChangelogCommand excludes(String rev) {
@@ -1094,6 +1092,7 @@ public ChangelogCommand excludes(ObjectId rev) {
}
}
+ @Override
public ChangelogCommand includes(String rev) {
try {
includes(repo.resolve(rev));
@@ -1157,8 +1156,7 @@ public void execute() throws GitException, InterruptedException {
this.includes("HEAD");
}
for (RevCommit commit : walk) {
- // git whatachanged doesn't show the merge commits unless -m is given
- if (commit.getParentCount()>1) continue;
+ if (commit.getParentCount() > 1 && !listMerges) continue;
formatter.format(commit, null, pw, true);
}
@@ -1168,6 +1166,11 @@ public void execute() throws GitException, InterruptedException {
closeResources();
}
}
+
+ public ChangelogCommand listMerges(boolean flag) {
+ this.listMerges = flag;
+ return this;
+ }
};
}
@@ -1281,57 +1284,60 @@ void format(RevCommit commit, @Nullable RevCommit parent, PrintWriter pw, Boolea
/**
* clean.
*
+ * @param cleanSubmodule flag to add extra -f
* @throws hudson.plugins.git.GitException if underlying git operation fails.
*/
- public void clean() throws GitException {
+ @Override
+ public void clean(boolean cleanSubmodule) throws GitException {
try (Repository repo = getRepository()) {
Git git = git(repo);
git.reset().setMode(HARD).call();
- git.clean().setCleanDirectories(true).setIgnore(false).call();
+ git.clean().setCleanDirectories(true).setIgnore(false).setForce(cleanSubmodule).call();
} catch (GitAPIException e) {
throw new GitException(e);
- // Fix JENKINS-43198:
- // don't throw a "Could not delete file" if the file has actually been deleted
- // See JGit bug 514434 https://bugs.eclipse.org/bugs/show_bug.cgi?id=514434
- } catch(JGitInternalException e) {
- String expected = "Could not delete file ";
- if (e.getMessage().startsWith(expected)) {
- String path = e.getMessage().substring(expected.length());
- if (Files.exists(Paths.get(path))) {
- throw e;
- } // else don't throw, everything is ok.
- } else {
- throw e;
- }
}
}
+ /**
+ * clean.
+ *
+ * @throws hudson.plugins.git.GitException if underlying git operation fails.
+ */
+ @Override
+ public void clean() throws GitException {
+ this.clean(false);
+ }
+
/**
* clone_.
*
* @return a {@link org.jenkinsci.plugins.gitclient.CloneCommand} object.
*/
+ @Override
public CloneCommand clone_() {
return new CloneCommand() {
- String url;
- String remote = Constants.DEFAULT_REMOTE_NAME;
- String reference;
- Integer timeout;
- boolean shared;
- boolean tags = true;
- List refspecs;
+ private String url;
+ private String remote = Constants.DEFAULT_REMOTE_NAME;
+ private String reference;
+ private Integer timeout;
+ private boolean shared;
+ private boolean tags = true;
+ private List refspecs;
+ @Override
public CloneCommand url(String url) {
this.url = url;
return this;
}
+ @Override
public CloneCommand repositoryName(String name) {
this.remote = name;
return this;
}
+ @Override
public CloneCommand shallow() {
return shallow(true);
}
@@ -1344,6 +1350,7 @@ public CloneCommand shallow(boolean shallow) {
return this;
}
+ @Override
public CloneCommand shared() {
return shared(true);
}
@@ -1354,36 +1361,43 @@ public CloneCommand shared(boolean shared) {
return this;
}
+ @Override
public CloneCommand reference(String reference) {
this.reference = reference;
return this;
}
+ @Override
public CloneCommand refspecs(List refspecs) {
this.refspecs = new ArrayList<>(refspecs);
return this;
}
+ @Override
public CloneCommand timeout(Integer timeout) {
this.timeout = timeout;
return this;
}
+ @Override
public CloneCommand tags(boolean tags) {
this.tags = tags;
return this;
}
+ @Override
public CloneCommand noCheckout() {
// this.noCheckout = true; ignored, we never do a checkout
return this;
}
+ @Override
public CloneCommand depth(Integer depth) {
listener.getLogger().println("[WARNING] JGit doesn't support shallow clone and therefore depth is meaningless. This flag is ignored");
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
Repository repository = null;
@@ -1482,21 +1496,23 @@ else if (!referencePath.isDirectory())
*
* @return a {@link org.jenkinsci.plugins.gitclient.MergeCommand} object.
*/
+ @Override
public MergeCommand merge() {
return new MergeCommand() {
+ private ObjectId rev;
+ private MergeStrategy strategy;
+ private FastForwardMode fastForwardMode;
+ private boolean squash;
+ private boolean commit = true;
+ private String comment;
- ObjectId rev;
- MergeStrategy strategy;
- FastForwardMode fastForwardMode;
- boolean squash;
- boolean commit = true;
- String comment;
-
+ @Override
public MergeCommand setRevisionToMerge(ObjectId rev) {
this.rev = rev;
return this;
}
+ @Override
public MergeCommand setStrategy(MergeCommand.Strategy strategy) {
if (strategy != null && !strategy.toString().isEmpty() && strategy != MergeCommand.Strategy.DEFAULT) {
if (strategy == MergeCommand.Strategy.OURS) {
@@ -1521,6 +1537,7 @@ public MergeCommand setStrategy(MergeCommand.Strategy strategy) {
return this;
}
+ @Override
public MergeCommand setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwardMode fastForwardMode) {
if (fastForwardMode == MergeCommand.GitPluginFastForwardMode.FF) {
this.fastForwardMode = FastForwardMode.FF;
@@ -1532,21 +1549,25 @@ public MergeCommand setGitPluginFastForwardMode(MergeCommand.GitPluginFastForwar
return this;
}
+ @Override
public MergeCommand setSquash(boolean squash){
this.squash = squash;
return this;
}
+ @Override
public MergeCommand setMessage(String comment) {
this.comment = comment;
return this;
}
+ @Override
public MergeCommand setCommit(boolean commit) {
this.commit = commit;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
Git git = git(repo);
@@ -1571,37 +1592,43 @@ public void execute() throws GitException, InterruptedException {
*
* @return a {@link org.jenkinsci.plugins.gitclient.InitCommand} object.
*/
+ @Override
public InitCommand init_() {
return new InitCommand() {
+ private String workspace;
+ private boolean bare;
- public String workspace;
- public boolean bare;
-
+ @Override
public InitCommand workspace(String workspace) {
this.workspace = workspace;
return this;
}
+ @Override
public InitCommand bare(boolean bare) {
this.bare = bare;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
doInit(workspace, bare);
}
};
}
+ @Override
public RebaseCommand rebase() {
return new RebaseCommand() {
private String upstream;
+ @Override
public RebaseCommand setUpstream(String upstream) {
this.upstream = upstream;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
Git git = git(repo);
@@ -1618,6 +1645,7 @@ public void execute() throws GitException, InterruptedException {
}
/** {@inheritDoc} */
+ @Override
public void deleteTag(String tagName) throws GitException {
try (Repository repo = getRepository()) {
git(repo).tagDelete().setTags(tagName).call();
@@ -1627,6 +1655,7 @@ public void deleteTag(String tagName) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public String getTagMessage(String tagName) throws GitException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -1638,6 +1667,7 @@ public String getTagMessage(String tagName) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public List getSubmodules(String treeIsh) throws GitException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -1659,6 +1689,7 @@ public List getSubmodules(String treeIsh) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void addSubmodule(String remoteURL, String subdir) throws GitException {
try (Repository repo = getRepository()) {
git(repo).submoduleAdd().setPath(subdir).setURI(remoteURL).call();
@@ -1668,6 +1699,7 @@ public void addSubmodule(String remoteURL, String subdir) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public Set getTagNames(String tagPattern) throws GitException {
if (tagPattern == null) tagPattern = "*";
@@ -1687,6 +1719,7 @@ public Set getTagNames(String tagPattern) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public Set getRemoteTagNames(String tagPattern) throws GitException {
/* BUG: Lists local tag names, not remote tag names */
if (tagPattern == null) tagPattern = "*";
@@ -1694,8 +1727,8 @@ public Set getRemoteTagNames(String tagPattern) throws GitException {
try (Repository repo = getRepository()) {
Set tags = new HashSet<>();
FileNameMatcher matcher = new FileNameMatcher(tagPattern, '/');
- Map refList = repo.getRefDatabase().getRefs(R_TAGS);
- for (Ref ref : refList.values()) {
+ List][ refList = repo.getRefDatabase().getRefsByPrefix(R_TAGS);
+ for (Ref ref : refList) {
String name = ref.getName().substring(R_TAGS.length());
matcher.reset();
matcher.append(name);
@@ -1713,6 +1746,7 @@ public Set getRemoteTagNames(String tagPattern) throws GitException {
* @return true if this workspace has a git repository
* @throws hudson.plugins.git.GitException if underlying git operation fails.
*/
+ @Override
public boolean hasGitRepo() throws GitException {
try (Repository repo = getRepository()) {
return repo.getObjectDatabase().exists();
@@ -1722,6 +1756,7 @@ public boolean hasGitRepo() throws GitException {
}
/** {@inheritDoc} */
+ @Override
public boolean isCommitInRepo(ObjectId commit) throws GitException {
if (commit == null) {
return false;
@@ -1734,6 +1769,7 @@ public boolean isCommitInRepo(ObjectId commit) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public void prune(RemoteConfig repository) throws GitException {
try (Repository gitRepo = getRepository()) {
String remote = repository.getName();
@@ -1777,23 +1813,27 @@ private Set listRemoteBranches(String remote) throws NotSupportedExcepti
*
* @return a {@link org.jenkinsci.plugins.gitclient.PushCommand} object.
*/
+ @Override
public PushCommand push() {
return new PushCommand() {
- public URIish remote;
- public String refspec;
- public boolean force;
- public boolean tags;
+ private URIish remote;
+ private String refspec;
+ private boolean force;
+ private boolean tags;
+ @Override
public PushCommand to(URIish remote) {
this.remote = remote;
return this;
}
+ @Override
public PushCommand ref(String refspec) {
this.refspec = refspec;
return this;
}
+ @Override
public PushCommand force() {
return force(true);
}
@@ -1804,16 +1844,19 @@ public PushCommand force(boolean force) {
return this;
}
+ @Override
public PushCommand tags(boolean tags) {
this.tags = tags;
return this;
}
+ @Override
public PushCommand timeout(Integer timeout) {
// noop in jgit
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
RefSpec ref = (refspec != null) ? new RefSpec(fixRefSpec(repo)) : Transport.REFSPEC_PUSH_ALL;
@@ -1866,7 +1909,7 @@ private String fixRefSpec(Repository repository) throws IOException {
switch (spec) {
default:
case 0: //for the source ref. we use the repository to determine what should be pushed
- Ref ref = repository.getRef(specs[spec]);
+ Ref ref = repository.findRef(specs[spec]);
if (ref == null) {
throw new IOException(String.format("Ref %s not found.", specs[spec]));
}
@@ -1894,15 +1937,17 @@ private String fixRefSpec(Repository repository) throws IOException {
*
* @return a {@link org.jenkinsci.plugins.gitclient.RevListCommand} object.
*/
+ @Override
public RevListCommand revList_()
{
return new RevListCommand() {
- public boolean all;
- public boolean nowalk;
- public boolean firstParent;
- public String refspec;
- public List out;
+ private boolean all;
+ private boolean nowalk;
+ private boolean firstParent;
+ private String refspec;
+ private List out;
+ @Override
public RevListCommand all() {
return all(true);
}
@@ -1913,11 +1958,13 @@ public RevListCommand all(boolean all) {
return this;
}
+ @Override
public RevListCommand nowalk(boolean nowalk) {
this.nowalk = nowalk;
return this;
}
+ @Override
public RevListCommand firstParent() {
return firstParent(true);
}
@@ -1928,16 +1975,19 @@ public RevListCommand firstParent(boolean firstParent) {
return this;
}
+ @Override
public RevListCommand to(List revs){
this.out = revs;
return this;
}
+ @Override
public RevListCommand reference(String reference){
this.refspec = reference;
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
if (firstParent) {
throw new UnsupportedOperationException("not implemented yet");
@@ -1988,6 +2038,7 @@ else if (refspec != null)
* @return a {@link java.util.List} object.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
*/
+ @Override
public List revListAll() throws GitException {
List oidList = new ArrayList<>();
RevListCommand revListCommand = revList_();
@@ -2002,6 +2053,7 @@ public List revListAll() throws GitException {
}
/** {@inheritDoc} */
+ @Override
public List revList(String ref) throws GitException {
List oidList = new ArrayList<>();
RevListCommand revListCommand = revList_();
@@ -2016,6 +2068,7 @@ public List revList(String ref) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public ObjectId revParse(String revName) throws GitException {
try (Repository repo = getRepository()) {
ObjectId id = repo.resolve(revName + "^{commit}");
@@ -2028,11 +2081,13 @@ public ObjectId revParse(String revName) throws GitException {
}
/** {@inheritDoc} */
+ @Override
public List showRevision(ObjectId from, ObjectId to) throws GitException {
return showRevision(from, to, true);
}
/** {@inheritDoc} */
+ @Override
public List showRevision(ObjectId from, ObjectId to, Boolean useRawOutput) throws GitException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -2080,6 +2135,7 @@ private Iterable submodules() throws IOException {
}
/** {@inheritDoc} */
+ @Override
public void submoduleClean(boolean recursive) throws GitException {
try {
for (JGitAPIImpl sub : submodules()) {
@@ -2094,45 +2150,80 @@ public void submoduleClean(boolean recursive) throws GitException {
}
/**
- * submoduleUpdate.
+ * Update submodules.
*
* @return a {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand} object.
*/
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand submoduleUpdate() {
return new org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand() {
- boolean recursive = false;
- boolean remoteTracking = false;
- String ref = null;
+ private boolean recursive = false;
+ private boolean remoteTracking = false;
+ private String ref = null;
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand recursive(boolean recursive) {
this.recursive = recursive;
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand remoteTracking(boolean remoteTracking) {
this.remoteTracking = remoteTracking;
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand parentCredentials(boolean parentCredentials) {
// No-op for JGit implementation
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand ref(String ref) {
this.ref = ref;
return this;
}
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand timeout(Integer timeout) {
// noop in jgit
return this;
}
+ @Override
+ public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand shallow(boolean shallow) {
+ if (shallow) {
+ listener.getLogger().println("[WARNING] JGit doesn't support shallow clone. This flag is ignored");
+ }
+ return this;
+ }
+
+ @Override
+ public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand depth(Integer depth) {
+ listener.getLogger().println("[WARNING] JGit doesn't support shallow clone and therefore depth is meaningless. This flag is ignored");
+ return this;
+ }
+
+ @Override
+ public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand threads(Integer threads) {
+ // TODO: I have no idea if JGit can update submodules in parallel
+ // It might work, or it might blow up horribly. This probably depends on
+ // whether JGit relies on any global/shared state. Since I have no
+ // experience with JGit, I'm leaving this unimplemented for the time
+ // being. But if some brave soul wants to test this, feel free to provide
+ // an implementation similar to the one in the CliGitAPIImpl class using
+ // an ExecutorService.
+ listener.getLogger().println("[WARNING] JGit doesn't support updating submodules in parallel. This flag is ignored");
+ return this;
+ }
+
+ @Override
public org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand useBranch(String submodule, String branchname) {
return this;
}
+ @Override
public void execute() throws GitException, InterruptedException {
if (remoteTracking) {
listener.getLogger().println("[ERROR] JGit doesn't support remoteTracking submodules yet.");
@@ -2171,6 +2262,7 @@ public void execute() throws GitException, InterruptedException {
/** {@inheritDoc} */
@Deprecated
+ @Override
public void merge(String refSpec) throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
merge(repo.resolve(refSpec));
@@ -2181,11 +2273,13 @@ public void merge(String refSpec) throws GitException, InterruptedException {
/** {@inheritDoc} */
@Deprecated
+ @Override
public void push(RemoteConfig repository, String refspec) throws GitException, InterruptedException {
push(repository.getName(),refspec);
}
/** {@inheritDoc} */
+ @Override
public List getBranchesContaining(String revspec) throws GitException, InterruptedException {
// For the reasons of backward compatibility - we do not query remote branches here.
return getBranchesContaining(revspec, false);
@@ -2214,6 +2308,7 @@ public List getBranchesContaining(String revspec) throws GitException, I
* Since we reuse {@link RevWalk}, it'd be nice to flag commits reachable from 't' as uninteresting
* and keep them across resets, but I'm not sure how to do it.
*/
+ @Override
public List getBranchesContaining(String revspec, boolean allBranches) throws GitException, InterruptedException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -2289,6 +2384,7 @@ private List][ getAllBranchRefs(boolean originBranches) {
/** {@inheritDoc} */
@Deprecated
+ @Override
public ObjectId mergeBase(ObjectId id1, ObjectId id2) throws InterruptedException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -2309,6 +2405,7 @@ public ObjectId mergeBase(ObjectId id1, ObjectId id2) throws InterruptedExceptio
/** {@inheritDoc} */
@Deprecated
+ @Override
public String getAllLogEntries(String branch) throws InterruptedException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -2354,6 +2451,7 @@ private void markRefs(RevWalk walk, Predicate][ filter) throws IOException {
* @throws java.lang.InterruptedException if interrupted.
*/
@Deprecated
+ @Override
public void submoduleInit() throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
git(repo).submoduleInit().call();
@@ -2369,6 +2467,7 @@ public void submoduleInit() throws GitException, InterruptedException {
* @throws java.lang.InterruptedException if interrupted.
*/
@Deprecated
+ @Override
public void submoduleSync() throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
git(repo).submoduleSync().call();
@@ -2379,6 +2478,7 @@ public void submoduleSync() throws GitException, InterruptedException {
/** {@inheritDoc} */
@Deprecated
+ @Override
public String getSubmoduleUrl(String name) throws GitException, InterruptedException {
String v = null;
try (Repository repo = getRepository()) {
@@ -2390,6 +2490,7 @@ public String getSubmoduleUrl(String name) throws GitException, InterruptedExcep
/** {@inheritDoc} */
@Deprecated
+ @Override
public void setSubmoduleUrl(String name, String url) throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
StoredConfig config = repo.getConfig();
@@ -2409,6 +2510,7 @@ public void setSubmoduleUrl(String name, String url) throws GitException, Interr
* whoever manipulating Git.
*/
@Deprecated
+ @Override
public void setupSubmoduleUrls(Revision rev, TaskListener listener) throws GitException {
throw new UnsupportedOperationException("not implemented yet");
}
@@ -2422,6 +2524,7 @@ public void setupSubmoduleUrls(Revision rev, TaskListener listener) throws GitEx
* whoever manipulating Git.
*/
@Deprecated
+ @Override
public void fixSubmoduleUrls(String remote, TaskListener listener) throws GitException, InterruptedException {
throw new UnsupportedOperationException();
}
@@ -2443,6 +2546,7 @@ public void fixSubmoduleUrls(String remote, TaskListener listener) throws GitExc
* As we walk further and find enough tags, we go into wind-down mode and only walk
* to the point of accurately determining all the depths.
*/
+ @Override
public String describe(String tip) throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
final ObjectReader or = repo.newObjectReader();
@@ -2563,11 +2667,7 @@ public String describe(ObjectId tip) throws IOException {
throw new GitException("No tags can describe "+tip);
// if all the nodes are dominated by all the tags, the walk stops
- Collections.sort(candidates,new Comparator() {
- public int compare(Candidate o1, Candidate o2) {
- return o1.depth-o2.depth;
- }
- });
+ Collections.sort(candidates, (Candidate o1, Candidate o2) -> o1.depth-o2.depth);
return candidates.get(0).describe(tipId);
} catch (IOException e) {
@@ -2577,6 +2677,7 @@ public int compare(Candidate o1, Candidate o2) {
/** {@inheritDoc} */
@Deprecated
+ @Override
public List lsTree(String treeIsh, boolean recursive) throws GitException, InterruptedException {
try (Repository repo = getRepository();
ObjectReader or = repo.newObjectReader();
@@ -2602,6 +2703,7 @@ public List lsTree(String treeIsh, boolean recursive) throws GitExce
/** {@inheritDoc} */
@Deprecated
+ @Override
public void reset(boolean hard) throws GitException, InterruptedException {
try (Repository repo = getRepository()) {
ResetCommand reset = new ResetCommand(repo);
@@ -2614,6 +2716,7 @@ public void reset(boolean hard) throws GitException, InterruptedException {
/** {@inheritDoc} */
@Deprecated
+ @Override
public boolean isBareRepository(String GIT_DIR) throws GitException, InterruptedException {
Repository repo = null;
boolean isBare = false;
@@ -2641,6 +2744,7 @@ public boolean isBareRepository(String GIT_DIR) throws GitException, Interrupted
/** {@inheritDoc} */
@Deprecated
+ @Override
public String getDefaultRemote(String _default_) throws GitException, InterruptedException {
Set remotes = getConfig(null).getSubsections("remote");
if (remotes.contains(_default_)) return _default_;
@@ -2649,6 +2753,7 @@ public String getDefaultRemote(String _default_) throws GitException, Interrupte
/** {@inheritDoc} */
@Deprecated
+ @Override
public void setRemoteUrl(String name, String url, String GIT_DIR) throws GitException, InterruptedException {
try (Repository repo = new RepositoryBuilder().setGitDir(new File(GIT_DIR)).build()) {
StoredConfig config = repo.getConfig();
@@ -2661,6 +2766,7 @@ public void setRemoteUrl(String name, String url, String GIT_DIR) throws GitExce
/** {@inheritDoc} */
@Deprecated
+ @Override
public String getRemoteUrl(String name, String GIT_DIR) throws GitException, InterruptedException {
return getConfig(GIT_DIR).getString("remote", name, "url");
}
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/LegacyCompatibleGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/LegacyCompatibleGitAPIImpl.java
index 8d88e74b64..acf41c41b2 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/LegacyCompatibleGitAPIImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/LegacyCompatibleGitAPIImpl.java
@@ -194,8 +194,11 @@ public final List lsTree(String treeIsh) throws GitException, Interr
/** {@inheritDoc} */
@Override
- protected Object writeReplace() {
- return remoteProxyFor(Channel.current().export(IGitAPI.class, this));
+ protected Object writeReplace() throws java.io.ObjectStreamException {
+ Channel currentChannel = Channel.current();
+ if (currentChannel == null)
+ throw new java.io.WriteAbortedException("No current channel", new java.lang.NullPointerException());
+ return remoteProxyFor(currentChannel.export(IGitAPI.class, this));
}
/**
@@ -238,18 +241,19 @@ public List showRevision(ObjectId r) throws GitException, InterruptedExc
* current use cases are not disrupted by a behavioral change.
* ]
* E.g.
- *
- * | branch spec | normalized |
- * | master | master* |
- * | feature1 | feature1* |
- * | feature1/master | master feature1/master* |
- * | origin/master | master* |
- * | repo2/feature1 | feature1* |
- * | refs/heads/feature1 | refs/heads/feature1 |
+ *
+ * Branch Spec Normalization Examples
+ * | branch spec | normalized |
+ * master | master* |
+ * feature1 | feature1* |
+ * feature1/master | master feature1/master* |
+ * origin/master | master* |
+ * repo2/feature1 | feature1* |
+ * refs/heads/feature1 | refs/heads/feature1 |
* | origin/namespaceA/fix15 |
- * fix15 namespaceA/fix15* | |
- * | refs/heads/namespaceA/fix15 | refs/heads/namespaceA/fix15 |
- * | remotes/origin/namespaceA/fix15 | refs/heads/namespaceA/fix15 |
+ * fix15 namespaceA/fix15* | |
+ * refs/heads/namespaceA/fix15 | refs/heads/namespaceA/fix15 |
+ * remotes/origin/namespaceA/fix15 | refs/heads/namespaceA/fix15 |
*
* *) TODO: Normalize to "refs/heads/"
*
@@ -266,8 +270,8 @@ protected String extractBranchNameFromBranchSpec(String branchSpec) {
} else if (branchSpec.startsWith("refs/heads/")) {
branch = branchSpec;
} else if (branchSpec.startsWith("refs/tags/")) {
- //TODO: Discuss if tags shall be allowed.
- //hudson.plugins.git.util.DefaultBuildChooser.getCandidateRevisions() in git plugin 2.0.1 explicitly allowed it.
+ // Tags are allowed because git plugin 2.0.1
+ // DefaultBuildChooser.getCandidateRevisions() allowed them.
branch = branchSpec;
} else {
/* Old behaviour - retained for compatibility.
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/Netrc.java b/src/main/java/org/jenkinsci/plugins/gitclient/Netrc.java
index 45a4ab88c0..6b12fb3e14 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/Netrc.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/Netrc.java
@@ -2,11 +2,9 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.plugins.git.GitException;
-import hudson.util.IOUtils;
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
@@ -122,20 +120,24 @@ synchronized private Netrc parse() {
break;
case REQ_KEY:
- if ("login".equals(match)) {
- state = ParseState.LOGIN;
- }
- else if ("password".equals(match)) {
- state = ParseState.PASSWORD;
- }
- else if ("macdef".equals(match)) {
- state = ParseState.MACDEF;
- }
- else if ("machine".equals(match)) {
- state = ParseState.MACHINE;
- }
- else {
+ if (null == match) {
state = ParseState.REQ_VALUE;
+ } else switch (match) {
+ case "login":
+ state = ParseState.LOGIN;
+ break;
+ case "password":
+ state = ParseState.PASSWORD;
+ break;
+ case "macdef":
+ state = ParseState.MACDEF;
+ break;
+ case "machine":
+ state = ParseState.MACHINE;
+ break;
+ default:
+ state = ParseState.REQ_VALUE;
+ break;
}
break;
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/RemoteGitImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/RemoteGitImpl.java
index f386794d62..888cf4dbd6 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/RemoteGitImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/RemoteGitImpl.java
@@ -143,34 +143,36 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
public void execute() throws GitException, InterruptedException {
try {
- channel.call(new jenkins.security.MasterToSlaveCallable() {
- public Void call() throws GitException {
- try {
- GitCommand cmd = createCommand();
- for (Invocation inv : invocations) {
- inv.replay(cmd);
- }
- cmd.execute();
- return null;
- } catch (InvocationTargetException | IllegalAccessException | InterruptedException e) {
- throw new GitException(e);
- }
- }
-
- private GitCommand createCommand() throws InvocationTargetException, IllegalAccessException {
- for (Method m : GitClient.class.getMethods()) {
- if (m.getReturnType()==command && m.getParameterTypes().length==0)
- return command.cast(m.invoke(proxy));
- }
- throw new IllegalStateException("Can't find the factory method for "+command);
- }
- });
+ channel.call(new GitCommandMasterToSlaveCallable());
} catch (IOException e) {
throw new GitException(e);
}
}
private static final long serialVersionUID = 1L;
+
+ private class GitCommandMasterToSlaveCallable extends jenkins.security.MasterToSlaveCallable {
+ public Void call() throws GitException {
+ try {
+ GitCommand cmd = createCommand();
+ for (Invocation inv : invocations) {
+ inv.replay(cmd);
+ }
+ cmd.execute();
+ return null;
+ } catch (InvocationTargetException | IllegalAccessException | InterruptedException e) {
+ throw new GitException(e);
+ }
+ }
+
+ private GitCommand createCommand() throws InvocationTargetException, IllegalAccessException {
+ for (Method m : GitClient.class.getMethods()) {
+ if (m.getReturnType()==command && m.getParameterTypes().length==0)
+ return command.cast(m.invoke(proxy));
+ }
+ throw new IllegalStateException("Can't find the factory method for "+command);
+ }
+ }
}
private OutputStream wrap(OutputStream os) {
@@ -440,6 +442,17 @@ public void prune(RemoteConfig repository) throws GitException, InterruptedExcep
proxy.prune(repository);
}
+ /**
+ * clean.
+ *
+ * @param cleanSubmodule flag to add extra -f
+ * @throws hudson.plugins.git.GitException if underlying git operation fails.
+ * @throws java.lang.InterruptedException if interrupted.
+ */
+ public void clean(boolean cleanSubmodule) throws GitException, InterruptedException {
+ proxy.clean(cleanSubmodule);
+ }
+
/**
* clean.
*
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/SubmoduleUpdateCommand.java b/src/main/java/org/jenkinsci/plugins/gitclient/SubmoduleUpdateCommand.java
index ada0c10ad6..e952cba39c 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/SubmoduleUpdateCommand.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/SubmoduleUpdateCommand.java
@@ -58,4 +58,30 @@ public interface SubmoduleUpdateCommand extends GitCommand {
* @return a {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand} object.
*/
SubmoduleUpdateCommand timeout(Integer timeout);
+
+ /**
+ * Only clone the most recent history, not preceding history. Depth of the
+ * shallow clone is controlled by the #depth method.
+ *
+ * @param shallow boolean controlling whether the clone is shallow (requires git>=1.8.4)
+ * @return a {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand} object.
+ */
+ SubmoduleUpdateCommand shallow(boolean shallow);
+
+ /**
+ * When shallow cloning, allow for a depth to be set in cases where you need more than the immediate last commit.
+ * Has no effect if shallow is set to false (default).
+ *
+ * @param depth number of revisions to be included in shallow clone (requires git>=1.8.4)
+ * @return a {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand} object.
+ */
+ SubmoduleUpdateCommand depth(Integer depth);
+
+ /**
+ * Update submodules in parallel with the given number of threads.
+ *
+ * @param threads number of threads to use for updating submodules in parallel
+ * @return a {@link org.jenkinsci.plugins.gitclient.SubmoduleUpdateCommand} object.
+ */
+ SubmoduleUpdateCommand threads(Integer threads);
}
diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/jgit/PreemptiveAuthHttpClientConnection.java b/src/main/java/org/jenkinsci/plugins/gitclient/jgit/PreemptiveAuthHttpClientConnection.java
index 6c2b816a3a..6705573a99 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/jgit/PreemptiveAuthHttpClientConnection.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/jgit/PreemptiveAuthHttpClientConnection.java
@@ -443,12 +443,12 @@ public boolean verify(String hostname, SSLSession session) {
public void verify(String host, String[] cns, String[] subjectAlts)
throws SSLException {
- throw new UnsupportedOperationException(); // TODO message
+ throw new UnsupportedOperationException("Unsupported hostname verifier called for " + host);
}
public void verify(String host, X509Certificate cert)
throws SSLException {
- throw new UnsupportedOperationException(); // TODO message
+ throw new UnsupportedOperationException("Unsupported hostname verifier called for " + host + " with X.509 certificate");
}
public void verify(String host, SSLSocket ssl) throws IOException {
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
index 762c46223c..c24cd95964 100644
--- a/src/main/javadoc/overview.html
+++ b/src/main/javadoc/overview.html
@@ -5,31 +5,26 @@
-The Jenkins git client plugin provides an API to execute
-general-purpose git operations on a local or remote repository. Its
-primary use is from the
-git-plugin;
-as such, it is also used by
-gerrit-plugin,
-git-parameter-plugin,
-workflow and cloudbees validated merge plugins.
+The Jenkins git client plugin provides an API to execute general-purpose git operations on a local or remote repository.
+Its primary use is from the
+git plugin;
+as such, it is also used by the
+gerrit trigger plugin,
+git parameter plugin,
+and the branch source plugins (GitHub branch source, Bitbucket branch source, Gitea, and others).
Plugin developers are encouraged to use
-GitClient API in
-replacement for the legacy IGitAPI.
+GitClient API
+instead of the legacy IGitAPI.
-
The plugin isolates this low-level git stuff from git-plugin, allowing
-alternate git implementations
-(like JGit).
+The plugin isolates low-level git commands from the git-plugin, allowing alternate git implementations
+(like JGit).
-For backwards compatibility, this plugin uses API classes from the
-hudson.plugins.git package.
+For backwards compatibility, this plugin uses API classes from the hudson.plugins.git package.
-The git client plugin also bundles JGit and JGit http server so
-that callers can rely on JGit and the JGit http server being available
-without including it in their own plugin packaging. This is used to
-reduce the size of the packaging of the git-server plugin, and may be
-useful in other plugins.
+The git client plugin bundles JGit and JGit http server.
+Callers can rely on JGit and the JGit http server being available without including it in their own plugin packaging.
+This reduces the size dependent plugins like git-server plugin, and may be useful in other plugins.