/* * Copyright (C) 2012, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import java.io.IOException; import java.util.Map; import java.util.Set; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; /** * Implementation of {@link org.eclipse.jgit.transport.AdvertiseRefsHook} that advertises the same refs for * upload-pack and receive-pack. * * @since 2.0 */ public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook { @Override public void advertiseRefs(UploadPack uploadPack) throws ServiceMayNotContinueException { uploadPack.setAdvertisedRefs(getAdvertisedRefs( uploadPack.getRepository(), uploadPack.getRevWalk())); } /** */ @Override public void advertiseRefs(ReceivePack receivePack) throws IOException { Map refs = getAdvertisedRefs(receivePack.getRepository(), receivePack.getRevWalk()); Set haves = getAdvertisedHaves(receivePack.getRepository(), receivePack.getRevWalk()); receivePack.setAdvertisedRefs(refs, haves); } /** * Get the refs to advertise. * * @param repository * repository instance. * @param revWalk * open rev walk on the repository. * @return set of refs to advertise. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ protected abstract Map getAdvertisedRefs( Repository repository, RevWalk revWalk) throws ServiceMayNotContinueException; /** * Get the additional haves to advertise. * * @param repository * repository instance. * @param revWalk * open rev walk on the repository. * @return set of additional haves; see * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedObjects()}. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ protected Set getAdvertisedHaves( Repository repository, RevWalk revWalk) throws ServiceMayNotContinueException { return null; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3670 Content-Disposition: inline; filename="AdvertiseRefsHook.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "6381f1e94630d6b88374c78916a0ee7f445afdfb" /* * Copyright (C) 2012, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import java.io.IOException; /** * Hook to allow callers to take over advertising refs to the client. * * @since 2.0 */ public interface AdvertiseRefsHook { /** * A simple hook that advertises the default refs. *

* The method implementations do nothing to preserve the default behavior; * see {@link UploadPack#setAdvertisedRefs(java.util.Map)} and * {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. */ AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() { @Override public void advertiseRefs(UploadPack uploadPack) { // Do nothing. } @Override public void advertiseRefs(ReceivePack receivePack) { // Do nothing. } }; /** * Advertise refs for upload-pack. * * @param uploadPack * instance on which to call * {@link org.eclipse.jgit.transport.UploadPack#setAdvertisedRefs(java.util.Map)} * if necessary. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ void advertiseRefs(UploadPack uploadPack) throws ServiceMayNotContinueException; /** * Advertise refs for receive-pack. * * @param receivePack * instance on which to call * {@link org.eclipse.jgit.transport.ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} * if necessary. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. * @throws IOException * if an IO error occurred * @since 5.6 */ void advertiseRefs(ReceivePack receivePack) throws ServiceMayNotContinueException, IOException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2065 Content-Disposition: inline; filename="AdvertiseRefsHookChain.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "6db0775769b18622caee90105dce73161b65f17a" /* * Copyright (C) 2012, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.util.List; /** * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} that delegates to a list * of other hooks. *

* Hooks are run in the order passed to the constructor. A hook may inspect or * modify the results of the previous hooks in the chain by calling * {@link org.eclipse.jgit.transport.UploadPack#getAdvertisedRefs()}, or * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedRefs()} or * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedObjects()}. */ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { private final AdvertiseRefsHook[] hooks; private final int count; /** * Create a new hook chaining the given hooks together. * * @param hooks * hooks to execute, in order. * @return a new hook chain of the given hooks. */ public static AdvertiseRefsHook newChain(List hooks) { AdvertiseRefsHook[] newHooks = new AdvertiseRefsHook[hooks.size()]; int i = 0; for (AdvertiseRefsHook hook : hooks) if (hook != AdvertiseRefsHook.DEFAULT) newHooks[i++] = hook; switch (i) { case 0: return AdvertiseRefsHook.DEFAULT; case 1: return newHooks[0]; default: return new AdvertiseRefsHookChain(newHooks, i); } } @Override public void advertiseRefs(ReceivePack rp) throws IOException { for (int i = 0; i < count; i++) hooks[i].advertiseRefs(rp); } @Override public void advertiseRefs(UploadPack rp) throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].advertiseRefs(rp); } private AdvertiseRefsHookChain(AdvertiseRefsHook[] hooks, int count) { this.hooks = hooks; this.count = count; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 28334 Content-Disposition: inline; filename="AmazonS3.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "9d9f5495fe17cc38f731337ced021f0a9c43ef21" /* * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.ProxySelector; import java.net.URL; import java.net.URLConnection; import java.security.DigestOutputStream; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.util.Base64; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.TemporaryBuffer; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; /** * A simple HTTP REST client for the Amazon S3 service. *

* This client uses the REST API to communicate with the Amazon S3 servers and * read or write content through a bucket that the user has access to. It is a * very lightweight implementation of the S3 API and therefore does not have all * of the bells and whistles of popular client implementations. *

* Authentication is always performed using the user's AWSAccessKeyId and their * private AWSSecretAccessKey. *

* Optional client-side encryption may be enabled if requested. The format is * compatible with jets3t, * a popular Java based Amazon S3 client library. Enabling encryption can hide * sensitive data from the operators of the S3 service. */ public class AmazonS3 { private static final Set SIGNED_HEADERS; private static final String AWS_API_V2 = "2"; //$NON-NLS-1$ private static final String AWS_API_V4 = "4"; //$NON-NLS-1$ private static final String AWS_S3_SERVICE_NAME = "s3"; //$NON-NLS-1$ private static final String HMAC = "HmacSHA1"; //$NON-NLS-1$ private static final String X_AMZ_ACL = "x-amz-acl"; //$NON-NLS-1$ private static final String X_AMZ_META = "x-amz-meta-"; //$NON-NLS-1$ static { SIGNED_HEADERS = new HashSet<>(); SIGNED_HEADERS.add("content-type"); //$NON-NLS-1$ SIGNED_HEADERS.add("content-md5"); //$NON-NLS-1$ SIGNED_HEADERS.add("date"); //$NON-NLS-1$ } private static boolean isSignedHeader(String name) { final String nameLC = StringUtils.toLowerCase(name); return SIGNED_HEADERS.contains(nameLC) || nameLC.startsWith("x-amz-"); //$NON-NLS-1$ } private static String toCleanString(List list) { final StringBuilder s = new StringBuilder(); for (String v : list) { if (s.length() > 0) s.append(','); s.append(v.replace("\n", "").trim()); //$NON-NLS-1$ //$NON-NLS-2$ } return s.toString(); } private static String remove(Map m, String k) { final String r = m.remove(k); return r != null ? r : ""; //$NON-NLS-1$ } private static String httpNow() { final String tz = "GMT"; //$NON-NLS-1$ final SimpleDateFormat fmt; fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US); //$NON-NLS-1$ fmt.setTimeZone(TimeZone.getTimeZone(tz)); return fmt.format(new Date()) + " " + tz; //$NON-NLS-1$ } private static MessageDigest newMD5() { try { return MessageDigest.getInstance("MD5"); //$NON-NLS-1$ } catch (NoSuchAlgorithmException e) { throw new RuntimeException(JGitText.get().JRELacksMD5Implementation, e); } } /** AWS API Signature Version. */ private final String awsApiSignatureVersion; /** AWSAccessKeyId, public string that identifies the user's account. */ private final String publicKey; /** Decoded form of the private AWSSecretAccessKey, to sign requests. */ private final SecretKeySpec secretKeySpec; /** AWSSecretAccessKey, private string used to access a user's account. */ private final char[] secretKey; // store as char[] for security /** Our HTTP proxy support, in case we are behind a firewall. */ private final ProxySelector proxySelector; /** ACL to apply to created objects. */ private final String acl; /** Maximum number of times to try an operation. */ final int maxAttempts; /** Encryption algorithm, may be a null instance that provides pass-through. */ private final WalkEncryption encryption; /** Directory for locally buffered content. */ private final File tmpDir; /** S3 Bucket Domain. */ private final String domain; /** S3 Protocol, "https" or "http"; defaults to "http". */ private final String protocol; /** S3 Region. */ private final String region; /** Property names used in amazon connection configuration file. */ interface Keys { String AWS_API_SIGNATURE_VERSION = "aws.api.signature.version"; //$NON-NLS-1$ String ACCESS_KEY = "accesskey"; //$NON-NLS-1$ String SECRET_KEY = "secretkey"; //$NON-NLS-1$ String PASSWORD = "password"; //$NON-NLS-1$ String CRYPTO_ALG = "crypto.algorithm"; //$NON-NLS-1$ String CRYPTO_VER = "crypto.version"; //$NON-NLS-1$ String ACL = "acl"; //$NON-NLS-1$ String PROTOCOL = "protocol"; //$NON-NLS-1$ String DOMAIN = "domain"; //$NON-NLS-1$ String REGION = "region"; //$NON-NLS-1$ String HTTP_RETRY = "httpclient.retry-max"; //$NON-NLS-1$ String TMP_DIR = "tmpdir"; //$NON-NLS-1$ } /** * Create a new S3 client for the supplied user information. *

* The connection properties are a subset of those supported by the popular * jets3t library. * For example: * *

	 * # AWS API signature version, must be one of:
	 * #   2 - deprecated (not supported in all AWS regions)
	 * #   4 - latest (supported in all AWS regions)
	 * # Defaults to 2.
	 * aws.api.signature.version: 4
	 *
	 * # AWS Access and Secret Keys (required)
	 * accesskey: <YourAWSAccessKey>
	 * secretkey: <YourAWSSecretKey>
	 *
	 * # Access Control List setting to apply to uploads, must be one of:
	 * # PRIVATE, PUBLIC_READ (defaults to PRIVATE).
	 * acl: PRIVATE
	 *
	 * # S3 Domain
	 * # AWS S3 Region Domain (defaults to s3.amazonaws.com)
	 * domain: s3.amazonaws.com
	 *
	 * # AWS S3 Region (required if aws.api.signature.version = 4)
	 * region: us-west-2
	 *
	 * # Number of times to retry after internal error from S3.
	 * httpclient.retry-max: 3
	 *
	 * # End-to-end encryption (hides content from S3 owners)
	 * password: <encryption pass-phrase>
	 * crypto.algorithm: PBEWithMD5AndDES
	 * 
* * @param props * connection properties. */ public AmazonS3(final Properties props) { awsApiSignatureVersion = props .getProperty(Keys.AWS_API_SIGNATURE_VERSION, AWS_API_V2); if (awsApiSignatureVersion.equals(AWS_API_V4)) { region = props.getProperty(Keys.REGION); if (region == null) { throw new IllegalArgumentException( JGitText.get().missingAwsRegion); } } else if (awsApiSignatureVersion.equals(AWS_API_V2)) { region = null; } else { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidAwsApiSignatureVersion, awsApiSignatureVersion)); } protocol = props.getProperty(Keys.PROTOCOL, "http"); //$NON-NLS-1$ domain = props.getProperty(Keys.DOMAIN, "s3.amazonaws.com"); //$NON-NLS-1$ publicKey = props.getProperty(Keys.ACCESS_KEY); if (publicKey == null) throw new IllegalArgumentException(JGitText.get().missingAccesskey); final String secretKeyStr = props.getProperty(Keys.SECRET_KEY); if (secretKeyStr == null) { throw new IllegalArgumentException(JGitText.get().missingSecretkey); } secretKeySpec = new SecretKeySpec(Constants.encodeASCII(secretKeyStr), HMAC); secretKey = secretKeyStr.toCharArray(); final String pacl = props.getProperty(Keys.ACL, "PRIVATE"); //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase("PRIVATE", pacl)) //$NON-NLS-1$ acl = "private"; //$NON-NLS-1$ else if (StringUtils.equalsIgnoreCase("PUBLIC", pacl)) //$NON-NLS-1$ acl = "public-read"; //$NON-NLS-1$ else if (StringUtils.equalsIgnoreCase("PUBLIC-READ", pacl)) //$NON-NLS-1$ acl = "public-read"; //$NON-NLS-1$ else if (StringUtils.equalsIgnoreCase("PUBLIC_READ", pacl)) //$NON-NLS-1$ acl = "public-read"; //$NON-NLS-1$ else throw new IllegalArgumentException("Invalid acl: " + pacl); //$NON-NLS-1$ try { encryption = WalkEncryption.instance(props); } catch (GeneralSecurityException e) { throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); } maxAttempts = Integer .parseInt(props.getProperty(Keys.HTTP_RETRY, "3")); //$NON-NLS-1$ proxySelector = ProxySelector.getDefault(); String tmp = props.getProperty(Keys.TMP_DIR); tmpDir = tmp != null && tmp.length() > 0 ? new File(tmp) : null; } /** * Get the content of a bucket object. * * @param bucket * name of the bucket storing the object. * @param key * key of the object within its bucket. * @return connection to stream the content of the object. The request * properties of the connection may not be modified by the caller as * the request parameters have already been signed. * @throws java.io.IOException * sending the request was not possible. */ public URLConnection get(String bucket, String key) throws IOException { for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) { final HttpURLConnection c = open("GET", bucket, key); //$NON-NLS-1$ authorize(c, Collections.emptyMap(), 0, null); switch (HttpSupport.response(c)) { case HttpURLConnection.HTTP_OK: encryption.validate(c, X_AMZ_META); return c; case HttpURLConnection.HTTP_NOT_FOUND: throw new FileNotFoundException(key); case HttpURLConnection.HTTP_INTERNAL_ERROR: continue; default: throw error(JGitText.get().s3ActionReading, key, c); } } throw maxAttempts(JGitText.get().s3ActionReading, key); } /** * Decrypt an input stream from {@link #get(String, String)}. * * @param u * connection previously created by {@link #get(String, String)}}. * @return stream to read plain text from. * @throws java.io.IOException * decryption could not be configured. */ public InputStream decrypt(URLConnection u) throws IOException { return encryption.decrypt(u.getInputStream()); } /** * List the names of keys available within a bucket. *

* This method is primarily meant for obtaining a "recursive directory * listing" rooted under the specified bucket and prefix location. * It returns the keys sorted in reverse order of LastModified time * (freshest keys first). * * @param bucket * name of the bucket whose objects should be listed. * @param prefix * common prefix to filter the results by. Must not be null. * Supplying the empty string will list all keys in the bucket. * Supplying a non-empty string will act as though a trailing '/' * appears in prefix, even if it does not. * @return list of keys starting with prefix, after removing * prefix (or prefix + "/")from all * of them. * @throws java.io.IOException * sending the request was not possible, or the response XML * document could not be parsed properly. */ public List list(String bucket, String prefix) throws IOException { if (prefix.length() > 0 && !prefix.endsWith("/")) //$NON-NLS-1$ prefix += "/"; //$NON-NLS-1$ final ListParser lp = new ListParser(bucket, prefix); do { lp.list(); } while (lp.truncated); Comparator comparator = Comparator.comparingLong(KeyInfo::getLastModifiedSecs); return lp.entries.stream().sorted(comparator.reversed()) .map(KeyInfo::getName).collect(Collectors.toList()); } /** * Delete a single object. *

* Deletion always succeeds, even if the object does not exist. * * @param bucket * name of the bucket storing the object. * @param key * key of the object within its bucket. * @throws java.io.IOException * deletion failed due to communications error. */ public void delete(String bucket, String key) throws IOException { for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) { final HttpURLConnection c = open("DELETE", bucket, key); //$NON-NLS-1$ authorize(c, Collections.emptyMap(), 0, null); switch (HttpSupport.response(c)) { case HttpURLConnection.HTTP_NO_CONTENT: return; case HttpURLConnection.HTTP_INTERNAL_ERROR: continue; default: throw error(JGitText.get().s3ActionDeletion, key, c); } } throw maxAttempts(JGitText.get().s3ActionDeletion, key); } /** * Atomically create or replace a single small object. *

* This form is only suitable for smaller contents, where the caller can * reasonable fit the entire thing into memory. *

* End-to-end data integrity is assured by internally computing the MD5 * checksum of the supplied data and transmitting the checksum along with * the data itself. * * @param bucket * name of the bucket storing the object. * @param key * key of the object within its bucket. * @param data * new data content for the object. Must not be null. Zero length * array will create a zero length object. * @throws java.io.IOException * creation/updating failed due to communications error. */ public void put(String bucket, String key, byte[] data) throws IOException { if (encryption != WalkEncryption.NONE) { // We have to copy to produce the cipher text anyway so use // the large object code path as it supports that behavior. // try (OutputStream os = beginPut(bucket, key, null, null)) { os.write(data); } return; } final String md5str = Base64.encodeBytes(newMD5().digest(data)); final String bodyHash = awsApiSignatureVersion.equals(AWS_API_V4) ? AwsRequestSignerV4.calculateBodyHash(data) : null; final String lenstr = String.valueOf(data.length); for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) { final HttpURLConnection c = open("PUT", bucket, key); //$NON-NLS-1$ c.setRequestProperty("Content-Length", lenstr); //$NON-NLS-1$ c.setRequestProperty("Content-MD5", md5str); //$NON-NLS-1$ c.setRequestProperty(X_AMZ_ACL, acl); authorize(c, Collections.emptyMap(), data.length, bodyHash); c.setDoOutput(true); c.setFixedLengthStreamingMode(data.length); try (OutputStream os = c.getOutputStream()) { os.write(data); } switch (HttpSupport.response(c)) { case HttpURLConnection.HTTP_OK: return; case HttpURLConnection.HTTP_INTERNAL_ERROR: continue; default: throw error(JGitText.get().s3ActionWriting, key, c); } } throw maxAttempts(JGitText.get().s3ActionWriting, key); } /** * Atomically create or replace a single large object. *

* Initially the returned output stream buffers data into memory, but if the * total number of written bytes starts to exceed an internal limit the data * is spooled to a temporary file on the local drive. *

* Network transmission is attempted only when close() gets * called at the end of output. Closing the returned stream can therefore * take significant time, especially if the written content is very large. *

* End-to-end data integrity is assured by internally computing the MD5 * checksum of the supplied data and transmitting the checksum along with * the data itself. * * @param bucket * name of the bucket storing the object. * @param key * key of the object within its bucket. * @param monitor * (optional) progress monitor to post upload completion to * during the stream's close method. * @param monitorTask * (optional) task name to display during the close method. * @return a stream which accepts the new data, and transmits once closed. * @throws java.io.IOException * if encryption was enabled it could not be configured. */ public OutputStream beginPut(final String bucket, final String key, final ProgressMonitor monitor, final String monitorTask) throws IOException { final MessageDigest md5 = newMD5(); final TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(tmpDir) { @Override public void close() throws IOException { super.close(); try { putImpl(bucket, key, md5.digest(), this, monitor, monitorTask); } finally { destroy(); } } }; return encryption.encrypt(new DigestOutputStream(buffer, md5)); } void putImpl(final String bucket, final String key, final byte[] csum, final TemporaryBuffer buf, ProgressMonitor monitor, String monitorTask) throws IOException { if (monitor == null) monitor = NullProgressMonitor.INSTANCE; if (monitorTask == null) monitorTask = MessageFormat.format(JGitText.get().progressMonUploading, key); final String md5str = Base64.encodeBytes(csum); final String bodyHash = awsApiSignatureVersion.equals(AWS_API_V4) ? AwsRequestSignerV4.calculateBodyHash(buf.toByteArray()) : null; final long len = buf.length(); for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) { final HttpURLConnection c = open("PUT", bucket, key); //$NON-NLS-1$ c.setFixedLengthStreamingMode(len); c.setRequestProperty("Content-MD5", md5str); //$NON-NLS-1$ c.setRequestProperty(X_AMZ_ACL, acl); encryption.request(c, X_AMZ_META); authorize(c, Collections.emptyMap(), len, bodyHash); c.setDoOutput(true); monitor.beginTask(monitorTask, (int) (len / 1024)); try (OutputStream os = c.getOutputStream()) { buf.writeTo(os, monitor); } finally { monitor.endTask(); } switch (HttpSupport.response(c)) { case HttpURLConnection.HTTP_OK: return; case HttpURLConnection.HTTP_INTERNAL_ERROR: continue; default: throw error(JGitText.get().s3ActionWriting, key, c); } } throw maxAttempts(JGitText.get().s3ActionWriting, key); } IOException error(final String action, final String key, final HttpURLConnection c) throws IOException { final IOException err = new IOException(MessageFormat.format( JGitText.get().amazonS3ActionFailed, action, key, Integer.valueOf(HttpSupport.response(c)), c.getResponseMessage())); if (c.getErrorStream() == null) { return err; } try (InputStream errorStream = c.getErrorStream()) { final ByteArrayOutputStream b = new ByteArrayOutputStream(); byte[] buf = new byte[2048]; for (;;) { final int n = errorStream.read(buf); if (n < 0) { break; } if (n > 0) { b.write(buf, 0, n); } } buf = b.toByteArray(); if (buf.length > 0) { err.initCause(new IOException("\n" + new String(buf, UTF_8))); //$NON-NLS-1$ } } return err; } IOException maxAttempts(String action, String key) { return new IOException(MessageFormat.format( JGitText.get().amazonS3ActionFailedGivingUp, action, key, Integer.valueOf(maxAttempts))); } private HttpURLConnection open(final String method, final String bucket, final String key) throws IOException { final Map noArgs = Collections.emptyMap(); return open(method, bucket, key, noArgs); } HttpURLConnection open(final String method, final String bucket, final String key, final Map args) throws IOException { final StringBuilder urlstr = new StringBuilder(); urlstr.append(protocol); urlstr.append("://"); //$NON-NLS-1$ urlstr.append(bucket); urlstr.append('.'); urlstr.append(domain); urlstr.append('/'); if (key.length() > 0) { if (awsApiSignatureVersion.equals(AWS_API_V2)) { HttpSupport.encode(urlstr, key); } else if (awsApiSignatureVersion.equals(AWS_API_V4)) { urlstr.append(key); } } if (!args.isEmpty()) { final Iterator> i; urlstr.append('?'); i = args.entrySet().iterator(); while (i.hasNext()) { final Map.Entry e = i.next(); urlstr.append(e.getKey()); urlstr.append('='); HttpSupport.encode(urlstr, e.getValue()); if (i.hasNext()) urlstr.append('&'); } } final URL url = new URL(urlstr.toString()); final Proxy proxy = HttpSupport.proxyFor(proxySelector, url); final HttpURLConnection c; c = (HttpURLConnection) url.openConnection(proxy); c.setRequestMethod(method); c.setRequestProperty("User-Agent", "jgit/1.0"); //$NON-NLS-1$ //$NON-NLS-2$ c.setRequestProperty("Date", httpNow()); //$NON-NLS-1$ return c; } void authorize(HttpURLConnection httpURLConnection, Map queryParams, long contentLength, final String bodyHash) throws IOException { if (awsApiSignatureVersion.equals(AWS_API_V2)) { authorizeV2(httpURLConnection); } else if (awsApiSignatureVersion.equals(AWS_API_V4)) { AwsRequestSignerV4.sign(httpURLConnection, queryParams, contentLength, bodyHash, AWS_S3_SERVICE_NAME, region, publicKey, secretKey); } } void authorizeV2(HttpURLConnection c) throws IOException { final Map> reqHdr = c.getRequestProperties(); final SortedMap sigHdr = new TreeMap<>(); for (Map.Entry> entry : reqHdr.entrySet()) { final String hdr = entry.getKey(); if (isSignedHeader(hdr)) sigHdr.put(StringUtils.toLowerCase(hdr), toCleanString(entry.getValue())); } final StringBuilder s = new StringBuilder(); s.append(c.getRequestMethod()); s.append('\n'); s.append(remove(sigHdr, "content-md5")); //$NON-NLS-1$ s.append('\n'); s.append(remove(sigHdr, "content-type")); //$NON-NLS-1$ s.append('\n'); s.append(remove(sigHdr, "date")); //$NON-NLS-1$ s.append('\n'); for (Map.Entry e : sigHdr.entrySet()) { s.append(e.getKey()); s.append(':'); s.append(e.getValue()); s.append('\n'); } final String host = c.getURL().getHost(); s.append('/'); s.append(host.substring(0, host.length() - domain.length() - 1)); s.append(c.getURL().getPath()); final String sec; try { final Mac m = Mac.getInstance(HMAC); m.init(secretKeySpec); sec = Base64.encodeBytes(m.doFinal(s.toString().getBytes(UTF_8))); } catch (NoSuchAlgorithmException e) { throw new IOException(MessageFormat.format(JGitText.get().noHMACsupport, HMAC, e.getMessage())); } catch (InvalidKeyException e) { throw new IOException(MessageFormat.format(JGitText.get().invalidKey, e.getMessage())); } c.setRequestProperty("Authorization", "AWS " + publicKey + ":" + sec); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } static Properties properties(File authFile) throws FileNotFoundException, IOException { final Properties p = new Properties(); try (FileInputStream in = new FileInputStream(authFile)) { p.load(in); } return p; } /** * KeyInfo enables sorting of keys by lastModified time */ private static final class KeyInfo { private final String name; private final long lastModifiedSecs; public KeyInfo(String aname, long lsecs) { name = aname; lastModifiedSecs = lsecs; } public String getName() { return name; } public long getLastModifiedSecs() { return lastModifiedSecs; } } private final class ListParser extends DefaultHandler { final List entries = new ArrayList<>(); private final String bucket; private final String prefix; boolean truncated; private StringBuilder data; private String keyName; private Instant keyLastModified; ListParser(String bn, String p) { bucket = bn; prefix = p; } void list() throws IOException { final Map args = new TreeMap<>(); if (prefix.length() > 0) args.put("prefix", prefix); //$NON-NLS-1$ if (!entries.isEmpty()) args.put("marker", prefix + entries.get(entries.size() - 1).getName()); //$NON-NLS-1$ for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) { final HttpURLConnection c = open("GET", bucket, "", args); //$NON-NLS-1$ //$NON-NLS-2$ authorize(c, args, 0, null); switch (HttpSupport.response(c)) { case HttpURLConnection.HTTP_OK: truncated = false; data = null; keyName = null; keyLastModified = null; final XMLReader xr; try { SAXParserFactory saxParserFactory = SAXParserFactory .newInstance(); saxParserFactory.setNamespaceAware(true); saxParserFactory.setFeature( "http://xml.org/sax/features/external-general-entities", //$NON-NLS-1$ false); saxParserFactory.setFeature( "http://xml.org/sax/features/external-parameter-entities", //$NON-NLS-1$ false); saxParserFactory.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", //$NON-NLS-1$ true); xr = saxParserFactory.newSAXParser().getXMLReader(); } catch (SAXException | ParserConfigurationException e) { throw new IOException( JGitText.get().noXMLParserAvailable, e); } xr.setContentHandler(this); try (InputStream in = c.getInputStream()) { xr.parse(new InputSource(in)); } catch (SAXException parsingError) { throw new IOException( MessageFormat.format( JGitText.get().errorListing, prefix), parsingError); } return; case HttpURLConnection.HTTP_INTERNAL_ERROR: continue; default: throw AmazonS3.this.error("Listing", prefix, c); //$NON-NLS-1$ } } throw maxAttempts("Listing", prefix); //$NON-NLS-1$ } @Override public void startElement(final String uri, final String name, final String qName, final Attributes attributes) throws SAXException { if ("Key".equals(name) || "IsTruncated".equals(name) || "LastModified".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ data = new StringBuilder(); } if ("Contents".equals(name)) { //$NON-NLS-1$ keyName = null; keyLastModified = null; } } @Override public void ignorableWhitespace(final char[] ch, final int s, final int n) throws SAXException { if (data != null) data.append(ch, s, n); } @Override public void characters(char[] ch, int s, int n) throws SAXException { if (data != null) data.append(ch, s, n); } @Override public void endElement(final String uri, final String name, final String qName) throws SAXException { if ("Key".equals(name)) { //$NON-NLS-1$ keyName = data.toString().substring(prefix.length()); } else if ("IsTruncated".equals(name)) { //$NON-NLS-1$ truncated = StringUtils.equalsIgnoreCase("true", data.toString()); //$NON-NLS-1$ } else if ("LastModified".equals(name)) { //$NON-NLS-1$ keyLastModified = Instant.parse(data.toString()); } else if ("Contents".equals(name)) { //$NON-NLS-1$ entries.add(new KeyInfo(keyName, keyLastModified.getEpochSecond())); } data = null; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10768 Content-Disposition: inline; filename="AwsRequestSignerV4.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "cf4420fe58b777640f2e3aefa9e329e53eba9b25" /* * Copyright (C) 2022, Workday Inc. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.Hex; import org.eclipse.jgit.util.HttpSupport; /** * Utility class for signing requests to AWS service endpoints using the V4 * signing protocol. * * Reference implementation: AWSS3SigV4JavaSamples.zip * * @see AWS * Signature Version 4 * * @since 5.13.1 */ public final class AwsRequestSignerV4 { /** AWS version 4 signing algorithm (for authorization header). **/ private static final String ALGORITHM = "HMAC-SHA256"; //$NON-NLS-1$ /** Java Message Authentication Code (MAC) algorithm name. **/ private static final String MAC_ALGORITHM = "HmacSHA256"; //$NON-NLS-1$ /** AWS version 4 signing scheme. **/ private static final String SCHEME = "AWS4"; //$NON-NLS-1$ /** AWS version 4 terminator string. **/ private static final String TERMINATOR = "aws4_request"; //$NON-NLS-1$ /** SHA-256 hash of an empty request body. **/ private static final String EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; //$NON-NLS-1$ /** Date format for the 'x-amz-date' header. **/ private static final DateTimeFormatter AMZ_DATE_FORMAT = DateTimeFormatter .ofPattern("yyyyMMdd'T'HHmmss'Z'"); //$NON-NLS-1$ /** Date format for the string-to-sign's scope. **/ private static final DateTimeFormatter SCOPE_DATE_FORMAT = DateTimeFormatter .ofPattern("yyyyMMdd"); //$NON-NLS-1$ private AwsRequestSignerV4() { // Don't instantiate utility class } /** * Sign the provided request with an AWS4 signature as the 'Authorization' * header. * * @param httpURLConnection * The request to sign. * @param queryParameters * The query parameters being sent in the request. * @param contentLength * The content length of the data being sent in the request * @param bodyHash * Hex-encoded SHA-256 hash of the data being sent in the request * @param serviceName * The signing name of the AWS service (e.g. "s3"). * @param regionName * The name of the AWS region that will handle the request (e.g. * "us-east-1"). * @param awsAccessKey * The user's AWS Access Key. * @param awsSecretKey * The user's AWS Secret Key. */ public static void sign(HttpURLConnection httpURLConnection, Map queryParameters, long contentLength, String bodyHash, String serviceName, String regionName, String awsAccessKey, char[] awsSecretKey) { // get request headers Map headers = new HashMap<>(); httpURLConnection.getRequestProperties() .forEach((headerName, headerValues) -> headers.put(headerName, String.join(",", headerValues))); //$NON-NLS-1$ // add required content headers if (contentLength > 0) { headers.put(HttpSupport.HDR_CONTENT_LENGTH, String.valueOf(contentLength)); } else { bodyHash = EMPTY_BODY_SHA256; } headers.put("x-amz-content-sha256", bodyHash); //$NON-NLS-1$ // add the 'x-amz-date' header OffsetDateTime now = Instant.now().atOffset(ZoneOffset.UTC); String amzDate = now.format(AMZ_DATE_FORMAT); headers.put("x-amz-date", amzDate); //$NON-NLS-1$ // add the 'host' header URL endpointUrl = httpURLConnection.getURL(); int port = endpointUrl.getPort(); String hostHeader = (port > -1) ? endpointUrl.getHost().concat(":" + port) //$NON-NLS-1$ : endpointUrl.getHost(); headers.put("Host", hostHeader); //$NON-NLS-1$ // construct the canonicalized request String canonicalizedHeaderNames = getCanonicalizeHeaderNames(headers); String canonicalizedHeaders = getCanonicalizedHeaderString(headers); String canonicalizedQueryParameters = getCanonicalizedQueryString( queryParameters); String httpMethod = httpURLConnection.getRequestMethod(); String canonicalRequest = httpMethod + '\n' + getCanonicalizedResourcePath(endpointUrl) + '\n' + canonicalizedQueryParameters + '\n' + canonicalizedHeaders + '\n' + canonicalizedHeaderNames + '\n' + bodyHash; // construct the string-to-sign String scopeDate = now.format(SCOPE_DATE_FORMAT); String scope = scopeDate + '/' + regionName + '/' + serviceName + '/' + TERMINATOR; String stringToSign = SCHEME + '-' + ALGORITHM + '\n' + amzDate + '\n' + scope + '\n' + Hex.toHexString(hash( canonicalRequest.getBytes(StandardCharsets.UTF_8))); // compute the signing key byte[] secretKey = (SCHEME + new String(awsSecretKey)).getBytes(UTF_8); byte[] dateKey = signStringWithKey(scopeDate, secretKey); byte[] regionKey = signStringWithKey(regionName, dateKey); byte[] serviceKey = signStringWithKey(serviceName, regionKey); byte[] signingKey = signStringWithKey(TERMINATOR, serviceKey); byte[] signature = signStringWithKey(stringToSign, signingKey); // construct the authorization header String credentialsAuthorizationHeader = "Credential=" + awsAccessKey //$NON-NLS-1$ + '/' + scope; String signedHeadersAuthorizationHeader = "SignedHeaders=" //$NON-NLS-1$ + canonicalizedHeaderNames; String signatureAuthorizationHeader = "Signature=" //$NON-NLS-1$ + Hex.toHexString(signature); String authorizationHeader = SCHEME + '-' + ALGORITHM + ' ' + credentialsAuthorizationHeader + ", " //$NON-NLS-1$ + signedHeadersAuthorizationHeader + ", " //$NON-NLS-1$ + signatureAuthorizationHeader; // Copy back the updated request headers headers.forEach(httpURLConnection::setRequestProperty); // Add the 'authorization' header httpURLConnection.setRequestProperty(HttpSupport.HDR_AUTHORIZATION, authorizationHeader); } /** * Calculates the hex-encoded SHA-256 hash of the provided byte array. * * @param data * Byte array to hash * * @return Hex-encoded SHA-256 hash of the provided byte array. */ public static String calculateBodyHash(final byte[] data) { return (data == null || data.length < 1) ? EMPTY_BODY_SHA256 : Hex.toHexString(hash(data)); } /** * Construct a string listing all request headers in sorted case-insensitive * order, separated by a ';'. * * @param headers * Map containing all request headers. * * @return String that lists all request headers in sorted case-insensitive * order, separated by a ';'. */ private static String getCanonicalizeHeaderNames( Map headers) { return headers.keySet().stream().map(String::toLowerCase).sorted() .collect(Collectors.joining(";")); //$NON-NLS-1$ } /** * Constructs the canonical header string for a request. * * @param headers * Map containing all request headers. * * @return The canonical headers with values for the request. */ private static String getCanonicalizedHeaderString( Map headers) { if (headers == null || headers.isEmpty()) { return ""; //$NON-NLS-1$ } StringBuilder sb = new StringBuilder(); headers.keySet().stream().sorted(String.CASE_INSENSITIVE_ORDER) .forEach(key -> { String header = key.toLowerCase().replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ String value = headers.get(key).replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ sb.append(header).append(':').append(value).append('\n'); }); return sb.toString(); } /** * Constructs the canonicalized resource path for an AWS service endpoint. * * @param url * The AWS service endpoint URL, including the path to any * resource. * * @return The canonicalized resource path for the AWS service endpoint. */ private static String getCanonicalizedResourcePath(URL url) { if (url == null) { return "/"; //$NON-NLS-1$ } String path = url.getPath(); if (path == null || path.isEmpty()) { return "/"; //$NON-NLS-1$ } String encodedPath = HttpSupport.urlEncode(path, true); if (encodedPath.startsWith("/")) { //$NON-NLS-1$ return encodedPath; } return "/".concat(encodedPath); //$NON-NLS-1$ } /** * Constructs the canonicalized query string for a request. * * @param queryParameters * The query parameters in the request. * * @return The canonicalized query string for the request. */ public static String getCanonicalizedQueryString( Map queryParameters) { if (queryParameters == null || queryParameters.isEmpty()) { return ""; //$NON-NLS-1$ } return queryParameters .keySet().stream().sorted().map( key -> HttpSupport.urlEncode(key, false) + '=' + HttpSupport.urlEncode( queryParameters.get(key), false)) .collect(Collectors.joining("&")); //$NON-NLS-1$ } /** * Hashes the provided byte array using the SHA-256 algorithm. * * @param data * The byte array to hash. * * @return Hashed string contents of the provided byte array. */ public static byte[] hash(byte[] data) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); //$NON-NLS-1$ md.update(data); return md.digest(); } catch (Exception e) { throw new RuntimeException( JGitText.get().couldNotHashByteArrayWithSha256, e); } } /** * Signs the provided string data using the specified key. * * @param stringToSign * The string data to sign. * @param key * The key material of the secret key. * * @return Signed string data. */ private static byte[] signStringWithKey(String stringToSign, byte[] key) { try { byte[] data = stringToSign.getBytes(StandardCharsets.UTF_8); Mac mac = Mac.getInstance(MAC_ALGORITHM); mac.init(new SecretKeySpec(key, MAC_ALGORITHM)); return mac.doFinal(data); } catch (Exception e) { throw new RuntimeException(JGitText.get().couldNotSignStringWithKey, e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3708 Content-Disposition: inline; filename="BaseConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "3ac9f59ba634ada866bbd84d8344a21fdbfdce2c" /* * Copyright (C) 2010, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.StringWriter; import java.io.Writer; import java.util.Collection; import java.util.Collections; import java.util.Map; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Ref; /** * Base helper class for implementing operations connections. * * @see BasePackConnection * @see BaseFetchConnection */ public abstract class BaseConnection implements Connection { private Map advertisedRefs = Collections.emptyMap(); private String peerUserAgent; private boolean startedOperation; private Writer messageWriter; @Override public Map getRefsMap() { return advertisedRefs; } @Override public final Collection getRefs() { return advertisedRefs.values(); } @Override public final Ref getRef(String name) { return advertisedRefs.get(name); } @Override public String getMessages() { return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$ } /** * {@inheritDoc} * * User agent advertised by the remote server. * @since 4.0 */ @Override public String getPeerUserAgent() { return peerUserAgent; } /** * Remember the remote peer's agent. * * @param agent * remote peer agent string. * @since 4.0 */ protected void setPeerUserAgent(String agent) { peerUserAgent = agent; } @Override public abstract void close(); /** * Denote the list of refs available on the remote repository. *

* Implementors should invoke this method once they have obtained the refs * that are available from the remote repository. * * @param all * the complete list of refs the remote has to offer. This map * will be wrapped in an unmodifiable way to protect it, but it * does not get copied. */ protected void available(Map all) { advertisedRefs = Collections.unmodifiableMap(all); } /** * Helper method for ensuring one-operation per connection. Check whether * operation was already marked as started, and mark it as started. * * @throws org.eclipse.jgit.errors.TransportException * if operation was already marked as started. */ protected void markStartedOperation() throws TransportException { if (startedOperation) throw new TransportException( JGitText.get().onlyOneOperationCallPerConnectionIsSupported); startedOperation = true; } /** * Get the writer that buffers messages from the remote side. * * @return writer to store messages from the remote. */ protected Writer getMessageWriter() { if (messageWriter == null) setMessageWriter(new StringWriter()); return messageWriter; } /** * Set the writer that buffers messages from the remote side. * * @param writer * the writer that messages will be delivered to. The writer's * {@code toString()} method should be overridden to return the * complete contents. */ protected void setMessageWriter(Writer writer) { if (messageWriter != null) throw new IllegalStateException(JGitText.get().writerAlreadyInitialized); messageWriter = writer; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2580 Content-Disposition: inline; filename="BaseFetchConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "48992f3e3bd016dcb8bd70b4579e92e41ea50366" /* * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.OutputStream; import java.util.Collection; import java.util.Set; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; /** * Base helper class for fetch connection implementations. Provides some common * typical structures and methods used during fetch connection. *

* Implementors of fetch over pack-based protocols should consider using * {@link BasePackFetchConnection} instead. *

*/ abstract class BaseFetchConnection extends BaseConnection implements FetchConnection { @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException { fetch(monitor, want, have, null); } @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have, OutputStream out) throws TransportException { markStartedOperation(); doFetch(monitor, want, have); } /** * {@inheritDoc} * * Default implementation of {@link FetchConnection#didFetchIncludeTags()} - * returning false. */ @Override public boolean didFetchIncludeTags() { return false; } /** * Implementation of {@link #fetch(ProgressMonitor, Collection, Set)} * without checking for multiple fetch. * * @param monitor * as in {@link #fetch(ProgressMonitor, Collection, Set)} * @param want * as in {@link #fetch(ProgressMonitor, Collection, Set)} * @param have * as in {@link #fetch(ProgressMonitor, Collection, Set)} * @throws org.eclipse.jgit.errors.TransportException * as in {@link #fetch(ProgressMonitor, Collection, Set)}, but * implementation doesn't have to care about multiple * {@link #fetch(ProgressMonitor, Collection, Set)} calls, as it * is checked in this class. */ protected abstract void doFetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 21734 Content-Disposition: inline; filename="BasePackConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "bac5025f83cb7c96ccea2bcf4357b4319e10d880" /* * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_PEELED; import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_SYMREF_TARGET; import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_1; import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.RemoteRepositoryException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutOutputStream; /** * Base helper class for pack-based operations implementations. Provides partial * implementation of pack-protocol - refs advertising and capabilities support, * and some other helper methods. * * @see BasePackFetchConnection * @see BasePackPushConnection */ abstract class BasePackConnection extends BaseConnection { /** The capability prefix for a symlink */ protected static final String CAPABILITY_SYMREF_PREFIX = "symref="; //$NON-NLS-1$ /** The repository this transport fetches into, or pushes out of. */ protected final Repository local; /** Remote repository location. */ protected final URIish uri; /** A transport connected to {@link BasePackConnection#uri}. */ protected final Transport transport; /** Low-level input stream, if a timeout was configured. */ protected TimeoutInputStream timeoutIn; /** Low-level output stream, if a timeout was configured. */ protected TimeoutOutputStream timeoutOut; /** * Timer to manage {@link #timeoutIn} and * {@link BasePackConnection#timeoutOut}. */ private InterruptTimer myTimer; /** Input stream reading from the remote. */ protected InputStream in; /** Output stream sending to the remote. */ protected OutputStream out; /** Packet line decoder around {@link BasePackConnection#in}. */ protected PacketLineIn pckIn; /** Packet line encoder around {@link BasePackConnection#out}. */ protected PacketLineOut pckOut; /** * Send {@link PacketLineOut#end()} before closing * {@link BasePackConnection#out}? */ protected boolean outNeedsEnd; /** True if this is a stateless RPC connection. */ protected boolean statelessRPC; /** Capability tokens advertised by the remote side. */ private final Map remoteCapabilities = new HashMap<>(); /** Extra objects the remote has, but which aren't offered as refs. */ protected final Set additionalHaves = new HashSet<>(); private TransferConfig.ProtocolVersion protocol = TransferConfig.ProtocolVersion.V0; BasePackConnection(PackTransport packTransport) { transport = (Transport) packTransport; local = transport.local; uri = transport.uri; } TransferConfig.ProtocolVersion getProtocolVersion() { return protocol; } void setProtocolVersion(@NonNull TransferConfig.ProtocolVersion protocol) { this.protocol = protocol; } /** * Configure this connection with the directional pipes. * * @param myIn * input stream to receive data from the peer. Caller must ensure * the input is buffered, otherwise read performance may suffer. * @param myOut * output stream to transmit data to the peer. Caller must ensure * the output is buffered, otherwise write performance may * suffer. */ protected final void init(InputStream myIn, OutputStream myOut) { final int timeout = transport.getTimeout(); if (timeout > 0) { final Thread caller = Thread.currentThread(); if (myTimer == null) { myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ } timeoutIn = new TimeoutInputStream(myIn, myTimer); timeoutOut = new TimeoutOutputStream(myOut, myTimer); timeoutIn.setTimeout(timeout * 1000); timeoutOut.setTimeout(timeout * 1000); myIn = timeoutIn; myOut = timeoutOut; } in = myIn; out = myOut; pckIn = new PacketLineIn(in); pckOut = new PacketLineOut(out); outNeedsEnd = true; } /** * Reads the advertised references through the initialized stream. *

* Subclass implementations may call this method only after setting up the * input and output streams with {@link #init(InputStream, OutputStream)}. *

* If any errors occur, this connection is automatically closed by invoking * {@link #close()} and the exception is wrapped (if necessary) and thrown * as a {@link org.eclipse.jgit.errors.TransportException}. * * @return {@code true} if the refs were read; {@code false} otherwise * indicating that {@link #lsRefs} must be called * * @throws org.eclipse.jgit.errors.TransportException * the reference list could not be scanned. */ protected boolean readAdvertisedRefs() throws TransportException { try { return readAdvertisedRefsImpl(); } catch (TransportException err) { close(); throw err; } catch (IOException | RuntimeException err) { close(); throw new TransportException(err.getMessage(), err); } } private String readLine() throws IOException { String line = pckIn.readString(); if (PacketLineIn.isEnd(line)) { return null; } if (line.startsWith("ERR ")) { //$NON-NLS-1$ // This is a customized remote service error. // Users should be informed about it. throw new RemoteRepositoryException(uri, line.substring(4)); } return line; } private boolean readAdvertisedRefsImpl() throws IOException { final Map avail = new LinkedHashMap<>(); final Map symRefs = new LinkedHashMap<>(); for (boolean first = true;; first = false) { String line; if (first) { boolean isV1 = false; try { line = readLine(); } catch (EOFException e) { throw noRepository(e); } if (line != null && VERSION_1.equals(line)) { // Same as V0, except for this extra line. We shouldn't get // it since we never request V1. setProtocolVersion(TransferConfig.ProtocolVersion.V0); isV1 = true; line = readLine(); } if (line == null) { break; } final int nul = line.indexOf('\0'); if (nul >= 0) { // Protocol V0: The first line (if any) may contain // "hidden" capability values after a NUL byte. for (String capability : line.substring(nul + 1) .split(" ")) { //$NON-NLS-1$ if (capability.startsWith(CAPABILITY_SYMREF_PREFIX)) { String[] parts = capability .substring( CAPABILITY_SYMREF_PREFIX.length()) .split(":", 2); //$NON-NLS-1$ if (parts.length == 2) { symRefs.put(parts[0], parts[1]); } } else { addCapability(capability); } } line = line.substring(0, nul); setProtocolVersion(TransferConfig.ProtocolVersion.V0); } else if (!isV1 && VERSION_2.equals(line)) { // Protocol V2: remaining lines are capabilities as // key=value pairs setProtocolVersion(TransferConfig.ProtocolVersion.V2); readCapabilitiesV2(); // Break out here so that stateless RPC transports get a // chance to set up the output stream. return false; } else { setProtocolVersion(TransferConfig.ProtocolVersion.V0); } } else { line = readLine(); if (line == null) { break; } } // Expecting to get a line in the form "sha1 refname" if (line.length() < 41 || line.charAt(40) != ' ') { throw invalidRefAdvertisementLine(line); } String name = line.substring(41, line.length()); if (first && name.equals("capabilities^{}")) { //$NON-NLS-1$ // special line from git-receive-pack (protocol V0) to show // capabilities when there are no refs to advertise continue; } final ObjectId id = toId(line, line.substring(0, 40)); if (name.equals(".have")) { //$NON-NLS-1$ additionalHaves.add(id); } else { processLineV1(name, id, avail); } } updateWithSymRefs(avail, symRefs); available(avail); return true; } /** * Issue a protocol V2 ls-refs command and read its response. * * @param refSpecs * to produce ref prefixes from if the server supports git * protocol V2 * @param additionalPatterns * to use for ref prefixes if the server supports git protocol V2 * @throws TransportException * if the command could not be run or its output not be read */ protected void lsRefs(Collection refSpecs, String... additionalPatterns) throws TransportException { try { lsRefsImpl(refSpecs, additionalPatterns); } catch (TransportException err) { close(); throw err; } catch (IOException | RuntimeException err) { close(); throw new TransportException(err.getMessage(), err); } } private void lsRefsImpl(Collection refSpecs, String... additionalPatterns) throws IOException { pckOut.writeString("command=" + COMMAND_LS_REFS); //$NON-NLS-1$ // Add the user-agent String agent = UserAgent.get(); if (agent != null && isCapableOf(OPTION_AGENT)) { pckOut.writeString(OPTION_AGENT + '=' + agent); } pckOut.writeDelim(); pckOut.writeString("peel"); //$NON-NLS-1$ pckOut.writeString("symrefs"); //$NON-NLS-1$ for (String refPrefix : getRefPrefixes(refSpecs, additionalPatterns)) { pckOut.writeString("ref-prefix " + refPrefix); //$NON-NLS-1$ } pckOut.end(); final Map avail = new LinkedHashMap<>(); final Map symRefs = new LinkedHashMap<>(); for (;;) { String line = readLine(); if (line == null) { break; } // Expecting to get a line in the form "sha1 refname" if (line.length() < 41 || line.charAt(40) != ' ') { throw invalidRefAdvertisementLine(line); } String name = line.substring(41, line.length()); final ObjectId id = toId(line, line.substring(0, 40)); if (name.equals(".have")) { //$NON-NLS-1$ additionalHaves.add(id); } else { processLineV2(line, id, name, avail, symRefs); } } updateWithSymRefs(avail, symRefs); available(avail); } private Collection getRefPrefixes(Collection refSpecs, String... additionalPatterns) { if (refSpecs.isEmpty() && (additionalPatterns == null || additionalPatterns.length == 0)) { return Collections.emptyList(); } Set patterns = new HashSet<>(); if (additionalPatterns != null) { Arrays.stream(additionalPatterns).filter(Objects::nonNull) .forEach(patterns::add); } for (RefSpec spec : refSpecs) { // TODO: for now we only do protocol V2 for fetch. For push // RefSpecs, the logic would need to be different. (At the // minimum, take spec.getDestination().) String src = spec.getSource(); if (ObjectId.isId(src)) { continue; } if (spec.isWildcard()) { patterns.add(src.substring(0, src.indexOf('*'))); } else { patterns.add(src); patterns.add(Constants.R_REFS + src); patterns.add(Constants.R_HEADS + src); patterns.add(Constants.R_TAGS + src); } } return patterns; } private void readCapabilitiesV2() throws IOException { // In git protocol V2, capabilities are different. If it's a key-value // pair, the key may be a command name, and the value a space-separated // list of capabilities for that command. We still store it in the same // map as for protocol v0/v1. Protocol v2 code has to account for this. for (;;) { String line = readLine(); if (line == null) { break; } addCapability(line); } } private void addCapability(String capability) { String parts[] = capability.split("=", 2); //$NON-NLS-1$ if (parts.length == 2) { remoteCapabilities.put(parts[0], parts[1]); } remoteCapabilities.put(capability, null); } private ObjectId toId(String line, String value) throws PackProtocolException { try { return ObjectId.fromString(value); } catch (InvalidObjectIdException e) { PackProtocolException ppe = invalidRefAdvertisementLine(line); ppe.initCause(e); throw ppe; } } private void processLineV1(String name, ObjectId id, Map avail) throws IOException { if (name.endsWith("^{}")) { //$NON-NLS-1$ name = name.substring(0, name.length() - 3); final Ref prior = avail.get(name); if (prior == null) { throw new PackProtocolException(uri, MessageFormat.format( JGitText.get().advertisementCameBefore, name, name)); } if (prior.getPeeledObjectId() != null) { throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$ } avail.put(name, new ObjectIdRef.PeeledTag(Ref.Storage.NETWORK, name, prior.getObjectId(), id)); } else { final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag( Ref.Storage.NETWORK, name, id)); if (prior != null) { throw duplicateAdvertisement(name); } } } private void processLineV2(String line, ObjectId id, String rest, Map avail, Map symRefs) throws IOException { String[] parts = rest.split(" "); //$NON-NLS-1$ String name = parts[0]; // Two attributes possible, symref-target or peeled String symRefTarget = null; String peeled = null; for (int i = 1; i < parts.length; i++) { if (parts[i].startsWith(REF_ATTR_SYMREF_TARGET)) { if (symRefTarget != null) { throw new PackProtocolException(uri, MessageFormat.format( JGitText.get().duplicateRefAttribute, line)); } symRefTarget = parts[i] .substring(REF_ATTR_SYMREF_TARGET.length()); } else if (parts[i].startsWith(REF_ATTR_PEELED)) { if (peeled != null) { throw new PackProtocolException(uri, MessageFormat.format( JGitText.get().duplicateRefAttribute, line)); } peeled = parts[i].substring(REF_ATTR_PEELED.length()); } if (peeled != null && symRefTarget != null) { break; } } Ref idRef; if (peeled != null) { idRef = new ObjectIdRef.PeeledTag(Ref.Storage.NETWORK, name, id, toId(line, peeled)); } else { idRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NETWORK, name, id); } Ref prior = avail.put(name, idRef); if (prior != null) { throw duplicateAdvertisement(name); } if (!StringUtils.isEmptyOrNull(symRefTarget)) { symRefs.put(name, symRefTarget); } } /** * Updates the given refMap with {@link SymbolicRef}s defined by the given * symRefs. *

* For each entry, symRef, in symRefs, whose value is a key in refMap, adds * a new entry to refMap with that same key and value of a new * {@link SymbolicRef} with source=symRef.key and * target=refMap.get(symRef.value), then removes that entry from symRefs. *

* If refMap already contains an entry for symRef.key, it is replaced. *

*

* For example, given: *

* *
	 * refMap.put("refs/heads/main", ref);
	 * symRefs.put("HEAD", "refs/heads/main");
	 * 
* * then: * *
	 * updateWithSymRefs(refMap, symRefs);
	 * 
* * has the effect of: * *
	 * refMap.put("HEAD",
	 * 		new SymbolicRef("HEAD", refMap.get(symRefs.remove("HEAD"))))
	 * 
*

* Any entry in symRefs whose value is not a key in refMap is ignored. Any * circular symRefs are ignored. *

*

* Upon completion, symRefs will contain only any unresolvable entries. *

* * @param refMap * a non-null, modifiable, Map to update, and the provider of * symref targets. * @param symRefs * a non-null, modifiable, Map of symrefs. * @throws NullPointerException * if refMap or symRefs is null */ static void updateWithSymRefs(Map refMap, Map symRefs) { boolean haveNewRefMapEntries = !refMap.isEmpty(); while (!symRefs.isEmpty() && haveNewRefMapEntries) { haveNewRefMapEntries = false; final Iterator> iterator = symRefs.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry symRef = iterator.next(); if (!symRefs.containsKey(symRef.getValue())) { // defer forward reference final Ref r = refMap.get(symRef.getValue()); if (r != null) { refMap.put(symRef.getKey(), new SymbolicRef(symRef.getKey(), r)); haveNewRefMapEntries = true; iterator.remove(); } } } } // If HEAD is still in the symRefs map here, the real ref was not // reported, but we know it must point to the object reported for HEAD. // So fill it in in the refMap. String headRefName = symRefs.get(Constants.HEAD); if (headRefName != null && !refMap.containsKey(headRefName)) { Ref headRef = refMap.get(Constants.HEAD); if (headRef != null) { ObjectId headObj = headRef.getObjectId(); headRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NETWORK, headRefName, headObj); refMap.put(headRefName, headRef); headRef = new SymbolicRef(Constants.HEAD, headRef); refMap.put(Constants.HEAD, headRef); symRefs.remove(Constants.HEAD); } } } /** * Create an exception to indicate problems finding a remote repository. The * caller is expected to throw the returned exception. * * Subclasses may override this method to provide better diagnostics. * * @param cause * root cause exception * @return a TransportException saying a repository cannot be found and * possibly why. */ protected TransportException noRepository(Throwable cause) { return new NoRemoteRepositoryException(uri, JGitText.get().notFound, cause); } /** * Whether this option is supported * * @param option * option string * @return whether this option is supported */ protected boolean isCapableOf(String option) { return remoteCapabilities.containsKey(option); } /** * Request capability * * @param b * buffer * @param option * option we want * @return {@code true} if the requested option is supported */ protected boolean wantCapability(StringBuilder b, String option) { if (!isCapableOf(option)) return false; b.append(' '); b.append(option); return true; } /** * Return a capability value. * * @param option * to get * @return the value stored, if any. */ protected String getCapability(String option) { return remoteCapabilities.get(option); } /** * Add user agent capability * * @param b * a {@link java.lang.StringBuilder} object. */ protected void addUserAgentCapability(StringBuilder b) { String a = UserAgent.get(); if (a != null && remoteCapabilities.get(OPTION_AGENT) != null) { b.append(' ').append(OPTION_AGENT).append('=').append(a); } } @Override public String getPeerUserAgent() { String agent = remoteCapabilities.get(OPTION_AGENT); return agent != null ? agent : super.getPeerUserAgent(); } private PackProtocolException duplicateAdvertisement(String name) { return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name)); } private PackProtocolException invalidRefAdvertisementLine(String line) { return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line)); } @Override public void close() { if (out != null) { try { if (outNeedsEnd) { outNeedsEnd = false; pckOut.end(); } out.close(); } catch (IOException err) { // Ignore any close errors. } finally { out = null; pckOut = null; } } if (in != null) { try { in.close(); } catch (IOException err) { // Ignore any close errors. } finally { in = null; pckIn = null; } } if (myTimer != null) { try { myTimer.terminate(); } finally { myTimer = null; timeoutIn = null; timeoutOut = null; } } } /** * Tell the peer we are disconnecting, if it cares to know. */ protected void endOut() { if (outNeedsEnd && out != null) { try { outNeedsEnd = false; pckOut.end(); } catch (IOException e) { try { out.close(); } catch (IOException err) { // Ignore any close errors. } finally { out = null; pckOut = null; } } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 37887 Content-Disposition: inline; filename="BasePackFetchConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "be0d37b96eece711e85cbf991f5516eb71f3a2fd" /* * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_END; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.RemoteRepositoryException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommitList; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck; import org.eclipse.jgit.transport.PacketLineIn.AckNackResult; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.TemporaryBuffer; /** * Fetch implementation using the native Git pack transfer service. *

* This is the canonical implementation for transferring objects from the remote * repository to the local repository by talking to the 'git-upload-pack' * service. Objects are packed on the remote side into a pack file and then sent * down the pipe to us. *

* This connection requires only a bi-directional pipe or socket, and thus is * easily wrapped up into a local process pipe, anonymous TCP socket, or a * command executed through an SSH tunnel. *

* If {@link org.eclipse.jgit.transport.BasePackConnection#statelessRPC} is * {@code true}, this connection can be tunneled over a request-response style * RPC system like HTTP. The RPC call boundary is determined by this class * switching from writing to the OutputStream to reading from the InputStream. *

* Concrete implementations should just call * {@link #init(java.io.InputStream, java.io.OutputStream)} and * {@link #readAdvertisedRefs()} methods in constructor or before any use. They * should also handle resources releasing in {@link #close()} method if needed. */ public abstract class BasePackFetchConnection extends BasePackConnection implements FetchConnection { /** * Maximum number of 'have' lines to send before giving up. *

* During {@link #negotiate(ProgressMonitor, boolean, Set)} we send at most this many * commits to the remote peer as 'have' lines without an ACK response before * we give up. */ private static final int MAX_HAVES = 256; /** * Amount of data the client sends before starting to read. *

* Any output stream given to the client must be able to buffer this many * bytes before the client will stop writing and start reading from the * input stream. If the output stream blocks before this many bytes are in * the send queue, the system will deadlock. */ protected static final int MIN_CLIENT_BUFFER = 2 * 32 * 46 + 8; /** * Include tags if we are also including the referenced objects. * @since 2.0 */ public static final String OPTION_INCLUDE_TAG = GitProtocolConstants.OPTION_INCLUDE_TAG; /** * Multi-ACK support for improved negotiation. * @since 2.0 */ public static final String OPTION_MULTI_ACK = GitProtocolConstants.OPTION_MULTI_ACK; /** * Multi-ACK detailed support for improved negotiation. * @since 2.0 */ public static final String OPTION_MULTI_ACK_DETAILED = GitProtocolConstants.OPTION_MULTI_ACK_DETAILED; /** * The client supports packs with deltas but not their bases. * @since 2.0 */ public static final String OPTION_THIN_PACK = GitProtocolConstants.OPTION_THIN_PACK; /** * The client supports using the side-band for progress messages. * @since 2.0 */ public static final String OPTION_SIDE_BAND = GitProtocolConstants.OPTION_SIDE_BAND; /** * The client supports using the 64K side-band for progress messages. * @since 2.0 */ public static final String OPTION_SIDE_BAND_64K = GitProtocolConstants.OPTION_SIDE_BAND_64K; /** * The client supports packs with OFS deltas. * @since 2.0 */ public static final String OPTION_OFS_DELTA = GitProtocolConstants.OPTION_OFS_DELTA; /** * The client supports shallow fetches. * @since 2.0 */ public static final String OPTION_SHALLOW = GitProtocolConstants.OPTION_SHALLOW; /** * The client does not want progress messages and will ignore them. * @since 2.0 */ public static final String OPTION_NO_PROGRESS = GitProtocolConstants.OPTION_NO_PROGRESS; /** * The client supports receiving a pack before it has sent "done". * @since 2.0 */ public static final String OPTION_NO_DONE = GitProtocolConstants.OPTION_NO_DONE; /** * The client supports fetching objects at the tip of any ref, even if not * advertised. * @since 3.1 */ public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT; /** * The client supports fetching objects that are reachable from a tip of a * ref that is allowed to fetch. * @since 4.1 */ public static final String OPTION_ALLOW_REACHABLE_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT; /** * The client specified a filter expression. * * @since 5.0 */ public static final String OPTION_FILTER = GitProtocolConstants.OPTION_FILTER; private final RevWalk walk; /** All commits that are immediately reachable by a local ref. */ private RevCommitList reachableCommits; /** Marks an object as having all its dependencies. */ final RevFlag REACHABLE; /** Marks a commit known to both sides of the connection. */ final RevFlag COMMON; /** Like {@link #COMMON} but means its also in {@link #pckState}. */ private final RevFlag STATE; /** Marks a commit listed in the advertised refs. */ final RevFlag ADVERTISED; private MultiAck multiAck = MultiAck.OFF; private boolean thinPack; private boolean sideband; private boolean includeTags; private boolean allowOfsDelta; private boolean useNegotiationTip; private boolean noDone; private boolean noProgress; private String lockMessage; private PackLock packLock; private int maxHaves; private Integer depth; private Instant deepenSince; private List deepenNots; /** * RPC state, if {@link BasePackConnection#statelessRPC} is true or protocol * V2 is used. */ private TemporaryBuffer.Heap state; private PacketLineOut pckState; /** * Either FilterSpec.NO_FILTER for a filter that doesn't filter * anything, or a filter that indicates what and what not to send to the * server. */ private final FilterSpec filterSpec; /** * Create a new connection to fetch using the native git transport. * * @param packTransport * the transport. */ public BasePackFetchConnection(PackTransport packTransport) { super(packTransport); if (local != null) { final FetchConfig cfg = getFetchConfig(); allowOfsDelta = cfg.allowOfsDelta; maxHaves = cfg.maxHaves; useNegotiationTip = cfg.useNegotiationTip; } else { allowOfsDelta = true; maxHaves = Integer.MAX_VALUE; useNegotiationTip = false; } includeTags = transport.getTagOpt() != TagOpt.NO_TAGS; thinPack = transport.isFetchThin(); filterSpec = transport.getFilterSpec(); depth = transport.getDepth(); deepenSince = transport.getDeepenSince(); deepenNots = transport.getDeepenNots(); if (local != null) { walk = new RevWalk(local); walk.setRetainBody(false); reachableCommits = new RevCommitList<>(); REACHABLE = walk.newFlag("REACHABLE"); //$NON-NLS-1$ COMMON = walk.newFlag("COMMON"); //$NON-NLS-1$ STATE = walk.newFlag("STATE"); //$NON-NLS-1$ ADVERTISED = walk.newFlag("ADVERTISED"); //$NON-NLS-1$ walk.carry(COMMON); walk.carry(REACHABLE); walk.carry(ADVERTISED); } else { walk = null; REACHABLE = null; COMMON = null; STATE = null; ADVERTISED = null; } } static class FetchConfig { final boolean allowOfsDelta; final int maxHaves; final boolean useNegotiationTip; FetchConfig(Config c) { allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true); //$NON-NLS-1$ //$NON-NLS-2$ maxHaves = c.getInt("fetch", "maxhaves", Integer.MAX_VALUE); //$NON-NLS-1$ //$NON-NLS-2$ useNegotiationTip = c.getBoolean("fetch", "usenegotiationtip", //$NON-NLS-1$ //$NON-NLS-2$ false); } FetchConfig(boolean allowOfsDelta, int maxHaves) { this(allowOfsDelta, maxHaves, false); } /** * @param allowOfsDelta * when true optimizes the pack size by deltafying base * object * @param maxHaves * max haves to be sent per negotiation * @param useNegotiationTip * if true uses the wanted refs instead of all refs as source * of the "have" list to send. * @since 6.6 */ FetchConfig(boolean allowOfsDelta, int maxHaves, boolean useNegotiationTip) { this.allowOfsDelta = allowOfsDelta; this.maxHaves = maxHaves; this.useNegotiationTip = useNegotiationTip; } } @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException { fetch(monitor, want, have, null); } @Override public final void fetch(final ProgressMonitor monitor, final Collection want, final Set have, OutputStream outputStream) throws TransportException { markStartedOperation(); doFetch(monitor, want, have, outputStream); } @Override public boolean didFetchIncludeTags() { return false; } @Override public boolean didFetchTestConnectivity() { return false; } @Override public void setPackLockMessage(String message) { lockMessage = message; } @Override public Collection getPackLocks() { if (packLock != null) return Collections.singleton(packLock); return Collections. emptyList(); } private void clearState() { walk.dispose(); reachableCommits = null; state = null; pckState = null; } /** * Execute common ancestor negotiation and fetch the objects. * * @param monitor * progress monitor to receive status updates. If the monitor is * the {@link org.eclipse.jgit.lib.NullProgressMonitor#INSTANCE}, then the no-progress * option enabled. * @param want * the advertised remote references the caller wants to fetch. * @param have * additional objects to assume that already exist locally. This * will be added to the set of objects reachable from the * destination repository's references. * @param outputStream * ouputStream to write sideband messages to * @throws org.eclipse.jgit.errors.TransportException * if any exception occurs. * @since 3.0 */ protected void doFetch(final ProgressMonitor monitor, final Collection want, final Set have, OutputStream outputStream) throws TransportException { boolean hasObjects = !have.isEmpty(); try { noProgress = monitor == NullProgressMonitor.INSTANCE; if (hasObjects) { markRefsAdvertised(); } markReachable(want, have, maxTimeWanted(want, hasObjects)); if (TransferConfig.ProtocolVersion.V2 .equals(getProtocolVersion())) { // Protocol V2 always is a "stateless" protocol, even over a // bidirectional pipe: the server serves one "fetch" request and // then forgets anything it has learned, so the next fetch // request has to re-send all wants and previously determined // common objects as "have"s again. state = new TemporaryBuffer.Heap(Integer.MAX_VALUE); pckState = new PacketLineOut(state); try { doFetchV2(monitor, want, outputStream, hasObjects); } finally { clearState(); } return; } // Protocol V0/1 if (statelessRPC) { state = new TemporaryBuffer.Heap(Integer.MAX_VALUE); pckState = new PacketLineOut(state); } PacketLineOut output = statelessRPC ? pckState : pckOut; if (sendWants(want, output, hasObjects)) { boolean mayHaveShallow = depth != null || deepenSince != null || !deepenNots.isEmpty(); Set shallowCommits = local.getObjectDatabase().getShallowCommits(); if (isCapableOf(GitProtocolConstants.CAPABILITY_SHALLOW)) { sendShallow(shallowCommits, output); } else if (mayHaveShallow) { throw new PackProtocolException(JGitText.get().shallowNotSupported); } output.end(); outNeedsEnd = false; negotiate(monitor, mayHaveShallow, shallowCommits); clearState(); receivePack(monitor, outputStream); } } catch (CancelledException ce) { close(); return; // Caller should test (or just know) this themselves. } catch (IOException | RuntimeException err) { close(); throw new TransportException(err.getMessage(), err); } } private void doFetchV2(ProgressMonitor monitor, Collection want, OutputStream outputStream, boolean hasObjects) throws IOException, CancelledException { sideband = true; negotiateBegin(); pckState.writeString("command=" + GitProtocolConstants.COMMAND_FETCH); //$NON-NLS-1$ // Capabilities are sent as command arguments in protocol V2 String agent = UserAgent.get(); if (agent != null && isCapableOf(GitProtocolConstants.OPTION_AGENT)) { pckState.writeString( GitProtocolConstants.OPTION_AGENT + '=' + agent); } Set capabilities = new HashSet<>(); String advertised = getCapability(GitProtocolConstants.COMMAND_FETCH); if (!StringUtils.isEmptyOrNull(advertised)) { capabilities.addAll(Arrays.asList(advertised.split("\\s+"))); //$NON-NLS-1$ } // Arguments pckState.writeDelim(); for (String capability : getCapabilitiesV2(capabilities)) { pckState.writeString(capability); } if (!sendWants(want, pckState, hasObjects)) { // We already have everything we wanted. return; } Set shallowCommits = local.getObjectDatabase().getShallowCommits(); if (capabilities.contains(GitProtocolConstants.CAPABILITY_SHALLOW)) { sendShallow(shallowCommits, pckState); } else if (depth != null || deepenSince != null || !deepenNots.isEmpty()) { throw new PackProtocolException(JGitText.get().shallowNotSupported); } // If we send something, we always close it properly ourselves. outNeedsEnd = false; FetchStateV2 fetchState = new FetchStateV2(); boolean sentDone = false; for (;;) { // The "state" buffer contains the full fetch request with all // common objects found so far. state.writeTo(out, monitor); sentDone = sendNextHaveBatch(fetchState, pckOut, monitor); if (sentDone) { break; } if (readAcknowledgments(fetchState, pckIn, monitor)) { // We got a "ready": next should be a patch file. break; } // Note: C git reads and requires here (and after a packfile) a // "0002" packet in stateless RPC transports (https). This "response // end" packet is even mentioned in the protocol V2 technical // documentation. However, it is not actually part of the public // protocol; it occurs only in an internal protocol wrapper in the C // git implementation. } clearState(); String line = pckIn.readString(); // If we sent a done, we may have an error reply here. if (sentDone && line.startsWith(PACKET_ERR)) { throw new RemoteRepositoryException(uri, line.substring(4)); } if (GitProtocolConstants.SECTION_SHALLOW_INFO.equals(line)) { line = handleShallowUnshallow(shallowCommits, pckIn); if (!PacketLineIn.isDelimiter(line)) { throw new PackProtocolException(MessageFormat .format(JGitText.get().expectedGot, PACKET_DELIM, line)); } line = pckIn.readString(); } // "wanted-refs" and "packfile-uris" would have to be // handled here in that order. if (!GitProtocolConstants.SECTION_PACKFILE.equals(line)) { throw new PackProtocolException( MessageFormat.format(JGitText.get().expectedGot, GitProtocolConstants.SECTION_PACKFILE, line)); } receivePack(monitor, outputStream); } /** * Sends the next batch of "have"s and terminates the {@code output}. * * @param fetchState * is updated with information about the number of items written, * and whether to expect a packfile next * @param output * to write to * @param monitor * for progress reporting and cancellation * @return {@code true} if a "done" was written and we should thus expect a * packfile next * @throws IOException * on errors * @throws CancelledException * on cancellation */ private boolean sendNextHaveBatch(FetchStateV2 fetchState, PacketLineOut output, ProgressMonitor monitor) throws IOException, CancelledException { long n = 0; while (n < fetchState.havesToSend) { final RevCommit c = walk.next(); if (c == null) { break; } output.writeString(PACKET_HAVE + c.getId().name() + '\n'); n++; if (n % 10 == 0 && monitor.isCancelled()) { throw new CancelledException(); } } fetchState.havesTotal += n; if (n == 0 || (fetchState.hadAcks && fetchState.havesWithoutAck > MAX_HAVES) || fetchState.havesTotal > maxHaves) { output.writeString(PACKET_DONE + '\n'); output.end(); return true; } // Increment only after the test above. Of course we have no ACKs yet // for the newly added "have"s, so it makes no sense to count them // against the MAX_HAVES limit. fetchState.havesWithoutAck += n; output.end(); fetchState.incHavesToSend(statelessRPC); return false; } /** * Reads and processes acknowledgments, adding ACKed objects as "have"s to * the global state {@link TemporaryBuffer}. * * @param fetchState * to update * @param input * to read from * @param monitor * for progress reporting and cancellation * @return {@code true} if a "ready" was received and a packfile is expected * next * @throws IOException * on errors * @throws CancelledException * on cancellation */ private boolean readAcknowledgments(FetchStateV2 fetchState, PacketLineIn input, ProgressMonitor monitor) throws IOException, CancelledException { String line = input.readString(); if (!GitProtocolConstants.SECTION_ACKNOWLEDGMENTS.equals(line)) { throw new PackProtocolException(MessageFormat.format( JGitText.get().expectedGot, GitProtocolConstants.SECTION_ACKNOWLEDGMENTS, line)); } MutableObjectId returnedId = new MutableObjectId(); line = input.readString(); boolean gotReady = false; long n = 0; while (!PacketLineIn.isEnd(line) && !PacketLineIn.isDelimiter(line)) { AckNackResult ack = PacketLineIn.parseACKv2(line, returnedId); // If we got a "ready", we just skip the remaining lines after // having checked them for being valid. (Normally, the "ready" // should be the last line anyway.) if (!gotReady) { if (ack == AckNackResult.ACK_COMMON) { // markCommon appends the object to the "state" markCommon(walk.parseAny(returnedId), ack, true); fetchState.havesWithoutAck = 0; fetchState.hadAcks = true; } else if (ack == AckNackResult.ACK_READY) { gotReady = true; } } n++; if (n % 10 == 0 && monitor.isCancelled()) { throw new CancelledException(); } line = input.readString(); } if (gotReady) { if (!PacketLineIn.isDelimiter(line)) { throw new PackProtocolException(MessageFormat .format(JGitText.get().expectedGot, PACKET_DELIM, line)); } } else if (!PacketLineIn.isEnd(line)) { throw new PackProtocolException(MessageFormat .format(JGitText.get().expectedGot, PACKET_END, line)); } return gotReady; } @Override public void close() { if (walk != null) walk.close(); super.close(); } FetchConfig getFetchConfig() { return local.getConfig().get(FetchConfig::new); } private int maxTimeWanted(Collection wants, boolean hasObjects) { int maxTime = 0; if (!hasObjects) { // we don't have any objects locally, we can immediately bail out return maxTime; } for (Ref r : wants) { try { final RevObject obj = walk.parseAny(r.getObjectId()); if (obj instanceof RevCommit) { final int cTime = ((RevCommit) obj).getCommitTime(); if (maxTime < cTime) maxTime = cTime; } } catch (IOException error) { // We don't have it, but we want to fetch (thus fixing error). } } return maxTime; } private void markReachable(Collection want, Set have, int maxTime) throws IOException { Collection refsToMark; if (useNegotiationTip) { refsToMark = translateToLocalTips(want); if (refsToMark.size() < want.size()) { refsToMark.addAll(local.getRefDatabase().getRefs()); } } else { refsToMark = local.getRefDatabase().getRefs(); } markReachableRefTips(refsToMark); for (ObjectId id : local.getAdditionalHaves()) markReachable(id); for (ObjectId id : have) markReachable(id); if (maxTime > 0) { // Mark reachable commits until we reach maxTime. These may // wind up later matching up against things we want and we // can avoid asking for something we already happen to have. // Instant maxWhen = Instant.ofEpochSecond(maxTime); walk.sort(RevSort.COMMIT_TIME_DESC); walk.markStart(reachableCommits); walk.setRevFilter(CommitTimeRevFilter.after(maxWhen)); for (;;) { final RevCommit c = walk.next(); if (c == null) break; if (c.has(ADVERTISED) && !c.has(COMMON)) { // This is actually going to be a common commit, but // our peer doesn't know that fact yet. // c.add(COMMON); c.carry(COMMON); reachableCommits.add(c); } } } } private Collection translateToLocalTips(Collection want) throws IOException { String[] refs = want.stream().map(Ref::getName) .collect(Collectors.toSet()).toArray(String[]::new); Map wantRefMap = local.getRefDatabase().exactRef(refs); return wantRefMap.values().stream() .filter(r -> getRefObjectId(r) != null) .collect(Collectors.toList()); } /** * Marks commits reachable. * * @param refsToMark * references that client is requesting to be marked. */ private void markReachableRefTips(Collection refsToMark) { refsToMark.stream().map(BasePackFetchConnection::getRefObjectId) .filter(Objects::nonNull) .forEach(oid -> markReachable(oid)); } private static ObjectId getRefObjectId(Ref ref) { ObjectId id = ref.getPeeledObjectId(); if (id == null) { id = ref.getObjectId(); } return id; } private void markReachable(ObjectId id) { try { RevCommit o = walk.parseCommit(id); if (!o.has(REACHABLE)) { o.add(REACHABLE); reachableCommits.add(o); } } catch (IOException readError) { // If we cannot read the value of the ref skip it. } } private boolean sendWants(Collection want, PacketLineOut p, boolean hasObjects) throws IOException { boolean first = true; for (Ref r : want) { ObjectId objectId = r.getObjectId(); if (objectId == null) { continue; } // if depth is set we need to fetch the objects even if they are already available if (transport.getDepth() == null // only check reachable objects when we have objects && hasObjects) { try { if (walk.parseAny(objectId).has(REACHABLE)) { // We already have this object. Asking for it is // not a very good idea. // continue; } } catch (IOException err) { // Its OK, we don't have it, but we want to fix that // by fetching the object from the other side. } } final StringBuilder line = new StringBuilder(46); line.append(PACKET_WANT).append(objectId.name()); if (first && TransferConfig.ProtocolVersion.V0 .equals(getProtocolVersion())) { line.append(enableCapabilities()); } first = false; line.append('\n'); p.writeString(line.toString()); } if (first) { return false; } if (!filterSpec.isNoOp()) { p.writeString(filterSpec.filterLine()); } return true; } private Set getCapabilitiesV2(Set advertisedCapabilities) throws TransportException { Set capabilities = new LinkedHashSet<>(); // Protocol V2 is implicitly capable of all these. if (noProgress) { capabilities.add(OPTION_NO_PROGRESS); } if (includeTags) { capabilities.add(OPTION_INCLUDE_TAG); } if (allowOfsDelta) { capabilities.add(OPTION_OFS_DELTA); } if (thinPack) { capabilities.add(OPTION_THIN_PACK); } if (!filterSpec.isNoOp() && !advertisedCapabilities.contains(OPTION_FILTER)) { throw new PackProtocolException(uri, JGitText.get().filterRequiresCapability); } // The FilterSpec will be added later in sendWants(). return capabilities; } private String enableCapabilities() throws TransportException { final StringBuilder line = new StringBuilder(); if (noProgress) wantCapability(line, OPTION_NO_PROGRESS); if (includeTags) includeTags = wantCapability(line, OPTION_INCLUDE_TAG); if (allowOfsDelta) wantCapability(line, OPTION_OFS_DELTA); if (wantCapability(line, OPTION_MULTI_ACK_DETAILED)) { multiAck = MultiAck.DETAILED; if (statelessRPC) noDone = wantCapability(line, OPTION_NO_DONE); } else if (wantCapability(line, OPTION_MULTI_ACK)) multiAck = MultiAck.CONTINUE; else multiAck = MultiAck.OFF; if (thinPack) thinPack = wantCapability(line, OPTION_THIN_PACK); if (wantCapability(line, OPTION_SIDE_BAND_64K)) sideband = true; else if (wantCapability(line, OPTION_SIDE_BAND)) sideband = true; if (statelessRPC && multiAck != MultiAck.DETAILED) { // Our stateless RPC implementation relies upon the detailed // ACK status to tell us common objects for reuse in future // requests. If its not enabled, we can't talk to the peer. // throw new PackProtocolException(uri, MessageFormat.format( JGitText.get().statelessRPCRequiresOptionToBeEnabled, OPTION_MULTI_ACK_DETAILED)); } if (!filterSpec.isNoOp() && !wantCapability(line, OPTION_FILTER)) { throw new PackProtocolException(uri, JGitText.get().filterRequiresCapability); } addUserAgentCapability(line); return line.toString(); } private void negotiate(ProgressMonitor monitor, boolean mayHaveShallow, Set shallowCommits) throws IOException, CancelledException { final MutableObjectId ackId = new MutableObjectId(); int resultsPending = 0; int havesSent = 0; int havesSinceLastContinue = 0; boolean receivedContinue = false; boolean receivedAck = false; boolean receivedReady = false; if (statelessRPC) { state.writeTo(out, null); } negotiateBegin(); SEND_HAVES: for (;;) { final RevCommit c = walk.next(); if (c == null) { break SEND_HAVES; } ObjectId o = c.getId(); pckOut.writeString(PACKET_HAVE + o.name() + '\n'); havesSent++; havesSinceLastContinue++; if ((31 & havesSent) != 0) { // We group the have lines into blocks of 32, each marked // with a flush (aka end). This one is within a block so // continue with another have line. // continue; } if (monitor.isCancelled()) { throw new CancelledException(); } pckOut.end(); resultsPending++; // Each end will cause a result to come back. if (havesSent == 32 && !statelessRPC) { // On the first block we race ahead and try to send // more of the second block while waiting for the // remote to respond to our first block request. // This keeps us one block ahead of the peer. // continue; } READ_RESULT: for (;;) { final AckNackResult anr = pckIn.readACK(ackId); switch (anr) { case NAK: // More have lines are necessary to compute the // pack on the remote side. Keep doing that. // resultsPending--; break READ_RESULT; case ACK: // The remote side is happy and knows exactly what // to send us. There is no further negotiation and // we can break out immediately. // multiAck = MultiAck.OFF; resultsPending = 0; receivedAck = true; if (statelessRPC) { state.writeTo(out, null); } break SEND_HAVES; case ACK_CONTINUE: case ACK_COMMON: case ACK_READY: // The server knows this commit (ackId). We don't // need to send any further along its ancestry, but // we need to continue to talk about other parts of // our local history. // markCommon(walk.parseAny(ackId), anr, statelessRPC); receivedAck = true; receivedContinue = true; havesSinceLastContinue = 0; if (anr == AckNackResult.ACK_READY) { receivedReady = true; } break; } if (monitor.isCancelled()) { throw new CancelledException(); } } if (noDone && receivedReady) { break SEND_HAVES; } if (statelessRPC) { state.writeTo(out, null); } if ((receivedContinue && havesSinceLastContinue > MAX_HAVES) || havesSent >= maxHaves) { // Our history must be really different from the remote's. // We just sent a whole slew of have lines, and it did not // recognize any of them. Avoid sending our entire history // to them by giving up early. // break SEND_HAVES; } } // Tell the remote side we have run out of things to talk about. // if (monitor.isCancelled()) { throw new CancelledException(); } if (!receivedReady || !noDone) { // When statelessRPC is true we should always leave SEND_HAVES // loop above while in the middle of a request. This allows us // to just write done immediately. // pckOut.writeString(PACKET_DONE + '\n'); pckOut.flush(); } if (!receivedAck) { // Apparently if we have never received an ACK earlier // there is one more result expected from the done we // just sent to the remote. // multiAck = MultiAck.OFF; resultsPending++; } if (mayHaveShallow) { String line = handleShallowUnshallow(shallowCommits, pckIn); if (!PacketLineIn.isEnd(line)) { throw new PackProtocolException(MessageFormat .format(JGitText.get().expectedGot, PACKET_END, line)); } } READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) { final AckNackResult anr = pckIn.readACK(ackId); resultsPending--; switch (anr) { case NAK: // A NAK is a response to an end we queued earlier // we eat it and look for another ACK/NAK message. // break; case ACK: // A solitary ACK at this point means the remote won't // speak anymore, but is going to send us a pack now. // break READ_RESULT; case ACK_CONTINUE: case ACK_COMMON: case ACK_READY: // We will expect a normal ACK to break out of the loop. // multiAck = MultiAck.CONTINUE; break; } if (monitor.isCancelled()) { throw new CancelledException(); } } } private void negotiateBegin() throws IOException { walk.resetRetain(REACHABLE, ADVERTISED); walk.markStart(reachableCommits); walk.sort(RevSort.COMMIT_TIME_DESC); walk.setRevFilter(new RevFilter() { @Override public RevFilter clone() { return this; } @Override public boolean include(RevWalk walker, RevCommit c) { final boolean remoteKnowsIsCommon = c.has(COMMON); if (c.has(ADVERTISED)) { // Remote advertised this, and we have it, hence common. // Whether or not the remote knows that fact is tested // before we added the flag. If the remote doesn't know // we have to still send them this object. // c.add(COMMON); } return !remoteKnowsIsCommon; } @Override public boolean requiresCommitBody() { return false; } }); } private void markRefsAdvertised() { for (Ref r : getRefs()) { markAdvertised(r.getObjectId()); if (r.getPeeledObjectId() != null) markAdvertised(r.getPeeledObjectId()); } } private void markAdvertised(AnyObjectId id) { try { walk.parseAny(id).add(ADVERTISED); } catch (IOException readError) { // We probably just do not have this object locally. } } private void markCommon(RevObject obj, AckNackResult anr, boolean useState) throws IOException { if (useState && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) { pckState.writeString(PACKET_HAVE + obj.name() + '\n'); obj.add(STATE); } obj.add(COMMON); if (obj instanceof RevCommit) ((RevCommit) obj).carry(COMMON); } private void receivePack(final ProgressMonitor monitor, OutputStream outputStream) throws IOException { onReceivePack(); InputStream input = in; SideBandInputStream sidebandIn = null; if (sideband) { sidebandIn = new SideBandInputStream(input, monitor, getMessageWriter(), outputStream); input = sidebandIn; } try (ObjectInserter ins = local.newObjectInserter()) { PackParser parser = ins.newPackParser(input); parser.setAllowThin(thinPack); parser.setObjectChecker(transport.getObjectChecker()); parser.setLockMessage(lockMessage); packLock = parser.parse(monitor); ins.flush(); } finally { if (sidebandIn != null) { sidebandIn.drainMessages(); } } } private void sendShallow(Set shallowCommits, PacketLineOut output) throws IOException { for (ObjectId shallowCommit : shallowCommits) { output.writeString(PACKET_SHALLOW + shallowCommit.name()); } if (depth != null) { output.writeString(PACKET_DEEPEN + depth); } if (deepenSince != null) { output.writeString( PACKET_DEEPEN_SINCE + deepenSince.getEpochSecond()); } if (deepenNots != null) { for (String deepenNotRef : deepenNots) { output.writeString(PACKET_DEEPEN_NOT + deepenNotRef); } } } private String handleShallowUnshallow( Set advertisedShallowCommits, PacketLineIn input) throws IOException { String line = input.readString(); ObjectDatabase objectDatabase = local.getObjectDatabase(); HashSet newShallowCommits = new HashSet<>( advertisedShallowCommits); while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) { if (line.startsWith(PACKET_SHALLOW)) { newShallowCommits.add(ObjectId .fromString(line.substring(PACKET_SHALLOW.length()))); } else if (line.startsWith(PACKET_UNSHALLOW)) { ObjectId unshallow = ObjectId .fromString(line.substring(PACKET_UNSHALLOW.length())); if (!advertisedShallowCommits.contains(unshallow)) { throw new PackProtocolException(MessageFormat.format( JGitText.get().notShallowedUnshallow, unshallow.name())); } newShallowCommits.remove(unshallow); } line = input.readString(); } objectDatabase.setShallowCommits(newShallowCommits); return line; } /** * Notification event delivered just before the pack is received from the * network. This event can be used by RPC such as {@link org.eclipse.jgit.transport.TransportHttp} to * disable its request magic and ensure the pack stream is read correctly. * * @since 2.0 */ protected void onReceivePack() { // By default do nothing for TCP based protocols. } private static class CancelledException extends Exception { private static final long serialVersionUID = 1L; } private static class FetchStateV2 { long havesToSend = 32; long havesTotal; // Set to true if we got at least one ACK in protocol V2. boolean hadAcks; // Counts haves without ACK. Use as cutoff for negotiation only once // hadAcks == true. long havesWithoutAck; void incHavesToSend(boolean statelessRPC) { if (statelessRPC) { // Increase this quicker since connection setup costs accumulate if (havesToSend < 16384) { havesToSend *= 2; } else { havesToSend = havesToSend * 11 / 10; } } else { havesToSend += 32; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 15235 Content-Disposition: inline; filename="BasePackPushConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "9a96fdcf37d5eb4bbec19ac966fb07c533a225b3" /* * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; /** * Push implementation using the native Git pack transfer service. *

* This is the canonical implementation for transferring objects to the remote * repository from the local repository by talking to the 'git-receive-pack' * service. Objects are packed on the local side into a pack file and then sent * to the remote repository. *

* This connection requires only a bi-directional pipe or socket, and thus is * easily wrapped up into a local process pipe, anonymous TCP socket, or a * command executed through an SSH tunnel. *

* This implementation honors * {@link org.eclipse.jgit.transport.Transport#isPushThin()} option. *

* Concrete implementations should just call * {@link #init(java.io.InputStream, java.io.OutputStream)} and * {@link #readAdvertisedRefs()} methods in constructor or before any use. They * should also handle resources releasing in {@link #close()} method if needed. */ public abstract class BasePackPushConnection extends BasePackConnection implements PushConnection { /** * The client expects a status report after the server processes the pack. * @since 2.0 */ public static final String CAPABILITY_REPORT_STATUS = GitProtocolConstants.CAPABILITY_REPORT_STATUS; /** * The server supports deleting refs. * @since 2.0 */ public static final String CAPABILITY_DELETE_REFS = GitProtocolConstants.CAPABILITY_DELETE_REFS; /** * The server supports packs with OFS deltas. * @since 2.0 */ public static final String CAPABILITY_OFS_DELTA = GitProtocolConstants.CAPABILITY_OFS_DELTA; /** * The client supports using the 64K side-band for progress messages. * @since 2.0 */ public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; /** * The server supports the receiving of push options. * @since 4.5 */ public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; private final boolean thinPack; private final boolean atomic; private final boolean useBitmaps; /** A list of option strings associated with this push. */ private List pushOptions; private boolean capableAtomic; private boolean capableDeleteRefs; private boolean capableReport; private boolean capableSideBand; private boolean capableOfsDelta; private boolean capablePushOptions; private boolean sentCommand; private boolean writePack; /** Time in milliseconds spent transferring the pack data. */ private long packTransferTime; /** * Create a new connection to push using the native git transport. * * @param packTransport * the transport. */ public BasePackPushConnection(PackTransport packTransport) { super(packTransport); thinPack = transport.isPushThin(); atomic = transport.isPushAtomic(); pushOptions = transport.getPushOptions(); useBitmaps = transport.isPushUseBitmaps(); } @Override public void push(final ProgressMonitor monitor, final Map refUpdates) throws TransportException { push(monitor, refUpdates, null); } @Override public void push(final ProgressMonitor monitor, final Map refUpdates, OutputStream outputStream) throws TransportException { markStartedOperation(); doPush(monitor, refUpdates, outputStream); } @Override protected TransportException noRepository(Throwable cause) { // Sadly we cannot tell the "invalid URI" case from "push not allowed". // Opening a fetch connection can help us tell the difference, as any // useful repository is going to support fetch if it also would allow // push. So if fetch throws NoRemoteRepositoryException we know the // URI is wrong. Otherwise we can correctly state push isn't allowed // as the fetch connection opened successfully. // TransportException te; try { transport.openFetch().close(); te = new TransportException(uri, JGitText.get().pushNotPermitted); } catch (NoRemoteRepositoryException e) { // Fetch concluded the repository doesn't exist. te = e; } catch (NotSupportedException | TransportException e) { te = new TransportException(uri, JGitText.get().pushNotPermitted, e); } te.addSuppressed(cause); return te; } /** * Push one or more objects and update the remote repository. * * @param monitor * progress monitor to receive status updates. * @param refUpdates * update commands to be applied to the remote repository. * @param outputStream * output stream to write sideband messages to * @throws org.eclipse.jgit.errors.TransportException * if any exception occurs. * @since 3.0 */ protected void doPush(final ProgressMonitor monitor, final Map refUpdates, OutputStream outputStream) throws TransportException { try { writeCommands(refUpdates.values(), monitor, outputStream); if (pushOptions != null && capablePushOptions) transmitOptions(); if (writePack) writePack(refUpdates, monitor); if (sentCommand) { if (capableReport) readStatusReport(refUpdates); if (capableSideBand) { // Ensure the data channel is at EOF, so we know we have // read all side-band data from all channels and have a // complete copy of the messages (if any) buffered from // the other data channels. // int b = in.read(); if (0 <= b) { throw new TransportException(uri, MessageFormat.format( JGitText.get().expectedEOFReceived, Character.valueOf((char) b))); } } } } catch (TransportException e) { throw e; } catch (Exception e) { throw new TransportException(uri, e.getMessage(), e); } finally { if (in instanceof SideBandInputStream) { ((SideBandInputStream) in).drainMessages(); } close(); } } private void writeCommands(final Collection refUpdates, final ProgressMonitor monitor, OutputStream outputStream) throws IOException { final String capabilities = enableCapabilities(monitor, outputStream); if (atomic && !capableAtomic) { throw new TransportException(uri, JGitText.get().atomicPushNotSupported); } if (pushOptions != null && !capablePushOptions) { throw new TransportException(uri, MessageFormat.format(JGitText.get().pushOptionsNotSupported, pushOptions.toString())); } for (RemoteRefUpdate rru : refUpdates) { if (!capableDeleteRefs && rru.isDelete()) { rru.setStatus(Status.REJECTED_NODELETE); continue; } final StringBuilder sb = new StringBuilder(); ObjectId oldId = rru.getExpectedOldObjectId(); if (oldId == null) { final Ref advertised = getRef(rru.getRemoteName()); oldId = advertised != null ? advertised.getObjectId() : null; if (oldId == null) { oldId = ObjectId.zeroId(); } } sb.append(oldId.name()); sb.append(' '); sb.append(rru.getNewObjectId().name()); sb.append(' '); sb.append(rru.getRemoteName()); if (!sentCommand) { sentCommand = true; sb.append(capabilities); } pckOut.writeString(sb.toString()); rru.setStatus(Status.AWAITING_REPORT); if (!rru.isDelete()) writePack = true; } if (monitor.isCancelled()) throw new TransportException(uri, JGitText.get().pushCancelled); pckOut.end(); outNeedsEnd = false; } private void transmitOptions() throws IOException { for (String pushOption : pushOptions) { pckOut.writeString(pushOption); } pckOut.end(); } private String enableCapabilities(final ProgressMonitor monitor, OutputStream outputStream) { final StringBuilder line = new StringBuilder(); if (atomic) capableAtomic = wantCapability(line, CAPABILITY_ATOMIC); capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS); capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS); capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA); if (pushOptions != null) { capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS); } capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K); if (capableSideBand) { in = new SideBandInputStream(in, monitor, getMessageWriter(), outputStream); pckIn = new PacketLineIn(in); } addUserAgentCapability(line); if (line.length() > 0) line.setCharAt(0, '\0'); return line.toString(); } private void writePack(final Map refUpdates, final ProgressMonitor monitor) throws IOException { Set remoteObjects = new HashSet<>(); Set newObjects = new HashSet<>(); try (PackWriter writer = new PackWriter(transport.getPackConfig(), local.newObjectReader())) { for (Ref r : getRefs()) { // only add objects that we actually have ObjectId oid = r.getObjectId(); if (local.getObjectDatabase().has(oid)) remoteObjects.add(oid); } remoteObjects.addAll(additionalHaves); for (RemoteRefUpdate r : refUpdates.values()) { if (!ObjectId.zeroId().equals(r.getNewObjectId())) newObjects.add(r.getNewObjectId()); } writer.setIndexDisabled(true); writer.setUseCachedPacks(true); writer.setUseBitmaps(useBitmaps); writer.setThin(thinPack); writer.setReuseValidatingObjects(false); writer.setDeltaBaseAsOffset(capableOfsDelta); writer.preparePack(monitor, newObjects, remoteObjects); OutputStream packOut = out; if (capableSideBand) { packOut = new CheckingSideBandOutputStream(in, out); } writer.writePack(monitor, monitor, packOut); packTransferTime = writer.getStatistics().getTimeWriting(); } } private void readStatusReport(Map refUpdates) throws IOException { final String unpackLine = readStringLongTimeout(); if (!unpackLine.startsWith("unpack ")) //$NON-NLS-1$ throw new PackProtocolException(uri, MessageFormat .format(JGitText.get().unexpectedReportLine, unpackLine)); final String unpackStatus = unpackLine.substring("unpack ".length()); //$NON-NLS-1$ if (unpackStatus.startsWith("error Pack exceeds the limit of")) {//$NON-NLS-1$ throw new TooLargePackException(uri, unpackStatus.substring("error ".length())); //$NON-NLS-1$ } else if (unpackStatus.startsWith("error Object too large")) {//$NON-NLS-1$ throw new TooLargeObjectInPackException(uri, unpackStatus.substring("error ".length())); //$NON-NLS-1$ } else if (!unpackStatus.equals("ok")) { //$NON-NLS-1$ throw new TransportException(uri, MessageFormat.format( JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus)); } for (String refLine : pckIn.readStrings()) { boolean ok = false; int refNameEnd = -1; if (refLine.startsWith("ok ")) { //$NON-NLS-1$ ok = true; refNameEnd = refLine.length(); } else if (refLine.startsWith("ng ")) { //$NON-NLS-1$ ok = false; refNameEnd = refLine.indexOf(' ', 3); } if (refNameEnd == -1) throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedReportLine2 , uri, refLine)); final String refName = refLine.substring(3, refNameEnd); final String message = (ok ? null : refLine .substring(refNameEnd + 1)); final RemoteRefUpdate rru = refUpdates.get(refName); if (rru == null) throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedRefReport, uri, refName)); if (ok) { rru.setStatus(Status.OK); } else { rru.setStatus(Status.REJECTED_OTHER_REASON); rru.setMessage(message); } } for (RemoteRefUpdate rru : refUpdates.values()) { if (rru.getStatus() == Status.AWAITING_REPORT) throw new PackProtocolException(MessageFormat.format( JGitText.get().expectedReportForRefNotReceived , uri, rru.getRemoteName())); } } private String readStringLongTimeout() throws IOException { if (timeoutIn == null) return pckIn.readString(); // The remote side may need a lot of time to choke down the pack // we just sent them. There may be many deltas that need to be // resolved by the remote. Its hard to say how long the other // end is going to be silent. Taking 10x the configured timeout // or the time spent transferring the pack, whichever is larger, // gives the other side some reasonable window to process the data, // but this is just a wild guess. // final int oldTimeout = timeoutIn.getTimeout(); final int sendTime = (int) Math.min(packTransferTime, 28800000L); try { int timeout = 10 * Math.max(sendTime, oldTimeout); timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout); return pckIn.readString(); } finally { timeoutIn.setTimeout(oldTimeout); } } /** * Gets the list of option strings associated with this push. * * @return pushOptions * @since 4.5 */ public List getPushOptions() { return pushOptions; } /** * Whether to use bitmaps for push. * * @return true if push use bitmaps. * @since 6.4 */ public boolean isUseBitmaps() { return useBitmaps; } private static class CheckingSideBandOutputStream extends OutputStream { private final InputStream in; private final OutputStream out; CheckingSideBandOutputStream(InputStream in, OutputStream out) { this.in = in; this.out = out; } @Override public void write(int b) throws IOException { write(new byte[] { (byte) b }); } @Override public void write(byte[] buf, int ptr, int cnt) throws IOException { try { out.write(buf, ptr, cnt); } catch (IOException e) { throw checkError(e); } } @Override public void flush() throws IOException { try { out.flush(); } catch (IOException e) { throw checkError(e); } } private IOException checkError(IOException e1) { try { in.read(); } catch (TransportException e2) { return e2; } catch (IOException e2) { return e1; } return e1; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7611 Content-Disposition: inline; filename="BundleFetchConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "5299dfa981ed7cfa8c40acadcf40781621527d01" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2008-2010, Google Inc. * Copyright (C) 2008-2009, Robin Rosenberg * Copyright (C) 2009, Sasa Zivkov * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.MissingBundlePrerequisiteException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; /** * Fetch connection for bundle based classes. It used by * instances of {@link TransportBundle} */ class BundleFetchConnection extends BaseFetchConnection { private final Transport transport; InputStream bin; final Map prereqs = new HashMap<>(); private String lockMessage; private PackLock packLock; BundleFetchConnection(Transport transportBundle, InputStream src) throws TransportException { transport = transportBundle; bin = new BufferedInputStream(src); try { switch (readSignature()) { case 2: readBundleV2(); break; default: throw new TransportException(transport.uri, JGitText.get().notABundle); } } catch (TransportException err) { close(); throw err; } catch (IOException | RuntimeException err) { close(); throw new TransportException(transport.uri, err.getMessage(), err); } } private int readSignature() throws IOException { final String rev = readLine(new byte[1024]); if (TransportBundle.V2_BUNDLE_SIGNATURE.equals(rev)) return 2; throw new TransportException(transport.uri, JGitText.get().notABundle); } private void readBundleV2() throws IOException { final byte[] hdrbuf = new byte[1024]; final LinkedHashMap avail = new LinkedHashMap<>(); for (;;) { String line = readLine(hdrbuf); if (line.length() == 0) break; if (line.charAt(0) == '-') { ObjectId id = ObjectId.fromString(line.substring(1, 41)); String shortDesc = null; if (line.length() > 42) shortDesc = line.substring(42); prereqs.put(id, shortDesc); continue; } final String name = line.substring(41, line.length()); final ObjectId id = ObjectId.fromString(line.substring(0, 40)); final Ref prior = avail.put(name, new ObjectIdRef.Unpeeled( Ref.Storage.NETWORK, name, id)); if (prior != null) throw duplicateAdvertisement(name); } available(avail); } private PackProtocolException duplicateAdvertisement(String name) { return new PackProtocolException(transport.uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name)); } private String readLine(byte[] hdrbuf) throws IOException { StringBuilder line = new StringBuilder(); boolean done = false; while (!done) { bin.mark(hdrbuf.length); final int cnt = bin.read(hdrbuf); if (cnt < 0) { throw new EOFException(JGitText.get().shortReadOfBlock); } int lf = 0; while (lf < cnt && hdrbuf[lf] != '\n') { lf++; } bin.reset(); IO.skipFully(bin, lf); if (lf < cnt && hdrbuf[lf] == '\n') { IO.skipFully(bin, 1); done = true; } line.append(RawParseUtils.decode(UTF_8, hdrbuf, 0, lf)); } return line.toString(); } @Override public boolean didFetchTestConnectivity() { return false; } @Override protected void doFetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException { verifyPrerequisites(); try { try (ObjectInserter ins = transport.local.newObjectInserter()) { PackParser parser = ins.newPackParser(bin); parser.setAllowThin(true); parser.setObjectChecker(transport.getObjectChecker()); parser.setLockMessage(lockMessage); packLock = parser.parse(NullProgressMonitor.INSTANCE); ins.flush(); } } catch (IOException | RuntimeException err) { close(); throw new TransportException(transport.uri, err.getMessage(), err); } } @Override public void setPackLockMessage(String message) { lockMessage = message; } @Override public Collection getPackLocks() { if (packLock != null) return Collections.singleton(packLock); return Collections. emptyList(); } private void verifyPrerequisites() throws TransportException { if (prereqs.isEmpty()) return; try (RevWalk rw = new RevWalk(transport.local)) { final RevFlag PREREQ = rw.newFlag("PREREQ"); //$NON-NLS-1$ final RevFlag SEEN = rw.newFlag("SEEN"); //$NON-NLS-1$ final Map missing = new HashMap<>(); final List commits = new ArrayList<>(); for (Map.Entry e : prereqs.entrySet()) { ObjectId p = e.getKey(); try { final RevCommit c = rw.parseCommit(p); if (!c.has(PREREQ)) { c.add(PREREQ); commits.add(c); } } catch (MissingObjectException notFound) { missing.put(p, e.getValue()); } catch (IOException err) { throw new TransportException(transport.uri, MessageFormat .format(JGitText.get().cannotReadCommit, p.name()), err); } } if (!missing.isEmpty()) throw new MissingBundlePrerequisiteException(transport.uri, missing); List localRefs; try { localRefs = transport.local.getRefDatabase().getRefs(); } catch (IOException e) { throw new TransportException(transport.uri, e.getMessage(), e); } for (Ref r : localRefs) { try { rw.markStart(rw.parseCommit(r.getObjectId())); } catch (IOException readError) { // If we cannot read the value of the ref skip it. } } int remaining = commits.size(); try { RevCommit c; while ((c = rw.next()) != null) { if (c.has(PREREQ)) { c.add(SEEN); if (--remaining == 0) break; } } } catch (IOException err) { throw new TransportException(transport.uri, JGitText.get().cannotReadObject, err); } if (remaining > 0) { for (RevObject o : commits) { if (!o.has(SEEN)) missing.put(o, prereqs.get(o)); } throw new MissingBundlePrerequisiteException(transport.uri, missing); } } } @Override public void close() { if (bin != null) { try { bin.close(); } catch (IOException ie) { // Ignore close failures. } finally { bin = null; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8841 Content-Disposition: inline; filename="BundleWriter.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "4649d33ff8e3b2d1cd5f0e64f63e8514d1f893ed" /* * Copyright (C) 2008-2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.CachedPack; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.pack.PackConfig; /** * Creates a Git bundle file, for sneaker-net transport to another system. *

* Bundles generated by this class can be later read in from a file URI using * the bundle transport, or from an application controlled buffer by the more * generic {@link org.eclipse.jgit.transport.TransportBundleStream}. *

* Applications creating bundles need to call one or more include * calls to reflect which objects should be available as refs in the bundle for * the other side to fetch. At least one include is required to create a valid * bundle file, and duplicate names are not permitted. *

* Optional assume calls can be made to declare commits which the * recipient must have in order to fetch from the bundle file. Objects reachable * from these assumed commits can be used as delta bases in order to reduce the * overall bundle size. */ public class BundleWriter { private final Repository db; private final ObjectReader reader; private final Map include; private final Set assume; private final Set tagTargets; private final List cachedPacks = new ArrayList<>(); private PackConfig packConfig; private ObjectCountCallback callback; /** * Create a writer for a bundle. * * @param repo * repository where objects are stored. */ public BundleWriter(Repository repo) { db = repo; reader = null; include = new TreeMap<>(); assume = new HashSet<>(); tagTargets = new HashSet<>(); } /** * Create a writer for a bundle. * * @param or * reader for reading objects. Will be closed at the end of {@link * #writeBundle(ProgressMonitor, OutputStream)}, but readers may be * reused after closing. * @since 4.8 */ public BundleWriter(ObjectReader or) { db = null; reader = or; include = new TreeMap<>(); assume = new HashSet<>(); tagTargets = new HashSet<>(); } /** * Set the configuration used by the pack generator. * * @param pc * configuration controlling packing parameters. If null the * source repository's settings will be used, or the default * settings if constructed without a repo. */ public void setPackConfig(PackConfig pc) { this.packConfig = pc; } /** * Include an object (and everything reachable from it) in the bundle. * * @param name * name the recipient can discover this object as from the * bundle's list of advertised refs . The name must be a valid * ref format and must not have already been included in this * bundle writer. * @param id * object to pack. Multiple refs may point to the same object. */ public void include(String name, AnyObjectId id) { boolean validRefName = Repository.isValidRefName(name) || Constants.HEAD.equals(name); if (!validRefName) throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidRefName, name)); if (include.containsKey(name)) throw new IllegalStateException(JGitText.get().duplicateRef + name); include.put(name, id.toObjectId()); } /** * Include a single ref (a name/object pair) in the bundle. *

* This is a utility function for: * include(r.getName(), r.getObjectId()). * * @param r * the ref to include. */ public void include(Ref r) { include(r.getName(), r.getObjectId()); if (r.getPeeledObjectId() != null) tagTargets.add(r.getPeeledObjectId()); else if (r.getObjectId() != null && r.getName().startsWith(Constants.R_HEADS)) tagTargets.add(r.getObjectId()); } /** * Add objects to the bundle file. * *

* When this method is used, object traversal is disabled and specified pack * files are directly saved to the Git bundle file. * *

* Unlike {@link #include}, this doesn't affect the refs. Even if the * objects are not reachable from any ref, they will be included in the * bundle file. * * @param c * pack to include * @since 5.9 */ public void addObjectsAsIs(Collection c) { cachedPacks.addAll(c); } /** * Assume a commit is available on the recipient's side. *

* In order to fetch from a bundle the recipient must have any assumed * commit. Each assumed commit is explicitly recorded in the bundle header * to permit the recipient to validate it has these objects. * * @param c * the commit to assume being available. This commit should be * parsed and not disposed in order to maximize the amount of * debugging information available in the bundle stream. */ public void assume(RevCommit c) { if (c != null) assume.add(c); } /** * Generate and write the bundle to the output stream. *

* This method can only be called once per BundleWriter instance. * * @param monitor * progress monitor to report bundle writing status to. * @param os * the stream the bundle is written to. The stream should be * buffered by the caller. The caller is responsible for closing * the stream. * @throws java.io.IOException * an error occurred reading a local object's data to include in * the bundle, or writing compressed object data to the output * stream. */ public void writeBundle(ProgressMonitor monitor, OutputStream os) throws IOException { try (PackWriter packWriter = newPackWriter()) { packWriter.setObjectCountCallback(callback); packWriter.setIndexDisabled(true); packWriter.setDeltaBaseAsOffset(true); packWriter.setReuseValidatingObjects(false); if (cachedPacks.isEmpty()) { HashSet inc = new HashSet<>(); HashSet exc = new HashSet<>(); inc.addAll(include.values()); for (RevCommit r : assume) { exc.add(r.getId()); } if (exc.isEmpty()) { packWriter.setTagTargets(tagTargets); } packWriter.setThin(!exc.isEmpty()); packWriter.preparePack(monitor, inc, exc); } else { packWriter.preparePack(cachedPacks); } final Writer w = new OutputStreamWriter(os, UTF_8); w.write(TransportBundle.V2_BUNDLE_SIGNATURE); w.write('\n'); final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH]; for (RevCommit a : assume) { w.write('-'); a.copyTo(tmp, w); if (a.getRawBuffer() != null) { w.write(' '); w.write(a.getShortMessage()); } w.write('\n'); } for (Map.Entry e : include.entrySet()) { e.getValue().copyTo(tmp, w); w.write(' '); w.write(e.getKey()); w.write('\n'); } w.write('\n'); w.flush(); packWriter.writePack(monitor, monitor, os); } } private PackWriter newPackWriter() { PackConfig pc = packConfig; if (pc == null) { pc = db != null ? new PackConfig(db) : new PackConfig(); } return new PackWriter(pc, reader != null ? reader : db.newObjectReader()); } /** * Set the {@link org.eclipse.jgit.transport.ObjectCountCallback}. *

* It should be set before calling * {@link #writeBundle(ProgressMonitor, OutputStream)}. *

* This callback will be passed on to * {@link org.eclipse.jgit.internal.storage.pack.PackWriter#setObjectCountCallback}. * * @param callback * the callback to set * @return this object for chaining. * @since 4.1 */ public BundleWriter setObjectCountCallback(ObjectCountCallback callback) { this.callback = callback; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 983 Content-Disposition: inline; filename="CapabilitiesV2Request.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "27afdc29beba346dc3c11d0798b095dde9544388" /* * Copyright (C) 2018, Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Capabilities protocol v2 request. * *

* This is used as an input to {@link ProtocolV2Hook}. * * @since 5.1 */ public final class CapabilitiesV2Request { private CapabilitiesV2Request() { } /** * Create builder * * @return A builder of {@link CapabilitiesV2Request}. */ public static Builder builder() { return new Builder(); } /** A builder for {@link CapabilitiesV2Request}. */ public static final class Builder { private Builder() { } /** * Build the request * * @return CapabilitiesV2Request */ public CapabilitiesV2Request build() { return new CapabilitiesV2Request(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2361 Content-Disposition: inline; filename="ChainingCredentialsProvider.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "3bae0eae5a19ebf82d9ac7d0241c026c8e311e76" /* * Copyright (C) 2014, Matthias Sohn and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.jgit.errors.UnsupportedCredentialItem; /** * A credentials provider chaining multiple credentials providers * * @since 3.5 */ public class ChainingCredentialsProvider extends CredentialsProvider { private List credentialProviders; /** * Create a new chaining credential provider. This provider tries to * retrieve credentials from the chained credential providers in the order * they are given here. If multiple providers support the requested items * and have non-null credentials the first of them will be used. * * @param providers * credential providers asked for credentials in the order given * here */ public ChainingCredentialsProvider(CredentialsProvider... providers) { this.credentialProviders = new ArrayList<>( Arrays.asList(providers)); } @Override public boolean isInteractive() { for (CredentialsProvider p : credentialProviders) if (p.isInteractive()) return true; return false; } @Override public boolean supports(CredentialItem... items) { for (CredentialsProvider p : credentialProviders) if (p.supports(items)) return true; return false; } /** * {@inheritDoc} *

* Populates the credential items with the credentials provided by the first * credential provider in the list which populates them with non-null values * * @see org.eclipse.jgit.transport.CredentialsProvider#supports(org.eclipse.jgit.transport.CredentialItem[]) */ @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { for (CredentialsProvider p : credentialProviders) { if (p.supports(items)) { if (!p.get(uri, items)) { if (p.isInteractive()) { return false; // user cancelled the request } continue; } if (isAnyNull(items)) { continue; } return true; } } return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3994 Content-Disposition: inline; filename="Connection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "996259f07ef7d132ceb4272264095df5ee117751" /* * Copyright (C) 2010, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import java.util.Map; import org.eclipse.jgit.lib.Ref; /** * Represent connection for operation on a remote repository. *

* Currently all operations on remote repository (fetch and push) provide * information about remote refs. Every connection is able to be closed and * should be closed - this is a connection client responsibility. * * @see Transport */ public interface Connection extends AutoCloseable { /** * Get the complete map of refs advertised as available for fetching or * pushing. * * @return available/advertised refs: map of refname to ref. Never null. Not * modifiable. The collection can be empty if the remote side has no * refs (it is an empty/newly created repository). */ Map getRefsMap(); /** * Get the complete list of refs advertised as available for fetching or * pushing. *

* The returned refs may appear in any order. If the caller needs these to * be sorted, they should be copied into a new array or List and then sorted * by the caller as necessary. * * @return available/advertised refs. Never null. Not modifiable. The * collection can be empty if the remote side has no refs (it is an * empty/newly created repository). */ Collection getRefs(); /** * Get a single advertised ref by name. *

* The name supplied should be valid ref name. To get a peeled value for a * ref (aka refs/tags/v1.0^{}) use the base name (without * the ^{} suffix) and look at the peeled object id. * * @param name * name of the ref to obtain. * @return the requested ref; null if the remote did not advertise this ref. */ Ref getRef(String name); /** * {@inheritDoc} *

* Close any resources used by this connection. *

* If the remote repository is contacted by a network socket this method * must close that network socket, disconnecting the two peers. If the * remote repository is actually local (same system) this method must close * any open file handles used to read the "remote" repository. *

* If additional messages were produced by the remote peer, these should * still be retained in the connection instance for {@link #getMessages()}. *

* {@code AutoClosable.close()} declares that it throws {@link Exception}. * Implementers shouldn't throw checked exceptions. This override narrows * the signature to prevent them from doing so. */ @Override void close(); /** * Get the additional messages, if any, returned by the remote process. *

* These messages are most likely informational or error messages, sent by * the remote peer, to help the end-user correct any problems that may have * prevented the operation from completing successfully. Application UIs * should try to show these in an appropriate context. *

* The message buffer is available after {@link #close()} has been called. * Prior to closing the connection, the message buffer may be empty. * * @return the messages returned by the remote, most likely terminated by a * newline (LF) character. The empty string is returned if the * remote produced no additional messages. */ String getMessages(); /** * User agent advertised by the remote server. * * @return agent (version of Git) running on the remote server. Null if the * server does not advertise this version. * @since 4.0 */ String getPeerUserAgent(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3576 Content-Disposition: inline; filename="ConnectivityChecker.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "cbb3420db26013df44173854fe39dd73955fdd66" /* * Copyright (c) 2019, Google LLC and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.util.List; import java.util.Set; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; /** * Checks that a received pack only depends on objects which are reachable from * a defined set of references. * * @since 5.7 */ public interface ConnectivityChecker { /** * Checks connectivity of the commit graph after pack uploading. * * @param connectivityCheckInfo * Input for the connectivity check. * @param haves * Set of references known for client. * @param pm * Monitor to publish progress to. * @throws IOException * an error occurred during connectivity checking. * */ void checkConnectivity(ConnectivityCheckInfo connectivityCheckInfo, Set haves, ProgressMonitor pm) throws IOException; /** * POJO which is used to pass all information which is needed to perform * connectivity check. */ public static class ConnectivityCheckInfo { private Repository repository; private PackParser parser; private boolean checkObjects; private List commands; private RevWalk walk; /** * Get database we write the stored objects into * * @return database we write the stored objects into. */ public Repository getRepository() { return repository; } /** * Set database we write the stored objects into * * @param repository * set database we write the stored objects into. */ public void setRepository(Repository repository) { this.repository = repository; } /** * Get the parser used to parse pack * * @return the parser used to parse pack. */ public PackParser getParser() { return parser; } /** * Set the parser * * @param parser * the parser to set */ public void setParser(PackParser parser) { this.parser = parser; } /** * Whether checker should check objects * * @return if checker should check objects. */ public boolean isCheckObjects() { return checkObjects; } /** * Set whether objects should be checked * * @param checkObjects * set if checker should check referenced objects outside of * the received pack are reachable. */ public void setCheckObjects(boolean checkObjects) { this.checkObjects = checkObjects; } /** * Get commands received by the current request * * @return commands received by the current request. */ public List getCommands() { return commands; } /** * Set commands received by the current request * * @param commands * commands received by the current request. */ public void setCommands(List commands) { this.commands = commands; } /** * Set the walk to parse commits * * @param walk * the walk to parse commits */ public void setWalk(RevWalk walk) { this.walk = walk; } /** * Get the walk to parse commits * * @return the walk to parse commits */ public RevWalk getWalk() { return walk; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7567 Content-Disposition: inline; filename="CredentialItem.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "32852890c0480fe4bccf8d9b6f7a2f71b2b531ce" /* * Copyright (C) 2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Arrays; import org.eclipse.jgit.internal.JGitText; /** * A credential requested from a * {@link org.eclipse.jgit.transport.CredentialsProvider}. * * Most users should work with the specialized subclasses: *

    *
  • {@link org.eclipse.jgit.transport.CredentialItem.Username} for * usernames
  • *
  • {@link org.eclipse.jgit.transport.CredentialItem.Password} for * passwords
  • *
  • {@link org.eclipse.jgit.transport.CredentialItem.StringType} for other * general string information
  • *
  • {@link org.eclipse.jgit.transport.CredentialItem.CharArrayType} for other * general secret information
  • *
* * This class is not thread-safe. Applications should construct their own * instance for each use, as the value is held within the CredentialItem object. */ public abstract class CredentialItem { private final String promptText; private final boolean valueSecure; /** * Initialize a prompt. * * @param promptText * prompt to display to the user alongside of the input field. * Should be sufficient text to indicate what to supply for this * item. * @param maskValue * true if the value should be masked from displaying during * input. This should be true for passwords and other secrets, * false for names and other public data. */ public CredentialItem(String promptText, boolean maskValue) { this.promptText = promptText; this.valueSecure = maskValue; } /** * Get prompt to display to the user. * * @return prompt to display to the user. */ public String getPromptText() { return promptText; } /** * Whether the value should be masked when entered. * * @return true if the value should be masked when entered. */ public boolean isValueSecure() { return valueSecure; } /** * Clear the stored value, destroying it as much as possible. */ public abstract void clear(); /** * An item whose value is stored as a string. * * When working with secret data, consider {@link CharArrayType} instead, as * the internal members of the array can be cleared, reducing the chances * that the password is left in memory after authentication is completed. */ public static class StringType extends CredentialItem { private String value; /** * Initialize a prompt for a single string. * * @param promptText * prompt to display to the user alongside of the input * field. Should be sufficient text to indicate what to * supply for this item. * @param maskValue * true if the value should be masked from displaying during * input. This should be true for passwords and other * secrets, false for names and other public data. */ public StringType(String promptText, boolean maskValue) { super(promptText, maskValue); } @Override public void clear() { value = null; } /** * Get value * * @return the current value */ public String getValue() { return value; } /** * Set value * * @param newValue * the new value */ public void setValue(String newValue) { value = newValue; } } /** An item whose value is stored as a char[] and is therefore clearable. */ public static class CharArrayType extends CredentialItem { private char[] value; /** * Initialize a prompt for a secure value stored in a character array. * * @param promptText * prompt to display to the user alongside of the input * field. Should be sufficient text to indicate what to * supply for this item. * @param maskValue * true if the value should be masked from displaying during * input. This should be true for passwords and other * secrets, false for names and other public data. */ public CharArrayType(String promptText, boolean maskValue) { super(promptText, maskValue); } /** Destroys the current value, clearing the internal array. */ @Override public void clear() { if (value != null) { Arrays.fill(value, (char) 0); value = null; } } /** * Get the current value. * * The returned array will be cleared out when {@link #clear()} is * called. Callers that need the array elements to survive should delay * invoking {@code clear()} until the value is no longer necessary. * * @return the current value array. The actual internal array is * returned, reducing the number of copies present in memory. */ public char[] getValue() { return value; } /** * Set the new value, clearing the old value array. * * @param newValue * if not null, the array is copied. */ public void setValue(char[] newValue) { clear(); if (newValue != null) { value = new char[newValue.length]; System.arraycopy(newValue, 0, value, 0, newValue.length); } } /** * Set the new value, clearing the old value array. * * @param newValue * the new internal array. The array is NOT copied. */ public void setValueNoCopy(char[] newValue) { clear(); value = newValue; } } /** An item whose value is a boolean choice, presented as Yes/No. */ public static class YesNoType extends CredentialItem { private boolean value; /** * Initialize a prompt for a single boolean answer. * * @param promptText * prompt to display to the user alongside of the input * field. Should be sufficient text to indicate what to * supply for this item. */ public YesNoType(String promptText) { super(promptText, false); } @Override public void clear() { value = false; } /** * Get value * * @return the current value */ public boolean getValue() { return value; } /** * Set the new value. * * @param newValue * the new value */ public void setValue(boolean newValue) { value = newValue; } } /** An advice message presented to the user, with no response required. */ public static class InformationalMessage extends CredentialItem { /** * Initialize an informational message. * * @param messageText * message to display to the user. */ public InformationalMessage(String messageText) { super(messageText, false); } @Override public void clear() { // Nothing to clear. } } /** Prompt for a username, which is not masked on input. */ public static class Username extends StringType { /** Initialize a new username item, with a default username prompt. */ public Username() { super(JGitText.get().credentialUsername, false); } } /** Prompt for a password, which is masked on input. */ public static class Password extends CharArrayType { /** Initialize a new password item, with a default password prompt. */ public Password() { super(JGitText.get().credentialPassword, true); } /** * Initialize a new password item, with given prompt. * * @param msg * prompt message */ public Password(String msg) { super(msg, true); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4522 Content-Disposition: inline; filename="CredentialsProvider.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "fd68324cf6f49ce77632c4c6085fbbb9233a9783" /* * Copyright (C) 2010, Christian Halstrick , * Copyright (C) 2010, Stefan Lay and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.List; import org.eclipse.jgit.errors.UnsupportedCredentialItem; /** * Provide credentials for use in connecting to Git repositories. * * Implementors are strongly encouraged to support at least the minimal * {@link org.eclipse.jgit.transport.CredentialItem.Username} and * {@link org.eclipse.jgit.transport.CredentialItem.Password} items. More * sophisticated implementors may implement additional types, such as * {@link org.eclipse.jgit.transport.CredentialItem.StringType}. * * CredentialItems are usually presented in bulk, allowing implementors to * combine them into a single UI widget and streamline the authentication * process for an end-user. * * @see UsernamePasswordCredentialsProvider */ public abstract class CredentialsProvider { private static volatile CredentialsProvider defaultProvider; /** * Get the default credentials provider, or null. * * @return the default credentials provider, or null. */ public static CredentialsProvider getDefault() { return defaultProvider; } /** * Set the default credentials provider. * * @param p * the new default provider, may be null to select no default. */ public static void setDefault(CredentialsProvider p) { defaultProvider = p; } /** * Whether any of the passed items is null * * @param items * credential items to check * @return {@code true} if any of the passed items is null, {@code false} * otherwise * @since 4.2 */ protected static boolean isAnyNull(CredentialItem... items) { for (CredentialItem i : items) if (i == null) return true; return false; } /** * Check if the provider is interactive with the end-user. * * An interactive provider may try to open a dialog box, or prompt for input * on the terminal, and will wait for a user response. A non-interactive * provider will either populate CredentialItems, or fail. * * @return {@code true} if the provider is interactive with the end-user. */ public abstract boolean isInteractive(); /** * Check if the provider can supply the necessary * {@link org.eclipse.jgit.transport.CredentialItem}s. * * @param items * the items the application requires to complete authentication. * @return {@code true} if this * {@link org.eclipse.jgit.transport.CredentialsProvider} supports * all of the items supplied. */ public abstract boolean supports(CredentialItem... items); /** * Ask for the credential items to be populated. * * @param uri * the URI of the remote resource that needs authentication. * @param items * the items the application requires to complete authentication. * @return {@code true} if the request was successful and values were * supplied; {@code false} if the user canceled the request and did * not supply all requested values. * @throws org.eclipse.jgit.errors.UnsupportedCredentialItem * if one of the items supplied is not supported. */ public abstract boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem; /** * Ask for the credential items to be populated. * * @param uri * the URI of the remote resource that needs authentication. * @param items * the items the application requires to complete authentication. * @return {@code true} if the request was successful and values were * supplied; {@code false} if the user canceled the request and did * not supply all requested values. * @throws org.eclipse.jgit.errors.UnsupportedCredentialItem * if one of the items supplied is not supported. */ public boolean get(URIish uri, List items) throws UnsupportedCredentialItem { return get(uri, items.toArray(new CredentialItem[0])); } /** * Reset the credentials provider for the given URI * * @param uri * a {@link org.eclipse.jgit.transport.URIish} object. */ public void reset(URIish uri) { // default does nothing } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12178 Content-Disposition: inline; filename="Daemon.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "221188aeeb921549c7590b647b0c7f37114de451" /* * Copyright (C) 2008-2009, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.Collection; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.RepositoryResolver; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; /** * Basic daemon for the anonymous git:// transport protocol. */ public class Daemon { /** 9418: IANA assigned port number for Git. */ public static final int DEFAULT_PORT = 9418; private static final int BACKLOG = 5; private InetSocketAddress myAddress; private final DaemonService[] services; private final ThreadGroup processors; private Acceptor acceptThread; private int timeout; private PackConfig packConfig; private volatile RepositoryResolver repositoryResolver; volatile UploadPackFactory uploadPackFactory; volatile ReceivePackFactory receivePackFactory; /** * Configure a daemon to listen on any available network port. */ public Daemon() { this(null); } /** * Configure a new daemon for the specified network address. * * @param addr * address to listen for connections on. If null, any available * port will be chosen on all network interfaces. */ @SuppressWarnings("unchecked") public Daemon(InetSocketAddress addr) { myAddress = addr; processors = new ThreadGroup("Git-Daemon"); //$NON-NLS-1$ repositoryResolver = (RepositoryResolver) RepositoryResolver.NONE; uploadPackFactory = (DaemonClient req, Repository db) -> { UploadPack up = new UploadPack(db); up.setTimeout(getTimeout()); up.setPackConfig(getPackConfig()); return up; }; receivePackFactory = (DaemonClient req, Repository db) -> { ReceivePack rp = new ReceivePack(db); InetAddress peer = req.getRemoteAddress(); String host = peer.getCanonicalHostName(); if (host == null) host = peer.getHostAddress(); String name = "anonymous"; //$NON-NLS-1$ String email = name + "@" + host; //$NON-NLS-1$ rp.setRefLogIdent(new PersonIdent(name, email)); rp.setTimeout(getTimeout()); return rp; }; services = new DaemonService[] { new DaemonService("upload-pack", "uploadpack") { //$NON-NLS-1$ //$NON-NLS-2$ { setEnabled(true); } @Override protected void execute(final DaemonClient dc, final Repository db, @Nullable Collection extraParameters) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { UploadPack up = uploadPackFactory.create(dc, db); InputStream in = dc.getInputStream(); OutputStream out = dc.getOutputStream(); if (extraParameters != null) { up.setExtraParameters(extraParameters); } up.upload(in, out, null); } }, new DaemonService("receive-pack", "receivepack") { //$NON-NLS-1$ //$NON-NLS-2$ { setEnabled(false); } @Override protected void execute(final DaemonClient dc, final Repository db, @Nullable Collection extraParameters) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { ReceivePack rp = receivePackFactory.create(dc, db); InputStream in = dc.getInputStream(); OutputStream out = dc.getOutputStream(); rp.receive(in, out, null); } } }; } /** * Get the address connections are received on. * * @return the address connections are received on. */ public synchronized InetSocketAddress getAddress() { return myAddress; } /** * Lookup a supported service so it can be reconfigured. * * @param name * name of the service; e.g. "receive-pack"/"git-receive-pack" or * "upload-pack"/"git-upload-pack". * @return the service; null if this daemon implementation doesn't support * the requested service type. */ public synchronized DaemonService getService(String name) { if (!name.startsWith("git-")) //$NON-NLS-1$ name = "git-" + name; //$NON-NLS-1$ for (DaemonService s : services) { if (s.getCommandName().equals(name)) return s; } return null; } /** * Get timeout (in seconds) before aborting an IO operation. * * @return timeout (in seconds) before aborting an IO operation. */ public int getTimeout() { return timeout; } /** * Set the timeout before willing to abort an IO call. * * @param seconds * number of seconds to wait (with no data transfer occurring) * before aborting an IO read or write operation with the * connected client. */ public void setTimeout(int seconds) { timeout = seconds; } /** * Get configuration controlling packing, may be null. * * @return configuration controlling packing, may be null. */ public PackConfig getPackConfig() { return packConfig; } /** * Set the configuration used by the pack generator. * * @param pc * configuration controlling packing parameters. If null the * source repository's settings will be used. */ public void setPackConfig(PackConfig pc) { this.packConfig = pc; } /** * Set the resolver used to locate a repository by name. * * @param resolver * the resolver instance. */ public void setRepositoryResolver(RepositoryResolver resolver) { repositoryResolver = resolver; } /** * Set the factory to construct and configure per-request UploadPack. * * @param factory * the factory. If null upload-pack is disabled. */ @SuppressWarnings("unchecked") public void setUploadPackFactory(UploadPackFactory factory) { if (factory != null) uploadPackFactory = factory; else uploadPackFactory = (UploadPackFactory) UploadPackFactory.DISABLED; } /** * Get the factory used to construct per-request ReceivePack. * * @return the factory. * @since 4.3 */ public ReceivePackFactory getReceivePackFactory() { return receivePackFactory; } /** * Set the factory to construct and configure per-request ReceivePack. * * @param factory * the factory. If null receive-pack is disabled. */ @SuppressWarnings("unchecked") public void setReceivePackFactory(ReceivePackFactory factory) { if (factory != null) receivePackFactory = factory; else receivePackFactory = (ReceivePackFactory) ReceivePackFactory.DISABLED; } private class Acceptor extends Thread { private final ServerSocket listenSocket; private final AtomicBoolean running = new AtomicBoolean(true); public Acceptor(ThreadGroup group, String name, ServerSocket socket) { super(group, name); this.listenSocket = socket; } @Override public void run() { setUncaughtExceptionHandler((thread, throwable) -> terminate()); while (isRunning()) { try { startClient(listenSocket.accept()); } catch (SocketException e) { // Test again to see if we should keep accepting. } catch (IOException e) { break; } } terminate(); } private void terminate() { try { shutDown(); } finally { clearThread(); } } public boolean isRunning() { return running.get(); } public void shutDown() { running.set(false); try { listenSocket.close(); } catch (IOException err) { // } } } /** * Start this daemon on a background thread. * * @throws java.io.IOException * the server socket could not be opened. * @throws java.lang.IllegalStateException * the daemon is already running. */ public synchronized void start() throws IOException { if (acceptThread != null) { throw new IllegalStateException(JGitText.get().daemonAlreadyRunning); } ServerSocket socket = new ServerSocket(); socket.setReuseAddress(true); if (myAddress != null) { socket.bind(myAddress, BACKLOG); } else { socket.bind(new InetSocketAddress((InetAddress) null, 0), BACKLOG); } myAddress = (InetSocketAddress) socket.getLocalSocketAddress(); acceptThread = new Acceptor(processors, "Git-Daemon-Accept", socket); //$NON-NLS-1$ acceptThread.start(); } private synchronized void clearThread() { acceptThread = null; } /** * Whether this daemon is receiving connections. * * @return {@code true} if this daemon is receiving connections. */ public synchronized boolean isRunning() { return acceptThread != null && acceptThread.isRunning(); } /** * Stop this daemon. */ public synchronized void stop() { if (acceptThread != null) { acceptThread.shutDown(); } } /** * Stops this daemon and waits until it's acceptor thread has finished. * * @throws java.lang.InterruptedException * if waiting for the acceptor thread is interrupted * @since 4.9 */ public void stopAndWait() throws InterruptedException { Thread acceptor = null; synchronized (this) { acceptor = acceptThread; stop(); } if (acceptor != null) { acceptor.join(); } } void startClient(Socket s) { final DaemonClient dc = new DaemonClient(this); final SocketAddress peer = s.getRemoteSocketAddress(); if (peer instanceof InetSocketAddress) dc.setRemoteAddress(((InetSocketAddress) peer).getAddress()); new Thread(processors, "Git-Daemon-Client " + peer.toString()) { //$NON-NLS-1$ @Override public void run() { try { dc.execute(s); } catch (ServiceNotEnabledException e) { // Ignored. Client cannot use this repository. } catch (ServiceNotAuthorizedException e) { // Ignored. Client cannot use this repository. } catch (IOException e) { // Ignore unexpected IO exceptions from clients } finally { try { s.getInputStream().close(); } catch (IOException e) { // Ignore close exceptions } try { s.getOutputStream().close(); } catch (IOException e) { // Ignore close exceptions } } } }.start(); } synchronized DaemonService matchService(String cmd) { for (DaemonService d : services) { if (d.handles(cmd)) return d; } return null; } Repository openRepository(DaemonClient client, String name) throws ServiceMayNotContinueException { // Assume any attempt to use \ was by a Windows client // and correct to the more typical / used in Git URIs. // name = name.replace('\\', '/'); // git://thishost/path should always be name="/path" here // if (!name.startsWith("/")) //$NON-NLS-1$ return null; try { return repositoryResolver.open(client, name.substring(1)); } catch (RepositoryNotFoundException e) { // null signals it "wasn't found", which is all that is suitable // for the remote client to know. return null; } catch (ServiceNotAuthorizedException e) { // null signals it "wasn't found", which is all that is suitable // for the remote client to know. return null; } catch (ServiceNotEnabledException e) { // null signals it "wasn't found", which is all that is suitable // for the remote client to know. return null; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2800 Content-Disposition: inline; filename="DaemonClient.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "cba973103181029dd1a66da897b54b0016ff3f9e" /* * Copyright (C) 2008-2009, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.util.Arrays; import java.util.Collection; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; /** * Active network client of {@link org.eclipse.jgit.transport.Daemon}. */ public class DaemonClient { private final Daemon daemon; private InetAddress peer; private InputStream rawIn; private OutputStream rawOut; DaemonClient(Daemon d) { daemon = d; } void setRemoteAddress(InetAddress ia) { peer = ia; } /** * Get the daemon which spawned this client. * * @return the daemon which spawned this client. */ public Daemon getDaemon() { return daemon; } /** * Get Internet address of the remote client. * * @return Internet address of the remote client. */ public InetAddress getRemoteAddress() { return peer; } /** * Get input stream to read from the connected client. * * @return input stream to read from the connected client. */ public InputStream getInputStream() { return rawIn; } /** * Get output stream to send data to the connected client. * * @return output stream to send data to the connected client. */ public OutputStream getOutputStream() { return rawOut; } void execute(Socket sock) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { rawIn = new BufferedInputStream(sock.getInputStream()); rawOut = new BufferedOutputStream(sock.getOutputStream()); if (0 < daemon.getTimeout()) sock.setSoTimeout(daemon.getTimeout() * 1000); String cmd = new PacketLineIn(rawIn).readStringRaw(); Collection extraParameters = null; int nulnul = cmd.indexOf("\0\0"); //$NON-NLS-1$ if (nulnul != -1) { extraParameters = Arrays.asList(cmd.substring(nulnul + 2).split("\0")); //$NON-NLS-1$ } final int nul = cmd.indexOf('\0'); if (nul >= 0) { // Newer clients hide a "host" header behind this byte. // Currently we don't use it for anything, so we ignore // this portion of the command. // cmd = cmd.substring(0, nul); } final DaemonService srv = getDaemon().matchService(cmd); if (srv == null) return; sock.setSoTimeout(0); srv.execute(this, cmd, extraParameters); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4370 Content-Disposition: inline; filename="DaemonService.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "78aaf3d5f910b5a3805cd5cfba4bd285dbe3910e" /* * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2009, Robin Rosenberg and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.util.Collection; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; /** * A service exposed by {@link org.eclipse.jgit.transport.Daemon} over anonymous * git://. */ public abstract class DaemonService { private final String command; private final SectionParser configKey; private boolean enabled; private boolean overridable; DaemonService(String cmdName, String cfgName) { command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$ configKey = cfg -> new ServiceConfig(DaemonService.this, cfg, cfgName); overridable = true; } private static class ServiceConfig { final boolean enabled; ServiceConfig(final DaemonService service, final Config cfg, final String name) { enabled = cfg.getBoolean("daemon", name, service.isEnabled()); //$NON-NLS-1$ } } /** * Whether this service is enabled for invocation. * * @return whether this service is enabled for invocation. */ public boolean isEnabled() { return enabled; } /** * Set if it is allowed to use this service * * @param on * {@code true} to allow this service to be used; {@code false} * to deny it. */ public void setEnabled(boolean on) { enabled = on; } /** @return can this service be configured in the repository config file? */ /** * Whether this service can be configured in the repository config file * * @return whether this service can be configured in the repository config * file */ public boolean isOverridable() { return overridable; } /** * Whether to permit repositories to override this service's enabled state * with the daemon.servicename config setting. * * @param on * {@code true} to permit repositories to override this service's * enabled state with the daemon.servicename config * setting. */ public void setOverridable(boolean on) { overridable = on; } /** * Get name of the command requested by clients. * * @return name of the command requested by clients. */ public String getCommandName() { return command; } /** * Determine if this service can handle the requested command. * * @param commandLine * input line from the client. * @return true if this command can accept the given command line. */ public boolean handles(String commandLine) { return command.length() + 1 < commandLine.length() && commandLine.charAt(command.length()) == ' ' && commandLine.startsWith(command); } void execute(DaemonClient client, String commandLine, @Nullable Collection extraParameters) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { final String name = commandLine.substring(command.length() + 1); try (Repository db = client.getDaemon().openRepository(client, name)) { if (isEnabledFor(db)) { execute(client, db, extraParameters); } } catch (ServiceMayNotContinueException e) { // An error when opening the repo means the client is expecting a ref // advertisement, so use that style of error. PacketLineOut pktOut = new PacketLineOut(client.getOutputStream()); pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ } } private boolean isEnabledFor(Repository db) { if (isOverridable()) return db.getConfig().get(configKey).enabled; return isEnabled(); } abstract void execute(DaemonClient client, Repository db, @Nullable Collection extraParameters) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8364 Content-Disposition: inline; filename="FetchConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "9dc0620665bfbf79f3aff7766a000e6fffecf9e3" /* * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Mike Ralphson * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.OutputStream; import java.util.Collection; import java.util.Set; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; /** * Lists known refs from the remote and copies objects of selected refs. *

* A fetch connection typically connects to the git-upload-pack * service running where the remote repository is stored. This provides a * one-way object transfer service to copy objects from the remote repository * into this local repository. *

* Instances of a FetchConnection must be created by a * {@link org.eclipse.jgit.transport.Transport} that implements a specific * object transfer protocol that both sides of the connection understand. *

* FetchConnection instances are not thread safe and may be accessed by only one * thread at a time. * * @see Transport */ public interface FetchConnection extends Connection { /** * Fetch objects we don't have but that are reachable from advertised refs. *

* Only one call per connection is allowed. Subsequent calls will result in * {@link org.eclipse.jgit.errors.TransportException}. *

*

* Implementations are free to use network connections as necessary to * efficiently (for both client and server) transfer objects from the remote * repository into this repository. When possible implementations should * avoid replacing/overwriting/duplicating an object already available in * the local destination repository. Locally available objects and packs * should always be preferred over remotely available objects and packs. * {@link org.eclipse.jgit.transport.Transport#isFetchThin()} should be * honored if applicable. *

* * @param monitor * progress monitor to inform the end-user about the amount of * work completed, or to indicate cancellation. Implementations * should poll the monitor at regular intervals to look for * cancellation requests from the user. * @param want * one or more refs advertised by this connection that the caller * wants to store locally. * @param have * additional objects known to exist in the destination * repository, especially if they aren't yet reachable by the ref * database. Connections should take this set as an addition to * what is reachable through all Refs, not in replace of it. * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * protocol error, or error on remote side, or connection was * already used for fetch. */ void fetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException; /** * Fetch objects we don't have but that are reachable from advertised refs. *

* Only one call per connection is allowed. Subsequent calls will result in * {@link org.eclipse.jgit.errors.TransportException}. *

*

* Implementations are free to use network connections as necessary to * efficiently (for both client and server) transfer objects from the remote * repository into this repository. When possible implementations should * avoid replacing/overwriting/duplicating an object already available in * the local destination repository. Locally available objects and packs * should always be preferred over remotely available objects and packs. * {@link org.eclipse.jgit.transport.Transport#isFetchThin()} should be * honored if applicable. *

* * @param monitor * progress monitor to inform the end-user about the amount of * work completed, or to indicate cancellation. Implementations * should poll the monitor at regular intervals to look for * cancellation requests from the user. * @param want * one or more refs advertised by this connection that the caller * wants to store locally. * @param have * additional objects known to exist in the destination * repository, especially if they aren't yet reachable by the ref * database. Connections should take this set as an addition to * what is reachable through all Refs, not in replace of it. * @param out * OutputStream to write sideband messages to * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * protocol error, or error on remote side, or connection was * already used for fetch. * @since 3.0 */ void fetch(final ProgressMonitor monitor, final Collection want, final Set have, OutputStream out) throws TransportException; /** * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} get tags? *

* Some Git aware transports are able to implicitly grab an annotated tag if * {@link org.eclipse.jgit.transport.TagOpt#AUTO_FOLLOW} or * {@link org.eclipse.jgit.transport.TagOpt#FETCH_TAGS} was selected and the * object the tag peels to (references) was transferred as part of the last * {@link #fetch(ProgressMonitor, Collection, Set)} call. If it is possible * for such tags to have been included in the transfer this method returns * true, allowing the caller to attempt tag discovery. *

* By returning only true/false (and not the actual list of tags obtained) * the transport itself does not need to be aware of whether or not tags * were included in the transfer. * * @return true if the last fetch call implicitly included tag objects; * false if tags were not implicitly obtained. */ boolean didFetchIncludeTags(); /** * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} validate * graph? *

* Some transports walk the object graph on the client side, with the client * looking for what objects it is missing and requesting them individually * from the remote peer. By virtue of completing the fetch call the client * implicitly tested the object connectivity, as every object in the graph * was either already local or was requested successfully from the peer. In * such transports this method returns true. *

* Some transports assume the remote peer knows the Git object graph and is * able to supply a fully connected graph to the client (although it may * only be transferring the parts the client does not yet have). Its faster * to assume such remote peers are well behaved and send the correct * response to the client. In such transports this method returns false. * * @return true if the last fetch had to perform a connectivity check on the * client side in order to succeed; false if the last fetch assumed * the remote peer supplied a complete graph. */ boolean didFetchTestConnectivity(); /** * Set the lock message used when holding a pack out of garbage collection. *

* Callers that set a lock message must ensure they call * {@link #getPackLocks()} after * {@link #fetch(ProgressMonitor, Collection, Set)}, even if an exception * was thrown, and release the locks that are held. * * @param message message to use when holding a pack in place. */ void setPackLockMessage(String message); /** * All locks created by the last * {@link #fetch(ProgressMonitor, Collection, Set)} call. * * @return collection (possibly empty) of locks created by the last call to * fetch. The caller must release these after refs are updated in * order to safely permit garbage collection. */ Collection getPackLocks(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1710 Content-Disposition: inline; filename="FetchHeadRecord.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "ffb011cb8b5c105c108fe9113eacef17813ef588" /* * Copyright (C) 2008, Charles O'Farrell * Copyright (C) 2009, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_REMOTES; import static org.eclipse.jgit.lib.Constants.R_TAGS; import java.io.IOException; import java.io.Writer; import org.eclipse.jgit.lib.ObjectId; class FetchHeadRecord { ObjectId newValue; boolean notForMerge; String sourceName; URIish sourceURI; void write(Writer pw) throws IOException { final String type; final String name; if (sourceName.startsWith(R_HEADS)) { type = "branch"; //$NON-NLS-1$ name = sourceName.substring(R_HEADS.length()); } else if (sourceName.startsWith(R_TAGS)) { type = "tag"; //$NON-NLS-1$ name = sourceName.substring(R_TAGS.length()); } else if (sourceName.startsWith(R_REMOTES)) { type = "remote branch"; //$NON-NLS-1$ name = sourceName.substring(R_REMOTES.length()); } else { type = ""; //$NON-NLS-1$ name = sourceName; } pw.write(newValue.name()); pw.write('\t'); if (notForMerge) pw.write("not-for-merge"); //$NON-NLS-1$ pw.write('\t'); pw.write(type); pw.write(" '"); //$NON-NLS-1$ pw.write(name); pw.write("' of "); //$NON-NLS-1$ pw.write(sourceURI.toString()); pw.write("\n"); //$NON-NLS-1$ } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 18866 Content-Disposition: inline; filename="FetchProcess.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "c510194ee6aa67ea00b28121286f98b23575202e" /* * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.LockFile; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.StringUtils; class FetchProcess { /** Transport we will fetch over. */ private final Transport transport; /** List of things we want to fetch from the remote repository. */ private final Collection toFetch; /** * List of things we don't want to fetch from the remote repository or to * the local repository. */ private final Collection negativeRefSpecs; /** Set of refs we will actually wind up asking to obtain. */ private final HashMap askFor = new HashMap<>(); /** Objects we know we have locally. */ private final HashSet have = new HashSet<>(); /** Updates to local tracking branches (if any). */ private final ArrayList localUpdates = new ArrayList<>(); /** Records to be recorded into FETCH_HEAD. */ private final ArrayList fetchHeadUpdates = new ArrayList<>(); private final ArrayList packLocks = new ArrayList<>(); private FetchConnection conn; private Map localRefs; FetchProcess(Transport t, Collection refSpecs) { transport = t; toFetch = refSpecs.stream().filter(refSpec -> !refSpec.isNegative()) .collect(Collectors.toList()); negativeRefSpecs = refSpecs.stream().filter(RefSpec::isNegative) .collect(Collectors.toList()); } @SuppressWarnings("Finally") void execute(ProgressMonitor monitor, FetchResult result, String initialBranch) throws NotSupportedException, TransportException { askFor.clear(); localUpdates.clear(); fetchHeadUpdates.clear(); packLocks.clear(); localRefs = null; Throwable e1 = null; try { executeImp(monitor, result, initialBranch); } catch (NotSupportedException | TransportException err) { e1 = err; throw err; } finally { try { for (PackLock lock : packLocks) { lock.unlock(); } } catch (Throwable e) { if (e1 != null) { e.addSuppressed(e1); } throw new TransportException(e.getMessage(), e); } } } private boolean isInitialBranchMissing(Map refsMap, String initialBranch) { if (StringUtils.isEmptyOrNull(initialBranch) || refsMap.isEmpty()) { return false; } if (refsMap.containsKey(initialBranch) || refsMap.containsKey(Constants.R_HEADS + initialBranch) || refsMap.containsKey(Constants.R_TAGS + initialBranch)) { return false; } return true; } private void executeImp(final ProgressMonitor monitor, final FetchResult result, String initialBranch) throws NotSupportedException, TransportException { final TagOpt tagopt = transport.getTagOpt(); String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS; String getHead = null; try { // If we don't have a HEAD yet, we're cloning and need to get the // upstream HEAD, too. Ref head = transport.local.exactRef(Constants.HEAD); ObjectId id = head != null ? head.getObjectId() : null; if (id == null || id.equals(ObjectId.zeroId())) { getHead = Constants.HEAD; } } catch (IOException e) { // Ignore } conn = transport.openFetch(toFetch, getTags, getHead); try { Map refsMap = conn.getRefsMap(); if (isInitialBranchMissing(refsMap, initialBranch)) { throw new TransportException(MessageFormat.format( JGitText.get().remoteBranchNotFound, initialBranch)); } result.setAdvertisedRefs(transport.getURI(), refsMap); result.peerUserAgent = conn.getPeerUserAgent(); final Set matched = new HashSet<>(); for (RefSpec spec : toFetch) { if (spec.getSource() == null) throw new TransportException(MessageFormat.format( JGitText.get().sourceRefNotSpecifiedForRefspec, spec)); if (spec.isWildcard()) expandWildcard(spec, matched); else expandSingle(spec, matched); } Collection additionalTags = Collections. emptyList(); if (tagopt == TagOpt.AUTO_FOLLOW) additionalTags = expandAutoFollowTags(); else if (tagopt == TagOpt.FETCH_TAGS) expandFetchTags(); final boolean includedTags; if (!askFor.isEmpty() && !askForIsComplete()) { fetchObjects(monitor); includedTags = conn.didFetchIncludeTags(); // Connection was used for object transfer. If we // do another fetch we must open a new connection. // closeConnection(result); } else { includedTags = false; } if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) { // There are more tags that we want to follow, but // not all were asked for on the initial request. // have.addAll(askFor.keySet()); askFor.clear(); for (Ref r : additionalTags) { ObjectId id = r.getPeeledObjectId(); if (id == null) id = r.getObjectId(); if (localHasObject(id)) wantTag(r); } if (!askFor.isEmpty() && (!includedTags || !askForIsComplete())) { reopenConnection(); if (!askFor.isEmpty()) fetchObjects(monitor); } } } finally { closeConnection(result); } BatchRefUpdate batch = transport.local.getRefDatabase() .newBatchUpdate() .setAllowNonFastForwards(true); // Generate reflog only when fetching updates and not at the first clone if (initialBranch == null) { batch.setRefLogMessage("fetch", true); //$NON-NLS-1$ } try (RevWalk walk = new RevWalk(transport.local)) { walk.setRetainBody(false); if (monitor instanceof BatchingProgressMonitor) { ((BatchingProgressMonitor) monitor).setDelayStart( 250, TimeUnit.MILLISECONDS); } if (transport.isRemoveDeletedRefs()) { deleteStaleTrackingRefs(result, batch); } addUpdateBatchCommands(result, batch); for (ReceiveCommand cmd : batch.getCommands()) { cmd.updateType(walk); if (cmd.getType() == UPDATE_NONFASTFORWARD && cmd instanceof TrackingRefUpdate.Command && !((TrackingRefUpdate.Command) cmd).canForceUpdate()) cmd.setResult(REJECTED_NONFASTFORWARD); } if (transport.isDryRun()) { for (ReceiveCommand cmd : batch.getCommands()) { if (cmd.getResult() == NOT_ATTEMPTED) cmd.setResult(OK); } } else { batch.execute(walk, monitor); } } catch (TransportException e) { throw e; } catch (IOException err) { throw new TransportException(MessageFormat.format( JGitText.get().failureUpdatingTrackingRef, getFirstFailedRefName(batch), err.getMessage()), err); } if (!fetchHeadUpdates.isEmpty()) { try { updateFETCH_HEAD(result); } catch (IOException err) { throw new TransportException(MessageFormat.format( JGitText.get().failureUpdatingFETCH_HEAD, err.getMessage()), err); } } } private void addUpdateBatchCommands(FetchResult result, BatchRefUpdate batch) throws TransportException { Map refs = new HashMap<>(); for (TrackingRefUpdate u : localUpdates) { // Try to skip duplicates if they'd update to the same object ID ObjectId existing = refs.get(u.getLocalName()); if (existing == null) { refs.put(u.getLocalName(), u.getNewObjectId()); result.add(u); batch.addCommand(u.asReceiveCommand()); } else if (!existing.equals(u.getNewObjectId())) { throw new TransportException(MessageFormat .format(JGitText.get().duplicateRef, u.getLocalName())); } } } private void fetchObjects(ProgressMonitor monitor) throws TransportException { try { conn.setPackLockMessage("jgit fetch " + transport.uri); //$NON-NLS-1$ conn.fetch(monitor, askFor.values(), have); } finally { packLocks.addAll(conn.getPackLocks()); } if (transport.isCheckFetchedObjects() && !conn.didFetchTestConnectivity() && !askForIsComplete()) throw new TransportException(transport.getURI(), JGitText.get().peerDidNotSupplyACompleteObjectGraph); } private void closeConnection(FetchResult result) { if (conn != null) { conn.close(); result.addMessages(conn.getMessages()); conn = null; } } private void reopenConnection() throws NotSupportedException, TransportException { if (conn != null) return; // Build prefixes Set prefixes = new HashSet<>(); for (Ref toGet : askFor.values()) { String src = toGet.getName(); prefixes.add(src); prefixes.add(Constants.R_REFS + src); prefixes.add(Constants.R_HEADS + src); prefixes.add(Constants.R_TAGS + src); } conn = transport.openFetch(Collections.emptyList(), prefixes.toArray(new String[0])); // Since we opened a new connection we cannot be certain // that the system we connected to has the same exact set // of objects available (think round-robin DNS and mirrors // that aren't updated at the same time). // // We rebuild our askFor list using only the refs that the // new connection has offered to us. // final HashMap avail = new HashMap<>(); for (Ref r : conn.getRefs()) avail.put(r.getObjectId(), r); final Collection wants = new ArrayList<>(askFor.values()); askFor.clear(); for (Ref want : wants) { final Ref newRef = avail.get(want.getObjectId()); if (newRef != null) { askFor.put(newRef.getObjectId(), newRef); } else { removeFetchHeadRecord(want.getObjectId()); removeTrackingRefUpdate(want.getObjectId()); } } } private void removeTrackingRefUpdate(ObjectId want) { final Iterator i = localUpdates.iterator(); while (i.hasNext()) { final TrackingRefUpdate u = i.next(); if (u.getNewObjectId().equals(want)) i.remove(); } } private void removeFetchHeadRecord(ObjectId want) { final Iterator i = fetchHeadUpdates.iterator(); while (i.hasNext()) { final FetchHeadRecord fh = i.next(); if (fh.newValue.equals(want)) i.remove(); } } private void updateFETCH_HEAD(FetchResult result) throws IOException { File meta = transport.local.getDirectory(); if (meta == null) return; final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD")); //$NON-NLS-1$ try { if (lock.lock()) { try (Writer w = new OutputStreamWriter( lock.getOutputStream(), UTF_8)) { for (FetchHeadRecord h : fetchHeadUpdates) { h.write(w); result.add(h); } } lock.commit(); } } finally { lock.unlock(); } } private boolean askForIsComplete() throws TransportException { try { try (ObjectWalk ow = new ObjectWalk(transport.local)) { boolean hasCommitObject = false; for (ObjectId want : askFor.keySet()) { RevObject obj = ow.parseAny(want); ow.markStart(obj); hasCommitObject |= obj.getType() == Constants.OBJ_COMMIT; } // Checking connectivity makes sense on commits only if (hasCommitObject) { for (Ref ref : localRefs().values()) { ow.markUninteresting(ow.parseAny(ref.getObjectId())); } ow.checkConnectivity(); } } return transport.getDepth() == null; // if depth is set we need to request objects that are already available } catch (MissingObjectException e) { return false; } catch (IOException e) { throw new TransportException(JGitText.get().unableToCheckConnectivity, e); } } private void expandWildcard(RefSpec spec, Set matched) throws TransportException { for (Ref src : conn.getRefs()) { if (spec.matchSource(src)) { RefSpec expandedRefSpec = spec.expandFromSource(src); if (!matchNegativeRefSpec(expandedRefSpec) && matched.add(src)) { want(src, expandedRefSpec); } } } } private void expandSingle(RefSpec spec, Set matched) throws TransportException { String want = spec.getSource(); if (ObjectId.isId(want)) { want(ObjectId.fromString(want)); return; } Ref src = conn.getRef(want); if (src == null) { throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, want)); } if (!matchNegativeRefSpec(spec) && matched.add(src)) { want(src, spec); } } private boolean matchNegativeRefSpec(RefSpec spec) { for (RefSpec negativeRefSpec : negativeRefSpecs) { if (negativeRefSpec.getSource() != null && spec.getSource() != null && negativeRefSpec.matchSource(spec.getSource())) { return true; } if (negativeRefSpec.getDestination() != null && spec.getDestination() != null && negativeRefSpec .matchDestination(spec.getDestination())) { return true; } } return false; } private boolean localHasObject(ObjectId id) throws TransportException { try { return transport.local.getObjectDatabase().has(id); } catch (IOException err) { throw new TransportException( MessageFormat.format( JGitText.get().readingObjectsFromLocalRepositoryFailed, err.getMessage()), err); } } private Collection expandAutoFollowTags() throws TransportException { final Collection additionalTags = new ArrayList<>(); final Map haveRefs = localRefs(); for (Ref r : conn.getRefs()) { if (!isTag(r)) continue; Ref local = haveRefs.get(r.getName()); if (local != null) // We already have a tag with this name, don't fetch it (even if // the local is different). continue; ObjectId obj = r.getPeeledObjectId(); if (obj == null) obj = r.getObjectId(); if (askFor.containsKey(obj) || localHasObject(obj)) wantTag(r); else additionalTags.add(r); } return additionalTags; } private void expandFetchTags() throws TransportException { final Map haveRefs = localRefs(); for (Ref r : conn.getRefs()) { if (!isTag(r)) { continue; } ObjectId id = r.getObjectId(); if (id == null) { continue; } final Ref local = haveRefs.get(r.getName()); if (local == null || !id.equals(local.getObjectId())) { wantTag(r); } } } private void wantTag(Ref r) throws TransportException { want(r, new RefSpec().setSource(r.getName()) .setDestination(r.getName()).setForceUpdate(true)); } private void want(Ref src, RefSpec spec) throws TransportException { final ObjectId newId = src.getObjectId(); if (newId == null) { throw new NullPointerException(MessageFormat.format( JGitText.get().transportProvidedRefWithNoObjectId, src.getName())); } if (spec.getDestination() != null) { final TrackingRefUpdate tru = createUpdate(spec, newId); // if depth is set we need to update the ref if (newId.equals(tru.getOldObjectId()) && transport.getDepth() == null) { return; } localUpdates.add(tru); } askFor.put(newId, src); final FetchHeadRecord fhr = new FetchHeadRecord(); fhr.newValue = newId; fhr.notForMerge = spec.getDestination() != null; fhr.sourceName = src.getName(); fhr.sourceURI = transport.getURI(); fetchHeadUpdates.add(fhr); } private void want(ObjectId id) { askFor.put(id, new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), id)); } private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId) throws TransportException { Ref ref = localRefs().get(spec.getDestination()); ObjectId oldId = ref != null && ref.getObjectId() != null ? ref.getObjectId() : ObjectId.zeroId(); return new TrackingRefUpdate( spec.isForceUpdate(), spec.getSource(), spec.getDestination(), oldId, newId); } private Map localRefs() throws TransportException { if (localRefs == null) { try { localRefs = transport.local.getRefDatabase() .getRefs(RefDatabase.ALL); } catch (IOException err) { throw new TransportException(JGitText.get().cannotListRefs, err); } } return localRefs; } private void deleteStaleTrackingRefs(FetchResult result, BatchRefUpdate batch) throws IOException { Set processed = new HashSet<>(); for (Ref ref : localRefs().values()) { if (ref.isSymbolic()) { continue; } String refname = ref.getName(); for (RefSpec spec : toFetch) { if (spec.matchDestination(refname)) { RefSpec s = spec.expandFromDestination(refname); if (result.getAdvertisedRef(s.getSource()) == null && processed.add(ref)) { deleteTrackingRef(result, batch, s, ref); } } } } } private void deleteTrackingRef(final FetchResult result, final BatchRefUpdate batch, final RefSpec spec, final Ref localRef) { if (localRef.getObjectId() == null) return; TrackingRefUpdate update = new TrackingRefUpdate( true, spec.getSource(), localRef.getName(), localRef.getObjectId(), ObjectId.zeroId()); result.add(update); batch.addCommand(update.asReceiveCommand()); } private static boolean isTag(Ref r) { return isTag(r.getName()); } private static boolean isTag(String name) { return name.startsWith(Constants.R_TAGS); } private static String getFirstFailedRefName(BatchRefUpdate batch) { for (ReceiveCommand cmd : batch.getCommands()) { if (cmd.getResult() != ReceiveCommand.Result.OK) return cmd.getRefName(); } return ""; //$NON-NLS-1$ } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4898 Content-Disposition: inline; filename="FetchRequest.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "91eed96d4ee3a61e1634d1f5af4f65a2e6630802" /* * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.util.Objects.requireNonNull; import java.util.List; import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.ObjectId; /** * Common fields between v0/v1/v2 fetch requests. */ abstract class FetchRequest { final Set wantIds; final int depth; final Set clientShallowCommits; final FilterSpec filterSpec; final Set clientCapabilities; final int deepenSince; final List deepenNots; @Nullable final String agent; @Nullable final String clientSID; /** * Initialize the common fields of a fetch request. * * @param wantIds * list of want ids * @param depth * how deep to go in the tree * @param clientShallowCommits * commits the client has without history * @param filterSpec * the filter spec * @param clientCapabilities * capabilities sent in the request * @param deepenNots * Requests that the shallow clone/fetch should be cut at these * specific revisions instead of a depth. * @param deepenSince * Requests that the shallow clone/fetch should be cut at a * specific time, instead of depth * @param agent * agent as reported by the client in the request body * @param clientSID * agent as reported by the client in the request body */ FetchRequest(@NonNull Set wantIds, int depth, @NonNull Set clientShallowCommits, @NonNull FilterSpec filterSpec, @NonNull Set clientCapabilities, int deepenSince, @NonNull List deepenNots, @Nullable String agent, @Nullable String clientSID) { this.wantIds = requireNonNull(wantIds); this.depth = depth; this.clientShallowCommits = requireNonNull(clientShallowCommits); this.filterSpec = requireNonNull(filterSpec); this.clientCapabilities = requireNonNull(clientCapabilities); this.deepenSince = deepenSince; this.deepenNots = requireNonNull(deepenNots); this.agent = agent; this.clientSID = clientSID; } /** * Get object ids in the "want" lines * * @return object ids in the "want" (and "want-ref") lines of the request */ @NonNull Set getWantIds() { return wantIds; } /** * Get the depth set in a "deepen" line * * @return the depth set in a "deepen" line. 0 by default. */ int getDepth() { return depth; } /** * Shallow commits the client already has. * * These are sent by the client in "shallow" request lines. * * @return set of commits the client has declared as shallow. */ @NonNull Set getClientShallowCommits() { return clientShallowCommits; } /** * Get the filter spec given in a "filter" line * * @return the filter spec given in a "filter" line */ @NonNull FilterSpec getFilterSpec() { return filterSpec; } /** * Capabilities that the client wants enabled from the server. * * Capabilities are options that tune the expected response from the server, * like "thin-pack", "no-progress" or "ofs-delta". This list should be a * subset of the capabilities announced by the server in its first response. * * These options are listed and well-defined in the git protocol * specification. * * The agent capability is not included in this set. It can be retrieved via * {@link #getAgent()}. * * @return capabilities sent by the client (excluding the "agent" * capability) */ @NonNull Set getClientCapabilities() { return clientCapabilities; } /** * The value in a "deepen-since" line in the request, indicating the * timestamp where to stop fetching/cloning. * * @return timestamp in seconds since the epoch, where to stop the shallow * fetch/clone. Defaults to 0 if not set in the request. */ int getDeepenSince() { return deepenSince; } /** * Get refs received in "deepen-not" lines * * @return refs received in "deepen-not" lines. */ @NonNull List getDeepenNots() { return deepenNots; } /** * Get string identifying the agent * * @return string identifying the agent (as sent in the request body by the * client) */ @Nullable String getAgent() { return agent; } /** * Get string identifying the client session ID * * @return string identifying the client session ID (as sent in the request * body by the client) */ @Nullable String getClientSID() { return clientSID; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1701 Content-Disposition: inline; filename="FetchResult.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "c55fb21d9495ef31861aa556ee6efa9467ae6085" /* * Copyright (C) 2009, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2007, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Final status after a successful fetch from a remote repository. * * @see Transport#fetch(org.eclipse.jgit.lib.ProgressMonitor, Collection) */ public class FetchResult extends OperationResult { private final List forMerge; private final Map submodules; FetchResult() { forMerge = new ArrayList<>(); submodules = new HashMap<>(); } void add(FetchHeadRecord r) { if (!r.notForMerge) forMerge.add(r); } /** * Add fetch results for a submodule. * * @param path * the submodule path * @param result * the fetch result * @since 4.7 */ public void addSubmodule(String path, FetchResult result) { submodules.put(path, result); } /** * Get fetch results for submodules. * * @return Fetch results for submodules as a map of submodule paths to fetch * results. * @since 4.7 */ public Map submoduleResults() { return Collections.unmodifiableMap(submodules); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4574 Content-Disposition: inline; filename="FetchV0Request.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "53b3e19263e6744743b1999a4e71b2094a5d98a2" /* * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.ObjectId; /** * Fetch request in the V0/V1 protocol. */ final class FetchV0Request extends FetchRequest { FetchV0Request(@NonNull Set wantIds, int depth, @NonNull Set clientShallowCommits, @NonNull FilterSpec filterSpec, @NonNull Set clientCapabilities, int deepenSince, @NonNull List deepenNotRefs, @Nullable String agent, @Nullable String clientSID) { super(wantIds, depth, clientShallowCommits, filterSpec, clientCapabilities, deepenSince, deepenNotRefs, agent, clientSID); } static final class Builder { int depth; final List deepenNots = new ArrayList<>(); int deepenSince; final Set wantIds = new HashSet<>(); final Set clientShallowCommits = new HashSet<>(); FilterSpec filterSpec = FilterSpec.NO_FILTER; final Set clientCaps = new HashSet<>(); String agent; String clientSID; /** * Add wantId * * @param objectId * object id received in a "want" line * @return this builder */ Builder addWantId(ObjectId objectId) { wantIds.add(objectId); return this; } /** * Set depth * * @param d * depth set in a "deepen" line * @return this builder */ Builder setDepth(int d) { depth = d; return this; } /** * Get depth * * @return depth set in the request (via a "deepen" line). Defaulting to * 0 if not set. */ int getDepth() { return depth; } /** * Whether there's at least one "deepen not" line * * @return true if there has been at least one "deepen not" line in the * request so far */ boolean hasDeepenNots() { return !deepenNots.isEmpty(); } /** * Add "deepen not" * * @param deepenNot * reference received in a "deepen not" line * @return this builder */ Builder addDeepenNot(String deepenNot) { deepenNots.add(deepenNot); return this; } /** * Set "deepen since" * * @param value * Unix timestamp received in a "deepen since" line * @return this builder */ Builder setDeepenSince(int value) { deepenSince = value; return this; } /** * Get "deepen since * * @return shallow since value, sent before in a "deepen since" line. 0 * by default. */ int getDeepenSince() { return deepenSince; } /** * Add client shallow commit * * @param shallowOid * object id received in a "shallow" line * @return this builder */ Builder addClientShallowCommit(ObjectId shallowOid) { clientShallowCommits.add(shallowOid); return this; } /** * Add client capabilities * * @param clientCapabilities * client capabilities sent by the client in the first want * line of the request * @return this builder */ Builder addClientCapabilities(Collection clientCapabilities) { clientCaps.addAll(clientCapabilities); return this; } /** * Set agent * * @param clientAgent * agent line sent by the client in the request body * @return this builder */ Builder setAgent(String clientAgent) { agent = clientAgent; return this; } /** * Set client session id * * @param clientSID * session-id line sent by the client in the request body * @return this builder */ Builder setClientSID(String clientSID) { this.clientSID = clientSID; return this; } /** * Set filter spec * * @param filter * the filter set in a filter line * @return this builder */ Builder setFilterSpec(@NonNull FilterSpec filter) { filterSpec = requireNonNull(filter); return this; } FetchV0Request build() { return new FetchV0Request(wantIds, depth, clientShallowCommits, filterSpec, clientCaps, deepenSince, deepenNots, agent, clientSID); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 9533 Content-Disposition: inline; filename="FetchV2Request.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "21284db2c175616146bd272950d494fb511c04c7" /* * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.ObjectId; /** * Fetch request from git protocol v2. * *

* This is used as an input to {@link ProtocolV2Hook}. * * @since 5.1 */ public final class FetchV2Request extends FetchRequest { private final List peerHas; private final List wantedRefs; private final boolean doneReceived; private final boolean waitForDone; @NonNull private final List serverOptions; private final boolean sidebandAll; @NonNull private final List packfileUriProtocols; FetchV2Request(@NonNull List peerHas, @NonNull List wantedRefs, @NonNull Set wantIds, @NonNull Set clientShallowCommits, int deepenSince, @NonNull List deepenNots, int depth, @NonNull FilterSpec filterSpec, boolean doneReceived, boolean waitForDone, @NonNull Set clientCapabilities, @Nullable String agent, @NonNull List serverOptions, boolean sidebandAll, @NonNull List packfileUriProtocols, @Nullable String clientSID) { super(wantIds, depth, clientShallowCommits, filterSpec, clientCapabilities, deepenSince, deepenNots, agent, clientSID); this.peerHas = requireNonNull(peerHas); this.wantedRefs = requireNonNull(wantedRefs); this.doneReceived = doneReceived; this.waitForDone = waitForDone; this.serverOptions = requireNonNull(serverOptions); this.sidebandAll = sidebandAll; this.packfileUriProtocols = packfileUriProtocols; } /** * Get object ids received in the "have" lines * * @return object ids received in the "have" lines */ @NonNull List getPeerHas() { return peerHas; } /** * Get list of references received in "want-ref" lines * * @return list of references received in "want-ref" lines * * @since 5.4 */ @NonNull public List getWantedRefs() { return wantedRefs; } /** * Whether the request had a "done" line * * @return true if the request had a "done" line */ boolean wasDoneReceived() { return doneReceived; } /** * Whether the request had a "wait-for-done" line * * @return true if the request had a "wait-for-done" line */ boolean wasWaitForDoneReceived() { return waitForDone; } /** * Options received in server-option lines. The caller can choose to act on * these in an application-specific way * * @return Immutable list of server options received in the request * * @since 5.2 */ @NonNull public List getServerOptions() { return serverOptions; } /** * Whether "sideband-all" was received * * @return true if "sideband-all" was received */ boolean getSidebandAll() { return sidebandAll; } @NonNull List getPackfileUriProtocols() { return packfileUriProtocols; } /** * Get builder * * @return A builder of {@link FetchV2Request}. */ static Builder builder() { return new Builder(); } /** A builder for {@link FetchV2Request}. */ static final class Builder { final List peerHas = new ArrayList<>(); final List wantedRefs = new ArrayList<>(); final Set wantIds = new HashSet<>(); final Set clientShallowCommits = new HashSet<>(); final List deepenNots = new ArrayList<>(); final Set clientCapabilities = new HashSet<>(); int depth; int deepenSince; FilterSpec filterSpec = FilterSpec.NO_FILTER; boolean doneReceived; boolean waitForDone; @Nullable String agent; @Nullable String clientSID; final List serverOptions = new ArrayList<>(); boolean sidebandAll; final List packfileUriProtocols = new ArrayList<>(); private Builder() { } /** * Add object the peer has * * @param objectId * object id received in a "have" line * @return this builder */ Builder addPeerHas(ObjectId objectId) { peerHas.add(objectId); return this; } /** * Add Ref received in "want-ref" line and the object-id it refers to * * @param refName * reference name * @return this builder */ Builder addWantedRef(String refName) { wantedRefs.add(refName); return this; } /** * Add client capability * * @param clientCapability * capability line sent by the client * @return this builder */ Builder addClientCapability(String clientCapability) { clientCapabilities.add(clientCapability); return this; } /** * Add object received in "want" line * * @param wantId * object id received in a "want" line * @return this builder */ Builder addWantId(ObjectId wantId) { wantIds.add(wantId); return this; } /** * Add Object received in a "shallow" line * * @param shallowOid * object id received in a "shallow" line * @return this builder */ Builder addClientShallowCommit(ObjectId shallowOid) { clientShallowCommits.add(shallowOid); return this; } /** * Set depth received in "deepen" line * * @param d * Depth received in a "deepen" line * @return this builder */ Builder setDepth(int d) { depth = d; return this; } /** * Get depth set in request * * @return depth set in the request (via a "deepen" line). Defaulting to * 0 if not set. */ int getDepth() { return depth; } /** * Whether there has been at least one ""deepen not" line * * @return true if there has been at least one "deepen not" line in the * request so far */ boolean hasDeepenNots() { return !deepenNots.isEmpty(); } /** * Add reference received in a "deepen not" line * * @param deepenNot * reference received in a "deepen not" line * @return this builder */ Builder addDeepenNot(String deepenNot) { deepenNots.add(deepenNot); return this; } /** * Set Unix timestamp received in a "deepen since" line * * @param value * Unix timestamp received in a "deepen since" line * @return this builder */ Builder setDeepenSince(int value) { deepenSince = value; return this; } /** * Get shallow since value * * @return shallow since value, sent before in a "deepen since" line. 0 * by default. */ int getDeepenSince() { return deepenSince; } /** * Set filter spec * * @param filter * spec set in a "filter" line * @return this builder */ Builder setFilterSpec(@NonNull FilterSpec filter) { filterSpec = requireNonNull(filter); return this; } /** * Mark that the "done" line has been received. * * @return this builder */ Builder setDoneReceived() { doneReceived = true; return this; } /** * Mark that the "wait-for-done" line has been received. * * @return this builder */ Builder setWaitForDone() { waitForDone = true; return this; } /** * Value of an agent line received after the command and before the * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0". * * @param agentValue * the client-supplied agent capability, without the leading * "agent=" * @return this builder */ Builder setAgent(@Nullable String agentValue) { agent = agentValue; return this; } /** * Set value of client-supplied session capability * * @param clientSIDValue * the client-supplied session capability, without the * leading "session-id=" * @return this builder */ Builder setClientSID(@Nullable String clientSIDValue) { clientSID = clientSIDValue; return this; } /** * Records an application-specific option supplied in a server-option * line, for later retrieval with * {@link FetchV2Request#getServerOptions}. * * @param value * the client-supplied server-option capability, without * leading "server-option=". * @return this builder */ Builder addServerOption(@NonNull String value) { serverOptions.add(value); return this; } /** * Set whether client sent "sideband-all * * @param value * true if client sent "sideband-all" * @return this builder */ Builder setSidebandAll(boolean value) { sidebandAll = value; return this; } Builder addPackfileUriProtocol(@NonNull String value) { packfileUriProtocols.add(value); return this; } /** * Build initialized fetch request * * @return Initialized fetch request */ FetchV2Request build() { return new FetchV2Request(peerHas, wantedRefs, wantIds, clientShallowCommits, deepenSince, deepenNots, depth, filterSpec, doneReceived, waitForDone, clientCapabilities, agent, Collections.unmodifiableList(serverOptions), sidebandAll, Collections.unmodifiableList(packfileUriProtocols), clientSID); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7231 Content-Disposition: inline; filename="FilterSpec.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "442b9634401c356c1c077f9c5f5a348dbb9c76a1" /* * Copyright (C) 2019, Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.math.BigInteger.ZERO; import static java.util.Objects.requireNonNull; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; import static org.eclipse.jgit.lib.Constants.OBJ_TAG; import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; import java.math.BigInteger; import java.text.MessageFormat; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; /** * Represents either a filter specified in a protocol "filter" line, or a * placeholder to indicate no filtering. * * @since 5.4 */ public final class FilterSpec { /** Immutable bit-set representation of a set of Git object types. */ static class ObjectTypes { static ObjectTypes ALL = allow(OBJ_BLOB, OBJ_TREE, OBJ_COMMIT, OBJ_TAG); private final BigInteger val; private ObjectTypes(BigInteger val) { this.val = requireNonNull(val); } static ObjectTypes allow(int... types) { BigInteger bits = ZERO; for (int type : types) { bits = bits.setBit(type); } return new ObjectTypes(bits); } boolean contains(int type) { return val.testBit(type); } @Override public boolean equals(Object obj) { if (!(obj instanceof ObjectTypes)) { return false; } ObjectTypes other = (ObjectTypes) obj; return other.val.equals(val); } @Override public int hashCode() { return val.hashCode(); } } private final ObjectTypes types; private final long blobLimit; private final long treeDepthLimit; private FilterSpec(ObjectTypes types, long blobLimit, long treeDepthLimit) { this.types = requireNonNull(types); this.blobLimit = blobLimit; this.treeDepthLimit = treeDepthLimit; } /** * Process the content of "filter" line from the protocol. It has a shape * like: * *

    *
  • "blob:none" *
  • "blob:limit=N", with N >= 0 *
  • "tree:DEPTH", with DEPTH >= 0 *
* * @param filterLine * the content of the "filter" line in the protocol * @return a FilterSpec representing the given filter * @throws PackProtocolException * invalid filter because due to unrecognized format or * negative/non-numeric filter. */ public static FilterSpec fromFilterLine(String filterLine) throws PackProtocolException { if (filterLine.equals("blob:none")) { //$NON-NLS-1$ return FilterSpec.withObjectTypes( ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)); } else if (filterLine.startsWith("blob:limit=")) { //$NON-NLS-1$ long blobLimit = -1; try { blobLimit = Long .parseLong(filterLine.substring("blob:limit=".length())); //$NON-NLS-1$ } catch (NumberFormatException e) { // Do not change blobLimit so that we throw a // PackProtocolException later. } if (blobLimit >= 0) { return FilterSpec.withBlobLimit(blobLimit); } } else if (filterLine.startsWith("tree:")) { //$NON-NLS-1$ long treeDepthLimit = -1; try { treeDepthLimit = Long .parseLong(filterLine.substring("tree:".length())); //$NON-NLS-1$ } catch (NumberFormatException e) { // Do not change blobLimit so that we throw a // PackProtocolException later. } if (treeDepthLimit >= 0) { return FilterSpec.withTreeDepthLimit(treeDepthLimit); } } // Did not match any known filter format. throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidFilter, filterLine)); } /** * Specify permitted object types * * @param types * set of permitted object types, for use in "blob:none" and * "object:none" filters * @return a filter spec which restricts to objects of the specified types */ static FilterSpec withObjectTypes(ObjectTypes types) { return new FilterSpec(types, -1, -1); } /** * Specify blob limit * * @param blobLimit * the blob limit in a "blob:[limit]" filter line * @return a filter spec which filters blobs above a certain size */ static FilterSpec withBlobLimit(long blobLimit) { if (blobLimit < 0) { throw new IllegalArgumentException( "blobLimit cannot be negative: " + blobLimit); //$NON-NLS-1$ } return new FilterSpec(ObjectTypes.ALL, blobLimit, -1); } /** * Specify tree depth limit * * @param treeDepthLimit * the tree depth limit in a "tree:[depth]" filter line * @return a filter spec which filters blobs and trees beyond a certain tree * depth */ static FilterSpec withTreeDepthLimit(long treeDepthLimit) { if (treeDepthLimit < 0) { throw new IllegalArgumentException( "treeDepthLimit cannot be negative: " + treeDepthLimit); //$NON-NLS-1$ } return new FilterSpec(ObjectTypes.ALL, -1, treeDepthLimit); } /** * A placeholder that indicates no filtering. */ public static final FilterSpec NO_FILTER = new FilterSpec(ObjectTypes.ALL, -1, -1); /** * Whether object type is allowed * * @param type * a Git object type, such as * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB} * @return whether this filter allows objects of the specified type * * @since 5.9 */ public boolean allowsType(int type) { return types.contains(type); } /** * Get blob size limit * * @return -1 if this filter does not filter blobs based on size, or a * non-negative integer representing the max size of blobs to allow */ public long getBlobLimit() { return blobLimit; } /** * Get tree depth limit * * @return -1 if this filter does not filter blobs and trees based on depth, * or a non-negative integer representing the max tree depth of * blobs and trees to fetch */ public long getTreeDepthLimit() { return treeDepthLimit; } /** * Whether this filter is a no-op * * @return true if this filter doesn't filter out anything */ public boolean isNoOp() { return types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit == -1; } /** * Get filter line describing this spec * * @return the filter line which describes this spec, e.g. "filter * blob:limit=42" */ @Nullable public String filterLine() { if (isNoOp()) { return null; } else if (types.equals(ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)) && blobLimit == -1 && treeDepthLimit == -1) { return OPTION_FILTER + " blob:none"; //$NON-NLS-1$ } else if (types.equals(ObjectTypes.ALL) && blobLimit >= 0 && treeDepthLimit == -1) { return OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$ } else if (types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit >= 0) { return OPTION_FILTER + " tree:" + treeDepthLimit; //$NON-NLS-1$ } else { throw new IllegalStateException(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5366 Content-Disposition: inline; filename="FtpChannel.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "5d334e67f7259aefb3fa9aa12eac61818d8ee0ec" /* * Copyright (C) 2018, Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.concurrent.TimeUnit; /** * An interface providing FTP operations over a {@link RemoteSession}. All * operations are supposed to throw {@link FtpException} for remote file system * errors and other IOExceptions on connection errors. * * @since 5.2 */ public interface FtpChannel { /** * An {@link Exception} for reporting SFTP errors. */ static class FtpException extends IOException { private static final long serialVersionUID = 7176525179280330876L; public static final int OK = 0; public static final int EOF = 1; public static final int NO_SUCH_FILE = 2; public static final int NO_PERMISSION = 3; public static final int UNSPECIFIED_FAILURE = 4; public static final int PROTOCOL_ERROR = 5; public static final int UNSUPPORTED = 8; private final int status; public FtpException(String message, int status) { super(message); this.status = status; } public FtpException(String message, int status, Throwable cause) { super(message, cause); this.status = status; } public int getStatus() { return status; } } /** * Connects the {@link FtpChannel} to the remote end. * * @param timeout * for establishing the FTP connection * @param unit * of the {@code timeout} * @throws IOException * if an IO error occurred */ void connect(int timeout, TimeUnit unit) throws IOException; /** * Disconnects and {@link FtpChannel}. */ void disconnect(); /** * Whether the FtpChannel is connected * * @return whether the {@link FtpChannel} is connected */ boolean isConnected(); /** * Changes the current remote directory. * * @param path * target directory * @throws IOException * if the operation could not be performed remotely */ void cd(String path) throws IOException; /** * Get current remote directory path * * @return the current remote directory path * @throws IOException * if an IO error occurred */ String pwd() throws IOException; /** * Simplified remote directory entry. */ interface DirEntry { String getFilename(); long getModifiedTime(); boolean isDirectory(); } /** * Lists contents of a remote directory * * @param path * of the directory to list * @return the directory entries * @throws IOException * if an IO error occurred */ Collection ls(String path) throws IOException; /** * Deletes a directory on the remote file system. The directory must be * empty. * * @param path * to delete * @throws IOException * if an IO error occurred */ void rmdir(String path) throws IOException; /** * Creates a directory on the remote file system. * * @param path * to create * @throws IOException * if an IO error occurred */ void mkdir(String path) throws IOException; /** * Obtain an {@link InputStream} to read the contents of a remote file. * * @param path * of the file to read * * @return the stream to read from * @throws IOException * if an IO error occurred */ InputStream get(String path) throws IOException; /** * Obtain an {@link OutputStream} to write to a remote file. If the file * exists already, it will be overwritten. * * @param path * of the file to read * * @return the stream to read from * @throws IOException * if an IO error occurred */ OutputStream put(String path) throws IOException; /** * Deletes a file on the remote file system. * * @param path * to delete * @throws IOException * if the file does not exist or could otherwise not be deleted */ void rm(String path) throws IOException; /** * Deletes a file on the remote file system. If the file does not exist, no * exception is thrown. * * @param path * to delete * @throws IOException * if the file exist but could not be deleted */ default void delete(String path) throws IOException { try { rm(path); } catch (FileNotFoundException e) { // Ignore; it's OK if the file doesn't exist } catch (FtpException f) { if (f.getStatus() == FtpException.NO_SUCH_FILE) { return; } throw f; } } /** * Renames a file on the remote file system. If {@code to} exists, it is * replaced by {@code from}. (POSIX rename() semantics) * * @param from * original name of the file * @param to * new name of the file * @throws IOException * if an IO error occurred * @see stdio.h: * rename() */ void rename(String from, String to) throws IOException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10729 Content-Disposition: inline; filename="GitProtocolConstants.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "7e5179d71d57123ce15f460892b577d9be392469" /* * Copyright (C) 2008, 2013 Google Inc. * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Wire constants for the native Git protocol. * * @since 3.2 */ public final class GitProtocolConstants { /** * Include tags if we are also including the referenced objects. * * @since 3.2 */ public static final String OPTION_INCLUDE_TAG = "include-tag"; //$NON-NLS-1$ /** * Multi-ACK support for improved negotiation. * * @since 3.2 */ public static final String OPTION_MULTI_ACK = "multi_ack"; //$NON-NLS-1$ /** * Multi-ACK detailed support for improved negotiation. * * @since 3.2 */ public static final String OPTION_MULTI_ACK_DETAILED = "multi_ack_detailed"; //$NON-NLS-1$ /** * The client supports packs with deltas but not their bases. * * @since 3.2 */ public static final String OPTION_THIN_PACK = "thin-pack"; //$NON-NLS-1$ /** * The client supports using the side-band for progress messages. * * @since 3.2 */ public static final String OPTION_SIDE_BAND = "side-band"; //$NON-NLS-1$ /** * The client supports using the 64K side-band for progress messages. * * @since 3.2 */ public static final String OPTION_SIDE_BAND_64K = "side-band-64k"; //$NON-NLS-1$ /** * The client supports packs with OFS deltas. * * @since 3.2 */ public static final String OPTION_OFS_DELTA = "ofs-delta"; //$NON-NLS-1$ /** * The client supports shallow fetches. * * @since 3.2 */ public static final String OPTION_SHALLOW = "shallow"; //$NON-NLS-1$ /** * The client wants the "deepen" command to be interpreted as relative to * the client's shallow commits. * * @since 5.0 */ public static final String OPTION_DEEPEN_RELATIVE = "deepen-relative"; //$NON-NLS-1$ /** * The client does not want progress messages and will ignore them. * * @since 3.2 */ public static final String OPTION_NO_PROGRESS = "no-progress"; //$NON-NLS-1$ /** * The client supports receiving a pack before it has sent "done". * * @since 3.2 */ public static final String OPTION_NO_DONE = "no-done"; //$NON-NLS-1$ /** * The client supports fetching objects at the tip of any ref, even if not * advertised. * * @since 3.2 */ public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = "allow-tip-sha1-in-want"; //$NON-NLS-1$ /** * The client supports fetching objects that are reachable from a tip of a * ref that is allowed to fetch. * * @since 4.1 */ public static final String OPTION_ALLOW_REACHABLE_SHA1_IN_WANT = "allow-reachable-sha1-in-want"; //$NON-NLS-1$ /** * Symbolic reference support for better negotiation. * * @since 3.6 */ public static final String OPTION_SYMREF = "symref"; //$NON-NLS-1$ /** * The client will send a push certificate. * * @since 4.0 */ public static final String OPTION_PUSH_CERT = "push-cert"; //$NON-NLS-1$ /** * The client specified a filter expression. * * @since 5.0 */ public static final String OPTION_FILTER = "filter"; //$NON-NLS-1$ /** * The client specified a want-ref expression. * * @since 5.1 */ public static final String OPTION_WANT_REF = "want-ref"; //$NON-NLS-1$ /** * The client requested that the whole response be multiplexed, with * each non-flush and non-delim pkt prefixed by a sideband designator. * * @since 5.5 */ public static final String OPTION_SIDEBAND_ALL = "sideband-all"; //$NON-NLS-1$ /** * The server waits for client to send "done" before sending any packs back. * * @since 5.13 */ public static final String OPTION_WAIT_FOR_DONE = "wait-for-done"; //$NON-NLS-1$ /** * The client supports atomic pushes. If this option is used, the server * will update all refs within one atomic transaction. * * @since 3.6 */ public static final String CAPABILITY_ATOMIC = "atomic"; //$NON-NLS-1$ /** * The client expects less noise, e.g. no progress. * * @since 4.0 */ public static final String CAPABILITY_QUIET = "quiet"; //$NON-NLS-1$ /** * The client expects a status report after the server processes the pack. * * @since 3.2 */ public static final String CAPABILITY_REPORT_STATUS = "report-status"; //$NON-NLS-1$ /** * The server supports deleting refs. * * @since 3.2 */ public static final String CAPABILITY_DELETE_REFS = "delete-refs"; //$NON-NLS-1$ /** * The server supports packs with OFS deltas. * * @since 3.2 */ public static final String CAPABILITY_OFS_DELTA = "ofs-delta"; //$NON-NLS-1$ /** * The client supports using the 64K side-band for progress messages. * * @since 3.2 */ public static final String CAPABILITY_SIDE_BAND_64K = "side-band-64k"; //$NON-NLS-1$ /** * The server allows recording of push certificates. * * @since 4.0 */ public static final String CAPABILITY_PUSH_CERT = "push-cert"; //$NON-NLS-1$ /** * Implementation name and version of the client or server. * * @since 4.0 */ public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$ /** * The server supports the receiving of push options. * * @since 4.5 */ public static final String CAPABILITY_PUSH_OPTIONS = "push-options"; //$NON-NLS-1$ /** * The server supports the client specifying ref names. * * @since 5.1 */ public static final String CAPABILITY_REF_IN_WANT = "ref-in-want"; //$NON-NLS-1$ /** * The server supports arbitrary options * * @since 5.2 */ public static final String CAPABILITY_SERVER_OPTION = "server-option"; //$NON-NLS-1$ /** * The server supports the receiving of shallow options. * * @since 6.3 */ public static final String CAPABILITY_SHALLOW = "shallow"; //$NON-NLS-1$ /** * Option for passing application-specific options to the server. * * @since 5.2 */ public static final String OPTION_SERVER_OPTION = "server-option"; //$NON-NLS-1$ /** * Option for passing client session ID to the server. * * @since 6.4 */ public static final String OPTION_SESSION_ID = "session-id"; //$NON-NLS-1$ /** * The server supports listing refs using protocol v2. * * @since 5.0 */ public static final String COMMAND_LS_REFS = "ls-refs"; //$NON-NLS-1$ /** * The server supports fetch using protocol v2. * * @since 5.0 */ public static final String COMMAND_FETCH = "fetch"; //$NON-NLS-1$ /** * The server supports the object-info capability. * * @since 5.13 */ public static final String COMMAND_OBJECT_INFO = "object-info"; //$NON-NLS-1$ /** * HTTP header to set by clients to request a specific git protocol version * in the HTTP transport. * * @since 5.11 */ public static final String PROTOCOL_HEADER = "Git-Protocol"; //$NON-NLS-1$ /** * Environment variable to set by clients to request a specific git protocol * in the file:// and ssh:// transports. * * @since 5.11 */ public static final String PROTOCOL_ENVIRONMENT_VARIABLE = "GIT_PROTOCOL"; //$NON-NLS-1$ /** * Protocol V2 ref advertisement attribute containing the peeled object id * for annotated tags. * * @since 5.11 */ public static final String REF_ATTR_PEELED = "peeled:"; //$NON-NLS-1$ /** * Protocol V2 ref advertisement attribute containing the name of the ref * for symbolic refs. * * @since 5.11 */ public static final String REF_ATTR_SYMREF_TARGET = "symref-target:"; //$NON-NLS-1$ /** * Protocol V2 acknowledgments section header. * * @since 5.11 */ public static final String SECTION_ACKNOWLEDGMENTS = "acknowledgments"; //$NON-NLS-1$ /** * Protocol V2 packfile section header. * * @since 5.11 */ public static final String SECTION_PACKFILE = "packfile"; //$NON-NLS-1$ /** * Protocol V2 shallow-info section header. * * @since 6.3 */ public static final String SECTION_SHALLOW_INFO = "shallow-info"; //$NON-NLS-1$ /** * Protocol announcement for protocol version 1. This is the same as V0, * except for this initial line. * * @since 5.11 */ public static final String VERSION_1 = "version 1"; //$NON-NLS-1$ /** * Protocol announcement for protocol version 2. * * @since 5.11 */ public static final String VERSION_2 = "version 2"; //$NON-NLS-1$ /** * Protocol request for protocol version 2. * * @since 5.11 */ public static final String VERSION_2_REQUEST = "version=2"; //$NON-NLS-1$ /** * The flush packet. * * @since 6.3 */ public static final String PACKET_FLUSH = "0000"; //$NON-NLS-1$ /** * An alias for {@link #PACKET_FLUSH}. "Flush" is the name used in the C git * documentation; the Java implementation calls this "end" in several * places. * * @since 6.3 */ public static final String PACKET_END = PACKET_FLUSH; /** * The delimiter packet in protocol V2. * * @since 6.3 */ public static final String PACKET_DELIM = "0001"; //$NON-NLS-1$ /** * A "deepen" packet beginning. * * @since 6.3 */ public static final String PACKET_DEEPEN = "deepen "; //$NON-NLS-1$ /** * A "deepen-not" packet beginning. * * @since 6.3 */ public static final String PACKET_DEEPEN_NOT = "deepen-not "; //$NON-NLS-1$ /** * A "deepen-since" packet beginning. * * @since 6.3 */ public static final String PACKET_DEEPEN_SINCE = "deepen-since "; //$NON-NLS-1$ /** * An "ACK" packet beginning. * * @since 6.3 */ public static final String PACKET_ACK = "ACK "; //$NON-NLS-1$ /** * A "done" packet beginning. * * @since 6.3 */ public static final String PACKET_DONE = "done"; //$NON-NLS-1$ /** * A "ERR" packet beginning. * * @since 6.3 */ public static final String PACKET_ERR = "ERR "; //$NON-NLS-1$ /** * A "have" packet beginning. * * @since 6.3 */ public static final String PACKET_HAVE = "have "; //$NON-NLS-1$ /** * A "shallow" packet beginning. * * @since 6.3 */ public static final String PACKET_SHALLOW = OPTION_SHALLOW + ' '; /** * A "shallow" packet beginning. * * @since 6.3 */ public static final String PACKET_UNSHALLOW = "unshallow "; //$NON-NLS-1$ /** * A "want" packet beginning. * * @since 6.3 */ public static final String PACKET_WANT = "want "; //$NON-NLS-1$ /** * A "want-ref" packet beginning. * * @since 6.3 */ public static final String PACKET_WANT_REF = OPTION_WANT_REF + ' '; enum MultiAck { OFF, CONTINUE, DETAILED; } private GitProtocolConstants() { } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3233 Content-Disposition: inline; filename="HMACSHA1NonceGenerator.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "655b4605e43caa7014e6955be3c3cddf4d2b2b0f" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PushCertificate.NonceStatus; /** * The nonce generator which was first introduced to git-core. * * @since 4.0 */ public class HMACSHA1NonceGenerator implements NonceGenerator { private Mac mac; /** * Constructor for HMACSHA1NonceGenerator. * * @param seed * seed the generator */ public HMACSHA1NonceGenerator(String seed) { try { byte[] keyBytes = seed.getBytes(ISO_8859_1); SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); //$NON-NLS-1$ mac = Mac.getInstance("HmacSHA1"); //$NON-NLS-1$ mac.init(signingKey); } catch (InvalidKeyException | NoSuchAlgorithmException e) { throw new IllegalStateException(e); } } @Override public synchronized String createNonce(Repository repo, long timestamp) { String input = repo.getIdentifier() + ":" + String.valueOf(timestamp); //$NON-NLS-1$ byte[] rawHmac = mac.doFinal(input.getBytes(UTF_8)); return Long.toString(timestamp) + "-" + toHex(rawHmac); //$NON-NLS-1$ } @Override public NonceStatus verify(String received, String sent, Repository db, boolean allowSlop, int slop) { if (received.isEmpty()) { return NonceStatus.MISSING; } else if (sent.isEmpty()) { return NonceStatus.UNSOLICITED; } else if (received.equals(sent)) { return NonceStatus.OK; } if (!allowSlop) { return NonceStatus.BAD; } /* nonce is concat(, "-", ) */ int idxSent = sent.indexOf('-'); int idxRecv = received.indexOf('-'); if (idxSent == -1 || idxRecv == -1) { return NonceStatus.BAD; } String signedStampStr = received.substring(0, idxRecv); String advertisedStampStr = sent.substring(0, idxSent); long signedStamp; long advertisedStamp; try { signedStamp = Long.parseLong(signedStampStr); advertisedStamp = Long.parseLong(advertisedStampStr); } catch (IllegalArgumentException e) { return NonceStatus.BAD; } // what we would have signed earlier String expect = createNonce(db, signedStamp); if (!expect.equals(received)) { return NonceStatus.BAD; } long nonceStampSlop = Math.abs(advertisedStamp - signedStamp); if (nonceStampSlop <= slop) { return NonceStatus.OK; } return NonceStatus.SLOP; } private static final String HEX = "0123456789ABCDEF"; //$NON-NLS-1$ private static String toHex(byte[] bytes) { StringBuilder builder = new StringBuilder(2 * bytes.length); for (byte b : bytes) { builder.append(HEX.charAt((b & 0xF0) >> 4)); builder.append(HEX.charAt(b & 0xF)); } return builder.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 14398 Content-Disposition: inline; filename="HttpAuthMethod.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "c2d8c1b73f1b7f92f2502436645eecd054ce9508" /* * Copyright (C) 2010, 2013, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION; import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE; import java.io.IOException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.util.Base64; import org.eclipse.jgit.util.GSSManagerFactory; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; /** * Support class to populate user authentication data on a connection. *

* Instances of an HttpAuthMethod are not thread-safe, as some implementations * may need to maintain per-connection state information. */ abstract class HttpAuthMethod { /** * Enum listing the http authentication method types supported by jgit. They * are sorted by priority order!!! */ public enum Type { NONE { @Override public HttpAuthMethod method(String hdr) { return None.INSTANCE; } @Override public String getSchemeName() { return "None"; //$NON-NLS-1$ } }, BASIC { @Override public HttpAuthMethod method(String hdr) { return new Basic(); } @Override public String getSchemeName() { return "Basic"; //$NON-NLS-1$ } }, DIGEST { @Override public HttpAuthMethod method(String hdr) { return new Digest(hdr); } @Override public String getSchemeName() { return "Digest"; //$NON-NLS-1$ } }, NEGOTIATE { @Override public HttpAuthMethod method(String hdr) { return new Negotiate(hdr); } @Override public String getSchemeName() { return "Negotiate"; //$NON-NLS-1$ } }; /** * Creates a HttpAuthMethod instance configured with the provided HTTP * WWW-Authenticate header. * * @param hdr the http header * @return a configured HttpAuthMethod instance */ public abstract HttpAuthMethod method(String hdr); /** * @return the name of the authentication scheme in the form to be used * in HTTP authentication headers as specified in RFC2617 and * RFC4559 */ public abstract String getSchemeName(); } static final String EMPTY_STRING = ""; //$NON-NLS-1$ static final String SCHEMA_NAME_SEPARATOR = " "; //$NON-NLS-1$ /** * Handle an authentication failure and possibly return a new response. * * @param conn * the connection that failed. * @param ignoreTypes * authentication types to be ignored. * @return new authentication method to try. */ static HttpAuthMethod scanResponse(final HttpConnection conn, Collection ignoreTypes) { final Map> headers = conn.getHeaderFields(); HttpAuthMethod authentication = Type.NONE.method(EMPTY_STRING); for (Entry> entry : headers.entrySet()) { if (HDR_WWW_AUTHENTICATE.equalsIgnoreCase(entry.getKey())) { if (entry.getValue() != null) { for (String value : entry.getValue()) { if (value != null && value.length() != 0) { final String[] valuePart = value.split( SCHEMA_NAME_SEPARATOR, 2); try { Type methodType = Type.valueOf( valuePart[0].toUpperCase(Locale.ROOT)); if ((ignoreTypes != null) && ignoreTypes.contains(methodType)) { continue; } if (authentication.getType().compareTo(methodType) >= 0) { continue; } final String param; if (valuePart.length == 1) param = EMPTY_STRING; else param = valuePart[1]; authentication = methodType .method(param); } catch (IllegalArgumentException e) { // This auth method is not supported } } } } break; } } return authentication; } protected final Type type; /** * Constructor for HttpAuthMethod. * * @param type * authentication method type */ protected HttpAuthMethod(Type type) { this.type = type; } /** * Update this method with the credentials from the URIish. * * @param uri * the URI used to create the connection. * @param credentialsProvider * the credentials provider, or null. If provided, * {@link URIish#getPass() credentials in the URI} are ignored. * * @return true if the authentication method is able to provide * authorization for the given URI */ boolean authorize(URIish uri, CredentialsProvider credentialsProvider) { String username; String password; if (credentialsProvider != null) { CredentialItem.Username u = new CredentialItem.Username(); CredentialItem.Password p = new CredentialItem.Password(); if (credentialsProvider.supports(u, p) && credentialsProvider.get(uri, u, p)) { username = u.getValue(); char[] v = p.getValue(); password = (v == null) ? null : new String(p.getValue()); p.clear(); } else return false; } else { username = uri.getUser(); password = uri.getPass(); } if (username != null) { authorize(username, password); return true; } return false; } /** * Update this method with the given username and password pair. * * @param user * username * @param pass * password */ abstract void authorize(String user, String pass); /** * Update connection properties based on this authentication method. * * @param conn * the connection to configure * @throws IOException * if an IO error occurred */ abstract void configureRequest(HttpConnection conn) throws IOException; /** * Gives the method type associated to this http auth method * * @return the method type */ public Type getType() { return type; } /** Performs no user authentication. */ private static class None extends HttpAuthMethod { static final None INSTANCE = new None(); public None() { super(Type.NONE); } @Override void authorize(String user, String pass) { // Do nothing when no authentication is enabled. } @Override void configureRequest(HttpConnection conn) throws IOException { // Do nothing when no authentication is enabled. } } /** Performs HTTP basic authentication (plaintext username/password). */ private static class Basic extends HttpAuthMethod { private String user; private String pass; public Basic() { super(Type.BASIC); } @Override void authorize(String username, String password) { this.user = username; this.pass = password; } @Override void configureRequest(HttpConnection conn) throws IOException { String ident = user + ":" + pass; //$NON-NLS-1$ String enc = Base64.encodeBytes(ident.getBytes(UTF_8)); conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName() + " " + enc); //$NON-NLS-1$ } } /** Performs HTTP digest authentication. */ private static class Digest extends HttpAuthMethod { private static final SecureRandom PRNG = new SecureRandom(); private final Map params; private int requestCount; private String user; private String pass; Digest(String hdr) { super(Type.DIGEST); params = parse(hdr); final String qop = params.get("qop"); //$NON-NLS-1$ if ("auth".equals(qop)) { //$NON-NLS-1$ final byte[] bin = new byte[8]; PRNG.nextBytes(bin); params.put("cnonce", Base64.encodeBytes(bin)); //$NON-NLS-1$ } } @Override void authorize(String username, String password) { this.user = username; this.pass = password; } @SuppressWarnings("boxing") @Override void configureRequest(HttpConnection conn) throws IOException { final Map r = new LinkedHashMap<>(); final String realm = params.get("realm"); //$NON-NLS-1$ final String nonce = params.get("nonce"); //$NON-NLS-1$ final String cnonce = params.get("cnonce"); //$NON-NLS-1$ final String uri = uri(conn.getURL()); final String qop = params.get("qop"); //$NON-NLS-1$ final String method = conn.getRequestMethod(); final String A1 = user + ":" + realm + ":" + pass; //$NON-NLS-1$ //$NON-NLS-2$ final String A2 = method + ":" + uri; //$NON-NLS-1$ r.put("username", user); //$NON-NLS-1$ r.put("realm", realm); //$NON-NLS-1$ r.put("nonce", nonce); //$NON-NLS-1$ r.put("uri", uri); //$NON-NLS-1$ final String response, nc; if ("auth".equals(qop)) { //$NON-NLS-1$ nc = String.format("%08x", ++requestCount); //$NON-NLS-1$ response = KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + qop + ":" //$NON-NLS-1$ + H(A2)); } else { nc = null; response = KD(H(A1), nonce + ":" + H(A2)); //$NON-NLS-1$ } r.put("response", response); //$NON-NLS-1$ if (params.containsKey("algorithm")) //$NON-NLS-1$ r.put("algorithm", "MD5"); //$NON-NLS-1$ //$NON-NLS-2$ if (cnonce != null && qop != null) r.put("cnonce", cnonce); //$NON-NLS-1$ if (params.containsKey("opaque")) //$NON-NLS-1$ r.put("opaque", params.get("opaque")); //$NON-NLS-1$ //$NON-NLS-2$ if (qop != null) r.put("qop", qop); //$NON-NLS-1$ if (nc != null) r.put("nc", nc); //$NON-NLS-1$ StringBuilder v = new StringBuilder(); for (Map.Entry e : r.entrySet()) { if (v.length() > 0) v.append(", "); //$NON-NLS-1$ v.append(e.getKey()); v.append('='); v.append('"'); v.append(e.getValue()); v.append('"'); } conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName() + " " + v); //$NON-NLS-1$ } private static String uri(URL u) { StringBuilder r = new StringBuilder(); r.append(u.getProtocol()); r.append("://"); //$NON-NLS-1$ r.append(u.getHost()); if (0 < u.getPort()) { if (u.getPort() == 80 && "http".equals(u.getProtocol())) { //$NON-NLS-1$ /* nothing */ } else if (u.getPort() == 443 && "https".equals(u.getProtocol())) { //$NON-NLS-1$ /* nothing */ } else { r.append(':').append(u.getPort()); } } r.append(u.getPath()); if (u.getQuery() != null) r.append('?').append(u.getQuery()); return r.toString(); } private static String H(String data) { MessageDigest md = newMD5(); md.update(data.getBytes(UTF_8)); return LHEX(md.digest()); } private static String KD(String secret, String data) { MessageDigest md = newMD5(); md.update(secret.getBytes(UTF_8)); md.update((byte) ':'); md.update(data.getBytes(UTF_8)); return LHEX(md.digest()); } private static MessageDigest newMD5() { try { return MessageDigest.getInstance("MD5"); //$NON-NLS-1$ } catch (NoSuchAlgorithmException e) { throw new RuntimeException("No MD5 available", e); //$NON-NLS-1$ } } private static final char[] LHEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // 'a', 'b', 'c', 'd', 'e', 'f' }; private static String LHEX(byte[] bin) { StringBuilder r = new StringBuilder(bin.length * 2); for (byte b : bin) { r.append(LHEX[(b >>> 4) & 0x0f]); r.append(LHEX[b & 0x0f]); } return r.toString(); } private static Map parse(String auth) { Map p = new HashMap<>(); int next = 0; while (next < auth.length()) { if (next < auth.length() && auth.charAt(next) == ',') { next++; } while (next < auth.length() && Character.isWhitespace(auth.charAt(next))) { next++; } int eq = auth.indexOf('=', next); if (eq < 0 || eq + 1 == auth.length()) { return Collections.emptyMap(); } final String name = auth.substring(next, eq); final String value; if (auth.charAt(eq + 1) == '"') { int dq = auth.indexOf('"', eq + 2); if (dq < 0) { return Collections.emptyMap(); } value = auth.substring(eq + 2, dq); next = dq + 1; } else { int space = auth.indexOf(' ', eq + 1); int comma = auth.indexOf(',', eq + 1); if (space < 0) space = auth.length(); if (comma < 0) comma = auth.length(); final int e = Math.min(space, comma); value = auth.substring(eq + 1, e); next = e + 1; } p.put(name, value); } return p; } } private static class Negotiate extends HttpAuthMethod { private static final GSSManagerFactory GSS_MANAGER_FACTORY = GSSManagerFactory .detect(); private static final Oid OID; static { try { // OID for SPNEGO OID = new Oid("1.3.6.1.5.5.2"); //$NON-NLS-1$ } catch (GSSException e) { throw new Error("Cannot create NEGOTIATE oid.", e); //$NON-NLS-1$ } } private final byte[] prevToken; public Negotiate(String hdr) { super(Type.NEGOTIATE); prevToken = Base64.decode(hdr); } @Override void authorize(String user, String pass) { // not used } @Override void configureRequest(HttpConnection conn) throws IOException { GSSManager gssManager = GSS_MANAGER_FACTORY.newInstance(conn .getURL()); String host = conn.getURL().getHost(); String peerName = "HTTP@" + host.toLowerCase(Locale.ROOT); //$NON-NLS-1$ try { GSSName gssName = gssManager.createName(peerName, GSSName.NT_HOSTBASED_SERVICE); GSSContext context = gssManager.createContext(gssName, OID, null, GSSContext.DEFAULT_LIFETIME); // Respect delegation policy in HTTP/SPNEGO. context.requestCredDeleg(true); byte[] token = context.initSecContext(prevToken, 0, prevToken.length); conn.setRequestProperty(HDR_AUTHORIZATION, getType().getSchemeName() + " " + Base64.encodeBytes(token)); //$NON-NLS-1$ } catch (GSSException e) { throw new IOException(e); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 15478 Content-Disposition: inline; filename="HttpConfig.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "f10b7bf452a29995ef343a975b720b5e1330add2" /* * Copyright (C) 2008, 2010, Google Inc. * Copyright (C) 2017, Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Supplier; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A representation of the "http.*" config values in a git * {@link org.eclipse.jgit.lib.Config}. git provides for setting values for * specific URLs through "http.<url>.*" subsections. git always considers * only the initial original URL for such settings, not any redirected URL. * * @since 4.9 */ public class HttpConfig { private static final Logger LOG = LoggerFactory.getLogger(HttpConfig.class); private static final String FTP = "ftp"; //$NON-NLS-1$ /** git config section key for http settings. */ public static final String HTTP = "http"; //$NON-NLS-1$ /** git config key for the "followRedirects" setting. */ public static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$ /** git config key for the "maxRedirects" setting. */ public static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$ /** git config key for the "postBuffer" setting. */ public static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$ /** git config key for the "sslVerify" setting. */ public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$ /** * git config key for the "userAgent" setting. * * @since 5.10 */ public static final String USER_AGENT = "userAgent"; //$NON-NLS-1$ /** * git config key for the "extraHeader" setting. * * @since 5.10 */ public static final String EXTRA_HEADER = "extraHeader"; //$NON-NLS-1$ /** * git config key for the "cookieFile" setting. * * @since 5.4 */ public static final String COOKIE_FILE_KEY = "cookieFile"; //$NON-NLS-1$ /** * git config key for the "saveCookies" setting. * * @since 5.4 */ public static final String SAVE_COOKIES_KEY = "saveCookies"; //$NON-NLS-1$ /** * Custom JGit config key which holds the maximum number of cookie files to * keep in the cache. * * @since 5.4 */ public static final String COOKIE_FILE_CACHE_LIMIT_KEY = "cookieFileCacheLimit"; //$NON-NLS-1$ private static final int DEFAULT_COOKIE_FILE_CACHE_LIMIT = 10; private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$ private static final int DEFAULT_MAX_REDIRECTS = 5; private static final int MAX_REDIRECTS = new Supplier() { @Override public Integer get() { String rawValue = SystemReader.getInstance() .getProperty(MAX_REDIRECT_SYSTEM_PROPERTY); Integer value = Integer.valueOf(DEFAULT_MAX_REDIRECTS); if (rawValue != null) { try { value = Integer.valueOf(Integer.parseUnsignedInt(rawValue)); } catch (NumberFormatException e) { LOG.warn(MessageFormat.format( JGitText.get().invalidSystemProperty, MAX_REDIRECT_SYSTEM_PROPERTY, rawValue, value)); } } return value; } }.get().intValue(); private static final String ENV_HTTP_USER_AGENT = "GIT_HTTP_USER_AGENT"; //$NON-NLS-1$ /** * Config values for http.followRedirect. */ public enum HttpRedirectMode implements Config.ConfigEnum { /** Always follow redirects (up to the http.maxRedirects limit). */ TRUE("true"), //$NON-NLS-1$ /** * Only follow redirects on the initial GET request. This is the * default. */ INITIAL("initial"), //$NON-NLS-1$ /** Never follow redirects. */ FALSE("false"); //$NON-NLS-1$ private final String configValue; private HttpRedirectMode(String configValue) { this.configValue = configValue; } @Override public String toConfigValue() { return configValue; } @Override public boolean matchConfigValue(String s) { return configValue.equals(s); } } private int postBuffer; private boolean sslVerify; private HttpRedirectMode followRedirects; private int maxRedirects; private String userAgent; private List extraHeaders; private String cookieFile; private boolean saveCookies; private int cookieFileCacheLimit; /** * Get the "http.postBuffer" setting * * @return the value of the "http.postBuffer" setting */ public int getPostBuffer() { return postBuffer; } /** * Get the "http.sslVerify" setting * * @return the value of the "http.sslVerify" setting */ public boolean isSslVerify() { return sslVerify; } /** * Get the "http.followRedirects" setting * * @return the value of the "http.followRedirects" setting */ public HttpRedirectMode getFollowRedirects() { return followRedirects; } /** * Get the "http.maxRedirects" setting * * @return the value of the "http.maxRedirects" setting */ public int getMaxRedirects() { return maxRedirects; } /** * Get the "http.userAgent" setting * * @return the value of the "http.userAgent" setting * @since 5.10 */ public String getUserAgent() { return userAgent; } /** * Get the "http.extraHeader" setting * * @return the value of the "http.extraHeader" setting * @since 5.10 */ @NonNull public List getExtraHeaders() { return extraHeaders == null ? Collections.emptyList() : extraHeaders; } /** * Get the "http.cookieFile" setting * * @return the value of the "http.cookieFile" setting * * @since 5.4 */ public String getCookieFile() { return cookieFile; } /** * Get the "http.saveCookies" setting * * @return the value of the "http.saveCookies" setting * * @since 5.4 */ public boolean getSaveCookies() { return saveCookies; } /** * Get the "http.cookieFileCacheLimit" setting (gives the maximum number of * cookie files to keep in the LRU cache) * * @return the value of the "http.cookieFileCacheLimit" setting * * @since 5.4 */ public int getCookieFileCacheLimit() { return cookieFileCacheLimit; } /** * Creates a new {@link org.eclipse.jgit.transport.HttpConfig} tailored to * the given {@link org.eclipse.jgit.transport.URIish}. * * @param config * to read the {@link org.eclipse.jgit.transport.HttpConfig} from * @param uri * to get the configuration values for */ public HttpConfig(Config config, URIish uri) { init(config, uri); } /** * Creates a {@link org.eclipse.jgit.transport.HttpConfig} that reads values * solely from the user config. * * @param uri * to get the configuration values for */ public HttpConfig(URIish uri) { StoredConfig userConfig = null; try { userConfig = SystemReader.getInstance().getUserConfig(); } catch (IOException | ConfigInvalidException e) { // Log it and then work with default values. LOG.error(e.getMessage(), e); init(new Config(), uri); return; } init(userConfig, uri); } private void init(Config config, URIish uri) { // Set defaults from the section first int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY, 1 * 1024 * 1024); boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true); HttpRedirectMode followRedirectsMode = config.getEnum(HTTP, null, FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL); int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY, MAX_REDIRECTS); if (redirectLimit < 0) { redirectLimit = MAX_REDIRECTS; } String agent = config.getString(HTTP, null, USER_AGENT); if (agent != null) { agent = UserAgent.clean(agent); } userAgent = agent; String[] headers = config.getStringList(HTTP, null, EXTRA_HEADER); // https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpextraHeader // "an empty value will reset the extra headers to the empty list." int start = findLastEmpty(headers) + 1; if (start > 0) { headers = Arrays.copyOfRange(headers, start, headers.length); } extraHeaders = Arrays.asList(headers); cookieFile = config.getString(HTTP, null, COOKIE_FILE_KEY); saveCookies = config.getBoolean(HTTP, SAVE_COOKIES_KEY, false); cookieFileCacheLimit = config.getInt(HTTP, COOKIE_FILE_CACHE_LIMIT_KEY, DEFAULT_COOKIE_FILE_CACHE_LIMIT); String match = findMatch(config.getSubsections(HTTP), uri); if (match != null) { // Override with more specific items postBufferSize = config.getInt(HTTP, match, POST_BUFFER_KEY, postBufferSize); sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY, sslVerifyFlag); followRedirectsMode = config.getEnum(HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode); int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY, redirectLimit); if (newMaxRedirects >= 0) { redirectLimit = newMaxRedirects; } String uriSpecificUserAgent = config.getString(HTTP, match, USER_AGENT); if (uriSpecificUserAgent != null) { userAgent = UserAgent.clean(uriSpecificUserAgent); } String[] uriSpecificExtraHeaders = config.getStringList(HTTP, match, EXTRA_HEADER); if (uriSpecificExtraHeaders.length > 0) { start = findLastEmpty(uriSpecificExtraHeaders) + 1; if (start > 0) { uriSpecificExtraHeaders = Arrays.copyOfRange( uriSpecificExtraHeaders, start, uriSpecificExtraHeaders.length); } extraHeaders = Arrays.asList(uriSpecificExtraHeaders); } String urlSpecificCookieFile = config.getString(HTTP, match, COOKIE_FILE_KEY); if (urlSpecificCookieFile != null) { cookieFile = urlSpecificCookieFile; } saveCookies = config.getBoolean(HTTP, match, SAVE_COOKIES_KEY, saveCookies); } // Environment overrides config agent = SystemReader.getInstance().getenv(ENV_HTTP_USER_AGENT); if (!StringUtils.isEmptyOrNull(agent)) { userAgent = UserAgent.clean(agent); } postBuffer = postBufferSize; sslVerify = sslVerifyFlag; followRedirects = followRedirectsMode; maxRedirects = redirectLimit; } private int findLastEmpty(String[] values) { for (int i = values.length - 1; i >= 0; i--) { if (values[i] == null) { return i; } } return -1; } /** * Determines the best match from a set of subsection names (representing * prefix URLs) for the given {@link URIish}. * * @param names * to match against the {@code uri} * @param uri * to find a match for * @return the best matching subsection name, or {@code null} if no * subsection matches */ private String findMatch(Set names, URIish uri) { String bestMatch = null; int bestMatchLength = -1; boolean withUser = false; String uPath = uri.getPath(); boolean hasPath = !StringUtils.isEmptyOrNull(uPath); if (hasPath) { uPath = normalize(uPath); if (uPath == null) { // Normalization failed; warning was logged. return null; } } for (String s : names) { try { URIish candidate = new URIish(s); // Scheme and host must match case-insensitively if (!compare(uri.getScheme(), candidate.getScheme()) || !compare(uri.getHost(), candidate.getHost())) { continue; } // Ports must match after default ports have been substituted if (defaultedPort(uri.getPort(), uri.getScheme()) != defaultedPort(candidate.getPort(), candidate.getScheme())) { continue; } // User: if present in candidate, must match boolean hasUser = false; if (candidate.getUser() != null) { if (!candidate.getUser().equals(uri.getUser())) { continue; } hasUser = true; } // Path: prefix match, longer is better String cPath = candidate.getPath(); int matchLength = -1; if (StringUtils.isEmptyOrNull(cPath)) { matchLength = 0; } else { if (!hasPath) { continue; } // Paths can match only on segments matchLength = segmentCompare(uPath, cPath); if (matchLength < 0) { continue; } } // A longer path match is always preferred even over a user // match. If the path matches are equal, a match with user wins // over a match without user. if (matchLength > bestMatchLength || (!withUser && hasUser && matchLength >= 0 && matchLength == bestMatchLength)) { bestMatch = s; bestMatchLength = matchLength; withUser = hasUser; } } catch (URISyntaxException e) { LOG.warn(MessageFormat .format(JGitText.get().httpConfigInvalidURL, s)); } } return bestMatch; } private boolean compare(String a, String b) { if (a == null) { return b == null; } return a.equalsIgnoreCase(b); } private int defaultedPort(int port, String scheme) { if (port >= 0) { return port; } if (FTP.equalsIgnoreCase(scheme)) { return 21; } else if (HTTP.equalsIgnoreCase(scheme)) { return 80; } else { return 443; // https } } static int segmentCompare(String uriPath, String m) { // Precondition: !uriPath.isEmpty() && !m.isEmpty(),and u must already // be normalized String matchPath = normalize(m); if (matchPath == null || !uriPath.startsWith(matchPath)) { return -1; } // We can match only on a segment boundary: either both paths are equal, // or if matchPath does not end in '/', there is a '/' in uriPath right // after the match. int uLength = uriPath.length(); int mLength = matchPath.length(); if (mLength == uLength || matchPath.charAt(mLength - 1) == '/' || (mLength < uLength && uriPath.charAt(mLength) == '/')) { return mLength; } return -1; } static String normalize(String path) { // C-git resolves . and .. segments int i = 0; int length = path.length(); StringBuilder builder = new StringBuilder(length); builder.append('/'); if (length > 0 && path.charAt(0) == '/') { i = 1; } while (i < length) { int slash = path.indexOf('/', i); if (slash < 0) { slash = length; } if (slash == i || (slash == i + 1 && path.charAt(i) == '.')) { // Skip /. or also double slashes } else if (slash == i + 2 && path.charAt(i) == '.' && path.charAt(i + 1) == '.') { // Remove previous segment if we have "/.." int l = builder.length() - 2; // Skip terminating slash. while (l >= 0 && builder.charAt(l) != '/') { l--; } if (l < 0) { LOG.warn(MessageFormat.format( JGitText.get().httpConfigCannotNormalizeURL, path)); return null; } builder.setLength(l + 1); } else { // Include the slash, if any builder.append(path, i, Math.min(length, slash + 1)); } i = slash + 1; } if (builder.length() > 1 && builder.charAt(builder.length() - 1) == '/' && length > 0 && path.charAt(length - 1) != '/') { // . or .. normalization left a trailing slash when the original // path had none at the end builder.setLength(builder.length() - 1); } return builder.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2370 Content-Disposition: inline; filename="HttpTransport.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "febeb3ce9c3321a196ea7d9ed47a7cde6636e754" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2009, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.http.HttpConnectionFactory; import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; /** * The base class for transports that use HTTP as underlying protocol. This class * allows customizing HTTP connection settings. */ public abstract class HttpTransport extends Transport { /** * factory for creating HTTP connections * * @since 3.3 */ protected static volatile HttpConnectionFactory connectionFactory = new JDKHttpConnectionFactory(); /** * Get the {@link org.eclipse.jgit.transport.http.HttpConnectionFactory} * used to create new connections * * @return the {@link org.eclipse.jgit.transport.http.HttpConnectionFactory} * used to create new connections * @since 3.3 */ public static HttpConnectionFactory getConnectionFactory() { return connectionFactory; } /** * Set the {@link org.eclipse.jgit.transport.http.HttpConnectionFactory} to * be used to create new connections * * @param cf * connection factory * @since 3.3 */ public static void setConnectionFactory(HttpConnectionFactory cf) { connectionFactory = cf; } /** * Create a new transport instance. * * @param local * the repository this instance will fetch into, or push out of. * This must be the repository passed to * {@link #open(Repository, URIish)}. * @param uri * the URI used to access the remote repository. This must be the * URI passed to {@link #open(Repository, URIish)}. */ protected HttpTransport(Repository local, URIish uri) { super(local, uri); } /** * Create a minimal HTTP transport instance not tied to a single repository. * * @param uri a {@link org.eclipse.jgit.transport.URIish} object. */ protected HttpTransport(URIish uri) { super(uri); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 993 Content-Disposition: inline; filename="InsecureCipherFactory.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "f9a94bfc54ae114472ff774070822f23a68fbfdf" /* * Copyright (C) 2016, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; /** * DO NOT USE Factory to create any cipher. *

* This is a hack for {@link WalkEncryption} to create any cipher configured by * the end-user. Using this class allows JGit to violate ErrorProne's security * recommendations (InsecureCryptoUsage), which is not secure. */ class InsecureCipherFactory { static Cipher create(String algo) throws NoSuchAlgorithmException, NoSuchPaddingException { return Cipher.getInstance(algo); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3350 Content-Disposition: inline; filename="InternalFetchConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "05736e1107c506b13b262a2f1a25f0895ea0b351" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; class InternalFetchConnection extends BasePackFetchConnection { private Thread worker; /** * Constructor for InternalFetchConnection. * * @param transport * a {@link org.eclipse.jgit.transport.PackTransport} * @param uploadPackFactory * a * {@link org.eclipse.jgit.transport.resolver.UploadPackFactory} * @param req * request * @param remote * the remote {@link org.eclipse.jgit.lib.Repository} * @throws org.eclipse.jgit.errors.TransportException * if any. */ public InternalFetchConnection(PackTransport transport, final UploadPackFactory uploadPackFactory, final C req, final Repository remote) throws TransportException { super(transport); final PipedInputStream in_r; final PipedOutputStream in_w; final PipedInputStream out_r; final PipedOutputStream out_w; try { in_r = new PipedInputStream(); in_w = new PipedOutputStream(in_r); out_r = new PipedInputStream() { // The client (BasePackFetchConnection) can write // a huge burst before it reads again. We need to // force the buffer to be big enough, otherwise it // will deadlock both threads. { buffer = new byte[MIN_CLIENT_BUFFER]; } }; out_w = new PipedOutputStream(out_r); } catch (IOException err) { remote.close(); throw new TransportException(uri, JGitText.get().cannotConnectPipes, err); } worker = new Thread("JGit-Upload-Pack") { //$NON-NLS-1$ @Override @SuppressWarnings("CatchAndPrintStackTrace") public void run() { try { final UploadPack rp = uploadPackFactory.create(req, remote); rp.upload(out_r, in_w, null); } catch (ServiceNotEnabledException | ServiceNotAuthorizedException e) { // Ignored. Client cannot use this repository. } catch (IOException | RuntimeException err) { // Client side of the pipes should report the problem. err.printStackTrace(); } finally { try { out_r.close(); } catch (IOException e2) { // Ignore close failure, we probably crashed above. } try { in_w.close(); } catch (IOException e2) { // Ignore close failure, we probably crashed above. } remote.close(); } } }; worker.start(); init(in_r, out_w); readAdvertisedRefs(); } @Override public void close() { super.close(); try { if (worker != null) { worker.join(); } } catch (InterruptedException ie) { // Stop waiting and return anyway. } finally { worker = null; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1116 Content-Disposition: inline; filename="InternalHttpServerGlue.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "357528bab951a752b8af68eab97a37d3a171cd77" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Internal API to assist {@code org.eclipse.jgit.http.server}. *

* Do not call. * * @since 4.0 */ public class InternalHttpServerGlue { /** * Apply a default user agent for a request. * * @param up * current UploadPack instance. * @param agent * user agent string from the HTTP headers. */ public static void setPeerUserAgent(UploadPack up, String agent) { up.userAgent = agent; } /** * Apply a default user agent for a request. * * @param rp * current ReceivePack instance. * @param agent * user agent string from the HTTP headers. */ public static void setPeerUserAgent(ReceivePack rp, String agent) { rp.userAgent = agent; } private InternalHttpServerGlue() { } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3262 Content-Disposition: inline; filename="InternalPushConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "a23fdc94ad4fa8ad221eba1d7b53651f98394030" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.UncheckedIOException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; class InternalPushConnection extends BasePackPushConnection { private Thread worker; /** * Constructor for InternalPushConnection. * * @param transport * a {@link org.eclipse.jgit.transport.PackTransport} * @param receivePackFactory * a * {@link org.eclipse.jgit.transport.resolver.ReceivePackFactory} * @param req * a request * @param remote * the {@link org.eclipse.jgit.lib.Repository} * @throws org.eclipse.jgit.errors.TransportException * if any. */ public InternalPushConnection(PackTransport transport, final ReceivePackFactory receivePackFactory, final C req, final Repository remote) throws TransportException { super(transport); final PipedInputStream in_r; final PipedOutputStream in_w; final PipedInputStream out_r; final PipedOutputStream out_w; try { in_r = new PipedInputStream(); in_w = new PipedOutputStream(in_r); out_r = new PipedInputStream(); out_w = new PipedOutputStream(out_r); } catch (IOException err) { remote.close(); throw new TransportException(uri, JGitText.get().cannotConnectPipes, err); } worker = new Thread("JGit-Receive-Pack") { //$NON-NLS-1$ @Override public void run() { try { final ReceivePack rp = receivePackFactory.create(req, remote); rp.receive(out_r, in_w, System.err); } catch (ServiceNotEnabledException | ServiceNotAuthorizedException e) { // Ignored. Client cannot use this repository. } catch (IOException e) { // Since the InternalPushConnection is used in tests, we // want to avoid hiding exceptions because they can point to // programming errors on the server side. By rethrowing, the // default handler will dump it to stderr. throw new UncheckedIOException(e); } finally { try { out_r.close(); } catch (IOException e2) { // Ignore close failure, we probably crashed above. } try { in_w.close(); } catch (IOException e2) { // Ignore close failure, we probably crashed above. } remote.close(); } } }; worker.start(); init(in_r, out_w); readAdvertisedRefs(); } @Override public void close() { super.close(); if (worker != null) { try { worker.join(); } catch (InterruptedException ie) { // Stop waiting and return anyway. } finally { worker = null; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5320 Content-Disposition: inline; filename="LsRefsV2Request.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "008f1d9b2020825b81c9cbfe1df0a57fc8670085" /* * Copyright (C) 2018, Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; /** * ls-refs protocol v2 request. * *

* This is used as an input to {@link ProtocolV2Hook}. * * @since 5.1 */ public final class LsRefsV2Request { private final List refPrefixes; private final boolean symrefs; private final boolean peel; @Nullable private final String agent; private final String clientSID; @NonNull private final List serverOptions; private LsRefsV2Request(List refPrefixes, boolean symrefs, boolean peel, @Nullable String agent, @NonNull List serverOptions, @Nullable String clientSID) { this.refPrefixes = refPrefixes; this.symrefs = symrefs; this.peel = peel; this.agent = agent; this.serverOptions = requireNonNull(serverOptions); this.clientSID = clientSID; } /** * Get ref prefixes * * @return ref prefixes that the client requested. */ public List getRefPrefixes() { return refPrefixes; } /** * Whether the client requests symbolic references * * @return true if the client requests symbolic references. */ public boolean getSymrefs() { return symrefs; } /** * Whether the client requests tags to be peeled * * @return true if the client requests tags to be peeled. */ public boolean getPeel() { return peel; } /** * Get agent reported by the client * * @return agent as reported by the client * * @since 5.2 */ @Nullable public String getAgent() { return agent; } /** * Get session-id reported by the client * * @return session-id as reported by the client * * @since 6.4 */ @Nullable public String getClientSID() { return clientSID; } /** * Get application-specific options provided by the client using * --server-option. *

* It returns just the content, without the "server-option=" prefix. E.g. a * request with server-option=A and server-option=B lines returns the list * [A, B]. * * @return application-specific options from the client as an unmodifiable * list * * @since 5.2 */ @NonNull public List getServerOptions() { return serverOptions; } /** * Create builder * * @return A builder of {@link LsRefsV2Request}. */ public static Builder builder() { return new Builder(); } /** A builder for {@link LsRefsV2Request}. */ public static final class Builder { private List refPrefixes = Collections.emptyList(); private boolean symrefs; private boolean peel; private final List serverOptions = new ArrayList<>(); private String agent; private String clientSID; private Builder() { } /** * Set ref prefixes * * @param value * ref prefix values * @return the Builder */ public Builder setRefPrefixes(List value) { refPrefixes = value; return this; } /** * Set symrefs * * @param value * of symrefs * @return the Builder */ public Builder setSymrefs(boolean value) { symrefs = value; return this; } /** * Set whether to peel tags * * @param value * of peel * @return the Builder */ public Builder setPeel(boolean value) { peel = value; return this; } /** * Records an application-specific option supplied in a server-option * line, for later retrieval with * {@link LsRefsV2Request#getServerOptions}. * * @param value * the client-supplied server-option capability, without * leading "server-option=". * @return this builder * @since 5.2 */ public Builder addServerOption(@NonNull String value) { serverOptions.add(value); return this; } /** * Value of an agent line received after the command and before the * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0". * * @param value * the client-supplied agent capability, without leading * "agent=" * @return this builder * * @since 5.2 */ public Builder setAgent(@Nullable String value) { agent = value; return this; } /** * Value of a session-id line received after the command and before the * arguments. E.g. "session-id=a.b.c" should set "a.b.c". * * @param value * the client-supplied session-id capability, without leading * "session-id=" * @return this builder * * @since 6.4 */ public Builder setClientSID(@Nullable String value) { clientSID = value; return this; } /** * Builds the request * * @return LsRefsV2Request the request */ public LsRefsV2Request build() { return new LsRefsV2Request( Collections.unmodifiableList(refPrefixes), symrefs, peel, agent, Collections.unmodifiableList(serverOptions), clientSID); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8518 Content-Disposition: inline; filename="NetRC.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "99937f6ff1c37b4a85d7e5464b223a1badc897ad" /* * Copyright (C) 2014, Alexey Kuznetsov * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.time.Instant; import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.util.FS; /** * NetRC file parser. * * @since 3.5 */ public class NetRC { static final Pattern NETRC = Pattern.compile("(\\S+)"); //$NON-NLS-1$ /** * 'default' netrc entry. This is the same as machine name except that * default matches any name. There can be only one default token, and it * must be after all machine tokens. */ static final String DEFAULT_ENTRY = "default"; //$NON-NLS-1$ /** * .netrc file entry */ public static class NetRCEntry { /** * login netrc entry */ public String login; /** * password netrc entry */ public char[] password; /** * machine netrc entry */ public String machine; /** * account netrc entry */ public String account; /** * macdef netrc entry. Defines a macro. This token functions like the * ftp macdef command functions. A macro is defined with the specified * name; its contents begins with the next .netrc line and continues * until a null line (consecutive new-line characters) is encountered. * If a macro named init is defined, it is automatically executed as the * last step in the auto-login process. */ public String macdef; /** * macro script body of macdef entry. */ public String macbody; /** * Default constructor */ public NetRCEntry() { } boolean complete() { return login != null && password != null && machine != null; } } private File netrc; private Instant lastModified; private Map hosts = new HashMap<>(); private static final TreeMap STATE = new TreeMap<>() { private static final long serialVersionUID = -4285910831814853334L; { put("machine", State.MACHINE); //$NON-NLS-1$ put("login", State.LOGIN); //$NON-NLS-1$ put("password", State.PASSWORD); //$NON-NLS-1$ put(DEFAULT_ENTRY, State.DEFAULT); put("account", State.ACCOUNT); //$NON-NLS-1$ put("macdef", State.MACDEF); //$NON-NLS-1$ } }; enum State { COMMAND, MACHINE, LOGIN, PASSWORD, DEFAULT, ACCOUNT, MACDEF } /** *

Constructor for NetRC.

*/ public NetRC() { netrc = getDefaultFile(); if (netrc != null) parse(); } /** *

Constructor for NetRC.

* * @param netrc * the .netrc file */ public NetRC(File netrc) { this.netrc = netrc; parse(); } private static File getDefaultFile() { File home = FS.DETECTED.userHome(); File netrc = new File(home, ".netrc"); //$NON-NLS-1$ if (netrc.exists()) return netrc; netrc = new File(home, "_netrc"); //$NON-NLS-1$ if (netrc.exists()) return netrc; return null; } /** * Get entry by host name * * @param host * the host name * @return entry associated with host name or null */ public NetRCEntry getEntry(String host) { if (netrc == null) return null; if (!this.lastModified .equals(FS.DETECTED.lastModifiedInstant(this.netrc))) { parse(); } NetRCEntry entry = this.hosts.get(host); if (entry == null) entry = this.hosts.get(DEFAULT_ENTRY); return entry; } /** * Get all entries collected from .netrc file * * @return all entries collected from .netrc file */ public Collection getEntries() { return hosts.values(); } private void parse() { this.hosts.clear(); this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc); try (BufferedReader r = new BufferedReader( new InputStreamReader(new FileInputStream(netrc), UTF_8))) { String line = null; NetRCEntry entry = new NetRCEntry(); State state = State.COMMAND; String macbody = ""; //$NON-NLS-1$ Matcher matcher = NETRC.matcher(""); //$NON-NLS-1$ while ((line = r.readLine()) != null) { // reading macbody if (entry.macdef != null && entry.macbody == null) { if (line.length() == 0) { entry.macbody = macbody; macbody = ""; //$NON-NLS-1$ continue; } macbody += line + "\n"; //$NON-NLS-1$; continue; } matcher.reset(line); while (matcher.find()) { String command = matcher.group().toLowerCase(Locale.ROOT); if (command.startsWith("#")) { //$NON-NLS-1$ matcher.reset(""); //$NON-NLS-1$ continue; } state = STATE.get(command); if (state == null) state = State.COMMAND; switch (state) { case COMMAND: break; case ACCOUNT: if (entry.account != null && entry.complete()) { hosts.put(entry.machine, entry); entry = new NetRCEntry(); } if (matcher.find()) entry.account = matcher.group(); state = State.COMMAND; break; case LOGIN: if (entry.login != null && entry.complete()) { hosts.put(entry.machine, entry); entry = new NetRCEntry(); } if (matcher.find()) entry.login = matcher.group(); state = State.COMMAND; break; case PASSWORD: if (entry.password != null && entry.complete()) { hosts.put(entry.machine, entry); entry = new NetRCEntry(); } if (matcher.find()) entry.password = matcher.group().toCharArray(); state = State.COMMAND; break; case DEFAULT: if (entry.machine != null && entry.complete()) { hosts.put(entry.machine, entry); entry = new NetRCEntry(); } entry.machine = DEFAULT_ENTRY; state = State.COMMAND; break; case MACDEF: if (entry.macdef != null && entry.complete()) { hosts.put(entry.machine, entry); entry = new NetRCEntry(); } if (matcher.find()) entry.macdef = matcher.group(); state = State.COMMAND; break; case MACHINE: if (entry.machine != null && entry.complete()) { hosts.put(entry.machine, entry); entry = new NetRCEntry(); } if (matcher.find()) entry.machine = matcher.group(); state = State.COMMAND; break; } } } // reading macbody on EOF if (entry.macdef != null && entry.macbody == null) entry.macbody = macbody; if (entry.complete()) hosts.put(entry.machine, entry); } catch (IOException e) { throw new RuntimeException(e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3702 Content-Disposition: inline; filename="NetRCCredentialsProvider.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "e32530cc9bd001d1c425be7aa0fa9c1ec5404a02" /* * Copyright (C) 2014, Alexey Kuznetsov * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.transport.NetRC.NetRCEntry; /** * Simple .netrc credentials provider. It can lookup the first machine entry * from your .netrc file. * * @since 3.5 */ public class NetRCCredentialsProvider extends CredentialsProvider { NetRC netrc = new NetRC(); /** *

Constructor for NetRCCredentialsProvider.

*/ public NetRCCredentialsProvider() { } /** * Install default provider for the .netrc parser. */ public static void install() { CredentialsProvider.setDefault(new NetRCCredentialsProvider()); } @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { if (i instanceof CredentialItem.Username) continue; else if (i instanceof CredentialItem.Password) continue; else return false; } return true; } @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { NetRCEntry cc = netrc.getEntry(uri.getHost()); if (cc == null) return false; for (CredentialItem i : items) { if (i instanceof CredentialItem.Username) { ((CredentialItem.Username) i).setValue(cc.login); continue; } if (i instanceof CredentialItem.Password) { ((CredentialItem.Password) i).setValue(cc.password); continue; } if (i instanceof CredentialItem.StringType) { if (i.getPromptText().equals("Password: ")) { //$NON-NLS-1$ ((CredentialItem.StringType) i).setValue(new String( cc.password)); continue; } } throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText()); //$NON-NLS-1$ } return !isAnyNull(items); } @Override public boolean isInteractive() { return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2249 Content-Disposition: inline; filename="NonceGenerator.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "30e80e0783d3d421ce9f62c632c8f4ba77d7806f" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PushCertificate.NonceStatus; /** * A NonceGenerator is used to create a nonce to be sent out to the pusher who * will sign the nonce to prove it is not a replay attack on the push * certificate. * * @since 4.0 */ public interface NonceGenerator { /** * Create nonce to be signed by the pusher * * @param db * The repository which should be used to obtain a unique String * such that the pusher cannot forge nonces by pushing to another * repository at the same time as well and reusing the nonce. * @param timestamp * The current time in seconds. * @return The nonce to be signed by the pusher */ String createNonce(Repository db, long timestamp); /** * Verify trustworthiness of the received nonce. * * @param received * The nonce which was received from the server * @param sent * The nonce which was originally sent out to the client. * @param db * The repository which should be used to obtain a unique String * such that the pusher cannot forge nonces by pushing to another * repository at the same time as well and reusing the nonce. * @param allowSlop * If the receiving backend is able to generate slop. This is * the case for serving via http protocol using more than one * http frontend. The client would talk to different http * frontends, which may have a slight difference of time due to * @param slop * If `allowSlop` is true, this specifies the number of seconds * which we allow as slop. * @return a NonceStatus indicating the trustworthiness of the received * nonce. */ NonceStatus verify(String received, String sent, Repository db, boolean allowSlop, int slop); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1256 Content-Disposition: inline; filename="ObjectCountCallback.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "ffeeca9ba3deee08cf7558fa9190073836b5a398" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.OutputStream; import org.eclipse.jgit.lib.ProgressMonitor; /** * A callback to tell caller the count of objects ASAP. * * @since 4.1 */ public interface ObjectCountCallback { /** * Invoked when the * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} has counted the * objects to be written to pack. *

* An {@code ObjectCountCallback} can use this information to decide whether * the * {@link org.eclipse.jgit.internal.storage.pack.PackWriter#writePack(ProgressMonitor, ProgressMonitor, OutputStream)} * operation should be aborted. *

* This callback will be called exactly once. * * @param objectCount * the count of the objects. * @throws org.eclipse.jgit.transport.WriteAbortedException * to indicate that the write operation should be aborted. */ void setObjectCount(long objectCount) throws WriteAbortedException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1835 Content-Disposition: inline; filename="ObjectInfoRequest.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "241f1e749faac43d8f72498d0b0e4e8ef44c71fe" /* * Copyright (C) 2021, Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collections; import java.util.List; import org.eclipse.jgit.lib.ObjectId; /** * object-info request. * *

* This is the parsed request for an object-info call, used as input to * {@link ProtocolV2Hook}. * * @see object-info * documentation * * @since 5.13 */ public final class ObjectInfoRequest { private final List objectIDs; private ObjectInfoRequest(List objectIDs) { this.objectIDs = objectIDs; } /** * Get object ids requested by the client * * @return object IDs that the client requested. */ public List getObjectIDs() { return this.objectIDs; } /** * Create builder * * @return A builder of {@link ObjectInfoRequest}. */ public static Builder builder() { return new Builder(); } /** A builder for {@link ObjectInfoRequest}. */ public static final class Builder { private List objectIDs = Collections.emptyList(); private Builder() { } /** * Set object ids * * @param value * of objectIds * @return the Builder */ public Builder setObjectIDs(List value) { objectIDs = value; return this; } /** * Build the request * * @return ObjectInfoRequest the request */ public ObjectInfoRequest build() { return new ObjectInfoRequest( Collections.unmodifiableList(objectIDs)); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4569 Content-Disposition: inline; filename="OperationResult.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "1000a5bd50a70c402e6f3b09dfa44d5124c0c3e9" /* * Copyright (C) 2010, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2007-2009, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.eclipse.jgit.lib.Ref; /** * Class holding result of operation on remote repository. This includes refs * advertised by remote repo and local tracking refs updates. */ public abstract class OperationResult { Map advertisedRefs = Collections.emptyMap(); URIish uri; final SortedMap updates = new TreeMap<>(); StringBuilder messageBuffer; String peerUserAgent; /** * Get the URI this result came from. *

* Each transport instance connects to at most one URI at any point in time. * * @return the URI describing the location of the remote repository. */ public URIish getURI() { return uri; } /** * Get the complete list of refs advertised by the remote. *

* The returned refs may appear in any order. If the caller needs these to * be sorted, they should be copied into a new array or List and then sorted * by the caller as necessary. * * @return available/advertised refs. Never null. Not modifiable. The * collection can be empty if the remote side has no refs (it is an * empty/newly created repository). */ public Collection getAdvertisedRefs() { return Collections.unmodifiableCollection(advertisedRefs.values()); } /** * Get a single advertised ref by name. *

* The name supplied should be valid ref name. To get a peeled value for a * ref (aka refs/tags/v1.0^{}) use the base name (without * the ^{} suffix) and look at the peeled object id. * * @param name * name of the ref to obtain. * @return the requested ref; null if the remote did not advertise this ref. */ public final Ref getAdvertisedRef(String name) { return advertisedRefs.get(name); } /** * Get the status of all local tracking refs that were updated. * * @return unmodifiable collection of local updates. Never null. Empty if * there were no local tracking refs updated. */ public Collection getTrackingRefUpdates() { return Collections.unmodifiableCollection(updates.values()); } /** * Get the status for a specific local tracking ref update. * * @param localName * name of the local ref (e.g. "refs/remotes/origin/master"). * @return status of the local ref; null if this local ref was not touched * during this operation. */ public TrackingRefUpdate getTrackingRefUpdate(String localName) { return updates.get(localName); } void setAdvertisedRefs(URIish u, Map ar) { uri = u; advertisedRefs = ar; } void add(TrackingRefUpdate u) { updates.put(u.getLocalName(), u); } /** * Get the additional messages, if any, returned by the remote process. *

* These messages are most likely informational or error messages, sent by * the remote peer, to help the end-user correct any problems that may have * prevented the operation from completing successfully. Application UIs * should try to show these in an appropriate context. * * @return the messages returned by the remote, most likely terminated by a * newline (LF) character. The empty string is returned if the * remote produced no additional messages. */ public String getMessages() { return messageBuffer != null ? messageBuffer.toString() : ""; //$NON-NLS-1$ } void addMessages(String msg) { if (msg != null && msg.length() > 0) { if (messageBuffer == null) messageBuffer = new StringBuilder(); messageBuffer.append(msg); if (!msg.endsWith("\n")) //$NON-NLS-1$ messageBuffer.append('\n'); } } /** * Get the user agent advertised by the peer server, if available. * * @return advertised user agent, e.g. {@code "JGit/4.0"}. Null if the peer * did not advertise version information. * @since 4.0 */ public String getPeerUserAgent() { return peerUserAgent; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 941 Content-Disposition: inline; filename="PackLock.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "bf2140759f4ae0b38bf3fb52e2f82ed3ad2982aa" /* * Copyright (C) 2021 Thomas Wolf and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; /** * A {@code PackLock} describes a {@code .keep} file that holds a pack in place. * If {@link PackParser#parse(org.eclipse.jgit.lib.ProgressMonitor)} creates * such a pack lock, it returns the lock so that it can be unlocked once the * pack doesn't need a {@code .keep} file anymore. * * @since 6.0 */ public interface PackLock { /** * Remove the {@code .keep} file that holds a pack in place. * * @throws java.io.IOException * if deletion of the {@code .keep} file failed */ void unlock() throws IOException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 54124 Content-Disposition: inline; filename="PackParser.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "e1f2b19ce5d3206c45097d9b6d49930264f37143" /* * Copyright (C) 2008-2011, Google Inc. * Copyright (C) 2007-2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.BinaryDelta; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.BlobObjectChecker; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.InflaterCache; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectStream; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.util.BlockList; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.LongMap; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.sha1.SHA1; /** * Parses a pack stream and imports it for an * {@link org.eclipse.jgit.lib.ObjectInserter}. *

* Applications can acquire an instance of a parser from ObjectInserter's * {@link org.eclipse.jgit.lib.ObjectInserter#newPackParser(InputStream)} * method. *

* Implementations of {@link org.eclipse.jgit.lib.ObjectInserter} should * subclass this type and provide their own logic for the various {@code on*()} * event methods declared to be abstract. */ public abstract class PackParser { /** Size of the internal stream buffer. */ private static final int BUFFER_SIZE = 8192; /** Location data is being obtained from. */ public enum Source { /** Data is read from the incoming stream. */ INPUT, /** Data is read back from the database's buffers. */ DATABASE; } /** Object database used for loading existing objects. */ private final ObjectDatabase objectDatabase; private InflaterStream inflater; private byte[] tempBuffer; private byte[] hdrBuf; private final SHA1 objectHasher = SHA1.newInstance(); private final MutableObjectId tempObjectId; private InputStream in; byte[] buf; /** Position in the input stream of {@code buf[0]}. */ private long bBase; private int bOffset; int bAvail; private ObjectChecker objCheck; private boolean allowThin; private boolean checkObjectCollisions; private boolean needBaseObjectIds; private boolean checkEofAfterPackFooter; private boolean expectDataAfterPackFooter; private long expectedObjectCount; private PackedObjectInfo[] entries; /** * Every object contained within the incoming pack. *

* This is a subset of {@link #entries}, as thin packs can add additional * objects to {@code entries} by copying already existing objects from the * repository onto the end of the thin pack to make it self-contained. */ private ObjectIdSubclassMap newObjectIds; private int deltaCount; private int entryCount; private ObjectIdOwnerMap baseById; /** * Objects referenced by their name from deltas, that aren't in this pack. *

* This is the set of objects that were copied onto the end of this pack to * make it complete. These objects were not transmitted by the remote peer, * but instead were assumed to already exist in the local repository. */ private ObjectIdSubclassMap baseObjectIds; private LongMap baseByPos; /** Objects need to be double-checked for collision after indexing. */ private BlockList collisionCheckObjs; private MessageDigest packDigest; private ObjectReader readCurs; /** Message to protect the pack data from garbage collection. */ private String lockMessage; /** Git object size limit */ private long maxObjectSizeLimit; private final ReceivedPackStatistics.Builder stats = new ReceivedPackStatistics.Builder(); /** * Initialize a pack parser. * * @param odb * database the parser will write its objects into. * @param src * the stream the parser will read. */ protected PackParser(ObjectDatabase odb, InputStream src) { objectDatabase = odb.newCachedDatabase(); in = src; inflater = new InflaterStream(); readCurs = objectDatabase.newReader(); buf = new byte[BUFFER_SIZE]; tempBuffer = new byte[BUFFER_SIZE]; hdrBuf = new byte[64]; tempObjectId = new MutableObjectId(); packDigest = Constants.newMessageDigest(); checkObjectCollisions = true; } /** * Whether a thin pack (missing base objects) is permitted. * * @return {@code true} if a thin pack (missing base objects) is permitted. */ public boolean isAllowThin() { return allowThin; } /** * Configure this index pack instance to allow a thin pack. *

* Thin packs are sometimes used during network transfers to allow a delta * to be sent without a base object. Such packs are not permitted on disk. * * @param allow * true to enable a thin pack. */ public void setAllowThin(boolean allow) { allowThin = allow; } /** * Whether received objects are verified to prevent collisions. * * @return if true received objects are verified to prevent collisions. * @since 4.1 */ protected boolean isCheckObjectCollisions() { return checkObjectCollisions; } /** * Enable checking for collisions with existing objects. *

* By default PackParser looks for each received object in the repository. * If the object already exists, the existing object is compared * byte-for-byte with the newly received copy to ensure they are identical. * The receive is aborted with an exception if any byte differs. This check * is necessary to prevent an evil attacker from supplying a replacement * object into this repository in the event that a discovery enabling SHA-1 * collisions is made. *

* This check may be very costly to perform, and some repositories may have * other ways to segregate newly received object data. The check is enabled * by default, but can be explicitly disabled if the implementation can * provide the same guarantee, or is willing to accept the risks associated * with bypassing the check. * * @param check * true to enable collision checking (strongly encouraged). * @since 4.1 */ protected void setCheckObjectCollisions(boolean check) { checkObjectCollisions = check; } /** * Configure this index pack instance to keep track of new objects. *

* By default an index pack doesn't save the new objects that were created * when it was instantiated. Setting this flag to {@code true} allows the * caller to use {@link #getNewObjectIds()} to retrieve that list. * * @param b * {@code true} to enable keeping track of new objects. */ public void setNeedNewObjectIds(boolean b) { if (b) newObjectIds = new ObjectIdSubclassMap<>(); else newObjectIds = null; } private boolean needNewObjectIds() { return newObjectIds != null; } /** * Configure this index pack instance to keep track of the objects assumed * for delta bases. *

* By default an index pack doesn't save the objects that were used as delta * bases. Setting this flag to {@code true} will allow the caller to use * {@link #getBaseObjectIds()} to retrieve that list. * * @param b * {@code true} to enable keeping track of delta bases. */ public void setNeedBaseObjectIds(boolean b) { this.needBaseObjectIds = b; } /** * Whether the EOF should be read from the input after the footer. * * @return true if the EOF should be read from the input after the footer. */ public boolean isCheckEofAfterPackFooter() { return checkEofAfterPackFooter; } /** * Ensure EOF is read from the input stream after the footer. * * @param b * true if the EOF should be read; false if it is not checked. */ public void setCheckEofAfterPackFooter(boolean b) { checkEofAfterPackFooter = b; } /** * Whether there is data expected after the pack footer. * * @return true if there is data expected after the pack footer. */ public boolean isExpectDataAfterPackFooter() { return expectDataAfterPackFooter; } /** * Set if there is additional data in InputStream after pack. * * @param e * true if there is additional data in InputStream after pack. * This requires the InputStream to support the mark and reset * functions. */ public void setExpectDataAfterPackFooter(boolean e) { expectDataAfterPackFooter = e; } /** * Get the new objects that were sent by the user * * @return the new objects that were sent by the user */ public ObjectIdSubclassMap getNewObjectIds() { if (newObjectIds != null) return newObjectIds; return new ObjectIdSubclassMap<>(); } /** * Get set of objects the incoming pack assumed for delta purposes * * @return set of objects the incoming pack assumed for delta purposes */ public ObjectIdSubclassMap getBaseObjectIds() { if (baseObjectIds != null) return baseObjectIds; return new ObjectIdSubclassMap<>(); } /** * Configure the checker used to validate received objects. *

* Usually object checking isn't necessary, as Git implementations only * create valid objects in pack files. However, additional checking may be * useful if processing data from an untrusted source. * * @param oc * the checker instance; null to disable object checking. */ public void setObjectChecker(ObjectChecker oc) { objCheck = oc; } /** * Configure the checker used to validate received objects. *

* Usually object checking isn't necessary, as Git implementations only * create valid objects in pack files. However, additional checking may be * useful if processing data from an untrusted source. *

* This is shorthand for: * *

	 * setObjectChecker(on ? new ObjectChecker() : null);
	 * 
* * @param on * true to enable the default checker; false to disable it. */ public void setObjectChecking(boolean on) { setObjectChecker(on ? new ObjectChecker() : null); } /** * Get the message to record with the pack lock. * * @return the message to record with the pack lock. */ public String getLockMessage() { return lockMessage; } /** * Set the lock message for the incoming pack data. * * @param msg * if not null, the message to associate with the incoming data * while it is locked to prevent garbage collection. */ public void setLockMessage(String msg) { lockMessage = msg; } /** * Set the maximum allowed Git object size. *

* If an object is larger than the given size the pack-parsing will throw an * exception aborting the parsing. * * @param limit * the Git object size limit. If zero then there is not limit. */ public void setMaxObjectSizeLimit(long limit) { maxObjectSizeLimit = limit; } /** * Get the number of objects in the stream. *

* The object count is only available after {@link #parse(ProgressMonitor)} * has returned. The count may have been increased if the stream was a thin * pack, and missing bases objects were appending onto it by the subclass. * * @return number of objects parsed out of the stream. */ public int getObjectCount() { return entryCount; } /** * Get the information about the requested object. *

* The object information is only available after * {@link #parse(ProgressMonitor)} has returned. * * @param nth * index of the object in the stream. Must be between 0 and * {@link #getObjectCount()}-1. * @return the object information. */ public PackedObjectInfo getObject(int nth) { return entries[nth]; } /** * Get all of the objects, sorted by their name. *

* The object information is only available after * {@link #parse(ProgressMonitor)} has returned. *

* To maintain lower memory usage and good runtime performance, this method * sorts the objects in-place and therefore impacts the ordering presented * by {@link #getObject(int)}. * * @param cmp * comparison function, if null objects are stored by ObjectId. * @return sorted list of objects in this pack stream. */ public List getSortedObjectList( Comparator cmp) { Arrays.sort(entries, 0, entryCount, cmp); List list = Arrays.asList(entries); if (entryCount < entries.length) list = list.subList(0, entryCount); return list; } /** * Get the size of the newly created pack. *

* This will also include the pack index size if an index was created. This * method should only be called after pack parsing is finished. * * @return the pack size (including the index size) or -1 if the size cannot * be determined * @since 3.3 */ public long getPackSize() { return -1; } /** * Returns the statistics of the parsed pack. *

* This should only be called after pack parsing is finished. * * @return {@link org.eclipse.jgit.transport.ReceivedPackStatistics} * @since 4.6 */ public ReceivedPackStatistics getReceivedPackStatistics() { return stats.build(); } /** * Parse the pack stream. * * @param progress * callback to provide progress feedback during parsing. If null, * {@link org.eclipse.jgit.lib.NullProgressMonitor} will be used. * @return the pack lock, if one was requested by setting * {@link #setLockMessage(String)}. * @throws java.io.IOException * the stream is malformed, or contains corrupt objects. * @since 6.0 */ public final PackLock parse(ProgressMonitor progress) throws IOException { return parse(progress, progress); } /** * Parse the pack stream. * * @param receiving * receives progress feedback during the initial receiving * objects phase. If null, * {@link org.eclipse.jgit.lib.NullProgressMonitor} will be used. * @param resolving * receives progress feedback during the resolving objects phase. * @return the pack lock, if one was requested by setting * {@link #setLockMessage(String)}. * @throws java.io.IOException * the stream is malformed, or contains corrupt objects. * @since 6.0 */ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving) throws IOException { if (receiving == null) receiving = NullProgressMonitor.INSTANCE; if (resolving == null) resolving = NullProgressMonitor.INSTANCE; if (receiving == resolving) receiving.start(2 /* tasks */); try { readPackHeader(); entries = new PackedObjectInfo[(int) expectedObjectCount]; baseById = new ObjectIdOwnerMap<>(); baseByPos = new LongMap<>(); collisionCheckObjs = new BlockList<>(); receiving.beginTask(JGitText.get().receivingObjects, (int) expectedObjectCount); try { for (long done = 0; done < expectedObjectCount; done++) { indexOneObject(); receiving.update(1); if (receiving.isCancelled()) throw new IOException(JGitText.get().downloadCancelled); } readPackFooter(); endInput(); } finally { receiving.endTask(); } if (!collisionCheckObjs.isEmpty()) { checkObjectCollision(); } if (deltaCount > 0) { processDeltas(resolving); } packDigest = null; baseById = null; baseByPos = null; } finally { try { if (readCurs != null) readCurs.close(); } finally { readCurs = null; } try { inflater.release(); } finally { inflater = null; } } return null; // By default there is no locking. } private void processDeltas(ProgressMonitor resolving) throws IOException { if (resolving instanceof BatchingProgressMonitor) { ((BatchingProgressMonitor) resolving).setDelayStart(1000, TimeUnit.MILLISECONDS); } resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount); resolveDeltas(resolving); if (entryCount < expectedObjectCount) { if (!isAllowThin()) { throw new IOException(MessageFormat.format( JGitText.get().packHasUnresolvedDeltas, Long.valueOf(expectedObjectCount - entryCount))); } resolveDeltasWithExternalBases(resolving); if (entryCount < expectedObjectCount) { throw new IOException(MessageFormat.format( JGitText.get().packHasUnresolvedDeltas, Long.valueOf(expectedObjectCount - entryCount))); } } resolving.endTask(); } private void resolveDeltas(ProgressMonitor progress) throws IOException { final int last = entryCount; for (int i = 0; i < last; i++) { resolveDeltas(entries[i], progress); if (progress.isCancelled()) throw new IOException( JGitText.get().downloadCancelledDuringIndexing); } } private void resolveDeltas(final PackedObjectInfo oe, ProgressMonitor progress) throws IOException { UnresolvedDelta children = firstChildOf(oe); if (children == null) return; DeltaVisit visit = new DeltaVisit(); visit.nextChild = children; ObjectTypeAndSize info = openDatabase(oe, new ObjectTypeAndSize()); switch (info.type) { case Constants.OBJ_COMMIT: case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: visit.data = inflateAndReturn(Source.DATABASE, info.size); visit.id = oe; break; default: throw new IOException(MessageFormat.format( JGitText.get().unknownObjectType, Integer.valueOf(info.type))); } if (!checkCRC(oe.getCRC())) { throw new IOException(MessageFormat.format( JGitText.get().corruptionDetectedReReadingAt, Long.valueOf(oe.getOffset()))); } resolveDeltas(visit.next(), info.type, info, progress); } private void resolveDeltas(DeltaVisit visit, final int type, ObjectTypeAndSize info, ProgressMonitor progress) throws IOException { stats.addDeltaObject(type); do { progress.update(1); info = openDatabase(visit.delta, info); switch (info.type) { case Constants.OBJ_OFS_DELTA: case Constants.OBJ_REF_DELTA: break; default: throw new IOException(MessageFormat.format( JGitText.get().unknownObjectType, Integer.valueOf(info.type))); } byte[] delta = inflateAndReturn(Source.DATABASE, info.size); long finalSz = BinaryDelta.getResultSize(delta); checkIfTooLarge(type, finalSz); visit.data = BinaryDelta.apply(visit.parent.data, delta); delta = null; if (!checkCRC(visit.delta.crc)) throw new IOException(MessageFormat.format( JGitText.get().corruptionDetectedReReadingAt, Long.valueOf(visit.delta.position))); SHA1 objectDigest = objectHasher.reset(); objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update((byte) ' '); objectDigest.update(Constants.encodeASCII(visit.data.length)); objectDigest.update((byte) 0); objectDigest.update(visit.data); objectDigest.digest(tempObjectId); verifySafeObject(tempObjectId, type, visit.data); if (isCheckObjectCollisions() && readCurs.has(tempObjectId)) { checkObjectCollision(tempObjectId, type, visit.data, visit.delta.sizeBeforeInflating); } PackedObjectInfo oe; oe = newInfo(tempObjectId, visit.delta, visit.parent.id); oe.setFullSize(finalSz); oe.setOffset(visit.delta.position); oe.setType(type); onInflatedObjectData(oe, type, visit.data); addObjectAndTrack(oe); visit.id = oe; visit.nextChild = firstChildOf(oe); visit = visit.next(); } while (visit != null); } private final void checkIfTooLarge(int typeCode, long size) throws IOException { if (0 < maxObjectSizeLimit && maxObjectSizeLimit < size) { switch (typeCode) { case Constants.OBJ_COMMIT: case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: throw new TooLargeObjectInPackException(size, maxObjectSizeLimit); case Constants.OBJ_OFS_DELTA: case Constants.OBJ_REF_DELTA: throw new TooLargeObjectInPackException(size, maxObjectSizeLimit); default: throw new IOException(MessageFormat.format( JGitText.get().unknownObjectType, Integer.valueOf(typeCode))); } } if (size > Integer.MAX_VALUE - 8) { throw new TooLargeObjectInPackException(size, Integer.MAX_VALUE - 8); } } /** * Read the header of the current object. *

* After the header has been parsed, this method automatically invokes * {@link #onObjectHeader(Source, byte[], int, int)} to allow the * implementation to update its internal checksums for the bytes read. *

* When this method returns the database will be positioned on the first * byte of the deflated data stream. * * @param info * the info object to populate. * @return {@code info}, after populating. * @throws java.io.IOException * the size cannot be read. */ protected ObjectTypeAndSize readObjectHeader(ObjectTypeAndSize info) throws IOException { int hdrPtr = 0; int c = readFrom(Source.DATABASE); hdrBuf[hdrPtr++] = (byte) c; info.type = (c >> 4) & 7; long sz = c & 15; int shift = 4; while ((c & 0x80) != 0) { c = readFrom(Source.DATABASE); hdrBuf[hdrPtr++] = (byte) c; sz += ((long) (c & 0x7f)) << shift; shift += 7; } info.size = sz; switch (info.type) { case Constants.OBJ_COMMIT: case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: onObjectHeader(Source.DATABASE, hdrBuf, 0, hdrPtr); break; case Constants.OBJ_OFS_DELTA: c = readFrom(Source.DATABASE); hdrBuf[hdrPtr++] = (byte) c; while ((c & 128) != 0) { c = readFrom(Source.DATABASE); hdrBuf[hdrPtr++] = (byte) c; } onObjectHeader(Source.DATABASE, hdrBuf, 0, hdrPtr); break; case Constants.OBJ_REF_DELTA: System.arraycopy(buf, fill(Source.DATABASE, 20), hdrBuf, hdrPtr, 20); hdrPtr += 20; use(20); onObjectHeader(Source.DATABASE, hdrBuf, 0, hdrPtr); break; default: throw new IOException(MessageFormat.format( JGitText.get().unknownObjectType, Integer.valueOf(info.type))); } return info; } private UnresolvedDelta removeBaseById(AnyObjectId id) { final DeltaChain d = baseById.get(id); return d != null ? d.remove() : null; } private static UnresolvedDelta reverse(UnresolvedDelta c) { UnresolvedDelta tail = null; while (c != null) { final UnresolvedDelta n = c.next; c.next = tail; tail = c; c = n; } return tail; } private UnresolvedDelta firstChildOf(PackedObjectInfo oe) { UnresolvedDelta a = reverse(removeBaseById(oe)); UnresolvedDelta b = reverse(baseByPos.remove(oe.getOffset())); if (a == null) return b; if (b == null) return a; UnresolvedDelta first = null; UnresolvedDelta last = null; while (a != null || b != null) { UnresolvedDelta curr; if (b == null || (a != null && a.position < b.position)) { curr = a; a = a.next; } else { curr = b; b = b.next; } if (last != null) last.next = curr; else first = curr; last = curr; curr.next = null; } return first; } private void resolveDeltasWithExternalBases(ProgressMonitor progress) throws IOException { growEntries(baseById.size()); if (needBaseObjectIds) baseObjectIds = new ObjectIdSubclassMap<>(); final List missing = new ArrayList<>(64); for (DeltaChain baseId : baseById) { if (baseId.head == null) continue; if (needBaseObjectIds) baseObjectIds.add(baseId); final ObjectLoader ldr; try { ldr = readCurs.open(baseId); } catch (MissingObjectException notFound) { missing.add(baseId); continue; } final DeltaVisit visit = new DeltaVisit(); visit.data = ldr.getCachedBytes(Integer.MAX_VALUE); visit.id = baseId; final int typeCode = ldr.getType(); final PackedObjectInfo oe = newInfo(baseId, null, null); oe.setType(typeCode); oe.setFullSize(ldr.getSize()); if (onAppendBase(typeCode, visit.data, oe)) entries[entryCount++] = oe; visit.nextChild = firstChildOf(oe); resolveDeltas(visit.next(), typeCode, new ObjectTypeAndSize(), progress); if (progress.isCancelled()) throw new IOException( JGitText.get().downloadCancelledDuringIndexing); } for (DeltaChain base : missing) { if (base.head != null) throw new MissingObjectException(base, "delta base"); //$NON-NLS-1$ } onEndThinPack(); } private void growEntries(int extraObjects) { final PackedObjectInfo[] ne; ne = new PackedObjectInfo[(int) expectedObjectCount + extraObjects]; System.arraycopy(entries, 0, ne, 0, entryCount); entries = ne; } private void readPackHeader() throws IOException { if (expectDataAfterPackFooter) { if (!in.markSupported()) throw new IOException( JGitText.get().inputStreamMustSupportMark); in.mark(buf.length); } final int hdrln = Constants.PACK_SIGNATURE.length + 4 + 4; final int p = fill(Source.INPUT, hdrln); for (int k = 0; k < Constants.PACK_SIGNATURE.length; k++) if (buf[p + k] != Constants.PACK_SIGNATURE[k]) throw new IOException(JGitText.get().notAPACKFile); final long vers = NB.decodeUInt32(buf, p + 4); if (vers != 2 && vers != 3) throw new IOException(MessageFormat.format( JGitText.get().unsupportedPackVersion, Long.valueOf(vers))); final long objectCount = NB.decodeUInt32(buf, p + 8); use(hdrln); setExpectedObjectCount(objectCount); onPackHeader(objectCount); } private void readPackFooter() throws IOException { sync(); final byte[] actHash = packDigest.digest(); final int c = fill(Source.INPUT, 20); final byte[] srcHash = new byte[20]; System.arraycopy(buf, c, srcHash, 0, 20); use(20); if (bAvail != 0 && !expectDataAfterPackFooter) throw new CorruptObjectException(MessageFormat.format( JGitText.get().expectedEOFReceived, "\\x" + Integer.toHexString(buf[bOffset] & 0xff))); //$NON-NLS-1$ if (isCheckEofAfterPackFooter()) { int eof = in.read(); if (0 <= eof) throw new CorruptObjectException(MessageFormat.format( JGitText.get().expectedEOFReceived, "\\x" + Integer.toHexString(eof))); //$NON-NLS-1$ } else if (bAvail > 0 && expectDataAfterPackFooter) { in.reset(); IO.skipFully(in, bOffset); } if (!Arrays.equals(actHash, srcHash)) throw new CorruptObjectException( JGitText.get().corruptObjectPackfileChecksumIncorrect); onPackFooter(srcHash); } // Cleanup all resources associated with our input parsing. private void endInput() { stats.setNumBytesRead(streamPosition()); in = null; } // Read one entire object or delta from the input. private void indexOneObject() throws IOException { final long streamPosition = streamPosition(); int hdrPtr = 0; int c = readFrom(Source.INPUT); hdrBuf[hdrPtr++] = (byte) c; final int typeCode = (c >> 4) & 7; long sz = c & 15; int shift = 4; while ((c & 0x80) != 0) { c = readFrom(Source.INPUT); hdrBuf[hdrPtr++] = (byte) c; sz += ((long) (c & 0x7f)) << shift; shift += 7; } checkIfTooLarge(typeCode, sz); switch (typeCode) { case Constants.OBJ_COMMIT: case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: stats.addWholeObject(typeCode); onBeginWholeObject(streamPosition, typeCode, sz); onObjectHeader(Source.INPUT, hdrBuf, 0, hdrPtr); whole(streamPosition, typeCode, sz); break; case Constants.OBJ_OFS_DELTA: { stats.addOffsetDelta(); c = readFrom(Source.INPUT); hdrBuf[hdrPtr++] = (byte) c; long ofs = c & 127; while ((c & 128) != 0) { ofs += 1; c = readFrom(Source.INPUT); hdrBuf[hdrPtr++] = (byte) c; ofs <<= 7; ofs += (c & 127); } final long base = streamPosition - ofs; onBeginOfsDelta(streamPosition, base, sz); onObjectHeader(Source.INPUT, hdrBuf, 0, hdrPtr); inflateAndSkip(Source.INPUT, sz); UnresolvedDelta n = onEndDelta(); n.position = streamPosition; n.next = baseByPos.put(base, n); n.sizeBeforeInflating = streamPosition() - streamPosition; deltaCount++; break; } case Constants.OBJ_REF_DELTA: { stats.addRefDelta(); c = fill(Source.INPUT, 20); final ObjectId base = ObjectId.fromRaw(buf, c); System.arraycopy(buf, c, hdrBuf, hdrPtr, 20); hdrPtr += 20; use(20); DeltaChain r = baseById.get(base); if (r == null) { r = new DeltaChain(base); baseById.add(r); } onBeginRefDelta(streamPosition, base, sz); onObjectHeader(Source.INPUT, hdrBuf, 0, hdrPtr); inflateAndSkip(Source.INPUT, sz); UnresolvedDelta n = onEndDelta(); n.position = streamPosition; n.sizeBeforeInflating = streamPosition() - streamPosition; r.add(n); deltaCount++; break; } default: throw new IOException( MessageFormat.format(JGitText.get().unknownObjectType, Integer.valueOf(typeCode))); } } private void whole(long pos, int type, long sz) throws IOException { SHA1 objectDigest = objectHasher.reset(); objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update((byte) ' '); objectDigest.update(Constants.encodeASCII(sz)); objectDigest.update((byte) 0); final byte[] data; if (type == Constants.OBJ_BLOB) { byte[] readBuffer = buffer(); BlobObjectChecker checker = null; if (objCheck != null) { checker = objCheck.newBlobObjectChecker(); } if (checker == null) { checker = BlobObjectChecker.NULL_CHECKER; } long cnt = 0; try (InputStream inf = inflate(Source.INPUT, sz)) { while (cnt < sz) { int r = inf.read(readBuffer); if (r <= 0) break; objectDigest.update(readBuffer, 0, r); checker.update(readBuffer, 0, r); cnt += r; } } objectDigest.digest(tempObjectId); checker.endBlob(tempObjectId); data = null; } else { data = inflateAndReturn(Source.INPUT, sz); objectDigest.update(data); objectDigest.digest(tempObjectId); verifySafeObject(tempObjectId, type, data); } long sizeBeforeInflating = streamPosition() - pos; PackedObjectInfo obj = newInfo(tempObjectId, null, null); obj.setOffset(pos); obj.setType(type); obj.setSize(sizeBeforeInflating); obj.setFullSize(sz); onEndWholeObject(obj); if (data != null) onInflatedObjectData(obj, type, data); addObjectAndTrack(obj); if (isCheckObjectCollisions()) { collisionCheckObjs.add(obj); } } /** * Verify the integrity of the object. * * @param id * identity of the object to be checked. * @param type * the type of the object. * @param data * raw content of the object. * @throws org.eclipse.jgit.errors.CorruptObjectException * if a corrupt object was found * @since 4.9 */ protected void verifySafeObject(final AnyObjectId id, final int type, final byte[] data) throws CorruptObjectException { if (objCheck != null) { try { objCheck.check(id, type, data); } catch (CorruptObjectException e) { if (e.getErrorType() != null) { throw e; } throw new CorruptObjectException( MessageFormat.format(JGitText.get().invalidObject, Constants.typeString(type), id.name(), e.getMessage()), e); } } } private void checkObjectCollision() throws IOException { for (PackedObjectInfo obj : collisionCheckObjs) { if (!readCurs.has(obj)) { continue; } checkObjectCollision(obj); } } private void checkObjectCollision(PackedObjectInfo obj) throws IOException { ObjectTypeAndSize info = openDatabase(obj, new ObjectTypeAndSize()); final byte[] readBuffer = buffer(); final byte[] curBuffer = new byte[readBuffer.length]; long sz = info.size; try (ObjectStream cur = readCurs.open(obj, info.type).openStream()) { if (cur.getSize() != sz) { throw new IOException(MessageFormat.format( JGitText.get().collisionOn, obj.name())); } try (InputStream pck = inflate(Source.DATABASE, sz)) { while (0 < sz) { int n = (int) Math.min(readBuffer.length, sz); IO.readFully(cur, curBuffer, 0, n); IO.readFully(pck, readBuffer, 0, n); for (int i = 0; i < n; i++) { if (curBuffer[i] != readBuffer[i]) { throw new IOException(MessageFormat.format( JGitText.get().collisionOn, obj.name())); } } sz -= n; } } stats.incrementObjectsDuplicated(); stats.incrementNumBytesDuplicated(obj.getSize()); } catch (MissingObjectException notLocal) { // This is OK, we don't have a copy of the object locally // but the API throws when we try to read it as usually it's // an error to read something that doesn't exist. } } private void checkObjectCollision(AnyObjectId obj, int type, byte[] data, long sizeBeforeInflating) throws IOException { try { final ObjectLoader ldr = readCurs.open(obj, type); final byte[] existingData = ldr.getCachedBytes(data.length); if (!Arrays.equals(data, existingData)) { throw new IOException(MessageFormat .format(JGitText.get().collisionOn, obj.name())); } stats.incrementObjectsDuplicated(); stats.incrementNumBytesDuplicated(sizeBeforeInflating); } catch (MissingObjectException notLocal) { // This is OK, we don't have a copy of the object locally // but the API throws when we try to read it as usually its // an error to read something that doesn't exist. } } /** @return current position of the input stream being parsed. */ private long streamPosition() { return bBase + bOffset; } private ObjectTypeAndSize openDatabase(PackedObjectInfo obj, ObjectTypeAndSize info) throws IOException { bOffset = 0; bAvail = 0; return seekDatabase(obj, info); } private ObjectTypeAndSize openDatabase(UnresolvedDelta delta, ObjectTypeAndSize info) throws IOException { bOffset = 0; bAvail = 0; return seekDatabase(delta, info); } // Consume exactly one byte from the buffer and return it. private int readFrom(Source src) throws IOException { if (bAvail == 0) fill(src, 1); bAvail--; return buf[bOffset++] & 0xff; } // Consume cnt bytes from the buffer. void use(int cnt) { bOffset += cnt; bAvail -= cnt; } // Ensure at least need bytes are available in {@link #buf}. int fill(Source src, int need) throws IOException { while (bAvail < need) { int next = bOffset + bAvail; int free = buf.length - next; if (free + bAvail < need) { switch (src) { case INPUT: sync(); break; case DATABASE: if (bAvail > 0) System.arraycopy(buf, bOffset, buf, 0, bAvail); bOffset = 0; break; } next = bAvail; free = buf.length - next; } switch (src) { case INPUT: next = in.read(buf, next, free); break; case DATABASE: next = readDatabase(buf, next, free); break; } if (next <= 0) throw new EOFException( JGitText.get().packfileIsTruncatedNoParam); bAvail += next; } return bOffset; } // Store consumed bytes in {@link #buf} up to {@link #bOffset}. private void sync() throws IOException { packDigest.update(buf, 0, bOffset); onStoreStream(buf, 0, bOffset); if (expectDataAfterPackFooter) { if (bAvail > 0) { in.reset(); IO.skipFully(in, bOffset); bAvail = 0; } in.mark(buf.length); } else if (bAvail > 0) System.arraycopy(buf, bOffset, buf, 0, bAvail); bBase += bOffset; bOffset = 0; } /** * Get a temporary byte array for use by the caller. * * @return a temporary byte array for use by the caller. */ protected byte[] buffer() { return tempBuffer; } /** * Construct a PackedObjectInfo instance for this parser. * * @param id * identity of the object to be tracked. * @param delta * if the object was previously an unresolved delta, this is the * delta object that was tracking it. Otherwise null. * @param deltaBase * if the object was previously an unresolved delta, this is the * ObjectId of the base of the delta. The base may be outside of * the pack stream if the stream was a thin-pack. * @return info object containing this object's data. */ protected PackedObjectInfo newInfo(AnyObjectId id, UnresolvedDelta delta, ObjectId deltaBase) { PackedObjectInfo oe = new PackedObjectInfo(id); if (delta != null) oe.setCRC(delta.crc); return oe; } /** * Set the expected number of objects in the pack stream. *

* The object count in the pack header is not always correct for some Dfs * pack files. e.g. INSERT pack always assume 1 object in the header since * the actual object count is unknown when the pack is written. *

* If external implementation wants to overwrite the expectedObjectCount, * they should call this method during {@link #onPackHeader(long)}. * * @param expectedObjectCount a long. * @since 4.9 */ protected void setExpectedObjectCount(long expectedObjectCount) { this.expectedObjectCount = expectedObjectCount; } /** * Store bytes received from the raw stream. *

* This method is invoked during {@link #parse(ProgressMonitor)} as data is * consumed from the incoming stream. Implementors may use this event to * archive the raw incoming stream to the destination repository in large * chunks, without paying attention to object boundaries. *

* The only component of the pack not supplied to this method is the last 20 * bytes of the pack that comprise the trailing SHA-1 checksum. Those are * passed to {@link #onPackFooter(byte[])}. * * @param raw * buffer to copy data out of. * @param pos * first offset within the buffer that is valid. * @param len * number of bytes in the buffer that are valid. * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onStoreStream(byte[] raw, int pos, int len) throws IOException; /** * Store (and/or checksum) an object header. *

* Invoked after any of the {@code onBegin()} events. The entire header is * supplied in a single invocation, before any object data is supplied. * * @param src * where the data came from * @param raw * buffer to read data from. * @param pos * first offset within buffer that is valid. * @param len * number of bytes in buffer that are valid. * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onObjectHeader(Source src, byte[] raw, int pos, int len) throws IOException; /** * Store (and/or checksum) a portion of an object's data. *

* This method may be invoked multiple times per object, depending on the * size of the object, the size of the parser's internal read buffer, and * the alignment of the object relative to the read buffer. *

* Invoked after {@link #onObjectHeader(Source, byte[], int, int)}. * * @param src * where the data came from * @param raw * buffer to read data from. * @param pos * first offset within buffer that is valid. * @param len * number of bytes in buffer that are valid. * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onObjectData(Source src, byte[] raw, int pos, int len) throws IOException; /** * Invoked for commits, trees, tags, and small blobs. * * @param obj * the object info, populated. * @param typeCode * the type of the object. * @param data * inflated data for the object. * @throws java.io.IOException * the object cannot be archived. */ protected abstract void onInflatedObjectData(PackedObjectInfo obj, int typeCode, byte[] data) throws IOException; /** * Provide the implementation with the original stream's pack header. * * @param objCnt * number of objects expected in the stream. * @throws java.io.IOException * the implementation refuses to work with this many objects. */ protected abstract void onPackHeader(long objCnt) throws IOException; /** * Provide the implementation with the original stream's pack footer. * * @param hash * the trailing 20 bytes of the pack, this is a SHA-1 checksum of * all of the pack data. * @throws java.io.IOException * the stream cannot be archived. */ protected abstract void onPackFooter(byte[] hash) throws IOException; /** * Provide the implementation with a base that was outside of the pack. *

* This event only occurs on a thin pack for base objects that were outside * of the pack and came from the local repository. Usually an implementation * uses this event to compress the base and append it onto the end of the * pack, so the pack stays self-contained. * * @param typeCode * type of the base object. * @param data * complete content of the base object. * @param info * packed object information for this base. Implementors must * populate the CRC and offset members if returning true. * @return true if the {@code info} should be included in the object list * returned by {@link #getSortedObjectList(Comparator)}, false if it * should not be included. * @throws java.io.IOException * the base could not be included into the pack. */ protected abstract boolean onAppendBase(int typeCode, byte[] data, PackedObjectInfo info) throws IOException; /** * Event indicating a thin pack has been completely processed. *

* This event is invoked only if a thin pack has delta references to objects * external from the pack. The event is called after all of those deltas * have been resolved. * * @throws java.io.IOException * the pack cannot be archived. */ protected abstract void onEndThinPack() throws IOException; /** * Reposition the database to re-read a previously stored object. *

* If the database is computing CRC-32 checksums for object data, it should * reset its internal CRC instance during this method call. * * @param obj * the object position to begin reading from. This is from * {@link #newInfo(AnyObjectId, UnresolvedDelta, ObjectId)}. * @param info * object to populate with type and size. * @return the {@code info} object. * @throws java.io.IOException * the database cannot reposition to this location. */ protected abstract ObjectTypeAndSize seekDatabase(PackedObjectInfo obj, ObjectTypeAndSize info) throws IOException; /** * Reposition the database to re-read a previously stored object. *

* If the database is computing CRC-32 checksums for object data, it should * reset its internal CRC instance during this method call. * * @param delta * the object position to begin reading from. This is an instance * previously returned by {@link #onEndDelta()}. * @param info * object to populate with type and size. * @return the {@code info} object. * @throws java.io.IOException * the database cannot reposition to this location. */ protected abstract ObjectTypeAndSize seekDatabase(UnresolvedDelta delta, ObjectTypeAndSize info) throws IOException; /** * Read from the database's current position into the buffer. * * @param dst * the buffer to copy read data into. * @param pos * position within {@code dst} to start copying data into. * @param cnt * ideal target number of bytes to read. Actual read length may * be shorter. * @return number of bytes stored. * @throws java.io.IOException * the database cannot be accessed. */ protected abstract int readDatabase(byte[] dst, int pos, int cnt) throws IOException; /** * Check the current CRC matches the expected value. *

* This method is invoked when an object is read back in from the database * and its data is used during delta resolution. The CRC is validated after * the object has been fully read, allowing the parser to verify there was * no silent data corruption. *

* Implementations are free to ignore this check by always returning true if * they are performing other data integrity validations at a lower level. * * @param oldCRC * the prior CRC that was recorded during the first scan of the * object from the pack stream. * @return true if the CRC matches; false if it does not. */ protected abstract boolean checkCRC(int oldCRC); /** * Event notifying the start of an object stored whole (not as a delta). * * @param streamPosition * position of this object in the incoming stream. * @param type * type of the object; one of * {@link org.eclipse.jgit.lib.Constants#OBJ_COMMIT}, * {@link org.eclipse.jgit.lib.Constants#OBJ_TREE}, * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}, or * {@link org.eclipse.jgit.lib.Constants#OBJ_TAG}. * @param inflatedSize * size of the object when fully inflated. The size stored within * the pack may be larger or smaller, and is not yet known. * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onBeginWholeObject(long streamPosition, int type, long inflatedSize) throws IOException; /** * Event notifying the current object. * *@param info * object information. * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onEndWholeObject(PackedObjectInfo info) throws IOException; /** * Event notifying start of a delta referencing its base by offset. * * @param deltaStreamPosition * position of this object in the incoming stream. * @param baseStreamPosition * position of the base object in the incoming stream. The base * must be before the delta, therefore {@code baseStreamPosition * < deltaStreamPosition}. This is not the position * returned by a prior end object event. * @param inflatedSize * size of the delta when fully inflated. The size stored within * the pack may be larger or smaller, and is not yet known. * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onBeginOfsDelta(long deltaStreamPosition, long baseStreamPosition, long inflatedSize) throws IOException; /** * Event notifying start of a delta referencing its base by ObjectId. * * @param deltaStreamPosition * position of this object in the incoming stream. * @param baseId * name of the base object. This object may be later in the * stream, or might not appear at all in the stream (in the case * of a thin-pack). * @param inflatedSize * size of the delta when fully inflated. The size stored within * the pack may be larger or smaller, and is not yet known. * @throws java.io.IOException * the object cannot be recorded. */ protected abstract void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId, long inflatedSize) throws IOException; /** * Event notifying the current object. * *@return object information that must be populated with at least the * offset. * @throws java.io.IOException * the object cannot be recorded. */ protected UnresolvedDelta onEndDelta() throws IOException { return new UnresolvedDelta(); } /** Type and size information about an object in the database buffer. */ public static class ObjectTypeAndSize { /** The type of the object. */ public int type; /** The inflated size of the object. */ public long size; } private void inflateAndSkip(Source src, long inflatedSize) throws IOException { try (InputStream inf = inflate(src, inflatedSize)) { IO.skipFully(inf, inflatedSize); } } private byte[] inflateAndReturn(Source src, long inflatedSize) throws IOException { final byte[] dst = new byte[(int) inflatedSize]; try (InputStream inf = inflate(src, inflatedSize)) { IO.readFully(inf, dst, 0, dst.length); } return dst; } private InputStream inflate(Source src, long inflatedSize) throws IOException { inflater.open(src, inflatedSize); return inflater; } private static class DeltaChain extends ObjectIdOwnerMap.Entry { UnresolvedDelta head; DeltaChain(AnyObjectId id) { super(id); } UnresolvedDelta remove() { final UnresolvedDelta r = head; if (r != null) head = null; return r; } void add(UnresolvedDelta d) { d.next = head; head = d; } } /** Information about an unresolved delta in this pack stream. */ public static class UnresolvedDelta { long position; int crc; UnresolvedDelta next; long sizeBeforeInflating; /** * Get offset within the input stream * * @return offset within the input stream. */ public long getOffset() { return position; } /** * Get the CRC-32 checksum of the stored delta data * * @return the CRC-32 checksum of the stored delta data. */ public int getCRC() { return crc; } /** * Set the CRC-32 checksum of the stored delta data * * @param crc32 * the CRC-32 checksum of the stored delta data. */ public void setCRC(int crc32) { crc = crc32; } } private static class DeltaVisit { final UnresolvedDelta delta; ObjectId id; byte[] data; DeltaVisit parent; UnresolvedDelta nextChild; DeltaVisit() { this.delta = null; // At the root of the stack we have a base. } DeltaVisit(DeltaVisit parent) { this.parent = parent; this.delta = parent.nextChild; parent.nextChild = delta.next; } DeltaVisit next() { // If our parent has no more children, discard it. if (parent != null && parent.nextChild == null) { parent.data = null; parent = parent.parent; } if (nextChild != null) return new DeltaVisit(this); // If we have no child ourselves, our parent must (if it exists), // due to the discard rule above. With no parent, we are done. if (parent != null) return new DeltaVisit(parent); return null; } } private void addObjectAndTrack(PackedObjectInfo oe) { entries[entryCount++] = oe; if (needNewObjectIds()) newObjectIds.add(oe); } private class InflaterStream extends InputStream { private final Inflater inf; private final byte[] skipBuffer; private Source src; private long expectedSize; private long actualSize; private int p; InflaterStream() { inf = InflaterCache.get(); skipBuffer = new byte[512]; } void release() { inf.reset(); InflaterCache.release(inf); } void open(Source source, long inflatedSize) throws IOException { src = source; expectedSize = inflatedSize; actualSize = 0; p = fill(src, 1); inf.setInput(buf, p, bAvail); } @Override public long skip(long toSkip) throws IOException { long n = 0; while (n < toSkip) { final int cnt = (int) Math.min(skipBuffer.length, toSkip - n); final int r = read(skipBuffer, 0, cnt); if (r <= 0) break; n += r; } return n; } @Override public int read() throws IOException { int n = read(skipBuffer, 0, 1); return n == 1 ? skipBuffer[0] & 0xff : -1; } @Override public int read(byte[] dst, int pos, int cnt) throws IOException { try { int n = 0; while (n < cnt) { int r = inf.inflate(dst, pos + n, cnt - n); n += r; if (inf.finished()) break; if (inf.needsInput()) { onObjectData(src, buf, p, bAvail); use(bAvail); p = fill(src, 1); inf.setInput(buf, p, bAvail); } else if (r == 0) { throw new CorruptObjectException(MessageFormat.format( JGitText.get().packfileCorruptionDetected, JGitText.get().unknownZlibError)); } } actualSize += n; return 0 < n ? n : -1; } catch (DataFormatException dfe) { throw new CorruptObjectException(MessageFormat.format(JGitText .get().packfileCorruptionDetected, dfe.getMessage())); } } @Override public void close() throws IOException { // We need to read here to enter the loop above and pump the // trailing checksum into the Inflater. It should return -1 as the // caller was supposed to consume all content. // if (read(skipBuffer) != -1 || actualSize != expectedSize) { throw new CorruptObjectException(MessageFormat.format(JGitText .get().packfileCorruptionDetected, JGitText.get().wrongDecompressedLength)); } int used = bAvail - inf.getRemaining(); if (0 < used) { onObjectData(src, buf, p, used); use(used); } inf.reset(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 944 Content-Disposition: inline; filename="PackTransport.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "0c27cae520b2c159a03c8618211dd4bbaf54c000" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Marker interface an object transport using Git pack transfers. *

* Implementations of PackTransport setup connections and move objects back and * forth by creating pack files on the source side and indexing them on the * receiving side. * * @see BasePackFetchConnection * @see BasePackPushConnection */ public interface PackTransport { // no methods in marker interface } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3738 Content-Disposition: inline; filename="PackedObjectInfo.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "fae26cda95b4ce3f451ea901595c971a1b9e8fd9" /* * Copyright (C) 2008-2009, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectIdOwnerMap; /** * Description of an object stored in a pack file, including offset. *

* When objects are stored in packs Git needs the ObjectId and the offset * (starting position of the object data) to perform random-access reads of * objects from the pack. This extension of ObjectId includes the offset. */ public class PackedObjectInfo extends ObjectIdOwnerMap.Entry { private long offset; private int crc; private int type = Constants.OBJ_BAD; private long sizeBeforeInflating; private long fullSize; PackedObjectInfo(final long headerOffset, final int packedCRC, final AnyObjectId id) { super(id); offset = headerOffset; crc = packedCRC; } /** * Create a new structure to remember information about an object. * * @param id * the identity of the object the new instance tracks. */ public PackedObjectInfo(AnyObjectId id) { super(id); } /** * Get offset in pack when object has been already written * * @return offset in pack when object has been already written, or 0 if it * has not been written yet */ public long getOffset() { return offset; } /** * Set the offset in pack when object has been written to. * * @param offset * offset where written object starts */ public void setOffset(long offset) { this.offset = offset; } /** * Get the 32 bit CRC checksum for the packed data. * * @return the 32 bit CRC checksum for the packed data. */ public int getCRC() { return crc; } /** * Record the 32 bit CRC checksum for the packed data. * * @param crc * checksum of all packed data (including object type code, * inflated length and delta base reference) as computed by * {@link java.util.zip.CRC32}. */ public void setCRC(int crc) { this.crc = crc; } /** * Get the object type. * * @return the object type. The default type is OBJ_BAD, which is considered * as unknown or invalid type. * @since 4.9 */ public int getType() { return type; } /** * Record the object type if applicable. * * @param type * the object type. * @since 4.9 */ public void setType(int type) { this.type = type; } /** * Size in storage * * @param sizeBeforeInflating * size before inflating */ void setSize(long sizeBeforeInflating) { this.sizeBeforeInflating = sizeBeforeInflating; } /** * Size in storage (maybe deflated and/or deltified). * * This is the size in storage. In packs, this is the bytes used by the * object contents (themselves or as deltas) compressed by zlib (deflated). * * @return size in storage */ long getSize() { return sizeBeforeInflating; } /** * Real (materialized) size of the object (inflated, undeltified) * * @param size * size of the object in bytes, without compressing nor * deltifying * @since 6.4 */ public void setFullSize(long size) { this.fullSize = size; } /** * Get full size (inflated, undeltified) * * @return size of the object (inflated, undeltified) * * @since 6.4 */ public long getFullSize() { return fullSize; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10145 Content-Disposition: inline; filename="PacketLineIn.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "614ad88246c97b2c2c5987bcadbce10120dcf878" /* * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, 2009 Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.text.MessageFormat; import java.util.Iterator; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Read Git style pkt-line formatting from an input stream. *

* This class is not thread safe and may issue multiple reads to the underlying * stream for each method call made. *

* This class performs no buffering on its own. This makes it suitable to * interleave reads performed by this class with reads performed directly * against the underlying InputStream. */ public class PacketLineIn { private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class); /** * Magic return from {@link #readString()} when a flush packet is found. */ private static final String END = new String(); /* must not string pool */ /** * Magic return from {@link #readString()} when a delim packet is found. */ private static final String DELIM = new String(); /* must not string pool */ enum AckNackResult { /** NAK */ NAK, /** ACK */ ACK, /** ACK + continue */ ACK_CONTINUE, /** ACK + common */ ACK_COMMON, /** ACK + ready */ ACK_READY; } private final byte[] lineBuffer = new byte[SideBandOutputStream.SMALL_BUF]; private final InputStream in; private long limit; /** * Create a new packet line reader. * * @param in * the input stream to consume. */ public PacketLineIn(InputStream in) { this(in, 0); } /** * Create a new packet line reader. * * @param in * the input stream to consume. * @param limit * bytes to read from the input; unlimited if set to 0. * @since 4.7 */ public PacketLineIn(InputStream in, long limit) { this.in = in; this.limit = limit; } /** * Parses a ACK/NAK line in protocol V2. * * @param line * to parse * @param returnedId * in case of {@link AckNackResult#ACK_COMMON ACK_COMMON} * @return one of {@link AckNackResult#NAK NAK}, * {@link AckNackResult#ACK_COMMON ACK_COMMON}, or * {@link AckNackResult#ACK_READY ACK_READY} * @throws IOException * on protocol or transport errors */ static AckNackResult parseACKv2(String line, MutableObjectId returnedId) throws IOException { if ("NAK".equals(line)) { //$NON-NLS-1$ return AckNackResult.NAK; } if (line.startsWith("ACK ") && line.length() == 44) { //$NON-NLS-1$ returnedId.fromString(line.substring(4, 44)); return AckNackResult.ACK_COMMON; } if ("ready".equals(line)) { //$NON-NLS-1$ return AckNackResult.ACK_READY; } if (line.startsWith("ERR ")) { //$NON-NLS-1$ throw new PackProtocolException(line.substring(4)); } throw new PackProtocolException( MessageFormat.format(JGitText.get().expectedACKNAKGot, line)); } AckNackResult readACK(MutableObjectId returnedId) throws IOException { final String line = readString(); if (line.length() == 0) throw new PackProtocolException(JGitText.get().expectedACKNAKFoundEOF); if ("NAK".equals(line)) //$NON-NLS-1$ return AckNackResult.NAK; if (line.startsWith("ACK ")) { //$NON-NLS-1$ returnedId.fromString(line.substring(4, 44)); if (line.length() == 44) return AckNackResult.ACK; final String arg = line.substring(44); switch (arg) { case " continue": //$NON-NLS-1$ return AckNackResult.ACK_CONTINUE; case " common": //$NON-NLS-1$ return AckNackResult.ACK_COMMON; case " ready": //$NON-NLS-1$ return AckNackResult.ACK_READY; default: break; } } if (line.startsWith("ERR ")) //$NON-NLS-1$ throw new PackProtocolException(line.substring(4)); throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedACKNAKGot, line)); } /** * Read a single UTF-8 encoded string packet from the input stream. *

* If the string ends with an LF, it will be removed before returning the * value to the caller. If this automatic trimming behavior is not desired, * use {@link #readStringRaw()} instead. * * @return the string. {@link #END} if the string was the magic flush * packet, {@link #DELIM} if the string was the magic DELIM * packet. * @throws java.io.IOException * the stream cannot be read. */ public String readString() throws IOException { int len = readLength(); if (len == 0) { log.debug("git< 0000"); //$NON-NLS-1$ return END; } if (len == 1) { log.debug("git< 0001"); //$NON-NLS-1$ return DELIM; } len -= 4; // length header (4 bytes) if (len == 0) { log.debug("git< "); //$NON-NLS-1$ return ""; //$NON-NLS-1$ } byte[] raw; if (len <= lineBuffer.length) raw = lineBuffer; else raw = new byte[len]; IO.readFully(in, raw, 0, len); if (raw[len - 1] == '\n') len--; String s = RawParseUtils.decode(UTF_8, raw, 0, len); log.debug("git< " + s); //$NON-NLS-1$ return s; } /** * Get an iterator to read strings from the input stream. * * @return an iterator that calls {@link #readString()} until {@link #END} * is encountered. * * @throws IOException * on failure to read the initial packet line. * @since 5.4 */ public PacketLineInIterator readStrings() throws IOException { return new PacketLineInIterator(this); } /** * Read a single UTF-8 encoded string packet from the input stream. *

* Unlike {@link #readString()} a trailing LF will be retained. * * @return the string. {@link #END} if the string was the magic flush * packet. * @throws java.io.IOException * the stream cannot be read. */ public String readStringRaw() throws IOException { int len = readLength(); if (len == 0) { log.debug("git< 0000"); //$NON-NLS-1$ return END; } len -= 4; // length header (4 bytes) byte[] raw; if (len <= lineBuffer.length) raw = lineBuffer; else raw = new byte[len]; IO.readFully(in, raw, 0, len); String s = RawParseUtils.decode(UTF_8, raw, 0, len); log.debug("git< " + s); //$NON-NLS-1$ return s; } /** * Check if a string is the delimiter marker. * * @param s * the string to check * @return true if the given string is {@link #DELIM}, otherwise false. * @since 5.4 */ @SuppressWarnings({ "ReferenceEquality", "StringEquality" }) public static boolean isDelimiter(String s) { return s == DELIM; } /** * Get the delimiter marker. *

* Intended for use only in tests. * * @return The delimiter marker. */ static String delimiter() { return DELIM; } /** * Get the end marker. *

* Intended for use only in tests. * * @return The end marker. */ static String end() { return END; } /** * Check if a string is the packet end marker. * * @param s * the string to check * @return true if the given string is {@link #END}, otherwise false. * @since 5.4 */ @SuppressWarnings({ "ReferenceEquality", "StringEquality" }) public static boolean isEnd(String s) { return s == END; } void discardUntilEnd() throws IOException { for (;;) { int n = readLength(); if (n == 0) { break; } IO.skipFully(in, n - 4); } } int readLength() throws IOException { IO.readFully(in, lineBuffer, 0, 4); int len; try { len = RawParseUtils.parseHexInt16(lineBuffer, 0); } catch (ArrayIndexOutOfBoundsException err) { throw invalidHeader(err); } if (len == 0) { return 0; } else if (len == 1) { return 1; } else if (len < 4) { throw invalidHeader(); } if (limit != 0) { int n = len - 4; if (limit < n) { limit = -1; try { IO.skipFully(in, n); } catch (IOException e) { // Ignore failure discarding packet over limit. } throw new InputOverLimitIOException(); } // if set limit must not be 0 (means unlimited). limit = n < limit ? limit - n : -1; } return len; } private IOException invalidHeader() { return new IOException(MessageFormat.format(JGitText.get().invalidPacketLineHeader, "" + (char) lineBuffer[0] + (char) lineBuffer[1] //$NON-NLS-1$ + (char) lineBuffer[2] + (char) lineBuffer[3])); } private IOException invalidHeader(Throwable cause) { IOException ioe = invalidHeader(); ioe.initCause(cause); return ioe; } /** * IOException thrown by read when the configured input limit is exceeded. * * @since 4.7 */ public static class InputOverLimitIOException extends IOException { private static final long serialVersionUID = 1L; } /** * Iterator over packet lines. *

* Calls {@link #readString()} on the {@link PacketLineIn} until * {@link #END} is encountered. * * @since 5.4 * */ public static class PacketLineInIterator implements Iterable { private PacketLineIn in; private String current; PacketLineInIterator(PacketLineIn in) throws IOException { this.in = in; current = in.readString(); } @Override public Iterator iterator() { return new Iterator<>() { @Override public boolean hasNext() { return !PacketLineIn.isEnd(current); } @Override public String next() { String next = current; try { current = in.readString(); } catch (IOException e) { throw new UncheckedIOException(e); } return next; } }; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6907 Content-Disposition: inline; filename="PacketLineOut.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "43b742b02deb0a08cf3442acf5283e76aa142185" /* * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, 2009 Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.OutputStream; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Write Git style pkt-line formatting to an output stream. *

* This class is not thread safe and may issue multiple writes to the underlying * stream for each method call made. *

* This class performs no buffering on its own. This makes it suitable to * interleave writes performed by this class with writes performed directly * against the underlying OutputStream. */ public class PacketLineOut { private static final Logger log = LoggerFactory.getLogger(PacketLineOut.class); private final OutputStream out; private final byte[] lenbuffer; private final boolean logEnabled; private boolean flushOnEnd; private boolean usingSideband; /** * Create a new packet line writer. * * @param outputStream * stream. */ public PacketLineOut(OutputStream outputStream) { this(outputStream, true); } /** * Create a new packet line writer that potentially doesn't log. * * @param outputStream * stream. * @param enableLogging * {@code false} to suppress all logging; {@code true} to log * normally * @since 5.11 */ public PacketLineOut(OutputStream outputStream, boolean enableLogging) { out = outputStream; lenbuffer = new byte[5]; flushOnEnd = true; logEnabled = enableLogging; } /** * Set the flush behavior during {@link #end()}. * * @param flushOnEnd * if true, a flush-pkt written during {@link #end()} also * flushes the underlying stream. */ public void setFlushOnEnd(boolean flushOnEnd) { this.flushOnEnd = flushOnEnd; } /** * Whether is using sideband * * @return whether to add a sideband designator to each non-flush and * non-delim packet * @see #setUsingSideband * @since 5.5 */ public boolean isUsingSideband() { return usingSideband; } /** * Set whether to use sideband * * @param value * If true, when writing packet lines, add, as the first byte, a * sideband designator to each non-flush and non-delim packet. * See pack-protocol.txt and protocol-v2.txt from the Git project * for more information, specifically the "side-band" and * "sideband-all" sections. * @since 5.5 */ public void setUsingSideband(boolean value) { this.usingSideband = value; } /** * Write a UTF-8 encoded string as a single length-delimited packet. * * @param s * string to write. * @throws java.io.IOException * the packet could not be written, the stream is corrupted as * the packet may have been only partially written. */ public void writeString(String s) throws IOException { writePacket(Constants.encode(s)); } /** * Write a binary packet to the stream. * * @param packet * the packet to write; the length of the packet is equal to the * size of the byte array. * @throws java.io.IOException * the packet could not be written, the stream is corrupted as * the packet may have been only partially written. */ public void writePacket(byte[] packet) throws IOException { writePacket(packet, 0, packet.length); } /** * Write a binary packet to the stream. * * @param buf * the packet to write * @param pos * first index within {@code buf}. * @param len * number of bytes to write. * @throws java.io.IOException * the packet could not be written, the stream is corrupted as * the packet may have been only partially written. * @since 4.5 */ public void writePacket(byte[] buf, int pos, int len) throws IOException { if (usingSideband) { formatLength(len + 5); out.write(lenbuffer, 0, 4); out.write(1); } else { formatLength(len + 4); out.write(lenbuffer, 0, 4); } out.write(buf, pos, len); if (logEnabled && log.isDebugEnabled()) { // Escape a trailing \n to avoid empty lines in the log. if (len > 0 && buf[pos + len - 1] == '\n') { log.debug( "git> " + RawParseUtils.decode(UTF_8, buf, pos, len - 1) //$NON-NLS-1$ + "\\n"); //$NON-NLS-1$ } else { log.debug("git> " + RawParseUtils.decode(UTF_8, buf, pos, len)); //$NON-NLS-1$ } } } /** * Write a packet delim marker (0001). * * @throws java.io.IOException * the marker could not be written, the stream is corrupted * as the marker may have been only partially written. * @since 5.0 */ public void writeDelim() throws IOException { formatLength(1); out.write(lenbuffer, 0, 4); if (logEnabled && log.isDebugEnabled()) { log.debug("git> 0001"); //$NON-NLS-1$ } } /** * Write a packet end marker, sometimes referred to as a flush command. *

* Technically this is a magical packet type which can be detected * separately from an empty string or an empty packet. *

* Implicitly performs a flush on the underlying OutputStream to ensure the * peer will receive all data written thus far. * * @throws java.io.IOException * the end marker could not be written, the stream is corrupted * as the end marker may have been only partially written. */ public void end() throws IOException { formatLength(0); out.write(lenbuffer, 0, 4); if (logEnabled && log.isDebugEnabled()) { log.debug("git> 0000"); //$NON-NLS-1$ } if (flushOnEnd) { flush(); } } /** * Flush the underlying OutputStream. *

* Performs a flush on the underlying OutputStream to ensure the peer will * receive all data written thus far. * * @throws java.io.IOException * the underlying stream failed to flush. */ public void flush() throws IOException { out.flush(); } private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private void formatLength(int w) { formatLength(lenbuffer, w); } static void formatLength(byte[] lenbuffer, int w) { int o = 3; while (o >= 0 && w != 0) { lenbuffer[o--] = hexchar[w & 0xf]; w >>>= 4; } while (o >= 0) lenbuffer[o--] = '0'; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1738 Content-Disposition: inline; filename="PostReceiveHook.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "c640de57ef0da59ac1012006ed4aaea4aa411c35" /* * Copyright (C) 2008, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; /** * Hook invoked by {@link org.eclipse.jgit.transport.ReceivePack} after all * updates are executed. *

* The hook is called after all commands have been processed. Only commands with * a status of {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK} are * passed into the hook. To get all commands within the hook, see * {@link org.eclipse.jgit.transport.ReceivePack#getAllCommands()}. *

* Any post-receive hook implementation should not update the status of a * command, as the command has already completed or failed, and the status has * already been returned to the client. *

* Hooks should execute quickly, as they block the server and the client from * completing the connection. */ public interface PostReceiveHook { /** A simple no-op hook. */ PostReceiveHook NULL = (final ReceivePack rp, final Collection commands) -> { // Do nothing. }; /** * Invoked after all commands are executed and status has been returned. * * @param rp * the process handling the current receive. Hooks may obtain * details about the destination repository through this handle. * @param commands * unmodifiable set of successfully completed commands. May be * the empty set. */ void onPostReceive(ReceivePack rp, Collection commands); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1589 Content-Disposition: inline; filename="PostReceiveHookChain.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "f9d2c38ad3f4e9d12e2bf91f11b109bb6dc16f2e" /* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import java.util.List; /** * {@link org.eclipse.jgit.transport.PostReceiveHook} that delegates to a list * of other hooks. *

* Hooks are run in the order passed to the constructor. */ public class PostReceiveHookChain implements PostReceiveHook { private final PostReceiveHook[] hooks; private final int count; /** * Create a new hook chaining the given hooks together. * * @param hooks * hooks to execute, in order. * @return a new hook chain of the given hooks. */ public static PostReceiveHook newChain( List hooks) { PostReceiveHook[] newHooks = new PostReceiveHook[hooks.size()]; int i = 0; for (PostReceiveHook hook : hooks) if (hook != PostReceiveHook.NULL) newHooks[i++] = hook; switch (i) { case 0: return PostReceiveHook.NULL; case 1: return newHooks[0]; default: return new PostReceiveHookChain(newHooks, i); } } @Override public void onPostReceive(ReceivePack rp, Collection commands) { for (int i = 0; i < count; i++) hooks[i].onPostReceive(rp, commands); } private PostReceiveHookChain(PostReceiveHook[] hooks, int count) { this.hooks = hooks; this.count = count; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2798 Content-Disposition: inline; filename="PostUploadHook.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "251bfe271adc06a61368c4546ce9efbd1149000b" /* * Copyright (C) 2015, Google Inc. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import org.eclipse.jgit.storage.pack.PackStatistics; /** * Hook invoked by {@link org.eclipse.jgit.transport.UploadPack} after the pack * has been uploaded. *

* Implementors of the interface are responsible for associating the current * thread to a particular connection, if they need to also include connection * information. One method is to use a {@link java.lang.ThreadLocal} to remember * the connection information before invoking UploadPack. * * @since 4.1 */ public interface PostUploadHook { /** A simple no-op hook. */ PostUploadHook NULL = (PackStatistics stats) -> { // Do nothing. }; /** * Notifies the hook that a pack has been sent. * * @param stats * the statistics gathered by * {@link org.eclipse.jgit.internal.storage.pack.PackWriter} for * the uploaded pack */ void onPostUpload(PackStatistics stats); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3162 Content-Disposition: inline; filename="PostUploadHookChain.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "072f43f41bd2d331e9d9b35471bc4314134e9876" /* * Copyright (C) 2015, Google Inc. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.eclipse.jgit.storage.pack.PackStatistics; /** * {@link org.eclipse.jgit.transport.PostUploadHook} that delegates to a list of * other hooks. *

* Hooks are run in the order passed to the constructor. * * @since 4.1 */ public class PostUploadHookChain implements PostUploadHook { private final List hooks; /** * Create a new hook chaining the given hooks together. * * @param hooks * hooks to execute, in order. * @return a new chain of the given hooks. */ public static PostUploadHook newChain(List hooks) { List newHooks = hooks.stream() .filter(hook -> !hook.equals(PostUploadHook.NULL)) .collect(Collectors.toList()); if (newHooks.isEmpty()) { return PostUploadHook.NULL; } else if (newHooks.size() == 1) { return newHooks.get(0); } else { return new PostUploadHookChain(newHooks); } } @Override public void onPostUpload(PackStatistics stats) { for (PostUploadHook hook : hooks) { hook.onPostUpload(stats); } } private PostUploadHookChain(List hooks) { this.hooks = Collections.unmodifiableList(hooks); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2598 Content-Disposition: inline; filename="PreReceiveHook.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "9bee73b4b70700dc0c75b41e486217cb1e5f4b0c" /* * Copyright (C) 2008, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; /** * Hook invoked by {@link org.eclipse.jgit.transport.ReceivePack} before any * updates are executed. *

* The hook is called with any commands that are deemed valid after parsing them * from the client and applying the standard receive configuration options to * them: *

    *
  • receive.denyDenyDeletes
  • *
  • receive.denyNonFastForwards
  • *
* This means the hook will not receive a non-fast-forward update command if * denyNonFastForwards is set to true in the configuration file. To get all * commands within the hook, see * {@link org.eclipse.jgit.transport.ReceivePack#getAllCommands()}. *

* As the hook is invoked prior to the commands being executed, the hook may * choose to block any command by setting its result status with * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(ReceiveCommand.Result)}. *

* The hook may also choose to perform the command itself (or merely pretend * that it has performed the command), by setting the result status to * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}. *

* Hooks should run quickly, as they block the caller thread and the client * process from completing. *

* Hooks may send optional messages back to the client via methods on * {@link org.eclipse.jgit.transport.ReceivePack}. Implementors should be aware * that not all network transports support this output, so some (or all) * messages may simply be discarded. These messages should be advisory only. */ public interface PreReceiveHook { /** A simple no-op hook. */ PreReceiveHook NULL = (final ReceivePack rp, final Collection commands) -> { // Do nothing. }; /** * Invoked just before commands are executed. *

* See the class description for how this method can impact execution. * * @param rp * the process handling the current receive. Hooks may obtain * details about the destination repository through this handle. * @param commands * unmodifiable set of valid commands still pending execution. * May be the empty set. */ void onPreReceive(ReceivePack rp, Collection commands); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1569 Content-Disposition: inline; filename="PreReceiveHookChain.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "2a5522bb4ce55c4f4e335a8e94d6217d0cfde797" /* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import java.util.List; /** * {@link org.eclipse.jgit.transport.PreReceiveHook} that delegates to a list of * other hooks. *

* Hooks are run in the order passed to the constructor. */ public class PreReceiveHookChain implements PreReceiveHook { private final PreReceiveHook[] hooks; private final int count; /** * Create a new hook chaining the given hooks together. * * @param hooks * hooks to execute, in order. * @return a new hook chain of the given hooks. */ public static PreReceiveHook newChain(List hooks) { PreReceiveHook[] newHooks = new PreReceiveHook[hooks.size()]; int i = 0; for (PreReceiveHook hook : hooks) if (hook != PreReceiveHook.NULL) newHooks[i++] = hook; switch (i) { case 0: return PreReceiveHook.NULL; case 1: return newHooks[0]; default: return new PreReceiveHookChain(newHooks, i); } } @Override public void onPreReceive(ReceivePack rp, Collection commands) { for (int i = 0; i < count; i++) hooks[i].onPreReceive(rp, commands); } private PreReceiveHookChain(PreReceiveHook[] hooks, int count) { this.hooks = hooks; this.count = count; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4034 Content-Disposition: inline; filename="PreUploadHook.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "5a8c8f27c91e11e91d6ed92c4d3610576d2f3f05" /* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import org.eclipse.jgit.lib.ObjectId; /** * Hook invoked by {@link org.eclipse.jgit.transport.UploadPack} before during * critical phases. *

* If any hook function throws * {@link org.eclipse.jgit.transport.ServiceMayNotContinueException} then * processing stops immediately and the exception is thrown up the call stack. * Most phases of UploadPack will try to report the exception's message text to * the end-user over the client's protocol connection. */ public interface PreUploadHook { /** A simple no-op hook. */ PreUploadHook NULL = new PreUploadHook() { @Override public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) throws ServiceMayNotContinueException { // Do nothing. } @Override public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) throws ServiceMayNotContinueException { // Do nothing. } @Override public void onSendPack(UploadPack up, Collection wants, Collection haves) throws ServiceMayNotContinueException { // Do nothing. } }; /** * Invoked before negotiation round is started. * * @param up * the upload pack instance handling the connection. * @param wants * the list of wanted objects. * @param cntOffered * number of objects the client has offered. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) throws ServiceMayNotContinueException; /** * Invoked after a negotiation round is completed. * * @param up * the upload pack instance handling the connection. * @param wants * the list of wanted objects. * @param cntCommon * number of objects this round found to be common. In a smart * HTTP transaction this includes the objects that were * previously found to be common. * @param cntNotFound * number of objects in this round the local repository does not * have, but that were offered as potential common bases. * @param ready * true if a pack is ready to be sent (the commit graph was * successfully cut). * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) throws ServiceMayNotContinueException; /** * Invoked just before a pack will be sent to the client. * * @param up * the upload pack instance handling the connection. * @param wants * the list of wanted objects. These may be RevObject or * RevCommit if the processed parsed them. Implementors should * not rely on the values being parsed. * @param haves * the list of common objects. Empty on an initial clone request. * These may be RevObject or RevCommit if the processed parsed * them. Implementors should not rely on the values being parsed. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. */ void onSendPack(UploadPack up, Collection wants, Collection haves) throws ServiceMayNotContinueException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2385 Content-Disposition: inline; filename="PreUploadHookChain.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "dc703d0b112ab4cd42d90debc33aab84ee0368ce" /* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.eclipse.jgit.lib.ObjectId; /** * {@link org.eclipse.jgit.transport.PreUploadHook} that delegates to a list of * other hooks. *

* Hooks are run in the order passed to the constructor. If running a method on * one hook throws an exception, execution of remaining hook methods is aborted. */ public class PreUploadHookChain implements PreUploadHook { private final List hooks; /** * Create a new hook chaining the given hooks together. * * @param hooks * hooks to execute, in order. * @return a new hook chain of the given hooks. */ public static PreUploadHook newChain(List hooks) { List newHooks = hooks.stream() .filter(hook -> !hook.equals(PreUploadHook.NULL)) .collect(Collectors.toList()); if (newHooks.isEmpty()) { return PreUploadHook.NULL; } else if (newHooks.size() == 1) { return newHooks.get(0); } else { return new PreUploadHookChain(newHooks); } } @Override public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) throws ServiceMayNotContinueException { for (PreUploadHook hook : hooks) { hook.onBeginNegotiateRound(up, wants, cntOffered); } } @Override public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) throws ServiceMayNotContinueException { for (PreUploadHook hook : hooks) { hook.onEndNegotiateRound(up, wants, cntCommon, cntNotFound, ready); } } @Override public void onSendPack(UploadPack up, Collection wants, Collection haves) throws ServiceMayNotContinueException { for (PreUploadHook hook : hooks) { hook.onSendPack(up, wants, haves); } } private PreUploadHookChain(List hooks) { this.hooks = Collections.unmodifiableList(hooks); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2910 Content-Disposition: inline; filename="ProgressSpinner.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "78ed7e2123aa5277d3defd9b27c82e1fd00d697c" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.TimeUnit; /** * A simple spinner connected to an {@code OutputStream}. *

* This is class is not thread-safe. The update method may only be used from a * single thread. Updates are sent only as frequently as {@link #update()} is * invoked by the caller, and are capped at no more than 2 times per second by * requiring at least 500 milliseconds between updates. * * @since 4.2 */ public class ProgressSpinner { private static final long MIN_REFRESH_MILLIS = 500; private static final char[] STATES = new char[] { '-', '\\', '|', '/' }; private final OutputStream out; private String msg; private int state; private boolean write; private boolean shown; private long nextUpdateMillis; /** * Initialize a new spinner. * * @param out * where to send output to. */ public ProgressSpinner(OutputStream out) { this.out = out; this.write = true; } /** * Begin a time consuming task. * * @param title * description of the task, suitable for human viewing. * @param delay * delay to wait before displaying anything at all. * @param delayUnits * unit for {@code delay}. */ public void beginTask(String title, long delay, TimeUnit delayUnits) { msg = title; state = 0; shown = false; long now = System.currentTimeMillis(); if (delay > 0) { nextUpdateMillis = now + delayUnits.toMillis(delay); } else { send(now); } } /** * Update the spinner if it is showing. */ public void update() { long now = System.currentTimeMillis(); if (now >= nextUpdateMillis) { send(now); state = (state + 1) % STATES.length; } } private void send(long now) { StringBuilder buf = new StringBuilder(msg.length() + 16); buf.append('\r').append(msg).append("... ("); //$NON-NLS-1$ buf.append(STATES[state]); buf.append(") "); //$NON-NLS-1$ shown = true; write(buf.toString()); nextUpdateMillis = now + MIN_REFRESH_MILLIS; } /** * Denote the current task completed. * * @param result * text to print after the task's title * {@code "$title ... $result"}. */ public void endTask(String result) { if (shown) { write('\r' + msg + "... " + result + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ } } private void write(String s) { if (write) { try { out.write(s.getBytes(UTF_8)); out.flush(); } catch (IOException e) { write = false; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5032 Content-Disposition: inline; filename="ProtocolV0Parser.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "f1afeab020ce3b6fb5f2062002c73790b76a4f54" /* * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; import java.io.EOFException; import java.io.IOException; import java.text.MessageFormat; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.transport.parser.FirstWant; import org.eclipse.jgit.lib.ObjectId; /** * Parser for git protocol versions 0 and 1. * * It reads the lines coming through the {@link PacketLineIn} and builds a * {@link FetchV0Request} object. * * It requires a transferConfig object to know if the server supports filters. */ final class ProtocolV0Parser { private final TransferConfig transferConfig; ProtocolV0Parser(TransferConfig transferConfig) { this.transferConfig = transferConfig; } /** * Parse an incoming protocol v1 upload request arguments from the wire. * * The incoming PacketLineIn is consumed until an END line, but the caller * is responsible for closing it (if needed). * * @param pckIn * incoming lines. This method will read until an END line. * @return a FetchV0Request with the data received in the wire. * @throws PackProtocolException * if a protocol occurred * @throws IOException * if an IO error occurred */ FetchV0Request recvWants(PacketLineIn pckIn) throws PackProtocolException, IOException { FetchV0Request.Builder reqBuilder = new FetchV0Request.Builder(); boolean isFirst = true; boolean filterReceived = false; for (;;) { String line; try { line = pckIn.readString(); } catch (EOFException eof) { if (isFirst) { break; } throw eof; } if (PacketLineIn.isEnd(line)) { break; } if (line.startsWith(PACKET_DEEPEN)) { int depth = Integer .parseInt(line.substring(PACKET_DEEPEN.length())); if (depth <= 0) { throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidDepth, Integer.valueOf(depth))); } if (reqBuilder.getDeepenSince() != 0) { throw new PackProtocolException( JGitText.get().deepenSinceWithDeepen); } if (reqBuilder.hasDeepenNots()) { throw new PackProtocolException( JGitText.get().deepenNotWithDeepen); } reqBuilder.setDepth(depth); continue; } if (line.startsWith(PACKET_DEEPEN_NOT)) { reqBuilder.addDeepenNot( line.substring(PACKET_DEEPEN_NOT.length())); if (reqBuilder.getDepth() != 0) { throw new PackProtocolException( JGitText.get().deepenNotWithDeepen); } continue; } if (line.startsWith(PACKET_DEEPEN_SINCE)) { // TODO: timestamps should be long int ts = Integer .parseInt(line.substring(PACKET_DEEPEN_SINCE.length())); if (ts <= 0) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidTimestamp, line)); } if (reqBuilder.getDepth() != 0) { throw new PackProtocolException( JGitText.get().deepenSinceWithDeepen); } reqBuilder.setDeepenSince(ts); continue; } if (line.startsWith(PACKET_SHALLOW)) { reqBuilder.addClientShallowCommit( ObjectId.fromString( line.substring(PACKET_SHALLOW.length()))); continue; } if (transferConfig.isAllowFilter() && line.startsWith(OPTION_FILTER + " ")) { //$NON-NLS-1$ String arg = line.substring(OPTION_FILTER.length() + 1); if (filterReceived) { throw new PackProtocolException( JGitText.get().tooManyFilters); } filterReceived = true; reqBuilder.setFilterSpec(FilterSpec.fromFilterLine(arg)); continue; } if (!line.startsWith(PACKET_WANT) || line.length() < 45) { throw new PackProtocolException(MessageFormat .format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$ } if (isFirst) { if (line.length() > 45) { FirstWant firstLine = FirstWant.fromLine(line); reqBuilder.addClientCapabilities(firstLine.getCapabilities()); reqBuilder.setAgent(firstLine.getAgent()); reqBuilder.setClientSID(firstLine.getClientSID()); line = firstLine.getLine(); } } reqBuilder.addWantId( ObjectId.fromString(line.substring(PACKET_WANT.length()))); isFirst = false; } return reqBuilder.build(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1993 Content-Disposition: inline; filename="ProtocolV2Hook.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "2caecbe8436ee534d0f4d7d90da4e26bd8b5ccc5" /* * Copyright (C) 2018, Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Hook to allow callers to be notified on Git protocol v2 requests. * * @see UploadPack#setProtocolV2Hook(ProtocolV2Hook) * @since 5.1 */ public interface ProtocolV2Hook { /** * The default hook implementation that does nothing. */ static ProtocolV2Hook DEFAULT = new ProtocolV2Hook() { // No override. }; /** * Handle capabilities request * * @param req * the capabilities request * @throws ServiceMayNotContinueException * abort; the message will be sent to the user * @since 5.1 */ default void onCapabilities(CapabilitiesV2Request req) throws ServiceMayNotContinueException { // Do nothing by default. } /** * Handle ls-refs request * * @param req * the ls-refs request * @throws ServiceMayNotContinueException * abort; the message will be sent to the user * @since 5.1 */ default void onLsRefs(LsRefsV2Request req) throws ServiceMayNotContinueException { // Do nothing by default. } /** * Handle fetch request * * @param req * the fetch request * @throws ServiceMayNotContinueException * abort; the message will be sent to the user */ default void onFetch(FetchV2Request req) throws ServiceMayNotContinueException { // Do nothing by default } /** * Handle object-info request * * @param req * the object-info request * @throws ServiceMayNotContinueException * abort; the message will be sent to the user * @since 5.13 */ default void onObjectInfo(ObjectInfoRequest req) throws ServiceMayNotContinueException { // Do nothing by default } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2257 Content-Disposition: inline; filename="ProtocolV2HookChain.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "7b3a4cea06b1fab51713bd30d6a4ea1d2fa1f8a4" /* * Copyright (C) 2019, Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * {@link org.eclipse.jgit.transport.ProtocolV2Hook} that delegates to a list of * other hooks. *

* Hooks are run in the order passed to the constructor. If running a method on * one hook throws an exception, execution of remaining hook methods is aborted. * * @since 5.5 */ public class ProtocolV2HookChain implements ProtocolV2Hook { private final List hooks; /** * Create a new hook chaining the given hooks together. * * @param hooks * hooks to execute, in order. * @return a new hook chain of the given hooks. */ public static ProtocolV2Hook newChain( List hooks) { List newHooks = hooks.stream() .filter(hook -> !hook.equals(ProtocolV2Hook.DEFAULT)) .collect(Collectors.toList()); if (newHooks.isEmpty()) { return ProtocolV2Hook.DEFAULT; } else if (newHooks.size() == 1) { return newHooks.get(0); } else { return new ProtocolV2HookChain(newHooks); } } @Override public void onCapabilities(CapabilitiesV2Request req) throws ServiceMayNotContinueException { for (ProtocolV2Hook hook : hooks) { hook.onCapabilities(req); } } @Override public void onLsRefs(LsRefsV2Request req) throws ServiceMayNotContinueException { for (ProtocolV2Hook hook : hooks) { hook.onLsRefs(req); } } @Override public void onFetch(FetchV2Request req) throws ServiceMayNotContinueException { for (ProtocolV2Hook hook : hooks) { hook.onFetch(req); } } @Override public void onObjectInfo(ObjectInfoRequest req) throws ServiceMayNotContinueException { for (ProtocolV2Hook hook : hooks) { hook.onObjectInfo(req); } } private ProtocolV2HookChain(List hooks) { this.hooks = Collections.unmodifiableList(hooks); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11708 Content-Disposition: inline; filename="ProtocolV2Parser.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "e437f22d02a85085180ebf800051b434ab8ba201" /* * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SERVER_OPTION; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT_REF; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; /** * Parse the incoming git protocol lines from the wire and translate them into a * Request object. * * It requires a transferConfig object to know what the server supports (e.g. * ref-in-want and/or filters). */ final class ProtocolV2Parser { private final TransferConfig transferConfig; ProtocolV2Parser(TransferConfig transferConfig) { this.transferConfig = transferConfig; } /* * Read lines until DELIM or END, calling the appropiate consumer. * * Returns the last read line (so caller can check if there is more to read * in the line). */ private static String consumeCapabilities(PacketLineIn pckIn, Consumer serverOptionConsumer, Consumer agentConsumer, Consumer clientSIDConsumer) throws IOException { String serverOptionPrefix = OPTION_SERVER_OPTION + '='; String agentPrefix = OPTION_AGENT + '='; String clientSIDPrefix = OPTION_SESSION_ID + '='; String line = pckIn.readString(); while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) { if (line.startsWith(serverOptionPrefix)) { serverOptionConsumer .accept(line.substring(serverOptionPrefix.length())); } else if (line.startsWith(agentPrefix)) { agentConsumer.accept(line.substring(agentPrefix.length())); } else if (line.startsWith(clientSIDPrefix)) { clientSIDConsumer .accept(line.substring(clientSIDPrefix.length())); } else { // Unrecognized capability. Ignore it. } line = pckIn.readString(); } return line; } /** * Parse the incoming fetch request arguments from the wire. The caller must * be sure that what is comings is a fetch request before coming here. * * @param pckIn * incoming lines * @return A FetchV2Request populated with information received from the * wire. * @throws PackProtocolException * incompatible options, wrong type of arguments or other issues * where the request breaks the protocol. * @throws IOException * an IO error prevented reading the incoming message. */ FetchV2Request parseFetchRequest(PacketLineIn pckIn) throws PackProtocolException, IOException { FetchV2Request.Builder reqBuilder = FetchV2Request.builder(); // Packs are always sent multiplexed and using full 64K // lengths. reqBuilder.addClientCapability(OPTION_SIDE_BAND_64K); String line = consumeCapabilities(pckIn, serverOption -> reqBuilder.addServerOption(serverOption), agent -> reqBuilder.setAgent(agent), clientSID -> reqBuilder.setClientSID(clientSID)); if (PacketLineIn.isEnd(line)) { return reqBuilder.build(); } if (!PacketLineIn.isDelimiter(line)) { throw new PackProtocolException( MessageFormat.format(JGitText.get().unexpectedPacketLine, line)); } boolean filterReceived = false; for (String line2 : pckIn.readStrings()) { if (line2.startsWith(PACKET_WANT)) { reqBuilder.addWantId(ObjectId .fromString(line2.substring(PACKET_WANT.length()))); } else if (transferConfig.isAllowRefInWant() && line2.startsWith(PACKET_WANT_REF)) { reqBuilder.addWantedRef( line2.substring(PACKET_WANT_REF.length())); } else if (line2.startsWith(PACKET_HAVE)) { reqBuilder.addPeerHas(ObjectId .fromString(line2.substring(PACKET_HAVE.length()))); } else if (line2.equals(PACKET_DONE)) { reqBuilder.setDoneReceived(); } else if (line2.equals(OPTION_WAIT_FOR_DONE)) { reqBuilder.setWaitForDone(); } else if (line2.equals(OPTION_THIN_PACK)) { reqBuilder.addClientCapability(OPTION_THIN_PACK); } else if (line2.equals(OPTION_NO_PROGRESS)) { reqBuilder.addClientCapability(OPTION_NO_PROGRESS); } else if (line2.equals(OPTION_INCLUDE_TAG)) { reqBuilder.addClientCapability(OPTION_INCLUDE_TAG); } else if (line2.equals(OPTION_OFS_DELTA)) { reqBuilder.addClientCapability(OPTION_OFS_DELTA); } else if (line2.startsWith(PACKET_SHALLOW)) { reqBuilder.addClientShallowCommit( ObjectId.fromString( line2.substring(PACKET_SHALLOW.length()))); } else if (line2.startsWith(PACKET_DEEPEN)) { int parsedDepth = Integer .parseInt(line2.substring(PACKET_DEEPEN.length())); if (parsedDepth <= 0) { throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidDepth, Integer.valueOf(parsedDepth))); } if (reqBuilder.getDeepenSince() != 0) { throw new PackProtocolException( JGitText.get().deepenSinceWithDeepen); } if (reqBuilder.hasDeepenNots()) { throw new PackProtocolException( JGitText.get().deepenNotWithDeepen); } reqBuilder.setDepth(parsedDepth); } else if (line2.startsWith(PACKET_DEEPEN_NOT)) { reqBuilder.addDeepenNot( line2.substring(PACKET_DEEPEN_NOT.length())); if (reqBuilder.getDepth() != 0) { throw new PackProtocolException( JGitText.get().deepenNotWithDeepen); } } else if (line2.equals(OPTION_DEEPEN_RELATIVE)) { reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE); } else if (line2.startsWith(PACKET_DEEPEN_SINCE)) { int ts = Integer.parseInt( line2.substring(PACKET_DEEPEN_SINCE.length())); if (ts <= 0) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidTimestamp, line2)); } if (reqBuilder.getDepth() != 0) { throw new PackProtocolException( JGitText.get().deepenSinceWithDeepen); } reqBuilder.setDeepenSince(ts); } else if (transferConfig.isAllowFilter() && line2.startsWith(OPTION_FILTER + ' ')) { if (filterReceived) { throw new PackProtocolException( JGitText.get().tooManyFilters); } filterReceived = true; reqBuilder.setFilterSpec(FilterSpec.fromFilterLine( line2.substring(OPTION_FILTER.length() + 1))); } else if (transferConfig.isAllowSidebandAll() && line2.equals(OPTION_SIDEBAND_ALL)) { reqBuilder.setSidebandAll(true); } else if (line2.startsWith("packfile-uris ")) { //$NON-NLS-1$ for (String s : line2.substring(14).split(",")) { //$NON-NLS-1$ reqBuilder.addPackfileUriProtocol(s); } } else { throw new PackProtocolException(MessageFormat .format(JGitText.get().unexpectedPacketLine, line2)); } } return reqBuilder.build(); } /** * Parse the incoming ls-refs request arguments from the wire. This is meant * for calling immediately after the caller has consumed a "command=ls-refs" * line indicating the beginning of a ls-refs request. * * The incoming PacketLineIn is consumed until an END line, but the caller * is responsible for closing it (if needed) * * @param pckIn * incoming lines. This method will read until an END line. * @return a LsRefsV2Request object with the data received in the wire. * @throws PackProtocolException * for inconsistencies in the protocol (e.g. unexpected lines) * @throws IOException * reporting problems reading the incoming messages from the * wire */ LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn) throws PackProtocolException, IOException { LsRefsV2Request.Builder builder = LsRefsV2Request.builder(); List prefixes = new ArrayList<>(); String line = consumeCapabilities(pckIn, serverOption -> builder.addServerOption(serverOption), agent -> builder.setAgent(agent), clientSID -> builder.setClientSID(clientSID)); if (PacketLineIn.isEnd(line)) { return builder.build(); } if (!PacketLineIn.isDelimiter(line)) { throw new PackProtocolException(MessageFormat .format(JGitText.get().unexpectedPacketLine, line)); } for (String line2 : pckIn.readStrings()) { if (line2.equals("peel")) { //$NON-NLS-1$ builder.setPeel(true); } else if (line2.equals("symrefs")) { //$NON-NLS-1$ builder.setSymrefs(true); } else if (line2.startsWith("ref-prefix ")) { //$NON-NLS-1$ prefixes.add(line2.substring("ref-prefix ".length())); //$NON-NLS-1$ } else { throw new PackProtocolException(MessageFormat .format(JGitText.get().unexpectedPacketLine, line2)); } } return builder.setRefPrefixes(prefixes).build(); } ObjectInfoRequest parseObjectInfoRequest(PacketLineIn pckIn) throws PackProtocolException, IOException { ObjectInfoRequest.Builder builder = ObjectInfoRequest.builder(); List objectIDs = new ArrayList<>(); String line = pckIn.readString(); if (PacketLineIn.isEnd(line)) { return builder.build(); } if (!PacketLineIn.isDelimiter(line)) { throw new PackProtocolException(MessageFormat .format(JGitText.get().unexpectedPacketLine, line)); } line = pckIn.readString(); if (!line.equals("size")) { //$NON-NLS-1$ throw new PackProtocolException(MessageFormat .format(JGitText.get().unexpectedPacketLine, line)); } for (String line2 : pckIn.readStrings()) { if (!line2.startsWith("oid ")) { //$NON-NLS-1$ throw new PackProtocolException(MessageFormat .format(JGitText.get().unexpectedPacketLine, line2)); } String oidStr = line2.substring("oid ".length()); //$NON-NLS-1$ try { objectIDs.add(ObjectId.fromString(oidStr)); } catch (InvalidObjectIdException e) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidObject, oidStr), e); } } return builder.setObjectIDs(objectIDs).build(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7434 Content-Disposition: inline; filename="PushCertificate.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "437abb06397e43963349e6deade177e267789501" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.PushCertificateParser.NONCE; import static org.eclipse.jgit.transport.PushCertificateParser.PUSHEE; import static org.eclipse.jgit.transport.PushCertificateParser.PUSHER; import static org.eclipse.jgit.transport.PushCertificateParser.VERSION; import java.text.MessageFormat; import java.util.List; import java.util.Objects; import org.eclipse.jgit.internal.JGitText; /** * The required information to verify the push. *

* A valid certificate will not return null from any getter methods; callers may * assume that any null value indicates a missing or invalid certificate. * * @since 4.0 */ public class PushCertificate { /** Verification result of the nonce returned during push. */ public enum NonceStatus { /** Nonce was not expected, yet client sent one anyway. */ UNSOLICITED, /** Nonce is invalid and did not match server's expectations. */ BAD, /** Nonce is required, but was not sent by client. */ MISSING, /** * Received nonce matches sent nonce, or is valid within the accepted slop * window. */ OK, /** Received nonce is valid, but outside the accepted slop window. */ SLOP } private final String version; private final PushCertificateIdent pusher; private final String pushee; private final String nonce; private final NonceStatus nonceStatus; private final List commands; private final String signature; PushCertificate(String version, PushCertificateIdent pusher, String pushee, String nonce, NonceStatus nonceStatus, List commands, String signature) { if (version == null || version.isEmpty()) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().pushCertificateInvalidField, VERSION)); } if (pusher == null) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().pushCertificateInvalidField, PUSHER)); } if (nonce == null || nonce.isEmpty()) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().pushCertificateInvalidField, NONCE)); } if (nonceStatus == null) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().pushCertificateInvalidField, "nonce status")); //$NON-NLS-1$ } if (commands == null || commands.isEmpty()) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().pushCertificateInvalidField, "command")); //$NON-NLS-1$ } if (signature == null || signature.isEmpty()) { throw new IllegalArgumentException( JGitText.get().pushCertificateInvalidSignature); } if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE) || !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) { throw new IllegalArgumentException( JGitText.get().pushCertificateInvalidSignature); } this.version = version; this.pusher = pusher; this.pushee = pushee; this.nonce = nonce; this.nonceStatus = nonceStatus; this.commands = commands; this.signature = signature; } /** * Get the certificate version string. * * @return the certificate version string. * @since 4.1 */ public String getVersion() { return version; } /** * Get the raw line that signed the cert, as a string. * * @return the raw line that signed the cert, as a string. * @since 4.0 */ public String getPusher() { return pusher.getRaw(); } /** * Get identity of the pusher who signed the cert. * * @return identity of the pusher who signed the cert. * @since 4.1 */ public PushCertificateIdent getPusherIdent() { return pusher; } /** * Get URL of the repository the push was originally sent to. * * @return URL of the repository the push was originally sent to. * @since 4.0 */ public String getPushee() { return pushee; } /** * Get the raw nonce value that was presented by the pusher. * * @return the raw nonce value that was presented by the pusher. * @since 4.1 */ public String getNonce() { return nonce; } /** * Get verification status of the nonce embedded in the certificate. * * @return verification status of the nonce embedded in the certificate. * @since 4.0 */ public NonceStatus getNonceStatus() { return nonceStatus; } /** * Get the list of commands as one string to be feed into the signature * verifier. * * @return the list of commands as one string to be feed into the signature * verifier. * @since 4.1 */ public List getCommands() { return commands; } /** * Get the raw signature * * @return the raw signature, consisting of the lines received between the * lines {@code "----BEGIN GPG SIGNATURE-----\n"} and * {@code "----END GPG SIGNATURE-----\n}", inclusive. * @since 4.0 */ public String getSignature() { return signature; } /** * Get text payload of the certificate for the signature verifier. * * @return text payload of the certificate for the signature verifier. * @since 4.1 */ public String toText() { return toStringBuilder().toString(); } /** * Get original text payload plus signature * * @return original text payload plus signature; the final output will be * valid as input to * {@link org.eclipse.jgit.transport.PushCertificateParser#fromString(String)}. * @since 4.1 */ public String toTextWithSignature() { return toStringBuilder().append(signature).toString(); } private StringBuilder toStringBuilder() { StringBuilder sb = new StringBuilder() .append(VERSION).append(' ').append(version).append('\n') .append(PUSHER).append(' ').append(getPusher()) .append('\n'); if (pushee != null) { sb.append(PUSHEE).append(' ').append(pushee).append('\n'); } sb.append(NONCE).append(' ').append(nonce).append('\n') .append('\n'); for (ReceiveCommand cmd : commands) { sb.append(cmd.getOldId().name()) .append(' ').append(cmd.getNewId().name()) .append(' ').append(cmd.getRefName()).append('\n'); } return sb; } @Override public int hashCode() { return signature.hashCode(); } @Override public boolean equals(Object o) { if (!(o instanceof PushCertificate)) { return false; } PushCertificate p = (PushCertificate) o; return version.equals(p.version) && pusher.equals(p.pusher) && Objects.equals(pushee, p.pushee) && nonceStatus == p.nonceStatus && signature.equals(p.signature) && commandsEqual(this, p); } private static boolean commandsEqual(PushCertificate c1, PushCertificate c2) { if (c1.commands.size() != c2.commands.size()) { return false; } for (int i = 0; i < c1.commands.size(); i++) { ReceiveCommand cmd1 = c1.commands.get(i); ReceiveCommand cmd2 = c2.commands.get(i); if (!cmd1.getOldId().equals(cmd2.getOldId()) || !cmd1.getNewId().equals(cmd2.getNewId()) || !cmd1.getRefName().equals(cmd2.getRefName())) { return false; } } return true; } @Override public String toString() { return getClass().getSimpleName() + '[' + toTextWithSignature() + ']'; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7179 Content-Disposition: inline; filename="PushCertificateIdent.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "bd76b34aef056d854eb737c729dd9980b18a74b5" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.util.RawParseUtils.lastIndexOfTrim; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.RawParseUtils; /** * Identity in a push certificate. *

* This is similar to a {@link org.eclipse.jgit.lib.PersonIdent} in that it * contains a name, timestamp, and timezone offset, but differs in the following * ways: *

    *
  • It is always parsed from a UTF-8 string, rather than a raw commit * buffer.
  • *
  • It is not guaranteed to contain a name and email portion, since any UTF-8 * string is a valid OpenPGP User ID (RFC4880 5.1.1). The raw User ID is always * available as {@link #getUserId()}, but {@link #getEmailAddress()} may return * null.
  • *
  • The raw text from which the identity was parsed is available with * {@link #getRaw()}. This is necessary for losslessly reconstructing the signed * push certificate payload.
  • *
  • *
* * @since 4.1 */ public class PushCertificateIdent { /** * Parse an identity from a string. *

* Spaces are trimmed when parsing the timestamp and timezone offset, with * one exception. The timestamp must be preceded by a single space, and the * rest of the string prior to that space (including any additional * whitespace) is treated as the OpenPGP User ID. *

* If either the timestamp or timezone offsets are missing, mimics * {@link RawParseUtils#parsePersonIdent(String)} behavior and sets them * both to zero. * * @param str * string to parse. * @return a {@link org.eclipse.jgit.transport.PushCertificateIdent} object. */ public static PushCertificateIdent parse(String str) { MutableInteger p = new MutableInteger(); byte[] raw = str.getBytes(UTF_8); int tzBegin = raw.length - 1; tzBegin = lastIndexOfTrim(raw, ' ', tzBegin); if (tzBegin < 0 || raw[tzBegin] != ' ') { return new PushCertificateIdent(str, str, 0, 0); } int whenBegin = tzBegin++; int tz = RawParseUtils.parseTimeZoneOffset(raw, tzBegin, p); boolean hasTz = p.value != tzBegin; whenBegin = lastIndexOfTrim(raw, ' ', whenBegin); if (whenBegin < 0 || raw[whenBegin] != ' ') { return new PushCertificateIdent(str, str, 0, 0); } int idEnd = whenBegin++; long when = RawParseUtils.parseLongBase10(raw, whenBegin, p); boolean hasWhen = p.value != whenBegin; if (hasTz && hasWhen) { idEnd = whenBegin - 1; } else { // If either tz or when are non-numeric, mimic parsePersonIdent behavior and // set them both to zero. tz = 0; when = 0; if (hasTz && !hasWhen) { // Only one trailing numeric field; assume User ID ends before this // field, but discard its value. idEnd = tzBegin - 1; } else { // No trailing numeric fields; User ID is whole raw value. idEnd = raw.length; } } String id = new String(raw, 0, idEnd, UTF_8); return new PushCertificateIdent(str, id, when * 1000L, tz); } private final String raw; private final String userId; private final long when; private final int tzOffset; /** * Construct a new identity from an OpenPGP User ID. * * @param userId * OpenPGP User ID; any UTF-8 string. * @param when * local time. * @param tzOffset * timezone offset; see {@link #getTimeZoneOffset()}. */ public PushCertificateIdent(String userId, long when, int tzOffset) { this.userId = userId; this.when = when; this.tzOffset = tzOffset; StringBuilder sb = new StringBuilder(userId).append(' ').append(when / 1000) .append(' '); PersonIdent.appendTimezone(sb, tzOffset); raw = sb.toString(); } private PushCertificateIdent(String raw, String userId, long when, int tzOffset) { this.raw = raw; this.userId = userId; this.when = when; this.tzOffset = tzOffset; } /** * Get the raw string from which this identity was parsed. *

* If the string was constructed manually, a suitable canonical string is * returned. *

* For the purposes of bytewise comparisons with other OpenPGP IDs, the string * must be encoded as UTF-8. * * @return the raw string. */ public String getRaw() { return raw; } /** * Get the OpenPGP User ID, which may be any string. * * @return the OpenPGP User ID, which may be any string. */ public String getUserId() { return userId; } /** * Get the name portion of the User ID. * * @return the name portion of the User ID. If no email address would be * parsed by {@link #getEmailAddress()}, returns the full User ID * with spaces trimmed. */ public String getName() { int nameEnd = userId.indexOf('<'); if (nameEnd < 0 || userId.indexOf('>', nameEnd) < 0) { nameEnd = userId.length(); } nameEnd--; while (nameEnd >= 0 && userId.charAt(nameEnd) == ' ') { nameEnd--; } int nameBegin = 0; while (nameBegin < nameEnd && userId.charAt(nameBegin) == ' ') { nameBegin++; } return userId.substring(nameBegin, nameEnd + 1); } /** * Get the email portion of the User ID * * @return the email portion of the User ID, if one was successfully parsed * from {@link #getUserId()}, or null. */ public String getEmailAddress() { int emailBegin = userId.indexOf('<'); if (emailBegin < 0) { return null; } int emailEnd = userId.indexOf('>', emailBegin); if (emailEnd < 0) { return null; } return userId.substring(emailBegin + 1, emailEnd); } /** * Get the timestamp of the identity. * * @return the timestamp of the identity. */ public Date getWhen() { return new Date(when); } /** * Get this person's declared time zone * * @return this person's declared time zone; null if the timezone is * unknown. */ public TimeZone getTimeZone() { return PersonIdent.getTimeZone(tzOffset); } /** * Get this person's declared time zone as minutes east of UTC. * * @return this person's declared time zone as minutes east of UTC. If the * timezone is to the west of UTC it is negative. */ public int getTimeZoneOffset() { return tzOffset; } @Override public boolean equals(Object o) { return (o instanceof PushCertificateIdent) && raw.equals(((PushCertificateIdent) o).raw); } @Override public int hashCode() { return raw.hashCode(); } @SuppressWarnings("nls") @Override public String toString() { SimpleDateFormat fmt; fmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US); fmt.setTimeZone(getTimeZone()); return getClass().getSimpleName() + "[raw=\"" + raw + "\"," + " userId=\"" + userId + "\"," + " " + fmt.format(Long.valueOf(when)) + "]"; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12925 Content-Disposition: inline; filename="PushCertificateParser.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "463d05393f740f57d7e363ec3687611bfabdd86e" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT; import static org.eclipse.jgit.transport.ReceivePack.parseCommand; import java.io.EOFException; import java.io.IOException; import java.io.Reader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PushCertificate.NonceStatus; import org.eclipse.jgit.util.IO; /** * Parser for signed push certificates. * * @since 4.0 */ public class PushCertificateParser { static final String BEGIN_SIGNATURE = "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$ static final String END_SIGNATURE = "-----END PGP SIGNATURE-----"; //$NON-NLS-1$ static final String VERSION = "certificate version"; //$NON-NLS-1$ static final String PUSHER = "pusher"; //$NON-NLS-1$ static final String PUSHEE = "pushee"; //$NON-NLS-1$ static final String NONCE = "nonce"; //$NON-NLS-1$ static final String END_CERT = "push-cert-end"; //$NON-NLS-1$ private static final String VERSION_0_1 = "0.1"; //$NON-NLS-1$ private static interface StringReader { /** * @return the next string from the input, up to an optional newline, with * newline stripped if present * * @throws EOFException * if EOF was reached. * @throws IOException * if an error occurred during reading. */ String read() throws EOFException, IOException; } private static class PacketLineReader implements StringReader { private final PacketLineIn pckIn; private PacketLineReader(PacketLineIn pckIn) { this.pckIn = pckIn; } @Override public String read() throws IOException { return pckIn.readString(); } } private static class StreamReader implements StringReader { private final Reader reader; private StreamReader(Reader reader) { this.reader = reader; } @Override public String read() throws IOException { // Presize for a command containing 2 SHA-1s and some refname. String line = IO.readLine(reader, 41 * 2 + 64); if (line.isEmpty()) { throw new EOFException(); } else if (line.charAt(line.length() - 1) == '\n') { line = line.substring(0, line.length() - 1); } return line; } } /** * Parse a push certificate from a reader. *

* Differences from the {@link org.eclipse.jgit.transport.PacketLineIn} * receiver methods: *

    *
  • Does not use pkt-line framing.
  • *
  • Reads an entire cert in one call rather than depending on a loop in * the caller.
  • *
  • Does not assume a {@code "push-cert-end"} line.
  • *
* * @param r * input reader; consumed only up until the end of the next * signature in the input. * @return the parsed certificate, or null if the reader was at EOF. * @throws org.eclipse.jgit.errors.PackProtocolException * if the certificate is malformed. * @throws java.io.IOException * if there was an error reading from the input. * @since 4.1 */ public static PushCertificate fromReader(Reader r) throws PackProtocolException, IOException { return new PushCertificateParser().parse(r); } /** * Parse a push certificate from a string. * * @see #fromReader(Reader) * @param str * input string. * @return the parsed certificate. * @throws org.eclipse.jgit.errors.PackProtocolException * if the certificate is malformed. * @throws java.io.IOException * if there was an error reading from the input. * @since 4.1 */ public static PushCertificate fromString(String str) throws PackProtocolException, IOException { return fromReader(new java.io.StringReader(str)); } private boolean received; private String version; private PushCertificateIdent pusher; private String pushee; /** The nonce that was sent to the client. */ private String sentNonce; /** * The nonce the pusher signed. *

* This may vary from {@link #sentNonce}; see git-core documentation for * reasons. */ private String receivedNonce; private NonceStatus nonceStatus; private String signature; /** Database we write the push certificate into. */ private final Repository db; /** * The maximum time difference which is acceptable between advertised nonce * and received signed nonce. */ private final int nonceSlopLimit; private final boolean enabled; private final NonceGenerator nonceGenerator; private final List commands = new ArrayList<>(); /** *

Constructor for PushCertificateParser.

* * @param into * destination repository for the push. * @param cfg * configuration for signed push. * @since 4.1 */ public PushCertificateParser(Repository into, SignedPushConfig cfg) { if (cfg != null) { nonceSlopLimit = cfg.getCertNonceSlopLimit(); nonceGenerator = cfg.getNonceGenerator(); } else { nonceSlopLimit = 0; nonceGenerator = null; } db = into; enabled = nonceGenerator != null; } private PushCertificateParser() { db = null; nonceSlopLimit = 0; nonceGenerator = null; enabled = true; } /** * Parse a push certificate from a reader. * * @see #fromReader(Reader) * @param r * input reader; consumed only up until the end of the next * signature in the input. * @return the parsed certificate, or null if the reader was at EOF. * @throws org.eclipse.jgit.errors.PackProtocolException * if the certificate is malformed. * @throws java.io.IOException * if there was an error reading from the input. * @since 4.1 */ public PushCertificate parse(Reader r) throws PackProtocolException, IOException { StreamReader reader = new StreamReader(r); receiveHeader(reader, true); String line; try { while (!(line = reader.read()).isEmpty()) { if (line.equals(BEGIN_SIGNATURE)) { receiveSignature(reader); break; } addCommand(line); } } catch (EOFException e) { // EOF reached, but might have been at a valid state. Let build call below // sort it out. } return build(); } /** * Build the parsed certificate * * @return the parsed certificate, or null if push certificates are * disabled. * @throws java.io.IOException * if the push certificate has missing or invalid fields. * @since 4.1 */ public PushCertificate build() throws IOException { if (!received || !enabled) { return null; } try { return new PushCertificate(version, pusher, pushee, receivedNonce, nonceStatus, Collections.unmodifiableList(commands), signature); } catch (IllegalArgumentException e) { throw new IOException(e.getMessage(), e); } } /** * Whether the repository is configured to use signed pushes in this * context. * * @return if the repository is configured to use signed pushes in this * context. * @since 4.0 */ public boolean enabled() { return enabled; } /** * Get the whole string for the nonce to be included into the capability * advertisement * * @return the whole string for the nonce to be included into the capability * advertisement, or null if push certificates are disabled. * @since 4.0 */ public String getAdvertiseNonce() { String nonce = sentNonce(); if (nonce == null) { return null; } return CAPABILITY_PUSH_CERT + '=' + nonce; } private String sentNonce() { if (sentNonce == null && nonceGenerator != null) { sentNonce = nonceGenerator.createNonce(db, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); } return sentNonce; } private static String parseHeader(StringReader reader, String header) throws IOException { return parseHeader(reader.read(), header); } private static String parseHeader(String s, String header) throws IOException { if (s.isEmpty()) { throw new EOFException(); } if (s.length() <= header.length() || !s.startsWith(header) || s.charAt(header.length()) != ' ') { throw new PackProtocolException(MessageFormat.format( JGitText.get().pushCertificateInvalidField, header)); } return s.substring(header.length() + 1); } /** * Receive a list of commands from the input encapsulated in a push * certificate. *

* This method doesn't parse the first line {@code "push-cert \NUL * "}, but assumes the first line including the * capabilities has already been handled by the caller. * * @param pckIn * where we take the push certificate header from. * @param stateless * affects nonce verification. When {@code stateless = true} the * {@code NonceGenerator} will allow for some time skew caused by * clients disconnected and reconnecting in the stateless smart * HTTP protocol. * @throws java.io.IOException * if the certificate from the client is badly malformed or the * client disconnects before sending the entire certificate. * @since 4.0 */ public void receiveHeader(PacketLineIn pckIn, boolean stateless) throws IOException { receiveHeader(new PacketLineReader(pckIn), stateless); } private void receiveHeader(StringReader reader, boolean stateless) throws IOException { try { try { version = parseHeader(reader, VERSION); } catch (EOFException e) { return; } received = true; if (!version.equals(VERSION_0_1)) { throw new PackProtocolException(MessageFormat.format( JGitText.get().pushCertificateInvalidFieldValue, VERSION, version)); } String rawPusher = parseHeader(reader, PUSHER); pusher = PushCertificateIdent.parse(rawPusher); if (pusher == null) { throw new PackProtocolException(MessageFormat.format( JGitText.get().pushCertificateInvalidFieldValue, PUSHER, rawPusher)); } String next = reader.read(); if (next.startsWith(PUSHEE)) { pushee = parseHeader(next, PUSHEE); receivedNonce = parseHeader(reader, NONCE); } else { receivedNonce = parseHeader(next, NONCE); } nonceStatus = nonceGenerator != null ? nonceGenerator.verify( receivedNonce, sentNonce(), db, stateless, nonceSlopLimit) : NonceStatus.UNSOLICITED; // An empty line. if (!reader.read().isEmpty()) { throw new PackProtocolException( JGitText.get().pushCertificateInvalidHeader); } } catch (EOFException eof) { throw new PackProtocolException( JGitText.get().pushCertificateInvalidHeader, eof); } } /** * Read the PGP signature. *

* This method assumes the line * {@code "-----BEGIN PGP SIGNATURE-----"} has already been parsed, * and continues parsing until an {@code "-----END PGP SIGNATURE-----"} is * found, followed by {@code "push-cert-end"}. * * @param pckIn * where we read the signature from. * @throws java.io.IOException * if the signature is invalid. * @since 4.0 */ public void receiveSignature(PacketLineIn pckIn) throws IOException { StringReader reader = new PacketLineReader(pckIn); receiveSignature(reader); if (!reader.read().equals(END_CERT)) { throw new PackProtocolException( JGitText.get().pushCertificateInvalidSignature); } } private void receiveSignature(StringReader reader) throws IOException { received = true; try { StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n'); String line; while (!(line = reader.read()).equals(END_SIGNATURE)) { sig.append(line).append('\n'); } signature = sig.append(END_SIGNATURE).append('\n').toString(); } catch (EOFException eof) { throw new PackProtocolException( JGitText.get().pushCertificateInvalidSignature, eof); } } /** * Add a command to the signature. * * @param cmd * the command. * @since 4.1 */ public void addCommand(ReceiveCommand cmd) { commands.add(cmd); } /** * Add a command to the signature. * * @param line * the line read from the wire that produced this * command, with optional trailing newline already trimmed. * @throws org.eclipse.jgit.errors.PackProtocolException * if the raw line cannot be parsed to a command. * @since 4.0 */ public void addCommand(String line) throws PackProtocolException { commands.add(parseCommand(line)); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 14906 Content-Disposition: inline; filename="PushCertificateStore.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "6bdaf0e2340fb6ba1f26114d722da31e9e520008" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; import static org.eclipse.jgit.lib.FileMode.TYPE_FILE; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; /** * Storage for recorded push certificates. *

* Push certificates are stored in a special ref {@code refs/meta/push-certs}. * The filenames in the tree are ref names followed by the special suffix * @{cert}, and the contents are the latest push cert affecting * that ref. The special suffix allows storing certificates for both refs/foo * and refs/foo/bar in case those both existed at some point. * * @since 4.1 */ public class PushCertificateStore implements AutoCloseable { /** Ref name storing push certificates. */ static final String REF_NAME = Constants.R_REFS + "meta/push-certs"; //$NON-NLS-1$ private static class PendingCert { PushCertificate cert; PersonIdent ident; Collection matching; PendingCert(PushCertificate cert, PersonIdent ident, Collection matching) { this.cert = cert; this.ident = ident; this.matching = matching; } } private final Repository db; private final List pending; ObjectReader reader; RevCommit commit; /** * Create a new store backed by the given repository. * * @param db * the repository. */ public PushCertificateStore(Repository db) { this.db = db; pending = new ArrayList<>(); } /** * {@inheritDoc} *

* Close resources opened by this store. *

* If {@link #get(String)} was called, closes the cached object reader * created by that method. Does not close the underlying repository. */ @Override public void close() { if (reader != null) { reader.close(); reader = null; commit = null; } } /** * Get latest push certificate associated with a ref. *

* Lazily opens {@code refs/meta/push-certs} and reads from the repository as * necessary. The state is cached between calls to {@code get}; to reread the, * call {@link #close()} first. * * @param refName * the ref name to get the certificate for. * @return last certificate affecting the ref, or null if no cert was recorded * for the last update to this ref. * @throws java.io.IOException * if a problem occurred reading the repository. */ public PushCertificate get(String refName) throws IOException { if (reader == null) { load(); } try (TreeWalk tw = newTreeWalk(refName)) { return read(tw); } } /** * Iterate over all push certificates affecting a ref. *

* Only includes push certificates actually stored in the tree; see class * Javadoc for conditions where this might not include all push certs ever * seen for this ref. *

* The returned iterable may be iterated multiple times, and push certs will * be re-read from the current state of the store on each call to {@link * Iterable#iterator()}. However, method calls on the returned iterator may * fail if {@code save} or {@code close} is called on the enclosing store * during iteration. * * @param refName * the ref name to get certificates for. * @return iterable over certificates; must be fully iterated in order to * close resources. */ public Iterable getAll(String refName) { return () -> new Iterator<>() { private final String path = pathName(refName); private PushCertificate next; private RevWalk rw; { try { if (reader == null) { load(); } if (commit != null) { rw = new RevWalk(reader); rw.setTreeFilter(AndTreeFilter.create( PathFilterGroup.create(Collections .singleton(PathFilter.create(path))), TreeFilter.ANY_DIFF)); rw.setRewriteParents(false); rw.markStart(rw.parseCommit(commit)); } else { rw = null; } } catch (IOException e) { throw new RuntimeException(e); } } @Override public boolean hasNext() { try { if (next == null) { if (rw == null) { return false; } try { RevCommit c = rw.next(); if (c != null) { try (TreeWalk tw = TreeWalk.forPath( rw.getObjectReader(), path, c.getTree())) { next = read(tw); } } else { next = null; } } catch (IOException e) { throw new RuntimeException(e); } } return next != null; } finally { if (next == null && rw != null) { rw.close(); rw = null; } } } @Override public PushCertificate next() { if (!hasNext()) { throw new NoSuchElementException(); } PushCertificate n = next; next = null; return n; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } void load() throws IOException { close(); reader = db.newObjectReader(); Ref ref = db.getRefDatabase().exactRef(REF_NAME); if (ref == null) { // No ref, same as empty. return; } try (RevWalk rw = new RevWalk(reader)) { commit = rw.parseCommit(ref.getObjectId()); } } static PushCertificate read(TreeWalk tw) throws IOException { if (tw == null || (tw.getRawMode(0) & TYPE_FILE) != TYPE_FILE) { return null; } ObjectLoader loader = tw.getObjectReader().open(tw.getObjectId(0), OBJ_BLOB); try (InputStream in = loader.openStream(); Reader r = new BufferedReader( new InputStreamReader(in, UTF_8))) { return PushCertificateParser.fromReader(r); } } /** * Put a certificate to be saved to the store. *

* Writes the contents of this certificate for each ref mentioned. It is up * to the caller to ensure this certificate accurately represents the state * of the ref. *

* Pending certificates added to this method are not returned by * {@link #get(String)} and {@link #getAll(String)} until after calling * {@link #save()}. * * @param cert * certificate to store. * @param ident * identity for the commit that stores this certificate. Pending * certificates are sorted by identity timestamp during * {@link #save()}. */ public void put(PushCertificate cert, PersonIdent ident) { put(cert, ident, null); } /** * Put a certificate to be saved to the store, matching a set of commands. *

* Like {@link #put(PushCertificate, PersonIdent)}, except a value is only * stored for a push certificate if there is a corresponding command in the * list that exactly matches the old/new values mentioned in the push * certificate. *

* Pending certificates added to this method are not returned by * {@link #get(String)} and {@link #getAll(String)} until after calling * {@link #save()}. * * @param cert * certificate to store. * @param ident * identity for the commit that stores this certificate. Pending * certificates are sorted by identity timestamp during * {@link #save()}. * @param matching * only store certs for the refs listed in this list whose values * match the commands in the cert. */ public void put(PushCertificate cert, PersonIdent ident, Collection matching) { pending.add(new PendingCert(cert, ident, matching)); } /** * Save pending certificates to the store. *

* One commit is created per certificate added with * {@link #put(PushCertificate, PersonIdent)}, in order of identity * timestamps, and a single ref update is performed. *

* The pending list is cleared if and only the ref update fails, which * allows for easy retries in case of lock failure. * * @return the result of attempting to update the ref. * @throws java.io.IOException * if there was an error reading from or writing to the * repository. */ public RefUpdate.Result save() throws IOException { ObjectId newId = write(); if (newId == null) { return RefUpdate.Result.NO_CHANGE; } try { RefUpdate.Result result = updateRef(newId); switch (result) { case FAST_FORWARD: case NEW: case NO_CHANGE: pending.clear(); break; default: break; } return result; } finally { close(); } } /** * Save pending certificates to the store in an existing batch ref update. *

* One commit is created per certificate added with * {@link #put(PushCertificate, PersonIdent)}, in order of identity * timestamps, all commits are flushed, and a single command is added to the * batch. *

* The cached ref value and pending list are not cleared. If the * ref update succeeds, the caller is responsible for calling * {@link #close()} and/or {@link #clear()}. * * @param batch * update to save to. * @return whether a command was added to the batch. * @throws java.io.IOException * if there was an error reading from or writing to the * repository. */ public boolean save(BatchRefUpdate batch) throws IOException { ObjectId newId = write(); if (newId == null || newId.equals(commit)) { return false; } batch.addCommand(new ReceiveCommand( commit != null ? commit : ObjectId.zeroId(), newId, REF_NAME)); return true; } /** * Clear pending certificates added with {@link #put(PushCertificate, * PersonIdent)}. */ public void clear() { pending.clear(); } private ObjectId write() throws IOException { if (pending.isEmpty()) { return null; } if (reader == null) { load(); } sortPending(pending); ObjectId curr = commit; DirCache dc = newDirCache(); try (ObjectInserter inserter = db.newObjectInserter()) { for (PendingCert pc : pending) { curr = saveCert(inserter, dc, pc, curr); } inserter.flush(); return curr; } } private static void sortPending(List pending) { Collections.sort(pending, Comparator.comparing((PendingCert a) -> a.ident.getWhenAsInstant())); } private DirCache newDirCache() throws IOException { if (commit != null) { return DirCache.read(reader, commit.getTree()); } return DirCache.newInCore(); } private ObjectId saveCert(ObjectInserter inserter, DirCache dc, PendingCert pc, ObjectId curr) throws IOException { Map byRef; if (pc.matching != null) { byRef = new HashMap<>(); for (ReceiveCommand cmd : pc.matching) { if (byRef.put(cmd.getRefName(), cmd) != null) { throw new IllegalStateException(); } } } else { byRef = null; } DirCacheEditor editor = dc.editor(); String certText = pc.cert.toText() + pc.cert.getSignature(); final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(UTF_8)); boolean any = false; for (ReceiveCommand cmd : pc.cert.getCommands()) { if (byRef != null && !commandsEqual(cmd, byRef.get(cmd.getRefName()))) { continue; } any = true; editor.add(new PathEdit(pathName(cmd.getRefName())) { @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.REGULAR_FILE); ent.setObjectId(certId); } }); } if (!any) { return curr; } editor.finish(); CommitBuilder cb = new CommitBuilder(); cb.setAuthor(pc.ident); cb.setCommitter(pc.ident); cb.setTreeId(dc.writeTree(inserter)); if (curr != null) { cb.setParentId(curr); } else { cb.setParentIds(Collections. emptyList()); } cb.setMessage(buildMessage(pc.cert)); return inserter.insert(OBJ_COMMIT, cb.build()); } private static boolean commandsEqual(ReceiveCommand c1, ReceiveCommand c2) { if (c1 == null || c2 == null) { return c1 == c2; } return c1.getRefName().equals(c2.getRefName()) && c1.getOldId().equals(c2.getOldId()) && c1.getNewId().equals(c2.getNewId()); } private RefUpdate.Result updateRef(ObjectId newId) throws IOException { RefUpdate ru = db.updateRef(REF_NAME); ru.setExpectedOldObjectId(commit != null ? commit : ObjectId.zeroId()); ru.setNewObjectId(newId); ru.setRefLogIdent(pending.get(pending.size() - 1).ident); ru.setRefLogMessage(JGitText.get().storePushCertReflog, false); try (RevWalk rw = new RevWalk(reader)) { return ru.update(rw); } } private TreeWalk newTreeWalk(String refName) throws IOException { if (commit == null) { return null; } return TreeWalk.forPath(reader, pathName(refName), commit.getTree()); } static String pathName(String refName) { return refName + "@{cert}"; //$NON-NLS-1$ } private static String buildMessage(PushCertificate cert) { StringBuilder sb = new StringBuilder(); if (cert.getCommands().size() == 1) { sb.append(MessageFormat.format( JGitText.get().storePushCertOneRef, cert.getCommands().get(0).getRefName())); } else { sb.append(MessageFormat.format( JGitText.get().storePushCertMultipleRefs, cert.getCommands().size())); } return sb.append('\n').toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3765 Content-Disposition: inline; filename="PushConfig.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "c8774d546a76a68b62d75a09f5647d76d34e8738" /* * Copyright (C) 2017, 2022 David Pursehouse and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Locale; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.util.StringUtils; /** * Push section of a Git configuration file. * * @since 4.9 */ public class PushConfig { /** * Git config values for {@code push.recurseSubmodules}. */ public enum PushRecurseSubmodulesMode implements Config.ConfigEnum { /** * Verify that all submodule commits that changed in the revisions to be * pushed are available on at least one remote of the submodule. */ CHECK("check"), //$NON-NLS-1$ /** * All submodules that changed in the revisions to be pushed will be * pushed. */ ON_DEMAND("on-demand"), //$NON-NLS-1$ /** Default behavior of ignoring submodules when pushing is retained. */ NO("false"); //$NON-NLS-1$ private final String configValue; private PushRecurseSubmodulesMode(String configValue) { this.configValue = configValue; } @Override public String toConfigValue() { return configValue; } @Override public boolean matchConfigValue(String s) { if (StringUtils.isEmptyOrNull(s)) { return false; } s = s.replace('-', '_'); return name().equalsIgnoreCase(s) || configValue.equalsIgnoreCase(s); } } /** * Git config values for {@code push.default}. * * @since 6.1 */ public enum PushDefault implements Config.ConfigEnum { /** * Do not push if there are no explicit refspecs. */ NOTHING, /** * Push the current branch to an upstream branch of the same name. */ CURRENT, /** * Push the current branch to an upstream branch determined by git * config {@code branch..merge}. */ UPSTREAM("tracking"), //$NON-NLS-1$ /** * Like {@link #UPSTREAM}, but only if the upstream name is the same as * the name of the current local branch. */ SIMPLE, /** * Push all current local branches that match a configured push refspec * of the remote configuration. */ MATCHING; private final String alias; private PushDefault() { alias = null; } private PushDefault(String alias) { this.alias = alias; } @Override public String toConfigValue() { return name().toLowerCase(Locale.ROOT); } @Override public boolean matchConfigValue(String in) { return toConfigValue().equalsIgnoreCase(in) || (alias != null && alias.equalsIgnoreCase(in)); } } private final PushRecurseSubmodulesMode recurseSubmodules; private final PushDefault pushDefault; /** * Creates a new instance. * * @param config * {@link Config} to fill the {@link PushConfig} from * @since 6.1 */ public PushConfig(Config config) { recurseSubmodules = config.getEnum(ConfigConstants.CONFIG_PUSH_SECTION, null, ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, PushRecurseSubmodulesMode.NO); pushDefault = config.getEnum(ConfigConstants.CONFIG_PUSH_SECTION, null, ConfigConstants.CONFIG_KEY_DEFAULT, PushDefault.SIMPLE); } /** * Retrieves the value of git config {@code push.recurseSubmodules}. * * @return the value * @since 6.1 */ public PushRecurseSubmodulesMode getRecurseSubmodules() { return recurseSubmodules; } /** * Retrieves the value of git config {@code push.default}. * * @return the value * @since 6.1 */ public PushDefault getPushDefault() { return pushDefault; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6151 Content-Disposition: inline; filename="PushConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "32c443f0f33f6a7b453b6590fbda0d75d104976e" /* * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.OutputStream; import java.util.Map; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ProgressMonitor; /** * Lists known refs from the remote and sends objects to the remote. *

* A push connection typically connects to the git-receive-pack * service running where the remote repository is stored. This provides a * one-way object transfer service to copy objects from the local repository * into the remote repository, as well as a way to modify the refs stored by the * remote repository. *

* Instances of a PushConnection must be created by a * {@link org.eclipse.jgit.transport.Transport} that implements a specific * object transfer protocol that both sides of the connection understand. *

* PushConnection instances are not thread safe and may be accessed by only one * thread at a time. * * @see Transport */ public interface PushConnection extends Connection { /** * Pushes to the remote repository basing on provided specification. This * possibly result in update/creation/deletion of refs on remote repository * and sending objects that remote repository need to have a consistent * objects graph from new refs. *

* Only one call per connection is allowed. Subsequent calls will result in * {@link org.eclipse.jgit.errors.TransportException}. *

* Implementation may use local repository to send a minimum set of objects * needed by remote repository in efficient way. * {@link org.eclipse.jgit.transport.Transport#isPushThin()} should be * honored if applicable. refUpdates should be filled with information about * status of each update. * * @param monitor * progress monitor to update the end-user about the amount of * work completed, or to indicate cancellation. Implementors * should poll the monitor at regular intervals to look for * cancellation requests from the user. * @param refUpdates * map of remote refnames to remote refs update * specifications/statuses. Can't be empty. This indicate what * refs caller want to update on remote side. Only refs updates * with * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * should passed. Implementation must ensure that and appropriate * status with optional message should be set during call. No * refUpdate with * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#AWAITING_REPORT} * or * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * can be leaved by implementation after return from this call. * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * critical protocol error, or error on remote side, or * connection was already used for push - new connection must be * created. Non-critical errors concerning only isolated refs * should be placed in refUpdates. */ void push(final ProgressMonitor monitor, final Map refUpdates) throws TransportException; /** * Pushes to the remote repository basing on provided specification. This * possibly result in update/creation/deletion of refs on remote repository * and sending objects that remote repository need to have a consistent * objects graph from new refs. *

* Only one call per connection is allowed. Subsequent calls will result in * {@link org.eclipse.jgit.errors.TransportException}. *

* Implementation may use local repository to send a minimum set of objects * needed by remote repository in efficient way. * {@link org.eclipse.jgit.transport.Transport#isPushThin()} should be * honored if applicable. refUpdates should be filled with information about * status of each update. * * @param monitor * progress monitor to update the end-user about the amount of * work completed, or to indicate cancellation. Implementors * should poll the monitor at regular intervals to look for * cancellation requests from the user. * @param refUpdates * map of remote refnames to remote refs update * specifications/statuses. Can't be empty. This indicate what * refs caller want to update on remote side. Only refs updates * with * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * should passed. Implementation must ensure that and appropriate * status with optional message should be set during call. No * refUpdate with * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#AWAITING_REPORT} * or * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * can be leaved by implementation after return from this call. * @param out * output stream to write sideband messages to * @throws org.eclipse.jgit.errors.TransportException * objects could not be copied due to a network failure, * critical protocol error, or error on remote side, or * connection was already used for push - new connection must be * created. Non-critical errors concerning only isolated refs * should be placed in refUpdates. * @since 3.0 */ void push(final ProgressMonitor monitor, final Map refUpdates, OutputStream out) throws TransportException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 14197 Content-Disposition: inline; filename="PushProcess.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "8f90f326d39687fba41e55c7eb8086cb6b4c9a1c" /* * Copyright (C) 2008, 2022 Marek Zawirski and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.hooks.PrePushHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; /** * Class performing push operation on remote repository. * * @see Transport#push(ProgressMonitor, Collection, OutputStream) */ class PushProcess { /** Task name for {@link ProgressMonitor} used during opening connection. */ static final String PROGRESS_OPENING_CONNECTION = JGitText.get().openingConnection; /** Transport used to perform this operation. */ private final Transport transport; /** Push operation connection created to perform this operation */ private PushConnection connection; /** Refs to update on remote side. */ private final Map toPush; /** Revision walker for checking some updates properties. */ private final RevWalk walker; /** an outputstream to write messages to */ private final OutputStream out; /** A list of option strings associated with this push */ private List pushOptions; private final PrePushHook prePush; /** * Create process for specified transport and refs updates specification. * * @param transport * transport between remote and local repository, used to create * connection. * @param toPush * specification of refs updates (and local tracking branches). * @param prePush * {@link PrePushHook} to run after the remote advertisement has * been gotten * @throws TransportException * if a protocol error occurred during push/fetch */ PushProcess(Transport transport, Collection toPush, PrePushHook prePush) throws TransportException { this(transport, toPush, prePush, null); } /** * Create process for specified transport and refs updates specification. * * @param transport * transport between remote and local repository, used to create * connection. * @param toPush * specification of refs updates (and local tracking branches). * @param prePush * {@link PrePushHook} to run after the remote advertisement has * been gotten * @param out * OutputStream to write messages to * @throws TransportException * if a protocol error occurred during push/fetch */ PushProcess(Transport transport, Collection toPush, PrePushHook prePush, OutputStream out) throws TransportException { this.walker = new RevWalk(transport.local); this.transport = transport; this.toPush = new LinkedHashMap<>(); this.prePush = prePush; this.out = out; this.pushOptions = transport.getPushOptions(); for (RemoteRefUpdate rru : toPush) { if (this.toPush.put(rru.getRemoteName(), rru) != null) throw new TransportException(MessageFormat.format( JGitText.get().duplicateRemoteRefUpdateIsIllegal, rru.getRemoteName())); } } /** * Perform push operation between local and remote repository - set remote * refs appropriately, send needed objects and update local tracking refs. *

* When {@link Transport#isDryRun()} is true, result of this operation is * just estimation of real operation result, no real action is performed. * * @param monitor * progress monitor used for feedback about operation. * @return result of push operation with complete status description. * @throws NotSupportedException * when push operation is not supported by provided transport. * @throws TransportException * when some error occurred during operation, like I/O, protocol * error, or local database consistency error. */ PushResult execute(ProgressMonitor monitor) throws NotSupportedException, TransportException { try { monitor.beginTask(PROGRESS_OPENING_CONNECTION, ProgressMonitor.UNKNOWN); final PushResult res = new PushResult(); connection = transport.openPush(); try { res.setAdvertisedRefs(transport.getURI(), connection .getRefsMap()); res.peerUserAgent = connection.getPeerUserAgent(); monitor.endTask(); Map expanded = expandMatching(); toPush.clear(); toPush.putAll(expanded); res.setRemoteUpdates(toPush); final Map preprocessed = prepareRemoteUpdates(); List willBeAttempted = preprocessed.values() .stream().filter(u -> { switch (u.getStatus()) { case NON_EXISTING: case REJECTED_NODELETE: case REJECTED_NONFASTFORWARD: case REJECTED_OTHER_REASON: case REJECTED_REMOTE_CHANGED: case UP_TO_DATE: return false; default: return true; } }).collect(Collectors.toList()); if (!willBeAttempted.isEmpty()) { if (prePush != null) { try { prePush.setRefs(willBeAttempted); prePush.setDryRun(transport.isDryRun()); prePush.call(); } catch (AbortedByHookException | IOException e) { throw new TransportException(e.getMessage(), e); } } } if (transport.isDryRun()) modifyUpdatesForDryRun(); else if (!preprocessed.isEmpty()) connection.push(monitor, preprocessed, out); } finally { connection.close(); res.addMessages(connection.getMessages()); } if (!transport.isDryRun()) updateTrackingRefs(); for (RemoteRefUpdate rru : toPush.values()) { final TrackingRefUpdate tru = rru.getTrackingRefUpdate(); if (tru != null) res.add(tru); } return res; } finally { walker.close(); } } private Map prepareRemoteUpdates() throws TransportException { boolean atomic = transport.isPushAtomic(); final Map result = new LinkedHashMap<>(); for (RemoteRefUpdate rru : toPush.values()) { final Ref advertisedRef = connection.getRef(rru.getRemoteName()); ObjectId advertisedOld = null; if (advertisedRef != null) { advertisedOld = advertisedRef.getObjectId(); } if (advertisedOld == null) { advertisedOld = ObjectId.zeroId(); } if (rru.getNewObjectId().equals(advertisedOld)) { if (rru.isDelete()) { // ref does exist neither locally nor remotely rru.setStatus(Status.NON_EXISTING); } else { // same object - nothing to do rru.setStatus(Status.UP_TO_DATE); } continue; } // caller has explicitly specified expected old object id, while it // has been changed in the mean time - reject if (rru.isExpectingOldObjectId() && !rru.getExpectedOldObjectId().equals(advertisedOld)) { rru.setStatus(Status.REJECTED_REMOTE_CHANGED); if (atomic) { return rejectAll(); } continue; } if (!rru.isExpectingOldObjectId()) { rru.setExpectedOldObjectId(advertisedOld); } // create ref (hasn't existed on remote side) and delete ref // are always fast-forward commands, feasible at this level if (advertisedOld.equals(ObjectId.zeroId()) || rru.isDelete()) { rru.setFastForward(true); result.put(rru.getRemoteName(), rru); continue; } boolean fastForward = isFastForward(advertisedOld, rru.getNewObjectId()); rru.setFastForward(fastForward); if (!fastForward && !rru.isForceUpdate()) { rru.setStatus(Status.REJECTED_NONFASTFORWARD); if (atomic) { return rejectAll(); } } else { result.put(rru.getRemoteName(), rru); } } return result; } /** * Determines whether an update from {@code oldOid} to {@code newOid} is a * fast-forward update: *

    *
  • both old and new must be commits, AND
  • *
  • both of them must be known to us and exist in the repository, * AND
  • *
  • the old commit must be an ancestor of the new commit.
  • *
* * @param oldOid * {@link ObjectId} of the old commit * @param newOid * {@link ObjectId} of the new commit * @return {@code true} if the update fast-forwards, {@code false} otherwise * @throws TransportException * if a protocol error occurred during push/fetch */ private boolean isFastForward(ObjectId oldOid, ObjectId newOid) throws TransportException { try { RevObject oldRev = walker.parseAny(oldOid); RevObject newRev = walker.parseAny(newOid); if (!(oldRev instanceof RevCommit) || !(newRev instanceof RevCommit) || !walker.isMergedInto((RevCommit) oldRev, (RevCommit) newRev)) { return false; } } catch (MissingObjectException x) { return false; } catch (Exception x) { throw new TransportException(transport.getURI(), MessageFormat.format(JGitText .get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x); } return true; } /** * Expands all placeholder {@link RemoteRefUpdate}s for "matching" * {@link RefSpec}s ":" in {@link #toPush} and returns the resulting map in * which the placeholders have been replaced by their expansion. * * @return a new map of {@link RemoteRefUpdate}s keyed by remote name * @throws TransportException * if the expansion results in duplicate updates */ private Map expandMatching() throws TransportException { Map result = new LinkedHashMap<>(); boolean hadMatch = false; for (RemoteRefUpdate update : toPush.values()) { if (update.isMatching()) { if (hadMatch) { throw new TransportException(MessageFormat.format( JGitText.get().duplicateRemoteRefUpdateIsIllegal, ":")); //$NON-NLS-1$ } expandMatching(result, update); hadMatch = true; } else if (result.put(update.getRemoteName(), update) != null) { throw new TransportException(MessageFormat.format( JGitText.get().duplicateRemoteRefUpdateIsIllegal, update.getRemoteName())); } } return result; } /** * Expands the placeholder {@link RemoteRefUpdate} {@code match} for a * "matching" {@link RefSpec} ":" or "+:" and puts the expansion into the * given map {@code updates}. * * @param updates * map to put the expansion in * @param match * the placeholder {@link RemoteRefUpdate} to expand * * @throws TransportException * if the expansion results in duplicate updates, or the local * branches cannot be determined */ private void expandMatching(Map updates, RemoteRefUpdate match) throws TransportException { try { Map advertisement = connection.getRefsMap(); Collection fetchSpecs = match.getFetchSpecs(); boolean forceUpdate = match.isForceUpdate(); for (Ref local : transport.local.getRefDatabase() .getRefsByPrefix(Constants.R_HEADS)) { if (local.isSymbolic()) { continue; } String name = local.getName(); Ref advertised = advertisement.get(name); if (advertised == null || advertised.isSymbolic()) { continue; } ObjectId oldOid = advertised.getObjectId(); if (oldOid == null || ObjectId.zeroId().equals(oldOid)) { continue; } ObjectId newOid = local.getObjectId(); if (newOid == null || ObjectId.zeroId().equals(newOid)) { continue; } RemoteRefUpdate rru = new RemoteRefUpdate(transport.local, name, newOid, name, forceUpdate, Transport.findTrackingRefName(name, fetchSpecs), oldOid); if (updates.put(rru.getRemoteName(), rru) != null) { throw new TransportException(MessageFormat.format( JGitText.get().duplicateRemoteRefUpdateIsIllegal, rru.getRemoteName())); } } } catch (IOException x) { throw new TransportException(transport.getURI(), MessageFormat.format(JGitText .get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x); } } private Map rejectAll() { for (RemoteRefUpdate rru : toPush.values()) { if (rru.getStatus() == Status.NOT_ATTEMPTED) { rru.setStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); rru.setMessage(JGitText.get().transactionAborted); } } return Collections.emptyMap(); } private void modifyUpdatesForDryRun() { for (RemoteRefUpdate rru : toPush.values()) if (rru.getStatus() == Status.NOT_ATTEMPTED) rru.setStatus(Status.OK); } private void updateTrackingRefs() { for (RemoteRefUpdate rru : toPush.values()) { final Status status = rru.getStatus(); if (rru.hasTrackingRefUpdate() && (status == Status.UP_TO_DATE || status == Status.OK)) { // update local tracking branch only when there is a chance that // it has changed; this is possible for: // -updated (OK) status, // -up to date (UP_TO_DATE) status try { rru.updateTrackingRef(walker); } catch (IOException e) { // ignore as RefUpdate has stored I/O error status } } } } /** * Gets the list of option strings associated with this push. * * @return pushOptions * @since 4.5 */ public List getPushOptions() { return pushOptions; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1842 Content-Disposition: inline; filename="PushResult.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "88542f28b41b5d625d17697b7b349bde35e3e113" /* * Copyright (C) 2009, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2009, Robin Rosenberg and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collection; import java.util.Collections; import java.util.Map; /** * Result of push operation to the remote repository. Holding information of * {@link org.eclipse.jgit.transport.OperationResult} and remote refs updates * status. * * @see Transport#push(org.eclipse.jgit.lib.ProgressMonitor, Collection) */ public class PushResult extends OperationResult { private Map remoteUpdates = Collections.emptyMap(); /** * Get status of remote refs updates. Together with * {@link #getAdvertisedRefs()} it provides full description/status of each * ref update. *

* Returned collection is not sorted in any order. *

* * @return collection of remote refs updates */ public Collection getRemoteUpdates() { return Collections.unmodifiableCollection(remoteUpdates.values()); } /** * Get status of specific remote ref update by remote ref name. Together * with {@link #getAdvertisedRef(String)} it provide full description/status * of this ref update. * * @param refName * remote ref name * @return status of remote ref update */ public RemoteRefUpdate getRemoteUpdate(String refName) { return remoteUpdates.get(refName); } void setRemoteUpdates( final Map remoteUpdates) { this.remoteUpdates = remoteUpdates; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 22788 Content-Disposition: inline; filename="ReceiveCommand.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "bfc75f036cf21dc9157aeed452dc75e0da95106a" /* * Copyright (C) 2008, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; /** * A command being processed by * {@link org.eclipse.jgit.transport.ReceivePack}. *

* This command instance roughly translates to the server side representation of * the {@link org.eclipse.jgit.transport.RemoteRefUpdate} created by the client. */ public class ReceiveCommand { /** Type of operation requested. */ public enum Type { /** Create a new ref; the ref must not already exist. */ CREATE, /** * Update an existing ref with a fast-forward update. *

* During a fast-forward update no changes will be lost; only new * commits are inserted into the ref. */ UPDATE, /** * Update an existing ref by potentially discarding objects. *

* The current value of the ref is not fully reachable from the new * value of the ref, so a successful command may result in one or more * objects becoming unreachable. */ UPDATE_NONFASTFORWARD, /** Delete an existing ref; the ref should already exist. */ DELETE; } /** Result of the update command. */ public enum Result { /** The command has not yet been attempted by the server. */ NOT_ATTEMPTED, /** The server is configured to deny creation of this ref. */ REJECTED_NOCREATE, /** The server is configured to deny deletion of this ref. */ REJECTED_NODELETE, /** The update is a non-fast-forward update and isn't permitted. */ REJECTED_NONFASTFORWARD, /** The update affects HEAD and cannot be permitted. */ REJECTED_CURRENT_BRANCH, /** * One or more objects aren't in the repository. *

* This is severe indication of either repository corruption on the * server side, or a bug in the client wherein the client did not supply * all required objects during the pack transfer. */ REJECTED_MISSING_OBJECT, /** Other failure; see {@link ReceiveCommand#getMessage()}. */ REJECTED_OTHER_REASON, /** The ref could not be locked and updated atomically; try again. */ LOCK_FAILURE, /** The change was completed successfully. */ OK; } /** * Filter a collection of commands according to result. * * @param in * commands to filter. * @param want * desired status to filter by. * @return a copy of the command list containing only those commands with * the desired status. * @since 4.2 */ public static List filter(Iterable in, Result want) { List r; if (in instanceof Collection) r = new ArrayList<>(((Collection) in).size()); else r = new ArrayList<>(); for (ReceiveCommand cmd : in) { if (cmd.getResult() == want) r.add(cmd); } return r; } /** * Filter a list of commands according to result. * * @param commands * commands to filter. * @param want * desired status to filter by. * @return a copy of the command list containing only those commands with * the desired status. * @since 2.0 */ public static List filter(List commands, Result want) { return filter((Iterable) commands, want); } /** * Set unprocessed commands as failed due to transaction aborted. *

* If a command is still * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} it * will be set to * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}. * * @param commands * commands to mark as failed. * @since 4.2 */ public static void abort(Iterable commands) { for (ReceiveCommand c : commands) { if (c.getResult() == NOT_ATTEMPTED) { c.setResult(REJECTED_OTHER_REASON, JGitText.get().transactionAborted); } } } /** * Check whether a command failed due to transaction aborted. * * @param cmd * command. * @return whether the command failed due to transaction aborted, as in * {@link #abort(Iterable)}. * @since 4.9 */ public static boolean isTransactionAborted(ReceiveCommand cmd) { return cmd.getResult() == REJECTED_OTHER_REASON && cmd.getMessage().equals(JGitText.get().transactionAborted); } /** * Create a command to switch a reference from object to symbolic. * * @param oldId * expected oldId. May be {@code zeroId} to create. * @param newTarget * new target; must begin with {@code "refs/"}. * @param name * name of the reference to make symbolic. * @return command instance. * @since 4.10 */ public static ReceiveCommand link(@NonNull ObjectId oldId, @NonNull String newTarget, @NonNull String name) { return new ReceiveCommand(oldId, newTarget, name); } /** * Create a command to switch a symbolic reference's target. * * @param oldTarget * expected old target. May be null to create. * @param newTarget * new target; must begin with {@code "refs/"}. * @param name * name of the reference to make symbolic. * @return command instance. * @since 4.10 */ public static ReceiveCommand link(@Nullable String oldTarget, @NonNull String newTarget, @NonNull String name) { return new ReceiveCommand(oldTarget, newTarget, name); } /** * Create a command to switch a reference from symbolic to object. * * @param oldTarget * expected old target. * @param newId * new object identifier. May be {@code zeroId()} to delete. * @param name * name of the reference to convert from symbolic. * @return command instance. * @since 4.10 */ public static ReceiveCommand unlink(@NonNull String oldTarget, @NonNull ObjectId newId, @NonNull String name) { return new ReceiveCommand(oldTarget, newId, name); } private final ObjectId oldId; private final String oldSymref; private final ObjectId newId; private final String newSymref; private final String name; private Type type; private boolean typeIsCorrect; private Ref ref; private Result status = Result.NOT_ATTEMPTED; private String message; private boolean customRefLog; private String refLogMessage; private boolean refLogIncludeResult; private Boolean forceRefLog; /** * Create a new command for * {@link org.eclipse.jgit.transport.ReceivePack}. * * @param oldId * the expected old object id; must not be null. Use * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a * ref creation. * @param newId * the new object id; must not be null. Use * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a * ref deletion. * @param name * name of the ref being affected. */ public ReceiveCommand(final ObjectId oldId, final ObjectId newId, final String name) { if (oldId == null) { throw new IllegalArgumentException( JGitText.get().oldIdMustNotBeNull); } if (newId == null) { throw new IllegalArgumentException( JGitText.get().newIdMustNotBeNull); } if (name == null || name.isEmpty()) { throw new IllegalArgumentException( JGitText.get().nameMustNotBeNullOrEmpty); } this.oldId = oldId; this.oldSymref = null; this.newId = newId; this.newSymref = null; this.name = name; type = Type.UPDATE; if (ObjectId.zeroId().equals(oldId)) { type = Type.CREATE; } if (ObjectId.zeroId().equals(newId)) { type = Type.DELETE; } } /** * Create a new command for * {@link org.eclipse.jgit.transport.ReceivePack}. * * @param oldId * the old object id; must not be null. Use * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a * ref creation. * @param newId * the new object id; must not be null. Use * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate a * ref deletion. * @param name * name of the ref being affected. * @param type * type of the command. Must be * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#CREATE} * if {@code * oldId} is zero, or * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#DELETE} * if {@code newId} is zero. * @since 2.0 */ public ReceiveCommand(final ObjectId oldId, final ObjectId newId, final String name, final Type type) { if (oldId == null) { throw new IllegalArgumentException( JGitText.get().oldIdMustNotBeNull); } if (newId == null) { throw new IllegalArgumentException( JGitText.get().newIdMustNotBeNull); } if (name == null || name.isEmpty()) { throw new IllegalArgumentException( JGitText.get().nameMustNotBeNullOrEmpty); } this.oldId = oldId; this.oldSymref = null; this.newId = newId; this.newSymref = null; this.name = name; switch (type) { case CREATE: if (!ObjectId.zeroId().equals(oldId)) { throw new IllegalArgumentException( JGitText.get().createRequiresZeroOldId); } break; case DELETE: if (!ObjectId.zeroId().equals(newId)) { throw new IllegalArgumentException( JGitText.get().deleteRequiresZeroNewId); } break; case UPDATE: case UPDATE_NONFASTFORWARD: if (ObjectId.zeroId().equals(newId) || ObjectId.zeroId().equals(oldId)) { throw new IllegalArgumentException( JGitText.get().updateRequiresOldIdAndNewId); } break; default: throw new IllegalStateException( JGitText.get().enumValueNotSupported0); } this.type = type; } /** * Create a command to switch a reference from object to symbolic. * * @param oldId * the old object id; must not be null. Use * {@link ObjectId#zeroId()} to indicate a ref creation. * @param newSymref * new target, must begin with {@code "refs/"}. Use {@code null} * to indicate a ref deletion. * @param name * name of the reference to make symbolic. * @since 4.10 */ private ReceiveCommand(ObjectId oldId, String newSymref, String name) { if (oldId == null) { throw new IllegalArgumentException( JGitText.get().oldIdMustNotBeNull); } if (name == null || name.isEmpty()) { throw new IllegalArgumentException( JGitText.get().nameMustNotBeNullOrEmpty); } this.oldId = oldId; this.oldSymref = null; this.newId = ObjectId.zeroId(); this.newSymref = newSymref; this.name = name; if (AnyObjectId.isEqual(ObjectId.zeroId(), oldId)) { type = Type.CREATE; } else if (newSymref != null) { type = Type.UPDATE; } else { type = Type.DELETE; } typeIsCorrect = true; } /** * Create a command to switch a reference from symbolic to object. * * @param oldSymref * expected old target. Use {@code null} to indicate a ref * creation. * @param newId * the new object id; must not be null. Use * {@link ObjectId#zeroId()} to indicate a ref deletion. * @param name * name of the reference to convert from symbolic. * @since 4.10 */ private ReceiveCommand(String oldSymref, ObjectId newId, String name) { if (newId == null) { throw new IllegalArgumentException( JGitText.get().newIdMustNotBeNull); } if (name == null || name.isEmpty()) { throw new IllegalArgumentException( JGitText.get().nameMustNotBeNullOrEmpty); } this.oldId = ObjectId.zeroId(); this.oldSymref = oldSymref; this.newId = newId; this.newSymref = null; this.name = name; if (oldSymref == null) { type = Type.CREATE; } else if (!AnyObjectId.isEqual(ObjectId.zeroId(), newId)) { type = Type.UPDATE; } else { type = Type.DELETE; } typeIsCorrect = true; } /** * Create a command to switch a symbolic reference's target. * * @param oldTarget * expected old target. Use {@code null} to indicate a ref * creation. * @param newTarget * new target. Use {@code null} to indicate a ref deletion. * @param name * name of the reference to make symbolic. * @since 4.10 */ private ReceiveCommand(@Nullable String oldTarget, String newTarget, String name) { if (name == null || name.isEmpty()) { throw new IllegalArgumentException( JGitText.get().nameMustNotBeNullOrEmpty); } this.oldId = ObjectId.zeroId(); this.oldSymref = oldTarget; this.newId = ObjectId.zeroId(); this.newSymref = newTarget; this.name = name; if (oldTarget == null) { if (newTarget == null) { throw new IllegalArgumentException( JGitText.get().bothRefTargetsMustNotBeNull); } type = Type.CREATE; } else if (newTarget != null) { type = Type.UPDATE; } else { type = Type.DELETE; } typeIsCorrect = true; } /** * Get the old value the client thinks the ref has. * * @return the old value the client thinks the ref has. */ public ObjectId getOldId() { return oldId; } /** * Get expected old target for a symbolic reference. * * @return expected old target for a symbolic reference. * @since 4.10 */ @Nullable public String getOldSymref() { return oldSymref; } /** * Get the requested new value for this ref. * * @return the requested new value for this ref. */ public ObjectId getNewId() { return newId; } /** * Get requested new target for a symbolic reference. * * @return requested new target for a symbolic reference. * @since 4.10 */ @Nullable public String getNewSymref() { return newSymref; } /** * Get the name of the ref being updated. * * @return the name of the ref being updated. */ public String getRefName() { return name; } /** * Get the type of this command; see {@link Type}. * * @return the type of this command; see {@link Type}. */ public Type getType() { return type; } /** * Get the ref, if this was advertised by the connection. * * @return the ref, if this was advertised by the connection. */ public Ref getRef() { return ref; } /** * Get the current status code of this command. * * @return the current status code of this command. */ public Result getResult() { return status; } /** * Get the message associated with a failure status. * * @return the message associated with a failure status. */ public String getMessage() { return message; } /** * Set the message to include in the reflog. *

* Overrides the default set by {@code setRefLogMessage} on any containing * {@link org.eclipse.jgit.lib.BatchRefUpdate}. * * @param msg * the message to describe this change. If null and appendStatus is * false, the reflog will not be updated. * @param appendStatus * true if the status of the ref change (fast-forward or * forced-update) should be appended to the user supplied message. * @since 4.9 */ public void setRefLogMessage(String msg, boolean appendStatus) { customRefLog = true; if (msg == null && !appendStatus) { disableRefLog(); } else if (msg == null && appendStatus) { refLogMessage = ""; //$NON-NLS-1$ refLogIncludeResult = true; } else { refLogMessage = msg; refLogIncludeResult = appendStatus; } } /** * Don't record this update in the ref's associated reflog. *

* Equivalent to {@code setRefLogMessage(null, false)}. * * @since 4.9 */ public void disableRefLog() { customRefLog = true; refLogMessage = null; refLogIncludeResult = false; } /** * Force writing a reflog for the updated ref. * * @param force whether to force. * @since 4.9 */ public void setForceRefLog(boolean force) { forceRefLog = Boolean.valueOf(force); } /** * Check whether this command has a custom reflog message setting that should * override defaults in any containing * {@link org.eclipse.jgit.lib.BatchRefUpdate}. *

* Does not take into account whether {@code #setForceRefLog(boolean)} has * been called. * * @return whether a custom reflog is set. * @since 4.9 */ public boolean hasCustomRefLog() { return customRefLog; } /** * Check whether log has been disabled by {@link #disableRefLog()}. * * @return true if disabled. * @since 4.9 */ public boolean isRefLogDisabled() { return refLogMessage == null; } /** * Get the message to include in the reflog. * * @return message the caller wants to include in the reflog; null if the * update should not be logged. * @since 4.9 */ @Nullable public String getRefLogMessage() { return refLogMessage; } /** * Check whether the reflog message should include the result of the update, * such as fast-forward or force-update. * * @return true if the message should include the result. * @since 4.9 */ public boolean isRefLogIncludingResult() { return refLogIncludeResult; } /** * Check whether the reflog should be written regardless of repo defaults. * * @return whether force writing is enabled; {@code null} if * {@code #setForceRefLog(boolean)} was never called. * @since 4.9 */ @Nullable public Boolean isForceRefLog() { return forceRefLog; } /** * Set the status of this command. * * @param s * the new status code for this command. */ public void setResult(Result s) { setResult(s, null); } /** * Set the status of this command. * * @param s * new status code for this command. * @param m * optional message explaining the new status. */ public void setResult(Result s, String m) { status = s; message = m; } /** * Update the type of this command by checking for fast-forward. *

* If the command's current type is UPDATE, a merge test will be performed * using the supplied RevWalk to determine if {@link #getOldId()} is fully * merged into {@link #getNewId()}. If some commits are not merged the * update type is changed to * {@link org.eclipse.jgit.transport.ReceiveCommand.Type#UPDATE_NONFASTFORWARD}. * * @param walk * an instance to perform the merge test with. The caller must * allocate and release this object. * @throws java.io.IOException * either oldId or newId is not accessible in the repository * used by the RevWalk. This usually indicates data corruption, * and the command cannot be processed. */ public void updateType(RevWalk walk) throws IOException { if (typeIsCorrect) return; if (type == Type.UPDATE && !AnyObjectId.isEqual(oldId, newId)) { RevObject o = walk.parseAny(oldId); RevObject n = walk.parseAny(newId); if (!(o instanceof RevCommit) || !(n instanceof RevCommit) || !walk.isMergedInto((RevCommit) o, (RevCommit) n)) setType(Type.UPDATE_NONFASTFORWARD); } typeIsCorrect = true; } /** * Execute this command during a receive-pack session. *

* Sets the status of the command as a side effect. * * @param rp * receive-pack session. * @since 5.6 */ public void execute(ReceivePack rp) { try { String expTarget = getOldSymref(); boolean detach = getNewSymref() != null || (type == Type.DELETE && expTarget != null); RefUpdate ru = rp.getRepository().updateRef(getRefName(), detach); if (expTarget != null) { if (!ru.getRef().isSymbolic() || !ru.getRef().getTarget() .getName().equals(expTarget)) { setResult(Result.LOCK_FAILURE); return; } } ru.setRefLogIdent(rp.getRefLogIdent()); ru.setRefLogMessage(refLogMessage, refLogIncludeResult); switch (getType()) { case DELETE: if (!ObjectId.zeroId().equals(getOldId())) { // We can only do a CAS style delete if the client // didn't bork its delete request by sending the // wrong zero id rather than the advertised one. // ru.setExpectedOldObjectId(getOldId()); } ru.setForceUpdate(true); setResult(ru.delete(rp.getRevWalk())); break; case CREATE: case UPDATE: case UPDATE_NONFASTFORWARD: ru.setForceUpdate(rp.isAllowNonFastForwards()); ru.setExpectedOldObjectId(getOldId()); ru.setRefLogMessage("push", true); //$NON-NLS-1$ if (getNewSymref() != null) { setResult(ru.link(getNewSymref())); } else { ru.setNewObjectId(getNewId()); setResult(ru.update(rp.getRevWalk())); } break; } } catch (IOException err) { reject(err); } } void setRef(Ref r) { ref = r; } void setType(Type t) { type = t; } void setTypeFastForwardUpdate() { type = Type.UPDATE; typeIsCorrect = true; } /** * Set the result of this command. * * @param r * the new result code for this command. */ public void setResult(RefUpdate.Result r) { switch (r) { case NOT_ATTEMPTED: setResult(Result.NOT_ATTEMPTED); break; case LOCK_FAILURE: case IO_FAILURE: setResult(Result.LOCK_FAILURE); break; case NO_CHANGE: case NEW: case FORCED: case FAST_FORWARD: setResult(Result.OK); break; case REJECTED: setResult(Result.REJECTED_NONFASTFORWARD); break; case REJECTED_CURRENT_BRANCH: setResult(Result.REJECTED_CURRENT_BRANCH); break; case REJECTED_MISSING_OBJECT: setResult(Result.REJECTED_MISSING_OBJECT); break; case REJECTED_OTHER_REASON: setResult(Result.REJECTED_OTHER_REASON); break; default: setResult(Result.REJECTED_OTHER_REASON, r.name()); break; } } void reject(IOException err) { setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format( JGitText.get().lockError, err.getMessage())); } @SuppressWarnings("nls") @Override public String toString() { return getType().name() + ": " + getOldId().name() + " " + getNewId().name() + " " + getRefName(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2156 Content-Disposition: inline; filename="ReceiveCommandErrorHandler.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "d9a148622b2c54c247dfc2079971efe19895cbc4" /* * Copyright (c) 2019, Google LLC and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.util.List; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.transport.ReceiveCommand.Result; /** * Exception handler for processing {@link ReceiveCommand}. * * @since 5.7 */ public interface ReceiveCommandErrorHandler { /** * Handle an exception thrown while validating the new commit ID. * * @param cmd * offending command * @param e * exception thrown */ default void handleNewIdValidationException(ReceiveCommand cmd, IOException e) { cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd.getNewId().name()); } /** * Handle an exception thrown while validating the old commit ID. * * @param cmd * offending command * @param e * exception thrown */ default void handleOldIdValidationException(ReceiveCommand cmd, IOException e) { cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd.getOldId().name()); } /** * Handle an exception thrown while checking if the update is fast-forward. * * @param cmd * offending command * @param e * exception thrown */ default void handleFastForwardCheckException(ReceiveCommand cmd, IOException e) { if (e instanceof MissingObjectException) { cmd.setResult(Result.REJECTED_MISSING_OBJECT, e.getMessage()); } else { cmd.setResult(Result.REJECTED_OTHER_REASON); } } /** * Handle an exception thrown while checking if the update is fast-forward. * * @param cmds * commands being processed * @param e * exception thrown */ default void handleBatchRefUpdateException(List cmds, IOException e) { for (ReceiveCommand cmd : cmds) { if (cmd.getResult() == Result.NOT_ATTEMPTED) { cmd.reject(e); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 67589 Content-Disposition: inline; filename="ReceivePack.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "6f211e079471cd271d4e1710a326cd767767fd53" /* * Copyright (C) 2008-2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.UnpackException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.submodule.SubmoduleValidator; import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException; import org.eclipse.jgit.internal.transport.connectivity.FullConnectivityChecker; import org.eclipse.jgit.internal.transport.parser.FirstCommand; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GitmoduleEntry; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ConnectivityChecker.ConnectivityCheckInfo; import org.eclipse.jgit.transport.PacketLineIn.InputOverLimitIOException; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.LimitedInputStream; import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutOutputStream; /** * Implements the server side of a push connection, receiving objects. */ public class ReceivePack { /** Database we write the stored objects into. */ private final Repository db; /** Revision traversal support over {@link #db}. */ private final RevWalk walk; /** * Is the client connection a bi-directional socket or pipe? *

* If true, this class assumes it can perform multiple read and write cycles * with the client over the input and output streams. This matches the * functionality available with a standard TCP/IP connection, or a local * operating system or in-memory pipe. *

* If false, this class runs in a read everything then output results mode, * making it suitable for single round-trip systems RPCs such as HTTP. */ private boolean biDirectionalPipe = true; /** Expecting data after the pack footer */ private boolean expectDataAfterPackFooter; /** Should an incoming transfer validate objects? */ private ObjectChecker objectChecker; /** Should an incoming transfer permit create requests? */ private boolean allowCreates; /** Should an incoming transfer permit delete requests? */ private boolean allowAnyDeletes; private boolean allowBranchDeletes; /** Should an incoming transfer permit non-fast-forward requests? */ private boolean allowNonFastForwards; /** Should an incoming transfer permit push options? **/ private boolean allowPushOptions; /** * Should the requested ref updates be performed as a single atomic * transaction? */ private boolean atomic; private boolean allowOfsDelta; private boolean allowQuiet = true; /** Should the server advertise and accept the session-id capability. */ private boolean allowReceiveClientSID; /** Identity to record action as within the reflog. */ private PersonIdent refLogIdent; /** Hook used while advertising the refs to the client. */ private AdvertiseRefsHook advertiseRefsHook; /** Filter used while advertising the refs to the client. */ private RefFilter refFilter; /** Timeout in seconds to wait for client interaction. */ private int timeout; /** Timer to manage {@link #timeout}. */ private InterruptTimer timer; private TimeoutInputStream timeoutIn; // Original stream passed to init(), since rawOut may be wrapped in a // sideband. private OutputStream origOut; /** Raw input stream. */ private InputStream rawIn; /** Raw output stream. */ private OutputStream rawOut; /** Optional message output stream. */ private OutputStream msgOut; private SideBandOutputStream errOut; /** Packet line input stream around {@link #rawIn}. */ private PacketLineIn pckIn; /** Packet line output stream around {@link #rawOut}. */ private PacketLineOut pckOut; private final MessageOutputWrapper msgOutWrapper = new MessageOutputWrapper(); private PackParser parser; /** The refs we advertised as existing at the start of the connection. */ private Map refs; /** All SHA-1s shown to the client, which can be possible edges. */ private Set advertisedHaves; /** Capabilities requested by the client. */ private Map enabledCapabilities; /** Session ID sent from the client. Null if none was received. */ private String clientSID; String userAgent; private Set clientShallowCommits; private List commands; private long maxCommandBytes; private long maxDiscardBytes; private StringBuilder advertiseError; /** * If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */ private boolean sideBand; private boolean quiet; /** Lock around the received pack file, while updating refs. */ private PackLock packLock; private boolean checkReferencedAreReachable; /** Git object size limit */ private long maxObjectSizeLimit; /** Total pack size limit */ private long maxPackSizeLimit = -1; /** The size of the received pack, including index size */ private Long packSize; private PushCertificateParser pushCertificateParser; private SignedPushConfig signedPushConfig; private PushCertificate pushCert; private ReceivedPackStatistics stats; /** * Connectivity checker to use. * @since 5.7 */ protected ConnectivityChecker connectivityChecker = new FullConnectivityChecker(); /** Hook to validate the update commands before execution. */ private PreReceiveHook preReceive; private ReceiveCommandErrorHandler receiveCommandErrorHandler = new ReceiveCommandErrorHandler() { // Use the default implementation. }; private UnpackErrorHandler unpackErrorHandler = new DefaultUnpackErrorHandler(); /** Hook to report on the commands after execution. */ private PostReceiveHook postReceive; /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */ private boolean reportStatus; /** Whether the client intends to use push options. */ private boolean usePushOptions; private List pushOptions; /** * Create a new pack receive for an open repository. * * @param into * the destination repository. */ public ReceivePack(Repository into) { db = into; walk = new RevWalk(db); walk.setRetainBody(false); TransferConfig tc = db.getConfig().get(TransferConfig.KEY); objectChecker = tc.newReceiveObjectChecker(); allowReceiveClientSID = tc.isAllowReceiveClientSID(); ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new); allowCreates = rc.allowCreates; allowAnyDeletes = true; allowBranchDeletes = rc.allowDeletes; allowNonFastForwards = rc.allowNonFastForwards; allowOfsDelta = rc.allowOfsDelta; allowPushOptions = rc.allowPushOptions; maxCommandBytes = rc.maxCommandBytes; maxDiscardBytes = rc.maxDiscardBytes; advertiseRefsHook = AdvertiseRefsHook.DEFAULT; refFilter = RefFilter.DEFAULT; advertisedHaves = new HashSet<>(); clientShallowCommits = new HashSet<>(); signedPushConfig = rc.signedPush; preReceive = PreReceiveHook.NULL; postReceive = PostReceiveHook.NULL; } /** Configuration for receive operations. */ private static class ReceiveConfig { final boolean allowCreates; final boolean allowDeletes; final boolean allowNonFastForwards; final boolean allowOfsDelta; final boolean allowPushOptions; final long maxCommandBytes; final long maxDiscardBytes; final SignedPushConfig signedPush; ReceiveConfig(Config config) { allowCreates = true; allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$ allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$ "denynonfastforwards", false); //$NON-NLS-1$ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ true); allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$ false); maxCommandBytes = config.getLong("receive", //$NON-NLS-1$ "maxCommandBytes", //$NON-NLS-1$ 3 << 20); maxDiscardBytes = config.getLong("receive", //$NON-NLS-1$ "maxCommandDiscardBytes", //$NON-NLS-1$ -1); signedPush = SignedPushConfig.KEY.parse(config); } } /** * Output stream that wraps the current {@link #msgOut}. *

* We don't want to expose {@link #msgOut} directly because it can change * several times over the course of a session. */ class MessageOutputWrapper extends OutputStream { @Override public void write(int ch) { if (msgOut != null) { try { msgOut.write(ch); } catch (IOException e) { // Ignore write failures. } } } @Override public void write(byte[] b, int off, int len) { if (msgOut != null) { try { msgOut.write(b, off, len); } catch (IOException e) { // Ignore write failures. } } } @Override public void write(byte[] b) { write(b, 0, b.length); } @Override public void flush() { if (msgOut != null) { try { msgOut.flush(); } catch (IOException e) { // Ignore write failures. } } } } /** * Get the repository this receive completes into. * * @return the repository this receive completes into. */ public Repository getRepository() { return db; } /** * Get the RevWalk instance used by this connection. * * @return the RevWalk instance used by this connection. */ public RevWalk getRevWalk() { return walk; } /** * Get refs which were advertised to the client. * * @return all refs which were advertised to the client, or null if * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. */ public Map getAdvertisedRefs() { return refs; } /** * Set the refs advertised by this ReceivePack. *

* Intended to be called from a * {@link org.eclipse.jgit.transport.PreReceiveHook}. * * @param allRefs * explicit set of references to claim as advertised by this * ReceivePack instance. This overrides any references that may * exist in the source repository. The map is passed to the * configured {@link #getRefFilter()}. If null, assumes all refs * were advertised. * @param additionalHaves * explicit set of additional haves to claim as advertised. If * null, assumes the default set of additional haves from the * repository. * @throws IOException * if an IO error occurred */ public void setAdvertisedRefs(Map allRefs, Set additionalHaves) throws IOException { refs = allRefs != null ? allRefs : getAllRefs(); refs = refFilter.filter(refs); advertisedHaves.clear(); Ref head = refs.get(HEAD); if (head != null && head.isSymbolic()) { refs.remove(HEAD); } for (Ref ref : refs.values()) { if (ref.getObjectId() != null) { advertisedHaves.add(ref.getObjectId()); } } if (additionalHaves != null) { advertisedHaves.addAll(additionalHaves); } else { advertisedHaves.addAll(db.getAdditionalHaves()); } } /** * Get objects advertised to the client. * * @return the set of objects advertised to the as present in this * repository, or null if {@link #setAdvertisedRefs(Map, Set)} has * not been called yet. */ public final Set getAdvertisedObjects() { return advertisedHaves; } /** * Whether this instance will validate all referenced, but not supplied by * the client, objects are reachable from another reference. * * @return true if this instance will validate all referenced, but not * supplied by the client, objects are reachable from another * reference. */ public boolean isCheckReferencedObjectsAreReachable() { return checkReferencedAreReachable; } /** * Validate all referenced but not supplied objects are reachable. *

* If enabled, this instance will verify that references to objects not * contained within the received pack are already reachable through at least * one other reference displayed as part of {@link #getAdvertisedRefs()}. *

* This feature is useful when the application doesn't trust the client to * not provide a forged SHA-1 reference to an object, in an attempt to * access parts of the DAG that they aren't allowed to see and which have * been hidden from them via the configured * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} or * {@link org.eclipse.jgit.transport.RefFilter}. *

* Enabling this feature may imply at least some, if not all, of the same * functionality performed by {@link #setCheckReceivedObjects(boolean)}. * Applications are encouraged to enable both features, if desired. * * @param b * {@code true} to enable the additional check. */ public void setCheckReferencedObjectsAreReachable(boolean b) { this.checkReferencedAreReachable = b; } /** * Whether this class expects a bi-directional pipe opened between the * client and itself. * * @return true if this class expects a bi-directional pipe opened between * the client and itself. The default is true. */ public boolean isBiDirectionalPipe() { return biDirectionalPipe; } /** * Whether this class will assume the socket is a fully bidirectional pipe * between the two peers and takes advantage of that by first transmitting * the known refs, then waiting to read commands. * * @param twoWay * if true, this class will assume the socket is a fully * bidirectional pipe between the two peers and takes advantage * of that by first transmitting the known refs, then waiting to * read commands. If false, this class assumes it must read the * commands before writing output and does not perform the * initial advertising. */ public void setBiDirectionalPipe(boolean twoWay) { biDirectionalPipe = twoWay; } /** * Whether there is data expected after the pack footer. * * @return {@code true} if there is data expected after the pack footer. */ public boolean isExpectDataAfterPackFooter() { return expectDataAfterPackFooter; } /** * Whether there is additional data in InputStream after pack. * * @param e * {@code true} if there is additional data in InputStream after * pack. */ public void setExpectDataAfterPackFooter(boolean e) { expectDataAfterPackFooter = e; } /** * Whether this instance will verify received objects are formatted * correctly. * * @return {@code true} if this instance will verify received objects are * formatted correctly. Validating objects requires more CPU time on * this side of the connection. */ public boolean isCheckReceivedObjects() { return objectChecker != null; } /** * Whether to enable checking received objects * * @param check * {@code true} to enable checking received objects; false to * assume all received objects are valid. * @see #setObjectChecker(ObjectChecker) */ public void setCheckReceivedObjects(boolean check) { if (check && objectChecker == null) setObjectChecker(new ObjectChecker()); else if (!check && objectChecker != null) setObjectChecker(null); } /** * Set the object checking instance to verify each received object with * * @param impl * if non-null the object checking instance to verify each * received object with; null to disable object checking. * @since 3.4 */ public void setObjectChecker(ObjectChecker impl) { objectChecker = impl; } /** * Whether the client can request refs to be created. * * @return {@code true} if the client can request refs to be created. */ public boolean isAllowCreates() { return allowCreates; } /** * Whether to permit create ref commands to be processed. * * @param canCreate * {@code true} to permit create ref commands to be processed. */ public void setAllowCreates(boolean canCreate) { allowCreates = canCreate; } /** * Whether the client can request refs to be deleted. * * @return {@code true} if the client can request refs to be deleted. */ public boolean isAllowDeletes() { return allowAnyDeletes; } /** * Whether to permit delete ref commands to be processed. * * @param canDelete * {@code true} to permit delete ref commands to be processed. */ public void setAllowDeletes(boolean canDelete) { allowAnyDeletes = canDelete; } /** * Whether the client can delete from {@code refs/heads/}. * * @return {@code true} if the client can delete from {@code refs/heads/}. * @since 3.6 */ public boolean isAllowBranchDeletes() { return allowBranchDeletes; } /** * Configure whether to permit deletion of branches from the * {@code refs/heads/} namespace. * * @param canDelete * {@code true} to permit deletion of branches from the * {@code refs/heads/} namespace. * @since 3.6 */ public void setAllowBranchDeletes(boolean canDelete) { allowBranchDeletes = canDelete; } /** * Whether the client can request non-fast-forward updates of a ref, * possibly making objects unreachable. * * @return {@code true} if the client can request non-fast-forward updates * of a ref, possibly making objects unreachable. */ public boolean isAllowNonFastForwards() { return allowNonFastForwards; } /** * Configure whether to permit the client to ask for non-fast-forward * updates of an existing ref. * * @param canRewind * {@code true} to permit the client to ask for non-fast-forward * updates of an existing ref. */ public void setAllowNonFastForwards(boolean canRewind) { allowNonFastForwards = canRewind; } /** * Whether the client's commands should be performed as a single atomic * transaction. * * @return {@code true} if the client's commands should be performed as a * single atomic transaction. * @since 4.4 */ public boolean isAtomic() { return atomic; } /** * Configure whether to perform the client's commands as a single atomic * transaction. * * @param atomic * {@code true} to perform the client's commands as a single * atomic transaction. * @since 4.4 */ public void setAtomic(boolean atomic) { this.atomic = atomic; } /** * Get identity of the user making the changes in the reflog. * * @return identity of the user making the changes in the reflog. */ public PersonIdent getRefLogIdent() { return refLogIdent; } /** * Set the identity of the user appearing in the affected reflogs. *

* The timestamp portion of the identity is ignored. A new identity with the * current timestamp will be created automatically when the updates occur * and the log records are written. * * @param pi * identity of the user. If null the identity will be * automatically determined based on the repository * configuration. */ public void setRefLogIdent(PersonIdent pi) { refLogIdent = pi; } /** * Get the hook used while advertising the refs to the client * * @return the hook used while advertising the refs to the client */ public AdvertiseRefsHook getAdvertiseRefsHook() { return advertiseRefsHook; } /** * Get the filter used while advertising the refs to the client * * @return the filter used while advertising the refs to the client */ public RefFilter getRefFilter() { return refFilter; } /** * Set the hook used while advertising the refs to the client. *

* If the {@link org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to * call {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook * and selected by the {@link org.eclipse.jgit.transport.RefFilter} * will be shown to the client. Clients may still attempt to create or * update a reference not advertised by the configured * {@link org.eclipse.jgit.transport.AdvertiseRefsHook}. These attempts * should be rejected by a matching * {@link org.eclipse.jgit.transport.PreReceiveHook}. * * @param advertiseRefsHook * the hook; may be null to show all refs. */ public void setAdvertiseRefsHook(AdvertiseRefsHook advertiseRefsHook) { if (advertiseRefsHook != null) this.advertiseRefsHook = advertiseRefsHook; else this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT; } /** * Set the filter used while advertising the refs to the client. *

* Only refs allowed by this filter will be shown to the client. The filter * is run against the refs specified by the * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable). * * @param refFilter * the filter; may be null to show all refs. */ public void setRefFilter(RefFilter refFilter) { this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } /** * Get timeout (in seconds) before aborting an IO operation. * * @return timeout (in seconds) before aborting an IO operation. */ public int getTimeout() { return timeout; } /** * Set the timeout before willing to abort an IO call. * * @param seconds * number of seconds to wait (with no data transfer occurring) * before aborting an IO read or write operation with the * connected client. */ public void setTimeout(int seconds) { timeout = seconds; } /** * Set the maximum number of command bytes to read from the client. * * @param limit * command limit in bytes; if 0 there is no limit. * @since 4.7 */ public void setMaxCommandBytes(long limit) { maxCommandBytes = limit; } /** * Set the maximum number of command bytes to discard from the client. *

* Discarding remaining bytes allows this instance to consume the rest of * the command block and send a human readable over-limit error via the * side-band channel. If the client sends an excessive number of bytes this * limit kicks in and the instance disconnects, resulting in a non-specific * 'pipe closed', 'end of stream', or similar generic error at the client. *

* When the limit is set to {@code -1} the implementation will default to * the larger of {@code 3 * maxCommandBytes} or {@code 3 MiB}. * * @param limit * discard limit in bytes; if 0 there is no limit; if -1 the * implementation tries to set a reasonable default. * @since 4.7 */ public void setMaxCommandDiscardBytes(long limit) { maxDiscardBytes = limit; } /** * Set the maximum allowed Git object size. *

* If an object is larger than the given size the pack-parsing will throw an * exception aborting the receive-pack operation. * * @param limit * the Git object size limit. If zero then there is not limit. */ public void setMaxObjectSizeLimit(long limit) { maxObjectSizeLimit = limit; } /** * Set the maximum allowed pack size. *

* A pack exceeding this size will be rejected. * * @param limit * the pack size limit, in bytes * @since 3.3 */ public void setMaxPackSizeLimit(long limit) { if (limit < 0) throw new IllegalArgumentException( MessageFormat.format(JGitText.get().receivePackInvalidLimit, Long.valueOf(limit))); maxPackSizeLimit = limit; } /** * Check whether the client expects a side-band stream. * * @return true if the client has advertised a side-band capability, false * otherwise. * @throws org.eclipse.jgit.transport.RequestNotYetReadException * if the client's request has not yet been read from the wire, * so we do not know if they expect side-band. Note that the * client may have already written the request, it just has not * been read. */ public boolean isSideBand() throws RequestNotYetReadException { checkRequestWasRead(); return enabledCapabilities.containsKey(CAPABILITY_SIDE_BAND_64K); } /** * Whether clients may request avoiding noisy progress messages. * * @return true if clients may request avoiding noisy progress messages. * @since 4.0 */ public boolean isAllowQuiet() { return allowQuiet; } /** * Configure if clients may request the server skip noisy messages. * * @param allow * true to allow clients to request quiet behavior; false to * refuse quiet behavior and send messages anyway. This may be * necessary if processing is slow and the client-server network * connection can timeout. * @since 4.0 */ public void setAllowQuiet(boolean allow) { allowQuiet = allow; } /** * Whether the server supports receiving push options. * * @return true if the server supports receiving push options. * @since 4.5 */ public boolean isAllowPushOptions() { return allowPushOptions; } /** * Configure if the server supports receiving push options. * * @param allow * true to optionally accept option strings from the client. * @since 4.5 */ public void setAllowPushOptions(boolean allow) { allowPushOptions = allow; } /** * True if the client wants less verbose output. * * @return true if the client has requested the server to be less verbose. * @throws org.eclipse.jgit.transport.RequestNotYetReadException * if the client's request has not yet been read from the wire, * so we do not know if they expect side-band. Note that the * client may have already written the request, it just has not * been read. * @since 4.0 */ public boolean isQuiet() throws RequestNotYetReadException { checkRequestWasRead(); return quiet; } /** * Set the configuration for push certificate verification. * * @param cfg * new configuration; if this object is null or its * {@link SignedPushConfig#getCertNonceSeed()} is null, push * certificate verification will be disabled. * @since 4.1 */ public void setSignedPushConfig(SignedPushConfig cfg) { signedPushConfig = cfg; } private PushCertificateParser getPushCertificateParser() { if (pushCertificateParser == null) { pushCertificateParser = new PushCertificateParser(db, signedPushConfig); } return pushCertificateParser; } /** * Get the user agent of the client. *

* If the client is new enough to use {@code agent=} capability that value * will be returned. Older HTTP clients may also supply their version using * the HTTP {@code User-Agent} header. The capability overrides the HTTP * header if both are available. *

* When an HTTP request has been received this method returns the HTTP * {@code User-Agent} header value until capabilities have been parsed. * * @return user agent supplied by the client. Available only if the client * is new enough to advertise its user agent. * @since 4.0 */ public String getPeerUserAgent() { if (enabledCapabilities == null || enabledCapabilities.isEmpty()) { return userAgent; } return enabledCapabilities.getOrDefault(OPTION_AGENT, userAgent); } /** * Get all of the command received by the current request. * * @return all of the command received by the current request. */ public List getAllCommands() { return Collections.unmodifiableList(commands); } /** * Set an error handler for {@link ReceiveCommand}. * * @param receiveCommandErrorHandler * the error handler * @since 5.7 */ public void setReceiveCommandErrorHandler( ReceiveCommandErrorHandler receiveCommandErrorHandler) { this.receiveCommandErrorHandler = receiveCommandErrorHandler; } /** * Send an error message to the client. *

* If any error messages are sent before the references are advertised to * the client, the errors will be sent instead of the advertisement and the * receive operation will be aborted. All clients should receive and display * such early stage errors. *

* If the reference advertisements have already been sent, messages are sent * in a side channel. If the client doesn't support receiving messages, the * message will be discarded, with no other indication to the caller or to * the client. *

* {@link org.eclipse.jgit.transport.PreReceiveHook}s should always try to * use * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(Result, String)} * with a result status of * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON} * to indicate any reasons for rejecting an update. Messages attached to a * command are much more likely to be returned to the client. * * @param what * string describing the problem identified by the hook. The * string must not end with an LF, and must not contain an LF. */ public void sendError(String what) { if (refs == null) { if (advertiseError == null) advertiseError = new StringBuilder(); advertiseError.append(what).append('\n'); } else { msgOutWrapper.write(Constants.encode("error: " + what + "\n")); //$NON-NLS-1$ //$NON-NLS-2$ } } private void fatalError(String msg) { if (errOut != null) { try { errOut.write(Constants.encode(msg)); errOut.flush(); } catch (IOException e) { // Ignore write failures } } else { sendError(msg); } } /** * Send a message to the client, if it supports receiving them. *

* If the client doesn't support receiving messages, the message will be * discarded, with no other indication to the caller or to the client. * * @param what * string describing the problem identified by the hook. The * string must not end with an LF, and must not contain an LF. */ public void sendMessage(String what) { msgOutWrapper.write(Constants.encode(what + "\n")); //$NON-NLS-1$ } /** * Get an underlying stream for sending messages to the client. * * @return an underlying stream for sending messages to the client. */ public OutputStream getMessageOutputStream() { return msgOutWrapper; } /** * Get whether or not a pack has been received. * * This can be called before calling {@link #getPackSize()} to avoid causing * {@code IllegalStateException} when the pack size was not set because no * pack was received. * * @return true if a pack has been received. * @since 5.6 */ public boolean hasReceivedPack() { return packSize != null; } /** * Get the size of the received pack file including the index size. * * This can only be called if the pack is already received. * * @return the size of the received pack including index size * @throws java.lang.IllegalStateException * if called before the pack has been received * @since 3.3 */ public long getPackSize() { if (packSize != null) return packSize.longValue(); throw new IllegalStateException(JGitText.get().packSizeNotSetYet); } /** * Get the commits from the client's shallow file. * * @return if the client is a shallow repository, the list of edge commits * that define the client's shallow boundary. Empty set if the * client is earlier than Git 1.9, or is a full clone. */ private Set getClientShallowCommits() { return clientShallowCommits; } /** * Whether any commands to be executed have been read. * * @return {@code true} if any commands to be executed have been read. */ private boolean hasCommands() { return !commands.isEmpty(); } /** * Whether an error occurred that should be advertised. * * @return true if an error occurred that should be advertised. */ private boolean hasError() { return advertiseError != null; } /** * Initialize the instance with the given streams. * * Visible for out-of-tree subclasses (e.g. tests that need to set the * streams without going through the {@link #service()} method). * * @param input * raw input to read client commands and pack data from. Caller * must ensure the input is buffered, otherwise read performance * may suffer. * @param output * response back to the Git network client. Caller must ensure * the output is buffered, otherwise write performance may * suffer. * @param messages * secondary "notice" channel to send additional messages out * through. When run over SSH this should be tied back to the * standard error channel of the command execution. For most * other network connections this should be null. */ protected void init(final InputStream input, final OutputStream output, final OutputStream messages) { origOut = output; rawIn = input; rawOut = output; msgOut = messages; if (timeout > 0) { final Thread caller = Thread.currentThread(); timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ timeoutIn = new TimeoutInputStream(rawIn, timer); TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer); timeoutIn.setTimeout(timeout * 1000); o.setTimeout(timeout * 1000); rawIn = timeoutIn; rawOut = o; } pckIn = new PacketLineIn(rawIn); pckOut = new PacketLineOut(rawOut); pckOut.setFlushOnEnd(false); enabledCapabilities = new HashMap<>(); commands = new ArrayList<>(); } /** * Get advertised refs, or the default if not explicitly advertised. * * @return advertised refs, or the default if not explicitly advertised. * @throws IOException * if an IO error occurred */ private Map getAdvertisedOrDefaultRefs() throws IOException { if (refs == null) setAdvertisedRefs(null, null); return refs; } /** * Receive a pack from the stream and check connectivity if necessary. * * Visible for out-of-tree subclasses. Subclasses overriding this method * should invoke this implementation, as it alters the instance state (e.g. * it reads the pack from the input and parses it before running the * connectivity checks). * * @throws java.io.IOException * an error occurred during unpacking or connectivity checking. * @throws LargeObjectException * an large object needs to be opened for the check. * @throws SubmoduleValidationException * fails to validate the submodule. */ protected void receivePackAndCheckConnectivity() throws IOException, LargeObjectException, SubmoduleValidationException { receivePack(); if (needCheckConnectivity()) { checkSubmodules(); checkConnectivity(); } parser = null; } /** * Unlock the pack written by this object. * * @throws java.io.IOException * the pack could not be unlocked. */ private void unlockPack() throws IOException { if (packLock != null) { packLock.unlock(); packLock = null; } } /** * Generate an advertisement of available refs and capabilities. * * @param adv * the advertisement formatter. * @throws java.io.IOException * the formatter failed to write an advertisement. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * the hook denied advertisement. */ public void sendAdvertisedRefs(RefAdvertiser adv) throws IOException, ServiceMayNotContinueException { if (advertiseError != null) { adv.writeOne(PACKET_ERR + advertiseError); return; } try { advertiseRefsHook.advertiseRefs(this); } catch (ServiceMayNotContinueException fail) { if (fail.getMessage() != null) { adv.writeOne(PACKET_ERR + fail.getMessage()); fail.setOutput(); } throw fail; } adv.init(db); adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); adv.advertiseCapability(CAPABILITY_DELETE_REFS); adv.advertiseCapability(CAPABILITY_REPORT_STATUS); if (allowReceiveClientSID) { adv.advertiseCapability(OPTION_SESSION_ID); } if (allowQuiet) { adv.advertiseCapability(CAPABILITY_QUIET); } String nonce = getPushCertificateParser().getAdvertiseNonce(); if (nonce != null) { adv.advertiseCapability(nonce); } if (db.getRefDatabase().performsAtomicTransactions()) { adv.advertiseCapability(CAPABILITY_ATOMIC); } if (allowOfsDelta) { adv.advertiseCapability(CAPABILITY_OFS_DELTA); } if (allowPushOptions) { adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS); } adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); adv.send(getAdvertisedOrDefaultRefs().values()); for (ObjectId obj : advertisedHaves) { adv.advertiseHave(obj); } if (adv.isEmpty()) { adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$ } adv.end(); } /** * Returns the statistics on the received pack if available. This should be * called after {@link #receivePack} is called. * * @return ReceivedPackStatistics * @since 4.6 */ @Nullable public ReceivedPackStatistics getReceivedPackStatistics() { return stats; } /** * Extract the full list of refs from the ref-db. * * @return Map of all refname/ref */ private Map getAllRefs() { try { return db.getRefDatabase().getRefs().stream() .collect(Collectors.toMap(Ref::getName, Function.identity())); } catch (IOException e) { throw new UncheckedIOException(e); } } /** * Receive a list of commands from the input. * * @throws IOException * if an IO error occurred */ private void recvCommands() throws IOException { PacketLineIn pck = maxCommandBytes > 0 ? new PacketLineIn(rawIn, maxCommandBytes) : pckIn; PushCertificateParser certParser = getPushCertificateParser(); boolean firstPkt = true; try { for (;;) { String line; try { line = pck.readString(); } catch (EOFException eof) { if (commands.isEmpty()) return; throw eof; } if (PacketLineIn.isEnd(line)) { break; } int len = PACKET_SHALLOW.length() + 40; if (line.length() >= len && line.startsWith(PACKET_SHALLOW)) { parseShallow(line.substring(PACKET_SHALLOW.length(), len)); continue; } if (firstPkt) { firstPkt = false; FirstCommand firstLine = FirstCommand.fromLine(line); enabledCapabilities = firstLine.getCapabilities(); line = firstLine.getLine(); enableCapabilities(); if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) { certParser.receiveHeader(pck, !isBiDirectionalPipe()); continue; } } if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) { certParser.receiveSignature(pck); continue; } ReceiveCommand cmd = parseCommand(line); if (cmd.getRefName().equals(Constants.HEAD)) { cmd.setResult(Result.REJECTED_CURRENT_BRANCH); } else { cmd.setRef(refs.get(cmd.getRefName())); } commands.add(cmd); if (certParser.enabled()) { certParser.addCommand(cmd); } } pushCert = certParser.build(); if (hasCommands()) { readPostCommands(pck); } } catch (Throwable t) { discardCommands(); throw t; } } private void discardCommands() { if (sideBand) { long max = maxDiscardBytes; if (max < 0) { max = Math.max(3 * maxCommandBytes, 3L << 20); } try { new PacketLineIn(rawIn, max).discardUntilEnd(); } catch (IOException e) { // Ignore read failures attempting to discard. } } } private void parseShallow(String idStr) throws PackProtocolException { ObjectId id; try { id = ObjectId.fromString(idStr); } catch (InvalidObjectIdException e) { throw new PackProtocolException(e.getMessage(), e); } clientShallowCommits.add(id); } /** * @param in * request stream. * @throws IOException * request line cannot be read. */ void readPostCommands(PacketLineIn in) throws IOException { if (usePushOptions) { pushOptions = new ArrayList<>(4); for (;;) { String option = in.readString(); if (PacketLineIn.isEnd(option)) { break; } pushOptions.add(option); } } } /** * Enable capabilities based on a previously read capabilities line. */ private void enableCapabilities() { reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS); usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS); sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K); quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET); clientSID = enabledCapabilities.get(OPTION_SESSION_ID); if (sideBand) { OutputStream out = rawOut; rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out); msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out); errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out); pckOut = new PacketLineOut(rawOut); pckOut.setFlushOnEnd(false); } } /** * Check if the peer requested a capability. * * @param name * protocol name identifying the capability. * @return true if the peer requested the capability to be enabled. */ private boolean isCapabilityEnabled(String name) { return enabledCapabilities.containsKey(name); } private void checkRequestWasRead() { if (enabledCapabilities == null) throw new RequestNotYetReadException(); } /** * Whether a pack is expected based on the list of commands. * * @return {@code true} if a pack is expected based on the list of commands. */ private boolean needPack() { for (ReceiveCommand cmd : commands) { if (cmd.getType() != ReceiveCommand.Type.DELETE) return true; } return false; } /** * Receive a pack from the input and store it in the repository. * * @throws IOException * an error occurred reading or indexing the pack. */ private void receivePack() throws IOException { // It might take the client a while to pack the objects it needs // to send to us. We should increase our timeout so we don't // abort while the client is computing. // if (timeoutIn != null) timeoutIn.setTimeout(10 * timeout * 1000); ProgressMonitor receiving = NullProgressMonitor.INSTANCE; ProgressMonitor resolving = NullProgressMonitor.INSTANCE; if (sideBand && !quiet) resolving = new SideBandProgressMonitor(msgOut); try (ObjectInserter ins = db.newObjectInserter()) { String lockMsg = "jgit receive-pack"; //$NON-NLS-1$ if (getRefLogIdent() != null) lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$ parser = ins.newPackParser(packInputStream()); parser.setAllowThin(true); parser.setNeedNewObjectIds(checkReferencedAreReachable); parser.setNeedBaseObjectIds(checkReferencedAreReachable); parser.setCheckEofAfterPackFooter(!biDirectionalPipe && !isExpectDataAfterPackFooter()); parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter()); parser.setObjectChecker(objectChecker); parser.setLockMessage(lockMsg); parser.setMaxObjectSizeLimit(maxObjectSizeLimit); packLock = parser.parse(receiving, resolving); packSize = Long.valueOf(parser.getPackSize()); stats = parser.getReceivedPackStatistics(); ins.flush(); } if (timeoutIn != null) timeoutIn.setTimeout(timeout * 1000); } private InputStream packInputStream() { InputStream packIn = rawIn; if (maxPackSizeLimit >= 0) { packIn = new LimitedInputStream(packIn, maxPackSizeLimit) { @Override protected void limitExceeded() throws TooLargePackException { throw new TooLargePackException(limit); } }; } return packIn; } private boolean needCheckConnectivity() { return isCheckReceivedObjects() || isCheckReferencedObjectsAreReachable() || !getClientShallowCommits().isEmpty(); } private void checkSubmodules() throws IOException, LargeObjectException, SubmoduleValidationException { ObjectDatabase odb = db.getObjectDatabase(); if (objectChecker == null) { return; } for (GitmoduleEntry entry : objectChecker.getGitsubmodules()) { AnyObjectId blobId = entry.getBlobId(); ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB); SubmoduleValidator.assertValidGitModulesFile( new String(blob.getBytes(), UTF_8)); } } private void checkConnectivity() throws IOException { ProgressMonitor checking = NullProgressMonitor.INSTANCE; if (sideBand && !quiet) { SideBandProgressMonitor m = new SideBandProgressMonitor(msgOut); m.setDelayStart(750, TimeUnit.MILLISECONDS); checking = m; } connectivityChecker.checkConnectivity(createConnectivityCheckInfo(), advertisedHaves, checking); } private ConnectivityCheckInfo createConnectivityCheckInfo() { ConnectivityCheckInfo info = new ConnectivityCheckInfo(); info.setCheckObjects(checkReferencedAreReachable); info.setCommands(getAllCommands()); info.setRepository(db); info.setParser(parser); info.setWalk(walk); return info; } /** * Validate the command list. */ private void validateCommands() { for (ReceiveCommand cmd : commands) { final Ref ref = cmd.getRef(); if (cmd.getResult() != Result.NOT_ATTEMPTED) continue; if (cmd.getType() == ReceiveCommand.Type.DELETE) { if (!isAllowDeletes()) { // Deletes are not supported on this repository. cmd.setResult(Result.REJECTED_NODELETE); continue; } if (!isAllowBranchDeletes() && ref.getName().startsWith(Constants.R_HEADS)) { // Branches cannot be deleted, but other refs can. cmd.setResult(Result.REJECTED_NODELETE); continue; } } if (cmd.getType() == ReceiveCommand.Type.CREATE) { if (!isAllowCreates()) { cmd.setResult(Result.REJECTED_NOCREATE); continue; } if (ref != null && !isAllowNonFastForwards()) { // Creation over an existing ref is certainly not going // to be a fast-forward update. We can reject it early. // cmd.setResult(Result.REJECTED_NONFASTFORWARD); continue; } if (ref != null) { // A well behaved client shouldn't have sent us a // create command for a ref we advertised to it. // cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().refAlreadyExists); continue; } } if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) { ObjectId id = ref.getObjectId(); if (id == null) { id = ObjectId.zeroId(); } if (!ObjectId.zeroId().equals(cmd.getOldId()) && !id.equals(cmd.getOldId())) { // Delete commands can be sent with the old id matching our // advertised value, *OR* with the old id being 0{40}. Any // other requested old id is invalid. // cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().invalidOldIdSent); continue; } } if (cmd.getType() == ReceiveCommand.Type.UPDATE) { if (ref == null) { // The ref must have been advertised in order to be updated. // cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef); continue; } ObjectId id = ref.getObjectId(); if (id == null) { // We cannot update unborn branch cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().cannotUpdateUnbornBranch); continue; } if (!id.equals(cmd.getOldId())) { // A properly functioning client will send the same // object id we advertised. // cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().invalidOldIdSent); continue; } // Is this possibly a non-fast-forward style update? // RevObject oldObj, newObj; try { oldObj = walk.parseAny(cmd.getOldId()); } catch (IOException e) { receiveCommandErrorHandler .handleOldIdValidationException(cmd, e); continue; } try { newObj = walk.parseAny(cmd.getNewId()); } catch (IOException e) { receiveCommandErrorHandler .handleNewIdValidationException(cmd, e); continue; } if (oldObj instanceof RevCommit && newObj instanceof RevCommit) { try { if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj)) { cmd.setTypeFastForwardUpdate(); } else { cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); } } catch (IOException e) { receiveCommandErrorHandler .handleFastForwardCheckException(cmd, e); } } else { cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); } if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD && !isAllowNonFastForwards()) { cmd.setResult(Result.REJECTED_NONFASTFORWARD); continue; } } if (!cmd.getRefName().startsWith(Constants.R_REFS) || !Repository.isValidRefName(cmd.getRefName())) { cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().funnyRefname); } } } /** * Whether any commands have been rejected so far. * * @return if any commands have been rejected so far. */ private boolean anyRejects() { for (ReceiveCommand cmd : commands) { if (cmd.getResult() != Result.NOT_ATTEMPTED && cmd.getResult() != Result.OK) return true; } return false; } /** * Set the result to fail for any command that was not processed yet. * */ private void failPendingCommands() { ReceiveCommand.abort(commands); } /** * Filter the list of commands according to result. * * @param want * desired status to filter by. * @return a copy of the command list containing only those commands with * the desired status. * @since 5.7 */ protected List filterCommands(Result want) { return ReceiveCommand.filter(commands, want); } /** * Execute commands to update references. * @since 5.7 */ protected void executeCommands() { List toApply = filterCommands(Result.NOT_ATTEMPTED); if (toApply.isEmpty()) return; ProgressMonitor updating = NullProgressMonitor.INSTANCE; if (sideBand) { SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); pm.setDelayStart(250, TimeUnit.MILLISECONDS); updating = pm; } BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate(); batch.setAllowNonFastForwards(isAllowNonFastForwards()); batch.setAtomic(isAtomic()); batch.setRefLogIdent(getRefLogIdent()); batch.setRefLogMessage("push", true); //$NON-NLS-1$ batch.addCommand(toApply); try { batch.setPushCertificate(getPushCertificate()); batch.execute(walk, updating); } catch (IOException e) { receiveCommandErrorHandler.handleBatchRefUpdateException(toApply, e); } } /** * Send a status report. * * @param unpackError * an error that occurred during unpacking, or {@code null} * @throws java.io.IOException * an error occurred writing the status report. * @since 5.6 */ private void sendStatusReport(Throwable unpackError) throws IOException { Reporter out = new Reporter() { @Override void sendString(String s) throws IOException { if (reportStatus) { pckOut.writeString(s + '\n'); } else if (msgOut != null) { msgOut.write(Constants.encode(s + '\n')); } } }; try { if (unpackError != null) { out.sendString("unpack error " + unpackError.getMessage()); //$NON-NLS-1$ if (reportStatus) { for (ReceiveCommand cmd : commands) { out.sendString("ng " + cmd.getRefName() //$NON-NLS-1$ + " n/a (unpacker error)"); //$NON-NLS-1$ } } return; } if (reportStatus) { out.sendString("unpack ok"); //$NON-NLS-1$ } for (ReceiveCommand cmd : commands) { if (cmd.getResult() == Result.OK) { if (reportStatus) { out.sendString("ok " + cmd.getRefName()); //$NON-NLS-1$ } continue; } final StringBuilder r = new StringBuilder(); if (reportStatus) { r.append("ng ").append(cmd.getRefName()).append(" "); //$NON-NLS-1$ //$NON-NLS-2$ } else { r.append(" ! [rejected] ").append(cmd.getRefName()) //$NON-NLS-1$ .append(" ("); //$NON-NLS-1$ } if (cmd.getResult() == Result.REJECTED_MISSING_OBJECT) { if (cmd.getMessage() == null) r.append("missing object(s)"); //$NON-NLS-1$ else if (cmd.getMessage() .length() == Constants.OBJECT_ID_STRING_LENGTH) { // TODO: Using get/setMessage to store an OID is a // misuse. The caller should set a full error message. r.append("object "); //$NON-NLS-1$ r.append(cmd.getMessage()); r.append(" missing"); //$NON-NLS-1$ } else { r.append(cmd.getMessage()); } } else if (cmd.getMessage() != null) { r.append(cmd.getMessage()); } else { switch (cmd.getResult()) { case NOT_ATTEMPTED: r.append("server bug; ref not processed"); //$NON-NLS-1$ break; case REJECTED_NOCREATE: r.append("creation prohibited"); //$NON-NLS-1$ break; case REJECTED_NODELETE: r.append("deletion prohibited"); //$NON-NLS-1$ break; case REJECTED_NONFASTFORWARD: r.append("non-fast forward"); //$NON-NLS-1$ break; case REJECTED_CURRENT_BRANCH: r.append("branch is currently checked out"); //$NON-NLS-1$ break; case REJECTED_OTHER_REASON: r.append("unspecified reason"); //$NON-NLS-1$ break; case LOCK_FAILURE: r.append("failed to lock"); //$NON-NLS-1$ break; case REJECTED_MISSING_OBJECT: case OK: // We shouldn't have reached this case (see 'ok' case // above and if-statement above). throw new AssertionError(); } } if (!reportStatus) { r.append(")"); //$NON-NLS-1$ } out.sendString(r.toString()); } } finally { if (reportStatus) { pckOut.end(); } } } /** * Close and flush (if necessary) the underlying streams. * * @throws IOException * if an IO error occurred */ private void close() throws IOException { if (sideBand) { // If we are using side band, we need to send a final // flush-pkt to tell the remote peer the side band is // complete and it should stop decoding. We need to // use the original output stream as rawOut is now the // side band data channel. // ((SideBandOutputStream) msgOut).flushBuffer(); ((SideBandOutputStream) rawOut).flushBuffer(); PacketLineOut plo = new PacketLineOut(origOut); plo.setFlushOnEnd(false); plo.end(); } if (biDirectionalPipe) { // If this was a native git connection, flush the pipe for // the caller. For smart HTTP we don't do this flush and // instead let the higher level HTTP servlet code do it. // if (!sideBand && msgOut != null) msgOut.flush(); rawOut.flush(); } } /** * Release any resources used by this object. * * @throws java.io.IOException * the pack could not be unlocked. */ private void release() throws IOException { walk.close(); unlockPack(); timeoutIn = null; rawIn = null; rawOut = null; msgOut = null; pckIn = null; pckOut = null; refs = null; // Keep the capabilities. If responses are sent after this release // we need to remember at least whether sideband communication has to be // used commands = null; if (timer != null) { try { timer.terminate(); } finally { timer = null; } } } /** Interface for reporting status messages. */ abstract static class Reporter { abstract void sendString(String s) throws IOException; } /** * Get the push certificate used to verify the pusher's identity. *

* Only valid after commands are read from the wire. * * @return the parsed certificate, or null if push certificates are disabled * or no cert was presented by the client. * @since 4.1 */ public PushCertificate getPushCertificate() { return pushCert; } /** * Set the push certificate used to verify the pusher's identity. *

* Should only be called if reconstructing an instance without going through * the normal {@link #recvCommands()} flow. * * @param cert * the push certificate to set. * @since 4.1 */ public void setPushCertificate(PushCertificate cert) { pushCert = cert; } /** * Gets an unmodifiable view of the option strings associated with the push. * * @return an unmodifiable view of pushOptions, or null (if pushOptions is). * @since 4.5 */ @Nullable public List getPushOptions() { if (isAllowPushOptions() && usePushOptions) { return Collections.unmodifiableList(pushOptions); } // The client doesn't support push options. Return null to // distinguish this from the case where the client declared support // for push options and sent an empty list of them. return null; } /** * Set the push options supplied by the client. *

* Should only be called if reconstructing an instance without going through * the normal {@link #recvCommands()} flow. * * @param options * the list of options supplied by the client. The * {@code ReceivePack} instance takes ownership of this list. * Callers are encouraged to first create a copy if the list may * be modified later. * @since 4.5 */ public void setPushOptions(@Nullable List options) { usePushOptions = options != null; pushOptions = options; } /** * Get the hook invoked before updates occur. * * @return the hook invoked before updates occur. */ public PreReceiveHook getPreReceiveHook() { return preReceive; } /** * Set the hook which is invoked prior to commands being executed. *

* Only valid commands (those which have no obvious errors according to the * received input and this instance's configuration) are passed into the * hook. The hook may mark a command with a result of any value other than * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} to * block its execution. *

* The hook may be called with an empty command collection if the current * set is completely invalid. * * @param h * the hook instance; may be null to disable the hook. */ public void setPreReceiveHook(PreReceiveHook h) { preReceive = h != null ? h : PreReceiveHook.NULL; } /** * Get the hook invoked after updates occur. * * @return the hook invoked after updates occur. */ public PostReceiveHook getPostReceiveHook() { return postReceive; } /** * Set the hook which is invoked after commands are executed. *

* Only successful commands (type is * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}) are passed * into the hook. The hook may be called with an empty command collection if * the current set all resulted in an error. * * @param h * the hook instance; may be null to disable the hook. */ public void setPostReceiveHook(PostReceiveHook h) { postReceive = h != null ? h : PostReceiveHook.NULL; } /** * Get the current unpack error handler. * * @return the current unpack error handler. * @since 5.8 */ public UnpackErrorHandler getUnpackErrorHandler() { return unpackErrorHandler; } /** * Set the unpackErrorHandler * * @param unpackErrorHandler * the unpackErrorHandler * @since 5.7 */ public void setUnpackErrorHandler(UnpackErrorHandler unpackErrorHandler) { this.unpackErrorHandler = unpackErrorHandler; } /** * Get the client session-id * * @return The client session-id. * @since 6.4 */ public String getClientSID() { return clientSID; } /** * Execute the receive task on the socket. * * @param input * raw input to read client commands and pack data from. Caller * must ensure the input is buffered, otherwise read performance * may suffer. * @param output * response back to the Git network client. Caller must ensure * the output is buffered, otherwise write performance may * suffer. * @param messages * secondary "notice" channel to send additional messages out * through. When run over SSH this should be tied back to the * standard error channel of the command execution. For most * other network connections this should be null. * @throws java.io.IOException * if an IO error occurred */ public void receive(final InputStream input, final OutputStream output, final OutputStream messages) throws IOException { init(input, output, messages); try { service(); } catch (PackProtocolException e) { fatalError(e.getMessage()); throw e; } catch (InputOverLimitIOException e) { String msg = JGitText.get().tooManyCommands; fatalError(msg); throw new PackProtocolException(msg, e); } finally { try { close(); } finally { release(); } } } /** * Execute the receive task on the socket. * *

* Same as {@link #receive}, but the exceptions are not reported to the * client yet. * * @param input * raw input to read client commands and pack data from. Caller * must ensure the input is buffered, otherwise read performance * may suffer. * @param output * response back to the Git network client. Caller must ensure * the output is buffered, otherwise write performance may * suffer. * @param messages * secondary "notice" channel to send additional messages out * through. When run over SSH this should be tied back to the * standard error channel of the command execution. For most * other network connections this should be null. * @throws java.io.IOException * if an IO error occurred * @since 5.7 */ public void receiveWithExceptionPropagation(InputStream input, OutputStream output, OutputStream messages) throws IOException { init(input, output, messages); try { service(); } finally { try { close(); } finally { release(); } } } private void service() throws IOException { if (isBiDirectionalPipe()) { sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); pckOut.flush(); } else getAdvertisedOrDefaultRefs(); if (hasError()) return; recvCommands(); if (hasCommands()) { try (PostReceiveExecutor e = new PostReceiveExecutor()) { if (needPack()) { try { receivePackAndCheckConnectivity(); } catch (IOException | RuntimeException | SubmoduleValidationException | Error err) { unlockPack(); unpackErrorHandler.handleUnpackException(err); throw new UnpackException(err); } } try { setAtomic(isCapabilityEnabled(CAPABILITY_ATOMIC)); validateCommands(); if (atomic && anyRejects()) { failPendingCommands(); } preReceive.onPreReceive( this, filterCommands(Result.NOT_ATTEMPTED)); if (atomic && anyRejects()) { failPendingCommands(); } executeCommands(); } finally { unlockPack(); } sendStatusReport(null); } autoGc(); } } private void autoGc() { Repository repo = getRepository(); if (!repo.getConfig().getBoolean(ConfigConstants.CONFIG_RECEIVE_SECTION, ConfigConstants.CONFIG_KEY_AUTOGC, true)) { return; } repo.autoGC(NullProgressMonitor.INSTANCE); } static ReceiveCommand parseCommand(String line) throws PackProtocolException { if (line == null || line.length() < 83) { throw new PackProtocolException( JGitText.get().errorInvalidProtocolWantedOldNewRef); } String oldStr = line.substring(0, 40); String newStr = line.substring(41, 81); ObjectId oldId, newId; try { oldId = ObjectId.fromString(oldStr); newId = ObjectId.fromString(newStr); } catch (InvalidObjectIdException e) { throw new PackProtocolException( JGitText.get().errorInvalidProtocolWantedOldNewRef, e); } String name = line.substring(82); if (!Repository.isValidRefName(name)) { throw new PackProtocolException( JGitText.get().errorInvalidProtocolWantedOldNewRef); } return new ReceiveCommand(oldId, newId, name); } private class PostReceiveExecutor implements AutoCloseable { @Override public void close() { postReceive.onPostReceive(ReceivePack.this, filterCommands(Result.OK)); } } private class DefaultUnpackErrorHandler implements UnpackErrorHandler { @Override public void handleUnpackException(Throwable t) throws IOException { sendStatusReport(t); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6733 Content-Disposition: inline; filename="ReceivedPackStatistics.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "8887e263eabd5a65d6dbfa05bb02ad4f5130d277" /* * Copyright (C) 2016, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Constants; /** * Statistics about {@link org.eclipse.jgit.transport.PackParser}. * * @since 4.6 */ public class ReceivedPackStatistics { private long numBytesRead; private long numBytesDuplicated; private long numWholeCommit; private long numWholeTree; private long numWholeBlob; private long numWholeTag; private long numOfsDelta; private long numRefDelta; private long numObjectsDuplicated; private long numDeltaCommit; private long numDeltaTree; private long numDeltaBlob; private long numDeltaTag; /** * Get number of bytes read from the input stream * * @return number of bytes read from the input stream */ public long getNumBytesRead() { return numBytesRead; } /** * Get number of bytes of objects already in the local database * * @return number of bytes of objects appeared in both the pack sent by the * client and the local database * @since 5.10 */ public long getNumBytesDuplicated() { return numBytesDuplicated; } /** * Get number of whole commit objects in the pack * * @return number of whole commit objects in the pack */ public long getNumWholeCommit() { return numWholeCommit; } /** * Get number of whole tree objects in the pack * * @return number of whole tree objects in the pack */ public long getNumWholeTree() { return numWholeTree; } /** * Get number of whole blob objects in the pack * * @return number of whole blob objects in the pack */ public long getNumWholeBlob() { return numWholeBlob; } /** * Get number of whole tag objects in the pack * * @return number of whole tag objects in the pack */ public long getNumWholeTag() { return numWholeTag; } /** * Get number of offset delta objects in the pack * * @return number of offset delta objects in the pack */ public long getNumOfsDelta() { return numOfsDelta; } /** * Get number of ref delta objects in the pack * * @return number of ref delta objects in the pack */ public long getNumRefDelta() { return numRefDelta; } /** * Get number of objects already in the local database * * @return number of objects appeared in both the pack sent by the client * and the local database * @since 5.10 */ public long getNumObjectsDuplicated() { return numObjectsDuplicated; } /** * Get number of delta commit objects in the pack * * @return number of delta commit objects in the pack */ public long getNumDeltaCommit() { return numDeltaCommit; } /** * Get number of delta tree objects in the pack * * @return number of delta tree objects in the pack */ public long getNumDeltaTree() { return numDeltaTree; } /** * Get number of delta blob objects in the pack * * @return number of delta blob objects in the pack */ public long getNumDeltaBlob() { return numDeltaBlob; } /** * Get number of delta tag objects in the pack * * @return number of delta tag objects in the pack */ public long getNumDeltaTag() { return numDeltaTag; } /** A builder for {@link ReceivedPackStatistics}. */ public static class Builder { private long numBytesRead; private long numBytesDuplicated; private long numWholeCommit; private long numWholeTree; private long numWholeBlob; private long numWholeTag; private long numOfsDelta; private long numRefDelta; private long numObjectsDuplicated; private long numDeltaCommit; private long numDeltaTree; private long numDeltaBlob; private long numDeltaTag; /** * Set number of bytes read from the input stream * * @param numBytesRead * number of bytes read from the input stream * @return this */ public Builder setNumBytesRead(long numBytesRead) { this.numBytesRead = numBytesRead; return this; } /** * Increment additional bytes already in the local database * * @param size * additional bytes already in the local database * @return this * @since 5.10 */ Builder incrementNumBytesDuplicated(long size) { numBytesDuplicated += size; return this; } /** * Increment a whole object count. * * @param type OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, or OBJ_TAG * @return this */ public Builder addWholeObject(int type) { switch (type) { case Constants.OBJ_COMMIT: numWholeCommit++; break; case Constants.OBJ_TREE: numWholeTree++; break; case Constants.OBJ_BLOB: numWholeBlob++; break; case Constants.OBJ_TAG: numWholeTag++; break; default: throw new IllegalArgumentException( type + " cannot be a whole object"); //$NON-NLS-1$ } return this; } /** * Increment offset delta * * @return this */ public Builder addOffsetDelta() { numOfsDelta++; return this; } /** * Increment ref delta * * @return this */ public Builder addRefDelta() { numRefDelta++; return this; } /** * Increment the duplicated object count. * * @return this * @since 5.10 */ Builder incrementObjectsDuplicated() { numObjectsDuplicated++; return this; } /** * Increment a delta object count. * * @param type OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, or OBJ_TAG * @return this */ public Builder addDeltaObject(int type) { switch (type) { case Constants.OBJ_COMMIT: numDeltaCommit++; break; case Constants.OBJ_TREE: numDeltaTree++; break; case Constants.OBJ_BLOB: numDeltaBlob++; break; case Constants.OBJ_TAG: numDeltaTag++; break; default: throw new IllegalArgumentException( "delta should be a delta to a whole object. " + //$NON-NLS-1$ type + " cannot be a whole object"); //$NON-NLS-1$ } return this; } ReceivedPackStatistics build() { ReceivedPackStatistics s = new ReceivedPackStatistics(); s.numBytesRead = numBytesRead; s.numBytesDuplicated = numBytesDuplicated; s.numWholeCommit = numWholeCommit; s.numWholeTree = numWholeTree; s.numWholeBlob = numWholeBlob; s.numWholeTag = numWholeTag; s.numOfsDelta = numOfsDelta; s.numRefDelta = numRefDelta; s.numDeltaCommit = numDeltaCommit; s.numDeltaTree = numDeltaTree; s.numDeltaBlob = numDeltaBlob; s.numDeltaTag = numDeltaTag; s.numObjectsDuplicated = numObjectsDuplicated; return s; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11146 Content-Disposition: inline; filename="RefAdvertiser.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "3d4bea2e48ac8f4de6004fbbb40838f5b9792e30" /* * Copyright (C) 2008, 2020 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF; import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_PEELED; import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_SYMREF_TARGET; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; import org.eclipse.jgit.lib.Repository; /** * Support for the start of {@link org.eclipse.jgit.transport.UploadPack} and * {@link org.eclipse.jgit.transport.ReceivePack}. */ public abstract class RefAdvertiser { /** Advertiser which frames lines in a {@link PacketLineOut} format. */ public static class PacketLineOutRefAdvertiser extends RefAdvertiser { private final CharsetEncoder utf8 = UTF_8.newEncoder(); private final PacketLineOut pckOut; private byte[] binArr = new byte[256]; private ByteBuffer binBuf = ByteBuffer.wrap(binArr); private char[] chArr = new char[256]; private CharBuffer chBuf = CharBuffer.wrap(chArr); /** * Create a new advertiser for the supplied stream. * * @param out * the output stream. */ public PacketLineOutRefAdvertiser(PacketLineOut out) { pckOut = out; } @Override public void advertiseId(AnyObjectId id, String refName) throws IOException { id.copyTo(binArr, 0); binArr[OBJECT_ID_STRING_LENGTH] = ' '; binBuf.position(OBJECT_ID_STRING_LENGTH + 1); append(refName); if (first) { first = false; if (!capablities.isEmpty()) { append('\0'); for (String cap : capablities) { append(' '); append(cap); } } } append('\n'); pckOut.writePacket(binArr, 0, binBuf.position()); } private void append(String str) throws CharacterCodingException { int n = str.length(); if (n > chArr.length) { chArr = new char[n + 256]; chBuf = CharBuffer.wrap(chArr); } str.getChars(0, n, chArr, 0); chBuf.position(0).limit(n); utf8.reset(); for (;;) { CoderResult cr = utf8.encode(chBuf, binBuf, true); if (cr.isOverflow()) { grow(); } else if (cr.isUnderflow()) { break; } else { cr.throwException(); } } } private void append(int b) { if (!binBuf.hasRemaining()) { grow(); } binBuf.put((byte) b); } private void grow() { int cnt = binBuf.position(); byte[] tmp = new byte[binArr.length << 1]; System.arraycopy(binArr, 0, tmp, 0, cnt); binArr = tmp; binBuf = ByteBuffer.wrap(binArr); binBuf.position(cnt); } @Override protected void writeOne(CharSequence line) throws IOException { pckOut.writeString(line.toString()); } @Override protected void end() throws IOException { pckOut.end(); } } private final StringBuilder tmpLine = new StringBuilder(100); private final char[] tmpId = new char[Constants.OBJECT_ID_STRING_LENGTH]; final Set capablities = new LinkedHashSet<>(); private final Set sent = new HashSet<>(); private Repository repository; private boolean derefTags; boolean first = true; private boolean useProtocolV2; /* only used in protocol v2 */ private final Map symrefs = new HashMap<>(); /** * Initialize this advertiser with a repository for peeling tags. * * @param src * the repository to read from. */ public void init(Repository src) { repository = src; } /** * Set whether this advertiser should use protocol v2 * * @param b * true if this advertiser should advertise using the protocol v2 * format, false otherwise * @since 5.0 */ public void setUseProtocolV2(boolean b) { useProtocolV2 = b; } /** * Toggle tag peeling. *

* * This method must be invoked prior to any of the following: *

    *
  • {@link #send(Collection)}
  • *
* * @param deref * true to show the dereferenced value of a tag as the special * ref $tag^{} ; false to omit it from the output. */ public void setDerefTags(boolean deref) { derefTags = deref; } /** * Add one protocol capability to the initial advertisement. *

* This method must be invoked prior to any of the following: *

    *
  • {@link #send(Collection)}
  • *
  • {@link #advertiseHave(AnyObjectId)}
  • *
* * @param name * the name of a single protocol capability supported by the * caller. The set of capabilities are sent to the client in the * advertisement, allowing the client to later selectively enable * features it recognizes. */ public void advertiseCapability(String name) { capablities.add(name); } /** * Add one protocol capability with a value ({@code "name=value"}). * * @param name * name of the capability. * @param value * value. If null the capability will not be added. * @since 4.0 */ public void advertiseCapability(String name, String value) { if (value != null) { capablities.add(name + '=' + value); } } /** * Add a symbolic ref to capabilities. *

* This method must be invoked prior to any of the following: *

    *
  • {@link #send(Collection)}
  • *
  • {@link #advertiseHave(AnyObjectId)}
  • *
* * @param from * The symbolic ref, e.g. "HEAD" * @param to * The real ref it points to, e.g. "refs/heads/master" * @since 3.6 */ public void addSymref(String from, String to) { if (useProtocolV2) { symrefs.put(from, to); } else { advertiseCapability(OPTION_SYMREF, from + ':' + to); } } /** * Format an advertisement for the supplied refs. * * @param refs * zero or more refs to format for the client. The collection is * sorted before display if necessary, and therefore may appear * in any order. * @return set of ObjectIds that were advertised to the client. * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. * @since 5.0 */ public Set send(Collection refs) throws IOException { for (Ref ref : RefComparator.sort(refs)) { // TODO(jrn) revive the SortedMap optimization e.g. by introducing // SortedList ObjectId objectId = ref.getObjectId(); if (objectId == null) { continue; } if (useProtocolV2) { String symrefPart = symrefs.containsKey(ref.getName()) ? (' ' + REF_ATTR_SYMREF_TARGET + symrefs.get(ref.getName())) : ""; //$NON-NLS-1$ String peelPart = ""; //$NON-NLS-1$ if (derefTags) { if (!ref.isPeeled() && repository != null) { ref = repository.getRefDatabase().peel(ref); } ObjectId peeledObjectId = ref.getPeeledObjectId(); if (peeledObjectId != null) { peelPart = ' ' + REF_ATTR_PEELED + peeledObjectId.getName(); } } writeOne(objectId.getName() + " " + ref.getName() + symrefPart //$NON-NLS-1$ + peelPart + "\n"); //$NON-NLS-1$ continue; } advertiseAny(objectId, ref.getName()); if (!derefTags) continue; if (!ref.isPeeled()) { if (repository == null) continue; ref = repository.getRefDatabase().peel(ref); } if (ref.getPeeledObjectId() != null) advertiseAny(ref.getPeeledObjectId(), ref.getName() + "^{}"); //$NON-NLS-1$ } return sent; } /** * Advertise one object is available using the magic {@code .have}. *

* The magic {@code .have} advertisement is not available for fetching by a * client, but can be used by a client when considering a delta base * candidate before transferring data in a push. Within the record created * by this method the ref name is simply the invalid string {@code .have}. * * @param id * identity of the object that is assumed to exist. * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ public void advertiseHave(AnyObjectId id) throws IOException { advertiseAnyOnce(id, ".have"); //$NON-NLS-1$ } /** * Whether no advertisements have been sent yet. * * @return true if no advertisements have been sent yet. */ public boolean isEmpty() { return first; } private void advertiseAnyOnce(AnyObjectId obj, String refName) throws IOException { if (!sent.contains(obj)) advertiseAny(obj, refName); } private void advertiseAny(AnyObjectId obj, String refName) throws IOException { sent.add(obj.toObjectId()); advertiseId(obj, refName); } /** * Advertise one object under a specific name. *

* If the advertised object is a tag, this method does not advertise the * peeled version of it. * * @param id * the object to advertise. * @param refName * name of the reference to advertise the object as, can be any * string not including the NUL byte. * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ public void advertiseId(AnyObjectId id, String refName) throws IOException { tmpLine.setLength(0); id.copyTo(tmpId, tmpLine); tmpLine.append(' '); tmpLine.append(refName); if (first) { first = false; if (!capablities.isEmpty()) { tmpLine.append('\0'); for (String capName : capablities) { tmpLine.append(' '); tmpLine.append(capName); } tmpLine.append(' '); } } tmpLine.append('\n'); writeOne(tmpLine); } /** * Write a single advertisement line. * * @param line * the advertisement line to be written. The line always ends * with LF. Never null or the empty string. * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ protected abstract void writeOne(CharSequence line) throws IOException; /** * Mark the end of the advertisements. * * @throws java.io.IOException * the underlying output stream failed to write out an * advertisement record. */ protected abstract void end() throws IOException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1216 Content-Disposition: inline; filename="RefFilter.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "580f2feaac221d974b2d7fb5b8e0565dcf6f99ea" /* * Copyright (C) 2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Map; import org.eclipse.jgit.lib.Ref; /** * Filters the list of refs that are advertised to the client. *

* The filter is called by {@link org.eclipse.jgit.transport.ReceivePack} and * {@link org.eclipse.jgit.transport.UploadPack} to ensure that the refs are * filtered before they are advertised to the client. *

* This can be used by applications to control visibility of certain refs based * on a custom set of rules. */ public interface RefFilter { /** * The default filter, allows all refs to be shown. */ RefFilter DEFAULT = (Map refs) -> refs; /** * Filters a {@code Map} of refs before it is advertised to the client. * * @param refs * the refs which this method need to consider. * @return * the filtered map of refs. */ Map filter(Map refs); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1478 Content-Disposition: inline; filename="RefLeaseSpec.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "8a131c897131c4a75ab9b31968a8b787c4c7f6f0" /* * Copyright (C) 2017 Two Sigma Open Source and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.Serializable; /** * Describes the expected value for a ref being pushed. * * @since 4.7 */ public class RefLeaseSpec implements Serializable { private static final long serialVersionUID = 1L; /** Name of the ref whose value we want to check. */ private final String ref; /** Local commitish to get expected value from. */ private final String expected; /** *

Constructor for RefLeaseSpec.

* * @param ref * ref being pushed * @param expected * the expected value of the ref */ public RefLeaseSpec(String ref, String expected) { this.ref = ref; this.expected = expected; } /** * Get the ref to protect. * * @return name of ref to check. */ public String getRef() { return ref; } /** * Get the expected value of the ref, in the form * of a local committish * * @return expected ref value. */ public String getExpected() { return expected; } @Override public String toString() { final StringBuilder r = new StringBuilder(); r.append(getRef()); r.append(':'); r.append(getExpected()); return r.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 20797 Content-Disposition: inline; filename="RefSpec.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "0466085b32097df51e70b035c682182101d37f5c" /* * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.Serializable; import java.text.MessageFormat; import java.util.Objects; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; /** * Describes how refs in one repository copy into another repository. *

* A ref specification provides matching support and limited rules to rewrite a * reference in one repository to another reference in another repository. */ public class RefSpec implements Serializable { private static final long serialVersionUID = 1L; /** * Suffix for wildcard ref spec component, that indicate matching all refs * with specified prefix. */ public static final String WILDCARD_SUFFIX = "/*"; //$NON-NLS-1$ /** * Check whether provided string is a wildcard ref spec component. * * @param s * ref spec component - string to test. Can be null. * @return true if provided string is a wildcard ref spec component. */ public static boolean isWildcard(String s) { return s != null && s.contains("*"); //$NON-NLS-1$ } /** Does this specification ask for forced updated (rewind/reset)? */ private boolean force; /** Is this specification actually a wildcard match? */ private boolean wildcard; /** Is this the special ":" RefSpec? */ private boolean matching; /** Is this a negative refspec. */ private boolean negative; /** * How strict to be about wildcards. * * @since 4.5 */ public enum WildcardMode { /** * Reject refspecs with an asterisk on the source side and not the * destination side or vice versa. This is the mode used by FetchCommand * and PushCommand to create a one-to-one mapping between source and * destination refs. */ REQUIRE_MATCH, /** * Allow refspecs with an asterisk on only one side. This can create a * many-to-one mapping between source and destination refs, so * expandFromSource and expandFromDestination are not usable in this * mode. */ ALLOW_MISMATCH } /** Whether a wildcard is allowed on one side but not the other. */ private WildcardMode allowMismatchedWildcards; /** Name of the ref(s) we would copy from. */ private String srcName; /** Name of the ref(s) we would copy into. */ private String dstName; /** * Construct an empty RefSpec. *

* A newly created empty RefSpec is not suitable for use in most * applications, as at least one field must be set to match a source name. */ public RefSpec() { matching = false; force = false; wildcard = false; srcName = Constants.HEAD; dstName = null; negative =false; allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH; } /** * Parse a ref specification for use during transport operations. *

* {@link RefSpec}s can be regular or negative, regular RefSpecs indicate * what to include in transport operations while negative RefSpecs indicate * what to exclude in fetch. *

* Negative {@link RefSpec}s can't be force, must have only source or * destination. Wildcard patterns are also supported in negative RefSpecs * but they can not go with {@code WildcardMode.REQUIRE_MATCH} because they * are natually one to many mappings. * *

* Specifications are typically one of the following forms: *

    *
  • refs/heads/master
  • *
  • refs/heads/master:refs/remotes/origin/master
  • *
  • refs/heads/*:refs/remotes/origin/*
  • *
  • +refs/heads/master
  • *
  • +refs/heads/master:refs/remotes/origin/master
  • *
  • +refs/heads/*:refs/remotes/origin/*
  • *
  • +refs/pull/*/head:refs/remotes/origin/pr/*
  • *
  • :refs/heads/master
  • *
* * If the wildcard mode allows mismatches, then these ref specs are also * valid: *
    *
  • refs/heads/*
  • *
  • refs/heads/*:refs/heads/master
  • *
* * Negative specifications are usually like: *
    *
  • ^:refs/heads/master
  • *
  • ^refs/heads/*
  • *
* * @param spec * string describing the specification. * @param mode * whether to allow a wildcard on one side without a wildcard on * the other. * @throws java.lang.IllegalArgumentException * the specification is invalid. * @since 4.5 */ public RefSpec(String spec, WildcardMode mode) { this.allowMismatchedWildcards = mode; String s = spec; if (s.startsWith("^+") || s.startsWith("+^")) { //$NON-NLS-1$ //$NON-NLS-2$ throw new IllegalArgumentException( JGitText.get().invalidNegativeAndForce); } if (s.startsWith("+")) { //$NON-NLS-1$ force = true; s = s.substring(1); } if (s.startsWith("^")) { //$NON-NLS-1$ negative = true; s = s.substring(1); } boolean matchPushSpec = false; final int c = s.lastIndexOf(':'); if (c == 0) { s = s.substring(1); if (s.isEmpty()) { matchPushSpec = true; wildcard = true; srcName = Constants.R_HEADS + '*'; dstName = srcName; } else { if (isWildcard(s)) { wildcard = true; if (mode == WildcardMode.REQUIRE_MATCH) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidWildcards, spec)); } } dstName = checkValid(s); } } else if (c > 0) { String src = s.substring(0, c); String dst = s.substring(c + 1); if (isWildcard(src) && isWildcard(dst)) { // Both contain wildcard wildcard = true; } else if (isWildcard(src) || isWildcard(dst)) { wildcard = true; if (mode == WildcardMode.REQUIRE_MATCH) throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidWildcards, spec)); } srcName = checkValid(src); dstName = checkValid(dst); } else { if (isWildcard(s)) { if (mode == WildcardMode.REQUIRE_MATCH) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidWildcards, spec)); } wildcard = true; } srcName = checkValid(s); } // Negative refspecs must only have dstName or srcName. if (isNegative()) { if (isNullOrEmpty(srcName) && isNullOrEmpty(dstName)) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidRefSpec, spec)); } if (!isNullOrEmpty(srcName) && !isNullOrEmpty(dstName)) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidRefSpec, spec)); } if(wildcard && mode == WildcardMode.REQUIRE_MATCH) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().invalidRefSpec, spec));} } matching = matchPushSpec; } /** * Parse a ref specification for use during transport operations. *

* Specifications are typically one of the following forms: *

    *
  • refs/heads/master
  • *
  • refs/heads/master:refs/remotes/origin/master
  • *
  • refs/heads/*:refs/remotes/origin/*
  • *
  • +refs/heads/master
  • *
  • +refs/heads/master:refs/remotes/origin/master
  • *
  • +refs/heads/*:refs/remotes/origin/*
  • *
  • +refs/pull/*/head:refs/remotes/origin/pr/*
  • *
  • :refs/heads/master
  • *
* * @param spec * string describing the specification. * @throws java.lang.IllegalArgumentException * the specification is invalid. */ public RefSpec(String spec) { this(spec, spec.startsWith("^") ? WildcardMode.ALLOW_MISMATCH //$NON-NLS-1$ : WildcardMode.REQUIRE_MATCH); } private RefSpec(RefSpec p) { matching = false; force = p.isForceUpdate(); wildcard = p.isWildcard(); negative = p.isNegative(); srcName = p.getSource(); dstName = p.getDestination(); allowMismatchedWildcards = p.allowMismatchedWildcards; } /** * Tells whether this {@link RefSpec} is the special "matching" RefSpec ":" * for pushing. * * @return whether this is a "matching" RefSpec * @since 6.1 */ public boolean isMatching() { return matching; } /** * Check if this specification wants to forcefully update the destination. * * @return true if this specification asks for updates without merge tests. */ public boolean isForceUpdate() { return force; } /** * Create a new RefSpec with a different force update setting. * * @param forceUpdate * new value for force update in the returned instance. * @return a new RefSpec with force update as specified. */ public RefSpec setForceUpdate(boolean forceUpdate) { final RefSpec r = new RefSpec(this); if (forceUpdate && isNegative()) { throw new IllegalArgumentException( JGitText.get().invalidNegativeAndForce); } r.matching = matching; r.force = forceUpdate; return r; } /** * Check if this specification is actually a wildcard pattern. *

* If this is a wildcard pattern then the source and destination names * returned by {@link #getSource()} and {@link #getDestination()} will not * be actual ref names, but instead will be patterns. * * @return true if this specification could match more than one ref. */ public boolean isWildcard() { return wildcard; } /** * Check if this specification is a negative one. * * @return true if this specification is negative. * @since 6.2 */ public boolean isNegative() { return negative; } /** * Get the source ref description. *

* During a fetch this is the name of the ref on the remote repository we * are fetching from. During a push this is the name of the ref on the local * repository we are pushing out from. * * @return name (or wildcard pattern) to match the source ref. */ public String getSource() { return srcName; } /** * Create a new RefSpec with a different source name setting. * * @param source * new value for source in the returned instance. * @return a new RefSpec with source as specified. * @throws java.lang.IllegalStateException * There is already a destination configured, and the wildcard * status of the existing destination disagrees with the * wildcard status of the new source. */ public RefSpec setSource(String source) { final RefSpec r = new RefSpec(this); r.srcName = checkValid(source); if (isWildcard(r.srcName) && r.dstName == null) throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard); if (isWildcard(r.srcName) != isWildcard(r.dstName)) throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); return r; } /** * Get the destination ref description. *

* During a fetch this is the local tracking branch that will be updated * with the new ObjectId after fetching is complete. During a push this is * the remote ref that will be updated by the remote's receive-pack process. *

* If null during a fetch no tracking branch should be updated and the * ObjectId should be stored transiently in order to prepare a merge. *

* If null during a push, use {@link #getSource()} instead. * * @return name (or wildcard) pattern to match the destination ref. */ public String getDestination() { return dstName; } /** * Create a new RefSpec with a different destination name setting. * * @param destination * new value for destination in the returned instance. * @return a new RefSpec with destination as specified. * @throws java.lang.IllegalStateException * There is already a source configured, and the wildcard status * of the existing source disagrees with the wildcard status of * the new destination. */ public RefSpec setDestination(String destination) { final RefSpec r = new RefSpec(this); r.dstName = checkValid(destination); if (isWildcard(r.dstName) && r.srcName == null) throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard); if (isWildcard(r.srcName) != isWildcard(r.dstName)) throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); return r; } /** * Create a new RefSpec with a different source/destination name setting. * * @param source * new value for source in the returned instance. * @param destination * new value for destination in the returned instance. * @return a new RefSpec with destination as specified. * @throws java.lang.IllegalArgumentException * The wildcard status of the new source disagrees with the * wildcard status of the new destination. */ public RefSpec setSourceDestination(String source, String destination) { if (isWildcard(source) != isWildcard(destination)) throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch); final RefSpec r = new RefSpec(this); r.wildcard = isWildcard(source); r.srcName = source; r.dstName = destination; return r; } /** * Does this specification's source description match the ref name? * * @param r * ref name that should be tested. * @return true if the names match; false otherwise. */ public boolean matchSource(String r) { return match(r, getSource()); } /** * Does this specification's source description match the ref? * * @param r * ref whose name should be tested. * @return true if the names match; false otherwise. */ public boolean matchSource(Ref r) { return match(r.getName(), getSource()); } /** * Does this specification's destination description match the ref name? * * @param r * ref name that should be tested. * @return true if the names match; false otherwise. */ public boolean matchDestination(String r) { return match(r, getDestination()); } /** * Does this specification's destination description match the ref? * * @param r * ref whose name should be tested. * @return true if the names match; false otherwise. */ public boolean matchDestination(Ref r) { return match(r.getName(), getDestination()); } /** * Expand this specification to exactly match a ref name. *

* Callers must first verify the passed ref name matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref name that matched our source specification. Could be a * wildcard also. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */ public RefSpec expandFromSource(String r) { if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { throw new IllegalStateException( JGitText.get().invalidExpandWildcard); } return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this; } private RefSpec expandFromSourceImp(String name) { final String psrc = srcName, pdst = dstName; wildcard = false; srcName = name; dstName = expandWildcard(name, psrc, pdst); return this; } private static boolean isNullOrEmpty(String refName) { return refName == null || refName.isEmpty(); } /** * Expand this specification to exactly match a ref. *

* Callers must first verify the passed ref matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref that matched our source specification. Could be a * wildcard also. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */ public RefSpec expandFromSource(Ref r) { return expandFromSource(r.getName()); } /** * Expand this specification to exactly match a ref name. *

* Callers must first verify the passed ref name matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref name that matched our destination specification. Could * be a wildcard also. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */ public RefSpec expandFromDestination(String r) { if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) { throw new IllegalStateException( JGitText.get().invalidExpandWildcard); } return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this; } private RefSpec expandFromDstImp(String name) { final String psrc = srcName, pdst = dstName; wildcard = false; srcName = expandWildcard(name, pdst, psrc); dstName = name; return this; } /** * Expand this specification to exactly match a ref. *

* Callers must first verify the passed ref matches this specification, * otherwise expansion results may be unpredictable. * * @param r * a ref that matched our destination specification. * @return a new specification expanded from provided ref name. Result * specification is wildcard if and only if provided ref name is * wildcard. * @throws java.lang.IllegalStateException * when the RefSpec was constructed with wildcard mode that * doesn't require matching wildcards. */ public RefSpec expandFromDestination(Ref r) { return expandFromDestination(r.getName()); } private boolean match(String name, String s) { if (s == null) return false; if (isWildcard(s)) { int wildcardIndex = s.indexOf('*'); String prefix = s.substring(0, wildcardIndex); String suffix = s.substring(wildcardIndex + 1); return name.length() > prefix.length() + suffix.length() && name.startsWith(prefix) && name.endsWith(suffix); } return name.equals(s); } private static String expandWildcard(String name, String patternA, String patternB) { int a = patternA.indexOf('*'); int trailingA = patternA.length() - (a + 1); int b = patternB.indexOf('*'); String match = name.substring(a, name.length() - trailingA); return patternB.substring(0, b) + match + patternB.substring(b + 1); } private static String checkValid(String spec) { if (spec != null && !isValid(spec)) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidRefSpec, spec)); return spec; } private static boolean isValid(String s) { if (s.startsWith("/")) //$NON-NLS-1$ return false; if (s.contains("//")) //$NON-NLS-1$ return false; if (s.endsWith("/")) //$NON-NLS-1$ return false; int i = s.indexOf('*'); if (i != -1) { if (s.indexOf('*', i + 1) > i) return false; } return true; } @Override public int hashCode() { int hc = 0; if (getSource() != null) hc = hc * 31 + getSource().hashCode(); if (getDestination() != null) hc = hc * 31 + getDestination().hashCode(); return hc; } @Override public boolean equals(Object obj) { if (!(obj instanceof RefSpec)) return false; final RefSpec b = (RefSpec) obj; if (isForceUpdate() != b.isForceUpdate()) { return false; } if(isNegative() != b.isNegative()) { return false; } if (isMatching()) { return b.isMatching(); } else if (b.isMatching()) { return false; } return isWildcard() == b.isWildcard() && Objects.equals(getSource(), b.getSource()) && Objects.equals(getDestination(), b.getDestination()); } @Override public String toString() { final StringBuilder r = new StringBuilder(); if (isForceUpdate()) { r.append('+'); } if(isNegative()) { r.append('^'); } if (isMatching()) { r.append(':'); } else { if (getSource() != null) { r.append(getSource()); } if (getDestination() != null) { r.append(':'); r.append(getDestination()); } } return r.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13963 Content-Disposition: inline; filename="RemoteConfig.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "c4e105ec4ac7463a260d5fb3c01466cc8ce0829b" /* * Copyright (C) 2009, Google Inc. * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.Serializable; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.jgit.lib.Config; /** * A remembered remote repository, including URLs and RefSpecs. *

* A remote configuration remembers one or more URLs for a frequently accessed * remote repository as well as zero or more fetch and push specifications * describing how refs should be transferred between this repository and the * remote repository. */ public class RemoteConfig implements Serializable { private static final long serialVersionUID = 1L; private static final String SECTION = "remote"; //$NON-NLS-1$ private static final String KEY_URL = "url"; //$NON-NLS-1$ private static final String KEY_PUSHURL = "pushurl"; //$NON-NLS-1$ private static final String KEY_FETCH = "fetch"; //$NON-NLS-1$ private static final String KEY_PUSH = "push"; //$NON-NLS-1$ private static final String KEY_UPLOADPACK = "uploadpack"; //$NON-NLS-1$ private static final String KEY_RECEIVEPACK = "receivepack"; //$NON-NLS-1$ private static final String KEY_TAGOPT = "tagopt"; //$NON-NLS-1$ private static final String KEY_MIRROR = "mirror"; //$NON-NLS-1$ private static final String KEY_TIMEOUT = "timeout"; //$NON-NLS-1$ private static final boolean DEFAULT_MIRROR = false; /** Default value for {@link #getUploadPack()} if not specified. */ public static final String DEFAULT_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$ /** Default value for {@link #getReceivePack()} if not specified. */ public static final String DEFAULT_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$ /** * Parse all remote blocks in an existing configuration file, looking for * remotes configuration. * * @param rc * the existing configuration to get the remote settings from. * The configuration must already be loaded into memory. * @return all remotes configurations existing in provided repository * configuration. Returned configurations are ordered * lexicographically by names. * @throws java.net.URISyntaxException * one of the URIs within the remote's configuration is invalid. */ public static List getAllRemoteConfigs(Config rc) throws URISyntaxException { final List names = new ArrayList<>(rc .getSubsections(SECTION)); Collections.sort(names); final List result = new ArrayList<>(names .size()); for (String name : names) result.add(new RemoteConfig(rc, name)); return result; } private String name; private List uris; private List pushURIs; private List fetch; private List push; private String uploadpack; private String receivepack; private TagOpt tagopt; private boolean mirror; private int timeout; /** * Parse a remote block from an existing configuration file. *

* This constructor succeeds even if the requested remote is not defined * within the supplied configuration file. If that occurs then there will be * no URIs and no ref specifications known to the new instance. * * @param rc * the existing configuration to get the remote settings from. * The configuration must already be loaded into memory. * @param remoteName * subsection key indicating the name of this remote. * @throws java.net.URISyntaxException * one of the URIs within the remote's configuration is invalid. */ public RemoteConfig(Config rc, String remoteName) throws URISyntaxException { name = remoteName; String[] vlst; String val; vlst = rc.getStringList(SECTION, name, KEY_URL); UrlConfig urls = new UrlConfig(rc); uris = new ArrayList<>(vlst.length); for (String s : vlst) { uris.add(new URIish(urls.replace(s))); } String[] plst = rc.getStringList(SECTION, name, KEY_PUSHURL); pushURIs = new ArrayList<>(plst.length); for (String s : plst) { pushURIs.add(new URIish(s)); } if (pushURIs.isEmpty()) { // Would default to the uris. If we have pushinsteadof, we must // supply rewritten push uris. if (urls.hasPushReplacements()) { for (String s : vlst) { String replaced = urls.replacePush(s); if (!s.equals(replaced)) { pushURIs.add(new URIish(replaced)); } } } } fetch = rc.getRefSpecs(SECTION, name, KEY_FETCH); push = rc.getRefSpecs(SECTION, name, KEY_PUSH); val = rc.getString(SECTION, name, KEY_UPLOADPACK); if (val == null) { val = DEFAULT_UPLOAD_PACK; } uploadpack = val; val = rc.getString(SECTION, name, KEY_RECEIVEPACK); if (val == null) { val = DEFAULT_RECEIVE_PACK; } receivepack = val; try { val = rc.getString(SECTION, name, KEY_TAGOPT); tagopt = TagOpt.fromOption(val); } catch (IllegalArgumentException e) { // C git silently ignores invalid tagopt values. tagopt = TagOpt.AUTO_FOLLOW; } mirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR); timeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0); } /** * Update this remote's definition within the configuration. * * @param rc * the configuration file to store ourselves into. */ public void update(Config rc) { final List vlst = new ArrayList<>(); vlst.clear(); for (URIish u : getURIs()) vlst.add(u.toPrivateString()); rc.setStringList(SECTION, getName(), KEY_URL, vlst); vlst.clear(); for (URIish u : getPushURIs()) vlst.add(u.toPrivateString()); rc.setStringList(SECTION, getName(), KEY_PUSHURL, vlst); vlst.clear(); for (RefSpec u : getFetchRefSpecs()) vlst.add(u.toString()); rc.setStringList(SECTION, getName(), KEY_FETCH, vlst); vlst.clear(); for (RefSpec u : getPushRefSpecs()) vlst.add(u.toString()); rc.setStringList(SECTION, getName(), KEY_PUSH, vlst); set(rc, KEY_UPLOADPACK, getUploadPack(), DEFAULT_UPLOAD_PACK); set(rc, KEY_RECEIVEPACK, getReceivePack(), DEFAULT_RECEIVE_PACK); set(rc, KEY_TAGOPT, getTagOpt().option(), TagOpt.AUTO_FOLLOW.option()); set(rc, KEY_MIRROR, mirror, DEFAULT_MIRROR); set(rc, KEY_TIMEOUT, timeout, 0); } private void set(final Config rc, final String key, final String currentValue, final String defaultValue) { if (defaultValue.equals(currentValue)) unset(rc, key); else rc.setString(SECTION, getName(), key, currentValue); } private void set(final Config rc, final String key, final boolean currentValue, final boolean defaultValue) { if (defaultValue == currentValue) unset(rc, key); else rc.setBoolean(SECTION, getName(), key, currentValue); } private void set(final Config rc, final String key, final int currentValue, final int defaultValue) { if (defaultValue == currentValue) unset(rc, key); else rc.setInt(SECTION, getName(), key, currentValue); } private void unset(Config rc, String key) { rc.unset(SECTION, getName(), key); } /** * Get the local name this remote configuration is recognized as. * * @return name assigned by the user to this configuration block. */ public String getName() { return name; } /** * Get all configured URIs under this remote. * * @return the set of URIs known to this remote. */ public List getURIs() { return Collections.unmodifiableList(uris); } /** * Add a new URI to the end of the list of URIs. * * @param toAdd * the new URI to add to this remote. * @return true if the URI was added; false if it already exists. */ public boolean addURI(URIish toAdd) { if (uris.contains(toAdd)) return false; return uris.add(toAdd); } /** * Remove a URI from the list of URIs. * * @param toRemove * the URI to remove from this remote. * @return true if the URI was added; false if it already exists. */ public boolean removeURI(URIish toRemove) { return uris.remove(toRemove); } /** * Get all configured push-only URIs under this remote. * * @return the set of URIs known to this remote. */ public List getPushURIs() { return Collections.unmodifiableList(pushURIs); } /** * Add a new push-only URI to the end of the list of URIs. * * @param toAdd * the new URI to add to this remote. * @return true if the URI was added; false if it already exists. */ public boolean addPushURI(URIish toAdd) { if (pushURIs.contains(toAdd)) return false; return pushURIs.add(toAdd); } /** * Remove a push-only URI from the list of URIs. * * @param toRemove * the URI to remove from this remote. * @return true if the URI was added; false if it already exists. */ public boolean removePushURI(URIish toRemove) { return pushURIs.remove(toRemove); } /** * Remembered specifications for fetching from a repository. * * @return set of specs used by default when fetching. */ public List getFetchRefSpecs() { return Collections.unmodifiableList(fetch); } /** * Add a new fetch RefSpec to this remote. * * @param s * the new specification to add. * @return true if the specification was added; false if it already exists. */ public boolean addFetchRefSpec(RefSpec s) { if (fetch.contains(s)) return false; return fetch.add(s); } /** * Override existing fetch specifications with new ones. * * @param specs * list of fetch specifications to set. List is copied, it can be * modified after this call. */ public void setFetchRefSpecs(List specs) { fetch.clear(); fetch.addAll(specs); } /** * Override existing push specifications with new ones. * * @param specs * list of push specifications to set. List is copied, it can be * modified after this call. */ public void setPushRefSpecs(List specs) { push.clear(); push.addAll(specs); } /** * Remove a fetch RefSpec from this remote. * * @param s * the specification to remove. * @return true if the specification existed and was removed. */ public boolean removeFetchRefSpec(RefSpec s) { return fetch.remove(s); } /** * Remembered specifications for pushing to a repository. * * @return set of specs used by default when pushing. */ public List getPushRefSpecs() { return Collections.unmodifiableList(push); } /** * Add a new push RefSpec to this remote. * * @param s * the new specification to add. * @return true if the specification was added; false if it already exists. */ public boolean addPushRefSpec(RefSpec s) { if (push.contains(s)) return false; return push.add(s); } /** * Remove a push RefSpec from this remote. * * @param s * the specification to remove. * @return true if the specification existed and was removed. */ public boolean removePushRefSpec(RefSpec s) { return push.remove(s); } /** * Override for the location of 'git-upload-pack' on the remote system. *

* This value is only useful for an SSH style connection, where Git is * asking the remote system to execute a program that provides the necessary * network protocol. * * @return location of 'git-upload-pack' on the remote system. If no * location has been configured the default of 'git-upload-pack' is * returned instead. */ public String getUploadPack() { return uploadpack; } /** * Override for the location of 'git-receive-pack' on the remote system. *

* This value is only useful for an SSH style connection, where Git is * asking the remote system to execute a program that provides the necessary * network protocol. * * @return location of 'git-receive-pack' on the remote system. If no * location has been configured the default of 'git-receive-pack' is * returned instead. */ public String getReceivePack() { return receivepack; } /** * Get the description of how annotated tags should be treated during fetch. * * @return option indicating the behavior of annotated tags in fetch. */ public TagOpt getTagOpt() { return tagopt; } /** * Set the description of how annotated tags should be treated on fetch. * * @param option * method to use when handling annotated tags. */ public void setTagOpt(TagOpt option) { tagopt = option != null ? option : TagOpt.AUTO_FOLLOW; } /** * Whether pushing to the remote automatically deletes remote refs which * don't exist on the source side. * * @return true if pushing to the remote automatically deletes remote refs * which don't exist on the source side. */ public boolean isMirror() { return mirror; } /** * Set the mirror flag to automatically delete remote refs. * * @param m * true to automatically delete remote refs during push. */ public void setMirror(boolean m) { mirror = m; } /** * Get timeout (in seconds) before aborting an IO operation. * * @return timeout (in seconds) before aborting an IO operation. */ public int getTimeout() { return timeout; } /** * Set the timeout before willing to abort an IO call. * * @param seconds * number of seconds to wait (with no data transfer occurring) * before aborting an IO read or write operation with this * remote. A timeout of 0 will block indefinitely. */ public void setTimeout(int seconds) { timeout = seconds; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 18457 Content-Disposition: inline; filename="RemoteRefUpdate.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "fb3cd21d1a8b51d17138b65ea8d3a4352f49cae3" /* * Copyright (C) 2008, Marek Zawirski and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.text.MessageFormat; import java.util.Collection; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; /** * Represent request and status of a remote ref update. Specification is * provided by client, while status is handled by * {@link org.eclipse.jgit.transport.PushProcess} class, being read-only for * client. *

* Client can create instances of this class directly, basing on user * specification and advertised refs * ({@link org.eclipse.jgit.transport.Connection} or through * {@link org.eclipse.jgit.transport.Transport} helper methods. Apply this * specification on remote repository using * {@link org.eclipse.jgit.transport.Transport#push(org.eclipse.jgit.lib.ProgressMonitor, java.util.Collection)} * method. *

*/ public class RemoteRefUpdate { /** * Represent current status of a remote ref update. */ public enum Status { /** * Push process hasn't yet attempted to update this ref. This is the * default status, prior to push process execution. */ NOT_ATTEMPTED, /** * Remote ref was up to date, there was no need to update anything. */ UP_TO_DATE, /** * Remote ref update was rejected, as it would cause non fast-forward * update. */ REJECTED_NONFASTFORWARD, /** * Remote ref update was rejected, because remote side doesn't * support/allow deleting refs. */ REJECTED_NODELETE, /** * Remote ref update was rejected, because old object id on remote * repository wasn't the same as defined expected old object. */ REJECTED_REMOTE_CHANGED, /** * Remote ref update was rejected for other reason, possibly described * in {@link RemoteRefUpdate#getMessage()}. */ REJECTED_OTHER_REASON, /** * Remote ref didn't exist. Can occur on delete request of a non * existing ref. */ NON_EXISTING, /** * Push process is awaiting update report from remote repository. This * is a temporary state or state after critical error in push process. */ AWAITING_REPORT, /** * Remote ref was successfully updated. */ OK; } private ObjectId expectedOldObjectId; private final ObjectId newObjectId; private final String remoteName; private final TrackingRefUpdate trackingRefUpdate; private final String srcRef; private final boolean forceUpdate; private Status status; private boolean fastForward; private String message; private final Repository localDb; private RefUpdate localUpdate; /** * If set, the RemoteRefUpdate is a placeholder for the "matching" RefSpec * to be expanded after the advertisements have been received in a push. */ private Collection fetchSpecs; /** * Construct remote ref update request by providing an update specification. * Object is created with default * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * status and no message. * * @param localDb * local repository to push from. * @param srcRef * source revision - any string resolvable by * {@link org.eclipse.jgit.lib.Repository#resolve(String)}. This * resolves to the new object that the caller want remote ref to * be after update. Use null or * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} string for * delete request. * @param remoteName * full name of a remote ref to update, e.g. "refs/heads/master" * (no wildcard, no short name). * @param forceUpdate * true when caller want remote ref to be updated regardless * whether it is fast-forward update (old object is ancestor of * new object). * @param localName * optional full name of a local stored tracking branch, to * update after push, e.g. "refs/remotes/zawir/dirty" (no * wildcard, no short name); null if no local tracking branch * should be updated. * @param expectedOldObjectId * optional object id that caller is expecting, requiring to be * advertised by remote side before update; update will take * place ONLY if remote side advertise exactly this expected id; * null if caller doesn't care what object id remote side * advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} * when expecting no remote ref with this name. * @throws java.io.IOException * when I/O error occurred during creating * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for * local tracking branch or srcRef can't be resolved to any * object. * @throws java.lang.IllegalArgumentException * if some required parameter was null */ public RemoteRefUpdate(Repository localDb, String srcRef, String remoteName, boolean forceUpdate, String localName, ObjectId expectedOldObjectId) throws IOException { this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef) : ObjectId.zeroId(), remoteName, forceUpdate, localName, expectedOldObjectId); } /** * Construct remote ref update request by providing an update specification. * Object is created with default * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * status and no message. * * @param localDb * local repository to push from. * @param srcRef * source revision. Use null to delete. * @param remoteName * full name of a remote ref to update, e.g. "refs/heads/master" * (no wildcard, no short name). * @param forceUpdate * true when caller want remote ref to be updated regardless * whether it is fast-forward update (old object is ancestor of * new object). * @param localName * optional full name of a local stored tracking branch, to * update after push, e.g. "refs/remotes/zawir/dirty" (no * wildcard, no short name); null if no local tracking branch * should be updated. * @param expectedOldObjectId * optional object id that caller is expecting, requiring to be * advertised by remote side before update; update will take * place ONLY if remote side advertise exactly this expected id; * null if caller doesn't care what object id remote side * advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} * when expecting no remote ref with this name. * @throws java.io.IOException * when I/O error occurred during creating * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for * local tracking branch or srcRef can't be resolved to any * object. * @throws java.lang.IllegalArgumentException * if some required parameter was null */ public RemoteRefUpdate(Repository localDb, Ref srcRef, String remoteName, boolean forceUpdate, String localName, ObjectId expectedOldObjectId) throws IOException { this(localDb, srcRef != null ? srcRef.getName() : null, srcRef != null ? srcRef.getObjectId() : null, remoteName, forceUpdate, localName, expectedOldObjectId); } /** * Construct remote ref update request by providing an update specification. * Object is created with default * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED} * status and no message. * * @param localDb * local repository to push from. * @param srcRef * source revision to label srcId with. If null srcId.name() will * be used instead. * @param srcId * The new object that the caller wants remote ref to be after * update. Use null or * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} for delete * request. * @param remoteName * full name of a remote ref to update, e.g. "refs/heads/master" * (no wildcard, no short name). * @param forceUpdate * true when caller want remote ref to be updated regardless * whether it is fast-forward update (old object is ancestor of * new object). * @param localName * optional full name of a local stored tracking branch, to * update after push, e.g. "refs/remotes/zawir/dirty" (no * wildcard, no short name); null if no local tracking branch * should be updated. * @param expectedOldObjectId * optional object id that caller is expecting, requiring to be * advertised by remote side before update; update will take * place ONLY if remote side advertise exactly this expected id; * null if caller doesn't care what object id remote side * advertise. Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} * when expecting no remote ref with this name. * @throws java.io.IOException * when I/O error occurred during creating * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for * local tracking branch or srcRef can't be resolved to any * object. * @throws java.lang.IllegalArgumentException * if some required parameter was null */ public RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId, String remoteName, boolean forceUpdate, String localName, ObjectId expectedOldObjectId) throws IOException { this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null, expectedOldObjectId); } private RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId, String remoteName, boolean forceUpdate, String localName, Collection fetchSpecs, ObjectId expectedOldObjectId) throws IOException { if (fetchSpecs == null) { if (remoteName == null) { throw new IllegalArgumentException( JGitText.get().remoteNameCannotBeNull); } if (srcId == null && srcRef != null) { throw new IOException(MessageFormat.format( JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef)); } } if (srcRef != null) { this.srcRef = srcRef; } else if (srcId != null && !srcId.equals(ObjectId.zeroId())) { this.srcRef = srcId.name(); } else { this.srcRef = null; } if (srcId != null) { this.newObjectId = srcId; } else { this.newObjectId = ObjectId.zeroId(); } this.fetchSpecs = fetchSpecs; this.remoteName = remoteName; this.forceUpdate = forceUpdate; if (localName != null && localDb != null) { localUpdate = localDb.updateRef(localName); localUpdate.setForceUpdate(true); localUpdate.setRefLogMessage("push", true); //$NON-NLS-1$ localUpdate.setNewObjectId(newObjectId); trackingRefUpdate = new TrackingRefUpdate( true, remoteName, localName, localUpdate.getOldObjectId() != null ? localUpdate.getOldObjectId() : ObjectId.zeroId(), newObjectId); } else { trackingRefUpdate = null; } this.localDb = localDb; this.expectedOldObjectId = expectedOldObjectId; this.status = Status.NOT_ATTEMPTED; } /** * Create a new instance of this object basing on existing instance for * configuration. State (like {@link #getMessage()}, {@link #getStatus()}) * of base object is not shared. Expected old object id is set up from * scratch, as this constructor may be used for 2-stage push: first one * being dry run, second one being actual push. * * @param base * configuration base. * @param newExpectedOldObjectId * new expected object id value. * @throws java.io.IOException * when I/O error occurred during creating * {@link org.eclipse.jgit.transport.TrackingRefUpdate} for * local tracking branch or srcRef of base object no longer can * be resolved to any object. */ public RemoteRefUpdate(RemoteRefUpdate base, ObjectId newExpectedOldObjectId) throws IOException { this(base.localDb, base.srcRef, base.newObjectId, base.remoteName, base.forceUpdate, (base.trackingRefUpdate == null ? null : base.trackingRefUpdate .getLocalName()), base.fetchSpecs, newExpectedOldObjectId); } /** * Creates a "placeholder" update for the "matching" RefSpec ":". * * @param localDb * local repository to push from * @param forceUpdate * whether non-fast-forward updates shall be allowed * @param fetchSpecs * The fetch {@link RefSpec}s to use when this placeholder is * expanded to determine remote tracking branch updates */ RemoteRefUpdate(Repository localDb, boolean forceUpdate, @NonNull Collection fetchSpecs) { this.localDb = localDb; this.forceUpdate = forceUpdate; this.fetchSpecs = fetchSpecs; this.trackingRefUpdate = null; this.srcRef = null; this.remoteName = null; this.newObjectId = null; this.status = Status.NOT_ATTEMPTED; } /** * Tells whether this {@link RemoteRefUpdate} is a placeholder for a * "matching" {@link RefSpec}. * * @return {@code true} if this is a placeholder, {@code false} otherwise * @since 6.1 */ public boolean isMatching() { return fetchSpecs != null; } /** * Retrieves the fetch {@link RefSpec}s of this {@link RemoteRefUpdate}. * * @return the fetch {@link RefSpec}s, or {@code null} if * {@code this.}{@link #isMatching()} {@code == false} */ Collection getFetchSpecs() { return fetchSpecs; } /** * Get expected old object id * * @return expectedOldObjectId required to be advertised by remote side, as * set in constructor; may be null. */ public ObjectId getExpectedOldObjectId() { return expectedOldObjectId; } /** * Whether some object is required to be advertised by remote side, as set * in constructor * * @return true if some object is required to be advertised by remote side, * as set in constructor; false otherwise. */ public boolean isExpectingOldObjectId() { return expectedOldObjectId != null; } /** * Get new object id * * @return newObjectId for remote ref, as set in constructor. */ public ObjectId getNewObjectId() { return newObjectId; } /** * Whether this update is a deleting update * * @return true if this update is deleting update; false otherwise. */ public boolean isDelete() { return ObjectId.zeroId().equals(newObjectId); } /** * Get name of remote ref to update * * @return name of remote ref to update, as set in constructor. */ public String getRemoteName() { return remoteName; } /** * Get tracking branch update if localName was set in constructor. * * @return local tracking branch update if localName was set in constructor. */ public TrackingRefUpdate getTrackingRefUpdate() { return trackingRefUpdate; } /** * Get source revision as specified by user (in constructor) * * @return source revision as specified by user (in constructor), could be * any string parseable by * {@link org.eclipse.jgit.lib.Repository#resolve(String)}; can be * null if specified that way in constructor - this stands for * delete request. */ public String getSrcRef() { return srcRef; } /** * Whether user specified a local tracking branch for remote update * * @return true if user specified a local tracking branch for remote update; * false otherwise. */ public boolean hasTrackingRefUpdate() { return trackingRefUpdate != null; } /** * Whether this update is forced regardless of old remote ref object * * @return true if this update is forced regardless of old remote ref * object; false otherwise. */ public boolean isForceUpdate() { return forceUpdate; } /** * Get status of remote ref update operation. * * @return status of remote ref update operation. */ public Status getStatus() { return status; } /** * Check whether update was fast-forward. Note that this result is * meaningful only after successful update (when status is * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#OK}). * * @return true if update was fast-forward; false otherwise. */ public boolean isFastForward() { return fastForward; } /** * Get message describing reasons of status when needed/possible; may be * null. * * @return message describing reasons of status when needed/possible; may be * null. */ public String getMessage() { return message; } void setExpectedOldObjectId(ObjectId id) { expectedOldObjectId = id; } void setStatus(Status status) { this.status = status; } void setFastForward(boolean fastForward) { this.fastForward = fastForward; } void setMessage(String message) { this.message = message; } /** * Update locally stored tracking branch with the new object. * * @param walk * walker used for checking update properties. * @throws java.io.IOException * when I/O error occurred during update */ protected void updateTrackingRef(RevWalk walk) throws IOException { if (isDelete()) trackingRefUpdate.setResult(localUpdate.delete(walk)); else trackingRefUpdate.setResult(localUpdate.update(walk)); } @SuppressWarnings("nls") @Override public String toString() { return "RemoteRefUpdate[remoteName=" + remoteName + ", " + status + ", " + (expectedOldObjectId != null ? expectedOldObjectId.name() : "(null)") + "..." + (newObjectId != null ? newObjectId.name() : "(null)") + (fastForward ? ", fastForward" : "") + ", srcRef=" + srcRef + (forceUpdate ? ", forceUpdate" : "") + ", message=" + (message != null ? "\"" + message + "\"" : "null") + "]"; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1947 Content-Disposition: inline; filename="RemoteSession.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "dd4c967f91ca3fed141f037fe0faec972444cd80" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2009, Google, Inc. * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; /** * An abstraction of a remote "session" for executing remote commands. */ public interface RemoteSession { /** * Creates a new remote {@link Process} to execute the given command. The * returned process's streams exist and are connected, and execution of the * process is already started. * * @param commandName * command to execute * @param timeout * timeout value, in seconds, for creating the remote process * @return a new remote process, already started * @throws java.io.IOException * may be thrown in several cases. For example, on problems * opening input or output streams or on problems connecting or * communicating with the remote host. For the latter two cases, * a TransportException may be thrown (a subclass of * java.io.IOException). */ Process exec(String commandName, int timeout) throws IOException; /** * Obtains an {@link FtpChannel} for performing FTP operations over this * {@link RemoteSession}. The default implementation returns {@code null}. * * @return the {@link FtpChannel} * @since 5.2 */ default FtpChannel getFtpChannel() { return null; } /** * Disconnects the remote session. */ void disconnect(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1526 Content-Disposition: inline; filename="RemoteSession2.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "23f670ae2516974995e898a744993c2b399f3020" /* * Copyright (C) 2020, Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.util.Map; /** * A {@link RemoteSession} that supports passing environment variables to * commands. * * @since 5.11 */ public interface RemoteSession2 extends RemoteSession { /** * Creates a new remote {@link Process} to execute the given command. The * returned process's streams exist and are connected, and execution of the * process is already started. * * @param commandName * command to execute * @param environment * environment variables to pass on * @param timeout * timeout value, in seconds, for creating the remote process * @return a new remote process, already started * @throws java.io.IOException * may be thrown in several cases. For example, on problems * opening input or output streams or on problems connecting or * communicating with the remote host. For the latter two cases, * a TransportException may be thrown (a subclass of * java.io.IOException). */ Process exec(String commandName, Map environment, int timeout) throws IOException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 937 Content-Disposition: inline; filename="RequestNotYetReadException.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "a1ead3dd8dff0c2d755d4f23a129eb8e66e459a8" /* * Copyright (C) 2012, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Indicates that a client request has not yet been read from the wire. * * @since 2.0 */ public class RequestNotYetReadException extends IllegalStateException { private static final long serialVersionUID = 1L; /** * Initialize with no message. */ public RequestNotYetReadException() { // Do not set a message. } /** *

Constructor for RequestNotYetReadException.

* * @param msg * a message explaining the state. This message should not * be shown to an end-user. */ public RequestNotYetReadException(String msg) { super(msg); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3199 Content-Disposition: inline; filename="ServiceMayNotContinueException.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "4fafce32d40c1d136ff2c8caa65bf7e559e7bc41" /* * Copyright (C) 2011-2012, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import org.eclipse.jgit.internal.JGitText; /** * Indicates a transport service may not continue execution. * * @since 2.0 */ public class ServiceMayNotContinueException extends IOException { private static final int FORBIDDEN = 403; private static final long serialVersionUID = 1L; private final int statusCode; private boolean output; /** * Initialize with no message. */ public ServiceMayNotContinueException() { // Do not set a message. statusCode = FORBIDDEN; } /** *

Constructor for ServiceMayNotContinueException.

* * @param msg * a message explaining why it cannot continue. This message may * be shown to an end-user. */ public ServiceMayNotContinueException(String msg) { super(msg); statusCode = FORBIDDEN; } /** *

Constructor for ServiceMayNotContinueException.

* * @param msg * a message explaining why it cannot continue. This message may * be shown to an end-user. * @param statusCode * the HTTP status code. * @since 4.5 */ public ServiceMayNotContinueException(String msg, int statusCode) { super(msg); this.statusCode = statusCode; } /** *

Constructor for ServiceMayNotContinueException.

* * @param msg * a message explaining why it cannot continue. This message may * be shown to an end-user. * @param cause * the cause of the exception. * @since 3.2 */ public ServiceMayNotContinueException(String msg, Throwable cause) { super(msg, cause); statusCode = FORBIDDEN; } /** *

Constructor for ServiceMayNotContinueException.

* * @param msg * a message explaining why it cannot continue. This message may * be shown to an end-user. * @param cause * the cause of the exception. * @param statusCode * the HTTP status code. * @since 4.5 */ public ServiceMayNotContinueException( String msg, Throwable cause, int statusCode) { super(msg, cause); this.statusCode = statusCode; } /** * Initialize with an "internal server error" message and a cause. * * @param cause * the cause of the exception. * @since 3.2 */ public ServiceMayNotContinueException(Throwable cause) { this(JGitText.get().internalServerError, cause); } /** * Whether the message was already output to the client. * * @return {@code true} if the message was already output to the client. */ public boolean isOutput() { return output; } /** * Mark this message has being sent to the client. */ public void setOutput() { output = true; } /** * Get status code * * @return true if the message was already output to the client. * @since 4.5 */ public int getStatusCode() { return statusCode; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6301 Content-Disposition: inline; filename="SideBandInputStream.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "1f96be8e47381d6c0d0d1a47cbc5e817d46cf455" /* * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.transport.SideBandOutputStream.HDR_SIZE; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; import java.text.MessageFormat; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Unmultiplexes the data portion of a side-band channel. *

* Reading from this input stream obtains data from channel 1, which is * typically the bulk data stream. *

* Channel 2 is transparently unpacked and "scraped" to update a progress * monitor. The scraping is performed behind the scenes as part of any of the * read methods offered by this stream. *

* Channel 3 results in an exception being thrown, as the remote side has issued * an unrecoverable error. * * @see SideBandOutputStream * @since 4.11 */ public class SideBandInputStream extends InputStream { private static final Logger LOG = LoggerFactory .getLogger(SideBandInputStream.class); static final int CH_DATA = 1; static final int CH_PROGRESS = 2; static final int CH_ERROR = 3; private static Pattern P_UNBOUNDED = Pattern .compile("^([\\w ]+): +(\\d+)(?:, done\\.)? *[\r\n]$"); //$NON-NLS-1$ private static Pattern P_BOUNDED = Pattern .compile("^([\\w ]+): +\\d+% +\\( *(\\d+)/ *(\\d+)\\)(?:, done\\.)? *[\r\n]$"); //$NON-NLS-1$ private final InputStream rawIn; private final PacketLineIn pckIn; private final ProgressMonitor monitor; private final Writer messages; private final OutputStream out; private String progressBuffer = ""; //$NON-NLS-1$ private String currentTask; private int lastCnt; private boolean eof; private int channel; private int available; SideBandInputStream(final InputStream in, final ProgressMonitor progress, final Writer messageStream, OutputStream outputStream) { rawIn = in; pckIn = new PacketLineIn(rawIn); monitor = progress; messages = messageStream; currentTask = ""; //$NON-NLS-1$ out = outputStream; } @Override public int read() throws IOException { needDataPacket(); if (eof) return -1; available--; return rawIn.read(); } @Override public int read(byte[] b, int off, int len) throws IOException { int r = 0; while (len > 0) { needDataPacket(); if (eof) break; final int n = rawIn.read(b, off, Math.min(len, available)); if (n < 0) break; r += n; off += n; len -= n; available -= n; } return eof && r == 0 ? -1 : r; } private void needDataPacket() throws IOException { if (eof || (channel == CH_DATA && available > 0)) return; for (;;) { available = pckIn.readLength(); if (available == 0) { eof = true; return; } channel = rawIn.read() & 0xff; available -= HDR_SIZE; // length header plus channel indicator if (available == 0) continue; switch (channel) { case CH_DATA: return; case CH_PROGRESS: progress(readString(available)); continue; case CH_ERROR: eof = true; throw new TransportException(remote(readString(available))); default: throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidChannel, Integer.valueOf(channel))); } } } private void progress(String pkt) throws IOException { pkt = progressBuffer + pkt; for (;;) { final int lf = pkt.indexOf('\n'); final int cr = pkt.indexOf('\r'); final int s; if (0 <= lf && 0 <= cr) s = Math.min(lf, cr); else if (0 <= lf) s = lf; else if (0 <= cr) s = cr; else break; doProgressLine(pkt.substring(0, s + 1)); pkt = pkt.substring(s + 1); } progressBuffer = pkt; } private void doProgressLine(String msg) throws IOException { Matcher matcher; matcher = P_BOUNDED.matcher(msg); if (matcher.matches()) { final String taskname = matcher.group(1); if (!currentTask.equals(taskname)) { currentTask = taskname; lastCnt = 0; beginTask(Integer.parseInt(matcher.group(3))); } final int cnt = Integer.parseInt(matcher.group(2)); monitor.update(cnt - lastCnt); lastCnt = cnt; return; } matcher = P_UNBOUNDED.matcher(msg); if (matcher.matches()) { final String taskname = matcher.group(1); if (!currentTask.equals(taskname)) { currentTask = taskname; lastCnt = 0; beginTask(ProgressMonitor.UNKNOWN); } final int cnt = Integer.parseInt(matcher.group(2)); monitor.update(cnt - lastCnt); lastCnt = cnt; return; } messages.write(msg); if (out != null) out.write(msg.getBytes(UTF_8)); } private void beginTask(int totalWorkUnits) { monitor.beginTask(remote(currentTask), totalWorkUnits); } /** * Forces any buffered progress messages to be written. */ void drainMessages() { if (!progressBuffer.isEmpty()) { try { progress("\n"); //$NON-NLS-1$ } catch (IOException e) { // Just log; otherwise this IOException might hide a real // TransportException LOG.error(e.getMessage(), e); } } } private static String remote(String msg) { String prefix = JGitText.get().prefixRemote; StringBuilder r = new StringBuilder(prefix.length() + msg.length() + 1); r.append(prefix); if (prefix.length() > 0 && prefix.charAt(prefix.length() - 1) != ' ') { r.append(' '); } r.append(msg); return r.toString(); } private String readString(int len) throws IOException { final byte[] raw = new byte[len]; IO.readFully(rawIn, raw, 0, len); return RawParseUtils.decode(UTF_8, raw, 0, len); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4083 Content-Disposition: inline; filename="SideBandOutputStream.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "b6d840dd81b02104e524ac41ec5a5f2bd49c8962" /* * Copyright (C) 2008-2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; /** * Multiplexes data and progress messages. *

* This stream is buffered at packet sizes, so the caller doesn't need to wrap * it in yet another buffered stream. * * @since 2.0 */ public class SideBandOutputStream extends OutputStream { /** Channel used for pack data. */ public static final int CH_DATA = SideBandInputStream.CH_DATA; /** Channel used for progress messages. */ public static final int CH_PROGRESS = SideBandInputStream.CH_PROGRESS; /** Channel used for error messages. */ public static final int CH_ERROR = SideBandInputStream.CH_ERROR; /** Default buffer size for a small amount of data. */ public static final int SMALL_BUF = 1000; /** Maximum buffer size for a single packet of sideband data. */ public static final int MAX_BUF = 65520; static final int HDR_SIZE = 5; private final OutputStream out; private final byte[] buffer; /** * Number of bytes in {@link #buffer} that are valid data. *

* Initialized to {@link #HDR_SIZE} if there is no application data in the * buffer, as the packet header always appears at the start of the buffer. */ private int cnt; /** * Create a new stream to write side band packets. * * @param chan * channel number to prefix all packets with, so the remote side * can demultiplex the stream and get back the original data. * Must be in the range [1, 255]. * @param sz * maximum size of a data packet within the stream. The remote * side needs to agree to the packet size to prevent buffer * overflows. Must be in the range [HDR_SIZE + 1, MAX_BUF). * @param os * stream that the packets are written onto. This stream should * be attached to a SideBandInputStream on the remote side. */ public SideBandOutputStream(int chan, int sz, OutputStream os) { if (chan <= 0 || chan > 255) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().channelMustBeInRange1_255, Integer.valueOf(chan))); if (sz <= HDR_SIZE) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().packetSizeMustBeAtLeast, Integer.valueOf(sz), Integer.valueOf(HDR_SIZE))); else if (MAX_BUF < sz) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().packetSizeMustBeAtMost, Integer.valueOf(sz), Integer.valueOf(MAX_BUF))); out = os; buffer = new byte[sz]; buffer[4] = (byte) chan; cnt = HDR_SIZE; } void flushBuffer() throws IOException { if (HDR_SIZE < cnt) writeBuffer(); } @Override public void flush() throws IOException { flushBuffer(); out.flush(); } @Override public void write(byte[] b, int off, int len) throws IOException { while (0 < len) { int capacity = buffer.length - cnt; if (cnt == HDR_SIZE && capacity < len) { // Our block to write is bigger than the packet size, // stream it out as-is to avoid unnecessary copies. PacketLineOut.formatLength(buffer, buffer.length); out.write(buffer, 0, HDR_SIZE); out.write(b, off, capacity); off += capacity; len -= capacity; } else { if (capacity == 0) writeBuffer(); int n = Math.min(len, capacity); System.arraycopy(b, off, buffer, cnt, n); cnt += n; off += n; len -= n; } } } @Override public void write(int b) throws IOException { if (cnt == buffer.length) writeBuffer(); buffer[cnt++] = (byte) b; } private void writeBuffer() throws IOException { PacketLineOut.formatLength(buffer, cnt); out.write(buffer, 0, cnt); cnt = HDR_SIZE; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2536 Content-Disposition: inline; filename="SideBandProgressMonitor.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "1e85d81084369938cc0eedd4d4b78022033dd9c6" /* * Copyright (C) 2008-2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.Constants; /** Write progress messages out to the sideband channel. */ class SideBandProgressMonitor extends BatchingProgressMonitor { private final OutputStream out; private boolean write; SideBandProgressMonitor(OutputStream os) { out = os; write = true; } @Override protected void onUpdate(String taskName, int workCurr, Duration duration) { StringBuilder s = new StringBuilder(); format(s, taskName, workCurr, duration); s.append(" \r"); //$NON-NLS-1$ send(s); } @Override protected void onEndTask(String taskName, int workCurr, Duration duration) { StringBuilder s = new StringBuilder(); format(s, taskName, workCurr, duration); s.append(", done\n"); //$NON-NLS-1$ send(s); } private void format(StringBuilder s, String taskName, int workCurr, Duration duration) { s.append(taskName); s.append(": "); //$NON-NLS-1$ s.append(workCurr); appendDuration(s, duration); } @Override protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt, Duration duration) { StringBuilder s = new StringBuilder(); format(s, taskName, cmp, totalWork, pcnt, duration); s.append(" \r"); //$NON-NLS-1$ send(s); } @Override protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt, Duration duration) { StringBuilder s = new StringBuilder(); format(s, taskName, cmp, totalWork, pcnt, duration); s.append("\n"); //$NON-NLS-1$ send(s); } private void format(StringBuilder s, String taskName, int cmp, int totalWork, int pcnt, Duration duration) { s.append(taskName); s.append(": "); //$NON-NLS-1$ if (pcnt < 100) s.append(' '); if (pcnt < 10) s.append(' '); s.append(pcnt); s.append("% ("); //$NON-NLS-1$ s.append(cmp); s.append('/'); s.append(totalWork); s.append(')'); appendDuration(s, duration); } private void send(StringBuilder s) { if (write) { try { out.write(Constants.encode(s.toString())); out.flush(); } catch (IOException err) { write = false; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3367 Content-Disposition: inline; filename="SignedPushConfig.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "e932869a5615505d3f8a9caa4cb399cad4a3a99e" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; /** * Configuration for server-side signed push verification. * * @since 4.1 */ public class SignedPushConfig { /** Key for {@link Config#get(SectionParser)}. */ public static final SectionParser KEY = SignedPushConfig::new; private String certNonceSeed; private int certNonceSlopLimit; private NonceGenerator nonceGenerator; /** * Create a new config with default values disabling push verification. */ public SignedPushConfig() { } SignedPushConfig(Config cfg) { setCertNonceSeed(cfg.getString("receive", null, "certnonceseed")); //$NON-NLS-1$ //$NON-NLS-2$ certNonceSlopLimit = cfg.getInt("receive", "certnonceslop", 0); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Set the seed used by the nonce verifier. *

* Setting this to a non-null value enables push certificate verification * using the default * {@link org.eclipse.jgit.transport.HMACSHA1NonceGenerator} implementation, * if a different implementation was not set using * {@link #setNonceGenerator(NonceGenerator)}. * * @param seed * new seed value. */ public void setCertNonceSeed(String seed) { certNonceSeed = seed; } /** * Get the configured seed. * * @return the configured seed. */ public String getCertNonceSeed() { return certNonceSeed; } /** * Set the nonce slop limit. *

* Old but valid nonces within this limit will be accepted. * * @param limit * new limit in seconds. */ public void setCertNonceSlopLimit(int limit) { certNonceSlopLimit = limit; } /** * Get the configured nonce slop limit. * * @return the configured nonce slop limit. */ public int getCertNonceSlopLimit() { return certNonceSlopLimit; } /** * Set the {@link org.eclipse.jgit.transport.NonceGenerator} used for signed * pushes. *

* Setting this to a non-null value enables push certificate verification. * If this method is called, this implementation will be used instead of the * default {@link org.eclipse.jgit.transport.HMACSHA1NonceGenerator} even if * {@link #setCertNonceSeed(String)} was called. * * @param generator * new nonce generator. */ public void setNonceGenerator(NonceGenerator generator) { nonceGenerator = generator; } /** * Get the {@link org.eclipse.jgit.transport.NonceGenerator} used for signed * pushes. *

* If {@link #setNonceGenerator(NonceGenerator)} was used to set a non-null * implementation, that will be returned. If no custom implementation was * set but {@link #setCertNonceSeed(String)} was called, returns a * newly-created {@link org.eclipse.jgit.transport.HMACSHA1NonceGenerator}. * * @return the configured nonce generator. */ public NonceGenerator getNonceGenerator() { if (nonceGenerator != null) { return nonceGenerator; } else if (certNonceSeed != null) { return new HMACSHA1NonceGenerator(certNonceSeed); } return null; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3993 Content-Disposition: inline; filename="SshConfigStore.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "b2e2549f1209a04a2a18525dcbde8c358f25eca0" /* * Copyright (C) 2020, Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.jgit.annotations.NonNull; /** * An abstraction for a SSH config storage, like the OpenSSH ~/.ssh/config file. * * @since 5.8 */ public interface SshConfigStore { /** * Locate the configuration for a specific host request. * * @param hostName * to look up * @param port * the user supplied; <= 0 if none * @param userName * the user supplied, may be {@code null} or empty if none given * @return the configuration for the requested name. */ @NonNull HostConfig lookup(@NonNull String hostName, int port, String userName); /** * Locate the configuration for a specific host request and if the * configuration has no values for {@link SshConstants#HOST_NAME}, * {@link SshConstants#PORT}, {@link SshConstants#USER}, or * {@link SshConstants#CONNECTION_ATTEMPTS}, fill those values with defaults * from the arguments: * * * * * * * * * * * * * * * * * * * * * * *
Description of arguments
ssh config keyvalue from argument
{@code HostName}{@code hostName}
{@code Port}{@code port > 0 ? port : 22}
{@code User}{@code userName}
{@code ConnectionAttempts}{@code 1}
* * @param hostName * host name to look up * @param port * port number; <= 0 if none * @param userName * the user name, may be {@code null} or empty if none given * @return the configuration for the requested name. * @since 6.0 */ @NonNull HostConfig lookupDefault(@NonNull String hostName, int port, String userName); /** * A host entry from the ssh config. Any merging of global values and of * several matching host entries, %-substitutions, and ~ replacement have * all been done. */ interface HostConfig { /** * Retrieves the value of a single-valued key, or the first if the key * has multiple values. Keys are case-insensitive, so * {@code getValue("HostName") == getValue("HOSTNAME")}. * * @param key * to get the value of * @return the value, or {@code null} if none */ String getValue(String key); /** * Retrieves the values of a multi- or list-valued key. Keys are * case-insensitive, so * {@code getValue("HostName") == getValue("HOSTNAME")}. * * @param key * to get the values of * @return a possibly empty list of values */ List getValues(String key); /** * Retrieves an unmodifiable map of all single-valued options, with * case-insensitive lookup by keys. * * @return all single-valued options */ @NonNull Map getOptions(); /** * Retrieves an unmodifiable map of all multi- or list-valued options, * with case-insensitive lookup by keys. * * @return all multi-valued options */ @NonNull Map> getMultiValuedOptions(); } /** * An empty {@link HostConfig}. */ static final HostConfig EMPTY_CONFIG = new HostConfig() { @Override public String getValue(String key) { return null; } @Override public List getValues(String key) { return Collections.emptyList(); } @Override public Map getOptions() { return Collections.emptyMap(); } @Override public Map> getMultiValuedOptions() { return Collections.emptyMap(); } }; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7727 Content-Disposition: inline; filename="SshConstants.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "41dbdcab5158b64f91717178abf47dfbf3c25c92" /* * Copyright (C) 2018, 2021 Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Constants; /** * Constants relating to ssh. * * @since 5.2 */ @SuppressWarnings("nls") public final class SshConstants { private SshConstants() { // No instances, please. } /** IANA assigned port number for ssh. */ public static final int SSH_DEFAULT_PORT = 22; /** URI scheme for ssh. */ public static final String SSH_SCHEME = "ssh"; /** URI scheme for sftp. */ public static final String SFTP_SCHEME = "sftp"; /** Default name for a ssh directory. */ public static final String SSH_DIR = ".ssh"; /** Name of the ssh config file. */ public static final String CONFIG = Constants.CONFIG; /** Default name of the user "known hosts" file. */ public static final String KNOWN_HOSTS = "known_hosts"; // Config file keys /** * Property to control whether private keys are added to an SSH agent, if * one is running, after having been loaded. * * @since 6.1 */ public static final String ADD_KEYS_TO_AGENT = "AddKeysToAgent"; /** Key in an ssh config file. */ public static final String BATCH_MODE = "BatchMode"; /** Key in an ssh config file. */ public static final String CANONICAL_DOMAINS = "CanonicalDomains"; /** Key in an ssh config file. */ public static final String CERTIFICATE_FILE = "CertificateFile"; /** Key in an ssh config file. */ public static final String CIPHERS = "Ciphers"; /** Key in an ssh config file. */ public static final String COMPRESSION = "Compression"; /** Key in an ssh config file. */ public static final String CONNECTION_ATTEMPTS = "ConnectionAttempts"; /** * An OpenSSH time value for the connection timeout. In OpenSSH, this * includes everything until the end of the initial key exchange; in JGit it * covers only the underlying TCP connect. * * @since 6.1 */ public static final String CONNECT_TIMEOUT = "ConnectTimeout"; /** Key in an ssh config file. */ public static final String CONTROL_PATH = "ControlPath"; /** Key in an ssh config file. */ public static final String GLOBAL_KNOWN_HOSTS_FILE = "GlobalKnownHostsFile"; /** * Key in an ssh config file. * * @since 5.5 */ public static final String HASH_KNOWN_HOSTS = "HashKnownHosts"; /** Key in an ssh config file. */ public static final String HOST = "Host"; /** Key in an ssh config file. */ public static final String HOST_KEY_ALGORITHMS = "HostKeyAlgorithms"; /** Key in an ssh config file. */ public static final String HOST_NAME = "HostName"; /** Key in an ssh config file. */ public static final String IDENTITIES_ONLY = "IdentitiesOnly"; /** Key in an ssh config file. */ public static final String IDENTITY_AGENT = "IdentityAgent"; /** Key in an ssh config file. */ public static final String IDENTITY_FILE = "IdentityFile"; /** Key in an ssh config file. */ public static final String KEX_ALGORITHMS = "KexAlgorithms"; /** Key in an ssh config file. */ public static final String LOCAL_COMMAND = "LocalCommand"; /** Key in an ssh config file. */ public static final String LOCAL_FORWARD = "LocalForward"; /** Key in an ssh config file. */ public static final String MACS = "MACs"; /** Key in an ssh config file. */ public static final String NUMBER_OF_PASSWORD_PROMPTS = "NumberOfPasswordPrompts"; /** * Path to a shared library of a PKCS11 key provider, or "none". *

* If set and not "none", the provider's keys should be used. *

* * @since 6.7 */ public static final String PKCS11_PROVIDER = "PKCS11Provider"; /** * Non-standard JGit addition: specify the PKCS#11 slot list index of the * token to use. A positive number; defaults to zero; ignored if negative * (in which case zero is used, too). * * @since 6.7 */ public static final String PKCS11_SLOT_LIST_INDEX = "PKCS11SlotListIndex"; /** Key in an ssh config file. */ public static final String PORT = "Port"; /** Key in an ssh config file. */ public static final String PREFERRED_AUTHENTICATIONS = "PreferredAuthentications"; /** * Key in an ssh config file; defines signature algorithms for public key * authentication as a comma-separated list. * * @since 5.11.1 */ public static final String PUBKEY_ACCEPTED_ALGORITHMS = "PubkeyAcceptedAlgorithms"; /** Key in an ssh config file. */ public static final String PROXY_COMMAND = "ProxyCommand"; /** * Comma-separated list of jump hosts, defining a jump host chain in * reverse order. Each jump host is a SSH URI or "[user@]host[:port]". *

* Reverse order means: to connect {@literal A -> B -> target}, one can do * in {@code ~/.ssh/config} either of: *

* *
	 * Host target
	 *   ProxyJump B,A
	 * 
*

* or *

* *
	 * Host target
	 *   ProxyJump B
	 *
	 * Host B
	 *   ProxyJump A
	 * 
* * @since 5.10 */ public static final String PROXY_JUMP = "ProxyJump"; /** Key in an ssh config file. */ public static final String REMOTE_COMMAND = "RemoteCommand"; /** Key in an ssh config file. */ public static final String REMOTE_FORWARD = "RemoteForward"; /** * (Absolute) path to a middleware library the SSH agent shall use to load * SK (U2F) keys. * * @since 6.1 */ public static final String SECURITY_KEY_PROVIDER = "SecurityKeyProvider"; /** Key in an ssh config file. */ public static final String SEND_ENV = "SendEnv"; /** Key in an ssh config file. */ public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking"; /** Key in an ssh config file. */ public static final String USER = "User"; /** Key in an ssh config file. */ public static final String USER_KNOWN_HOSTS_FILE = "UserKnownHostsFile"; // Values /** Flag value. */ public static final String YES = "yes"; /** Flag value. */ public static final String ON = "on"; /** Flag value. */ public static final String TRUE = "true"; /** Flag value. */ public static final String NO = "no"; /** Flag value. */ public static final String OFF = "off"; /** Flag value. */ public static final String FALSE = "false"; /** * Property value. Some keys accept a special 'none' value to override and * clear a setting otherwise contributed by another host entry, for instance * {@link #PROXY_COMMAND} or {@link #PROXY_JUMP}. Example: * *
	 * Host bastion.example.org
	 *   ProxyJump none
	 *
	 * Host *.example.org
	 *   ProxyJump bastion.example.org
	 * 
*

* OpenSSH supports this since OpenSSH 7.8. *

* * @since 6.0 */ public static final String NONE = "none"; // Default identity file names /** Name of the default RSA private identity file. */ public static final String ID_RSA = "id_rsa"; /** Name of the default DSA private identity file. */ public static final String ID_DSA = "id_dsa"; /** Name of the default ECDSA private identity file. */ public static final String ID_ECDSA = "id_ecdsa"; /** Name of the default ED25519 private identity file. */ public static final String ID_ED25519 = "id_ed25519"; /** All known default identity file names. */ public static final String[] DEFAULT_IDENTITIES = { // ID_RSA, ID_DSA, ID_ECDSA, ID_ED25519 }; /** * Name of the environment variable holding the Unix domain socket for * communication with an SSH agent. * * @since 6.1 */ public static final String ENV_SSH_AUTH_SOCKET = "SSH_AUTH_SOCK"; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4374 Content-Disposition: inline; filename="SshSessionFactory.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "8120df0698076c5a42f988a117fe76a968453320" /* * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Iterator; import java.util.ServiceLoader; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; /** * Creates and destroys SSH connections to a remote system. *

* Different implementations of the session factory may be used to control * communicating with the end-user as well as reading their personal SSH * configuration settings, such as known hosts and private keys. *

*

* A {@link RemoteSession} must be returned to the factory that created it. * Callers are encouraged to retain the SshSessionFactory for the duration of * the period they are using the session. *

*/ public abstract class SshSessionFactory { private static class DefaultFactory { private static volatile SshSessionFactory INSTANCE = loadSshSessionFactory(); private static SshSessionFactory loadSshSessionFactory() { ServiceLoader loader = ServiceLoader .load(SshSessionFactory.class); Iterator iter = loader.iterator(); if (iter.hasNext()) { return iter.next(); } return null; } private DefaultFactory() { // No instantiation } public static SshSessionFactory getInstance() { return INSTANCE; } public static void setInstance(SshSessionFactory newFactory) { if (newFactory != null) { INSTANCE = newFactory; } else { INSTANCE = loadSshSessionFactory(); } } } /** * Gets the currently configured JVM-wide factory. *

* By default the factory will read from the user's {@code $HOME/.ssh} and * assume OpenSSH compatibility. *

* * @return factory the current factory for this JVM. */ public static SshSessionFactory getInstance() { return DefaultFactory.getInstance(); } /** * Changes the JVM-wide factory to a different implementation. * * @param newFactory * factory for future sessions to be created through; if * {@code null} the default factory will be restored. */ public static void setInstance(SshSessionFactory newFactory) { DefaultFactory.setInstance(newFactory); } /** * Retrieves the local user name as defined by the system property * "user.name". * * @return the user name * @since 5.2 */ public static String getLocalUserName() { return SystemReader.getInstance() .getProperty(Constants.OS_USER_NAME_KEY); } /** * Opens (or reuses) a session to a host. The returned session is connected * and authenticated and is ready for further use. * * @param uri * URI of the remote host to connect to * @param credentialsProvider * provider to support authentication, may be {@code null} if no * user input for authentication is needed * @param fs * the file system abstraction to use for certain file * operations, such as reading configuration files * @param tms * connection timeout for creating the session, in milliseconds * @return a connected and authenticated session for communicating with the * remote host given by the {@code uri} * @throws org.eclipse.jgit.errors.TransportException * if the session could not be created */ public abstract RemoteSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms) throws TransportException; /** * The name of the type of session factory. * * @return the name of the type of session factory. * * @since 5.8 */ public abstract String getType(); /** * Closes (or recycles) a session to a host. * * @param session * a session previously obtained from this factory's * {@link #getSession(URIish, CredentialsProvider, FS, int)} * method. */ public void releaseSession(RemoteSession session) { session.disconnect(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3308 Content-Disposition: inline; filename="SshTransport.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "8d93977a1d6b29344410606dd1d51d2cb788b0d1" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2009, Google Inc. * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008-2009, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; /** * The base class for transports that use SSH protocol. This class allows * customizing SSH connection settings. */ public abstract class SshTransport extends TcpTransport { private SshSessionFactory sch; /** * The open SSH session */ private RemoteSession sock; /** * Create a new transport instance. * * @param local * the repository this instance will fetch into, or push out of. * This must be the repository passed to * {@link #open(Repository, URIish)}. * @param uri * the URI used to access the remote repository. This must be the * URI passed to {@link #open(Repository, URIish)}. */ protected SshTransport(Repository local, URIish uri) { super(local, uri); sch = SshSessionFactory.getInstance(); } /** * Create a new transport instance without a local repository. * * @param uri the URI used to access the remote repository. This must be the * URI passed to {@link #open(URIish)}. * @since 3.5 */ protected SshTransport(URIish uri) { super(uri); sch = SshSessionFactory.getInstance(); } /** * Set SSH session factory instead of the default one for this instance of * the transport. * * @param factory * a factory to set, must not be null * @throws java.lang.IllegalStateException * if session has been already created. */ public void setSshSessionFactory(SshSessionFactory factory) { if (factory == null) throw new NullPointerException(JGitText.get().theFactoryMustNotBeNull); if (sock != null) throw new IllegalStateException( JGitText.get().anSSHSessionHasBeenAlreadyCreated); sch = factory; } /** * Get the SSH session factory * * @return the SSH session factory that will be used for creating SSH * sessions */ public SshSessionFactory getSshSessionFactory() { return sch; } /** * Get the default SSH session * * @return a remote session * @throws org.eclipse.jgit.errors.TransportException * in case of error with opening SSH session */ protected RemoteSession getSession() throws TransportException { if (sock != null) return sock; final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0; final FS fs = local == null ? FS.detect() : local.getFS(); sock = sch .getSession(uri, getCredentialsProvider(), fs, tms); return sock; } @Override public void close() { if (sock != null) { try { sch.releaseSession(sock); } finally { sock = null; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2547 Content-Disposition: inline; filename="TagOpt.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "72dc3ed7df2ed3cf19c854a61e28dd51db9d6369" /* * Copyright (C) 2008, Mike Ralphson * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; /** * Specification of annotated tag behavior during fetch. */ public enum TagOpt { /** * Automatically follow tags if we fetch the thing they point at. *

* This is the default behavior and tries to balance the benefit of having * an annotated tag against the cost of possibly objects that are only on * branches we care nothing about. Annotated tags are fetched only if we can * prove that we already have (or will have when the fetch completes) the * object the annotated tag peels (dereferences) to. */ AUTO_FOLLOW(""), //$NON-NLS-1$ /** * Never fetch tags, even if we have the thing it points at. *

* This option must be requested by the user and always avoids fetching * annotated tags. It is most useful if the location you are fetching from * publishes annotated tags, but you are not interested in the tags and only * want their branches. */ NO_TAGS("--no-tags"), //$NON-NLS-1$ /** * Always fetch tags, even if we do not have the thing it points at. *

* Unlike {@link #AUTO_FOLLOW} the tag is always obtained. This may cause * hundreds of megabytes of objects to be fetched if the receiving * repository does not yet have the necessary dependencies. */ FETCH_TAGS("--tags"); //$NON-NLS-1$ private final String option; private TagOpt(String o) { option = o; } /** * Get the command line/configuration file text for this value. * * @return text that appears in the configuration file to activate this. */ public String option() { return option; } /** * Convert a command line/configuration file text into a value instance. * * @param o * the configuration file text value. * @return the option that matches the passed parameter. */ public static TagOpt fromOption(String o) { if (o == null || o.length() == 0) return AUTO_FOLLOW; for (TagOpt tagopt : values()) { if (tagopt.option().equals(o)) return tagopt; } throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidTagOption, o)); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1478 Content-Disposition: inline; filename="TcpTransport.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "5ebe3295a5cda69f9c875f6a250ac3b08ad985ad" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2009, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.lib.Repository; /** * The base class for transports based on TCP sockets. This class * holds settings common for all TCP based transports. */ public abstract class TcpTransport extends Transport { /** * Create a new transport instance. * * @param local * the repository this instance will fetch into, or push out of. * This must be the repository passed to * {@link #open(Repository, URIish)}. * @param uri * the URI used to access the remote repository. This must be the * URI passed to {@link #open(Repository, URIish)}. */ protected TcpTransport(Repository local, URIish uri) { super(local, uri); } /** * Create a new transport instance without a local repository. * * @param uri the URI used to access the remote repository. This must be the * URI passed to {@link #open(URIish)}. * @since 3.5 */ protected TcpTransport(URIish uri) { super(uri); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5393 Content-Disposition: inline; filename="TestProtocol.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "77ab0f676d85bbf9519a24e6d54531ccc5074f39" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Set; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.BasePackFetchConnection.FetchConfig; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.UploadPackFactory; /** * Protocol for transport between manually-specified repositories in tests. *

* Remote repositories are registered using * {@link #register(Object, Repository)}, after which they can be accessed using * the returned URI. As this class provides both the client side (the protocol) * and the server side, the caller is responsible for setting up and passing the * connection context, whatever form that may take. *

* Unlike the other built-in protocols, which are automatically-registered * singletons, callers are expected to register/unregister specific protocol * instances on demand with * {@link org.eclipse.jgit.transport.Transport#register(TransportProtocol)}. * * @param * the connection type * @since 4.0 */ public class TestProtocol extends TransportProtocol { private static final String SCHEME = "test"; //$NON-NLS-1$ private static FetchConfig fetchConfig; private class Handle { final C req; final Repository remote; Handle(C req, Repository remote) { this.req = req; this.remote = remote; } } final UploadPackFactory uploadPackFactory; final ReceivePackFactory receivePackFactory; private final HashMap handles; /** * Constructor for TestProtocol. * * @param uploadPackFactory * factory for creating * {@link org.eclipse.jgit.transport.UploadPack} used by all * connections from this protocol instance. * @param receivePackFactory * factory for creating * {@link org.eclipse.jgit.transport.ReceivePack} used by all * connections from this protocol instance. */ public TestProtocol(UploadPackFactory uploadPackFactory, ReceivePackFactory receivePackFactory) { this.uploadPackFactory = uploadPackFactory; this.receivePackFactory = receivePackFactory; this.handles = new HashMap<>(); } @Override public String getName() { return JGitText.get().transportProtoTest; } @Override public Set getSchemes() { return Collections.singleton(SCHEME); } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException, TransportException { Handle h = handles.get(uri); if (h == null) { throw new NotSupportedException(MessageFormat.format( JGitText.get().URINotSupported, uri)); } return new TransportInternal(local, uri, h); } @Override public Set getRequiredFields() { return EnumSet.of(URIishField.HOST, URIishField.PATH); } @Override public Set getOptionalFields() { return Collections.emptySet(); } static void setFetchConfig(FetchConfig c) { fetchConfig = c; } /** * Register a repository connection over the internal test protocol. * * @param req * connection context. This instance is reused for all connections * made using this protocol; if it is stateful and usable only for * one connection, the same repository should be registered * multiple times. * @param remote * remote repository to connect to. * @return a URI that can be used to connect to this repository for both fetch * and push. */ public synchronized URIish register(C req, Repository remote) { URIish uri; try { int n = handles.size(); uri = new URIish(SCHEME + "://test/conn" + n); //$NON-NLS-1$ } catch (URISyntaxException e) { throw new IllegalStateException(e); } handles.put(uri, new Handle(req, remote)); return uri; } private class TransportInternal extends Transport implements PackTransport { private final Handle handle; TransportInternal(Repository local, URIish uri, Handle handle) { super(local, uri); this.handle = handle; } @Override public FetchConnection openFetch() throws NotSupportedException, TransportException { handle.remote.incrementOpen(); return new InternalFetchConnection<>(this, uploadPackFactory, handle.req, handle.remote) { @Override FetchConfig getFetchConfig() { return fetchConfig != null ? fetchConfig : super.getFetchConfig(); } }; } @Override public PushConnection openPush() throws NotSupportedException, TransportException { handle.remote.incrementOpen(); return new InternalPushConnection<>( this, receivePackFactory, handle.req, handle.remote); } @Override public void close() { // Resources must be established per-connection. } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4919 Content-Disposition: inline; filename="TrackingRefUpdate.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "26045a2c7c2b9efe6d19b8d56e892124a6fe03d4" /* * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefUpdate; /** * Update of a locally stored tracking branch. */ public class TrackingRefUpdate { private final String remoteName; final String localName; boolean forceUpdate; ObjectId oldObjectId; ObjectId newObjectId; private RefUpdate.Result result; private ReceiveCommand cmd; TrackingRefUpdate( boolean canForceUpdate, String remoteName, String localName, AnyObjectId oldValue, AnyObjectId newValue) { this.remoteName = remoteName; this.localName = localName; this.forceUpdate = canForceUpdate; this.oldObjectId = oldValue.copy(); this.newObjectId = newValue.copy(); } /** * Get the name of the remote ref. *

* Usually this is of the form "refs/heads/master". * * @return the name used within the remote repository. */ public String getRemoteName() { return remoteName; } /** * Get the name of the local tracking ref. *

* Usually this is of the form "refs/remotes/origin/master". * * @return the name used within this local repository. */ public String getLocalName() { return localName; } /** * Get the new value the ref will be (or was) updated to. * * @return new value. Null if the caller has not configured it. */ public ObjectId getNewObjectId() { return newObjectId; } /** * The old value of the ref, prior to the update being attempted. *

* This value may differ before and after the update method. Initially it is * populated with the value of the ref before the lock is taken, but the old * value may change if someone else modified the ref between the time we * last read it and when the ref was locked for update. * * @return the value of the ref prior to the update being attempted. */ public ObjectId getOldObjectId() { return oldObjectId; } /** * Get the status of this update. * * @return the status of the update. */ public RefUpdate.Result getResult() { return result; } void setResult(RefUpdate.Result result) { this.result = result; } /** * Get this update wrapped by a ReceiveCommand. * * @return this update wrapped by a ReceiveCommand. * @since 3.4 */ public ReceiveCommand asReceiveCommand() { if (cmd == null) cmd = new Command(); return cmd; } final class Command extends ReceiveCommand { Command() { super(oldObjectId, newObjectId, localName); } boolean canForceUpdate() { return forceUpdate; } @Override public void setResult(RefUpdate.Result status) { result = status; super.setResult(status); } @Override public void setResult(ReceiveCommand.Result status) { result = decode(status); super.setResult(status); } @Override public void setResult(ReceiveCommand.Result status, String msg) { result = decode(status); super.setResult(status, msg); } private RefUpdate.Result decode(ReceiveCommand.Result status) { switch (status) { case OK: if (AnyObjectId.isEqual(oldObjectId, newObjectId)) return RefUpdate.Result.NO_CHANGE; switch (getType()) { case CREATE: return RefUpdate.Result.NEW; case UPDATE: return RefUpdate.Result.FAST_FORWARD; case DELETE: case UPDATE_NONFASTFORWARD: default: return RefUpdate.Result.FORCED; } case REJECTED_NOCREATE: case REJECTED_NODELETE: case REJECTED_NONFASTFORWARD: return RefUpdate.Result.REJECTED; case REJECTED_CURRENT_BRANCH: return RefUpdate.Result.REJECTED_CURRENT_BRANCH; case REJECTED_MISSING_OBJECT: return RefUpdate.Result.IO_FAILURE; case LOCK_FAILURE: case NOT_ATTEMPTED: case REJECTED_OTHER_REASON: default: return RefUpdate.Result.LOCK_FAILURE; } } } @SuppressWarnings("nls") @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("TrackingRefUpdate["); sb.append(remoteName); sb.append(" -> "); sb.append(localName); if (forceUpdate) sb.append(" (forced)"); sb.append(" "); sb.append(oldObjectId == null ? "" : oldObjectId.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) .name()); sb.append(".."); sb.append(newObjectId == null ? "" : newObjectId.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) .name()); sb.append("]"); return sb.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11975 Content-Disposition: inline; filename="TransferConfig.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "5333beff4b625ca41f7807e4138331b3db5d272e" /* * Copyright (C) 2008, 2023 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase; import static org.eclipse.jgit.util.StringUtils.toLowerCase; import java.io.File; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.SystemReader; /** * The standard "transfer", "fetch", "protocol", "receive", and "uploadpack" * configuration parameters. */ public class TransferConfig { private static final String FSCK = "fsck"; //$NON-NLS-1$ /** Key for {@link Config#get(SectionParser)}. */ public static final Config.SectionParser KEY = TransferConfig::new; /** * A git configuration value for how to handle a fsck failure of a particular kind. * Used in e.g. fsck.missingEmail. * @since 4.9 */ public enum FsckMode { /** * Treat it as an error (the default). */ ERROR, /** * Issue a warning (in fact, jgit treats this like IGNORE, but git itself does warn). */ WARN, /** * Ignore the error. */ IGNORE; } /** * A git configuration variable for which versions of the Git protocol to * prefer. Used in protocol.version. * * @since 5.9 */ public enum ProtocolVersion { /** * Git wire protocol version 0 (the default). */ V0("0"), //$NON-NLS-1$ /** * Git wire protocol version 2. */ V2("2"); //$NON-NLS-1$ final String name; ProtocolVersion(String name) { this.name = name; } /** * Returns version number * * @return string version */ public String version() { return name; } @Nullable static ProtocolVersion parse(@Nullable String name) { if (name == null) { return null; } for (ProtocolVersion v : ProtocolVersion.values()) { if (v.name.equals(name)) { return v; } } if ("1".equals(name)) { //$NON-NLS-1$ return V0; } return null; } } private final boolean fetchFsck; private final boolean receiveFsck; private final String fsckSkipList; private final EnumSet ignore; private final boolean allowInvalidPersonIdent; private final boolean safeForWindows; private final boolean safeForMacOS; private final boolean allowRefInWant; private final boolean allowTipSha1InWant; private final boolean allowReachableSha1InWant; private final boolean allowAnySha1InWant; private final boolean allowFilter; private final boolean allowSidebandAll; private final boolean advertiseSidebandAll; private final boolean advertiseWaitForDone; private final boolean advertiseObjectInfo; private final boolean allowReceiveClientSID; final @Nullable ProtocolVersion protocolVersion; final String[] hideRefs; /** * Create a configuration honoring the repository's settings. * * @param db * the repository to read settings from. The repository is not * retained by the new configuration, instead its settings are * copied during the constructor. * @since 5.1.4 */ public TransferConfig(Repository db) { this(db.getConfig()); } /** * Create a configuration honoring settings in a * {@link org.eclipse.jgit.lib.Config}. * * @param rc * the source to read settings from. The source is not retained * by the new configuration, instead its settings are copied * during the constructor. * @since 5.1.4 */ @SuppressWarnings("nls") public TransferConfig(Config rc) { boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); fsckSkipList = rc.getString(FSCK, null, "skipList"); allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent", false); safeForWindows = rc.getBoolean(FSCK, "safeForWindows", SystemReader.getInstance().isWindows()); safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS", SystemReader.getInstance().isMacOS()); ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class); EnumSet set = EnumSet .noneOf(ObjectChecker.ErrorType.class); for (String key : rc.getNames(FSCK)) { if (equalsIgnoreCase(key, "skipList") || equalsIgnoreCase(key, "allowLeadingZeroFileMode") || equalsIgnoreCase(key, "allowInvalidPersonIdent") || equalsIgnoreCase(key, "safeForWindows") || equalsIgnoreCase(key, "safeForMacOS")) { continue; } ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key); if (id != null) { switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) { case ERROR: ignore.remove(id); break; case WARN: case IGNORE: ignore.add(id); break; } set.add(id); } } if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE) && rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) { ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE); } allowRefInWant = rc.getBoolean("uploadpack", "allowrefinwant", false); allowTipSha1InWant = rc.getBoolean( "uploadpack", "allowtipsha1inwant", false); allowReachableSha1InWant = rc.getBoolean( "uploadpack", "allowreachablesha1inwant", false); allowAnySha1InWant = rc.getBoolean("uploadpack", "allowanysha1inwant", false); allowFilter = rc.getBoolean( "uploadpack", "allowfilter", false); protocolVersion = ProtocolVersion.parse(rc .getString(ConfigConstants.CONFIG_PROTOCOL_SECTION, null, ConfigConstants.CONFIG_KEY_VERSION)); hideRefs = rc.getStringList("uploadpack", null, "hiderefs"); allowSidebandAll = rc.getBoolean( "uploadpack", "allowsidebandall", false); advertiseSidebandAll = rc.getBoolean("uploadpack", "advertisesidebandall", false); advertiseWaitForDone = rc.getBoolean("uploadpack", "advertisewaitfordone", false); advertiseObjectInfo = rc.getBoolean("uploadpack", "advertiseobjectinfo", false); allowReceiveClientSID = rc.getBoolean("transfer", "advertisesid", false); } /** * Create checker to verify fetched objects * * @return checker to verify fetched objects, or null if checking is not * enabled in the repository configuration. * @since 3.6 */ @Nullable public ObjectChecker newObjectChecker() { return newObjectChecker(fetchFsck); } /** * Create checker to verify objects pushed into this repository * * @return checker to verify objects pushed into this repository, or null if * checking is not enabled in the repository configuration. * @since 4.2 */ @Nullable public ObjectChecker newReceiveObjectChecker() { return newObjectChecker(receiveFsck); } private ObjectChecker newObjectChecker(boolean check) { if (!check) { return null; } return new ObjectChecker() .setIgnore(ignore) .setAllowInvalidPersonIdent(allowInvalidPersonIdent) .setSafeForWindows(safeForWindows) .setSafeForMacOS(safeForMacOS) .setSkipList(skipList()); } private ObjectIdSet skipList() { if (fsckSkipList != null && !fsckSkipList.isEmpty()) { return new LazyObjectIdSetFile(new File(fsckSkipList)); } return null; } /** * Whether to allow clients to request non-advertised tip SHA-1s * * @return allow clients to request non-advertised tip SHA-1s? * @since 3.1 */ public boolean isAllowTipSha1InWant() { return allowTipSha1InWant; } /** * Whether to allow clients to request non-tip SHA-1s * * @return allow clients to request non-tip SHA-1s? * @since 4.1 */ public boolean isAllowReachableSha1InWant() { return allowReachableSha1InWant; } /** * Whether to allow clients to request any SHA-1s * * @return allow clients to request any SHA-1s? * @since 6.5 */ public boolean isAllowAnySha1InWant() { return allowAnySha1InWant; } /** * Whether clients are allowed to specify "filter" line * * @return true if clients are allowed to specify a "filter" line * @since 5.0 */ public boolean isAllowFilter() { return allowFilter; } /** * Whether clients are allowed to specify "want-ref" line * * @return true if clients are allowed to specify a "want-ref" line * @since 5.1 */ public boolean isAllowRefInWant() { return allowRefInWant; } /** * Whether the server accepts sideband-all requests * * @return true if the server accepts sideband-all requests (see * {{@link #isAdvertiseSidebandAll()} for the advertisement) * @since 5.5 */ public boolean isAllowSidebandAll() { return allowSidebandAll; } /** * Whether to advertise sideband all to the clients * * @return true to advertise sideband all to the clients * @since 5.6 */ public boolean isAdvertiseSidebandAll() { return advertiseSidebandAll && allowSidebandAll; } /** * Whether to advertise wait-for-done all to the clients * * @return true to advertise wait-for-done all to the clients * @since 5.13 */ public boolean isAdvertiseWaitForDone() { return advertiseWaitForDone; } /** * Whether to advertise object-info to all clients * * @return true to advertise object-info to all clients * @since 5.13 */ public boolean isAdvertiseObjectInfo() { return advertiseObjectInfo; } /** * Whether to advertise and receive session-id capability * * @return true to advertise and receive session-id capability * @since 6.4 */ public boolean isAllowReceiveClientSID() { return allowReceiveClientSID; } /** * Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured * hidden refs. * * @return {@link org.eclipse.jgit.transport.RefFilter} respecting * configured hidden refs. * @since 3.1 */ public RefFilter getRefFilter() { if (hideRefs.length == 0) return RefFilter.DEFAULT; return new RefFilter() { @Override public Map filter(Map refs) { Map result = new HashMap<>(); for (Map.Entry e : refs.entrySet()) { boolean add = true; for (String hide : hideRefs) { if (e.getKey().equals(hide) || prefixMatch(hide, e.getKey())) { add = false; break; } } if (add) result.put(e.getKey(), e.getValue()); } return result; } private boolean prefixMatch(String p, String s) { return p.charAt(p.length() - 1) == '/' && s.startsWith(p); } }; } /** * Like {@code getRefFilter() == RefFilter.DEFAULT}, but faster. * * @return {@code true} if no ref filtering is needed because there * are no configured hidden refs. */ boolean hasDefaultRefFilter() { return hideRefs.length == 0; } static class FsckKeyNameHolder { private static final Map errors; static { errors = new HashMap<>(); for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) { errors.put(keyNameFor(m.name()), m); } } @Nullable static ObjectChecker.ErrorType parse(String key) { return errors.get(toLowerCase(key)); } private static String keyNameFor(String name) { StringBuilder r = new StringBuilder(name.length()); for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); if (c != '_') { r.append(c); } } return toLowerCase(r.toString()); } private FsckKeyNameHolder() { } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 58441 Content-Disposition: inline; filename="Transport.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "ac76e83d3442fc91c82ee3711faecb1ca8026742" /* * Copyright (C) 2008, 2009 Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2022 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URISyntaxException; import java.net.URL; import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.hooks.Hooks; import org.eclipse.jgit.hooks.PrePushHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.pack.PackConfig; /** * Connects two Git repositories together and copies objects between them. *

* A transport can be used for either fetching (copying objects into the * caller's repository from the remote repository) or pushing (copying objects * into the remote repository from the caller's repository). Each transport * implementation is responsible for the details associated with establishing * the network connection(s) necessary for the copy, as well as actually * shuffling data back and forth. *

* Transport instances and the connections they create are not thread-safe. * Callers must ensure a transport is accessed by only one thread at a time. */ public abstract class Transport implements AutoCloseable { /** Type of operation a Transport is being opened for. */ public enum Operation { /** Transport is to fetch objects locally. */ FETCH, /** Transport is to push objects remotely. */ PUSH; } // Use weak references to enable unloading dynamically loaded protocols private static final List> protocols = new CopyOnWriteArrayList<>(); static { // Registration goes backwards in order of priority. register(TransportLocal.PROTO_LOCAL); register(TransportBundleFile.PROTO_BUNDLE); register(TransportAmazonS3.PROTO_S3); register(TransportGitAnon.PROTO_GIT); register(TransportSftp.PROTO_SFTP); register(TransportHttp.PROTO_FTP); register(TransportHttp.PROTO_HTTP); register(TransportGitSsh.PROTO_SSH); registerByService(); } private static void registerByService() { ClassLoader ldr = Thread.currentThread().getContextClassLoader(); if (ldr == null) ldr = Transport.class.getClassLoader(); Enumeration catalogs = catalogs(ldr); while (catalogs.hasMoreElements()) scan(ldr, catalogs.nextElement()); } private static Enumeration catalogs(ClassLoader ldr) { try { String prefix = "META-INF/services/"; //$NON-NLS-1$ String name = prefix + Transport.class.getName(); return ldr.getResources(name); } catch (IOException err) { return Collections.emptyEnumeration(); } } private static void scan(ClassLoader ldr, URL url) { try (BufferedReader br = new BufferedReader( new InputStreamReader(url.openStream(), UTF_8))) { String line; while ((line = br.readLine()) != null) { line = line.trim(); if (line.length() == 0) continue; int comment = line.indexOf('#'); if (comment == 0) continue; if (comment != -1) line = line.substring(0, comment).trim(); load(ldr, line); } } catch (IOException e) { // Ignore errors } } private static void load(ClassLoader ldr, String cn) { Class clazz; try { clazz = Class.forName(cn, false, ldr); } catch (ClassNotFoundException notBuiltin) { // Doesn't exist, even though the service entry is present. // return; } for (Field f : clazz.getDeclaredFields()) { if ((f.getModifiers() & Modifier.STATIC) == Modifier.STATIC && TransportProtocol.class.isAssignableFrom(f.getType())) { TransportProtocol proto; try { proto = (TransportProtocol) f.get(null); } catch (IllegalArgumentException | IllegalAccessException e) { // If we cannot access the field, don't. continue; } if (proto != null) register(proto); } } } /** * Register a TransportProtocol instance for use during open. *

* Protocol definitions are held by WeakReference, allowing them to be * garbage collected when the calling application drops all strongly held * references to the TransportProtocol. Therefore applications should use a * singleton pattern as described in * {@link org.eclipse.jgit.transport.TransportProtocol}'s class * documentation to ensure their protocol does not get disabled by garbage * collection earlier than expected. *

* The new protocol is registered in front of all earlier protocols, giving * it higher priority than the built-in protocol definitions. * * @param proto * the protocol definition. Must not be null. */ public static void register(TransportProtocol proto) { protocols.add(0, new WeakReference<>(proto)); } /** * Unregister a TransportProtocol instance. *

* Unregistering a protocol usually isn't necessary, as protocols are held * by weak references and will automatically clear when they are garbage * collected by the JVM. Matching is handled by reference equality, so the * exact reference given to {@link #register(TransportProtocol)} must be * used. * * @param proto * the exact object previously given to register. */ @SuppressWarnings("ModifyCollectionInEnhancedForLoop") public static void unregister(TransportProtocol proto) { for (WeakReference ref : protocols) { TransportProtocol refProto = ref.get(); if (refProto == null || refProto == proto) { protocols.remove(ref); } } } /** * Obtain a copy of the registered protocols. * * @return an immutable copy of the currently registered protocols. */ @SuppressWarnings("ModifyCollectionInEnhancedForLoop") public static List getTransportProtocols() { int cnt = protocols.size(); List res = new ArrayList<>(cnt); for (WeakReference ref : protocols) { TransportProtocol proto = ref.get(); if (proto != null) { res.add(proto); } else { protocols.remove(ref); } } return Collections.unmodifiableList(res); } /** * Open a new transport instance to connect two repositories. *

* This method assumes * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. * @param remote * location of the remote repository - may be URI or remote * configuration name. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(Repository local, String remote) throws NotSupportedException, URISyntaxException, TransportException { return open(local, remote, Operation.FETCH); } /** * Open a new transport instance to connect two repositories. * * @param local * existing local repository. * @param remote * location of the remote repository - may be URI or remote * configuration name. * @param op * planned use of the returned Transport; the URI may differ * based on the type of connection desired. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(final Repository local, final String remote, final Operation op) throws NotSupportedException, URISyntaxException, TransportException { if (local != null) { final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote); if (doesNotExist(cfg)) { return open(local, new URIish(remote), null); } return open(local, cfg, op); } return open(new URIish(remote)); } /** * Open new transport instances to connect two repositories. *

* This method assumes * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. * @param remote * location of the remote repository - may be URI or remote * configuration name. * @return the list of new transport instances for every URI in remote * configuration. * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, final String remote) throws NotSupportedException, URISyntaxException, TransportException { return openAll(local, remote, Operation.FETCH); } /** * Open new transport instances to connect two repositories. * * @param local * existing local repository. * @param remote * location of the remote repository - may be URI or remote * configuration name. * @param op * planned use of the returned Transport; the URI may differ * based on the type of connection desired. * @return the list of new transport instances for every URI in remote * configuration. * @throws java.net.URISyntaxException * the location is not a remote defined in the configuration * file and is not a well-formed URL. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, final String remote, final Operation op) throws NotSupportedException, URISyntaxException, TransportException { final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote); if (doesNotExist(cfg)) { final ArrayList transports = new ArrayList<>(1); transports.add(open(local, new URIish(remote), null)); return transports; } return openAll(local, cfg, op); } /** * Open a new transport instance to connect two repositories. *

* This method assumes * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. * @param cfg * configuration describing how to connect to the remote * repository. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. * @throws java.lang.IllegalArgumentException * if provided remote configuration doesn't have any URI * associated. */ public static Transport open(Repository local, RemoteConfig cfg) throws NotSupportedException, TransportException { return open(local, cfg, Operation.FETCH); } /** * Open a new transport instance to connect two repositories. * * @param local * existing local repository. * @param cfg * configuration describing how to connect to the remote * repository. * @param op * planned use of the returned Transport; the URI may differ * based on the type of connection desired. * @return the new transport instance. Never null. In case of multiple URIs * in remote configuration, only the first is chosen. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. * @throws java.lang.IllegalArgumentException * if provided remote configuration doesn't have any URI * associated. */ public static Transport open(final Repository local, final RemoteConfig cfg, final Operation op) throws NotSupportedException, TransportException { final List uris = getURIs(cfg, op); if (uris.isEmpty()) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().remoteConfigHasNoURIAssociated, cfg.getName())); final Transport tn = open(local, uris.get(0), cfg.getName()); tn.applyConfig(cfg); return tn; } /** * Open new transport instances to connect two repositories. *

* This method assumes * {@link org.eclipse.jgit.transport.Transport.Operation#FETCH}. * * @param local * existing local repository. * @param cfg * configuration describing how to connect to the remote * repository. * @return the list of new transport instances for every URI in remote * configuration. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, final RemoteConfig cfg) throws NotSupportedException, TransportException { return openAll(local, cfg, Operation.FETCH); } /** * Open new transport instances to connect two repositories. * * @param local * existing local repository. * @param cfg * configuration describing how to connect to the remote * repository. * @param op * planned use of the returned Transport; the URI may differ * based on the type of connection desired. * @return the list of new transport instances for every URI in remote * configuration. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static List openAll(final Repository local, final RemoteConfig cfg, final Operation op) throws NotSupportedException, TransportException { final List uris = getURIs(cfg, op); final List transports = new ArrayList<>(uris.size()); for (URIish uri : uris) { final Transport tn = open(local, uri, cfg.getName()); tn.applyConfig(cfg); transports.add(tn); } return transports; } private static List getURIs(final RemoteConfig cfg, final Operation op) { switch (op) { case FETCH: return cfg.getURIs(); case PUSH: { List uris = cfg.getPushURIs(); if (uris.isEmpty()) uris = cfg.getURIs(); return uris; } default: throw new IllegalArgumentException(op.toString()); } } private static boolean doesNotExist(RemoteConfig cfg) { return cfg.getURIs().isEmpty() && cfg.getPushURIs().isEmpty(); } /** * Open a new transport instance to connect two repositories. * * @param local * existing local repository. * @param uri * location of the remote repository. * @return the new transport instance. Never null. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public static Transport open(Repository local, URIish uri) throws NotSupportedException, TransportException { return open(local, uri, null); } /** * Open a new transport instance to connect two repositories. * * @param local * existing local repository. * @param uri * location of the remote repository. * @param remoteName * name of the remote, if the remote as configured in * {@code local}; otherwise null. * @return the new transport instance. Never null. * @throws org.eclipse.jgit.errors.NotSupportedException * the protocol specified is not supported. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ @SuppressWarnings("ModifyCollectionInEnhancedForLoop") public static Transport open(Repository local, URIish uri, String remoteName) throws NotSupportedException, TransportException { for (WeakReference ref : protocols) { TransportProtocol proto = ref.get(); if (proto == null) { protocols.remove(ref); continue; } if (proto.canHandle(uri, local, remoteName)) { Transport tn = proto.open(uri, local, remoteName); tn.remoteName = remoteName; return tn; } } throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri)); } /** * Open a new transport with no local repository. *

* Note that the resulting transport instance can not be used for fetching * or pushing, but only for reading remote refs. * * @param uri * a {@link org.eclipse.jgit.transport.URIish} object. * @return new Transport instance * @throws org.eclipse.jgit.errors.NotSupportedException * case that is not supported by JGit * @throws org.eclipse.jgit.errors.TransportException * if transport failed */ @SuppressWarnings("ModifyCollectionInEnhancedForLoop") public static Transport open(URIish uri) throws NotSupportedException, TransportException { for (WeakReference ref : protocols) { TransportProtocol proto = ref.get(); if (proto == null) { protocols.remove(ref); continue; } if (proto.canHandle(uri, null, null)) { return proto.open(uri); } } throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri)); } /** * Convert push remote refs update specification from * {@link org.eclipse.jgit.transport.RefSpec} form to * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands * wildcards by matching source part to local refs. expectedOldObjectId in * RemoteRefUpdate is set when specified in leases. Tracking branch is * configured if RefSpec destination matches source of any fetch ref spec * for this transport remote configuration. * * @param db * local database. * @param specs * collection of RefSpec to convert. * @param leases * map from ref to lease (containing expected old object id) * @param fetchSpecs * fetch specifications used for finding localtracking refs. May * be null or empty collection. * @return collection of set up * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. * @throws java.io.IOException * when problem occurred during conversion or specification set * up: most probably, missing objects or refs. * @since 4.7 */ public static Collection findRemoteRefUpdatesFor( final Repository db, final Collection specs, final Map leases, Collection fetchSpecs) throws IOException { if (fetchSpecs == null) fetchSpecs = Collections.emptyList(); final List result = new ArrayList<>(); final Collection procRefs = expandPushWildcardsFor(db, specs); for (RefSpec spec : procRefs) { if (spec.isMatching()) { result.add(new RemoteRefUpdate(db, spec.isForceUpdate(), fetchSpecs)); continue; } String srcSpec = spec.getSource(); final Ref srcRef = db.findRef(srcSpec); if (srcRef != null) srcSpec = srcRef.getName(); String destSpec = spec.getDestination(); if (destSpec == null) { // No destination (no-colon in ref-spec), DWIMery assumes src // destSpec = srcSpec; } if (srcRef != null && !destSpec.startsWith(Constants.R_REFS)) { // Assume the same kind of ref at the destination, e.g. // "refs/heads/foo:master", DWIMery assumes master is also // under "refs/heads/". // final String n = srcRef.getName(); final int kindEnd = n.indexOf('/', Constants.R_REFS.length()); destSpec = n.substring(0, kindEnd + 1) + destSpec; } final boolean forceUpdate = spec.isForceUpdate(); final String localName = findTrackingRefName(destSpec, fetchSpecs); final RefLeaseSpec leaseSpec = leases.get(destSpec); final ObjectId expected = leaseSpec == null ? null : db.resolve(leaseSpec.getExpected()); final RemoteRefUpdate rru = new RemoteRefUpdate(db, srcSpec, destSpec, forceUpdate, localName, expected); result.add(rru); } return result; } /** * Convert push remote refs update specification from * {@link org.eclipse.jgit.transport.RefSpec} form to * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands * wildcards by matching source part to local refs. expectedOldObjectId in * RemoteRefUpdate is always set as null. Tracking branch is configured if * RefSpec destination matches source of any fetch ref spec for this * transport remote configuration. * * @param db * local database. * @param specs * collection of RefSpec to convert. * @param fetchSpecs * fetch specifications used for finding localtracking refs. May * be null or empty collection. * @return collection of set up * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. * @throws java.io.IOException * when problem occurred during conversion or specification set * up: most probably, missing objects or refs. */ public static Collection findRemoteRefUpdatesFor( final Repository db, final Collection specs, Collection fetchSpecs) throws IOException { return findRemoteRefUpdatesFor(db, specs, Collections.emptyMap(), fetchSpecs); } private static Collection expandPushWildcardsFor( final Repository db, final Collection specs) throws IOException { final Collection procRefs = new LinkedHashSet<>(); List localRefs = null; for (RefSpec spec : specs) { if (!spec.isMatching() && spec.isWildcard()) { if (localRefs == null) { localRefs = db.getRefDatabase().getRefs(); } for (Ref localRef : localRefs) { if (spec.matchSource(localRef)) { procRefs.add(spec.expandFromSource(localRef)); } } } else { procRefs.add(spec); } } return procRefs; } static String findTrackingRefName(final String remoteName, final Collection fetchSpecs) { // try to find matching tracking refs for (RefSpec fetchSpec : fetchSpecs) { if (fetchSpec.matchSource(remoteName)) { if (fetchSpec.isWildcard()) { return fetchSpec.expandFromSource(remoteName) .getDestination(); } return fetchSpec.getDestination(); } } return null; } /** * Default setting for {@link #fetchThin} option. */ public static final boolean DEFAULT_FETCH_THIN = true; /** * Default setting for {@link #pushThin} option. */ public static final boolean DEFAULT_PUSH_THIN = false; /** * Default setting for {@link #pushUseBitmaps} option. * * @since 6.4 */ public static final boolean DEFAULT_PUSH_USE_BITMAPS = true; /** * Specification for fetch or push operations, to fetch or push all tags. * Acts as --tags. */ public static final RefSpec REFSPEC_TAGS = new RefSpec( "refs/tags/*:refs/tags/*"); //$NON-NLS-1$ /** * Specification for push operation, to push all refs under refs/heads. Acts * as --all. */ public static final RefSpec REFSPEC_PUSH_ALL = new RefSpec( "refs/heads/*:refs/heads/*"); //$NON-NLS-1$ /** The repository this transport fetches into, or pushes out of. */ protected final Repository local; /** The URI used to create this transport. */ protected final URIish uri; /** Name of the upload pack program, if it must be executed. */ private String optionUploadPack = RemoteConfig.DEFAULT_UPLOAD_PACK; /** Specifications to apply during fetch. */ private List fetch = Collections.emptyList(); /** * How {@link #fetch(ProgressMonitor, Collection)} should handle tags. *

* We default to {@link TagOpt#NO_TAGS} so as to avoid fetching annotated * tags during one-shot fetches used for later merges. This prevents * dragging down tags from repositories that we do not have established * tracking branches for. If we do not track the source repository, we most * likely do not care about any tags it publishes. */ private TagOpt tagopt = TagOpt.NO_TAGS; /** Should fetch request thin-pack if remote repository can produce it. */ private boolean fetchThin = DEFAULT_FETCH_THIN; /** Name of the receive pack program, if it must be executed. */ private String optionReceivePack = RemoteConfig.DEFAULT_RECEIVE_PACK; /** Specifications to apply during push. */ private List push = Collections.emptyList(); /** Should push produce thin-pack when sending objects to remote repository. */ private boolean pushThin = DEFAULT_PUSH_THIN; /** Should push be all-or-nothing atomic behavior? */ private boolean pushAtomic; /** Should push use bitmaps? */ private boolean pushUseBitmaps = DEFAULT_PUSH_USE_BITMAPS; /** Should push just check for operation result, not really push. */ private boolean dryRun; /** Should an incoming (fetch) transfer validate objects? */ private ObjectChecker objectChecker; /** Should refs no longer on the source be pruned from the destination? */ private boolean removeDeletedRefs; private FilterSpec filterSpec = FilterSpec.NO_FILTER; /** Timeout in seconds to wait before aborting an IO read or write. */ private int timeout; /** Pack configuration used by this transport to make pack file. */ private PackConfig packConfig; /** Assists with authentication the connection. */ private CredentialsProvider credentialsProvider; /** The option strings associated with the push operation. */ private List pushOptions; private PrintStream hookOutRedirect; private PrintStream hookErrRedirect; private String remoteName; private Integer depth; private Instant deepenSince; private List deepenNots = new ArrayList<>(); @Nullable TransferConfig.ProtocolVersion protocol; /** * Create a new transport instance. * * @param local * the repository this instance will fetch into, or push out of. * This must be the repository passed to * {@link #open(Repository, URIish)}. * @param uri * the URI used to access the remote repository. This must be the * URI passed to {@link #open(Repository, URIish)}. */ protected Transport(Repository local, URIish uri) { final TransferConfig tc = local.getConfig().get(TransferConfig.KEY); this.local = local; this.uri = uri; this.protocol = tc.protocolVersion; this.objectChecker = tc.newObjectChecker(); this.credentialsProvider = CredentialsProvider.getDefault(); } /** * Create a minimal transport instance not tied to a single repository. * * @param uri * a {@link org.eclipse.jgit.transport.URIish} object. */ protected Transport(URIish uri) { this.uri = uri; this.local = null; this.objectChecker = new ObjectChecker(); this.credentialsProvider = CredentialsProvider.getDefault(); } /** * Get the URI this transport connects to. *

* Each transport instance connects to at most one URI at any point in time. * * @return the URI describing the location of the remote repository. */ public URIish getURI() { return uri; } /** * Get the name of the remote executable providing upload-pack service. * * @return typically "git-upload-pack". */ public String getOptionUploadPack() { return optionUploadPack; } /** * Set the name of the remote executable providing upload-pack services. * * @param where * name of the executable. */ public void setOptionUploadPack(String where) { if (where != null && where.length() > 0) optionUploadPack = where; else optionUploadPack = RemoteConfig.DEFAULT_UPLOAD_PACK; } /** * Sets a {@link PrintStream} a {@link PrePushHook} may write its stdout to. * If not set, {@link System#out} will be used. * * @param redirect * {@link PrintStream} to use; if {@code null}, * {@link System#out} will be used * @since 6.4 */ public void setHookOutputStream(PrintStream redirect) { hookOutRedirect = redirect; } /** * Sets a {@link PrintStream} a {@link PrePushHook} may write its stderr to. * If not set, {@link System#err} will be used. * * @param redirect * {@link PrintStream} to use; if {@code null}, * {@link System#err} will be used * @since 6.4 */ public void setHookErrorStream(PrintStream redirect) { hookErrRedirect = redirect; } /** * Get the description of how annotated tags should be treated during fetch. * * @return option indicating the behavior of annotated tags in fetch. */ public TagOpt getTagOpt() { return tagopt; } /** * Set the description of how annotated tags should be treated on fetch. * * @param option * method to use when handling annotated tags. */ public void setTagOpt(TagOpt option) { tagopt = option != null ? option : TagOpt.AUTO_FOLLOW; } /** * Default setting is: {@link #DEFAULT_FETCH_THIN} * * @return true if fetch should request thin-pack when possible; false * otherwise * @see PackTransport */ public boolean isFetchThin() { return fetchThin; } /** * Set the thin-pack preference for fetch operation. Default setting is: * {@link #DEFAULT_FETCH_THIN} * * @param fetchThin * true when fetch should request thin-pack when possible; false * when it shouldn't * @see PackTransport */ public void setFetchThin(boolean fetchThin) { this.fetchThin = fetchThin; } /** * Whether fetch will verify if received objects are formatted correctly. * * @return true if fetch will verify received objects are formatted * correctly. Validating objects requires more CPU time on the * client side of the connection. */ public boolean isCheckFetchedObjects() { return getObjectChecker() != null; } /** * Configure if checking received objects is enabled * * @param check * true to enable checking received objects; false to assume all * received objects are valid. * @see #setObjectChecker(ObjectChecker) */ public void setCheckFetchedObjects(boolean check) { if (check && objectChecker == null) setObjectChecker(new ObjectChecker()); else if (!check && objectChecker != null) setObjectChecker(null); } /** * Get configured object checker for received objects * * @return configured object checker for received objects, or null. * @since 3.6 */ public ObjectChecker getObjectChecker() { return objectChecker; } /** * Set the object checker to verify each received object with * * @param impl * if non-null the object checking instance to verify each * received object with; null to disable object checking. * @since 3.6 */ public void setObjectChecker(ObjectChecker impl) { objectChecker = impl; } /** * Default setting is: * {@link org.eclipse.jgit.transport.RemoteConfig#DEFAULT_RECEIVE_PACK} * * @return remote executable providing receive-pack service for pack * transports. * @see PackTransport */ public String getOptionReceivePack() { return optionReceivePack; } /** * Set remote executable providing receive-pack service for pack transports. * Default setting is: * {@link org.eclipse.jgit.transport.RemoteConfig#DEFAULT_RECEIVE_PACK} * * @param optionReceivePack * remote executable, if null or empty default one is set; */ public void setOptionReceivePack(String optionReceivePack) { if (optionReceivePack != null && optionReceivePack.length() > 0) this.optionReceivePack = optionReceivePack; else this.optionReceivePack = RemoteConfig.DEFAULT_RECEIVE_PACK; } /** * Default setting is: {@value #DEFAULT_PUSH_THIN} * * @return true if push should produce thin-pack in pack transports * @see PackTransport */ public boolean isPushThin() { return pushThin; } /** * Set thin-pack preference for push operation. Default setting is: * {@value #DEFAULT_PUSH_THIN} * * @param pushThin * true when push should produce thin-pack in pack transports; * false when it shouldn't * @see PackTransport */ public void setPushThin(boolean pushThin) { this.pushThin = pushThin; } /** * Default setting is false. * * @return true if push requires all-or-nothing atomic behavior. * @since 4.2 */ public boolean isPushAtomic() { return pushAtomic; } /** * Request atomic push (all references succeed, or none do). *

* Server must also support atomic push. If the server does not support the * feature the push will abort without making changes. * * @param atomic * true when push should be an all-or-nothing operation. * @see PackTransport * @since 4.2 */ public void setPushAtomic(boolean atomic) { this.pushAtomic = atomic; } /** * Default setting is: {@value #DEFAULT_PUSH_USE_BITMAPS} * * @return true if push use bitmaps. * @since 6.4 */ public boolean isPushUseBitmaps() { return pushUseBitmaps; } /** * Set whether to use bitmaps for push. Default setting is: * {@value #DEFAULT_PUSH_USE_BITMAPS} * * @param useBitmaps * false to disable use of bitmaps for push, true otherwise. * @since 6.4 */ public void setPushUseBitmaps(boolean useBitmaps) { this.pushUseBitmaps = useBitmaps; } /** * Whether destination refs should be removed if they no longer exist at the * source repository. * * @return true if destination refs should be removed if they no longer * exist at the source repository. */ public boolean isRemoveDeletedRefs() { return removeDeletedRefs; } /** * Set whether or not to remove refs which no longer exist in the source. *

* If true, refs at the destination repository (local for fetch, remote for * push) are deleted if they no longer exist on the source side (remote for * fetch, local for push). *

* False by default, as this may cause data to become unreachable, and * eventually be deleted on the next GC. * * @param remove true to remove refs that no longer exist. */ public void setRemoveDeletedRefs(boolean remove) { removeDeletedRefs = remove; } /** * Get filter spec * * @return the last filter spec set with {@link #setFilterSpec(FilterSpec)}, * or {@link FilterSpec#NO_FILTER} if it was never invoked. * @since 5.4 */ public final FilterSpec getFilterSpec() { return filterSpec; } /** * Set filter spec * * @param filter * a new filter to use for this transport * @since 5.4 */ public final void setFilterSpec(@NonNull FilterSpec filter) { filterSpec = requireNonNull(filter); } /** * Retrieves the depth for a shallow clone. * * @return the depth, or {@code null} if none set * @since 6.3 */ public final Integer getDepth() { return depth; } /** * Limits fetching to the specified number of commits from the tip of each * remote branch history. * * @param depth * the depth * @since 6.3 */ public final void setDepth(int depth) { if (depth < 1) { throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); } this.depth = Integer.valueOf(depth); } /** * Limits fetching to the specified number of commits from the tip of each * remote branch history. * * @param depth * the depth, or {@code null} to unset the depth * @since 6.3 */ public final void setDepth(Integer depth) { if (depth != null && depth.intValue() < 1) { throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); } this.depth = depth; } /** * Get deepen-since * * @return the deepen-since for a shallow clone * @since 6.3 */ public final Instant getDeepenSince() { return deepenSince; } /** * Deepen or shorten the history of a shallow repository to include all reachable commits after a specified time. * * @param deepenSince the deepen-since. Must not be {@code null} * @since 6.3 */ public final void setDeepenSince(@NonNull Instant deepenSince) { this.deepenSince = deepenSince; } /** * Get list of deepen-not * * @return the list of deepen-not for a shallow clone * @since 6.3 */ public final List getDeepenNots() { return deepenNots; } /** * Deepen or shorten the history of a shallow repository to exclude commits reachable from a specified remote branch or tag. * * @param deepenNots the deepen-not. Must not be {@code null} * @since 6.3 */ public final void setDeepenNots(@NonNull List deepenNots) { this.deepenNots = deepenNots; } /** * Apply provided remote configuration on this transport. * * @param cfg * configuration to apply on this transport. */ public void applyConfig(RemoteConfig cfg) { setOptionUploadPack(cfg.getUploadPack()); setOptionReceivePack(cfg.getReceivePack()); setTagOpt(cfg.getTagOpt()); fetch = cfg.getFetchRefSpecs(); push = cfg.getPushRefSpecs(); timeout = cfg.getTimeout(); } /** * Whether push operation should just check for possible result and not * really update remote refs * * @return true if push operation should just check for possible result and * not really update remote refs, false otherwise - when push should * act normally. */ public boolean isDryRun() { return dryRun; } /** * Set dry run option for push operation. * * @param dryRun * true if push operation should just check for possible result * and not really update remote refs, false otherwise - when push * should act normally. */ public void setDryRun(boolean dryRun) { this.dryRun = dryRun; } /** * Get timeout (in seconds) before aborting an IO operation. * * @return timeout (in seconds) before aborting an IO operation. */ public int getTimeout() { return timeout; } /** * Set the timeout before willing to abort an IO call. * * @param seconds * number of seconds to wait (with no data transfer occurring) * before aborting an IO read or write operation with this * remote. */ public void setTimeout(int seconds) { timeout = seconds; } /** * Get the configuration used by the pack generator to make packs. * * If {@link #setPackConfig(PackConfig)} was previously given null a new * PackConfig is created on demand by this method using the source * repository's settings. * * @return the pack configuration. Never null. */ public PackConfig getPackConfig() { if (packConfig == null) packConfig = new PackConfig(local); return packConfig; } /** * Set the configuration used by the pack generator. * * @param pc * configuration controlling packing parameters. If null the * source repository's settings will be used. */ public void setPackConfig(PackConfig pc) { packConfig = pc; } /** * A credentials provider to assist with authentication connections.. * * @param credentialsProvider * the credentials provider, or null if there is none */ public void setCredentialsProvider(CredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; } /** * The configured credentials provider. * * @return the credentials provider, or null if no credentials provider is * associated with this transport. */ public CredentialsProvider getCredentialsProvider() { return credentialsProvider; } /** * Get the option strings associated with the push operation * * @return the option strings associated with the push operation * @since 4.5 */ public List getPushOptions() { return pushOptions; } /** * Sets the option strings associated with the push operation. * * @param pushOptions * null if push options are unsupported * @since 4.5 */ public void setPushOptions(List pushOptions) { this.pushOptions = pushOptions; } /** * Fetch objects and refs from the remote repository to the local one. *

* This is a utility function providing standard fetch behavior. Local * tracking refs associated with the remote repository are automatically * updated if this transport was created from a * {@link org.eclipse.jgit.transport.RemoteConfig} with fetch RefSpecs * defined. * * @param monitor * progress monitor to inform the user about our processing * activity. Must not be null. Use * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress * updates are not interesting or necessary. * @param toFetch * specification of refs to fetch locally. May be null or the * empty collection to use the specifications from the * RemoteConfig. May contains regular and negative * {@link RefSpec}s. Source for each regular RefSpec can't * be null. * @return information describing the tracking refs updated. * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support fetching * objects. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed or update specification was * incorrect. * @since 5.11 */ public FetchResult fetch(final ProgressMonitor monitor, Collection toFetch) throws NotSupportedException, TransportException { return fetch(monitor, toFetch, null); } /** * Fetch objects and refs from the remote repository to the local one. *

* This is a utility function providing standard fetch behavior. Local * tracking refs associated with the remote repository are automatically * updated if this transport was created from a * {@link org.eclipse.jgit.transport.RemoteConfig} with fetch RefSpecs * defined. * * @param monitor * progress monitor to inform the user about our processing * activity. Must not be null. Use * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress * updates are not interesting or necessary. * @param toFetch * specification of refs to fetch locally. May be null or the * empty collection to use the specifications from the * RemoteConfig. May contain regular and negative * {@link RefSpec}s. Source for each regular RefSpec can't * be null. * @param branch * the initial branch to check out when cloning the repository. * Can be specified as ref name (refs/heads/master), * branch name (master) or tag name * (v1.2.3). The default is to use the branch * pointed to by the cloned repository's HEAD and can be * requested by passing {@code null} or HEAD. * @return information describing the tracking refs updated. * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support fetching * objects. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed or update specification was * incorrect. * @since 5.11 */ public FetchResult fetch(final ProgressMonitor monitor, Collection toFetch, String branch) throws NotSupportedException, TransportException { if (toFetch == null || toFetch.isEmpty()) { // If the caller did not ask for anything use the defaults. // if (fetch.isEmpty()) throw new TransportException(JGitText.get().nothingToFetch); toFetch = fetch; } else if (!fetch.isEmpty()) { // If the caller asked for something specific without giving // us the local tracking branch see if we can update any of // the local tracking branches without incurring additional // object transfer overheads. // final Collection tmp = new ArrayList<>(toFetch); for (RefSpec requested : toFetch) { final String reqSrc = requested.getSource(); for (RefSpec configured : fetch) { final String cfgSrc = configured.getSource(); final String cfgDst = configured.getDestination(); if (cfgSrc.equals(reqSrc) && cfgDst != null) { tmp.add(configured); break; } } } toFetch = tmp; } final FetchResult result = new FetchResult(); new FetchProcess(this, toFetch).execute(monitor, result, branch); local.autoGC(monitor); return result; } /** * Push objects and refs from the local repository to the remote one. *

* This is a utility function providing standard push behavior. It updates * remote refs and send there necessary objects according to remote ref * update specification. After successful remote ref update, associated * locally stored tracking branch is updated if set up accordingly. Detailed * operation result is provided after execution. *

* For setting up remote ref update specification from ref spec, see helper * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using * directly {@link org.eclipse.jgit.transport.RemoteRefUpdate} for more * possibilities. *

* When {@link #isDryRun()} is true, result of this operation is just * estimation of real operation result, no real action is performed. * * @see RemoteRefUpdate * @param monitor * progress monitor to inform the user about our processing * activity. Must not be null. Use * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress * updates are not interesting or necessary. * @param toPush * specification of refs to push. May be null or the empty * collection to use the specifications from the RemoteConfig * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No * more than 1 RemoteRefUpdate with the same remoteName is * allowed. These objects are modified during this call. * @param out * output stream to write messages to * @return information about results of remote refs updates, tracking refs * updates and refs advertised by remote repository. * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support pushing * objects. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed at I/O or protocol level or * update specification was incorrect. * @since 3.0 */ public PushResult push(final ProgressMonitor monitor, Collection toPush, OutputStream out) throws NotSupportedException, TransportException { if (toPush == null || toPush.isEmpty()) { // If the caller did not ask for anything use the defaults. try { toPush = findRemoteRefUpdatesFor(push); } catch (final IOException e) { throw new TransportException(MessageFormat.format( JGitText.get().problemWithResolvingPushRefSpecsLocally, e.getMessage()), e); } if (toPush.isEmpty()) throw new TransportException(JGitText.get().nothingToPush); } PrePushHook prePush = null; if (local != null) { // Pushing will always have a local repository. But better safe than // sorry. prePush = Hooks.prePush(local, hookOutRedirect, hookErrRedirect); prePush.setRemoteLocation(uri.toString()); prePush.setRemoteName(remoteName); } PushProcess pushProcess = new PushProcess(this, toPush, prePush, out); return pushProcess.execute(monitor); } /** * Push objects and refs from the local repository to the remote one. *

* This is a utility function providing standard push behavior. It updates * remote refs and sends necessary objects according to remote ref update * specification. After successful remote ref update, associated locally * stored tracking branch is updated if set up accordingly. Detailed * operation result is provided after execution. *

* For setting up remote ref update specification from ref spec, see helper * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using * directly {@link org.eclipse.jgit.transport.RemoteRefUpdate} for more * possibilities. *

* When {@link #isDryRun()} is true, result of this operation is just * estimation of real operation result, no real action is performed. * * @see RemoteRefUpdate * @param monitor * progress monitor to inform the user about our processing * activity. Must not be null. Use * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress * updates are not interesting or necessary. * @param toPush * specification of refs to push. May be null or the empty * collection to use the specifications from the RemoteConfig * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No * more than 1 RemoteRefUpdate with the same remoteName is * allowed. These objects are modified during this call. * @return information about results of remote refs updates, tracking refs * updates and refs advertised by remote repository. * @throws org.eclipse.jgit.errors.NotSupportedException * this transport implementation does not support pushing * objects. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established or object * copying (if necessary) failed at I/O or protocol level or * update specification was incorrect. */ public PushResult push(final ProgressMonitor monitor, Collection toPush) throws NotSupportedException, TransportException { return push(monitor, toPush, null); } /** * Convert push remote refs update specification from * {@link org.eclipse.jgit.transport.RefSpec} form to * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands * wildcards by matching source part to local refs. expectedOldObjectId in * RemoteRefUpdate is always set as null. Tracking branch is configured if * RefSpec destination matches source of any fetch ref spec for this * transport remote configuration. *

* Conversion is performed for context of this transport (database, fetch * specifications). * * @param specs * collection of RefSpec to convert. * @return collection of set up * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. * @throws java.io.IOException * when problem occurred during conversion or specification set * up: most probably, missing objects or refs. */ public Collection findRemoteRefUpdatesFor( final Collection specs) throws IOException { return findRemoteRefUpdatesFor(local, specs, Collections.emptyMap(), fetch); } /** * Convert push remote refs update specification from * {@link org.eclipse.jgit.transport.RefSpec} form to * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. Conversion expands * wildcards by matching source part to local refs. expectedOldObjectId in * RemoteRefUpdate is set according to leases. Tracking branch is configured * if RefSpec destination matches source of any fetch ref spec for this * transport remote configuration. *

* Conversion is performed for context of this transport (database, fetch * specifications). * * @param specs * collection of RefSpec to convert. * @param leases * map from ref to lease (containing expected old object id) * @return collection of set up * {@link org.eclipse.jgit.transport.RemoteRefUpdate}. * @throws java.io.IOException * when problem occurred during conversion or specification set * up: most probably, missing objects or refs. * @since 4.7 */ public Collection findRemoteRefUpdatesFor( final Collection specs, final Map leases) throws IOException { return findRemoteRefUpdatesFor(local, specs, leases, fetch); } /** * Begins a new connection for fetching from the remote repository. *

* If the transport has no local repository, the fetch connection can only * be used for reading remote refs. * * @return a fresh connection to fetch from the remote repository. * @throws org.eclipse.jgit.errors.NotSupportedException * the implementation does not support fetching. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established. */ public abstract FetchConnection openFetch() throws NotSupportedException, TransportException; /** * Begins a new connection for fetching from the remote repository. *

* If the transport has no local repository, the fetch connection can only * be used for reading remote refs. *

*

* If the server supports git protocol V2, the {@link RefSpec}s and the * additional patterns, if any, are used to restrict the server's ref * advertisement to matching refs only. *

*

* Transports that want to support git protocol V2 must override * this; the default implementation ignores its arguments and calls * {@link #openFetch()}. *

* * @param refSpecs * that will be fetched via * {@link FetchConnection#fetch(ProgressMonitor, Collection, java.util.Set, OutputStream)} later * @param additionalPatterns * that will be set as ref prefixes if the server supports git * protocol V2; {@code null} values are ignored * * @return a fresh connection to fetch from the remote repository. * @throws org.eclipse.jgit.errors.NotSupportedException * the implementation does not support fetching. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established. * @since 5.11 */ public FetchConnection openFetch(Collection refSpecs, String... additionalPatterns) throws NotSupportedException, TransportException { return openFetch(); } /** * Begins a new connection for pushing into the remote repository. * * @return a fresh connection to push into the remote repository. * @throws org.eclipse.jgit.errors.NotSupportedException * the implementation does not support pushing. * @throws org.eclipse.jgit.errors.TransportException * the remote connection could not be established */ public abstract PushConnection openPush() throws NotSupportedException, TransportException; /** * {@inheritDoc} *

* Close any resources used by this transport. *

* If the remote repository is contacted by a network socket this method * must close that network socket, disconnecting the two peers. If the * remote repository is actually local (same system) this method must close * any open file handles used to read the "remote" repository. *

* {@code AutoClosable.close()} declares that it throws {@link Exception}. * Implementers shouldn't throw checked exceptions. This override narrows * the signature to prevent them from doing so. */ @Override public abstract void close(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10906 Content-Disposition: inline; filename="TransportAmazonS3.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "58232a7dde17c21cb0f1664679aa00fac03dabfa" /* * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLConnection; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; /** * Transport over the non-Git aware Amazon S3 protocol. *

* This transport communicates with the Amazon S3 servers (a non-free commercial * hosting service that users must subscribe to). Some users may find transport * to and from S3 to be a useful backup service. *

* The transport does not require any specialized Git support on the remote * (server side) repository, as Amazon does not provide any such support. * Repository files are retrieved directly through the S3 API, which uses * extended HTTP/1.1 semantics. This make it possible to read or write Git data * from a remote repository that is stored on S3. *

* Unlike the HTTP variant (see * {@link org.eclipse.jgit.transport.TransportHttp}) we rely upon being able to * list objects in a bucket, as the S3 API supports this function. By listing * the bucket contents we can avoid relying on objects/info/packs * or info/refs in the remote repository. *

* Concurrent pushing over this transport is not supported. Multiple concurrent * push operations may cause confusion in the repository state. * * @see WalkFetchConnection * @see WalkPushConnection */ public class TransportAmazonS3 extends HttpTransport implements WalkTransport { static final String S3_SCHEME = "amazon-s3"; //$NON-NLS-1$ static final TransportProtocol PROTO_S3 = new TransportProtocol() { @Override public String getName() { return "Amazon S3"; //$NON-NLS-1$ } @Override public Set getSchemes() { return Collections.singleton(S3_SCHEME); } @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.HOST, URIishField.PATH)); } @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.PASS)); } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportAmazonS3(local, uri); } }; /** User information necessary to connect to S3. */ final AmazonS3 s3; /** Bucket the remote repository is stored in. */ final String bucket; /** * Key prefix which all objects related to the repository start with. *

* The prefix does not start with "/". *

* The prefix does not end with "/". The trailing slash is stripped during * the constructor if a trailing slash was supplied in the URIish. *

* All files within the remote repository start with * keyPrefix + "/". */ private final String keyPrefix; TransportAmazonS3(final Repository local, final URIish uri) throws NotSupportedException { super(local, uri); Properties props = loadProperties(); File directory = local.getDirectory(); if (!props.containsKey("tmpdir") && directory != null) //$NON-NLS-1$ props.put("tmpdir", directory.getPath()); //$NON-NLS-1$ s3 = new AmazonS3(props); bucket = uri.getHost(); String p = uri.getPath(); if (p.startsWith("/")) //$NON-NLS-1$ p = p.substring(1); if (p.endsWith("/")) //$NON-NLS-1$ p = p.substring(0, p.length() - 1); keyPrefix = p; } private Properties loadProperties() throws NotSupportedException { if (local.getDirectory() != null) { File propsFile = new File(local.getDirectory(), uri.getUser()); if (propsFile.isFile()) return loadPropertiesFile(propsFile); } File propsFile = new File(local.getFS().userHome(), uri.getUser()); if (propsFile.isFile()) return loadPropertiesFile(propsFile); Properties props = new Properties(); String user = uri.getUser(); String pass = uri.getPass(); if (user != null && pass != null) { props.setProperty("accesskey", user); //$NON-NLS-1$ props.setProperty("secretkey", pass); //$NON-NLS-1$ } else throw new NotSupportedException(MessageFormat.format( JGitText.get().cannotReadFile, propsFile)); return props; } private static Properties loadPropertiesFile(File propsFile) throws NotSupportedException { try { return AmazonS3.properties(propsFile); } catch (IOException e) { throw new NotSupportedException(MessageFormat.format( JGitText.get().cannotReadFile, propsFile), e); } } @Override public FetchConnection openFetch() throws TransportException { final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); //$NON-NLS-1$ final WalkFetchConnection r = new WalkFetchConnection(this, c); r.available(c.readAdvertisedRefs()); return r; } @Override public PushConnection openPush() throws TransportException { final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); //$NON-NLS-1$ final WalkPushConnection r = new WalkPushConnection(this, c); r.available(c.readAdvertisedRefs()); return r; } @Override public void close() { // No explicit connections are maintained. } class DatabaseS3 extends WalkRemoteObjectDatabase { private final String bucketName; private final String objectsKey; DatabaseS3(final String b, final String o) { bucketName = b; objectsKey = o; } private String resolveKey(String subpath) { if (subpath.endsWith("/")) //$NON-NLS-1$ subpath = subpath.substring(0, subpath.length() - 1); String k = objectsKey; while (subpath.startsWith(ROOT_DIR)) { k = k.substring(0, k.lastIndexOf('/')); subpath = subpath.substring(3); } return k + "/" + subpath; //$NON-NLS-1$ } @Override URIish getURI() { URIish u = new URIish(); u = u.setScheme(S3_SCHEME); u = u.setHost(bucketName); u = u.setPath("/" + objectsKey); //$NON-NLS-1$ return u; } @Override Collection getAlternates() throws IOException { try { return readAlternates(Constants.INFO_ALTERNATES); } catch (FileNotFoundException err) { // Fall through. } return null; } @Override WalkRemoteObjectDatabase openAlternate(String location) throws IOException { return new DatabaseS3(bucketName, resolveKey(location)); } @Override Collection getPackNames() throws IOException { // s3.list returns most recently modified packs first. // These are the packs most likely to contain missing refs. final List packList = s3.list(bucket, resolveKey("pack")); //$NON-NLS-1$ final HashSet have = new HashSet<>(); have.addAll(packList); final Collection packs = new ArrayList<>(); for (String n : packList) { if (!n.startsWith("pack-") || !n.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$ continue; final String in = n.substring(0, n.length() - 5) + ".idx"; //$NON-NLS-1$ if (have.contains(in)) packs.add(n); } return packs; } @Override FileStream open(String path) throws IOException { final URLConnection c = s3.get(bucket, resolveKey(path)); final InputStream raw = c.getInputStream(); final InputStream in = s3.decrypt(c); final int len = c.getContentLength(); return new FileStream(in, raw == in ? len : -1); } @Override void deleteFile(String path) throws IOException { s3.delete(bucket, resolveKey(path)); } @Override OutputStream writeFile(final String path, final ProgressMonitor monitor, final String monitorTask) throws IOException { return s3.beginPut(bucket, resolveKey(path), monitor, monitorTask); } @Override void writeFile(String path, byte[] data) throws IOException { s3.put(bucket, resolveKey(path), data); } Map readAdvertisedRefs() throws TransportException { final TreeMap avail = new TreeMap<>(); readPackedRefs(avail); readLooseRefs(avail); readRef(avail, Constants.HEAD); return avail; } private void readLooseRefs(TreeMap avail) throws TransportException { try { for (final String n : s3.list(bucket, resolveKey(ROOT_DIR + "refs"))) //$NON-NLS-1$ readRef(avail, "refs/" + n); //$NON-NLS-1$ } catch (IOException e) { throw new TransportException(getURI(), JGitText.get().cannotListRefs, e); } } private Ref readRef(TreeMap avail, String rn) throws TransportException { final String s; String ref = ROOT_DIR + rn; try { try (BufferedReader br = openReader(ref)) { s = br.readLine(); } } catch (FileNotFoundException noRef) { return null; } catch (IOException err) { throw new TransportException(getURI(), MessageFormat.format( JGitText.get().transportExceptionReadRef, ref), err); } if (s == null) throw new TransportException(getURI(), MessageFormat.format(JGitText.get().transportExceptionEmptyRef, rn)); if (s.startsWith("ref: ")) { //$NON-NLS-1$ final String target = s.substring("ref: ".length()); //$NON-NLS-1$ Ref r = avail.get(target); if (r == null) r = readRef(avail, target); if (r == null) r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null); r = new SymbolicRef(rn, r); avail.put(r.getName(), r); return r; } if (ObjectId.isId(s)) { final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(rn)), rn, ObjectId.fromString(s)); avail.put(r.getName(), r); return r; } throw new TransportException(getURI(), MessageFormat.format(JGitText.get().transportExceptionBadRef, rn, s)); } private Storage loose(Ref r) { if (r != null && r.getStorage() == Storage.PACKED) return Storage.LOOSE_PACKED; return Storage.LOOSE; } @Override void close() { // We do not maintain persistent connections. } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1013 Content-Disposition: inline; filename="TransportBundle.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "9205f0f40ed0adb323969c388bded66332d80927" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Marker interface for transports that supports fetching from a git bundle * (sneaker-net object transport). *

* Push support for a bundle is complex, as one does not have a peer to * communicate with to decide what the peer already knows. So push is not * supported by the bundle transport. */ public interface TransportBundle extends PackTransport { /** * Bundle signature */ String V2_BUNDLE_SIGNATURE = "# v2 git bundle"; //$NON-NLS-1$ } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3959 Content-Disposition: inline; filename="TransportBundleFile.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "0f84f7e21ec188db9b5203f50a7f24d9fc37d0d2" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; class TransportBundleFile extends Transport implements TransportBundle { static final TransportProtocol PROTO_BUNDLE = new TransportProtocol() { private final String[] schemeNames = { "bundle", "file" }; //$NON-NLS-1$ //$NON-NLS-2$ private final Set schemeSet = Collections .unmodifiableSet(new LinkedHashSet<>(Arrays .asList(schemeNames))); @Override public String getName() { return JGitText.get().transportProtoBundleFile; } @Override public Set getSchemes() { return schemeSet; } @Override public boolean canHandle(URIish uri, Repository local, String remoteName) { if (uri.getPath() == null || uri.getPort() > 0 || uri.getUser() != null || uri.getPass() != null || uri.getHost() != null || (uri.getScheme() != null && !getSchemes().contains(uri.getScheme()))) return false; return true; } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException, TransportException { if ("bundle".equals(uri.getScheme())) { //$NON-NLS-1$ File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$ return new TransportBundleFile(local, uri, path); } // This is an ambiguous reference, it could be a bundle file // or it could be a Git repository. Allow TransportLocal to // resolve the path and figure out which type it is by testing // the target. // return TransportLocal.PROTO_LOCAL.open(uri, local, remoteName); } @Override public Transport open(URIish uri) throws NotSupportedException, TransportException { if ("bundle".equals(uri.getScheme())) { //$NON-NLS-1$ File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$ return new TransportBundleFile(uri, path); } return TransportLocal.PROTO_LOCAL.open(uri); } }; private final File bundle; TransportBundleFile(Repository local, URIish uri, File bundlePath) { super(local, uri); bundle = bundlePath; } /** * Constructor for TransportBundleFile. * * @param uri * a {@link org.eclipse.jgit.transport.URIish} object. * @param bundlePath * transport bundle path */ public TransportBundleFile(URIish uri, File bundlePath) { super(uri); bundle = bundlePath; } @Override public FetchConnection openFetch() throws NotSupportedException, TransportException { final InputStream src; try { src = new FileInputStream(bundle); } catch (FileNotFoundException err) { TransportException te = new TransportException(uri, JGitText.get().notFound); te.initCause(err); throw te; } return new BundleFetchConnection(this, src); } @Override public PushConnection openPush() throws NotSupportedException { throw new NotSupportedException( JGitText.get().pushIsNotSupportedForBundleTransport); } @Override public void close() { // Resources must be established per-connection. } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2711 Content-Disposition: inline; filename="TransportBundleStream.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "0366bf3aa257e050c7ac36741682953d3ce5cbb8" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.InputStream; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; /** * Single shot fetch from a streamed Git bundle. *

* The bundle is read from an unbuffered input stream, which limits the * transport to opening at most one FetchConnection before needing to recreate * the transport instance. */ public class TransportBundleStream extends Transport implements TransportBundle { private InputStream src; /** * Create a new transport to fetch objects from a streamed bundle. *

* The stream can be unbuffered (buffering is automatically provided * internally to smooth out short reads) and unpositionable (the stream is * read from only once, sequentially). *

* When the FetchConnection or the this instance is closed the supplied * input stream is also automatically closed. This frees callers from * needing to keep track of the supplied stream. * * @param db * repository the fetched objects will be loaded into. * @param uri * symbolic name of the source of the stream. The URI can * reference a non-existent resource. It is used only for * exception reporting. * @param in * the stream to read the bundle from. */ public TransportBundleStream(final Repository db, final URIish uri, final InputStream in) { super(db, uri); src = in; } @Override public FetchConnection openFetch() throws TransportException { if (src == null) throw new TransportException(uri, JGitText.get().onlyOneFetchSupported); try { return new BundleFetchConnection(this, src); } finally { src = null; } } @Override public PushConnection openPush() throws NotSupportedException { throw new NotSupportedException( JGitText.get().pushIsNotSupportedForBundleTransport); } @Override public void close() { if (src != null) { try { src.close(); } catch (IOException err) { // Ignore a close error. } finally { src = null; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6782 Content-Disposition: inline; filename="TransportGitAnon.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "43b3f6cce0ab7da60660e4489167a3db77dbe89f" /* * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Set; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; /** * Transport through a git-daemon waiting for anonymous TCP connections. *

* This transport supports the git:// protocol, usually run on * the IANA registered port 9418. It is a popular means for distributing open * source projects, as there are no authentication or authorization overheads. */ class TransportGitAnon extends TcpTransport implements PackTransport { static final int GIT_PORT = Daemon.DEFAULT_PORT; static final TransportProtocol PROTO_GIT = new TransportProtocol() { @Override public String getName() { return JGitText.get().transportProtoGitAnon; } @Override public Set getSchemes() { return Collections.singleton("git"); //$NON-NLS-1$ } @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.PORT)); } @Override public int getDefaultPort() { return GIT_PORT; } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportGitAnon(local, uri); } @Override public Transport open(URIish uri) throws NotSupportedException, TransportException { return new TransportGitAnon(uri); } }; TransportGitAnon(Repository local, URIish uri) { super(local, uri); } TransportGitAnon(URIish uri) { super(uri); } @Override public FetchConnection openFetch() throws TransportException { return new TcpFetchConnection(); } @Override public FetchConnection openFetch(Collection refSpecs, String... additionalPatterns) throws NotSupportedException, TransportException { return new TcpFetchConnection(refSpecs, additionalPatterns); } @Override public PushConnection openPush() throws TransportException { return new TcpPushConnection(); } @Override public void close() { // Resources must be established per-connection. } Socket openConnection() throws TransportException { final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0; final int port = uri.getPort() > 0 ? uri.getPort() : GIT_PORT; @SuppressWarnings("resource") // Closed by the caller final Socket s = new Socket(); try { final InetAddress host = InetAddress.getByName(uri.getHost()); s.connect(new InetSocketAddress(host, port), tms); } catch (IOException c) { try { s.close(); } catch (IOException closeErr) { // ignore a failure during close, we're already failing } if (c instanceof UnknownHostException) throw new TransportException(uri, JGitText.get().unknownHost); if (c instanceof ConnectException) throw new TransportException(uri, c.getMessage()); throw new TransportException(uri, c.getMessage(), c); } return s; } void service(String name, PacketLineOut pckOut, TransferConfig.ProtocolVersion gitProtocol) throws IOException { final StringBuilder cmd = new StringBuilder(); cmd.append(name); cmd.append(' '); cmd.append(uri.getPath()); cmd.append('\0'); cmd.append("host="); //$NON-NLS-1$ cmd.append(uri.getHost()); if (uri.getPort() > 0 && uri.getPort() != GIT_PORT) { cmd.append(":"); //$NON-NLS-1$ cmd.append(uri.getPort()); } cmd.append('\0'); if (TransferConfig.ProtocolVersion.V2.equals(gitProtocol)) { cmd.append('\0'); cmd.append(GitProtocolConstants.VERSION_2_REQUEST); cmd.append('\0'); } pckOut.writeString(cmd.toString()); pckOut.flush(); } class TcpFetchConnection extends BasePackFetchConnection { private Socket sock; TcpFetchConnection() throws TransportException { this(Collections.emptyList()); } TcpFetchConnection(Collection refSpecs, String... additionalPatterns) throws TransportException { super(TransportGitAnon.this); sock = openConnection(); try { InputStream sIn = sock.getInputStream(); OutputStream sOut = sock.getOutputStream(); sIn = new BufferedInputStream(sIn); sOut = new BufferedOutputStream(sOut); init(sIn, sOut); TransferConfig.ProtocolVersion gitProtocol = protocol; if (gitProtocol == null) { gitProtocol = TransferConfig.ProtocolVersion.V2; } service("git-upload-pack", pckOut, gitProtocol); //$NON-NLS-1$ } catch (IOException err) { close(); throw new TransportException(uri, JGitText.get().remoteHungUpUnexpectedly, err); } if (!readAdvertisedRefs()) { lsRefs(refSpecs, additionalPatterns); } } @Override public void close() { super.close(); if (sock != null) { try { sock.close(); } catch (IOException err) { // Ignore errors during close. } finally { sock = null; } } } } class TcpPushConnection extends BasePackPushConnection { private Socket sock; TcpPushConnection() throws TransportException { super(TransportGitAnon.this); sock = openConnection(); try { InputStream sIn = sock.getInputStream(); OutputStream sOut = sock.getOutputStream(); sIn = new BufferedInputStream(sIn); sOut = new BufferedOutputStream(sOut); init(sIn, sOut); service("git-receive-pack", pckOut, null); //$NON-NLS-1$ } catch (IOException err) { close(); throw new TransportException(uri, JGitText.get().remoteHungUpUnexpectedly, err); } readAdvertisedRefs(); } @Override public void close() { super.close(); if (sock != null) { try { sock.close(); } catch (IOException err) { // Ignore errors during close. } finally { sock = null; } } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11665 Content-Disposition: inline; filename="TransportGitSsh.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "f77b04110d20a0b71d4feaf78af2b5524bd1fdb9" /* * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.QuotedString; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; /** * Transport through an SSH tunnel. *

* The SSH transport requires the remote side to have Git installed, as the * transport logs into the remote system and executes a Git helper program on * the remote side to read (or write) the remote repository's files. *

* This transport does not support direct SCP style of copying files, as it * assumes there are Git specific smarts on the remote side to perform object * enumeration, save file modification and hook execution. */ public class TransportGitSsh extends SshTransport implements PackTransport { private static final String EXT = "ext"; //$NON-NLS-1$ static final TransportProtocol PROTO_SSH = new TransportProtocol() { private final String[] schemeNames = { "ssh", "ssh+git", "git+ssh" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ private final Set schemeSet = Collections .unmodifiableSet(new LinkedHashSet<>(Arrays .asList(schemeNames))); @Override public String getName() { return JGitText.get().transportProtoSSH; } @Override public Set getSchemes() { return schemeSet; } @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } @Override public int getDefaultPort() { return 22; } @Override public boolean canHandle(URIish uri, Repository local, String remoteName) { if (uri.getScheme() == null) { // scp-style URI "host:path" does not have scheme. return uri.getHost() != null && uri.getPath() != null && uri.getHost().length() != 0 && uri.getPath().length() != 0; } return super.canHandle(uri, local, remoteName); } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportGitSsh(local, uri); } @Override public Transport open(URIish uri) throws NotSupportedException, TransportException { return new TransportGitSsh(uri); } }; TransportGitSsh(Repository local, URIish uri) { super(local, uri); initSshSessionFactory(); } TransportGitSsh(URIish uri) { super(uri); initSshSessionFactory(); } private void initSshSessionFactory() { if (useExtSession()) { setSshSessionFactory(new SshSessionFactory() { @Override public RemoteSession getSession(URIish uri2, CredentialsProvider credentialsProvider, FS fs, int tms) throws TransportException { return new ExtSession(); } @Override public String getType() { return EXT; } }); } } @Override public FetchConnection openFetch() throws TransportException { return new SshFetchConnection(); } @Override public FetchConnection openFetch(Collection refSpecs, String... additionalPatterns) throws NotSupportedException, TransportException { return new SshFetchConnection(refSpecs, additionalPatterns); } @Override public PushConnection openPush() throws TransportException { return new SshPushConnection(); } String commandFor(String exe) { String path = uri.getPath(); if (uri.getScheme() != null && uri.getPath().startsWith("/~")) //$NON-NLS-1$ path = uri.getPath().substring(1); final StringBuilder cmd = new StringBuilder(); cmd.append(exe); cmd.append(' '); cmd.append(QuotedString.BOURNE.quote(path)); return cmd.toString(); } void checkExecFailure(int status, String exe, String why) throws TransportException { if (status == 127) { IOException cause = null; if (why != null && why.length() > 0) cause = new IOException(why); throw new TransportException(uri, MessageFormat.format( JGitText.get().cannotExecute, commandFor(exe)), cause); } } NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf, String why) { if (why == null || why.length() == 0) return nf; String path = uri.getPath(); if (uri.getScheme() != null && uri.getPath().startsWith("/~")) //$NON-NLS-1$ path = uri.getPath().substring(1); final StringBuilder pfx = new StringBuilder(); pfx.append("fatal: "); //$NON-NLS-1$ pfx.append(QuotedString.BOURNE.quote(path)); pfx.append(": "); //$NON-NLS-1$ if (why.startsWith(pfx.toString())) why = why.substring(pfx.length()); return new NoRemoteRepositoryException(uri, why); } private static boolean useExtSession() { return SystemReader.getInstance().getenv("GIT_SSH") != null; //$NON-NLS-1$ } private class ExtSession implements RemoteSession2 { @Override public Process exec(String command, int timeout) throws TransportException { return exec(command, null, timeout); } @Override public Process exec(String command, Map environment, int timeout) throws TransportException { String ssh = SystemReader.getInstance().getenv("GIT_SSH"); //$NON-NLS-1$ boolean putty = ssh.toLowerCase(Locale.ROOT).contains("plink"); //$NON-NLS-1$ List args = new ArrayList<>(); args.add(ssh); if (putty && !ssh.toLowerCase(Locale.ROOT) .contains("tortoiseplink")) {//$NON-NLS-1$ args.add("-batch"); //$NON-NLS-1$ } if (0 < getURI().getPort()) { args.add(putty ? "-P" : "-p"); //$NON-NLS-1$ //$NON-NLS-2$ args.add(String.valueOf(getURI().getPort())); } if (getURI().getUser() != null) { args.add(getURI().getUser() + "@" + getURI().getHost()); //$NON-NLS-1$ } else { args.add(getURI().getHost()); } args.add(command); ProcessBuilder pb = createProcess(args, environment); try { return pb.start(); } catch (IOException err) { throw new TransportException(err.getMessage(), err); } } private ProcessBuilder createProcess(List args, Map environment) { ProcessBuilder pb = new ProcessBuilder(); pb.command(args); if (environment != null) { pb.environment().putAll(environment); } File directory = local != null ? local.getDirectory() : null; if (directory != null) { pb.environment().put(Constants.GIT_DIR_KEY, directory.getPath()); } File commonDirectory = local != null ? local.getCommonDirectory() : null; if (commonDirectory != null) { pb.environment().put(Constants.GIT_COMMON_DIR_KEY, commonDirectory.getPath()); } return pb; } @Override public void disconnect() { // Nothing to do } } class SshFetchConnection extends BasePackFetchConnection { private final Process process; private StreamCopyThread errorThread; SshFetchConnection() throws TransportException { this(Collections.emptyList()); } SshFetchConnection(Collection refSpecs, String... additionalPatterns) throws TransportException { super(TransportGitSsh.this); try { RemoteSession session = getSession(); TransferConfig.ProtocolVersion gitProtocol = protocol; if (gitProtocol == null) { gitProtocol = TransferConfig.ProtocolVersion.V2; } if (session instanceof RemoteSession2 && TransferConfig.ProtocolVersion.V2 .equals(gitProtocol)) { process = ((RemoteSession2) session).exec( commandFor(getOptionUploadPack()), Collections .singletonMap( GitProtocolConstants.PROTOCOL_ENVIRONMENT_VARIABLE, GitProtocolConstants.VERSION_2_REQUEST), getTimeout()); } else { process = session.exec(commandFor(getOptionUploadPack()), getTimeout()); } final MessageWriter msg = new MessageWriter(); setMessageWriter(msg); final InputStream upErr = process.getErrorStream(); errorThread = new StreamCopyThread(upErr, msg.getRawStream()); errorThread.start(); init(process.getInputStream(), process.getOutputStream()); } catch (TransportException err) { close(); throw err; } catch (Throwable err) { close(); throw new TransportException(uri, JGitText.get().remoteHungUpUnexpectedly, err); } try { if (!readAdvertisedRefs()) { lsRefs(refSpecs, additionalPatterns); } } catch (NoRemoteRepositoryException notFound) { final String msgs = getMessages(); checkExecFailure(process.exitValue(), getOptionUploadPack(), msgs); throw cleanNotFound(notFound, msgs); } } @Override public void close() { endOut(); if (process != null) { process.destroy(); } if (errorThread != null) { try { errorThread.halt(); } catch (InterruptedException e) { // Stop waiting and return anyway. } finally { errorThread = null; } } super.close(); } } class SshPushConnection extends BasePackPushConnection { private final Process process; private StreamCopyThread errorThread; SshPushConnection() throws TransportException { super(TransportGitSsh.this); try { process = getSession().exec(commandFor(getOptionReceivePack()), getTimeout()); final MessageWriter msg = new MessageWriter(); setMessageWriter(msg); final InputStream rpErr = process.getErrorStream(); errorThread = new StreamCopyThread(rpErr, msg.getRawStream()); errorThread.start(); init(process.getInputStream(), process.getOutputStream()); } catch (TransportException err) { try { close(); } catch (Exception e) { // ignore } throw err; } catch (Throwable err) { try { close(); } catch (Exception e) { // ignore } throw new TransportException(uri, JGitText.get().remoteHungUpUnexpectedly, err); } try { readAdvertisedRefs(); } catch (NoRemoteRepositoryException notFound) { final String msgs = getMessages(); checkExecFailure(process.exitValue(), getOptionReceivePack(), msgs); throw cleanNotFound(notFound, msgs); } } @Override public void close() { endOut(); if (process != null) { process.destroy(); } if (errorThread != null) { try { errorThread.halt(); } catch (InterruptedException e) { // Stop waiting and return anyway. } finally { errorThread = null; } } super.close(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 63640 Content-Disposition: inline; filename="TransportHttp.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "a65d0b756c1b1fce77332e01e01016566401f39d" /* * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, Shawn O. Pearce * Copyright (C) 2013, Matthias Sohn * Copyright (C) 2017, 2020 Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES; import static org.eclipse.jgit.lib.Constants.INFO_HTTP_ALTERNATES; import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP; import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT; import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; import static org.eclipse.jgit.util.HttpSupport.HDR_COOKIE; import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION; import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA; import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE; import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE2; import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT; import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE; import static org.eclipse.jgit.util.HttpSupport.METHOD_GET; import static org.eclipse.jgit.util.HttpSupport.METHOD_POST; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpCookie; import java.net.MalformedURLException; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.nio.file.InvalidPathException; import java.security.GeneralSecurityException; import java.security.cert.CertPathBuilderException; import java.security.cert.CertPathValidatorException; import java.security.cert.CertificateException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.net.ssl.SSLHandshakeException; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.RefDirectory; import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile; import org.eclipse.jgit.internal.transport.http.NetscapeCookieFileCache; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode; import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.transport.http.HttpConnectionFactory; import org.eclipse.jgit.transport.http.HttpConnectionFactory2; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.io.DisabledOutputStream; import org.eclipse.jgit.util.io.UnionInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Transport over HTTP and FTP protocols. *

* If the transport is using HTTP and the remote HTTP service is Git-aware * (speaks the "smart-http protocol") this client will automatically take * advantage of the additional Git-specific HTTP extensions. If the remote * service does not support these extensions, the client will degrade to direct * file fetching. *

* If the remote (server side) repository does not have the specialized Git * support, object files are retrieved directly through standard HTTP GET (or * binary FTP GET) requests. This make it easy to serve a Git repository through * a standard web host provider that does not offer specific support for Git. * * @see WalkFetchConnection */ public class TransportHttp extends HttpTransport implements WalkTransport, PackTransport { private static final Logger LOG = LoggerFactory .getLogger(TransportHttp.class); private static final String SVC_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$ private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$ private static final byte[] VERSION = "version" //$NON-NLS-1$ .getBytes(StandardCharsets.US_ASCII); /** * Accept-Encoding header in the HTTP request * (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). * * @since 4.6 */ public enum AcceptEncoding { /** * Do not specify an Accept-Encoding header. In most servers this * results in the content being transmitted as-is. */ UNSPECIFIED, /** * Accept gzip content encoding. */ GZIP } static final TransportProtocol PROTO_HTTP = new TransportProtocol() { private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$ private final Set schemeSet = Collections .unmodifiableSet(new LinkedHashSet<>(Arrays .asList(schemeNames))); @Override public String getName() { return JGitText.get().transportProtoHTTP; } @Override public Set getSchemes() { return schemeSet; } @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } @Override public int getDefaultPort() { return 80; } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportHttp(local, uri); } @Override public Transport open(URIish uri) throws NotSupportedException { return new TransportHttp(uri); } }; static final TransportProtocol PROTO_FTP = new TransportProtocol() { @Override public String getName() { return JGitText.get().transportProtoFTP; } @Override public Set getSchemes() { return Collections.singleton("ftp"); //$NON-NLS-1$ } @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } @Override public int getDefaultPort() { return 21; } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportHttp(local, uri); } }; /** * The current URI we're talking to. The inherited (final) field * {@link #uri} stores the original URI; {@code currentUri} may be different * after redirects. */ private URIish currentUri; private URL baseUrl; private URL objectsUrl; private final HttpConfig http; private final ProxySelector proxySelector; private boolean useSmartHttp = true; private HttpAuthMethod authMethod = HttpAuthMethod.Type.NONE.method(null); private Map headers; private boolean sslVerify; private boolean sslFailure = false; private HttpConnectionFactory factory; private HttpConnectionFactory2.GitSession gitSession; private boolean factoryUsed; /** * All stored cookies bound to this repo (independent of the baseUrl) */ private final NetscapeCookieFile cookieFile; /** * The cookies to be sent with each request to the given {@link #baseUrl}. * Filtered view on top of {@link #cookieFile} where only cookies which * apply to the current url are left. This set needs to be filtered for * expired entries each time prior to sending them. */ private final Set relevantCookies; TransportHttp(Repository local, URIish uri) throws NotSupportedException { super(local, uri); setURI(uri); http = new HttpConfig(local.getConfig(), uri); proxySelector = ProxySelector.getDefault(); sslVerify = http.isSslVerify(); cookieFile = getCookieFileFromConfig(http); relevantCookies = filterCookies(cookieFile, baseUrl); factory = HttpTransport.getConnectionFactory(); } private URL toURL(URIish urish) throws MalformedURLException { String uriString = urish.toString(); if (!uriString.endsWith("/")) { //$NON-NLS-1$ uriString += '/'; } return new URL(uriString); } /** * Set uri a {@link org.eclipse.jgit.transport.URIish} object. * * @param uri * a {@link org.eclipse.jgit.transport.URIish} object. * @throws org.eclipse.jgit.errors.NotSupportedException * if URI is not supported by JGit * @since 4.9 */ protected void setURI(URIish uri) throws NotSupportedException { try { currentUri = uri; baseUrl = toURL(uri); objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$ } catch (MalformedURLException e) { throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e); } } /** * Create a minimal HTTP transport with default configuration values. * * @param uri * URI to create a HTTP transport for * @throws NotSupportedException * if URI is not supported by JGit */ TransportHttp(URIish uri) throws NotSupportedException { super(uri); setURI(uri); http = new HttpConfig(uri); proxySelector = ProxySelector.getDefault(); sslVerify = http.isSslVerify(); cookieFile = getCookieFileFromConfig(http); relevantCookies = filterCookies(cookieFile, baseUrl); factory = HttpTransport.getConnectionFactory(); } /** * Toggle whether or not smart HTTP transport should be used. *

* This flag exists primarily to support backwards compatibility testing * within a testing framework, there is no need to modify it in most * applications. * * @param on * if {@code true} (default), smart HTTP is enabled. */ public void setUseSmartHttp(boolean on) { useSmartHttp = on; } @SuppressWarnings("resource") // Closed by caller private FetchConnection getConnection(HttpConnection c, InputStream in, String service, Collection refSpecs, String... additionalPatterns) throws IOException { BaseConnection f; if (isSmartHttp(c, service)) { InputStream withMark = in.markSupported() ? in : new BufferedInputStream(in); readSmartHeaders(withMark, service); f = new SmartHttpFetchConnection(withMark, refSpecs, additionalPatterns); } else { // Assume this server doesn't support smart HTTP fetch // and fall back on dumb object walking. f = newDumbConnection(in); } f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER)); return (FetchConnection) f; } /** * Sets the {@link HttpConnectionFactory} to be used by this * {@link TransportHttp} instance. *

* If no factory is set explicitly, the {@link TransportHttp} instance uses * the {@link HttpTransport#getConnectionFactory() globally defined * factory}. *

* * @param customFactory * the {@link HttpConnectionFactory} to use * @throws IllegalStateException * if an HTTP/HTTPS connection has already been opened on this * {@link TransportHttp} instance * @since 5.11 */ public void setHttpConnectionFactory( @NonNull HttpConnectionFactory customFactory) { if (factoryUsed) { throw new IllegalStateException(JGitText.get().httpFactoryInUse); } factory = customFactory; } /** * Retrieves the {@link HttpConnectionFactory} used by this * {@link TransportHttp} instance. * * @return the {@link HttpConnectionFactory} * @since 5.11 */ @NonNull public HttpConnectionFactory getHttpConnectionFactory() { return factory; } /** * Sets preemptive Basic HTTP authentication. If the given {@code username} * or {@code password} is empty or {@code null}, no preemptive * authentication will be done. If {@code username} and {@code password} are * set, they will override authority information from the URI * ("user:password@"). *

* If the connection encounters redirects, the pre-authentication will be * cleared if the redirect goes to a different host. *

* * @param username * to use * @param password * to use * @throws IllegalStateException * if an HTTP/HTTPS connection has already been opened on this * {@link TransportHttp} instance * @since 5.11 */ public void setPreemptiveBasicAuthentication(String username, String password) { if (factoryUsed) { throw new IllegalStateException(JGitText.get().httpPreAuthTooLate); } if (StringUtils.isEmptyOrNull(username) || StringUtils.isEmptyOrNull(password)) { authMethod = authFromUri(currentUri); } else { HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null); basic.authorize(username, password); authMethod = basic; } } @Override public FetchConnection openFetch() throws TransportException, NotSupportedException { return openFetch(Collections.emptyList()); } @Override public FetchConnection openFetch(Collection refSpecs, String... additionalPatterns) throws NotSupportedException, TransportException { final String service = SVC_UPLOAD_PACK; try { TransferConfig.ProtocolVersion gitProtocol = protocol; if (gitProtocol == null) { gitProtocol = TransferConfig.ProtocolVersion.V2; } HttpConnection c = connect(service, gitProtocol); try (InputStream in = openInputStream(c)) { return getConnection(c, in, service, refSpecs, additionalPatterns); } } catch (NotSupportedException | TransportException err) { throw err; } catch (IOException err) { throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err); } } private WalkFetchConnection newDumbConnection(InputStream in) throws IOException, PackProtocolException { HttpObjectDB d = new HttpObjectDB(objectsUrl); Map refs; try (BufferedReader br = toBufferedReader(in)) { refs = d.readAdvertisedImpl(br); } if (!refs.containsKey(HEAD)) { // If HEAD was not published in the info/refs file (it usually // is not there) download HEAD by itself as a loose file and do // the resolution by hand. // HttpConnection conn = httpOpen( METHOD_GET, new URL(baseUrl, HEAD), AcceptEncoding.GZIP); int status = HttpSupport.response(conn); switch (status) { case HttpConnection.HTTP_OK: { try (BufferedReader br = toBufferedReader( openInputStream(conn))) { String line = br.readLine(); if (line != null && line.startsWith(RefDirectory.SYMREF)) { String target = line.substring(RefDirectory.SYMREF.length()); Ref r = refs.get(target); if (r == null) r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null); r = new SymbolicRef(HEAD, r); refs.put(r.getName(), r); } else if (line != null && ObjectId.isId(line)) { Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, HEAD, ObjectId.fromString(line)); refs.put(r.getName(), r); } } break; } case HttpConnection.HTTP_NOT_FOUND: break; default: throw new TransportException(uri, MessageFormat.format( JGitText.get().cannotReadHEAD, Integer.valueOf(status), conn.getResponseMessage())); } } WalkFetchConnection wfc = new WalkFetchConnection(this, d); wfc.available(refs); return wfc; } private BufferedReader toBufferedReader(InputStream in) { return new BufferedReader(new InputStreamReader(in, UTF_8)); } @Override public PushConnection openPush() throws NotSupportedException, TransportException { final String service = SVC_RECEIVE_PACK; try { final HttpConnection c = connect(service); try (InputStream in = openInputStream(c)) { if (isSmartHttp(c, service)) { return smartPush(service, c, in); } else if (!useSmartHttp) { final String msg = JGitText.get().smartHTTPPushDisabled; throw new NotSupportedException(msg); } else { final String msg = JGitText.get().remoteDoesNotSupportSmartHTTPPush; throw new NotSupportedException(msg); } } } catch (NotSupportedException | TransportException err) { throw err; } catch (IOException err) { throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err); } } private PushConnection smartPush(String service, HttpConnection c, InputStream in) throws IOException, TransportException { BufferedInputStream inBuf = new BufferedInputStream(in); readSmartHeaders(inBuf, service); SmartHttpPushConnection p = new SmartHttpPushConnection(inBuf); p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER)); return p; } @Override public void close() { if (gitSession != null) { gitSession.close(); gitSession = null; } } /** * Set additional headers on the HTTP connection * * @param headers * a map of name:values that are to be set as headers on the HTTP * connection * @since 3.4 */ public void setAdditionalHeaders(Map headers) { this.headers = headers; } /** * Get additional headers on the HTTP connection * * @return unmodifiable map of additional name:values that are set as * headers on the HTTP connection * @since 6.6 */ public Map getAdditionalHeaders() { return Collections.unmodifiableMap(headers); } private NoRemoteRepositoryException createNotFoundException(URIish u, URL url, String msg) { String text; if (msg != null && !msg.isEmpty()) { text = MessageFormat.format(JGitText.get().uriNotFoundWithMessage, url, msg); } else { text = MessageFormat.format(JGitText.get().uriNotFound, url); } return new NoRemoteRepositoryException(u, text); } private HttpAuthMethod authFromUri(URIish u) { String user = u.getUser(); String pass = u.getPass(); if (user != null && pass != null) { try { // User/password are _not_ application/x-www-form-urlencoded. In // particular the "+" sign would be replaced by a space. user = URLDecoder.decode(user.replace("+", "%2B"), //$NON-NLS-1$ //$NON-NLS-2$ StandardCharsets.UTF_8.name()); pass = URLDecoder.decode(pass.replace("+", "%2B"), //$NON-NLS-1$ //$NON-NLS-2$ StandardCharsets.UTF_8.name()); HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null); basic.authorize(user, pass); return basic; } catch (IllegalArgumentException | UnsupportedEncodingException e) { LOG.warn(JGitText.get().httpUserInfoDecodeError, u); } } return HttpAuthMethod.Type.NONE.method(null); } private HttpConnection connect(String service) throws TransportException, NotSupportedException { return connect(service, null); } private HttpConnection connect(String service, TransferConfig.ProtocolVersion protocolVersion) throws TransportException, NotSupportedException { URL u = getServiceURL(service); if (HttpAuthMethod.Type.NONE.equals(authMethod.getType())) { authMethod = authFromUri(currentUri); } int authAttempts = 1; int redirects = 0; Collection ignoreTypes = null; for (;;) { try { final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP); if (useSmartHttp) { String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$ conn.setRequestProperty(HDR_ACCEPT, exp + ", */*"); //$NON-NLS-1$ } else { conn.setRequestProperty(HDR_ACCEPT, "*/*"); //$NON-NLS-1$ } if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) { conn.setRequestProperty( GitProtocolConstants.PROTOCOL_HEADER, GitProtocolConstants.VERSION_2_REQUEST); } final int status = HttpSupport.response(conn); processResponseCookies(conn); switch (status) { case HttpConnection.HTTP_OK: // Check if HttpConnection did some authentication in the // background (e.g Kerberos/SPNEGO). // That may not work for streaming requests and jgit // explicit authentication would be required if (authMethod.getType() == HttpAuthMethod.Type.NONE && conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null) authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes); return conn; case HttpConnection.HTTP_NOT_FOUND: throw createNotFoundException(uri, u, conn.getResponseMessage()); case HttpConnection.HTTP_UNAUTHORIZED: authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes); if (authMethod.getType() == HttpAuthMethod.Type.NONE) throw new TransportException(uri, MessageFormat.format( JGitText.get().authenticationNotSupported, uri)); CredentialsProvider credentialsProvider = getCredentialsProvider(); if (credentialsProvider == null) throw new TransportException(uri, JGitText.get().noCredentialsProvider); if (authAttempts > 1) credentialsProvider.reset(currentUri); if (3 < authAttempts || !authMethod.authorize(currentUri, credentialsProvider)) { throw new TransportException(uri, JGitText.get().notAuthorized); } authAttempts++; continue; case HttpConnection.HTTP_FORBIDDEN: throw new TransportException(uri, MessageFormat.format( JGitText.get().serviceNotPermitted, baseUrl, service)); case HttpConnection.HTTP_MOVED_PERM: case HttpConnection.HTTP_MOVED_TEMP: case HttpConnection.HTTP_SEE_OTHER: case HttpConnection.HTTP_11_MOVED_PERM: case HttpConnection.HTTP_11_MOVED_TEMP: // SEE_OTHER should actually never be sent by a git server, // and in general should occur only on POST requests. But it // doesn't hurt to accept it here as a redirect. if (http.getFollowRedirects() == HttpRedirectMode.FALSE) { throw new TransportException(uri, MessageFormat.format( JGitText.get().redirectsOff, Integer.valueOf(status))); } URIish newUri = redirect(u, conn.getHeaderField(HDR_LOCATION), Constants.INFO_REFS, redirects++); setURI(newUri); u = getServiceURL(service); authAttempts = 1; break; default: String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$ throw new TransportException(uri, err); } } catch (NotSupportedException | TransportException e) { throw e; } catch (InterruptedIOException e) { // Timeout!? Don't try other authentication methods. throw new TransportException(uri, MessageFormat.format( JGitText.get().connectionTimeOut, u.getHost()), e); } catch (SocketException e) { // Nothing on other end, timeout, connection reset, ... throw new TransportException(uri, JGitText.get().connectionFailed, e); } catch (SSLHandshakeException e) { handleSslFailure(e); continue; // Re-try } catch (IOException e) { if (authMethod.getType() != HttpAuthMethod.Type.NONE) { if (ignoreTypes == null) { ignoreTypes = new HashSet<>(); } ignoreTypes.add(authMethod.getType()); // reset auth method & attempts for next authentication type authMethod = HttpAuthMethod.Type.NONE.method(null); authAttempts = 1; continue; } throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e); } } } void processResponseCookies(HttpConnection conn) { if (cookieFile != null && http.getSaveCookies()) { List foundCookies = new ArrayList<>(); List cookieHeaderValues = conn .getHeaderFields(HDR_SET_COOKIE); if (!cookieHeaderValues.isEmpty()) { foundCookies.addAll( extractCookies(HDR_SET_COOKIE, cookieHeaderValues)); } cookieHeaderValues = conn.getHeaderFields(HDR_SET_COOKIE2); if (!cookieHeaderValues.isEmpty()) { foundCookies.addAll( extractCookies(HDR_SET_COOKIE2, cookieHeaderValues)); } if (!foundCookies.isEmpty()) { try { // update cookie lists with the newly received cookies! Set cookies = cookieFile.getCookies(false); cookies.addAll(foundCookies); cookieFile.write(baseUrl); relevantCookies.addAll(foundCookies); } catch (IOException | IllegalArgumentException | InterruptedException e) { LOG.warn(MessageFormat.format( JGitText.get().couldNotPersistCookies, cookieFile.getPath()), e); } } } } private List extractCookies(String headerKey, List headerValues) { List foundCookies = new ArrayList<>(); for (String headerValue : headerValues) { foundCookies .addAll(HttpCookie.parse(headerKey + ':' + headerValue)); } // HttpCookies.parse(...) is only compliant with RFC 2965. Make it RFC // 6265 compliant by applying the logic from // https://tools.ietf.org/html/rfc6265#section-5.2.3 for (HttpCookie foundCookie : foundCookies) { String domain = foundCookie.getDomain(); if (domain != null && domain.startsWith(".")) { //$NON-NLS-1$ foundCookie.setDomain(domain.substring(1)); } } return foundCookies; } private static class CredentialItems { CredentialItem.InformationalMessage message; /** Trust the server for this git operation */ CredentialItem.YesNoType now; /** * Trust the server for all git operations from this repository; may be * {@code null} if the transport was created via * {@link TransportHttp#TransportHttp(URIish)}. */ CredentialItem.YesNoType forRepo; /** Always trust the server from now on. */ CredentialItem.YesNoType always; public CredentialItem[] items() { if (forRepo == null) { return new CredentialItem[] { message, now, always }; } return new CredentialItem[] { message, now, forRepo, always }; } } private void handleSslFailure(Throwable e) throws TransportException { if (sslFailure || !trustInsecureSslConnection(e.getCause())) { throw new TransportException(uri, MessageFormat.format( JGitText.get().sslFailureExceptionMessage, currentUri.setPass(null)), e); } sslFailure = true; } private boolean trustInsecureSslConnection(Throwable cause) { if (cause instanceof CertificateException || cause instanceof CertPathBuilderException || cause instanceof CertPathValidatorException) { // Certificate expired or revoked, PKIX path building not // possible, self-signed certificate, host does not match ... CredentialsProvider provider = getCredentialsProvider(); if (provider != null) { CredentialItems trust = constructSslTrustItems(cause); CredentialItem[] items = trust.items(); if (provider.supports(items)) { boolean answered = provider.get(uri, items); if (answered) { // Not canceled boolean trustNow = trust.now.getValue(); boolean trustLocal = trust.forRepo != null && trust.forRepo.getValue(); boolean trustAlways = trust.always.getValue(); if (trustNow || trustLocal || trustAlways) { sslVerify = false; if (trustAlways) { updateSslVerifyUser(false); } else if (trustLocal) { updateSslVerify(local.getConfig(), false); } return true; } } } } } return false; } private CredentialItems constructSslTrustItems(Throwable cause) { CredentialItems items = new CredentialItems(); String info = MessageFormat.format(JGitText.get().sslFailureInfo, currentUri.setPass(null)); String sslMessage = cause.getLocalizedMessage(); if (sslMessage == null) { sslMessage = cause.toString(); } sslMessage = MessageFormat.format(JGitText.get().sslFailureCause, sslMessage); items.message = new CredentialItem.InformationalMessage(info + '\n' + sslMessage + '\n' + JGitText.get().sslFailureTrustExplanation); items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow); if (local != null) { items.forRepo = new CredentialItem.YesNoType( MessageFormat.format(JGitText.get().sslTrustForRepo, local.getDirectory())); } items.always = new CredentialItem.YesNoType( JGitText.get().sslTrustAlways); return items; } private void updateSslVerify(StoredConfig config, boolean value) { // Since git uses the original URI for matching, we must also use the // original URI and cannot use the current URI (which might be different // after redirects). String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$ int port = uri.getPort(); if (port > 0) { uriPattern += ":" + port; //$NON-NLS-1$ } config.setBoolean(HttpConfig.HTTP, uriPattern, HttpConfig.SSL_VERIFY_KEY, value); try { config.save(); } catch (IOException e) { LOG.error(JGitText.get().sslVerifyCannotSave, e); } } private void updateSslVerifyUser(boolean value) { StoredConfig userConfig = null; try { userConfig = SystemReader.getInstance().getUserConfig(); updateSslVerify(userConfig, value); } catch (IOException | ConfigInvalidException e) { // Log it, but otherwise ignore here. LOG.error(e.getMessage(), e); } } private URIish redirect(URL currentUrl, String location, String checkFor, int redirects) throws TransportException { if (location == null || location.isEmpty()) { throw new TransportException(uri, MessageFormat.format(JGitText.get().redirectLocationMissing, baseUrl)); } if (redirects >= http.getMaxRedirects()) { throw new TransportException(uri, MessageFormat.format(JGitText.get().redirectLimitExceeded, Integer.valueOf(http.getMaxRedirects()), baseUrl, location)); } try { URI redirectTo = new URI(location); // Reset authentication if the redirect has user/password info or // if the host is different. boolean resetAuth = !StringUtils .isEmptyOrNull(redirectTo.getUserInfo()); String currentHost = currentUrl.getHost(); redirectTo = currentUrl.toURI().resolve(redirectTo); resetAuth = resetAuth || !currentHost.equals(redirectTo.getHost()); String redirected = redirectTo.toASCIIString(); if (!isValidRedirect(baseUrl, redirected, checkFor)) { throw new TransportException(uri, MessageFormat.format(JGitText.get().redirectBlocked, baseUrl, redirected)); } redirected = redirected.substring(0, redirected.indexOf(checkFor)); URIish result = new URIish(redirected); if (resetAuth) { authMethod = HttpAuthMethod.Type.NONE.method(null); } if (LOG.isInfoEnabled()) { LOG.info(MessageFormat.format(JGitText.get().redirectHttp, uri.setPass(null), Integer.valueOf(redirects), baseUrl, result)); } return result; } catch (URISyntaxException e) { throw new TransportException(uri, MessageFormat.format(JGitText.get().invalidRedirectLocation, baseUrl, location), e); } } private boolean isValidRedirect(URL current, String next, String checkFor) { // Protocols must be the same, or current is "http" and next "https". We // do not follow redirects from https back to http. String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT); int schemeEnd = next.indexOf("://"); //$NON-NLS-1$ if (schemeEnd < 0) { return false; } String newProtocol = next.substring(0, schemeEnd) .toLowerCase(Locale.ROOT); if (!oldProtocol.equals(newProtocol)) { if (!"https".equals(newProtocol)) { //$NON-NLS-1$ return false; } } // git allows only rewriting the root, i.e., everything before INFO_REFS // or the service name if (!next.contains(checkFor)) { return false; } // Basically we should test here that whatever follows INFO_REFS is // unchanged. But since we re-construct the query part // anyway, it doesn't matter. return true; } private URL getServiceURL(String service) throws NotSupportedException { try { final StringBuilder b = new StringBuilder(); b.append(baseUrl); if (b.charAt(b.length() - 1) != '/') { b.append('/'); } b.append(Constants.INFO_REFS); if (useSmartHttp) { b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$ b.append("service="); //$NON-NLS-1$ b.append(service); } return new URL(b.toString()); } catch (MalformedURLException e) { throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e); } } /** * Open an HTTP connection. * * @param method * HTTP request method * @param u * url of the HTTP connection * @param acceptEncoding * accept-encoding header option * @return the HTTP connection * @throws java.io.IOException * if an IO error occurred * @since 4.6 */ protected HttpConnection httpOpen(String method, URL u, AcceptEncoding acceptEncoding) throws IOException { if (method == null || u == null || acceptEncoding == null) { throw new NullPointerException(); } final Proxy proxy = HttpSupport.proxyFor(proxySelector, u); factoryUsed = true; HttpConnection conn = factory.create(u, proxy); if (gitSession == null && (factory instanceof HttpConnectionFactory2)) { gitSession = ((HttpConnectionFactory2) factory).newSession(); } if (gitSession != null) { try { gitSession.configure(conn, sslVerify); } catch (GeneralSecurityException e) { throw new IOException(e.getMessage(), e); } } else if (!sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$ // Backwards compatibility HttpSupport.disableSslVerify(conn); } // We must do our own redirect handling to implement git rules and to // handle http->https redirects conn.setInstanceFollowRedirects(false); conn.setRequestMethod(method); conn.setUseCaches(false); if (acceptEncoding == AcceptEncoding.GZIP) { conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP); } conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$ if (http.getUserAgent() != null) { conn.setRequestProperty(HDR_USER_AGENT, http.getUserAgent()); } else if (UserAgent.get() != null) { conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get()); } int timeOut = getTimeout(); if (timeOut != -1) { int effTimeOut = timeOut * 1000; conn.setConnectTimeout(effTimeOut); conn.setReadTimeout(effTimeOut); } addHeaders(conn, http.getExtraHeaders()); // set cookie header if necessary if (!relevantCookies.isEmpty()) { setCookieHeader(conn); } if (this.headers != null && !this.headers.isEmpty()) { for (Map.Entry entry : this.headers.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } } authMethod.configureRequest(conn); return conn; } /** * Adds a list of header strings to the connection. Headers are expected to * separate keys from values, i.e. "Key: Value". Headers without colon or * key are ignored (and logged), as are headers with keys that are not RFC * 7230 tokens or with non-ASCII values. * * @param conn * The target HttpConnection * @param headersToAdd * A list of header strings */ static void addHeaders(HttpConnection conn, List headersToAdd) { for (String header : headersToAdd) { // Empty values are allowed according to // https://tools.ietf.org/html/rfc7230 int colon = header.indexOf(':'); String key = null; if (colon > 0) { key = header.substring(0, colon).trim(); } if (key == null || key.isEmpty()) { LOG.warn(MessageFormat.format( JGitText.get().invalidHeaderFormat, header)); } else if (HttpSupport.scanToken(key, 0) != key.length()) { LOG.warn(MessageFormat.format(JGitText.get().invalidHeaderKey, header)); } else { String value = header.substring(colon + 1).trim(); if (!StandardCharsets.US_ASCII.newEncoder().canEncode(value)) { LOG.warn(MessageFormat .format(JGitText.get().invalidHeaderValue, header)); } else { conn.setRequestProperty(key, value); } } } } private void setCookieHeader(HttpConnection conn) { StringBuilder cookieHeaderValue = new StringBuilder(); for (HttpCookie cookie : relevantCookies) { if (!cookie.hasExpired()) { if (cookieHeaderValue.length() > 0) { cookieHeaderValue.append(';'); } cookieHeaderValue.append(cookie.toString()); } } if (cookieHeaderValue.length() > 0) { conn.setRequestProperty(HDR_COOKIE, cookieHeaderValue.toString()); } } final InputStream openInputStream(HttpConnection conn) throws IOException { InputStream input = conn.getInputStream(); if (isGzipContent(conn)) input = new GZIPInputStream(input); return input; } IOException wrongContentType(String expType, String actType) { final String why = MessageFormat.format(JGitText.get().expectedReceivedContentType, expType, actType); return new TransportException(uri, why); } private NetscapeCookieFile getCookieFileFromConfig( HttpConfig config) { String path = config.getCookieFile(); if (!StringUtils.isEmptyOrNull(path)) { try { FS fs = local != null ? local.getFS() : FS.DETECTED; File f; if (path.startsWith("~/")) { //$NON-NLS-1$ f = fs.resolve(fs.userHome(), path.substring(2)); } else { f = new File(path); if (!f.isAbsolute()) { f = fs.resolve(null, path); LOG.warn(MessageFormat.format( JGitText.get().cookieFilePathRelative, f)); } } return NetscapeCookieFileCache.getInstance(config) .getEntry(f.toPath()); } catch (InvalidPathException e) { LOG.warn(MessageFormat.format( JGitText.get().couldNotReadCookieFile, path), e); } } return null; } private static Set filterCookies(NetscapeCookieFile cookieFile, URL url) { if (cookieFile != null) { return filterCookies(cookieFile.getCookies(true), url); } return Collections.emptySet(); } /** * * @param allCookies * a list of cookies. * @param url * the url for which to filter the list of cookies. * @return only the cookies from {@code allCookies} which are relevant (i.e. * are not expired, have a matching domain, have a matching path and * have a matching secure attribute) */ private static Set filterCookies(Set allCookies, URL url) { Set filteredCookies = new HashSet<>(); for (HttpCookie cookie : allCookies) { if (cookie.hasExpired()) { continue; } if (!matchesCookieDomain(url.getHost(), cookie.getDomain())) { continue; } if (!matchesCookiePath(url.getPath(), cookie.getPath())) { continue; } if (cookie.getSecure() && !"https".equals(url.getProtocol())) { //$NON-NLS-1$ continue; } filteredCookies.add(cookie); } return filteredCookies; } /** * * The utility method to check whether a host name is in a cookie's domain * or not. Similar to {@link HttpCookie#domainMatches(String, String)} but * implements domain matching rules according to * RFC 6265, * section 5.1.3 instead of the rules from * RFC 2965, * section 3.3.1. *

* The former rules are also used by libcurl internally. *

* The rules are as follows * * A string matches another domain string if at least one of the following * conditions holds: *

    *
  • The domain string and the string are identical. (Note that both the * domain string and the string will have been canonicalized to lower case * at this point.)
  • *
  • All of the following conditions hold *
      *
    • The domain string is a suffix of the string.
    • *
    • The last character of the string that is not included in the domain * string is a %x2E (".") character.
    • *
    • The string is a host name (i.e., not an IP address).
    • *
    *
  • *
* * @param host * the host to compare against the cookieDomain * @param cookieDomain * the domain to compare against * @return {@code true} if they domain-match; {@code false} if not * * @see RFC * 6265, section 5.1.3 (Domain Matching) * @see JDK-8206092 * : HttpCookie.domainMatches() does not match to sub-sub-domain */ static boolean matchesCookieDomain(String host, String cookieDomain) { cookieDomain = cookieDomain.toLowerCase(Locale.ROOT); host = host.toLowerCase(Locale.ROOT); if (host.equals(cookieDomain)) { return true; } if (!host.endsWith(cookieDomain)) { return false; } return host.charAt(host.length() - cookieDomain.length() - 1) == '.'; } /** * The utility method to check whether a path is matching a cookie path * domain or not. The rules are defined by * RFC 6265, * section 5.1.4: * * A request-path path-matches a given cookie-path if at least one of the * following conditions holds: *
    *
  • The cookie-path and the request-path are identical.
  • *
  • The cookie-path is a prefix of the request-path, and the last * character of the cookie-path is %x2F ("/").
  • *
  • The cookie-path is a prefix of the request-path, and the first * character of the request-path that is not included in the cookie- path is * a %x2F ("/") character.
  • *
* @param path * the path to check * @param cookiePath * the cookie's path * * @return {@code true} if they path-match; {@code false} if not */ static boolean matchesCookiePath(String path, String cookiePath) { if (cookiePath.equals(path)) { return true; } if (!cookiePath.endsWith("/")) { //$NON-NLS-1$ cookiePath += "/"; //$NON-NLS-1$ } return path.startsWith(cookiePath); } private boolean isSmartHttp(HttpConnection c, String service) { final String expType = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$ final String actType = c.getContentType(); return expType.equals(actType); } private boolean isGzipContent(HttpConnection c) { return ENCODING_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING)) || ENCODING_X_GZIP.equals(c.getHeaderField(HDR_CONTENT_ENCODING)); } private void readSmartHeaders(InputStream in, String service) throws IOException { // A smart protocol V0 reply will have a '#' after the first 4 bytes, // but a dumb reply cannot contain a '#' until after byte 41. Do a // quick check to make sure its a smart reply before we parse // as a pkt-line stream. // // There appears to be a confusion about this in protocol V2. Github // sends the # service line as a git (not http) header also when // protocol V2 is used. Gitlab also does so. JGit's UploadPack doesn't, // and thus Gerrit also does not. final byte[] magic = new byte[14]; if (!in.markSupported()) { throw new TransportException(uri, JGitText.get().inputStreamMustSupportMark); } in.mark(14); IO.readFully(in, magic, 0, magic.length); // Did we get 000dversion 2 or similar? (Canonical is 000eversion 2\n, // but JGit and thus Gerrit omits the \n.) if (Arrays.equals(Arrays.copyOfRange(magic, 4, 11), VERSION) && magic[12] >= '1' && magic[12] <= '9') { // It's a smart server doing version 1 or greater, but not sending // the # service line header. Don't consume the version line. in.reset(); return; } if (magic[4] != '#') { throw new TransportException(uri, MessageFormat.format( JGitText.get().expectedPktLineWithService, RawParseUtils.decode(magic))); } in.reset(); final PacketLineIn pckIn = new PacketLineIn(in); final String exp = "# service=" + service; //$NON-NLS-1$ final String act = pckIn.readString(); if (!exp.equals(act)) { throw new TransportException(uri, MessageFormat.format( JGitText.get().expectedGot, exp, act)); } while (!PacketLineIn.isEnd(pckIn.readString())) { // for now, ignore the remaining header lines } } class HttpObjectDB extends WalkRemoteObjectDatabase { private final URL httpObjectsUrl; HttpObjectDB(URL b) { httpObjectsUrl = b; } @Override URIish getURI() { return new URIish(httpObjectsUrl); } @Override Collection getAlternates() throws IOException { try { return readAlternates(INFO_HTTP_ALTERNATES); } catch (FileNotFoundException err) { // Fall through. } try { return readAlternates(INFO_ALTERNATES); } catch (FileNotFoundException err) { // Fall through. } return null; } @Override WalkRemoteObjectDatabase openAlternate(String location) throws IOException { return new HttpObjectDB(new URL(httpObjectsUrl, location)); } @Override BufferedReader openReader(String path) throws IOException { // Line oriented readable content is likely to compress well. // Request gzip encoding. InputStream is = open(path, AcceptEncoding.GZIP).in; return new BufferedReader(new InputStreamReader(is, UTF_8)); } @Override Collection getPackNames() throws IOException { final Collection packs = new ArrayList<>(); try (BufferedReader br = openReader(INFO_PACKS)) { for (;;) { final String s = br.readLine(); if (s == null || s.length() == 0) break; if (!s.startsWith("P pack-") || !s.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$ throw invalidAdvertisement(s); packs.add(s.substring(2)); } return packs; } catch (FileNotFoundException err) { return packs; } } @Override FileStream open(String path) throws IOException { return open(path, AcceptEncoding.UNSPECIFIED); } FileStream open(String path, AcceptEncoding acceptEncoding) throws IOException { final URL base = httpObjectsUrl; final URL u = new URL(base, path); final HttpConnection c = httpOpen(METHOD_GET, u, acceptEncoding); switch (HttpSupport.response(c)) { case HttpConnection.HTTP_OK: final InputStream in = openInputStream(c); // If content is being gzipped and then transferred, the content // length in the header is the zipped content length, not the // actual content length. if (!isGzipContent(c)) { final int len = c.getContentLength(); return new FileStream(in, len); } return new FileStream(in); case HttpConnection.HTTP_NOT_FOUND: throw new FileNotFoundException(u.toString()); default: throw new IOException(u.toString() + ": " //$NON-NLS-1$ + HttpSupport.response(c) + " " //$NON-NLS-1$ + c.getResponseMessage()); } } Map readAdvertisedImpl(final BufferedReader br) throws IOException, PackProtocolException { final TreeMap avail = new TreeMap<>(); for (;;) { String line = br.readLine(); if (line == null) break; final int tab = line.indexOf('\t'); if (tab < 0) throw invalidAdvertisement(line); String name; final ObjectId id; name = line.substring(tab + 1); id = ObjectId.fromString(line.substring(0, tab)); if (name.endsWith("^{}")) { //$NON-NLS-1$ name = name.substring(0, name.length() - 3); final Ref prior = avail.get(name); if (prior == null) throw outOfOrderAdvertisement(name); if (prior.getPeeledObjectId() != null) throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$ avail.put(name, new ObjectIdRef.PeeledTag( Ref.Storage.NETWORK, name, prior.getObjectId(), id)); } else { Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag( Ref.Storage.NETWORK, name, id)); if (prior != null) throw duplicateAdvertisement(name); } } return avail; } private PackProtocolException outOfOrderAdvertisement(String n) { return new PackProtocolException(MessageFormat.format(JGitText.get().advertisementOfCameBefore, n, n)); } private PackProtocolException invalidAdvertisement(String n) { return new PackProtocolException(MessageFormat.format(JGitText.get().invalidAdvertisementOf, n)); } private PackProtocolException duplicateAdvertisement(String n) { return new PackProtocolException(MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, n)); } @Override void close() { // We do not maintain persistent connections. } } class SmartHttpFetchConnection extends BasePackFetchConnection { private MultiRequestService svc; SmartHttpFetchConnection(InputStream advertisement) throws TransportException { this(advertisement, Collections.emptyList()); } SmartHttpFetchConnection(InputStream advertisement, Collection refSpecs, String... additionalPatterns) throws TransportException { super(TransportHttp.this); statelessRPC = true; init(advertisement, DisabledOutputStream.INSTANCE); outNeedsEnd = false; if (!readAdvertisedRefs()) { // Must be protocol V2 LongPollService service = new LongPollService(SVC_UPLOAD_PACK, getProtocolVersion()); init(service.getInputStream(), service.getOutputStream()); lsRefs(refSpecs, additionalPatterns); } } @Override protected void doFetch(ProgressMonitor monitor, Collection want, Set have, OutputStream outputStream) throws TransportException { svc = new MultiRequestService(SVC_UPLOAD_PACK, getProtocolVersion()); try (InputStream svcIn = svc.getInputStream(); OutputStream svcOut = svc.getOutputStream()) { init(svcIn, svcOut); super.doFetch(monitor, want, have, outputStream); } catch (TransportException e) { throw e; } catch (IOException e) { throw new TransportException(e.getMessage(), e); } finally { svc = null; } } @Override protected void onReceivePack() { svc.finalRequest = true; } } class SmartHttpPushConnection extends BasePackPushConnection { SmartHttpPushConnection(InputStream advertisement) throws TransportException { super(TransportHttp.this); statelessRPC = true; init(advertisement, DisabledOutputStream.INSTANCE); outNeedsEnd = false; readAdvertisedRefs(); } @Override protected void doPush(ProgressMonitor monitor, Map refUpdates, OutputStream outputStream) throws TransportException { Service svc = new MultiRequestService(SVC_RECEIVE_PACK, getProtocolVersion()); try (InputStream svcIn = svc.getInputStream(); OutputStream svcOut = svc.getOutputStream()) { init(svcIn, svcOut); super.doPush(monitor, refUpdates, outputStream); } catch (TransportException e) { throw e; } catch (IOException e) { throw new TransportException(e.getMessage(), e); } } } /** Basic service for sending and receiving HTTP requests. */ abstract class Service { protected final String serviceName; protected final String requestType; protected final String responseType; protected HttpConnection conn; protected HttpOutputStream out; protected final HttpExecuteStream execute; protected final TransferConfig.ProtocolVersion protocolVersion; final UnionInputStream in; Service(String serviceName, TransferConfig.ProtocolVersion protocolVersion) { this.serviceName = serviceName; this.protocolVersion = protocolVersion; this.requestType = "application/x-" + serviceName + "-request"; //$NON-NLS-1$ //$NON-NLS-2$ this.responseType = "application/x-" + serviceName + "-result"; //$NON-NLS-1$ //$NON-NLS-2$ this.out = new HttpOutputStream(); this.execute = new HttpExecuteStream(); this.in = new UnionInputStream(execute); } void openStream() throws IOException { conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName), AcceptEncoding.GZIP); conn.setInstanceFollowRedirects(false); conn.setDoOutput(true); conn.setRequestProperty(HDR_CONTENT_TYPE, requestType); conn.setRequestProperty(HDR_ACCEPT, responseType); if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) { conn.setRequestProperty(GitProtocolConstants.PROTOCOL_HEADER, GitProtocolConstants.VERSION_2_REQUEST); } } void sendRequest() throws IOException { // Try to compress the content, but only if that is smaller. TemporaryBuffer buf = new TemporaryBuffer.Heap( http.getPostBuffer()); try (GZIPOutputStream gzip = new GZIPOutputStream(buf)) { out.writeTo(gzip, null); if (out.length() < buf.length()) buf = out; } catch (IOException err) { // Most likely caused by overflowing the buffer, meaning // its larger if it were compressed. Don't compress. buf = out; } HttpAuthMethod authenticator = null; Collection ignoreTypes = EnumSet .noneOf(HttpAuthMethod.Type.class); // Counts number of repeated authentication attempts using the same // authentication scheme int authAttempts = 1; int redirects = 0; for (;;) { try { // The very first time we will try with the authentication // method used on the initial GET request. This is a hint // only; it may fail. If so, we'll then re-try with proper // 401 handling, going through the available authentication // schemes. openStream(); if (buf != out) { conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP); } conn.setFixedLengthStreamingMode((int) buf.length()); try (OutputStream httpOut = conn.getOutputStream()) { buf.writeTo(httpOut, null); } final int status = HttpSupport.response(conn); switch (status) { case HttpConnection.HTTP_OK: // We're done. return; case HttpConnection.HTTP_NOT_FOUND: throw createNotFoundException(uri, conn.getURL(), conn.getResponseMessage()); case HttpConnection.HTTP_FORBIDDEN: throw new TransportException(uri, MessageFormat.format( JGitText.get().serviceNotPermitted, baseUrl, serviceName)); case HttpConnection.HTTP_MOVED_PERM: case HttpConnection.HTTP_MOVED_TEMP: case HttpConnection.HTTP_11_MOVED_PERM: case HttpConnection.HTTP_11_MOVED_TEMP: // SEE_OTHER after a POST doesn't make sense for a git // server, so we don't handle it here and thus we'll // report an error in openResponse() later on. if (http.getFollowRedirects() != HttpRedirectMode.TRUE) { // Let openResponse() issue an error return; } currentUri = redirect(conn.getURL(), conn.getHeaderField(HDR_LOCATION), '/' + serviceName, redirects++); try { baseUrl = toURL(currentUri); } catch (MalformedURLException e) { throw new TransportException(uri, MessageFormat.format( JGitText.get().invalidRedirectLocation, baseUrl, currentUri), e); } continue; case HttpConnection.HTTP_UNAUTHORIZED: HttpAuthMethod nextMethod = HttpAuthMethod .scanResponse(conn, ignoreTypes); switch (nextMethod.getType()) { case NONE: throw new TransportException(uri, MessageFormat.format( JGitText.get().authenticationNotSupported, conn.getURL())); case NEGOTIATE: // RFC 4559 states "When using the SPNEGO [...] with // [...] POST, the authentication should be complete // [...] before sending the user data." So in theory // the initial GET should have been authenticated // already. (Unless there was a redirect?) // // We try this only once: ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE); if (authenticator != null) { ignoreTypes.add(authenticator.getType()); } authAttempts = 1; // We only do the Kerberos part of SPNEGO, which // requires only one round. break; default: // DIGEST or BASIC. Let's be sure we ignore // NEGOTIATE; if it was available, we have tried it // before. ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE); if (authenticator == null || authenticator .getType() != nextMethod.getType()) { if (authenticator != null) { ignoreTypes.add(authenticator.getType()); } authAttempts = 1; } break; } authMethod = nextMethod; authenticator = nextMethod; CredentialsProvider credentialsProvider = getCredentialsProvider(); if (credentialsProvider == null) { throw new TransportException(uri, JGitText.get().noCredentialsProvider); } if (authAttempts > 1) { credentialsProvider.reset(currentUri); } if (3 < authAttempts || !authMethod .authorize(currentUri, credentialsProvider)) { throw new TransportException(uri, JGitText.get().notAuthorized); } authAttempts++; continue; default: // Just return here; openResponse() will report an // appropriate error. return; } } catch (SSLHandshakeException e) { handleSslFailure(e); continue; // Re-try } catch (SocketException | InterruptedIOException e) { // Timeout!? Must propagate; don't try other authentication // methods. throw e; } catch (IOException e) { if (authenticator == null || authMethod .getType() != HttpAuthMethod.Type.NONE) { // Can happen for instance if the server advertises // Negotiate, but the client isn't configured for // Kerberos. The first time (authenticator == null) we // must re-try even if the authMethod was NONE: this may // occur if the server advertised NTLM on the GET // and the HttpConnection managed to successfully // authenticate under the hood with NTLM. We might not // have picked this up on the GET's 200 response. if (authMethod.getType() != HttpAuthMethod.Type.NONE) { ignoreTypes.add(authMethod.getType()); } // Start over with the remaining available methods. authMethod = HttpAuthMethod.Type.NONE.method(null); authenticator = authMethod; authAttempts = 1; continue; } throw e; } } } void openResponse() throws IOException { final int status = HttpSupport.response(conn); if (status != HttpConnection.HTTP_OK) { throw new TransportException(uri, status + " " //$NON-NLS-1$ + conn.getResponseMessage()); } final String contentType = conn.getContentType(); if (!responseType.equals(contentType)) { conn.getInputStream().close(); throw wrongContentType(responseType, contentType); } } HttpOutputStream getOutputStream() { return out; } InputStream getInputStream() { return in; } abstract void execute() throws IOException; class HttpExecuteStream extends InputStream { @Override public int read() throws IOException { execute(); return -1; } @Override public int read(byte[] b, int off, int len) throws IOException { execute(); return -1; } @Override public long skip(long n) throws IOException { execute(); return 0; } } class HttpOutputStream extends TemporaryBuffer { HttpOutputStream() { super(http.getPostBuffer()); } @Override protected OutputStream overflow() throws IOException { openStream(); conn.setChunkedStreamingMode(0); return conn.getOutputStream(); } } } /** * State required to speak multiple HTTP requests with the remote. *

* A service wrapper provides a normal looking InputStream and OutputStream * pair which are connected via HTTP to the named remote service. Writing to * the OutputStream is buffered until either the buffer overflows, or * reading from the InputStream occurs. If overflow occurs HTTP/1.1 and its * chunked transfer encoding is used to stream the request data to the * remote service. If the entire request fits in the memory buffer, the * older HTTP/1.0 standard and a fixed content length is used instead. *

* It is an error to attempt to read without there being outstanding data * ready for transmission on the OutputStream. *

* No state is preserved between write-read request pairs. The caller is * responsible for replaying state vector information as part of the request * data written to the OutputStream. Any session HTTP cookies may or may not * be preserved between requests, it is left up to the JVM's implementation * of the HTTP client. */ class MultiRequestService extends Service { boolean finalRequest; MultiRequestService(String serviceName, TransferConfig.ProtocolVersion protocolVersion) { super(serviceName, protocolVersion); } /** Keep opening send-receive pairs to the given URI. */ @Override void execute() throws IOException { out.close(); if (conn == null) { if (out.length() == 0) { // Request output hasn't started yet, but more data is being // requested. If there is no request data buffered and the // final request was already sent, do nothing to ensure the // caller is shown EOF on the InputStream; otherwise an // programming error has occurred within this module. if (finalRequest) return; throw new TransportException(uri, JGitText.get().startingReadStageWithoutWrittenRequestDataPendingIsNotSupported); } sendRequest(); } out.reset(); openResponse(); in.add(openInputStream(conn)); if (!finalRequest) in.add(execute); conn = null; } } /** Service for maintaining a single long-poll connection. */ class LongPollService extends Service { LongPollService(String serviceName, TransferConfig.ProtocolVersion protocolVersion) { super(serviceName, protocolVersion); } /** Only open one send-receive request. */ @Override void execute() throws IOException { out.close(); if (conn == null) sendRequest(); openResponse(); in.add(openInputStream(conn)); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11076 Content-Disposition: inline; filename="TransportLocal.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "1b9431ce6ea68538aa3d0019f3eaf6f400d1c5e7" /* * Copyright (C) 2007, Dave Watson * Copyright (C) 2008, 2010 Google Inc. * Copyright (C) 2008, Marek Zawirski * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, 2020 Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.UploadPackFactory; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; /** * Transport to access a local directory as though it were a remote peer. *

* This transport is suitable for use on the local system, where the caller has * direct read or write access to the "remote" repository. *

* By default this transport works by spawning a helper thread within the same * JVM, and processes the data transfer using a shared memory buffer between the * calling thread and the helper thread. This is a pure-Java implementation * which does not require forking an external process. *

* However, during {@link #openFetch()}, if the Transport has configured * {@link Transport#getOptionUploadPack()} to be anything other than * "git-upload-pack" or "git upload-pack", this * implementation will fork and execute the external process, using an operating * system pipe to transfer data. *

* Similarly, during {@link #openPush()}, if the Transport has configured * {@link Transport#getOptionReceivePack()} to be anything other than * "git-receive-pack" or "git receive-pack", this * implementation will fork and execute the external process, using an operating * system pipe to transfer data. */ class TransportLocal extends Transport implements PackTransport { static final TransportProtocol PROTO_LOCAL = new TransportProtocol() { @Override public String getName() { return JGitText.get().transportProtoLocal; } @Override public Set getSchemes() { return Collections.singleton("file"); //$NON-NLS-1$ } @Override public boolean canHandle(URIish uri, Repository local, String remoteName) { if (uri.getPath() == null || uri.getPort() > 0 || uri.getUser() != null || uri.getPass() != null || uri.getHost() != null || (uri.getScheme() != null && !getSchemes().contains(uri.getScheme()))) return false; return true; } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NoRemoteRepositoryException { File localPath = local.isBare() ? local.getDirectory() : local.getWorkTree(); File path = local.getFS().resolve(localPath, uri.getPath()); // If the reference is to a local file, C Git behavior says // assume this is a bundle, since repositories are directories. if (path.isFile()) return new TransportBundleFile(local, uri, path); File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS()); if (gitDir == null) throw new NoRemoteRepositoryException(uri, JGitText.get().notFound); return new TransportLocal(local, uri, gitDir); } @Override public Transport open(URIish uri) throws NotSupportedException, TransportException { File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$ // If the reference is to a local file, C Git behavior says // assume this is a bundle, since repositories are directories. if (path.isFile()) return new TransportBundleFile(uri, path); File gitDir = RepositoryCache.FileKey.resolve(path, FS.DETECTED); if (gitDir == null) throw new NoRemoteRepositoryException(uri, JGitText.get().notFound); return new TransportLocal(uri, gitDir); } }; private final File remoteGitDir; TransportLocal(Repository local, URIish uri, File gitDir) { super(local, uri); remoteGitDir = gitDir; } TransportLocal(URIish uri, File gitDir) { super(uri); remoteGitDir = gitDir; } UploadPack createUploadPack(Repository dst) { return new UploadPack(dst); } ReceivePack createReceivePack(Repository dst) { return new ReceivePack(dst); } private Repository openRepo() throws TransportException { try { return new RepositoryBuilder() .setFS(local != null ? local.getFS() : FS.DETECTED) .setGitDir(remoteGitDir).build(); } catch (IOException err) { TransportException te = new TransportException(uri, JGitText.get().notAGitDirectory); te.initCause(err); throw te; } } @Override public FetchConnection openFetch() throws TransportException { return openFetch(Collections.emptyList()); } @Override public FetchConnection openFetch(Collection refSpecs, String... additionalPatterns) throws TransportException { final String up = getOptionUploadPack(); if (!"git-upload-pack".equals(up) //$NON-NLS-1$ && !"git upload-pack".equals(up)) {//$NON-NLS-1$ return new ForkLocalFetchConnection(refSpecs, additionalPatterns); } UploadPackFactory upf = (Void req, Repository db) -> createUploadPack(db); return new InternalFetchConnection<>(this, upf, null, openRepo()); } @Override public PushConnection openPush() throws TransportException { final String rp = getOptionReceivePack(); if (!"git-receive-pack".equals(rp) //$NON-NLS-1$ && !"git receive-pack".equals(rp)) //$NON-NLS-1$ return new ForkLocalPushConnection(); ReceivePackFactory rpf = (Void req, Repository db) -> createReceivePack(db); return new InternalPushConnection<>(this, rpf, null, openRepo()); } @Override public void close() { // Resources must be established per-connection. } /** * Spawn process * * @param cmd * command * @return a {@link java.lang.Process} object. * @throws org.eclipse.jgit.errors.TransportException * if any. */ protected Process spawn(String cmd) throws TransportException { return spawn(cmd, null); } /** * Spawn process * * @param cmd * command * @param protocolVersion * to use * @return a {@link java.lang.Process} object. * @throws org.eclipse.jgit.errors.TransportException * if any. */ private Process spawn(String cmd, TransferConfig.ProtocolVersion protocolVersion) throws TransportException { try { String[] args = { "." }; //$NON-NLS-1$ ProcessBuilder proc = local.getFS().runInShell(cmd, args); proc.directory(remoteGitDir); // Remove the same variables CGit does. Map env = proc.environment(); env.remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); //$NON-NLS-1$ env.remove("GIT_CONFIG"); //$NON-NLS-1$ env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$ env.remove("GIT_DIR"); //$NON-NLS-1$ env.remove("GIT_COMMON_DIR"); //$NON-NLS-1$ env.remove("GIT_WORK_TREE"); //$NON-NLS-1$ env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$ env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$ env.remove("GIT_NO_REPLACE_OBJECTS"); //$NON-NLS-1$ if (TransferConfig.ProtocolVersion.V2.equals(protocolVersion)) { env.put(GitProtocolConstants.PROTOCOL_ENVIRONMENT_VARIABLE, GitProtocolConstants.VERSION_2_REQUEST); } return proc.start(); } catch (IOException err) { throw new TransportException(uri, err.getMessage(), err); } } class ForkLocalFetchConnection extends BasePackFetchConnection { private Process uploadPack; private Thread errorReaderThread; ForkLocalFetchConnection() throws TransportException { this(Collections.emptyList()); } ForkLocalFetchConnection(Collection refSpecs, String... additionalPatterns) throws TransportException { super(TransportLocal.this); final MessageWriter msg = new MessageWriter(); setMessageWriter(msg); TransferConfig.ProtocolVersion gitProtocol = protocol; if (gitProtocol == null) { gitProtocol = TransferConfig.ProtocolVersion.V2; } uploadPack = spawn(getOptionUploadPack(), gitProtocol); final InputStream upErr = uploadPack.getErrorStream(); errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream()); errorReaderThread.start(); InputStream upIn = uploadPack.getInputStream(); OutputStream upOut = uploadPack.getOutputStream(); upIn = new BufferedInputStream(upIn); upOut = new BufferedOutputStream(upOut); init(upIn, upOut); if (!readAdvertisedRefs()) { lsRefs(refSpecs, additionalPatterns); } } @Override public void close() { super.close(); if (uploadPack != null) { try { uploadPack.waitFor(); } catch (InterruptedException ie) { // Stop waiting and return anyway. } finally { uploadPack = null; } } if (errorReaderThread != null) { try { errorReaderThread.join(); } catch (InterruptedException e) { // Stop waiting and return anyway. } finally { errorReaderThread = null; } } } } class ForkLocalPushConnection extends BasePackPushConnection { private Process receivePack; private Thread errorReaderThread; ForkLocalPushConnection() throws TransportException { super(TransportLocal.this); final MessageWriter msg = new MessageWriter(); setMessageWriter(msg); receivePack = spawn(getOptionReceivePack()); final InputStream rpErr = receivePack.getErrorStream(); errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream()); errorReaderThread.start(); InputStream rpIn = receivePack.getInputStream(); OutputStream rpOut = receivePack.getOutputStream(); rpIn = new BufferedInputStream(rpIn); rpOut = new BufferedOutputStream(rpOut); init(rpIn, rpOut); readAdvertisedRefs(); } @Override public void close() { super.close(); if (receivePack != null) { try { receivePack.waitFor(); } catch (InterruptedException ie) { // Stop waiting and return anyway. } finally { receivePack = null; } } if (errorReaderThread != null) { try { errorReaderThread.join(); } catch (InterruptedException e) { // Stop waiting and return anyway. } finally { errorReaderThread = null; } } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8748 Content-Disposition: inline; filename="TransportProtocol.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "bfb840d75f485c89b7f5745c880077bb2eaf1a0a" /* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Collections; import java.util.EnumSet; import java.util.Set; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; /** * Describes a way to connect to another Git repository. *

* Implementations of this class are typically immutable singletons held by * static class members, for example: * *

 * package com.example.my_transport;
 *
 * class MyTransport extends Transport {
 * 	public static final TransportProtocol PROTO = new TransportProtocol() {
 * 		public String getName() {
 * 			return "My Protocol";
 * 		}
 * 	};
 * }
 * 
* *

* Applications may register additional protocols for use by JGit by calling * {@link org.eclipse.jgit.transport.Transport#register(TransportProtocol)}. * Because that API holds onto the protocol object by a WeakReference, * applications must ensure their own ClassLoader retains the TransportProtocol * for the life of the application. Using a static singleton pattern as above * will ensure the protocol is valid so long as the ClassLoader that defines it * remains valid. *

* Applications may automatically register additional protocols by filling in * the names of their TransportProtocol defining classes using the services file * {@code META-INF/services/org.eclipse.jgit.transport.Transport}. For each * class name listed in the services file, any static fields of type * {@code TransportProtocol} will be automatically registered. For the above * example the string {@code com.example.my_transport.MyTransport} should be * listed in the file, as that is the name of the class that defines the static * PROTO singleton. */ public abstract class TransportProtocol { /** Fields within a {@link URIish} that a transport uses. */ public enum URIishField { /** the user field */ USER, /** the pass (aka password) field */ PASS, /** the host field */ HOST, /** the port field */ PORT, /** the path field */ PATH, } /** * Get text name of the protocol suitable for display to a user. * * @return text name of the protocol suitable for display to a user. */ public abstract String getName(); /** * Get immutable set of schemes supported by this protocol. * * @return immutable set of schemes supported by this protocol. */ public Set getSchemes() { return Collections.emptySet(); } /** * Get immutable set of URIishFields that must be filled in. * * @return immutable set of URIishFields that must be filled in. */ public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.PATH)); } /** * Get immutable set of URIishFields that may be filled in. * * @return immutable set of URIishFields that may be filled in. */ public Set getOptionalFields() { return Collections.emptySet(); } /** * Get the default port if the protocol supports a port, else -1. * * @return the default port if the protocol supports a port, else -1. */ public int getDefaultPort() { return -1; } /** * Determine if this protocol can handle a particular URI. *

* Implementations should try to avoid looking at the local filesystem, but * may look at implementation specific configuration options in the remote * block of {@code local.getConfig()} using {@code remoteName} if the name * is non-null. *

* The default implementation of this method matches the scheme against * {@link #getSchemes()}, required fields against * {@link #getRequiredFields()}, and optional fields against * {@link #getOptionalFields()}, returning true only if all of the fields * match the specification. * * @param uri * address of the Git repository; never null. * @return true if this protocol can handle this URI; false otherwise. */ public boolean canHandle(URIish uri) { return canHandle(uri, null, null); } /** * Determine if this protocol can handle a particular URI. *

* Implementations should try to avoid looking at the local filesystem, but * may look at implementation specific configuration options in the remote * block of {@code local.getConfig()} using {@code remoteName} if the name * is non-null. *

* The default implementation of this method matches the scheme against * {@link #getSchemes()}, required fields against * {@link #getRequiredFields()}, and optional fields against * {@link #getOptionalFields()}, returning true only if all of the fields * match the specification. * * @param uri * address of the Git repository; never null. * @param local * the local repository that will communicate with the other Git * repository. May be null if the caller is only asking about a * specific URI and does not have a local Repository. * @param remoteName * name of the remote, if the remote as configured in * {@code local}; otherwise null. * @return true if this protocol can handle this URI; false otherwise. */ public boolean canHandle(URIish uri, Repository local, String remoteName) { if (!getSchemes().isEmpty() && !getSchemes().contains(uri.getScheme())) return false; for (URIishField field : getRequiredFields()) { switch (field) { case USER: if (uri.getUser() == null || uri.getUser().length() == 0) return false; break; case PASS: if (uri.getPass() == null || uri.getPass().length() == 0) return false; break; case HOST: if (uri.getHost() == null || uri.getHost().length() == 0) return false; break; case PORT: if (uri.getPort() <= 0) return false; break; case PATH: if (uri.getPath() == null || uri.getPath().length() == 0) return false; break; default: return false; } } Set canHave = EnumSet.copyOf(getRequiredFields()); canHave.addAll(getOptionalFields()); if (uri.getUser() != null && !canHave.contains(URIishField.USER)) return false; if (uri.getPass() != null && !canHave.contains(URIishField.PASS)) return false; if (uri.getHost() != null && !canHave.contains(URIishField.HOST)) return false; if (uri.getPort() > 0 && !canHave.contains(URIishField.PORT)) return false; if (uri.getPath() != null && !canHave.contains(URIishField.PATH)) return false; return true; } /** * Open a Transport instance to the other repository. *

* Implementations should avoid making remote connections until an operation * on the returned Transport is invoked, however they may fail fast here if * they know a connection is impossible, such as when using the local * filesystem and the target path does not exist. *

* Implementations may access implementation-specific configuration options * within {@code local.getConfig()} using the remote block named by the * {@code remoteName}, if the name is non-null. * * @param uri * address of the Git repository. * @param local * the local repository that will communicate with the other Git * repository. * @param remoteName * name of the remote, if the remote as configured in * {@code local}; otherwise null. * @return the transport. * @throws org.eclipse.jgit.errors.NotSupportedException * this protocol does not support the URI. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public abstract Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException, TransportException; /** * Open a new transport instance to the remote repository. Use default * configuration instead of reading from configuration files. * * @param uri * a {@link org.eclipse.jgit.transport.URIish} object. * @return new Transport * @throws org.eclipse.jgit.errors.NotSupportedException * this protocol does not support the URI. * @throws org.eclipse.jgit.errors.TransportException * the transport cannot open this URI. */ public Transport open(URIish uri) throws NotSupportedException, TransportException { throw new NotSupportedException(JGitText .get().transportNeedsRepository); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12641 Content-Disposition: inline; filename="TransportSftp.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "a59d352e0da5f7bc97ce6a270d51bd7c401fa569" /* * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES; import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; import static org.eclipse.jgit.lib.Constants.OBJECTS; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; /** * Transport over the non-Git aware SFTP (SSH based FTP) protocol. *

* The SFTP transport does not require any specialized Git support on the remote * (server side) repository. Object files are retrieved directly through secure * shell's FTP protocol, making it possible to copy objects from a remote * repository that is available over SSH, but whose remote host does not have * Git installed. *

* Unlike the HTTP variant (see * {@link org.eclipse.jgit.transport.TransportHttp}) we rely upon being able to * list files in directories, as the SFTP protocol supports this function. By * listing files through SFTP we can avoid needing to have current * objects/info/packs or info/refs files on the remote * repository and access the data directly, much as Git itself would. *

* Concurrent pushing over this transport is not supported. Multiple concurrent * push operations may cause confusion in the repository state. * * @see WalkFetchConnection */ public class TransportSftp extends SshTransport implements WalkTransport { static final TransportProtocol PROTO_SFTP = new TransportProtocol() { @Override public String getName() { return JGitText.get().transportProtoSFTP; } @Override public Set getSchemes() { return Collections.singleton("sftp"); //$NON-NLS-1$ } @Override public Set getRequiredFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST, URIishField.PATH)); } @Override public Set getOptionalFields() { return Collections.unmodifiableSet(EnumSet.of(URIishField.USER, URIishField.PASS, URIishField.PORT)); } @Override public int getDefaultPort() { return 22; } @Override public Transport open(URIish uri, Repository local, String remoteName) throws NotSupportedException { return new TransportSftp(local, uri); } }; TransportSftp(Repository local, URIish uri) { super(local, uri); } @Override public FetchConnection openFetch() throws TransportException { final SftpObjectDB c = new SftpObjectDB(uri.getPath()); final WalkFetchConnection r = new WalkFetchConnection(this, c); r.available(c.readAdvertisedRefs()); return r; } @Override public PushConnection openPush() throws TransportException { final SftpObjectDB c = new SftpObjectDB(uri.getPath()); final WalkPushConnection r = new WalkPushConnection(this, c); r.available(c.readAdvertisedRefs()); return r; } FtpChannel newSftp() throws IOException { FtpChannel channel = getSession().getFtpChannel(); channel.connect(getTimeout(), TimeUnit.SECONDS); return channel; } class SftpObjectDB extends WalkRemoteObjectDatabase { private final String objectsPath; private FtpChannel ftp; SftpObjectDB(String path) throws TransportException { if (path.startsWith("/~")) //$NON-NLS-1$ path = path.substring(1); if (path.startsWith("~/")) //$NON-NLS-1$ path = path.substring(2); try { ftp = newSftp(); ftp.cd(path); ftp.cd(OBJECTS); objectsPath = ftp.pwd(); } catch (FtpChannel.FtpException f) { throw new TransportException(MessageFormat.format( JGitText.get().cannotEnterObjectsPath, path, f.getMessage()), f); } catch (IOException ioe) { close(); throw new TransportException(uri, ioe.getMessage(), ioe); } } SftpObjectDB(SftpObjectDB parent, String p) throws TransportException { try { ftp = newSftp(); ftp.cd(parent.objectsPath); ftp.cd(p); objectsPath = ftp.pwd(); } catch (FtpChannel.FtpException f) { throw new TransportException(MessageFormat.format( JGitText.get().cannotEnterPathFromParent, p, parent.objectsPath, f.getMessage()), f); } catch (IOException ioe) { close(); throw new TransportException(uri, ioe.getMessage(), ioe); } } @Override URIish getURI() { return uri.setPath(objectsPath); } @Override Collection getAlternates() throws IOException { try { return readAlternates(INFO_ALTERNATES); } catch (FileNotFoundException err) { return null; } } @Override WalkRemoteObjectDatabase openAlternate(String location) throws IOException { return new SftpObjectDB(this, location); } @Override Collection getPackNames() throws IOException { final List packs = new ArrayList<>(); try { Collection list = ftp.ls("pack"); //$NON-NLS-1$ Set files = list.stream() .map(FtpChannel.DirEntry::getFilename) .collect(Collectors.toSet()); HashMap mtimes = new HashMap<>(); for (FtpChannel.DirEntry ent : list) { String n = ent.getFilename(); if (!n.startsWith("pack-") || !n.endsWith(".pack")) { //$NON-NLS-1$ //$NON-NLS-2$ continue; } String in = n.substring(0, n.length() - 5) + ".idx"; //$NON-NLS-1$ if (!files.contains(in)) { continue; } mtimes.put(n, Long.valueOf(ent.getModifiedTime())); packs.add(n); } Collections.sort(packs, (o1, o2) -> mtimes.get(o2).compareTo(mtimes.get(o1))); } catch (FtpChannel.FtpException f) { throw new TransportException( MessageFormat.format(JGitText.get().cannotListPackPath, objectsPath, f.getMessage()), f); } return packs; } @Override FileStream open(String path) throws IOException { try { return new FileStream(ftp.get(path)); } catch (FtpChannel.FtpException f) { if (f.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) { throw new FileNotFoundException(path); } throw new TransportException(MessageFormat.format( JGitText.get().cannotGetObjectsPath, objectsPath, path, f.getMessage()), f); } } @Override void deleteFile(String path) throws IOException { try { ftp.delete(path); } catch (FtpChannel.FtpException f) { throw new TransportException(MessageFormat.format( JGitText.get().cannotDeleteObjectsPath, objectsPath, path, f.getMessage()), f); } // Prune any now empty directories. // String dir = path; int s = dir.lastIndexOf('/'); while (s > 0) { try { dir = dir.substring(0, s); ftp.rmdir(dir); s = dir.lastIndexOf('/'); } catch (IOException je) { // If we cannot delete it, leave it alone. It may have // entries still in it, or maybe we lack write access on // the parent. Either way it isn't a fatal error. // break; } } } @Override OutputStream writeFile(String path, ProgressMonitor monitor, String monitorTask) throws IOException { Throwable err = null; try { return ftp.put(path); } catch (FileNotFoundException e) { mkdir_p(path); } catch (FtpChannel.FtpException je) { if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) { mkdir_p(path); } else { err = je; } } if (err == null) { try { return ftp.put(path); } catch (IOException e) { err = e; } } throw new TransportException( MessageFormat.format(JGitText.get().cannotWriteObjectsPath, objectsPath, path, err.getMessage()), err); } @Override void writeFile(String path, byte[] data) throws IOException { final String lock = path + LOCK_SUFFIX; try { super.writeFile(lock, data); try { ftp.rename(lock, path); } catch (IOException e) { throw new TransportException(MessageFormat.format( JGitText.get().cannotWriteObjectsPath, objectsPath, path, e.getMessage()), e); } } catch (IOException err) { try { ftp.rm(lock); } catch (IOException e) { // Ignore deletion failure, we are already // failing anyway. } throw err; } } private void mkdir_p(String path) throws IOException { final int s = path.lastIndexOf('/'); if (s <= 0) return; path = path.substring(0, s); Throwable err = null; try { ftp.mkdir(path); return; } catch (FileNotFoundException f) { mkdir_p(path); } catch (FtpChannel.FtpException je) { if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) { mkdir_p(path); } else { err = je; } } if (err == null) { try { ftp.mkdir(path); return; } catch (IOException e) { err = e; } } throw new TransportException(MessageFormat.format( JGitText.get().cannotMkdirObjectPath, objectsPath, path, err.getMessage()), err); } Map readAdvertisedRefs() throws TransportException { final TreeMap avail = new TreeMap<>(); readPackedRefs(avail); readRef(avail, ROOT_DIR + Constants.HEAD, Constants.HEAD); readLooseRefs(avail, ROOT_DIR + "refs", "refs/"); //$NON-NLS-1$ //$NON-NLS-2$ return avail; } private void readLooseRefs(TreeMap avail, String dir, String prefix) throws TransportException { final Collection list; try { list = ftp.ls(dir); } catch (IOException e) { throw new TransportException(MessageFormat.format( JGitText.get().cannotListObjectsPath, objectsPath, dir, e.getMessage()), e); } for (FtpChannel.DirEntry ent : list) { String n = ent.getFilename(); if (".".equals(n) || "..".equals(n)) //$NON-NLS-1$ //$NON-NLS-2$ continue; String nPath = dir + "/" + n; //$NON-NLS-1$ if (ent.isDirectory()) { readLooseRefs(avail, nPath, prefix + n + "/"); //$NON-NLS-1$ } else { readRef(avail, nPath, prefix + n); } } } private Ref readRef(TreeMap avail, String path, String name) throws TransportException { final String line; try (BufferedReader br = openReader(path)) { line = br.readLine(); } catch (FileNotFoundException noRef) { return null; } catch (IOException err) { throw new TransportException(MessageFormat.format( JGitText.get().cannotReadObjectsPath, objectsPath, path, err.getMessage()), err); } if (line == null) { throw new TransportException( MessageFormat.format(JGitText.get().emptyRef, name)); } if (line.startsWith("ref: ")) { //$NON-NLS-1$ final String target = line.substring("ref: ".length()); //$NON-NLS-1$ Ref r = avail.get(target); if (r == null) r = readRef(avail, ROOT_DIR + target, target); if (r == null) r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null); r = new SymbolicRef(name, r); avail.put(r.getName(), r); return r; } if (ObjectId.isId(line)) { final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(name)), name, ObjectId.fromString(line)); avail.put(r.getName(), r); return r; } throw new TransportException( MessageFormat.format(JGitText.get().badRef, name, line)); } private Storage loose(Ref r) { if (r != null && r.getStorage() == Storage.PACKED) { return Storage.LOOSE_PACKED; } return Storage.LOOSE; } @Override void close() { if (ftp != null) { try { if (ftp.isConnected()) { ftp.disconnect(); } } finally { ftp = null; } } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 20608 Content-Disposition: inline; filename="URIish.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "7b5842b7123fc96220aa8239c15d5a375352ecc5" /* * Copyright (C) 2009, Mykola Nikishov * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2013, Robin Stocker * Copyright (C) 2015, Patrick Steinhardt and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.Serializable; import java.net.URISyntaxException; import java.net.URL; import java.util.BitSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.References; import org.eclipse.jgit.util.StringUtils; /** * This URI like construct used for referencing Git archives over the net, as * well as locally stored archives. It is similar to RFC 2396 URI's, but also * support SCP and the malformed file://<path> syntax (as opposed to the correct * file:<path> syntax. */ public class URIish implements Serializable { /** * Part of a pattern which matches the scheme part (git, http, ...) of an * URI. Defines one capturing group containing the scheme without the * trailing colon and slashes */ private static final String SCHEME_P = "([a-z][a-z0-9+-]+)://"; //$NON-NLS-1$ /** * Part of a pattern which matches the optional user/password part (e.g. * root:pwd@ in git://root:pwd@host.xyz/a.git) of URIs. Defines two * capturing groups: the first containing the user and the second containing * the password */ private static final String OPT_USER_PWD_P = "(?:([^/:]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$ /** * Part of a pattern which matches the host part of URIs. Defines one * capturing group containing the host name. */ private static final String HOST_P = "((?:[^\\\\/:]+)|(?:\\[[0-9a-f:]+\\]))"; //$NON-NLS-1$ /** * Part of a pattern which matches the optional port part of URIs. Defines * one capturing group containing the port without the preceding colon. */ private static final String OPT_PORT_P = "(?::(\\d*))?"; //$NON-NLS-1$ /** * Part of a pattern which matches the ~username part (e.g. /~root in * git://host.xyz/~root/a.git) of URIs. Defines no capturing group. */ private static final String USER_HOME_P = "(?:/~(?:[^\\\\/]+))"; //$NON-NLS-1$ /** * Part of a pattern which matches the optional drive letter in paths (e.g. * D: in file:///D:/a.txt). Defines no capturing group. */ private static final String OPT_DRIVE_LETTER_P = "(?:[A-Za-z]:)?"; //$NON-NLS-1$ /** * Part of a pattern which matches a relative path. Relative paths don't * start with slash or drive letters. Defines no capturing group. */ private static final String RELATIVE_PATH_P = "(?:(?:[^\\\\/]+[\\\\/]+)*+[^\\\\/]*)"; //$NON-NLS-1$ /** * Part of a pattern which matches a relative or absolute path. Defines no * capturing group. */ private static final String PATH_P = "(" + OPT_DRIVE_LETTER_P + "[\\\\/]?" //$NON-NLS-1$ //$NON-NLS-2$ + RELATIVE_PATH_P + ")"; //$NON-NLS-1$ private static final long serialVersionUID = 1L; /** * A pattern matching standard URI:
* scheme "://" user_password? hostname? portnumber? path */ private static final Pattern FULL_URI = Pattern.compile("^" // //$NON-NLS-1$ + SCHEME_P // + "(?:" // start a group containing hostname and all options only //$NON-NLS-1$ // availabe when a hostname is there + OPT_USER_PWD_P // + HOST_P // + OPT_PORT_P // + "(" // open a group capturing the user-home-dir-part //$NON-NLS-1$ + (USER_HOME_P + "?") //$NON-NLS-1$ + "(?:" // start non capturing group for host //$NON-NLS-1$ // separator or end of line + "[\\\\/])|$" //$NON-NLS-1$ + ")" // close non capturing group for the host//$NON-NLS-1$ // separator or end of line + ")?" // close the optional group containing hostname //$NON-NLS-1$ + "(.+)?" //$NON-NLS-1$ + "$"); //$NON-NLS-1$ /** * A pattern matching the reference to a local file. This may be an absolute * path (maybe even containing windows drive-letters) or a relative path. */ private static final Pattern LOCAL_FILE = Pattern.compile("^" // //$NON-NLS-1$ + "([\\\\/]?+" + PATH_P + ")" // //$NON-NLS-1$ //$NON-NLS-2$ + "$"); //$NON-NLS-1$ /** * A pattern matching a URI for the scheme 'file' which has only ':/' as * separator between scheme and path. Standard file URIs have '://' as * separator, but java.io.File.toURI() constructs those URIs. */ private static final Pattern SINGLE_SLASH_FILE_URI = Pattern.compile("^" // //$NON-NLS-1$ + "(file):([\\\\/](?![\\\\/])" // //$NON-NLS-1$ + PATH_P // + ")$"); //$NON-NLS-1$ /** * A pattern matching a SCP URI's of the form user@host:path/to/repo.git */ private static final Pattern RELATIVE_SCP_URI = Pattern.compile("^" // //$NON-NLS-1$ + OPT_USER_PWD_P // + HOST_P // + ":(" // //$NON-NLS-1$ + ("(?:" + USER_HOME_P + "[\\\\/])?") // //$NON-NLS-1$ //$NON-NLS-2$ + RELATIVE_PATH_P // + ")$"); //$NON-NLS-1$ /** * A pattern matching a SCP URI's of the form user@host:/path/to/repo.git */ private static final Pattern ABSOLUTE_SCP_URI = Pattern.compile("^" // //$NON-NLS-1$ + OPT_USER_PWD_P // + "([^\\\\/:]{2,})" // //$NON-NLS-1$ + ":(" // //$NON-NLS-1$ + "[\\\\/]" + RELATIVE_PATH_P // //$NON-NLS-1$ + ")$"); //$NON-NLS-1$ private String scheme; private String path; private String rawPath; private String user; private String pass; private int port = -1; private String host; /** * Parse and construct an {@link org.eclipse.jgit.transport.URIish} from a * string * * @param s * a {@link java.lang.String} object. * @throws java.net.URISyntaxException * if {@code s} was null or couldn't be parsed */ public URIish(String s) throws URISyntaxException { if (StringUtils.isEmptyOrNull(s)) { throw new URISyntaxException("The uri was empty or null", //$NON-NLS-1$ JGitText.get().cannotParseGitURIish); } Matcher matcher = SINGLE_SLASH_FILE_URI.matcher(s); if (matcher.matches()) { scheme = matcher.group(1); rawPath = cleanLeadingSlashes(matcher.group(2), scheme); path = unescape(rawPath); return; } matcher = FULL_URI.matcher(s); if (matcher.matches()) { scheme = matcher.group(1); user = unescape(matcher.group(2)); pass = unescape(matcher.group(3)); // empty ports are in general allowed, except for URLs like // file://D:/path for which it is more desirable to parse with // host=null and path=D:/path String portString = matcher.group(5); if ("file".equals(scheme) && "".equals(portString)) { //$NON-NLS-1$ //$NON-NLS-2$ rawPath = cleanLeadingSlashes( n2e(matcher.group(4)) + ":" + portString //$NON-NLS-1$ + n2e(matcher.group(6)) + n2e(matcher.group(7)), scheme); } else { host = unescape(matcher.group(4)); if (portString != null && portString.length() > 0) { port = Integer.parseInt(portString); } rawPath = cleanLeadingSlashes( n2e(matcher.group(6)) + n2e(matcher.group(7)), scheme); } path = unescape(rawPath); return; } matcher = RELATIVE_SCP_URI.matcher(s); if (matcher.matches()) { user = matcher.group(1); pass = matcher.group(2); host = matcher.group(3); rawPath = matcher.group(4); path = rawPath; return; } matcher = ABSOLUTE_SCP_URI.matcher(s); if (matcher.matches()) { user = matcher.group(1); pass = matcher.group(2); host = matcher.group(3); rawPath = matcher.group(4); path = rawPath; return; } matcher = LOCAL_FILE.matcher(s); if (matcher.matches()) { rawPath = matcher.group(1); path = rawPath; return; } throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish); } private static int parseHexByte(byte c1, byte c2) { return ((RawParseUtils.parseHexInt4(c1) << 4) | RawParseUtils.parseHexInt4(c2)); } private static String unescape(String s) throws URISyntaxException { if (s == null) return null; if (s.indexOf('%') < 0) return s; byte[] bytes = s.getBytes(UTF_8); byte[] os = new byte[bytes.length]; int j = 0; for (int i = 0; i < bytes.length; ++i) { byte c = bytes[i]; if (c == '%') { if (i + 2 >= bytes.length) throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish); byte c1 = bytes[i + 1]; byte c2 = bytes[i + 2]; int val; try { val = parseHexByte(c1, c2); } catch (ArrayIndexOutOfBoundsException e) { URISyntaxException use = new URISyntaxException(s, JGitText.get().cannotParseGitURIish); use.initCause(e); throw use; } os[j++] = (byte) val; i += 2; } else os[j++] = c; } return RawParseUtils.decode(os, 0, j); } private static final BitSet reservedChars = new BitSet(127); static { for (byte b : Constants.encodeASCII("!*'();:@&=+$,/?#[]")) //$NON-NLS-1$ reservedChars.set(b); } /** * Escape unprintable characters optionally URI-reserved characters * * @param s * The Java String to encode (may contain any character) * @param escapeReservedChars * true to escape URI reserved characters * @param encodeNonAscii * encode any non-ASCII characters * @return a URI-encoded string */ private static String escape(String s, boolean escapeReservedChars, boolean encodeNonAscii) { if (s == null) return null; ByteArrayOutputStream os = new ByteArrayOutputStream(s.length()); byte[] bytes = s.getBytes(UTF_8); for (byte c : bytes) { int b = c & 0xFF; if (b <= 32 || (encodeNonAscii && b > 127) || b == '%' || (escapeReservedChars && reservedChars.get(b))) { os.write('%'); byte[] tmp = Constants.encodeASCII(String.format("%02x", //$NON-NLS-1$ Integer.valueOf(b))); os.write(tmp[0]); os.write(tmp[1]); } else { os.write(b); } } byte[] buf = os.toByteArray(); return RawParseUtils.decode(buf, 0, buf.length); } private String n2e(String s) { return s == null ? "" : s; //$NON-NLS-1$ } // takes care to cut of a leading slash if a windows drive letter or a // user-home-dir specifications are private String cleanLeadingSlashes(String p, String s) { if (p.length() >= 3 && p.charAt(0) == '/' && p.charAt(2) == ':' && ((p.charAt(1) >= 'A' && p.charAt(1) <= 'Z') || (p.charAt(1) >= 'a' && p.charAt(1) <= 'z'))) return p.substring(1); else if (s != null && p.length() >= 2 && p.charAt(0) == '/' && p.charAt(1) == '~') return p.substring(1); else return p; } /** * Construct a URIish from a standard URL. * * @param u * the source URL to convert from. */ public URIish(URL u) { scheme = u.getProtocol(); path = u.getPath(); path = cleanLeadingSlashes(path, scheme); try { rawPath = u.toURI().getRawPath(); rawPath = cleanLeadingSlashes(rawPath, scheme); } catch (URISyntaxException e) { throw new RuntimeException(e); // Impossible } final String ui = u.getUserInfo(); if (ui != null) { final int d = ui.indexOf(':'); user = d < 0 ? ui : ui.substring(0, d); pass = d < 0 ? null : ui.substring(d + 1); } port = u.getPort(); host = u.getHost(); } /** * Create an empty, non-configured URI. */ public URIish() { // Configure nothing. } private URIish(URIish u) { this.scheme = u.scheme; this.rawPath = u.rawPath; this.path = u.path; this.user = u.user; this.pass = u.pass; this.port = u.port; this.host = u.host; } /** * Whether this URI references a repository on another system. * * @return true if this URI references a repository on another system. */ public boolean isRemote() { return getHost() != null; } /** * Get host name part. * * @return host name part or null */ public String getHost() { return host; } /** * Return a new URI matching this one, but with a different host. * * @param n * the new value for host. * @return a new URI with the updated value. */ public URIish setHost(String n) { final URIish r = new URIish(this); r.host = n; return r; } /** * Get protocol name * * @return protocol name or null for local references */ public String getScheme() { return scheme; } /** * Return a new URI matching this one, but with a different scheme. * * @param n * the new value for scheme. * @return a new URI with the updated value. */ public URIish setScheme(String n) { final URIish r = new URIish(this); r.scheme = n; return r; } /** * Get path name component * * @return path name component */ public String getPath() { return path; } /** * Get path name component * * @return path name component */ public String getRawPath() { return rawPath; } /** * Return a new URI matching this one, but with a different path. * * @param n * the new value for path. * @return a new URI with the updated value. */ public URIish setPath(String n) { final URIish r = new URIish(this); r.path = n; r.rawPath = n; return r; } /** * Return a new URI matching this one, but with a different (raw) path. * * @param n * the new value for path. * @return a new URI with the updated value. * @throws java.net.URISyntaxException * if URI couldn't be parsed from String */ public URIish setRawPath(String n) throws URISyntaxException { final URIish r = new URIish(this); r.path = unescape(n); r.rawPath = n; return r; } /** * Get user name requested for transfer * * @return user name requested for transfer or null */ public String getUser() { return user; } /** * Return a new URI matching this one, but with a different user. * * @param n * the new value for user. * @return a new URI with the updated value. */ public URIish setUser(String n) { final URIish r = new URIish(this); r.user = n; return r; } /** * Get password requested for transfer * * @return password requested for transfer or null */ public String getPass() { return pass; } /** * Return a new URI matching this one, but with a different password. * * @param n * the new value for password. * @return a new URI with the updated value. */ public URIish setPass(String n) { final URIish r = new URIish(this); r.pass = n; return r; } /** * Get port number requested for transfer or -1 if not explicit * * @return port number requested for transfer or -1 if not explicit */ public int getPort() { return port; } /** * Return a new URI matching this one, but with a different port. * * @param n * the new value for port. * @return a new URI with the updated value. */ public URIish setPort(int n) { final URIish r = new URIish(this); r.port = n > 0 ? n : -1; return r; } @Override public int hashCode() { int hc = 0; if (getScheme() != null) hc = hc * 31 + getScheme().hashCode(); if (getUser() != null) hc = hc * 31 + getUser().hashCode(); if (getPass() != null) hc = hc * 31 + getPass().hashCode(); if (getHost() != null) hc = hc * 31 + getHost().hashCode(); if (getPort() > 0) hc = hc * 31 + getPort(); if (getPath() != null) hc = hc * 31 + getPath().hashCode(); return hc; } @Override public boolean equals(Object obj) { if (!(obj instanceof URIish)) return false; final URIish b = (URIish) obj; if (!eq(getScheme(), b.getScheme())) return false; if (!eq(getUser(), b.getUser())) return false; if (!eq(getPass(), b.getPass())) return false; if (!eq(getHost(), b.getHost())) return false; if (getPort() != b.getPort()) return false; if (!eq(getPath(), b.getPath())) return false; return true; } private static boolean eq(String a, String b) { if (References.isSameObject(a, b)) { return true; } if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b)) return true; if (a == null || b == null) return false; return a.equals(b); } /** * Obtain the string form of the URI, with the password included. * * @return the URI, including its password field, if any. */ public String toPrivateString() { return format(true, false); } @Override public String toString() { return format(false, false); } private String format(boolean includePassword, boolean escapeNonAscii) { final StringBuilder r = new StringBuilder(); if (getScheme() != null) { r.append(getScheme()); r.append("://"); //$NON-NLS-1$ } if (getUser() != null) { r.append(escape(getUser(), true, escapeNonAscii)); if (includePassword && getPass() != null) { r.append(':'); r.append(escape(getPass(), true, escapeNonAscii)); } } if (getHost() != null) { if (getUser() != null && getUser().length() > 0) r.append('@'); r.append(escape(getHost(), false, escapeNonAscii)); if (getScheme() != null && getPort() > 0) { r.append(':'); r.append(getPort()); } } if (getPath() != null) { if (getScheme() != null) { if (!getPath().startsWith("/") && !getPath().isEmpty()) //$NON-NLS-1$ r.append('/'); } else if (getHost() != null) r.append(':'); if (getScheme() != null) if (escapeNonAscii) r.append(escape(getPath(), false, escapeNonAscii)); else r.append(getRawPath()); else r.append(getPath()); } return r.toString(); } /** * Get the URI as an ASCII string. * * @return the URI as an ASCII string. Password is not included. */ public String toASCIIString() { return format(false, true); } /** * Convert the URI including password, formatted with only ASCII characters * such that it will be valid for use over the network. * * @return the URI including password, formatted with only ASCII characters * such that it will be valid for use over the network. */ public String toPrivateASCIIString() { return format(true, true); } /** * Get the "humanish" part of the path. Some examples of a 'humanish' part * for a full path: * * * * * * * * * * * * * * * * * * * * * * * * * * * *
path vs. humanish path
PathHumanish part
/path/to/repo.gitrepo
/path/to/repo.git/
/path/to/repo/.git
/path/to/repo/
localhostssh://localhost/
/path//toan empty string
* * @return the "humanish" part of the path. May be an empty string. Never * {@code null}. * @throws java.lang.IllegalArgumentException * if it's impossible to determine a humanish part, or path is * {@code null} or empty * @see #getPath */ public String getHumanishName() throws IllegalArgumentException { String s = getPath(); if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$ //$NON-NLS-2$ s = getHost(); if (s == null) // $NON-NLS-1$ throw new IllegalArgumentException(); String[] elements; if ("file".equals(scheme) || LOCAL_FILE.matcher(s).matches()) //$NON-NLS-1$ elements = s.split("[\\" + File.separatorChar + "/]"); //$NON-NLS-1$ //$NON-NLS-2$ else elements = s.split("/+"); //$NON-NLS-1$ if (elements.length == 0) throw new IllegalArgumentException(); String result = elements[elements.length - 1]; if (Constants.DOT_GIT.equals(result)) result = elements[elements.length - 2]; else if (result.endsWith(Constants.DOT_GIT_EXT)) result = result.substring(0, result.length() - Constants.DOT_GIT_EXT.length()); if (("file".equals(scheme) || LOCAL_FILE.matcher(s) //$NON-NLS-1$ .matches()) && result.endsWith(Constants.DOT_BUNDLE_EXT)) { result = result.substring(0, result.length() - Constants.DOT_BUNDLE_EXT.length()); } return result; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 772 Content-Disposition: inline; filename="UnpackErrorHandler.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "12c9a7621468162d14158c8a4077e122426334bd" /* * Copyright (c) 2019, Google LLC and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; /** * Exception handler for processing an incoming pack file. * * @since 5.7 */ public interface UnpackErrorHandler { /** * Handle an exception thrown while unpacking the pack file. * * @param t * exception thrown * @throws IOException * thrown when failed to write an error back to the client. */ void handleUnpackException(Throwable t) throws IOException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 80203 Content-Disposition: inline; filename="UploadPack.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "41ab8acf0571e49eae585c86df2619101ef863a5" /* * Copyright (C) 2008, 2022 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; import static org.eclipse.jgit.lib.Constants.R_TAGS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION; import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH; import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS; import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_OBJECT_INFO; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK_DETAILED; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ACK; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2_REQUEST; import static org.eclipse.jgit.util.RefMap.toRefMap; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.CachedPackUriProvider; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; import org.eclipse.jgit.revwalk.DepthWalk; import org.eclipse.jgit.revwalk.ObjectReachabilityChecker; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.ReachabilityChecker; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevFlagSet; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackStatistics; import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.transport.TransferConfig.ProtocolVersion; import org.eclipse.jgit.util.io.InterruptTimer; import org.eclipse.jgit.util.io.NullOutputStream; import org.eclipse.jgit.util.io.TimeoutInputStream; import org.eclipse.jgit.util.io.TimeoutOutputStream; /** * Implements the server side of a fetch connection, transmitting objects. */ public class UploadPack implements Closeable { /** Policy the server uses to validate client requests */ public enum RequestPolicy { /** Client may only ask for objects the server advertised a reference for. */ ADVERTISED(0x08), /** * Client may ask for any commit reachable from a reference advertised by * the server. */ REACHABLE_COMMIT(0x02), /** * Client may ask for objects that are the tip of any reference, even if not * advertised. *

* This may happen, for example, when a custom {@link RefFilter} is set. * * @since 3.1 */ TIP(0x01), /** * Client may ask for any commit reachable from any reference, even if that * reference wasn't advertised, implies REACHABLE_COMMIT and TIP. * * @since 3.1 */ REACHABLE_COMMIT_TIP(0x03), /** Client may ask for any SHA-1 in the repository, implies REACHABLE_COMMIT_TIP. */ ANY(0x07); private final int bitmask; RequestPolicy(int bitmask) { this.bitmask = bitmask; } /** * Check if the current policy implies another, based on its bitmask. * * @param implied * the implied policy based on its bitmask. * @return true if the policy is implied. * @since 6.10.1 */ public boolean implies(RequestPolicy implied) { return (bitmask & implied.bitmask) != 0; } } /** * Validator for client requests. * * @since 3.1 */ public interface RequestValidator { /** * Check a list of client wants against the request policy. * * @param up * {@link UploadPack} instance. * @param wants * objects the client requested that were not advertised. * * @throws PackProtocolException * if one or more wants is not valid. * @throws IOException * if a low-level exception occurred. * @since 3.1 */ void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException; } /* * {@link java.util.function.Consumer} doesn't allow throwing checked * exceptions. Define our own to propagate IOExceptions. */ @FunctionalInterface private static interface IOConsumer { void accept(R t) throws IOException; } /** Database we read the objects from. */ private final Repository db; /** Revision traversal support over {@link #db}. */ private final RevWalk walk; /** Configuration to pass into the PackWriter. */ private PackConfig packConfig; /** Configuration for various transfer options. */ private TransferConfig transferConfig; /** Timeout in seconds to wait for client interaction. */ private int timeout; /** * Is the client connection a bi-directional socket or pipe? *

* If true, this class assumes it can perform multiple read and write cycles * with the client over the input and output streams. This matches the * functionality available with a standard TCP/IP connection, or a local * operating system or in-memory pipe. *

* If false, this class runs in a read everything then output results mode, * making it suitable for single round-trip systems RPCs such as HTTP. */ private boolean biDirectionalPipe = true; /** Timer to manage {@link #timeout}. */ private InterruptTimer timer; /** * Whether the client requested to use protocol V2 through a side * channel (such as the Git-Protocol HTTP header). */ private boolean clientRequestedV2; private InputStream rawIn; private ResponseBufferedOutputStream rawOut; private PacketLineIn pckIn; private OutputStream msgOut = NullOutputStream.INSTANCE; private ErrorWriter errOut = new PackProtocolErrorWriter(); /** * Refs eligible for advertising to the client, set using * {@link #setAdvertisedRefs}. */ private Map refs; /** Hook used while processing Git protocol v2 requests. */ private ProtocolV2Hook protocolV2Hook = ProtocolV2Hook.DEFAULT; /** Hook used while advertising the refs to the client. */ private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT; /** Whether the {@link #advertiseRefsHook} has been invoked. */ private boolean advertiseRefsHookCalled; /** Filter used while advertising the refs to the client. */ private RefFilter refFilter = RefFilter.DEFAULT; /** Hook handling the various upload phases. */ private PreUploadHook preUploadHook = PreUploadHook.NULL; /** Hook for taking post upload actions. */ private PostUploadHook postUploadHook = PostUploadHook.NULL; /** Caller user agent */ String userAgent; /** Raw ObjectIds the client has asked for, before validating them. */ private Set wantIds = new HashSet<>(); /** Objects the client wants to obtain. */ private final Set wantAll = new HashSet<>(); /** Objects on both sides, these don't have to be sent. */ private final Set commonBase = new HashSet<>(); /** Commit time of the oldest common commit, in seconds. */ private int oldestTime; /** null if {@link #commonBase} should be examined again. */ private Boolean okToGiveUp; private boolean sentReady; /** Objects we sent in our advertisement list. */ private Set advertised; /** Marked on objects the client has asked us to give them. */ private final RevFlag WANT; /** Marked on objects both we and the client have. */ private final RevFlag PEER_HAS; /** Marked on objects in {@link #commonBase}. */ private final RevFlag COMMON; /** Objects where we found a path from the want list to a common base. */ private final RevFlag SATISFIED; private final RevFlagSet SAVE; private RequestValidator requestValidator = new AdvertisedRequestValidator(); private MultiAck multiAck = MultiAck.OFF; private boolean noDone; private PackStatistics statistics; /** * Request this instance is handling. * * We need to keep a reference to it for {@link PreUploadHook pre upload * hooks}. They receive a reference this instance and invoke methods like * getDepth() to get information about the request. */ private FetchRequest currentRequest; private CachedPackUriProvider cachedPackUriProvider; /** * Create a new pack upload for an open repository. * * @param copyFrom * the source repository. */ public UploadPack(Repository copyFrom) { db = copyFrom; walk = new RevWalk(db); walk.setRetainBody(false); WANT = walk.newFlag("WANT"); //$NON-NLS-1$ PEER_HAS = walk.newFlag("PEER_HAS"); //$NON-NLS-1$ COMMON = walk.newFlag("COMMON"); //$NON-NLS-1$ SATISFIED = walk.newFlag("SATISFIED"); //$NON-NLS-1$ walk.carry(PEER_HAS); SAVE = new RevFlagSet(); SAVE.add(WANT); SAVE.add(PEER_HAS); SAVE.add(COMMON); SAVE.add(SATISFIED); setTransferConfig(null); } /** * Get the repository this upload is reading from. * * @return the repository this upload is reading from. */ public final Repository getRepository() { return db; } /** * Get the RevWalk instance used by this connection. * * @return the RevWalk instance used by this connection. */ public final RevWalk getRevWalk() { return walk; } /** * Get refs which were advertised to the client. * * @return all refs which were advertised to the client. Only valid during * the negotiation phase. Will return {@code null} if * {@link #setAdvertisedRefs(Map)} has not been called yet or if * {@code #sendPack()} has been called. */ public final Map getAdvertisedRefs() { return refs; } /** * Set the refs advertised by this UploadPack. *

* Intended to be called from a * {@link org.eclipse.jgit.transport.PreUploadHook}. * * @param allRefs * explicit set of references to claim as advertised by this * UploadPack instance. This overrides any references that may * exist in the source repository. The map is passed to the * configured {@link #getRefFilter()}. If null, assumes all refs * were advertised. */ public void setAdvertisedRefs(@Nullable Map allRefs) { if (allRefs != null) { refs = allRefs; } else { refs = getAllRefs(); } if (refFilter == RefFilter.DEFAULT) { refs = transferConfig.getRefFilter().filter(refs); } else { refs = refFilter.filter(refs); } } /** * Get timeout (in seconds) before aborting an IO operation. * * @return timeout (in seconds) before aborting an IO operation. */ public int getTimeout() { return timeout; } /** * Set the timeout before willing to abort an IO call. * * @param seconds * number of seconds to wait (with no data transfer occurring) * before aborting an IO read or write operation with the * connected client. */ public void setTimeout(int seconds) { timeout = seconds; } /** * Whether this class expects a bi-directional pipe opened between the * client and itself. * * @return true if this class expects a bi-directional pipe opened between * the client and itself. The default is true. */ public boolean isBiDirectionalPipe() { return biDirectionalPipe; } /** * Set whether this class will assume the socket is a fully bidirectional * pipe between the two peers * * @param twoWay * if true, this class will assume the socket is a fully * bidirectional pipe between the two peers and takes advantage * of that by first transmitting the known refs, then waiting to * read commands. If false, this class assumes it must read the * commands before writing output and does not perform the * initial advertising. */ public void setBiDirectionalPipe(boolean twoWay) { biDirectionalPipe = twoWay; } /** * Get policy used by the service to validate client requests * * @return policy used by the service to validate client requests, or null * for a custom request validator. */ public RequestPolicy getRequestPolicy() { if (requestValidator instanceof AdvertisedRequestValidator) return RequestPolicy.ADVERTISED; if (requestValidator instanceof ReachableCommitRequestValidator) return RequestPolicy.REACHABLE_COMMIT; if (requestValidator instanceof TipRequestValidator) return RequestPolicy.TIP; if (requestValidator instanceof ReachableCommitTipRequestValidator) return RequestPolicy.REACHABLE_COMMIT_TIP; if (requestValidator instanceof AnyRequestValidator) return RequestPolicy.ANY; return null; } /** * Set the policy used to enforce validation of a client's want list. * * @param policy * the policy used to enforce validation of a client's want list. * By default the policy is * {@link org.eclipse.jgit.transport.UploadPack.RequestPolicy#ADVERTISED}, * which is the Git default requiring clients to only ask for an * object that a reference directly points to. This may be * relaxed to * {@link org.eclipse.jgit.transport.UploadPack.RequestPolicy#REACHABLE_COMMIT} * or * {@link org.eclipse.jgit.transport.UploadPack.RequestPolicy#REACHABLE_COMMIT_TIP} * when callers have {@link #setBiDirectionalPipe(boolean)} set * to false. Overrides any policy specified in a * {@link org.eclipse.jgit.transport.TransferConfig}. */ public void setRequestPolicy(RequestPolicy policy) { switch (policy) { case ADVERTISED: default: requestValidator = new AdvertisedRequestValidator(); break; case REACHABLE_COMMIT: requestValidator = new ReachableCommitRequestValidator(); break; case TIP: requestValidator = new TipRequestValidator(); break; case REACHABLE_COMMIT_TIP: requestValidator = new ReachableCommitTipRequestValidator(); break; case ANY: requestValidator = new AnyRequestValidator(); break; } } /** * Set custom validator for client want list. * * @param validator * custom validator for client want list. * @since 3.1 */ public void setRequestValidator(@Nullable RequestValidator validator) { requestValidator = validator != null ? validator : new AdvertisedRequestValidator(); } /** * Get the hook used while advertising the refs to the client. * * @return the hook used while advertising the refs to the client. */ public AdvertiseRefsHook getAdvertiseRefsHook() { return advertiseRefsHook; } /** * Get the filter used while advertising the refs to the client. * * @return the filter used while advertising the refs to the client. */ public RefFilter getRefFilter() { return refFilter; } /** * Set the hook used while advertising the refs to the client. *

* If the {@link org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to * call {@link #setAdvertisedRefs(Map)}, only refs set by this hook * and selected by the {@link org.eclipse.jgit.transport.RefFilter} * will be shown to the client. * * @param advertiseRefsHook * the hook; may be null to show all refs. */ public void setAdvertiseRefsHook( @Nullable AdvertiseRefsHook advertiseRefsHook) { this.advertiseRefsHook = advertiseRefsHook != null ? advertiseRefsHook : AdvertiseRefsHook.DEFAULT; } /** * Set the protocol V2 hook. * * @param hook * the hook; if null no special actions are taken. * @since 5.1 */ public void setProtocolV2Hook(@Nullable ProtocolV2Hook hook) { this.protocolV2Hook = hook != null ? hook : ProtocolV2Hook.DEFAULT; } /** * Get the currently installed protocol v2 hook. * * @return the hook or a default implementation if none installed. * * @since 5.5 */ public ProtocolV2Hook getProtocolV2Hook() { return this.protocolV2Hook != null ? this.protocolV2Hook : ProtocolV2Hook.DEFAULT; } /** * Set the filter used while advertising the refs to the client. *

* Only refs allowed by this filter will be sent to the client. The filter * is run against the refs specified by the * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable). If * null or not set, uses the filter implied by the * {@link org.eclipse.jgit.transport.TransferConfig}. * * @param refFilter * the filter; may be null to show all refs. */ public void setRefFilter(@Nullable RefFilter refFilter) { this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } /** * Get the configured pre upload hook. * * @return the configured pre upload hook. */ public PreUploadHook getPreUploadHook() { return preUploadHook; } /** * Set the hook that controls how this instance will behave. * * @param hook * the hook; if null no special actions are taken. */ public void setPreUploadHook(@Nullable PreUploadHook hook) { preUploadHook = hook != null ? hook : PreUploadHook.NULL; } /** * Get the configured post upload hook. * * @return the configured post upload hook. * @since 4.1 */ public PostUploadHook getPostUploadHook() { return postUploadHook; } /** * Set the hook for post upload actions (logging, repacking). * * @param hook * the hook; if null no special actions are taken. * @since 4.1 */ public void setPostUploadHook(@Nullable PostUploadHook hook) { postUploadHook = hook != null ? hook : PostUploadHook.NULL; } /** * Set the configuration used by the pack generator. * * @param pc * configuration controlling packing parameters. If null the * source repository's settings will be used. */ public void setPackConfig(@Nullable PackConfig pc) { this.packConfig = pc; } /** * Set configuration controlling transfer options. * * @param tc * configuration controlling transfer options. If null the source * repository's settings will be used. * @since 3.1 */ public void setTransferConfig(@Nullable TransferConfig tc) { this.transferConfig = tc != null ? tc : new TransferConfig(db); if (transferConfig.isAllowAnySha1InWant()) { setRequestPolicy(RequestPolicy.ANY); return; } if (transferConfig.isAllowTipSha1InWant()) { setRequestPolicy(transferConfig.isAllowReachableSha1InWant() ? RequestPolicy.REACHABLE_COMMIT_TIP : RequestPolicy.TIP); } else { setRequestPolicy(transferConfig.isAllowReachableSha1InWant() ? RequestPolicy.REACHABLE_COMMIT : RequestPolicy.ADVERTISED); } } /** * Check whether the client expects a side-band stream. * * @return true if the client has advertised a side-band capability, false * otherwise. * @throws org.eclipse.jgit.transport.RequestNotYetReadException * if the client's request has not yet been read from the wire, so * we do not know if they expect side-band. Note that the client * may have already written the request, it just has not been * read. */ public boolean isSideBand() throws RequestNotYetReadException { if (currentRequest == null) { throw new RequestNotYetReadException(); } Set caps = currentRequest.getClientCapabilities(); return caps.contains(OPTION_SIDE_BAND) || caps.contains(OPTION_SIDE_BAND_64K); } /** * Set the Extra Parameters provided by the client. * *

These are parameters passed by the client through a side channel * such as the Git-Protocol HTTP header, to allow a client to request * a newer response format while remaining compatible with older servers * that do not understand different request formats. * * @param params * parameters supplied by the client, split at colons or NUL * bytes. * @since 5.0 */ public void setExtraParameters(Collection params) { this.clientRequestedV2 = params.contains(VERSION_2_REQUEST); } /** * Set provider of cached pack URIs * * @param p * provider of URIs corresponding to cached packs (to support the * packfile URIs feature) * @since 5.5 */ public void setCachedPackUriProvider(@Nullable CachedPackUriProvider p) { cachedPackUriProvider = p; } private boolean useProtocolV2() { return (transferConfig.protocolVersion == null || ProtocolVersion.V2.equals(transferConfig.protocolVersion)) && clientRequestedV2; } @Override public void close() { if (timer != null) { try { timer.terminate(); } finally { timer = null; } } } /** * Execute the upload task on the socket. * *

* Same as {@link #uploadWithExceptionPropagation} except that the thrown * exceptions are handled in the method, and the error messages are sent to * the clients. * *

* Call this method if the caller does not have an error handling mechanism. * Call {@link #uploadWithExceptionPropagation} if the caller wants to have * its own error handling mechanism. * * @param input * input stream * @param output * output stream * @param messages * stream for messages * @throws java.io.IOException * if an IO error occurred */ public void upload(InputStream input, OutputStream output, @Nullable OutputStream messages) throws IOException { try { uploadWithExceptionPropagation(input, output, messages); } catch (ServiceMayNotContinueException err) { if (!err.isOutput() && err.getMessage() != null) { try { errOut.writeError(err.getMessage()); } catch (IOException e) { err.addSuppressed(e); throw err; } err.setOutput(); } throw err; } catch (IOException | RuntimeException | Error err) { if (rawOut != null) { String msg = err instanceof PackProtocolException ? err.getMessage() : JGitText.get().internalServerError; try { errOut.writeError(msg); } catch (IOException e) { err.addSuppressed(e); throw err; } throw new UploadPackInternalServerErrorException(err); } throw err; } finally { close(); } } /** * Execute the upload task on the socket. * *

* If the client passed extra parameters (e.g., "version=2") through a side * channel, the caller must call setExtraParameters first to supply them. * Callers of this method should call {@link #close()} to terminate the * internal interrupt timer thread. If the caller fails to terminate the * thread, it will (eventually) terminate itself when the InterruptTimer * instance is garbage collected. * * @param input * raw input to read client commands from. Caller must ensure the * input is buffered, otherwise read performance may suffer. * @param output * response back to the Git network client, to write the pack * data onto. Caller must ensure the output is buffered, * otherwise write performance may suffer. * @param messages * secondary "notice" channel to send additional messages out * through. When run over SSH this should be tied back to the * standard error channel of the command execution. For most * other network connections this should be null. * @throws ServiceMayNotContinueException * thrown if one of the hooks throws this. * @throws IOException * thrown if the server or the client I/O fails, or there's an * internal server error. * @since 5.6 */ public void uploadWithExceptionPropagation(InputStream input, OutputStream output, @Nullable OutputStream messages) throws ServiceMayNotContinueException, IOException { try { rawIn = input; if (messages != null) { msgOut = messages; } if (timeout > 0) { final Thread caller = Thread.currentThread(); timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ TimeoutInputStream i = new TimeoutInputStream(rawIn, timer); @SuppressWarnings("resource") TimeoutOutputStream o = new TimeoutOutputStream(output, timer); i.setTimeout(timeout * 1000); o.setTimeout(timeout * 1000); rawIn = i; output = o; } rawOut = new ResponseBufferedOutputStream(output); if (biDirectionalPipe) { rawOut.stopBuffering(); } pckIn = new PacketLineIn(rawIn); PacketLineOut pckOut = new PacketLineOut(rawOut); if (useProtocolV2()) { serviceV2(pckOut); } else { service(pckOut); } } finally { msgOut = NullOutputStream.INSTANCE; walk.close(); } } /** * Get the PackWriter's statistics if a pack was sent to the client. * * @return statistics about pack output, if a pack was sent. Null if no pack * was sent, such as during the negotiation phase of a smart HTTP * connection, or if the client was already up-to-date. * @since 4.1 */ public PackStatistics getStatistics() { return statistics; } /** * Extract the full list of refs from the ref-db. * * @return Map of all refname/ref */ private Map getAllRefs() { try { return db.getRefDatabase().getRefs().stream().collect( Collectors.toMap(Ref::getName, Function.identity())); } catch (IOException e) { throw new UncheckedIOException(e); } } private Map getAdvertisedOrDefaultRefs() throws IOException { if (refs != null) { return refs; } if (!advertiseRefsHookCalled) { advertiseRefsHook.advertiseRefs(this); advertiseRefsHookCalled = true; } if (refs == null) { // Fall back to all refs. setAdvertisedRefs( db.getRefDatabase().getRefs().stream() .collect(toRefMap((a, b) -> b))); } return refs; } private Map getFilteredRefs(Collection refPrefixes) throws IOException { if (refPrefixes.isEmpty()) { return getAdvertisedOrDefaultRefs(); } if (refs == null && !advertiseRefsHookCalled) { advertiseRefsHook.advertiseRefs(this); advertiseRefsHookCalled = true; } if (refs == null) { // Fast path: the advertised refs hook did not set advertised refs. String[] prefixes = refPrefixes.toArray(new String[0]); Map rs = db.getRefDatabase().getRefsByPrefix(prefixes).stream() .collect(toRefMap((a, b) -> b)); if (refFilter != RefFilter.DEFAULT) { return refFilter.filter(rs); } return transferConfig.getRefFilter().filter(rs); } // Slow path: filter the refs provided by the advertised refs hook. // refFilter has already been applied to refs. return refs.values().stream() .filter(ref -> refPrefixes.stream() .anyMatch(ref.getName()::startsWith)) .collect(toRefMap((a, b) -> b)); } /** * Returns the specified references. *

* This produces an immutable map containing whatever subset of the * refs named by the caller are present in the supplied {@code refs} * map. * * @param refs * Map to search for refs to return. * @param names * which refs to search for in {@code refs}. * @return the requested Refs, omitting any that are null or missing. */ @NonNull private static Map mapRefs( Map refs, List names) { return unmodifiableMap( names.stream() .map(refs::get) .filter(Objects::nonNull) .collect(toRefMap((a, b) -> b))); } /** * Read refs on behalf of the client. *

* This checks whether the refs are present in the ref advertisement * since otherwise the client might not be supposed to be able to * read them. * * @param names * unabbreviated names of references. * @return the requested Refs, omitting any that are not visible or * do not exist. * @throws java.io.IOException * on failure to read a ref or check it for visibility. */ @NonNull private Map exactRefs(List names) throws IOException { if (refs != null) { return mapRefs(refs, names); } if (!advertiseRefsHookCalled) { advertiseRefsHook.advertiseRefs(this); advertiseRefsHookCalled = true; } if (refs == null && refFilter == RefFilter.DEFAULT && transferConfig.hasDefaultRefFilter()) { // Fast path: no ref filtering is needed. String[] ns = names.toArray(new String[0]); return unmodifiableMap(db.getRefDatabase().exactRef(ns)); } return mapRefs(getAdvertisedOrDefaultRefs(), names); } /** * Find a ref in the usual search path on behalf of the client. *

* This checks that the ref is present in the ref advertisement since * otherwise the client might not be supposed to be able to read it. * * @param name * short name of the ref to find, e.g. "master" to find * "refs/heads/master". * @return the requested Ref, or {@code null} if it is not visible or * does not exist. * @throws java.io.IOException * on failure to read the ref or check it for visibility. */ @Nullable private Ref findRef(String name) throws IOException { if (refs != null) { return RefDatabase.findRef(refs, name); } if (!advertiseRefsHookCalled) { advertiseRefsHook.advertiseRefs(this); advertiseRefsHookCalled = true; } if (refs == null && refFilter == RefFilter.DEFAULT && transferConfig.hasDefaultRefFilter()) { // Fast path: no ref filtering is needed. return db.getRefDatabase().findRef(name); } return RefDatabase.findRef(getAdvertisedOrDefaultRefs(), name); } private void service(PacketLineOut pckOut) throws IOException { boolean sendPack = false; // If it's a non-bidi request, we need to read the entire request before // writing a response. Buffer the response until then. PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator(); List unshallowCommits = new ArrayList<>(); List deepenNots = emptyList(); FetchRequest req; try { if (biDirectionalPipe) sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); else if (requestValidator instanceof AnyRequestValidator) advertised = Collections.emptySet(); else advertised = refIdSet(getAdvertisedOrDefaultRefs().values()); Instant negotiateStart = Instant.now(); accumulator.advertised = advertised.size(); ProtocolV0Parser parser = new ProtocolV0Parser(transferConfig); req = parser.recvWants(pckIn); currentRequest = req; wantIds = req.getWantIds(); if (req.getWantIds().isEmpty()) { preUploadHook.onBeginNegotiateRound(this, req.getWantIds(), 0); preUploadHook.onEndNegotiateRound(this, req.getWantIds(), 0, 0, false); return; } accumulator.wants = req.getWantIds().size(); if (req.getClientCapabilities().contains(OPTION_MULTI_ACK_DETAILED)) { multiAck = MultiAck.DETAILED; noDone = req.getClientCapabilities().contains(OPTION_NO_DONE); } else if (req.getClientCapabilities().contains(OPTION_MULTI_ACK)) multiAck = MultiAck.CONTINUE; else multiAck = MultiAck.OFF; if (!req.getClientShallowCommits().isEmpty()) { verifyClientShallow(req.getClientShallowCommits()); } deepenNots = parseDeepenNots(req.getDeepenNots()); if (req.getDepth() != 0 || req.getDeepenSince() != 0 || !req.getDeepenNots().isEmpty()) { computeShallowsAndUnshallows(req, shallow -> { pckOut.writeString(PACKET_SHALLOW + shallow.name() + '\n'); }, unshallow -> { pckOut.writeString( PACKET_UNSHALLOW + unshallow.name() + '\n'); unshallowCommits.add(unshallow); }, deepenNots); pckOut.end(); } if (!req.getClientShallowCommits().isEmpty()) walk.assumeShallow(req.getClientShallowCommits()); sendPack = negotiate(req, accumulator, pckOut); accumulator.timeNegotiating = Duration .between(negotiateStart, Instant.now()).toMillis(); if (sendPack && !biDirectionalPipe) { // Ensure the request was fully consumed. Any remaining input must // be a protocol error. If we aren't at EOF the implementation is broken. int eof = rawIn.read(); if (0 <= eof) { sendPack = false; throw new CorruptObjectException(MessageFormat.format( JGitText.get().expectedEOFReceived, "\\x" + Integer.toHexString(eof))); //$NON-NLS-1$ } } } finally { if (!sendPack && !biDirectionalPipe) { while (0 < rawIn.skip(2048) || 0 <= rawIn.read()) { // Discard until EOF. } } rawOut.stopBuffering(); } if (sendPack) { sendPack(accumulator, req, refs == null ? null : refs.values(), unshallowCommits, deepenNots, pckOut); } } private void lsRefsV2(PacketLineOut pckOut) throws IOException { ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig); LsRefsV2Request req = parser.parseLsRefsRequest(pckIn); protocolV2Hook.onLsRefs(req); rawOut.stopBuffering(); PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut); adv.init(db); adv.setUseProtocolV2(true); if (req.getPeel()) { adv.setDerefTags(true); } Map refsToSend = getFilteredRefs(req.getRefPrefixes()); if (req.getSymrefs()) { findSymrefs(adv, refsToSend); } adv.send(refsToSend.values()); adv.end(); } // Resolves ref names from the request's want-ref lines to // object ids, throwing PackProtocolException if any are missing. private Map wantedRefs(FetchV2Request req) throws IOException { Map result = new TreeMap<>(); List wanted = req.getWantedRefs(); Map resolved = exactRefs(wanted); for (String refName : wanted) { Ref ref = resolved.get(refName); if (ref == null) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidRefName, refName)); } ObjectId oid = ref.getObjectId(); if (oid == null) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidRefName, refName)); } result.put(refName, oid); } return result; } private void fetchV2(PacketLineOut pckOut) throws IOException { ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig); FetchV2Request req = parser.parseFetchRequest(pckIn); currentRequest = req; Map wantedRefs = wantedRefs(req); // Depending on the requestValidator, #processHaveLines may // require that advertised be set. Set it only in the required // circumstances (to avoid a full ref lookup in the case that // we don't need it). if (requestValidator instanceof TipRequestValidator || requestValidator instanceof ReachableCommitTipRequestValidator || requestValidator instanceof AnyRequestValidator) { advertised = Collections.emptySet(); } else { if (req.wantIds.isEmpty()) { // Only refs-in-wants in request. These ref-in-wants where used as // filters already in the ls-refs, there is no need to use a full // advertisement now in fetch. This improves performance and also // accuracy: when the ref db prioritize and truncates the returned // refs (e.g. Gerrit hides too old refs), applying a filter can // return different results than a plain listing. advertised = refIdSet(getFilteredRefs(wantedRefs.keySet()).values()); } else { // At least one SHA1 in wants, so we need to take the full // advertisement as base for a reachability check. advertised = refIdSet(getAdvertisedOrDefaultRefs().values()); } } PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator(); Instant negotiateStart = Instant.now(); accumulator.advertised = advertised.size(); rawOut.stopBuffering(); protocolV2Hook.onFetch(req); if (req.getSidebandAll()) { pckOut.setUsingSideband(true); } // TODO(ifrade): Refactor to pass around the Request object, instead of // copying data back to class fields List deepenNots = parseDeepenNots(req.getDeepenNots()); // TODO(ifrade): Avoid mutating the parsed request. req.getWantIds().addAll(wantedRefs.values()); wantIds = req.getWantIds(); accumulator.wants = wantIds.size(); boolean sectionSent = false; boolean mayHaveShallow = req.getDepth() != 0 || req.getDeepenSince() != 0 || !req.getDeepenNots().isEmpty(); List shallowCommits = new ArrayList<>(); List unshallowCommits = new ArrayList<>(); if (!req.getClientShallowCommits().isEmpty()) { verifyClientShallow(req.getClientShallowCommits()); } if (mayHaveShallow) { computeShallowsAndUnshallows(req, shallowCommit -> shallowCommits.add(shallowCommit), unshallowCommit -> unshallowCommits.add(unshallowCommit), deepenNots); } if (!req.getClientShallowCommits().isEmpty()) walk.assumeShallow(req.getClientShallowCommits()); if (req.wasDoneReceived()) { processHaveLines( req.getPeerHas(), ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE, false), accumulator, req.wasWaitForDoneReceived() ? Option.WAIT_FOR_DONE : Option.NONE); } else { pckOut.writeString( GitProtocolConstants.SECTION_ACKNOWLEDGMENTS + '\n'); for (ObjectId id : req.getPeerHas()) { if (walk.getObjectReader().has(id)) { pckOut.writeString(PACKET_ACK + id.getName() + '\n'); } } processHaveLines(req.getPeerHas(), ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE, false), accumulator, Option.NONE); if (!req.wasWaitForDoneReceived() && okToGiveUp()) { pckOut.writeString("ready\n"); //$NON-NLS-1$ } else if (commonBase.isEmpty()) { pckOut.writeString("NAK\n"); //$NON-NLS-1$ } sectionSent = true; } if (req.wasDoneReceived() || (!req.wasWaitForDoneReceived() && okToGiveUp())) { if (mayHaveShallow) { if (sectionSent) pckOut.writeDelim(); pckOut.writeString( GitProtocolConstants.SECTION_SHALLOW_INFO + '\n'); for (ObjectId o : shallowCommits) { pckOut.writeString(PACKET_SHALLOW + o.getName() + '\n'); } for (ObjectId o : unshallowCommits) { pckOut.writeString(PACKET_UNSHALLOW + o.getName() + '\n'); } sectionSent = true; } if (!wantedRefs.isEmpty()) { if (sectionSent) { pckOut.writeDelim(); } pckOut.writeString("wanted-refs\n"); //$NON-NLS-1$ for (Map.Entry entry : wantedRefs.entrySet()) { pckOut.writeString(entry.getValue().getName() + ' ' + entry.getKey() + '\n'); } sectionSent = true; } if (sectionSent) pckOut.writeDelim(); if (!pckOut.isUsingSideband()) { // sendPack will write "packfile\n" for us if sideband-all is used. // But sideband-all is not used, so we have to write it ourselves. pckOut.writeString( GitProtocolConstants.SECTION_PACKFILE + '\n'); } accumulator.timeNegotiating = Duration .between(negotiateStart, Instant.now()).toMillis(); sendPack(accumulator, req, req.getClientCapabilities().contains(OPTION_INCLUDE_TAG) ? db.getRefDatabase().getRefsByPrefix(R_TAGS) : null, unshallowCommits, deepenNots, pckOut); // sendPack invokes pckOut.end() for us, so we do not // need to invoke it here. } else { // Invoke pckOut.end() by ourselves. pckOut.end(); } } private void objectInfo(PacketLineOut pckOut) throws IOException { ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig); ObjectInfoRequest req = parser.parseObjectInfoRequest(pckIn); protocolV2Hook.onObjectInfo(req); ObjectReader or = getRepository().newObjectReader(); // Size is the only attribute currently supported. pckOut.writeString("size"); //$NON-NLS-1$ for (ObjectId oid : req.getObjectIDs()) { long size; try { size = or.getObjectSize(oid, ObjectReader.OBJ_ANY); } catch (MissingObjectException e) { throw new PackProtocolException(MessageFormat .format(JGitText.get().missingObject, oid.name()), e); } pckOut.writeString(oid.getName() + ' ' + size); } pckOut.end(); } /* * Returns true if this is the last command and we should tear down the * connection. */ private boolean serveOneCommandV2(PacketLineOut pckOut) throws IOException { String command; try { command = pckIn.readString(); } catch (EOFException eof) { /* EOF when awaiting command is fine */ return true; } if (PacketLineIn.isEnd(command)) { // A blank request is valid according // to the protocol; do nothing in this // case. return true; } if (command.equals("command=" + COMMAND_LS_REFS)) { //$NON-NLS-1$ lsRefsV2(pckOut); return false; } if (command.equals("command=" + COMMAND_FETCH)) { //$NON-NLS-1$ fetchV2(pckOut); return false; } if (command.equals("command=" + COMMAND_OBJECT_INFO)) { //$NON-NLS-1$ objectInfo(pckOut); return false; } throw new PackProtocolException(MessageFormat .format(JGitText.get().unknownTransportCommand, command)); } @SuppressWarnings("nls") private List getV2CapabilityAdvertisement() { ArrayList caps = new ArrayList<>(); caps.add("version 2"); caps.add(COMMAND_LS_REFS); boolean advertiseRefInWant = transferConfig.isAllowRefInWant() && db.getConfig().getBoolean("uploadpack", null, "advertiserefinwant", true); caps.add(COMMAND_FETCH + '=' + (transferConfig.isAllowFilter() ? OPTION_FILTER + ' ' : "") + (advertiseRefInWant ? CAPABILITY_REF_IN_WANT + ' ' : "") + (transferConfig.isAdvertiseSidebandAll() ? OPTION_SIDEBAND_ALL + ' ' : "") + (cachedPackUriProvider != null ? "packfile-uris " : "") + (transferConfig.isAdvertiseWaitForDone() ? OPTION_WAIT_FOR_DONE + ' ' : "") + OPTION_SHALLOW); caps.add(CAPABILITY_SERVER_OPTION); if (transferConfig.isAllowReceiveClientSID()) { caps.add(OPTION_SESSION_ID); } if (transferConfig.isAdvertiseObjectInfo()) { caps.add(COMMAND_OBJECT_INFO); } caps.add(OPTION_AGENT + "=" + UserAgent.get()); return caps; } private void serviceV2(PacketLineOut pckOut) throws IOException { if (biDirectionalPipe) { // Just like in service(), the capability advertisement // is sent only if this is a bidirectional pipe. (If // not, the client is expected to call // sendAdvertisedRefs() on its own.) protocolV2Hook .onCapabilities(CapabilitiesV2Request.builder().build()); for (String s : getV2CapabilityAdvertisement()) { pckOut.writeString(s + '\n'); } pckOut.end(); while (!serveOneCommandV2(pckOut)) { // Repeat until an empty command or EOF. } return; } try { serveOneCommandV2(pckOut); } finally { while (0 < rawIn.skip(2048) || 0 <= rawIn.read()) { // Discard until EOF. } rawOut.stopBuffering(); } } private static Set refIdSet(Collection refs) { Set ids = new HashSet<>(refs.size()); for (Ref ref : refs) { ObjectId id = ref.getObjectId(); if (id != null) { ids.add(id); } id = ref.getPeeledObjectId(); if (id != null) { ids.add(id); } } return ids; } /* * Determines what object ids must be marked as shallow or unshallow for the * client. */ private void computeShallowsAndUnshallows(FetchRequest req, IOConsumer shallowFunc, IOConsumer unshallowFunc, List deepenNots) throws IOException { if (req.getClientCapabilities().contains(OPTION_DEEPEN_RELATIVE)) { // TODO(jonathantanmy): Implement deepen-relative throw new UnsupportedOperationException(); } int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE : req.getDepth() - 1; try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk( walk.getObjectReader(), walkDepth)) { depthWalk.setDeepenSince(req.getDeepenSince()); // Find all the commits which will be shallow for (ObjectId o : req.getWantIds()) { try { depthWalk.markRoot(depthWalk.parseCommit(o)); } catch (IncorrectObjectTypeException notCommit) { // Ignore non-commits in this loop. } } depthWalk.setDeepenNots(deepenNots); RevCommit o; boolean atLeastOne = false; while ((o = depthWalk.next()) != null) { DepthWalk.Commit c = (DepthWalk.Commit) o; atLeastOne = true; boolean isBoundary = (c.getDepth() == walkDepth) || c.isBoundary(); // Commits at the boundary which aren't already shallow in // the client need to be marked as such if (isBoundary && !req.getClientShallowCommits().contains(c)) { shallowFunc.accept(c.copy()); } // Commits not on the boundary which are shallow in the client // need to become unshallowed if (!isBoundary && req.getClientShallowCommits().remove(c)) { unshallowFunc.accept(c.copy()); } } if (!atLeastOne) { throw new PackProtocolException( JGitText.get().noCommitsSelectedForShallow); } } } /* * Verify all shallow lines refer to commits * * It can mutate the input set (removing missing object ids from it) */ private void verifyClientShallow(Set shallowCommits) throws IOException, PackProtocolException { AsyncRevObjectQueue q = walk.parseAny(shallowCommits, true); try { for (;;) { try { // Shallow objects named by the client must be commits. RevObject o = q.next(); if (o == null) { break; } if (!(o instanceof RevCommit)) { throw new PackProtocolException( MessageFormat.format( JGitText.get().invalidShallowObject, o.name())); } } catch (MissingObjectException notCommit) { // shallow objects not known at the server are ignored // by git-core upload-pack, match that behavior. shallowCommits.remove(notCommit.getObjectId()); continue; } } } finally { q.release(); } } /** * Generate an advertisement of available refs and capabilities. * * @param adv * the advertisement formatter. * @throws java.io.IOException * the formatter failed to write an advertisement. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * the hook denied advertisement. */ public void sendAdvertisedRefs(RefAdvertiser adv) throws IOException, ServiceMayNotContinueException { sendAdvertisedRefs(adv, null); } /** * Generate an advertisement of available refs and capabilities. * * @param adv * the advertisement formatter. * @param serviceName * if not null, also output "# service=serviceName" followed by a * flush packet before the advertisement. This is required * in v0 of the HTTP protocol, described in Git's * Documentation/technical/http-protocol.txt. * @throws java.io.IOException * the formatter failed to write an advertisement. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * the hook denied advertisement. * @since 5.0 */ public void sendAdvertisedRefs(RefAdvertiser adv, @Nullable String serviceName) throws IOException, ServiceMayNotContinueException { if (useProtocolV2()) { // The equivalent in v2 is only the capabilities // advertisement. protocolV2Hook .onCapabilities(CapabilitiesV2Request.builder().build()); for (String s : getV2CapabilityAdvertisement()) { adv.writeOne(s); } adv.end(); return; } Map advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs(); if (serviceName != null) { adv.writeOne("# service=" + serviceName + '\n'); //$NON-NLS-1$ adv.end(); } adv.init(db); adv.advertiseCapability(OPTION_INCLUDE_TAG); adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED); adv.advertiseCapability(OPTION_MULTI_ACK); adv.advertiseCapability(OPTION_OFS_DELTA); adv.advertiseCapability(OPTION_SIDE_BAND); adv.advertiseCapability(OPTION_SIDE_BAND_64K); adv.advertiseCapability(OPTION_THIN_PACK); adv.advertiseCapability(OPTION_NO_PROGRESS); adv.advertiseCapability(OPTION_SHALLOW); if (!biDirectionalPipe) adv.advertiseCapability(OPTION_NO_DONE); RequestPolicy policy = getRequestPolicy(); if (policy == null || policy.implies(RequestPolicy.TIP)) adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT); if (policy == null || policy.implies(RequestPolicy.REACHABLE_COMMIT)) adv.advertiseCapability(OPTION_ALLOW_REACHABLE_SHA1_IN_WANT); adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); if (transferConfig.isAllowFilter()) { adv.advertiseCapability(OPTION_FILTER); } adv.setDerefTags(true); findSymrefs(adv, advertisedOrDefaultRefs); advertised = adv.send(advertisedOrDefaultRefs.values()); if (adv.isEmpty()) adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$ adv.end(); } /** * Send a message to the client, if it supports receiving them. *

* If the client doesn't support receiving messages, the message will be * discarded, with no other indication to the caller or to the client. * * @param what * string describing the problem identified by the hook. The * string must not end with an LF, and must not contain an LF. * @since 3.1 */ public void sendMessage(String what) { try { msgOut.write(Constants.encode(what + '\n')); } catch (IOException e) { // Ignore write failures. } } /** * Get an underlying stream for sending messages to the client * * @return an underlying stream for sending messages to the client, or null. * @since 3.1 */ public OutputStream getMessageOutputStream() { return msgOut; } /** * Returns the clone/fetch depth. Valid only after calling recvWants(). A * depth of 1 means return only the wants. * * @return the depth requested by the client, or 0 if unbounded. * @since 4.0 */ public int getDepth() { if (currentRequest == null) throw new RequestNotYetReadException(); return currentRequest.getDepth(); } /** * Returns the filter spec for the current request. Valid only after * calling recvWants(). This may be a no-op filter spec, but it won't be * null. * * @return filter requested by the client * @since 5.4 */ public final FilterSpec getFilterSpec() { if (currentRequest == null) { throw new RequestNotYetReadException(); } return currentRequest.getFilterSpec(); } /** * Get the user agent of the client. *

* If the client is new enough to use {@code agent=} capability that value * will be returned. Older HTTP clients may also supply their version using * the HTTP {@code User-Agent} header. The capability overrides the HTTP * header if both are available. *

* When an HTTP request has been received this method returns the HTTP * {@code User-Agent} header value until capabilities have been parsed. * * @return user agent supplied by the client. Available only if the client * is new enough to advertise its user agent. * @since 4.0 */ public String getPeerUserAgent() { if (currentRequest != null && currentRequest.getAgent() != null) { return currentRequest.getAgent(); } return userAgent; } /** * Get the session ID if received from the client. * * @return The session ID if it has been received from the client. * @since 6.4 */ @Nullable public String getClientSID() { if (currentRequest == null) { return null; } return currentRequest.getClientSID(); } private boolean negotiate(FetchRequest req, PackStatistics.Accumulator accumulator, PacketLineOut pckOut) throws IOException { okToGiveUp = Boolean.FALSE; ObjectId last = ObjectId.zeroId(); List peerHas = new ArrayList<>(64); for (;;) { String line; try { line = pckIn.readString(); } catch (EOFException eof) { // EOF on stateless RPC (aka smart HTTP) and non-shallow request // means the client asked for the updated shallow/unshallow data, // disconnected, and will try another request with actual want/have. // Don't report the EOF here, its a bug in the protocol that the client // just disconnects without sending an END. if (!biDirectionalPipe && req.getDepth() > 0) return false; throw eof; } if (PacketLineIn.isEnd(line)) { last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE); if (commonBase.isEmpty() || multiAck != MultiAck.OFF) pckOut.writeString("NAK\n"); //$NON-NLS-1$ if (noDone && sentReady) { pckOut.writeString(PACKET_ACK + last.name() + '\n'); return true; } if (!biDirectionalPipe) return false; pckOut.flush(); } else if (line.startsWith(PACKET_HAVE) && line.length() == PACKET_HAVE.length() + 40) { peerHas.add(ObjectId .fromString(line.substring(PACKET_HAVE.length()))); } else if (line.equals(PACKET_DONE)) { last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE); if (commonBase.isEmpty()) pckOut.writeString("NAK\n"); //$NON-NLS-1$ else if (multiAck != MultiAck.OFF) pckOut.writeString(PACKET_ACK + last.name() + '\n'); return true; } else { throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "have", line)); //$NON-NLS-1$ } } } private enum Option { WAIT_FOR_DONE, NONE; } private ObjectId processHaveLines(List peerHas, ObjectId last, PacketLineOut out, PackStatistics.Accumulator accumulator, Option option) throws IOException { preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size()); if (wantAll.isEmpty() && !wantIds.isEmpty()) parseWants(accumulator); if (peerHas.isEmpty()) return last; accumulator.haves += peerHas.size(); sentReady = false; int haveCnt = 0; walk.getObjectReader().setAvoidUnreachableObjects(true); AsyncRevObjectQueue q = walk.parseAny(peerHas, false); try { for (;;) { RevObject obj; try { obj = q.next(); } catch (MissingObjectException notFound) { continue; } if (obj == null) break; last = obj; haveCnt++; if (obj instanceof RevCommit) { RevCommit c = (RevCommit) obj; if (oldestTime == 0 || c.getCommitTime() < oldestTime) oldestTime = c.getCommitTime(); } if (obj.has(PEER_HAS)) continue; obj.add(PEER_HAS); if (obj instanceof RevCommit) ((RevCommit) obj).carry(PEER_HAS); addCommonBase(obj); // If both sides have the same object; let the client know. // switch (multiAck) { case OFF: if (commonBase.size() == 1) { out.writeString(PACKET_ACK + obj.name() + '\n'); } break; case CONTINUE: out.writeString(PACKET_ACK + obj.name() + " continue\n"); //$NON-NLS-1$ break; case DETAILED: out.writeString(PACKET_ACK + obj.name() + " common\n"); //$NON-NLS-1$ break; } } } finally { q.release(); walk.getObjectReader().setAvoidUnreachableObjects(false); } int missCnt = peerHas.size() - haveCnt; // If we don't have one of the objects but we're also willing to // create a pack at this point, let the client know so it stops // telling us about its history. // if (option != Option.WAIT_FOR_DONE) { sentReady = shouldGiveUp(peerHas, out, missCnt); } preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady); peerHas.clear(); return last; } private boolean shouldGiveUp(List peerHas, PacketLineOut out, int missCnt) throws IOException { boolean readySent = false; boolean didOkToGiveUp = false; if (0 < missCnt) { for (int i = peerHas.size() - 1; i >= 0; i--) { ObjectId id = peerHas.get(i); if (walk.lookupOrNull(id) == null) { didOkToGiveUp = true; if (okToGiveUp()) { switch (multiAck) { case OFF: break; case CONTINUE: out.writeString( PACKET_ACK + id.name() + " continue\n"); //$NON-NLS-1$ break; case DETAILED: out.writeString( PACKET_ACK + id.name() + " ready\n"); //$NON-NLS-1$ readySent = true; break; } } break; } } } if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) { ObjectId id = peerHas.get(peerHas.size() - 1); out.writeString(PACKET_ACK + id.name() + " ready\n"); //$NON-NLS-1$ readySent = true; } return readySent; } private void parseWants(PackStatistics.Accumulator accumulator) throws IOException { List notAdvertisedWants = null; for (ObjectId obj : wantIds) { if (!advertised.contains(obj)) { if (notAdvertisedWants == null) notAdvertisedWants = new ArrayList<>(); notAdvertisedWants.add(obj); } } if (notAdvertisedWants != null) { accumulator.notAdvertisedWants = notAdvertisedWants.size(); Instant startReachabilityChecking = Instant.now(); requestValidator.checkWants(this, notAdvertisedWants); accumulator.reachabilityCheckDuration = Duration .between(startReachabilityChecking, Instant.now()) .toMillis(); } AsyncRevObjectQueue q = walk.parseAny(wantIds, true); try { RevObject obj; while ((obj = q.next()) != null) { want(obj); if (!(obj instanceof RevCommit)) obj.add(SATISFIED); if (obj instanceof RevTag) { obj = walk.peel(obj); if (obj instanceof RevCommit) want(obj); } } wantIds.clear(); } catch (MissingObjectException notFound) { throw new WantNotValidException(notFound.getObjectId(), notFound); } finally { q.release(); } } private void want(RevObject obj) { if (!obj.has(WANT)) { obj.add(WANT); wantAll.add(obj); } } /** * Validator corresponding to {@link RequestPolicy#ADVERTISED}. * * @since 3.1 */ public static final class AdvertisedRequestValidator implements RequestValidator { @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { if (!up.isBiDirectionalPipe() || !wants.isEmpty()) { new ReachableCommitRequestValidator().checkWants(up, wants); } } } /** * Validator corresponding to {@link RequestPolicy#REACHABLE_COMMIT}. * * @since 3.1 */ public static final class ReachableCommitRequestValidator implements RequestValidator { @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { checkNotAdvertisedWants(up, wants, up.getAdvertisedRefs().values()); } } /** * Validator corresponding to {@link RequestPolicy#TIP}. * * @since 3.1 */ public static final class TipRequestValidator implements RequestValidator { @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { if (!up.isBiDirectionalPipe()) new ReachableCommitTipRequestValidator().checkWants(up, wants); else if (!wants.isEmpty()) { Set refIds = refIdSet(up.getRepository().getRefDatabase().getRefs()); for (ObjectId obj : wants) { if (!refIds.contains(obj)) throw new WantNotValidException(obj); } } } } /** * Validator corresponding to {@link RequestPolicy#REACHABLE_COMMIT_TIP}. * * @since 3.1 */ public static final class ReachableCommitTipRequestValidator implements RequestValidator { @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { checkNotAdvertisedWants(up, wants, up.getRepository().getRefDatabase().getRefs()); } } /** * Validator corresponding to {@link RequestPolicy#ANY}. * * @since 3.1 */ public static final class AnyRequestValidator implements RequestValidator { @Override public void checkWants(UploadPack up, List wants) throws PackProtocolException, IOException { // All requests are valid. } } private static void checkNotAdvertisedWants(UploadPack up, List notAdvertisedWants, Collection visibleRefs) throws IOException { ObjectReader reader = up.getRevWalk().getObjectReader(); Set directlyVisibleObjects = refIdSet(visibleRefs); List nonTipWants = notAdvertisedWants.stream() .filter(not(directlyVisibleObjects::contains)) .collect(Collectors.toList()); try (RevWalk walk = new RevWalk(reader)) { walk.setRetainBody(false); // Missing "wants" throw exception here List wantsAsObjs = objectIdsToRevObjects(walk, nonTipWants); List wantsAsCommits = wantsAsObjs.stream() .filter(obj -> obj instanceof RevCommit) .map(obj -> (RevCommit) obj) .collect(Collectors.toList()); boolean allWantsAreCommits = wantsAsObjs.size() == wantsAsCommits .size(); boolean repoHasBitmaps = reader.getBitmapIndex() != null; if (!allWantsAreCommits) { if (!repoHasBitmaps && !up.transferConfig.isAllowFilter()) { // Checking unadvertised non-commits without bitmaps // requires an expensive manual walk. Use allowFilter as an // indication that the server operator is willing to pay // this cost. Reject the request otherwise. RevObject nonCommit = wantsAsObjs .stream() .filter(obj -> !(obj instanceof RevCommit)) .limit(1) .collect(Collectors.toList()).get(0); throw new WantNotValidException(nonCommit, new Exception("Cannot walk without bitmaps")); //$NON-NLS-1$ } try (ObjectWalk objWalk = walk.toObjectWalkWithSameObjects()) { Stream startersAsObjs = importantRefsFirst(visibleRefs) .map(UploadPack::refToObjectId) .map(objId -> objectIdToRevObject(objWalk, objId)) .filter(Objects::nonNull); // Ignore missing tips ObjectReachabilityChecker reachabilityChecker = reader .createObjectReachabilityChecker(objWalk); Optional unreachable = reachabilityChecker .areAllReachable(wantsAsObjs, startersAsObjs); if (unreachable.isPresent()) { if (!repoHasBitmaps) { throw new WantNotValidException( unreachable.get(), new Exception( "Retry with bitmaps enabled")); //$NON-NLS-1$ } throw new WantNotValidException(unreachable.get()); } } return; } // All wants are commits, we can use ReachabilityChecker ReachabilityChecker reachabilityChecker = reader .createReachabilityChecker(walk); Stream reachableCommits = importantRefsFirst(visibleRefs) .map(UploadPack::refToObjectId) .map(objId -> objectIdToRevCommit(walk, objId)) .filter(Objects::nonNull); // Ignore missing tips Optional unreachable = reachabilityChecker .areAllReachable(wantsAsCommits, reachableCommits); if (unreachable.isPresent()) { throw new WantNotValidException(unreachable.get()); } } catch (MissingObjectException notFound) { throw new WantNotValidException(notFound.getObjectId(), notFound); } } private static Predicate not(Predicate t) { return t.negate(); } static Stream importantRefsFirst( Collection visibleRefs) { Predicate startsWithRefsHeads = ref -> ref.getName() .startsWith(Constants.R_HEADS); Predicate startsWithRefsTags = ref -> ref.getName() .startsWith(Constants.R_TAGS); Predicate allOther = ref -> !startsWithRefsHeads.test(ref) && !startsWithRefsTags.test(ref); return Stream.concat( visibleRefs.stream().filter(startsWithRefsHeads), Stream.concat( visibleRefs.stream().filter(startsWithRefsTags), visibleRefs.stream().filter(allOther))); } private static ObjectId refToObjectId(Ref ref) { return ref.getObjectId() != null ? ref.getObjectId() : ref.getPeeledObjectId(); } /** * Translate an object id to a RevCommit. * * @param walk * walk on the relevant object storae * @param objectId * Object Id * @return RevCommit instance or null if the object is missing */ @Nullable private static RevCommit objectIdToRevCommit(RevWalk walk, ObjectId objectId) { if (objectId == null) { return null; } try { return walk.parseCommit(objectId); } catch (IOException e) { return null; } } /** * Translate an object id to a RevObject. * * @param walk * walk on the relevant object storage * @param objectId * Object Id * @return RevObject instance or null if the object is missing */ @Nullable private static RevObject objectIdToRevObject(RevWalk walk, ObjectId objectId) { if (objectId == null) { return null; } try { return walk.parseAny(objectId); } catch (IOException e) { return null; } } // Resolve the ObjectIds into RevObjects. Any missing object raises an // exception private static List objectIdsToRevObjects(RevWalk walk, Iterable objectIds) throws MissingObjectException, IOException { List result = new ArrayList<>(); for (ObjectId objectId : objectIds) { result.add(walk.parseAny(objectId)); } return result; } private void addCommonBase(RevObject o) { if (!o.has(COMMON)) { o.add(COMMON); commonBase.add(o); okToGiveUp = null; } } private boolean okToGiveUp() throws PackProtocolException { if (okToGiveUp == null) okToGiveUp = Boolean.valueOf(okToGiveUpImp()); return okToGiveUp.booleanValue(); } private boolean okToGiveUpImp() throws PackProtocolException { if (commonBase.isEmpty()) return false; try { for (RevObject obj : wantAll) { if (!wantSatisfied(obj)) return false; } return true; } catch (IOException e) { throw new PackProtocolException(JGitText.get().internalRevisionError, e); } } private boolean wantSatisfied(RevObject want) throws IOException { if (want.has(SATISFIED)) return true; if (((RevCommit) want).getParentCount() == 0) { want.add(SATISFIED); return true; } walk.resetRetain(SAVE); walk.markStart((RevCommit) want); if (oldestTime != 0) walk.setRevFilter(CommitTimeRevFilter.after(Instant.ofEpochSecond(oldestTime))); for (;;) { final RevCommit c = walk.next(); if (c == null) break; if (c.has(PEER_HAS)) { addCommonBase(c); want.add(SATISFIED); return true; } } return false; } /** * Send the requested objects to the client. * * @param accumulator * where to write statistics about the content of the pack. * @param req * request in process * @param allTags * refs to search for annotated tags to include in the pack if * the {@link GitProtocolConstants#OPTION_INCLUDE_TAG} capability * was requested. * @param unshallowCommits * shallow commits on the client that are now becoming unshallow * @param deepenNots * objects that the client specified using --shallow-exclude * @param pckOut * output writer * @throws IOException * if an error occurred while generating or writing the pack. */ private void sendPack(PackStatistics.Accumulator accumulator, FetchRequest req, @Nullable Collection allTags, List unshallowCommits, List deepenNots, PacketLineOut pckOut) throws IOException { Set caps = req.getClientCapabilities(); boolean sideband = caps.contains(OPTION_SIDE_BAND) || caps.contains(OPTION_SIDE_BAND_64K); if (sideband) { errOut = new SideBandErrorWriter(); int bufsz = SideBandOutputStream.SMALL_BUF; if (req.getClientCapabilities().contains(OPTION_SIDE_BAND_64K)) { bufsz = SideBandOutputStream.MAX_BUF; } OutputStream packOut = new SideBandOutputStream( SideBandOutputStream.CH_DATA, bufsz, rawOut); ProgressMonitor pm = NullProgressMonitor.INSTANCE; if (!req.getClientCapabilities().contains(OPTION_NO_PROGRESS)) { msgOut = new SideBandOutputStream( SideBandOutputStream.CH_PROGRESS, bufsz, rawOut); pm = new SideBandProgressMonitor(msgOut); } sendPack(pm, pckOut, packOut, req, accumulator, allTags, unshallowCommits, deepenNots); pckOut.end(); } else { sendPack(NullProgressMonitor.INSTANCE, pckOut, rawOut, req, accumulator, allTags, unshallowCommits, deepenNots); } } /** * Send the requested objects to the client. * * @param pm * progress monitor * @param pckOut * PacketLineOut that shares the output with packOut * @param packOut * packfile output * @param req * request being processed * @param accumulator * where to write statistics about the content of the pack. * @param allTags * refs to search for annotated tags to include in the pack if * the {@link GitProtocolConstants#OPTION_INCLUDE_TAG} capability * was requested. * @param unshallowCommits * shallow commits on the client that are now becoming unshallow * @param deepenNots * objects that the client specified using --shallow-exclude * @throws IOException * if an error occurred while generating or writing the pack. */ private void sendPack(ProgressMonitor pm, PacketLineOut pckOut, OutputStream packOut, FetchRequest req, PackStatistics.Accumulator accumulator, @Nullable Collection allTags, List unshallowCommits, List deepenNots) throws IOException { if (wantAll.isEmpty()) { preUploadHook.onSendPack(this, wantIds, commonBase); } else { preUploadHook.onSendPack(this, wantAll, commonBase); } msgOut.flush(); PackConfig cfg = packConfig; if (cfg == null) cfg = new PackConfig(db); @SuppressWarnings("resource") // PackWriter is referenced in the finally // block, and is closed there final PackWriter pw = new PackWriter(cfg, walk.getObjectReader(), accumulator); try { pw.setIndexDisabled(true); if (req.getFilterSpec().isNoOp()) { pw.setUseCachedPacks(true); } else { pw.setFilterSpec(req.getFilterSpec()); pw.setUseCachedPacks(false); } pw.setUseBitmaps( req.getDepth() == 0 && req.getClientShallowCommits().isEmpty() && req.getFilterSpec().getTreeDepthLimit() == -1); pw.setClientShallowCommits(req.getClientShallowCommits()); pw.setReuseDeltaCommits(true); pw.setDeltaBaseAsOffset( req.getClientCapabilities().contains(OPTION_OFS_DELTA)); pw.setThin(req.getClientCapabilities().contains(OPTION_THIN_PACK)); pw.setReuseValidatingObjects(false); // Objects named directly by references go at the beginning // of the pack. if (commonBase.isEmpty() && refs != null) { Set tagTargets = new HashSet<>(); for (Ref ref : refs.values()) { if (ref.getPeeledObjectId() != null) tagTargets.add(ref.getPeeledObjectId()); else if (ref.getObjectId() == null) continue; else if (ref.getName().startsWith(Constants.R_HEADS)) tagTargets.add(ref.getObjectId()); } pw.setTagTargets(tagTargets); } // Advertised objects and refs are not used from here on and can be // cleared. advertised = null; refs = null; RevWalk rw = walk; if (req.getDepth() > 0 || req.getDeepenSince() != 0 || !deepenNots.isEmpty()) { int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE : req.getDepth() - 1; pw.setShallowPack(req.getDepth(), unshallowCommits); // dw borrows the reader from walk which is closed by #close @SuppressWarnings("resource") DepthWalk.RevWalk dw = new DepthWalk.RevWalk( walk.getObjectReader(), walkDepth); dw.setDeepenSince(req.getDeepenSince()); dw.setDeepenNots(deepenNots); dw.assumeShallow(req.getClientShallowCommits()); rw = dw; } if (wantAll.isEmpty()) { pw.preparePack(pm, wantIds, commonBase, req.getClientShallowCommits()); } else { walk.reset(); ObjectWalk ow = rw.toObjectWalkWithSameObjects(); pw.preparePack(pm, ow, wantAll, commonBase, PackWriter.NONE); rw = ow; } if (req.getClientCapabilities().contains(OPTION_INCLUDE_TAG) && allTags != null) { for (Ref ref : allTags) { ObjectId objectId = ref.getObjectId(); if (objectId == null) { // skip unborn branch continue; } // If the object was already requested, skip it. if (wantAll.isEmpty()) { if (wantIds.contains(objectId)) continue; } else { RevObject obj = rw.lookupOrNull(objectId); if (obj != null && obj.has(WANT)) continue; } if (!ref.isPeeled()) ref = db.getRefDatabase().peel(ref); ObjectId peeledId = ref.getPeeledObjectId(); objectId = ref.getObjectId(); if (peeledId == null || objectId == null) continue; if (pw.willInclude(peeledId)) { // We don't need to handle parseTag throwing an // IncorrectObjectTypeException as we only reach // here when ref is an annotated tag addTagChain(rw.parseTag(objectId), pw); } } } if (pckOut.isUsingSideband()) { if (req instanceof FetchV2Request && cachedPackUriProvider != null && !((FetchV2Request) req).getPackfileUriProtocols().isEmpty()) { FetchV2Request reqV2 = (FetchV2Request) req; pw.setPackfileUriConfig(new PackWriter.PackfileUriConfig( pckOut, reqV2.getPackfileUriProtocols(), cachedPackUriProvider)); } else { // PackWriter will write "packfile-uris\n" and "packfile\n" // for us if provided a PackfileUriConfig. In this case, we // are not providing a PackfileUriConfig, so we have to // write this line ourselves. pckOut.writeString( GitProtocolConstants.SECTION_PACKFILE + '\n'); } } pw.enableSearchForReuseTimeout(); pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut); if (msgOut != NullOutputStream.INSTANCE) { String msg = pw.getStatistics().getMessage() + '\n'; msgOut.write(Constants.encode(msg)); msgOut.flush(); } } finally { statistics = pw.getStatistics(); if (statistics != null) { postUploadHook.onPostUpload(statistics); } pw.close(); } } private static void findSymrefs( final RefAdvertiser adv, final Map refs) { Ref head = refs.get(Constants.HEAD); if (head != null && head.isSymbolic()) { adv.addSymref(Constants.HEAD, head.getLeaf().getName()); } } private void addTagChain( RevTag tag, PackWriter pw) throws IOException { RevObject o = tag; do { tag = (RevTag) o; walk.parseBody(tag); if (!pw.willInclude(tag.getId())) { pw.addObject(tag); } o = tag.getObject(); } while (Constants.OBJ_TAG == o.getType()); } private List parseDeepenNots(List deepenNots) throws IOException { List result = new ArrayList<>(); for (String s : deepenNots) { if (ObjectId.isId(s)) { result.add(ObjectId.fromString(s)); } else { Ref ref = findRef(s); if (ref == null) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidRefName, s)); } result.add(ref.getObjectId()); } } return result; } private static class ResponseBufferedOutputStream extends OutputStream { private final OutputStream rawOut; private OutputStream out; ResponseBufferedOutputStream(OutputStream rawOut) { this.rawOut = rawOut; this.out = new ByteArrayOutputStream(); } @Override public void write(int b) throws IOException { out.write(b); } @Override public void write(byte[] b) throws IOException { out.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); } @Override public void flush() throws IOException { out.flush(); } @Override public void close() throws IOException { out.close(); } void stopBuffering() throws IOException { if (out != rawOut) { ((ByteArrayOutputStream) out).writeTo(rawOut); out = rawOut; } } } private interface ErrorWriter { void writeError(String message) throws IOException; } private class SideBandErrorWriter implements ErrorWriter { @Override public void writeError(String message) throws IOException { @SuppressWarnings("resource" /* java 7 */) SideBandOutputStream err = new SideBandOutputStream( SideBandOutputStream.CH_ERROR, SideBandOutputStream.SMALL_BUF, requireNonNull(rawOut)); err.write(Constants.encode(message)); err.flush(); } } private class PackProtocolErrorWriter implements ErrorWriter { @Override public void writeError(String message) throws IOException { new PacketLineOut(requireNonNull(rawOut)) .writeString(PACKET_ERR + message + '\n'); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 738 Content-Disposition: inline; filename="UploadPackInternalServerErrorException.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "aac4ea35e3290abcba335f66e14cc09075bbaa26" /* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; /** * UploadPack has already reported an error to the client. */ public class UploadPackInternalServerErrorException extends IOException { private static final long serialVersionUID = 1L; /** * Initialize a new exception. * * @param why * root cause. */ public UploadPackInternalServerErrorException(Throwable why) { initCause(why); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3154 Content-Disposition: inline; filename="UrlConfig.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "574fcf806dd5be0307468bb3bad871bebb04d2c0" /* * Copyright (C) 2022 Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.eclipse.jgit.lib.Config; /** * Support for URL translations via git configs {@code url..insteadOf} and * {@code url..pushInsteadOf}. * * @since 6.2 */ public class UrlConfig { private static final String KEY_INSTEADOF = "insteadof"; //$NON-NLS-1$ private static final String KEY_PUSHINSTEADOF = "pushinsteadof"; //$NON-NLS-1$ private static final String SECTION_URL = "url"; //$NON-NLS-1$ private final Config config; private Map insteadOf; private Map pushInsteadOf; /** * Creates a new {@link UrlConfig} instance. * * @param config * {@link Config} to read values from */ public UrlConfig(Config config) { this.config = config; } /** * Performs replacements as defined by git config * {@code url..insteadOf}. If there is no match, the input is returned * unchanged. * * @param url * to substitute * @return the {@code url} with substitution applied */ public String replace(String url) { if (insteadOf == null) { insteadOf = load(KEY_INSTEADOF); } return replace(url, insteadOf); } /** * Tells whether there are push replacements. * * @return {@code true} if there are push replacements, {@code false} * otherwise */ public boolean hasPushReplacements() { if (pushInsteadOf == null) { pushInsteadOf = load(KEY_PUSHINSTEADOF); } return !pushInsteadOf.isEmpty(); } /** * Performs replacements as defined by git config * {@code url..pushInsteadOf}. If there is no match, the input is * returned unchanged. * * @param url * to substitute * @return the {@code url} with substitution applied */ public String replacePush(String url) { if (pushInsteadOf == null) { pushInsteadOf = load(KEY_PUSHINSTEADOF); } return replace(url, pushInsteadOf); } private Map load(String key) { Map replacements = new HashMap<>(); for (String url : config.getSubsections(SECTION_URL)) { for (String prefix : config.getStringList(SECTION_URL, url, key)) { replacements.put(prefix, url); } } return replacements; } private String replace(String uri, Map replacements) { Entry match = null; for (Entry replacement : replacements.entrySet()) { // Ignore current entry if not longer than previous match if (match != null && match.getKey().length() > replacement.getKey() .length()) { continue; } if (uri.startsWith(replacement.getKey())) { match = replacement; } } if (match != null) { return match.getValue() + uri.substring(match.getKey().length()); } return uri; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2669 Content-Disposition: inline; filename="UserAgent.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "b23ee97dcb88dbd9aa8f85273e4b9a906ce52954" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import org.eclipse.jgit.util.StringUtils; /** * User agent to be reported by this JGit client and server on the network. *

* On HTTP transports this user agent string is always supplied by the JGit * client in the {@code User-Agent} HTTP header. *

* On native transports this user agent string is always sent when JGit is a * server. When JGit is a client the user agent string will be supplied to the * remote server only if the remote server advertises its own agent identity. * * @since 4.0 */ public class UserAgent { private static volatile String userAgent = computeUserAgent(); private static String computeUserAgent() { return clean("JGit/" + computeVersion()); //$NON-NLS-1$ } private static String computeVersion() { Package pkg = UserAgent.class.getPackage(); if (pkg != null) { String ver = pkg.getImplementationVersion(); if (!StringUtils.isEmptyOrNull(ver)) { return ver; } } return "unknown"; //$NON-NLS-1$ } static String clean(String s) { s = s.trim(); StringBuilder b = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c <= 32 || c >= 127) { if (b.length() > 0 && b.charAt(b.length() - 1) == '.') continue; c = '.'; } b.append(c); } return b.length() > 0 ? b.toString() : null; } /** * Get the user agent string advertised by JGit. * * @return a string similar to {@code "JGit/4.0"}; null if the agent has * been cleared and should not be shared with a peer. */ public static String get() { return userAgent; } /** * Change the user agent string advertised by JGit. *

* The new string should start with {@code "JGit/"} (for example * {@code "JGit/4.0"}) to advertise the implementation as JGit based. *

* Spaces and other whitespace should be avoided as these will be * automatically converted to {@code "."}. *

* User agent strings are restricted to printable ASCII. * * @param agent * new user agent string for this running JGit library. Setting * to null or empty string will avoid sending any identification * to the peer. */ public static void set(String agent) { userAgent = StringUtils.isEmptyOrNull(agent) ? null : clean(agent); } private UserAgent() { } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2836 Content-Disposition: inline; filename="UsernamePasswordCredentialsProvider.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "da8af5a0fe1e6c1f0fb9e0f1b70e535f2a782a32" /* * Copyright (C) 2010, 2021 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.util.Arrays; import org.eclipse.jgit.errors.UnsupportedCredentialItem; /** * Simple {@link org.eclipse.jgit.transport.CredentialsProvider} that always * uses the same information. */ public class UsernamePasswordCredentialsProvider extends CredentialsProvider { private String username; private char[] password; /** * Initialize the provider with a single username and password. * * @param username * user name * @param password * password */ public UsernamePasswordCredentialsProvider(String username, String password) { this(username, password.toCharArray()); } /** * Initialize the provider with a single username and password. * * @param username * user name * @param password * password */ public UsernamePasswordCredentialsProvider(String username, char[] password) { this.username = username; this.password = password; } @Override public boolean isInteractive() { return false; } @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { if (i instanceof CredentialItem.InformationalMessage) { continue; } if (i instanceof CredentialItem.Username) { continue; } if (i instanceof CredentialItem.Password) { continue; } if (i instanceof CredentialItem.StringType) { if (i.getPromptText().equals("Password: ")) { //$NON-NLS-1$ continue; } } return false; } return true; } @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { for (CredentialItem i : items) { if (i instanceof CredentialItem.InformationalMessage) { continue; } if (i instanceof CredentialItem.Username) { ((CredentialItem.Username) i).setValue(username); continue; } if (i instanceof CredentialItem.Password) { ((CredentialItem.Password) i).setValue(password); continue; } if (i instanceof CredentialItem.StringType) { if (i.getPromptText().equals("Password: ")) { //$NON-NLS-1$ ((CredentialItem.StringType) i).setValue(new String( password)); continue; } } throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText()); //$NON-NLS-1$ } return true; } /** * Destroy the saved username and password.. */ public void clear() { username = null; if (password != null) { Arrays.fill(password, (char) 0); password = null; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 17989 Content-Disposition: inline; filename="WalkEncryption.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "3bfc5234ee410e33d813a6cfa602fc48ced7d697" /* * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.KeySpec; import java.text.MessageFormat; import java.util.Locale; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.Base64; import org.eclipse.jgit.util.Hex; abstract class WalkEncryption { static final WalkEncryption NONE = new NoEncryption(); static final String JETS3T_CRYPTO_VER = "jets3t-crypto-ver"; //$NON-NLS-1$ static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg"; //$NON-NLS-1$ // Note: encrypt -> request state machine, step 1. abstract OutputStream encrypt(OutputStream output) throws IOException; // Note: encrypt -> request state machine, step 2. abstract void request(HttpURLConnection conn, String prefix) throws IOException; // Note: validate -> decrypt state machine, step 1. abstract void validate(HttpURLConnection conn, String prefix) throws IOException; // Note: validate -> decrypt state machine, step 2. abstract InputStream decrypt(InputStream input) throws IOException; // TODO mixed ciphers // consider permitting mixed ciphers to facilitate algorithm migration // i.e. user keeps the password, but changes the algorithm // then existing remote entries will still be readable /** * Validate * * @param u * a {@link java.net.HttpURLConnection} object. * @param prefix * a {@link java.lang.String} object. * @param version * a {@link java.lang.String} object. * @param name * a {@link java.lang.String} object. * @throws java.io.IOException * if any. */ protected void validateImpl(final HttpURLConnection u, final String prefix, final String version, final String name) throws IOException { String v; v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER); if (v == null) v = ""; //$NON-NLS-1$ if (!version.equals(v)) throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v)); v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG); if (v == null) v = ""; //$NON-NLS-1$ // Standard names are not case-sensitive. // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html if (!name.equalsIgnoreCase(v)) throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v)); } IOException error(Throwable why) { return new IOException(MessageFormat .format(JGitText.get().encryptionError, why.getMessage()), why); } private static class NoEncryption extends WalkEncryption { @Override void request(HttpURLConnection u, String prefix) { // Don't store any request properties. } @Override void validate(HttpURLConnection u, String prefix) throws IOException { validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ } @Override InputStream decrypt(InputStream in) { return in; } @Override OutputStream encrypt(OutputStream os) { return os; } } /** * JetS3t compatibility reference: * EncryptionUtil.java *

* Note: EncryptionUtil is inadequate: *

    *
  • EncryptionUtil.isCipherAvailableForUse checks encryption only which * "always works", but in JetS3t both encryption and decryption use non-IV * aware algorithm parameters for all PBE specs, which breaks in case of AES *
  • that means that only non-IV algorithms will work round trip in * JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC *
  • any AES based algorithms such as "PBE...With...And...AES" will not * work, since they need proper IV setup *
*/ static class JetS3tV2 extends WalkEncryption { static final String VERSION = "2"; //$NON-NLS-1$ static final String ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$ static final int ITERATIONS = 5000; static final int KEY_SIZE = 32; static final byte[] SALT = { // (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, // (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 // }; // Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE static final byte[] ZERO_AES_IV = new byte[16]; private static final String CRYPTO_VER = VERSION; private final String cryptoAlg; private final SecretKey secretKey; private final AlgorithmParameterSpec paramSpec; JetS3tV2(final String algo, final String key) throws GeneralSecurityException { cryptoAlg = algo; // Verify if cipher is present. Cipher cipher = InsecureCipherFactory.create(cryptoAlg); // Standard names are not case-sensitive. // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html String cryptoName = cryptoAlg.toUpperCase(Locale.ROOT); if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$ throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE); PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), SALT, ITERATIONS, KEY_SIZE); secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec); // Detect algorithms which require initialization vector. boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$ // PBEParameterSpec algorithm parameters are supported from Java 8. if (useIV) { // Support IV where possible: // * since JCE provider uses random IV for PBE/AES // * and there is no place to store dynamic IV in JetS3t V2 // * we use static IV, and tolerate increased security risk // TODO back port this change to JetS3t V2 // See: // https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java // http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV); paramSpec = new PBEParameterSpec(SALT, ITERATIONS, paramIV); } else { // Strict legacy JetS3t V2 compatibility, with no IV support. paramSpec = new PBEParameterSpec(SALT, ITERATIONS); } // Verify if cipher + key are allowed by policy. cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); cipher.doFinal(); } @Override void request(HttpURLConnection u, String prefix) { u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, CRYPTO_VER); u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg); } @Override void validate(HttpURLConnection u, String prefix) throws IOException { validateImpl(u, prefix, CRYPTO_VER, cryptoAlg); } @Override OutputStream encrypt(OutputStream os) throws IOException { try { final Cipher cipher = InsecureCipherFactory.create(cryptoAlg); cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); return new CipherOutputStream(os, cipher); } catch (GeneralSecurityException e) { throw error(e); } } @Override InputStream decrypt(InputStream in) throws IOException { try { final Cipher cipher = InsecureCipherFactory.create(cryptoAlg); cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); return new CipherInputStream(in, cipher); } catch (GeneralSecurityException e) { throw error(e); } } } /** Encryption property names. */ interface Keys { // Remote S3 meta: V1 algorithm name or V2 profile name. String JGIT_PROFILE = "jgit-crypto-profile"; //$NON-NLS-1$ // Remote S3 meta: JGit encryption implementation version. String JGIT_VERSION = "jgit-crypto-version"; //$NON-NLS-1$ // Remote S3 meta: base-64 encoded cipher algorithm parameters. String JGIT_CONTEXT = "jgit-crypto-context"; //$NON-NLS-1$ // Amazon S3 connection configuration file profile property suffixes: String X_ALGO = ".algo"; //$NON-NLS-1$ String X_KEY_ALGO = ".key.algo"; //$NON-NLS-1$ String X_KEY_SIZE = ".key.size"; //$NON-NLS-1$ String X_KEY_ITER = ".key.iter"; //$NON-NLS-1$ String X_KEY_SALT = ".key.salt"; //$NON-NLS-1$ } /** Encryption constants and defaults. */ interface Vals { // Compatibility defaults. String DEFAULT_VERS = "0"; //$NON-NLS-1$ String DEFAULT_ALGO = JetS3tV2.ALGORITHM; String DEFAULT_KEY_ALGO = JetS3tV2.ALGORITHM; String DEFAULT_KEY_SIZE = Integer.toString(JetS3tV2.KEY_SIZE); String DEFAULT_KEY_ITER = Integer.toString(JetS3tV2.ITERATIONS); String DEFAULT_KEY_SALT = Hex.toHexString(JetS3tV2.SALT); String EMPTY = ""; //$NON-NLS-1$ // Match white space. String REGEX_WS = "\\s+"; //$NON-NLS-1$ // Match PBE ciphers, i.e: PBEWithMD5AndDES String REGEX_PBE = "(PBE).*(WITH).+(AND).+"; //$NON-NLS-1$ // Match transformation ciphers, i.e: AES/CBC/PKCS5Padding String REGEX_TRANS = "(.+)/(.+)/(.+)"; //$NON-NLS-1$ } static GeneralSecurityException securityError(String message, Throwable cause) { GeneralSecurityException e = new GeneralSecurityException( MessageFormat.format(JGitText.get().encryptionError, message)); e.initCause(cause); return e; } /** * Base implementation of JGit symmetric encryption. Supports V2 properties * format. */ abstract static class SymmetricEncryption extends WalkEncryption implements Keys, Vals { /** Encryption profile, root name of group of related properties. */ final String profile; /** Encryption version, reflects actual implementation class. */ final String version; /** Full cipher algorithm name. */ final String cipherAlgo; /** Cipher algorithm name for parameters lookup. */ final String paramsAlgo; /** Generated secret key. */ final SecretKey secretKey; SymmetricEncryption(Properties props) throws GeneralSecurityException { profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); version = props.getProperty(AmazonS3.Keys.CRYPTO_VER); String pass = props.getProperty(AmazonS3.Keys.PASSWORD); cipherAlgo = props.getProperty(profile + X_ALGO, DEFAULT_ALGO); String keyAlgo = props.getProperty(profile + X_KEY_ALGO, DEFAULT_KEY_ALGO); String keySize = props.getProperty(profile + X_KEY_SIZE, DEFAULT_KEY_SIZE); String keyIter = props.getProperty(profile + X_KEY_ITER, DEFAULT_KEY_ITER); String keySalt = props.getProperty(profile + X_KEY_SALT, DEFAULT_KEY_SALT); // Verify if cipher is present. Cipher cipher = InsecureCipherFactory.create(cipherAlgo); // Verify if key factory is present. SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgo); final int size; try { size = Integer.parseInt(keySize); } catch (Exception e) { throw securityError(X_KEY_SIZE + EMPTY + keySize, e); } final int iter; try { iter = Integer.parseInt(keyIter); } catch (Exception e) { throw securityError(X_KEY_ITER + EMPTY + keyIter, e); } final byte[] salt; try { salt = Hex.decode(keySalt.replaceAll(REGEX_WS, EMPTY)); } catch (Exception e) { throw securityError(X_KEY_SALT + EMPTY + keySalt, e); } KeySpec keySpec = new PBEKeySpec(pass.toCharArray(), salt, iter, size); SecretKey keyBase = factory.generateSecret(keySpec); String name = cipherAlgo.toUpperCase(Locale.ROOT); Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name); Matcher matcherTrans = Pattern.compile(REGEX_TRANS).matcher(name); if (matcherPBE.matches()) { paramsAlgo = cipherAlgo; secretKey = keyBase; } else if (matcherTrans.find()) { paramsAlgo = matcherTrans.group(1); secretKey = new SecretKeySpec(keyBase.getEncoded(), paramsAlgo); } else { throw new GeneralSecurityException(MessageFormat.format( JGitText.get().unsupportedEncryptionAlgorithm, cipherAlgo)); } // Verify if cipher + key are allowed by policy. cipher.init(Cipher.ENCRYPT_MODE, secretKey); cipher.doFinal(); } // Shared state encrypt -> request. volatile String context; @Override OutputStream encrypt(OutputStream output) throws IOException { try { Cipher cipher = InsecureCipherFactory.create(cipherAlgo); cipher.init(Cipher.ENCRYPT_MODE, secretKey); AlgorithmParameters params = cipher.getParameters(); if (params == null) { context = EMPTY; } else { context = Base64.encodeBytes(params.getEncoded()); } return new CipherOutputStream(output, cipher); } catch (Exception e) { throw error(e); } } @Override void request(HttpURLConnection conn, String prefix) throws IOException { conn.setRequestProperty(prefix + JGIT_PROFILE, profile); conn.setRequestProperty(prefix + JGIT_VERSION, version); conn.setRequestProperty(prefix + JGIT_CONTEXT, context); // No cleanup: // single encrypt can be followed by several request // from the AmazonS3.putImpl() multiple retry attempts // context = null; // Cleanup encrypt -> request transition. // TODO re-factor AmazonS3.putImpl to be more transaction-like } // Shared state validate -> decrypt. volatile Cipher decryptCipher; @Override void validate(HttpURLConnection conn, String prefix) throws IOException { String prof = conn.getHeaderField(prefix + JGIT_PROFILE); String vers = conn.getHeaderField(prefix + JGIT_VERSION); String cont = conn.getHeaderField(prefix + JGIT_CONTEXT); if (prof == null) { throw new IOException(MessageFormat .format(JGitText.get().encryptionError, JGIT_PROFILE)); } if (vers == null) { throw new IOException(MessageFormat .format(JGitText.get().encryptionError, JGIT_VERSION)); } if (cont == null) { throw new IOException(MessageFormat .format(JGitText.get().encryptionError, JGIT_CONTEXT)); } if (!profile.equals(prof)) { throw new IOException(MessageFormat.format( JGitText.get().unsupportedEncryptionAlgorithm, prof)); } if (!version.equals(vers)) { throw new IOException(MessageFormat.format( JGitText.get().unsupportedEncryptionVersion, vers)); } try { decryptCipher = InsecureCipherFactory.create(cipherAlgo); if (cont.isEmpty()) { decryptCipher.init(Cipher.DECRYPT_MODE, secretKey); } else { AlgorithmParameters params = AlgorithmParameters .getInstance(paramsAlgo); params.init(Base64.decode(cont)); decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, params); } } catch (Exception e) { throw error(e); } } @Override InputStream decrypt(InputStream input) throws IOException { try { return new CipherInputStream(input, decryptCipher); } finally { decryptCipher = null; // Cleanup validate -> decrypt transition. } } } /** * Provides JetS3t-like encryption with AES support. Uses V1 connection file * format. For reference, see: 'jgit-s3-connection-v-1.properties'. */ static class JGitV1 extends SymmetricEncryption { static final String VERSION = "1"; //$NON-NLS-1$ // Re-map connection properties V1 -> V2. static Properties wrap(String algo, String pass) { Properties props = new Properties(); props.put(AmazonS3.Keys.CRYPTO_ALG, algo); props.put(AmazonS3.Keys.CRYPTO_VER, VERSION); props.put(AmazonS3.Keys.PASSWORD, pass); props.put(algo + Keys.X_ALGO, algo); props.put(algo + Keys.X_KEY_ALGO, algo); props.put(algo + Keys.X_KEY_ITER, DEFAULT_KEY_ITER); props.put(algo + Keys.X_KEY_SIZE, DEFAULT_KEY_SIZE); props.put(algo + Keys.X_KEY_SALT, DEFAULT_KEY_SALT); return props; } JGitV1(String algo, String pass) throws GeneralSecurityException { super(wrap(algo, pass)); String name = cipherAlgo.toUpperCase(Locale.ROOT); Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name); if (!matcherPBE.matches()) throw new GeneralSecurityException( JGitText.get().encryptionOnlyPBE); } } /** * Supports both PBE and non-PBE algorithms. Uses V2 connection file format. * For reference, see: 'jgit-s3-connection-v-2.properties'. */ static class JGitV2 extends SymmetricEncryption { static final String VERSION = "2"; //$NON-NLS-1$ JGitV2(Properties props) throws GeneralSecurityException { super(props); } } /** * Encryption factory. * * @param props * configuration properties * @return instance this object * @throws GeneralSecurityException * if generic security failure occurred */ static WalkEncryption instance(Properties props) throws GeneralSecurityException { String algo = props.getProperty(AmazonS3.Keys.CRYPTO_ALG, Vals.DEFAULT_ALGO); String vers = props.getProperty(AmazonS3.Keys.CRYPTO_VER, Vals.DEFAULT_VERS); String pass = props.getProperty(AmazonS3.Keys.PASSWORD); if (pass == null) // Disable encryption. return WalkEncryption.NONE; switch (vers) { case Vals.DEFAULT_VERS: return new JetS3tV2(algo, pass); case JGitV1.VERSION: return new JGitV1(algo, pass); case JGitV2.VERSION: return new JGitV2(props); default: throw new GeneralSecurityException(MessageFormat.format( JGitText.get().unsupportedEncryptionVersion, vers)); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 25842 Content-Disposition: inline; filename="WalkFetchConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "b7bb0cbce31dc6d17714c3c94e9f7a4abe1fb0b2" /* * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.jgit.errors.CompoundException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.ObjectDirectory; import org.eclipse.jgit.internal.storage.file.PackIndex; import org.eclipse.jgit.internal.storage.file.UnpackedObject; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.DateRevQueue; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FileUtils; /** * Generic fetch support for dumb transport protocols. *

* Since there are no Git-specific smarts on the remote side of the connection * the client side must determine which objects it needs to copy in order to * completely fetch the requested refs and their history. The generic walk * support in this class parses each individual object (once it has been copied * to the local repository) and examines the list of objects that must also be * copied to create a complete history. Objects which are already available * locally are retained (and not copied), saving bandwidth for incremental * fetches. Pack files are copied from the remote repository only as a last * resort, as the entire pack must be copied locally in order to access any * single object. *

* This fetch connection does not actually perform the object data transfer. * Instead it delegates the transfer to a {@link WalkRemoteObjectDatabase}, * which knows how to read individual files from the remote repository and * supply the data as a standard Java InputStream. * * @see WalkRemoteObjectDatabase */ class WalkFetchConnection extends BaseFetchConnection { /** The repository this transport fetches into, or pushes out of. */ final Repository local; /** If not null the validator for received objects. */ final ObjectChecker objCheck; /** * List of all remote repositories we may need to get objects out of. *

* The first repository in the list is the one we were asked to fetch from; * the remaining repositories point to the alternate locations we can fetch * objects through. */ private final List remotes; /** Most recently used item in {@link #remotes}. */ private int lastRemoteIdx; private final RevWalk revWalk; private final TreeWalk treeWalk; /** Objects whose direct dependents we know we have (or will have). */ private final RevFlag COMPLETE; /** Objects that have already entered {@link #workQueue}. */ private final RevFlag IN_WORK_QUEUE; /** Commits that have already entered {@link #localCommitQueue}. */ private final RevFlag LOCALLY_SEEN; /** Commits already reachable from all local refs. */ private final DateRevQueue localCommitQueue; /** Objects we need to copy from the remote repository. */ private Deque workQueue; /** Databases we have not yet obtained the list of packs from. */ private final Deque noPacksYet; /** Databases we have not yet obtained the alternates from. */ private final Deque noAlternatesYet; /** Packs we have discovered, but have not yet fetched locally. */ private final Map unfetchedPacks; /** * Packs whose indexes we have looked at in {@link #unfetchedPacks}. *

* We try to avoid getting duplicate copies of the same pack through * multiple alternates by only looking at packs whose names are not yet in * this collection. */ private final Set packsConsidered; private final MutableObjectId idBuffer = new MutableObjectId(); /** * Errors received while trying to obtain an object. *

* If the fetch winds up failing because we cannot locate a specific object * then we need to report all errors related to that object back to the * caller as there may be cascading failures. */ private final HashMap> fetchErrors; String lockMessage; final List packLocks; /** Inserter to write objects onto {@link #local}. */ final ObjectInserter inserter; /** Inserter to read objects from {@link #local}. */ private final ObjectReader reader; WalkFetchConnection(WalkTransport t, WalkRemoteObjectDatabase w) { Transport wt = (Transport)t; local = wt.local; objCheck = wt.getObjectChecker(); inserter = local.newObjectInserter(); reader = inserter.newReader(); remotes = new ArrayList<>(); remotes.add(w); unfetchedPacks = new LinkedHashMap<>(); packsConsidered = new HashSet<>(); noPacksYet = new ArrayDeque<>(); noPacksYet.add(w); noAlternatesYet = new ArrayDeque<>(); noAlternatesYet.add(w); fetchErrors = new HashMap<>(); packLocks = new ArrayList<>(4); revWalk = new RevWalk(reader); revWalk.setRetainBody(false); treeWalk = new TreeWalk(reader); COMPLETE = revWalk.newFlag("COMPLETE"); //$NON-NLS-1$ IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE"); //$NON-NLS-1$ LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); //$NON-NLS-1$ localCommitQueue = new DateRevQueue(); workQueue = new ArrayDeque<>(); } @Override public boolean didFetchTestConnectivity() { return true; } @Override protected void doFetch(final ProgressMonitor monitor, final Collection want, final Set have) throws TransportException { markLocalRefsComplete(have); queueWants(want); while (!monitor.isCancelled() && !workQueue.isEmpty()) { final ObjectId id = workQueue.removeFirst(); if (!(id instanceof RevObject) || !((RevObject) id).has(COMPLETE)) downloadObject(monitor, id); process(id); } try { inserter.flush(); } catch (IOException e) { throw new TransportException(e.getMessage(), e); } } @Override public Collection getPackLocks() { return packLocks; } @Override public void setPackLockMessage(String message) { lockMessage = message; } @Override public void close() { inserter.close(); reader.close(); for (RemotePack p : unfetchedPacks.values()) { if (p.tmpIdx != null) p.tmpIdx.delete(); } for (WalkRemoteObjectDatabase r : remotes) r.close(); } private void queueWants(Collection want) throws TransportException { final HashSet inWorkQueue = new HashSet<>(); for (Ref r : want) { final ObjectId id = r.getObjectId(); if (id == null) { throw new NullPointerException(MessageFormat.format( JGitText.get().transportProvidedRefWithNoObjectId, r.getName())); } try { final RevObject obj = revWalk.parseAny(id); if (obj.has(COMPLETE)) continue; if (inWorkQueue.add(id)) { obj.add(IN_WORK_QUEUE); workQueue.add(obj); } } catch (MissingObjectException e) { if (inWorkQueue.add(id)) workQueue.add(id); } catch (IOException e) { throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e); } } } private void process(ObjectId id) throws TransportException { final RevObject obj; try { if (id instanceof RevObject) { obj = (RevObject) id; if (obj.has(COMPLETE)) return; revWalk.parseHeaders(obj); } else { obj = revWalk.parseAny(id); if (obj.has(COMPLETE)) return; } } catch (IOException e) { throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e); } switch (obj.getType()) { case Constants.OBJ_BLOB: processBlob(obj); break; case Constants.OBJ_TREE: processTree(obj); break; case Constants.OBJ_COMMIT: processCommit(obj); break; case Constants.OBJ_TAG: processTag(obj); break; default: throw new TransportException(MessageFormat.format(JGitText.get().unknownObjectType, id.name())); } // If we had any prior errors fetching this object they are // now resolved, as the object was parsed successfully. // fetchErrors.remove(id); } private void processBlob(RevObject obj) throws TransportException { try { if (reader.has(obj, Constants.OBJ_BLOB)) obj.add(COMPLETE); else throw new TransportException(MessageFormat.format(JGitText .get().cannotReadBlob, obj.name()), new MissingObjectException(obj, Constants.TYPE_BLOB)); } catch (IOException error) { throw new TransportException(MessageFormat.format( JGitText.get().cannotReadBlob, obj.name()), error); } } private void processTree(RevObject obj) throws TransportException { try { treeWalk.reset(obj); while (treeWalk.next()) { final FileMode mode = treeWalk.getFileMode(0); final int sType = mode.getObjectType(); switch (sType) { case Constants.OBJ_BLOB: case Constants.OBJ_TREE: treeWalk.getObjectId(idBuffer, 0); needs(revWalk.lookupAny(idBuffer, sType)); continue; default: if (FileMode.GITLINK.equals(mode)) continue; treeWalk.getObjectId(idBuffer, 0); throw new CorruptObjectException(MessageFormat.format(JGitText.get().invalidModeFor , mode, idBuffer.name(), treeWalk.getPathString(), obj.getId().name())); } } } catch (IOException ioe) { throw new TransportException(MessageFormat.format(JGitText.get().cannotReadTree, obj.name()), ioe); } obj.add(COMPLETE); } private void processCommit(RevObject obj) throws TransportException { final RevCommit commit = (RevCommit) obj; markLocalCommitsComplete(commit.getCommitTime()); needs(commit.getTree()); for (RevCommit p : commit.getParents()) needs(p); obj.add(COMPLETE); } private void processTag(RevObject obj) { final RevTag tag = (RevTag) obj; needs(tag.getObject()); obj.add(COMPLETE); } private void needs(RevObject obj) { if (obj.has(COMPLETE)) return; if (!obj.has(IN_WORK_QUEUE)) { obj.add(IN_WORK_QUEUE); workQueue.add(obj); } } private void downloadObject(ProgressMonitor pm, AnyObjectId id) throws TransportException { if (alreadyHave(id)) return; for (;;) { // Try a pack file we know about, but don't have yet. Odds are // that if it has this object, it has others related to it so // getting the pack is a good bet. // if (downloadPackedObject(pm, id)) return; // Search for a loose object over all alternates, starting // from the one we last successfully located an object through. // final String idStr = id.name(); final String subdir = idStr.substring(0, 2); final String file = idStr.substring(2); final String looseName = subdir + "/" + file; //$NON-NLS-1$ for (int i = lastRemoteIdx; i < remotes.size(); i++) { if (downloadLooseObject(id, looseName, remotes.get(i))) { lastRemoteIdx = i; return; } } for (int i = 0; i < lastRemoteIdx; i++) { if (downloadLooseObject(id, looseName, remotes.get(i))) { lastRemoteIdx = i; return; } } // Try to obtain more pack information and search those. // while (!noPacksYet.isEmpty()) { final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst(); final Collection packNameList; try { pm.beginTask(JGitText.get().listingPacks, ProgressMonitor.UNKNOWN); packNameList = wrr.getPackNames(); } catch (IOException e) { // Try another repository. // recordError(id, e); continue; } finally { pm.endTask(); } if (packNameList == null || packNameList.isEmpty()) continue; for (String packName : packNameList) { if (packsConsidered.add(packName)) { unfetchedPacks.put(packName, new RemotePack(wrr, packName)); } } if (downloadPackedObject(pm, id)) return; } // Try to expand the first alternate we haven't expanded yet. // Collection al = expandOneAlternate(id, pm); if (al != null && !al.isEmpty()) { for (WalkRemoteObjectDatabase alt : al) { remotes.add(alt); noPacksYet.add(alt); noAlternatesYet.add(alt); } continue; } // We could not obtain the object. There may be reasons why. // List failures = fetchErrors.get(id); final TransportException te; te = new TransportException(MessageFormat.format(JGitText.get().cannotGet, id.name())); if (failures != null && !failures.isEmpty()) { if (failures.size() == 1) te.initCause(failures.get(0)); else te.initCause(new CompoundException(failures)); } throw te; } } private boolean alreadyHave(AnyObjectId id) throws TransportException { try { return reader.has(id); } catch (IOException error) { throw new TransportException(MessageFormat.format( JGitText.get().cannotReadObject, id.name()), error); } } private boolean downloadPackedObject(ProgressMonitor monitor, AnyObjectId id) throws TransportException { Set brokenPacks = new HashSet<>(); try { return downloadPackedObject(monitor, id, brokenPacks); } finally { brokenPacks.forEach(unfetchedPacks::remove); } } @SuppressWarnings("Finally") private boolean downloadPackedObject(final ProgressMonitor monitor, final AnyObjectId id, Set brokenPacks) throws TransportException { // Search for the object in a remote pack whose index we have, // but whose pack we do not yet have. // for (Entry entry : unfetchedPacks.entrySet()) { if (monitor.isCancelled()) { break; } final RemotePack pack = entry.getValue(); try { pack.openIndex(monitor); } catch (IOException err) { // If the index won't open its either not found or // its a format we don't recognize. In either case // we may still be able to obtain the object from // another source, so don't consider it a failure. // recordError(id, err); brokenPacks.add(entry.getKey()); continue; } if (monitor.isCancelled()) { // If we were cancelled while the index was opening // the open may have aborted. We can't search an // unopen index. // return false; } if (!pack.index.hasObject(id)) { // Not in this pack? Try another. // continue; } // It should be in the associated pack. Download that // and attach it to the local repository so we can use // all of the contained objects. // Throwable e1 = null; try { pack.downloadPack(monitor); } catch (IOException err) { // If the pack failed to download, index correctly, // or open in the local repository we may still be // able to obtain this object from another pack or // an alternate. // recordError(id, err); e1 = err; continue; } finally { // If the pack was good its in the local repository // and Repository.getObjectDatabase().has(id) will // succeed in the future, so we do not need this // data any more. If it failed the index and pack // are unusable and we shouldn't consult them again. // try { if (pack.tmpIdx != null) { FileUtils.delete(pack.tmpIdx); } } catch (Throwable e) { if (e1 != null) { e.addSuppressed(e1); } throw new TransportException(e.getMessage(), e); } brokenPacks.add(entry.getKey()); } if (!alreadyHave(id)) { // What the hell? This pack claimed to have // the object, but after indexing we didn't // actually find it in the pack. // recordError(id, new FileNotFoundException(MessageFormat.format( JGitText.get().objectNotFoundIn, id.name(), pack.packName))); continue; } // Complete any other objects that we can. // final Deque pending = swapFetchQueue(); for (ObjectId p : pending) { if (pack.index.hasObject(p)) { process(p); } else { workQueue.add(p); } } return true; } return false; } private Deque swapFetchQueue() { final Deque r = workQueue; workQueue = new ArrayDeque<>(); return r; } private boolean downloadLooseObject(final AnyObjectId id, final String looseName, final WalkRemoteObjectDatabase remote) throws TransportException { try { final byte[] compressed = remote.open(looseName).toArray(); verifyAndInsertLooseObject(id, compressed); return true; } catch (FileNotFoundException e) { // Not available in a loose format from this alternate? // Try another strategy to get the object. // recordError(id, e); return false; } catch (IOException e) { throw new TransportException(MessageFormat.format(JGitText.get().cannotDownload, id.name()), e); } } private void verifyAndInsertLooseObject(final AnyObjectId id, final byte[] compressed) throws IOException { final ObjectLoader uol; try { uol = UnpackedObject.parse(compressed, id); } catch (CorruptObjectException parsingError) { // Some HTTP servers send back a "200 OK" status with an HTML // page that explains the requested file could not be found. // These servers are most certainly misconfigured, but many // of them exist in the world, and many of those are hosting // Git repositories. // // Since an HTML page is unlikely to hash to one of our loose // objects we treat this condition as a FileNotFoundException // and attempt to recover by getting the object from another // source. // final FileNotFoundException e; e = new FileNotFoundException(id.name()); e.initCause(parsingError); throw e; } final int type = uol.getType(); final byte[] raw = uol.getCachedBytes(); if (objCheck != null) { try { objCheck.check(id, type, raw); } catch (CorruptObjectException e) { throw new TransportException(MessageFormat.format( JGitText.get().transportExceptionInvalid, Constants.typeString(type), id.name(), e.getMessage())); } } ObjectId act = inserter.insert(type, raw); if (!AnyObjectId.isEqual(id, act)) { throw new TransportException(MessageFormat.format( JGitText.get().incorrectHashFor, id.name(), act.name(), Constants.typeString(type), Integer.valueOf(compressed.length))); } } private Collection expandOneAlternate( final AnyObjectId id, final ProgressMonitor pm) { while (!noAlternatesYet.isEmpty()) { final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst(); try { pm.beginTask(JGitText.get().listingAlternates, ProgressMonitor.UNKNOWN); Collection altList = wrr .getAlternates(); if (altList != null && !altList.isEmpty()) return altList; } catch (IOException e) { // Try another repository. // recordError(id, e); } finally { pm.endTask(); } } return null; } private void markLocalRefsComplete(Set have) throws TransportException { List refs; try { refs = local.getRefDatabase().getRefs(); } catch (IOException e) { throw new TransportException(e.getMessage(), e); } for (Ref r : refs) { try { markLocalObjComplete(revWalk.parseAny(r.getObjectId())); } catch (IOException readError) { throw new TransportException(MessageFormat.format(JGitText.get().localRefIsMissingObjects, r.getName()), readError); } } for (ObjectId id : have) { try { markLocalObjComplete(revWalk.parseAny(id)); } catch (IOException readError) { throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionMissingAssumed, id.name()), readError); } } } private void markLocalObjComplete(RevObject obj) throws IOException { while (obj.getType() == Constants.OBJ_TAG) { obj.add(COMPLETE); obj = ((RevTag) obj).getObject(); revWalk.parseHeaders(obj); } switch (obj.getType()) { case Constants.OBJ_BLOB: obj.add(COMPLETE); break; case Constants.OBJ_COMMIT: pushLocalCommit((RevCommit) obj); break; case Constants.OBJ_TREE: markTreeComplete((RevTree) obj); break; } } private void markLocalCommitsComplete(int until) throws TransportException { try { for (;;) { final RevCommit c = localCommitQueue.peek(); if (c == null || c.getCommitTime() < until) return; localCommitQueue.next(); markTreeComplete(c.getTree()); for (RevCommit p : c.getParents()) pushLocalCommit(p); } } catch (IOException err) { throw new TransportException(JGitText.get().localObjectsIncomplete, err); } } private void pushLocalCommit(RevCommit p) throws MissingObjectException, IOException { if (p.has(LOCALLY_SEEN)) return; revWalk.parseHeaders(p); p.add(LOCALLY_SEEN); p.add(COMPLETE); p.carry(COMPLETE); localCommitQueue.add(p); } private void markTreeComplete(RevTree tree) throws IOException { if (tree.has(COMPLETE)) return; tree.add(COMPLETE); treeWalk.reset(tree); while (treeWalk.next()) { final FileMode mode = treeWalk.getFileMode(0); final int sType = mode.getObjectType(); switch (sType) { case Constants.OBJ_BLOB: treeWalk.getObjectId(idBuffer, 0); revWalk.lookupAny(idBuffer, sType).add(COMPLETE); continue; case Constants.OBJ_TREE: { treeWalk.getObjectId(idBuffer, 0); final RevObject o = revWalk.lookupAny(idBuffer, sType); if (!o.has(COMPLETE)) { o.add(COMPLETE); treeWalk.enterSubtree(); } continue; } default: if (FileMode.GITLINK.equals(mode)) continue; treeWalk.getObjectId(idBuffer, 0); throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3 , mode, idBuffer.name(), treeWalk.getPathString(), tree.name())); } } } private void recordError(AnyObjectId id, Throwable what) { final ObjectId objId = id.copy(); List errors = fetchErrors.get(objId); if (errors == null) { errors = new ArrayList<>(2); fetchErrors.put(objId, errors); } errors.add(what); } private class RemotePack { final WalkRemoteObjectDatabase connection; final String packName; final String idxName; File tmpIdx; PackIndex index; RemotePack(WalkRemoteObjectDatabase c, String pn) { connection = c; packName = pn; idxName = packName.substring(0, packName.length() - 5) + ".idx"; //$NON-NLS-1$ String tn = idxName; if (tn.startsWith("pack-")) //$NON-NLS-1$ tn = tn.substring(5); if (tn.endsWith(".idx")) //$NON-NLS-1$ tn = tn.substring(0, tn.length() - 4); if (local.getObjectDatabase() instanceof ObjectDirectory) { tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase()) .getDirectory(), "walk-" + tn + ".walkidx"); //$NON-NLS-1$ //$NON-NLS-2$ } } void openIndex(ProgressMonitor pm) throws IOException { if (index != null) return; if (tmpIdx == null) tmpIdx = File.createTempFile("jgit-walk-", ".idx"); //$NON-NLS-1$ //$NON-NLS-2$ else if (tmpIdx.isFile()) { try { index = PackIndex.open(tmpIdx); return; } catch (FileNotFoundException err) { // Fall through and get the file. } } final WalkRemoteObjectDatabase.FileStream s; s = connection.open("pack/" + idxName); //$NON-NLS-1$ pm.beginTask("Get " + idxName.substring(0, 12) + "..idx", //$NON-NLS-1$ //$NON-NLS-2$ s.length < 0 ? ProgressMonitor.UNKNOWN : (int) (s.length / 1024)); try (FileOutputStream fos = new FileOutputStream(tmpIdx)) { final byte[] buf = new byte[2048]; int cnt; while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) { fos.write(buf, 0, cnt); pm.update(cnt / 1024); } } catch (IOException err) { FileUtils.delete(tmpIdx); throw err; } finally { s.in.close(); } pm.endTask(); if (pm.isCancelled()) { FileUtils.delete(tmpIdx); return; } try { index = PackIndex.open(tmpIdx); } catch (IOException e) { FileUtils.delete(tmpIdx); throw e; } } void downloadPack(ProgressMonitor monitor) throws IOException { String name = "pack/" + packName; //$NON-NLS-1$ WalkRemoteObjectDatabase.FileStream s = connection.open(name); try { PackParser parser = inserter.newPackParser(s.in); parser.setAllowThin(false); parser.setObjectChecker(objCheck); parser.setLockMessage(lockMessage); PackLock lock = parser.parse(monitor); if (lock != null) packLocks.add(lock); } finally { s.in.close(); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11899 Content-Disposition: inline; filename="WalkPushConnection.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "464017a84db03cf509dc0af717dbd1272f82b58d" /* * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.WalkRemoteObjectDatabase.ROOT_DIR; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.PackFile; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; /** * Generic push support for dumb transport protocols. *

* Since there are no Git-specific smarts on the remote side of the connection * the client side must handle everything on its own. The generic push support * requires being able to delete, create and overwrite files on the remote side, * as well as create any missing directories (if necessary). Typically this can * be handled through an FTP style protocol. *

* Objects not on the remote side are uploaded as pack files, using one pack * file per invocation. This simplifies the implementation as only two data * files need to be written to the remote repository. *

* Push support supplied by this class is not multiuser safe. Concurrent pushes * to the same repository may yield an inconsistent reference database which may * confuse fetch clients. *

* A single push is concurrently safe with multiple fetch requests, due to the * careful order of operations used to update the repository. Clients fetching * may receive transient failures due to short reads on certain files if the * protocol does not support atomic file replacement. * * @see WalkRemoteObjectDatabase */ class WalkPushConnection extends BaseConnection implements PushConnection { /** The repository this transport pushes out of. */ private final Repository local; /** Location of the remote repository we are writing to. */ private final URIish uri; /** Database connection to the remote repository. */ final WalkRemoteObjectDatabase dest; /** The configured transport we were constructed by. */ private final Transport transport; /** * Packs already known to reside in the remote repository. *

* This is a LinkedHashMap to maintain the original order. */ private LinkedHashMap packNames; /** Complete listing of refs the remote will have after our push. */ private Map newRefs; /** * Updates which require altering the packed-refs file to complete. *

* If this collection is non-empty then any refs listed in {@link #newRefs} * with a storage class of {@link Storage#PACKED} will be written. */ private Collection packedRefUpdates; WalkPushConnection(final WalkTransport walkTransport, final WalkRemoteObjectDatabase w) { transport = (Transport) walkTransport; local = transport.local; uri = transport.getURI(); dest = w; } @Override public void push(final ProgressMonitor monitor, final Map refUpdates) throws TransportException { push(monitor, refUpdates, null); } @Override public void push(final ProgressMonitor monitor, final Map refUpdates, OutputStream out) throws TransportException { markStartedOperation(); packNames = null; newRefs = new TreeMap<>(getRefsMap()); packedRefUpdates = new ArrayList<>(refUpdates.size()); // Filter the commands and issue all deletes first. This way we // can correctly handle a directory being cleared out and a new // ref using the directory name being created. // final List updates = new ArrayList<>(); for (RemoteRefUpdate u : refUpdates.values()) { final String n = u.getRemoteName(); if (!n.startsWith("refs/") || !Repository.isValidRefName(n)) { //$NON-NLS-1$ u.setStatus(Status.REJECTED_OTHER_REASON); u.setMessage(JGitText.get().funnyRefname); continue; } if (AnyObjectId.isEqual(ObjectId.zeroId(), u.getNewObjectId())) deleteCommand(u); else updates.add(u); } // If we have any updates we need to upload the objects first, to // prevent creating refs pointing at non-existent data. Then we // can update the refs, and the info-refs file for dumb transports. // if (!updates.isEmpty()) sendpack(updates, monitor); for (RemoteRefUpdate u : updates) updateCommand(u); // Is this a new repository? If so we should create additional // metadata files so it is properly initialized during the push. // if (!updates.isEmpty() && isNewRepository()) createNewRepository(updates); RefWriter refWriter = new RefWriter(newRefs.values()) { @Override protected void writeFile(String file, byte[] content) throws IOException { dest.writeFile(ROOT_DIR + file, content); } }; if (!packedRefUpdates.isEmpty()) { try { refWriter.writePackedRefs(); for (RemoteRefUpdate u : packedRefUpdates) u.setStatus(Status.OK); } catch (IOException err) { for (RemoteRefUpdate u : packedRefUpdates) { u.setStatus(Status.REJECTED_OTHER_REASON); u.setMessage(err.getMessage()); } throw new TransportException(uri, JGitText.get().failedUpdatingRefs, err); } } try { refWriter.writeInfoRefs(); } catch (IOException err) { throw new TransportException(uri, JGitText.get().failedUpdatingRefs, err); } } @Override public void close() { dest.close(); } private void sendpack(final List updates, final ProgressMonitor monitor) throws TransportException { PackFile pack = null; PackFile idx = null; try (PackWriter writer = new PackWriter(transport.getPackConfig(), local.newObjectReader())) { final Set need = new HashSet<>(); final Set have = new HashSet<>(); for (RemoteRefUpdate r : updates) need.add(r.getNewObjectId()); for (Ref r : getRefs()) { have.add(r.getObjectId()); if (r.getPeeledObjectId() != null) have.add(r.getPeeledObjectId()); } writer.preparePack(monitor, need, have); // We don't have to continue further if the pack will // be an empty pack, as the remote has all objects it // needs to complete this change. // if (writer.getObjectCount() == 0) return; packNames = new LinkedHashMap<>(); for (String n : dest.getPackNames()) packNames.put(n, n); File packDir = new File("pack"); //$NON-NLS-1$ pack = new PackFile(packDir, writer.computeName(), PackExt.PACK); idx = pack.create(PackExt.INDEX); if (packNames.remove(pack.getName()) != null) { // The remote already contains this pack. We should // remove the index before overwriting to prevent bad // offsets from appearing to clients. // dest.writeInfoPacks(packNames.keySet()); dest.deleteFile(sanitizedPath(idx)); } // Write the pack file, then the index, as readers look the // other direction (index, then pack file). // String wt = "Put " + pack.getName().substring(0, 12); //$NON-NLS-1$ try (OutputStream os = new BufferedOutputStream( dest.writeFile(sanitizedPath(pack), monitor, wt + "." + pack.getPackExt().getExtension()))) { //$NON-NLS-1$ writer.writePack(monitor, monitor, os); } try (OutputStream os = new BufferedOutputStream( dest.writeFile(sanitizedPath(idx), monitor, wt + "." + idx.getPackExt().getExtension()))) { //$NON-NLS-1$ writer.writeIndex(os); } // Record the pack at the start of the pack info list. This // way clients are likely to consult the newest pack first, // and discover the most recent objects there. // final ArrayList infoPacks = new ArrayList<>(); infoPacks.add(pack.getName()); infoPacks.addAll(packNames.keySet()); dest.writeInfoPacks(infoPacks); } catch (IOException err) { safeDelete(idx); safeDelete(pack); throw new TransportException(uri, JGitText.get().cannotStoreObjects, err); } } private void safeDelete(File path) { if (path != null) { try { dest.deleteFile(sanitizedPath(path)); } catch (IOException cleanupFailure) { // Ignore the deletion failure. We probably are // already failing and were just trying to pick // up after ourselves. } } } private void deleteCommand(RemoteRefUpdate u) { final Ref r = newRefs.remove(u.getRemoteName()); if (r == null) { // Already gone. // u.setStatus(Status.OK); return; } if (r.getStorage().isPacked()) packedRefUpdates.add(u); if (r.getStorage().isLoose()) { try { dest.deleteRef(u.getRemoteName()); u.setStatus(Status.OK); } catch (IOException e) { u.setStatus(Status.REJECTED_OTHER_REASON); u.setMessage(e.getMessage()); } } try { dest.deleteRefLog(u.getRemoteName()); } catch (IOException e) { u.setStatus(Status.REJECTED_OTHER_REASON); u.setMessage(e.getMessage()); } } private void updateCommand(RemoteRefUpdate u) { try { dest.writeRef(u.getRemoteName(), u.getNewObjectId()); newRefs.put(u.getRemoteName(), new ObjectIdRef.Unpeeled( Storage.LOOSE, u.getRemoteName(), u.getNewObjectId())); u.setStatus(Status.OK); } catch (IOException e) { u.setStatus(Status.REJECTED_OTHER_REASON); u.setMessage(e.getMessage()); } } private boolean isNewRepository() { return getRefsMap().isEmpty() && packNames != null && packNames.isEmpty(); } private void createNewRepository(List updates) throws TransportException { try { final String ref = "ref: " + pickHEAD(updates) + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ final byte[] bytes = Constants.encode(ref); dest.writeFile(ROOT_DIR + Constants.HEAD, bytes); } catch (IOException e) { throw new TransportException(uri, JGitText.get().cannotCreateHEAD, e); } try { final String config = "[core]\n" //$NON-NLS-1$ + "\trepositoryformatversion = 0\n"; //$NON-NLS-1$ final byte[] bytes = Constants.encode(config); dest.writeFile(ROOT_DIR + Constants.CONFIG, bytes); } catch (IOException e) { throw new TransportException(uri, JGitText.get().cannotCreateConfig, e); } } private static String pickHEAD(List updates) { // Try to use master if the user is pushing that, it is the // default branch and is likely what they want to remain as // the default on the new remote. // for (RemoteRefUpdate u : updates) { final String n = u.getRemoteName(); if (n.equals(Constants.R_HEADS + Constants.MASTER)) return n; } // Pick any branch, under the assumption the user pushed only // one to the remote side. // for (RemoteRefUpdate u : updates) { final String n = u.getRemoteName(); if (n.startsWith(Constants.R_HEADS)) return n; } return updates.get(0).getRemoteName(); } private static String sanitizedPath(File file) { String path = file.getPath(); if (File.separatorChar != '/') { path = path.replace(File.separatorChar, '/'); } return path; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 17853 Content-Disposition: inline; filename="WalkRemoteObjectDatabase.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "38c86a6ed0433f7d4129bcb592f5e2ebe017014d" /* * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.RefDirectory; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.util.IO; /** * Transfers object data through a dumb transport. *

* Implementations are responsible for resolving path names relative to the * objects/ subdirectory of a single remote Git repository or * naked object database and make the content available as a Java input stream * for reading during fetch. The actual object traversal logic to determine the * names of files to retrieve is handled through the generic, protocol * independent {@link WalkFetchConnection}. */ abstract class WalkRemoteObjectDatabase { static final String ROOT_DIR = "../"; //$NON-NLS-1$ static final String INFO_PACKS = "info/packs"; //$NON-NLS-1$ static final String INFO_REFS = ROOT_DIR + Constants.INFO_REFS; abstract URIish getURI(); /** * Obtain the list of available packs (if any). *

* Pack names should be the file name in the packs directory, that is * pack-035760ab452d6eebd123add421f253ce7682355a.pack. Index * names should not be included in the returned collection. * * @return list of pack names; null or empty list if none are available. * @throws IOException * The connection is unable to read the remote repository's list * of available pack files. */ abstract Collection getPackNames() throws IOException; /** * Obtain alternate connections to alternate object databases (if any). *

* Alternates are typically read from the file * {@link org.eclipse.jgit.lib.Constants#INFO_ALTERNATES} or * {@link org.eclipse.jgit.lib.Constants#INFO_HTTP_ALTERNATES}. * The content of each line must be resolved * by the implementation and a new database reference should be returned to * represent the additional location. *

* Alternates may reuse the same network connection handle, however the * fetch connection will {@link #close()} each created alternate. * * @return list of additional object databases the caller could fetch from; * null or empty list if none are configured. * @throws IOException * The connection is unable to read the remote repository's list * of configured alternates. */ abstract Collection getAlternates() throws IOException; /** * Open a single file for reading. *

* Implementors should make every attempt possible to ensure * {@link FileNotFoundException} is used when the remote object does not * exist. However when fetching over HTTP some misconfigured servers may * generate a 200 OK status message (rather than a 404 Not Found) with an * HTML formatted message explaining the requested resource does not exist. * Callers such as {@link WalkFetchConnection} are prepared to handle this * by validating the content received, and assuming content that fails to * match its hash is an incorrectly phrased FileNotFoundException. *

* This method is recommended for already compressed files like loose objects * and pack files. For text files, see {@link #openReader(String)}. * * @param path * location of the file to read, relative to this objects * directory (e.g. * cb/95df6ab7ae9e57571511ef451cf33767c26dd2 or * pack/pack-035760ab452d6eebd123add421f253ce7682355a.pack). * @return a stream to read from the file. Never null. * @throws FileNotFoundException * the requested file does not exist at the given location. * @throws IOException * The connection is unable to read the remote's file, and the * failure occurred prior to being able to determine if the file * exists, or after it was determined to exist but before the * stream could be created. */ abstract FileStream open(String path) throws FileNotFoundException, IOException; /** * Create a new connection for a discovered alternate object database *

* This method is typically called by {@link #readAlternates(String)} when * subclasses us the generic alternate parsing logic for their * implementation of {@link #getAlternates()}. * * @param location * the location of the new alternate, relative to the current * object database. * @return a new database connection that can read from the specified * alternate. * @throws IOException * The database connection cannot be established with the * alternate, such as if the alternate location does not * actually exist and the connection's constructor attempts to * verify that. */ abstract WalkRemoteObjectDatabase openAlternate(String location) throws IOException; /** * Close any resources used by this connection. *

* If the remote repository is contacted by a network socket this method * must close that network socket, disconnecting the two peers. If the * remote repository is actually local (same system) this method must close * any open file handles used to read the "remote" repository. */ abstract void close(); /** * Delete a file from the object database. *

* Path may start with ../ to request deletion of a file that * resides in the repository itself. *

* When possible empty directories must be removed, up to but not including * the current object database directory itself. *

* This method does not support deletion of directories. * * @param path * name of the item to be removed, relative to the current object * database. * @throws IOException * deletion is not supported, or deletion failed. */ void deleteFile(String path) throws IOException { throw new IOException(MessageFormat.format(JGitText.get().deletingNotSupported, path)); } /** * Open a remote file for writing. *

* Path may start with ../ to request writing of a file that * resides in the repository itself. *

* The requested path may or may not exist. If the path already exists as a * file the file should be truncated and completely replaced. *

* This method creates any missing parent directories, if necessary. * * @param path * name of the file to write, relative to the current object * database. * @return stream to write into this file. Caller must close the stream to * complete the write request. The stream is not buffered and each * write may cause a network request/response so callers should * buffer to smooth out small writes. * @param monitor * (optional) progress monitor to post write completion to during * the stream's close method. * @param monitorTask * (optional) task name to display during the close method. * @throws IOException * writing is not supported, or attempting to write the file * failed, possibly due to permissions or remote disk full, etc. */ OutputStream writeFile(final String path, final ProgressMonitor monitor, final String monitorTask) throws IOException { throw new IOException(MessageFormat.format(JGitText.get().writingNotSupported, path)); } /** * Atomically write a remote file. *

* This method attempts to perform as atomic of an update as it can, * reducing (or eliminating) the time that clients might be able to see * partial file content. This method is not suitable for very large * transfers as the complete content must be passed as an argument. *

* Path may start with ../ to request writing of a file that * resides in the repository itself. *

* The requested path may or may not exist. If the path already exists as a * file the file should be truncated and completely replaced. *

* This method creates any missing parent directories, if necessary. * * @param path * name of the file to write, relative to the current object * database. * @param data * complete new content of the file. * @throws IOException * writing is not supported, or attempting to write the file * failed, possibly due to permissions or remote disk full, etc. */ void writeFile(String path, byte[] data) throws IOException { try (OutputStream os = writeFile(path, null, null)) { os.write(data); } } /** * Delete a loose ref from the remote repository. * * @param name * name of the ref within the ref space, for example * refs/heads/pu. * @throws IOException * deletion is not supported, or deletion failed. */ void deleteRef(String name) throws IOException { deleteFile(ROOT_DIR + name); } /** * Delete a reflog from the remote repository. * * @param name * name of the ref within the ref space, for example * refs/heads/pu. * @throws IOException * deletion is not supported, or deletion failed. */ void deleteRefLog(String name) throws IOException { deleteFile(ROOT_DIR + Constants.LOGS + "/" + name); //$NON-NLS-1$ } /** * Overwrite (or create) a loose ref in the remote repository. *

* This method creates any missing parent directories, if necessary. * * @param name * name of the ref within the ref space, for example * refs/heads/pu. * @param value * new value to store in this ref. Must not be null. * @throws IOException * writing is not supported, or attempting to write the file * failed, possibly due to permissions or remote disk full, etc. */ void writeRef(String name, ObjectId value) throws IOException { final ByteArrayOutputStream b; b = new ByteArrayOutputStream(Constants.OBJECT_ID_STRING_LENGTH + 1); value.copyTo(b); b.write('\n'); writeFile(ROOT_DIR + name, b.toByteArray()); } /** * Rebuild the {@link #INFO_PACKS} for dumb transport clients. *

* This method rebuilds the contents of the {@link #INFO_PACKS} file to * match the passed list of pack names. * * @param packNames * names of available pack files, in the order they should appear * in the file. Valid pack name strings are of the form * pack-035760ab452d6eebd123add421f253ce7682355a.pack. * @throws IOException * writing is not supported, or attempting to write the file * failed, possibly due to permissions or remote disk full, etc. */ void writeInfoPacks(Collection packNames) throws IOException { final StringBuilder w = new StringBuilder(); for (String n : packNames) { w.append("P "); //$NON-NLS-1$ w.append(n); w.append('\n'); } writeFile(INFO_PACKS, Constants.encodeASCII(w.toString())); } /** * Open a buffered reader around a file. *

* This method is suitable for reading line-oriented resources like * info/packs, info/refs, and the alternates list. * * @return a stream to read from the file. Never null. * @param path * location of the file to read, relative to this objects * directory (e.g. info/packs). * @throws FileNotFoundException * the requested file does not exist at the given location. * @throws IOException * The connection is unable to read the remote's file, and the * failure occurred prior to being able to determine if the file * exists, or after it was determined to exist but before the * stream could be created. */ BufferedReader openReader(String path) throws IOException { final InputStream is = open(path).in; return new BufferedReader(new InputStreamReader(is, UTF_8)); } /** * Read a standard Git alternates file to discover other object databases. *

* This method is suitable for reading the standard formats of the * alternates file, such as found in objects/info/alternates * or objects/info/http-alternates within a Git repository. *

* Alternates appear one per line, with paths expressed relative to this * object database. * * @param listPath * location of the alternate file to read, relative to this * object database (e.g. info/alternates). * @return the list of discovered alternates. Empty list if the file exists, * but no entries were discovered. * @throws FileNotFoundException * the requested file does not exist at the given location. * @throws IOException * The connection is unable to read the remote's file, and the * failure occurred prior to being able to determine if the file * exists, or after it was determined to exist but before the * stream could be created. */ Collection readAlternates(final String listPath) throws IOException { try (BufferedReader br = openReader(listPath)) { final Collection alts = new ArrayList<>(); for (;;) { String line = br.readLine(); if (line == null) break; if (!line.endsWith("/")) //$NON-NLS-1$ line += "/"; //$NON-NLS-1$ alts.add(openAlternate(line)); } return alts; } } /** * Read a standard Git packed-refs file to discover known references. * * @param avail * return collection of references. Any existing entries will be * replaced if they are found in the packed-refs file. * @throws org.eclipse.jgit.errors.TransportException * an error occurred reading from the packed refs file. */ protected void readPackedRefs(Map avail) throws TransportException { try (BufferedReader br = openReader(ROOT_DIR + Constants.PACKED_REFS)) { readPackedRefsImpl(avail, br); } catch (FileNotFoundException notPacked) { // Perhaps it wasn't worthwhile, or is just an older repository. } catch (IOException e) { throw new TransportException(getURI(), JGitText.get().errorInPackedRefs, e); } } private void readPackedRefsImpl(final Map avail, final BufferedReader br) throws IOException { Ref last = null; boolean peeled = false; for (;;) { String line = br.readLine(); if (line == null) break; if (line.charAt(0) == '#') { if (line.startsWith(RefDirectory.PACKED_REFS_HEADER)) { line = line.substring(RefDirectory.PACKED_REFS_HEADER.length()); peeled = line.contains(RefDirectory.PACKED_REFS_PEELED); } continue; } if (line.charAt(0) == '^') { if (last == null) throw new TransportException(JGitText.get().peeledLineBeforeRef); final ObjectId id = ObjectId.fromString(line.substring(1)); last = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, last .getName(), last.getObjectId(), id); avail.put(last.getName(), last); continue; } final int sp = line.indexOf(' '); if (sp < 0) throw new TransportException(MessageFormat.format(JGitText.get().unrecognizedRef, line)); final ObjectId id = ObjectId.fromString(line.substring(0, sp)); final String name = line.substring(sp + 1); if (peeled) last = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED, name, id); else last = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, name, id); avail.put(last.getName(), last); } } static final class FileStream { final InputStream in; final long length; /** * Create a new stream of unknown length. * * @param i * stream containing the file data. This stream will be * closed by the caller when reading is complete. */ FileStream(InputStream i) { in = i; length = -1; } /** * Create a new stream of known length. * * @param i * stream containing the file data. This stream will be * closed by the caller when reading is complete. * @param n * total number of bytes available for reading through * i. */ FileStream(InputStream i, long n) { in = i; length = n; } byte[] toArray() throws IOException { try { if (length >= 0) { final byte[] r = new byte[(int) length]; IO.readFully(in, r, 0, r.length); return r; } final ByteArrayOutputStream r = new ByteArrayOutputStream(); final byte[] buf = new byte[2048]; int n; while ((n = in.read(buf)) >= 0) r.write(buf, 0, n); return r.toByteArray(); } finally { in.close(); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1109 Content-Disposition: inline; filename="WalkTransport.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "d02fe7e7dd3dd81d47c3e55c5dddd571d5de7899" /* * Copyright (C) 2009, Constantine Plotnikov * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2008, Mike Ralphson * Copyright (C) 2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; /** * Marker interface for an object transport walking transport. *

* Implementations of WalkTransport transfer individual objects one at a time * from the loose objects directory, or entire packs if the source side does not * have the object as a loose object. *

* WalkTransports are not as efficient as * {@link org.eclipse.jgit.transport.PackTransport} instances, but can be useful * in situations where a pack transport is not acceptable. * * @see WalkFetchConnection */ public interface WalkTransport { // no methods in marker interface } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1662 Content-Disposition: inline; filename="WantNotValidException.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "0d3c89b27c6fe570182852d2c6126a5364f7b4ed" /* * Copyright (C) 2016, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.text.MessageFormat; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; /** * Indicates client requested an object the server does not want to serve. *

* Typically visible only inside of the server implementation; clients are * usually looking at the text message from the server in a generic * {@link org.eclipse.jgit.errors.PackProtocolException}. * * @since 4.3 */ public class WantNotValidException extends PackProtocolException { private static final long serialVersionUID = 1L; /** * Construct a {@code "want $id not valid"} exception. * * @param id * invalid object identifier received from the client. */ public WantNotValidException(AnyObjectId id) { super(msg(id)); } /** * Construct a {@code "want $id not valid"} exception. * * @param id * invalid object identifier received from the client. * @param cause * root cause of the object being invalid, such as an IOException * from the storage system. */ public WantNotValidException(AnyObjectId id, Throwable cause) { super(msg(id), cause); } private static String msg(AnyObjectId id) { return MessageFormat.format(JGitText.get().wantNotValid, id.name()); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1214 Content-Disposition: inline; filename="WriteAbortedException.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "a6256c50344bc4521d2ce36c57c38c2176d5a7bf" /* * Copyright (C) 2015, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.transport; import java.io.IOException; /** * An exception to be thrown when the write operation is aborted. *

* That can be thrown inside * {@link org.eclipse.jgit.transport.ObjectCountCallback#setObjectCount(long)}. * * @since 4.1 */ public class WriteAbortedException extends IOException { private static final long serialVersionUID = 1L; /** * Construct a {@code WriteAbortedException}. */ public WriteAbortedException() { } /** * Construct a {@code WriteAbortedException}. * * @param s message describing the issue */ public WriteAbortedException(String s) { super(s); } /** * Construct a {@code WriteAbortedException}. * * @param s * message describing the issue * @param why * a lower level implementation specific issue. */ public WriteAbortedException(String s, Throwable why) { super(s, why); } } Content-Type: text/plain; charset=UTF-8 Content-Length: 1214 Content-Disposition: inline; filename="WriteAbortedException.java" Last-Modified: Sun, 06 Jul 2025 08:28:22 GMT Expires: Sun, 06 Jul 2025 08:33:22 GMT ETag: "a9862a344d99417fa265bd66fe2e58059176047b" /org.eclipse.jgit/src/org/eclipse/jgit/transport/http/

/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/