// // NOTE: The following source code is heavily derived from the // iHarder.net public domain Base64 library. See the original at // http://iharder.sourceforge.net/current/java/base64/ // package org.eclipse.jgit.util; import static java.nio.charset.StandardCharsets.UTF_8; import java.text.MessageFormat; import java.util.Arrays; import org.eclipse.jgit.internal.JGitText; /** * Encodes and decodes to and from Base64 notation. *

* I am placing this code in the Public Domain. Do with it as you will. This * software comes with no guarantees or warranties but with plenty of * well-wishing instead! Please visit * http://iharder.net/base64 * periodically to check for updates or to contribute improvements. *

* * @author Robert Harder * @author rob@iharder.net */ public class Base64 { /** The equals sign (=) as a byte. */ private static final byte EQUALS_SIGN = (byte) '='; /** Indicates equals sign in encoding. */ private static final byte EQUALS_SIGN_DEC = -1; /** Indicates white space in encoding. */ private static final byte WHITE_SPACE_DEC = -2; /** Indicates an invalid byte during decoding. */ private static final byte INVALID_DEC = -3; /** The 64 valid Base64 values. */ private static final byte[] ENC; /** * Translates a Base64 value to either its 6-bit reconstruction value or a * negative number indicating some other meaning. The table is only 7 bits * wide, as the 8th bit is discarded during decoding. */ private static final byte[] DEC; static { ENC = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" // //$NON-NLS-1$ + "abcdefghijklmnopqrstuvwxyz" // //$NON-NLS-1$ + "0123456789" // //$NON-NLS-1$ + "+/" // //$NON-NLS-1$ ).getBytes(UTF_8); DEC = new byte[128]; Arrays.fill(DEC, INVALID_DEC); for (int i = 0; i < 64; i++) DEC[ENC[i]] = (byte) i; DEC[EQUALS_SIGN] = EQUALS_SIGN_DEC; DEC['\t'] = WHITE_SPACE_DEC; DEC['\n'] = WHITE_SPACE_DEC; DEC['\r'] = WHITE_SPACE_DEC; DEC[' '] = WHITE_SPACE_DEC; } /** Defeats instantiation. */ private Base64() { // Suppress empty block warning. } /** * Encodes up to three bytes of the array source and writes the * resulting four Base64 bytes to destination. The source and * destination arrays can be manipulated anywhere along their length by * specifying srcOffset and destOffset. This method * does not check to make sure your arrays are large enough to accommodate * srcOffset + 3 for the source array or * destOffset + 4 for the destination array. The * actual number of significant bytes in your array is given by * numSigBytes. * * @param source * the array to convert * @param srcOffset * the index where conversion begins * @param numSigBytes * the number of significant bytes in your array * @param destination * the array to hold the conversion * @param destOffset * the index where output will be put */ @SuppressWarnings("UnnecessaryParentheses") private static void encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset) { // We have to shift left 24 in order to flush out the 1's that appear // when Java treats a value as negative that is cast from a byte. int inBuff = 0; switch (numSigBytes) { case 3: inBuff |= (source[srcOffset + 2] << 24) >>> 24; //$FALL-THROUGH$ case 2: inBuff |= (source[srcOffset + 1] << 24) >>> 16; //$FALL-THROUGH$ case 1: inBuff |= (source[srcOffset] << 24) >>> 8; } switch (numSigBytes) { case 3: destination[destOffset] = ENC[(inBuff >>> 18)]; destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = ENC[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = ENC[(inBuff) & 0x3f]; break; case 2: destination[destOffset] = ENC[(inBuff >>> 18)]; destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = ENC[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = EQUALS_SIGN; break; case 1: destination[destOffset] = ENC[(inBuff >>> 18)]; destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = EQUALS_SIGN; destination[destOffset + 3] = EQUALS_SIGN; break; } } /** * Encodes a byte array into Base64 notation. * * @param source * The data to convert * @return encoded base64 representation of source. */ public static String encodeBytes(byte[] source) { return encodeBytes(source, 0, source.length); } /** * Encodes a byte array into Base64 notation. * * @param source * The data to convert * @param off * Offset in array where conversion should begin * @param len * Length of data to convert * @return encoded base64 representation of source. */ public static String encodeBytes(byte[] source, int off, int len) { final int len43 = len * 4 / 3; byte[] outBuff = new byte[len43 + ((len % 3) > 0 ? 4 : 0)]; int d = 0; int e = 0; int len2 = len - 2; for (; d < len2; d += 3, e += 4) encode3to4(source, d + off, 3, outBuff, e); if (d < len) { encode3to4(source, d + off, len - d, outBuff, e); e += 4; } return new String(outBuff, 0, e, UTF_8); } /** * Decodes four bytes from array source and writes the resulting * bytes (up to three of them) to destination. The source and * destination arrays can be manipulated anywhere along their length by * specifying srcOffset and destOffset. This method * does not check to make sure your arrays are large enough to accommodate * srcOffset + 4 for the source array or * destOffset + 3 for the destination array. This * method returns the actual number of bytes that were converted from the * Base64 encoding. * * @param source * the array to convert * @param srcOffset * the index where conversion begins * @param destination * the array to hold the conversion * @param destOffset * the index where output will be put * @return the number of decoded bytes converted */ @SuppressWarnings("UnnecessaryParentheses") private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { // Example: Dk== if (source[srcOffset + 2] == EQUALS_SIGN) { int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) | ((DEC[source[srcOffset + 1]] & 0xFF) << 12); destination[destOffset] = (byte) (outBuff >>> 16); return 1; } // Example: DkL= else if (source[srcOffset + 3] == EQUALS_SIGN) { int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) | ((DEC[source[srcOffset + 1]] & 0xFF) << 12) | ((DEC[source[srcOffset + 2]] & 0xFF) << 6); destination[destOffset] = (byte) (outBuff >>> 16); destination[destOffset + 1] = (byte) (outBuff >>> 8); return 2; } // Example: DkLE else { int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) | ((DEC[source[srcOffset + 1]] & 0xFF) << 12) | ((DEC[source[srcOffset + 2]] & 0xFF) << 6) | ((DEC[source[srcOffset + 3]] & 0xFF)); destination[destOffset] = (byte) (outBuff >> 16); destination[destOffset + 1] = (byte) (outBuff >> 8); destination[destOffset + 2] = (byte) (outBuff); return 3; } } /** * Low-level decoding ASCII characters from a byte array. * * @param source * The Base64 encoded data * @param off * The offset of where to begin decoding * @param len * The length of characters to decode * @return decoded data * @throws java.lang.IllegalArgumentException * the input is not a valid Base64 sequence. */ public static byte[] decode(byte[] source, int off, int len) { byte[] outBuff = new byte[len * 3 / 4]; // Upper limit on size of output int outBuffPosn = 0; byte[] b4 = new byte[4]; int b4Posn = 0; for (int i = off; i < off + len; i++) { byte sbiCrop = (byte) (source[i] & 0x7f); byte sbiDecode = DEC[sbiCrop]; if (EQUALS_SIGN_DEC <= sbiDecode) { b4[b4Posn++] = sbiCrop; if (b4Posn > 3) { outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); b4Posn = 0; // If that was the equals sign, break out of 'for' loop if (sbiCrop == EQUALS_SIGN) break; } } else if (sbiDecode != WHITE_SPACE_DEC) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().badBase64InputCharacterAt, Integer.valueOf(i), Integer.valueOf(source[i] & 0xff))); } if (outBuff.length == outBuffPosn) return outBuff; byte[] out = new byte[outBuffPosn]; System.arraycopy(outBuff, 0, out, 0, outBuffPosn); return out; } /** * Decodes data from Base64 notation. * * @param s * the string to decode * @return the decoded data */ public static byte[] decode(String s) { byte[] bytes = s.getBytes(UTF_8); return decode(bytes, 0, bytes.length); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5579 Content-Disposition: inline; filename="Base85.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "54b7cfcaa71d1b4580a1c75ad1462b81ae0e30cd" /* * 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.util; import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.Arrays; import org.eclipse.jgit.internal.JGitText; /** * Base-85 encoder/decoder. * * @since 5.12 */ public final class Base85 { private static final byte[] ENCODE = ("0123456789" //$NON-NLS-1$ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" //$NON-NLS-1$ + "abcdefghijklmnopqrstuvwxyz" //$NON-NLS-1$ + "!#$%&()*+-;<=>?@^_`{|}~") //$NON-NLS-1$ .getBytes(StandardCharsets.US_ASCII); private static final int[] DECODE = new int[256]; static { Arrays.fill(DECODE, -1); for (int i = 0; i < ENCODE.length; i++) { DECODE[ENCODE[i]] = i; } } private Base85() { // No instantiation } /** * Determines the length of the base-85 encoding for {@code rawLength} * bytes. * * @param rawLength * number of bytes to encode * @return number of bytes needed for the base-85 encoding of * {@code rawLength} bytes */ public static int encodedLength(int rawLength) { return (rawLength + 3) / 4 * 5; } /** * Encodes the given {@code data} in Base-85. * * @param data * to encode * @return encoded data */ public static byte[] encode(byte[] data) { return encode(data, 0, data.length); } /** * Encodes {@code length} bytes of {@code data} in Base-85, beginning at the * {@code start} index. * * @param data * to encode * @param start * index of the first byte to encode * @param length * number of bytes to encode * @return encoded data */ public static byte[] encode(byte[] data, int start, int length) { byte[] result = new byte[encodedLength(length)]; int end = start + length; int in = start; int out = 0; while (in < end) { // Accumulate remaining bytes MSB first as a 32bit value long accumulator = ((long) (data[in++] & 0xFF)) << 24; if (in < end) { accumulator |= (data[in++] & 0xFF) << 16; if (in < end) { accumulator |= (data[in++] & 0xFF) << 8; if (in < end) { accumulator |= (data[in++] & 0xFF); } } } // Write the 32bit value in base-85 encoding, also MSB first for (int i = 4; i >= 0; i--) { result[out + i] = ENCODE[(int) (accumulator % 85)]; accumulator /= 85; } out += 5; } return result; } /** * Decodes the Base-85 {@code encoded} data into a byte array of * {@code expectedSize} bytes. * * @param encoded * Base-85 encoded data * @param expectedSize * of the result * @return the decoded bytes * @throws IllegalArgumentException * if expectedSize doesn't match, the encoded data has a length * that is not a multiple of 5, or there are invalid characters * in the encoded data */ public static byte[] decode(byte[] encoded, int expectedSize) { return decode(encoded, 0, encoded.length, expectedSize); } /** * Decodes {@code length} bytes of Base-85 {@code encoded} data, beginning * at the {@code start} index, into a byte array of {@code expectedSize} * bytes. * * @param encoded * Base-85 encoded data * @param start * index at which the data to decode starts in {@code encoded} * @param length * of the Base-85 encoded data * @param expectedSize * of the result * @return the decoded bytes * @throws IllegalArgumentException * if expectedSize doesn't match, {@code length} is not a * multiple of 5, or there are invalid characters in the encoded * data */ public static byte[] decode(byte[] encoded, int start, int length, int expectedSize) { if (length % 5 != 0) { throw new IllegalArgumentException(JGitText.get().base85length); } byte[] result = new byte[expectedSize]; int end = start + length; int in = start; int out = 0; while (in < end && out < expectedSize) { // Accumulate 5 bytes, "MSB" first long accumulator = 0; for (int i = 4; i >= 0; i--) { int val = DECODE[encoded[in++] & 0xFF]; if (val < 0) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().base85invalidChar, Integer.toHexString(encoded[in - 1] & 0xFF))); } accumulator = accumulator * 85 + val; } if (accumulator > 0xFFFF_FFFFL) { throw new IllegalArgumentException( MessageFormat.format(JGitText.get().base85overflow, Long.toHexString(accumulator))); } // Write remaining bytes, MSB first result[out++] = (byte) (accumulator >>> 24); if (out < expectedSize) { result[out++] = (byte) (accumulator >>> 16); if (out < expectedSize) { result[out++] = (byte) (accumulator >>> 8); if (out < expectedSize) { result[out++] = (byte) accumulator; } } } } // Should have exhausted 'in' and filled 'out' completely if (in < end) { throw new IllegalArgumentException( MessageFormat.format(JGitText.get().base85tooLong, Integer.valueOf(expectedSize))); } if (out < expectedSize) { throw new IllegalArgumentException( MessageFormat.format(JGitText.get().base85tooShort, Integer.valueOf(expectedSize))); } return result; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7583 Content-Disposition: inline; filename="BlockList.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "557e2cde3bf9de09746d09a4b59a278987d9a8e5" /* * 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.util; import java.util.AbstractList; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; /** * Random access list that allocates entries in blocks. *

* Unlike {@link java.util.ArrayList}, this type does not need to reallocate the * internal array in order to expand the capacity of the list. Access to any * element is constant time, but requires two array lookups instead of one. *

* To handle common usages, {@link #add(Object)} and {@link #iterator()} use * internal code paths to amortize out the second array lookup, making addition * and simple iteration closer to one array operation per element processed. *

* Similar to {@code ArrayList}, adding or removing from any position except the * end of the list requires O(N) time to copy all elements between the * modification point and the end of the list. Applications are strongly * encouraged to not use this access pattern with this list implementation. * * @param * type of list element. */ public class BlockList extends AbstractList { private static final int BLOCK_BITS = 10; static final int BLOCK_SIZE = 1 << BLOCK_BITS; private static final int BLOCK_MASK = BLOCK_SIZE - 1; T[][] directory; int size; private int tailDirIdx; private int tailBlkIdx; private T[] tailBlock; /** * Initialize an empty list. */ public BlockList() { directory = BlockList. newDirectory(256); directory[0] = BlockList. newBlock(); tailBlock = directory[0]; } /** * Initialize an empty list with an expected capacity. * * @param capacity * number of elements expected to be in the list. */ public BlockList(int capacity) { int dirSize = toDirectoryIndex(capacity); if ((capacity & BLOCK_MASK) != 0 || dirSize == 0) dirSize++; directory = BlockList. newDirectory(dirSize); directory[0] = BlockList. newBlock(); tailBlock = directory[0]; } @Override public int size() { return size; } @Override public void clear() { for (T[] block : directory) { if (block != null) Arrays.fill(block, null); } size = 0; tailDirIdx = 0; tailBlkIdx = 0; tailBlock = directory[0]; } @Override public T get(int index) { if (index < 0 || size <= index) throw new IndexOutOfBoundsException(String.valueOf(index)); return directory[toDirectoryIndex(index)][toBlockIndex(index)]; } @Override public T set(int index, T element) { if (index < 0 || size <= index) throw new IndexOutOfBoundsException(String.valueOf(index)); T[] blockRef = directory[toDirectoryIndex(index)]; int blockIdx = toBlockIndex(index); T old = blockRef[blockIdx]; blockRef[blockIdx] = element; return old; } /** * Quickly append all elements of another BlockList. * * @param src * the list to copy elements from. */ public void addAll(BlockList src) { if (src.size == 0) return; int srcDirIdx = 0; for (; srcDirIdx < src.tailDirIdx; srcDirIdx++) addAll(src.directory[srcDirIdx], 0, BLOCK_SIZE); if (src.tailBlkIdx != 0) addAll(src.tailBlock, 0, src.tailBlkIdx); } /** * Quickly append all elements from an array. * * @param src * the source array. * @param srcIdx * first index to copy. * @param srcCnt * number of elements to copy. */ public void addAll(T[] src, int srcIdx, int srcCnt) { while (0 < srcCnt) { int i = tailBlkIdx; int n = Math.min(srcCnt, BLOCK_SIZE - i); if (n == 0) { // Our tail is full, expand by one. add(src[srcIdx++]); srcCnt--; continue; } System.arraycopy(src, srcIdx, tailBlock, i, n); tailBlkIdx += n; size += n; srcIdx += n; srcCnt -= n; } } @Override public boolean add(T element) { int i = tailBlkIdx; if (i < BLOCK_SIZE) { // Fast-path: Append to current tail block. tailBlock[i] = element; tailBlkIdx = i + 1; size++; return true; } // Slow path: Move to the next block, expanding if necessary. if (++tailDirIdx == directory.length) { T[][] newDir = BlockList. newDirectory(directory.length << 1); System.arraycopy(directory, 0, newDir, 0, directory.length); directory = newDir; } T[] blockRef = directory[tailDirIdx]; if (blockRef == null) { blockRef = BlockList. newBlock(); directory[tailDirIdx] = blockRef; } blockRef[0] = element; tailBlock = blockRef; tailBlkIdx = 1; size++; return true; } @Override public void add(int index, T element) { if (index == size) { // Fast-path: append onto the end of the list. add(element); } else if (index < 0 || size < index) { throw new IndexOutOfBoundsException(String.valueOf(index)); } else { // Slow-path: the list needs to expand and insert. // Do this the naive way, callers shouldn't abuse // this class by entering this code path. // add(null); // expand the list by one for (int oldIdx = size - 2; index <= oldIdx; oldIdx--) set(oldIdx + 1, get(oldIdx)); set(index, element); } } @Override public T remove(int index) { if (index == size - 1) { // Fast-path: remove the last element. T[] blockRef = directory[toDirectoryIndex(index)]; int blockIdx = toBlockIndex(index); T old = blockRef[blockIdx]; blockRef[blockIdx] = null; size--; if (0 < tailBlkIdx) tailBlkIdx--; else resetTailBlock(); return old; } else if (index < 0 || size <= index) { throw new IndexOutOfBoundsException(String.valueOf(index)); } else { // Slow-path: the list needs to contract and remove. // Do this the naive way, callers shouldn't abuse // this class by entering this code path. // T old = get(index); for (; index < size - 1; index++) set(index, get(index + 1)); set(size - 1, null); size--; resetTailBlock(); return old; } } private void resetTailBlock() { tailDirIdx = toDirectoryIndex(size); tailBlkIdx = toBlockIndex(size); tailBlock = directory[tailDirIdx]; } @Override public Iterator iterator() { return new MyIterator(); } static final int toDirectoryIndex(int index) { return index >>> BLOCK_BITS; } static final int toBlockIndex(int index) { return index & BLOCK_MASK; } @SuppressWarnings("unchecked") private static T[][] newDirectory(int size) { return (T[][]) new Object[size][]; } @SuppressWarnings("unchecked") private static T[] newBlock() { return (T[]) new Object[BLOCK_SIZE]; } private class MyIterator implements Iterator { private int index; private int dirIdx; private int blkIdx; private T[] block = directory[0]; @Override public boolean hasNext() { return index < size; } @Override public T next() { if (size <= index) throw new NoSuchElementException(); T res = block[blkIdx]; if (++blkIdx == BLOCK_SIZE) { if (++dirIdx < directory.length) block = directory[dirIdx]; else block = null; blkIdx = 0; } index++; return res; } @Override public void remove() { if (index == 0) throw new IllegalStateException(); BlockList.this.remove(--index); dirIdx = toDirectoryIndex(index); blkIdx = toBlockIndex(index); block = directory[dirIdx]; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2617 Content-Disposition: inline; filename="CachedAuthenticator.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "d8183eb8beb67a7a9429a205f5de45e2961613a3" /* * Copyright (C) 2009, Google Inc. * 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.util; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; /** * Abstract authenticator which remembers prior authentications. */ public abstract class CachedAuthenticator extends Authenticator { private static final Collection cached = new CopyOnWriteArrayList<>(); /** * Add a cached authentication for future use. * * @param ca * the information we should remember. */ public static void add(CachedAuthentication ca) { cached.add(ca); } @Override protected final PasswordAuthentication getPasswordAuthentication() { final String host = getRequestingHost(); final int port = getRequestingPort(); for (CachedAuthentication ca : cached) { if (ca.host.equals(host) && ca.port == port) return ca.toPasswordAuthentication(); } PasswordAuthentication pa = promptPasswordAuthentication(); if (pa != null) { CachedAuthentication ca = new CachedAuthentication(host, port, pa .getUserName(), new String(pa.getPassword())); add(ca); return ca.toPasswordAuthentication(); } return null; } /** * Prompt for and request authentication from the end-user. * * @return the authentication data; null if the user canceled the request * and does not want to continue. */ protected abstract PasswordAuthentication promptPasswordAuthentication(); /** Authentication data to remember and reuse. */ public static class CachedAuthentication { final String host; final int port; final String user; final String pass; /** * Create a new cached authentication. * * @param aHost * system this is for. * @param aPort * port number of the service. * @param aUser * username at the service. * @param aPass * password at the service. */ public CachedAuthentication(final String aHost, final int aPort, final String aUser, final String aPass) { host = aHost; port = aPort; user = aUser; pass = aPass; } PasswordAuthentication toPasswordAuthentication() { return new PasswordAuthentication(user, pass.toCharArray()); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8641 Content-Disposition: inline; filename="ChangeIdUtil.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "c8421d6012f1e5014fa2fe67de1872feb56b6d8f" /* * Copyright (C) 2010, 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.util; import java.util.regex.Pattern; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; /** * Utilities for creating and working with Change-Id's, like the one used by * Gerrit Code Review. *

* A Change-Id is a SHA-1 computed from the content of a commit, in a similar * fashion to how the commit id is computed. Unlike the commit id a Change-Id is * retained in the commit and subsequent revised commits in the footer of the * commit text. */ public class ChangeIdUtil { static final String CHANGE_ID = "Change-Id:"; //$NON-NLS-1$ // package-private so the unit test can test this part only @SuppressWarnings("nls") static String clean(String msg) { return msg.// replaceAll("(?i)(?m)^Signed-off-by:.*$\n?", "").// //$NON-NLS-1$ replaceAll("(?m)^#.*$\n?", "").// //$NON-NLS-1$ replaceAll("(?m)\n\n\n+", "\\\n").// //$NON-NLS-1$ replaceAll("\\n*$", "").// //$NON-NLS-1$ replaceAll("(?s)\ndiff --git.*", "").// //$NON-NLS-1$ trim(); } /** * Compute a Change-Id. * * @param treeId * The id of the tree that would be committed * @param firstParentId * parent id of previous commit or null * @param author * the {@link org.eclipse.jgit.lib.PersonIdent} for the presumed * author and time * @param committer * the {@link org.eclipse.jgit.lib.PersonIdent} for the presumed * committer and time * @param message * The commit message * @return the change id SHA1 string (without the 'I') or null if the * message is not complete enough */ public static ObjectId computeChangeId(final ObjectId treeId, final ObjectId firstParentId, final PersonIdent author, final PersonIdent committer, final String message) { String cleanMessage = clean(message); if (cleanMessage.length() == 0) return null; StringBuilder b = new StringBuilder(); b.append("tree "); //$NON-NLS-1$ b.append(ObjectId.toString(treeId)); b.append("\n"); //$NON-NLS-1$ if (firstParentId != null) { b.append("parent "); //$NON-NLS-1$ b.append(ObjectId.toString(firstParentId)); b.append("\n"); //$NON-NLS-1$ } b.append("author "); //$NON-NLS-1$ b.append(author.toExternalString()); b.append("\n"); //$NON-NLS-1$ b.append("committer "); //$NON-NLS-1$ b.append(committer.toExternalString()); b.append("\n\n"); //$NON-NLS-1$ b.append(cleanMessage); try (ObjectInserter f = new ObjectInserter.Formatter()) { return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString())); } } private static final Pattern signedOffByPattern = Pattern .compile("^Signed-off-by:.*$"); //$NON-NLS-1$ private static final Pattern footerPattern = Pattern .compile("(^[a-zA-Z0-9-]+:(?!//).*$)"); //$NON-NLS-1$ private static final Pattern changeIdPattern = Pattern .compile("(^" + CHANGE_ID + " *I[a-f0-9]{40}$)"); //$NON-NLS-1$ //$NON-NLS-2$ private static final Pattern includeInFooterPattern = Pattern .compile("^[ \\[].*$"); //$NON-NLS-1$ private static final Pattern trailingWhitespace = Pattern.compile("\\s+$"); //$NON-NLS-1$ /** * Find the right place to insert a Change-Id and return it. *

* The Change-Id is inserted before the first footer line but after a Bug * line. * * @param message * a message. * @param changeId * a Change-Id. * @return a commit message with an inserted Change-Id line */ public static String insertId(String message, ObjectId changeId) { return insertId(message, changeId, false); } /** * Find the right place to insert a Change-Id and return it. *

* If no Change-Id is found the Change-Id is inserted before the first * footer line but after a Bug line. * * If Change-Id is found and replaceExisting is set to false, the message is * unchanged. * * If Change-Id is found and replaceExisting is set to true, the Change-Id * is replaced with {@code changeId}. * * @param message * a message. * @param changeId * a Change-Id. * @param replaceExisting * a boolean. * @return a commit message with an inserted Change-Id line */ public static String insertId(String message, ObjectId changeId, boolean replaceExisting) { int indexOfChangeId = indexOfChangeId(message, "\n"); //$NON-NLS-1$ if (indexOfChangeId > 0) { if (!replaceExisting) { return message; } StringBuilder ret = new StringBuilder( message.substring(0, indexOfChangeId)); ret.append(CHANGE_ID); ret.append(" I"); //$NON-NLS-1$ ret.append(ObjectId.toString(changeId)); int indexOfNextLineBreak = message.indexOf('\n', indexOfChangeId); if (indexOfNextLineBreak > 0) ret.append(message.substring(indexOfNextLineBreak)); return ret.toString(); } String[] lines = message.split("\n"); //$NON-NLS-1$ int footerFirstLine = indexOfFirstFooterLine(lines); int insertAfter = footerFirstLine; for (int i = footerFirstLine; i < lines.length; ++i) { if (!signedOffByPattern.matcher(lines[i]).matches()) { insertAfter = i + 1; continue; } break; } StringBuilder ret = new StringBuilder(); int i = 0; for (; i < insertAfter; ++i) { ret.append(lines[i]); ret.append("\n"); //$NON-NLS-1$ } if (insertAfter == lines.length && insertAfter == footerFirstLine) ret.append("\n"); //$NON-NLS-1$ ret.append(CHANGE_ID); ret.append(" I"); //$NON-NLS-1$ ret.append(ObjectId.toString(changeId)); ret.append("\n"); //$NON-NLS-1$ for (; i < lines.length; ++i) { ret.append(lines[i]); ret.append("\n"); //$NON-NLS-1$ } return ret.toString(); } /** * Return the index in the String {@code message} where the Change-Id entry * in the footer begins. If there are more than one entries matching the * pattern, return the index of the last one in the last section. Because of * Bug: 400818 we release the constraint here that a footer must contain * only lines matching {@code footerPattern}. * * @param message * a message. * @param delimiter * the line delimiter, like "\n" or "\r\n", needed to find the * footer * @return the index of the ChangeId footer in the message, or -1 if no * ChangeId footer available */ public static int indexOfChangeId(String message, String delimiter) { String[] lines = message.split(delimiter); if (lines.length == 0) return -1; int indexOfChangeIdLine = 0; boolean inFooter = false; for (int i = lines.length - 1; i >= 0; --i) { if (!inFooter && isEmptyLine(lines[i])) continue; inFooter = true; if (changeIdPattern.matcher(trimRight(lines[i])).matches()) { indexOfChangeIdLine = i; break; } else if (isEmptyLine(lines[i]) || i == 0) return -1; } int indexOfChangeIdLineinString = 0; for (int i = 0; i < indexOfChangeIdLine; ++i) indexOfChangeIdLineinString += lines[i].length() + delimiter.length(); return indexOfChangeIdLineinString + lines[indexOfChangeIdLine].indexOf(CHANGE_ID); } private static boolean isEmptyLine(String line) { return line.trim().length() == 0; } private static String trimRight(String s) { return trailingWhitespace.matcher(s).replaceAll(""); //$NON-NLS-1$ } /** * Find the index of the first line of the footer paragraph in an array of * the lines, or lines.length if no footer is available * * @param lines * the commit message split into lines and the line delimiters * stripped off * @return the index of the first line of the footer paragraph, or * lines.length if no footer is available */ public static int indexOfFirstFooterLine(String[] lines) { int footerFirstLine = lines.length; for (int i = lines.length - 1; i > 1; --i) { if (footerPattern.matcher(lines[i]).matches()) { footerFirstLine = i; continue; } if (footerFirstLine != lines.length && lines[i].length() == 0) break; if (footerFirstLine != lines.length && includeInFooterPattern.matcher(lines[i]).matches()) { footerFirstLine = i + 1; continue; } footerFirstLine = lines.length; break; } return footerFirstLine; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 882 Content-Disposition: inline; filename="Equality.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "ff136f7b3b9f3a84321c028ca669f6b1a97eef33" /* * Copyright (C) 2022, Fabio Ponciroli 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.util; /** * Equality utilities. * * @since 6.2 */ public class Equality { /** * Compare by reference * * @param * type of the objects to compare * @param a * First object to compare * @param b * Second object to compare * @return {@code true} if the objects are identical, {@code false} * otherwise * * @since 6.2 */ @SuppressWarnings("ReferenceEquality") public static boolean isSameInstance(T a, T b) { return a == b; } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 77429 Content-Disposition: inline; filename="FS.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "6a40fad1db9a3acb8189fc1abfb2a6a1ce7a397a" /* * 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.util; import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.Instant.EPOCH; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.file.AccessDeniedException; import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.internal.util.ShutdownHook; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy; import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry; import org.eclipse.jgit.util.ProcessResult.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstraction to support various file system operations not in Java. */ public abstract class FS { private static final Logger LOG = LoggerFactory.getLogger(FS.class); /** * An empty array of entries, suitable as a return value for * {@link #list(File, FileModeStrategy)}. * * @since 5.0 */ protected static final Entry[] NO_ENTRIES = {}; private static final Pattern VERSION = Pattern .compile("\\s(\\d+)\\.(\\d+)\\.(\\d+)"); //$NON-NLS-1$ private static final Pattern EMPTY_PATH = Pattern .compile("^[\\p{javaWhitespace}" + File.pathSeparator + "]*$"); //$NON-NLS-1$ //$NON-NLS-2$ private volatile Boolean supportSymlinks; /** * This class creates FS instances. It will be overridden by a Java7 variant * if such can be detected in {@link #detect(Boolean)}. * * @since 3.0 */ public static class FSFactory { /** * Constructor */ protected FSFactory() { // empty } /** * Detect the file system * * @param cygwinUsed * whether cygwin is used * @return FS instance */ public FS detect(Boolean cygwinUsed) { if (SystemReader.getInstance().isWindows()) { if (cygwinUsed == null) { cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin()); } if (cygwinUsed.booleanValue()) { return new FS_Win32_Cygwin(); } return new FS_Win32(); } return new FS_POSIX(); } } /** * Result of an executed process. The caller is responsible to close the * contained {@link TemporaryBuffer}s * * @since 4.2 */ public static class ExecutionResult { private TemporaryBuffer stdout; private TemporaryBuffer stderr; private int rc; /** * @param stdout * stdout stream * @param stderr * stderr stream * @param rc * return code */ public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr, int rc) { this.stdout = stdout; this.stderr = stderr; this.rc = rc; } /** * Get buffered standard output stream * * @return buffered standard output stream */ public TemporaryBuffer getStdout() { return stdout; } /** * Get buffered standard error stream * * @return buffered standard error stream */ public TemporaryBuffer getStderr() { return stderr; } /** * Get the return code of the process * * @return the return code of the process */ public int getRc() { return rc; } } /** * Attributes of FileStores on this system * * @since 5.1.9 */ public static final class FileStoreAttributes { /** * Marker to detect undefined values when reading from the config file. */ private static final Duration UNDEFINED_DURATION = Duration .ofNanos(Long.MAX_VALUE); /** * Fallback filesystem timestamp resolution. The worst case timestamp * resolution on FAT filesystems is 2 seconds. *

* Must be at least 1 second. *

*/ public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration .ofSeconds(2); /** * Fallback FileStore attributes used when we can't measure the * filesystem timestamp resolution. The last modified time granularity * of FAT filesystems is 2 seconds. */ public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes( FALLBACK_TIMESTAMP_RESOLUTION); private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS .toNanos(1); private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS .toNanos(1); private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1); /** * Minimum file system timestamp resolution granularity to check, in * nanoseconds. Should be a positive power of ten smaller than * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e., * minimum value is 1 nanosecond. *

* Currently set to 1 microsecond, but could also be lower still. *

*/ private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND; private static final String JAVA_VERSION_PREFIX = System .getProperty("java.vendor") + '|' //$NON-NLS-1$ + System.getProperty("java.version") + '|'; //$NON-NLS-1$ private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration .ofMillis(10); private static final Map attributeCache = new ConcurrentHashMap<>(); private static final SimpleLruCache attrCacheByPath = new SimpleLruCache<>( 100, 0.2f); private static final AtomicBoolean background = new AtomicBoolean(); private static final Map locks = new ConcurrentHashMap<>(); private static final AtomicInteger threadNumber = new AtomicInteger(1); /** * Use a separate executor with at most one thread to synchronize * writing to the config. We write asynchronously since the config * itself might be on a different file system, which might otherwise * lead to locking problems. *

* Writing the config must not use a daemon thread, otherwise we may * leave an inconsistent state on disk when the JVM shuts down. Use a * small keep-alive time to avoid delays on shut-down. *

*/ private static final ExecutorService SAVE_RUNNER = new ThreadPoolExecutor( 0, 1, 1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), runnable -> { Thread t = new Thread(runnable, "JGit-FileStoreAttributeWriter-" //$NON-NLS-1$ + threadNumber.getAndIncrement()); // Make sure these threads do finish t.setDaemon(false); return t; }); static { // Shut down the SAVE_RUNNER on System.exit() ShutdownHook.INSTANCE .register(FileStoreAttributes::shutdownSafeRunner); } private static void shutdownSafeRunner() { try { SAVE_RUNNER.shutdownNow(); SAVE_RUNNER.awaitTermination(100, TimeUnit.MILLISECONDS); } catch (Exception e) { // Ignore; we're shutting down } } /** * Whether FileStore attributes should be determined asynchronously * * @param async * whether FileStore attributes should be determined * asynchronously. If false access to cached attributes may * block for some seconds for the first call per FileStore * @since 5.6.2 */ public static void setBackground(boolean async) { background.set(async); } /** * Configures size and purge factor of the path-based cache for file * system attributes. Caching of file system attributes avoids recurring * lookup of @{code FileStore} of files which may be expensive on some * platforms. * * @param maxSize * maximum size of the cache, default is 100 * @param purgeFactor * when the size of the map reaches maxSize the oldest * entries will be purged to free up some space for new * entries, {@code purgeFactor} is the fraction of * {@code maxSize} to purge when this happens * @since 5.1.9 */ public static void configureAttributesPathCache(int maxSize, float purgeFactor) { FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor); } /** * Get the FileStoreAttributes for the given FileStore * * @param path * file residing in the FileStore to get attributes for * @return FileStoreAttributes for the given path. */ public static FileStoreAttributes get(Path path) { try { path = path.toAbsolutePath(); Path dir = Files.isDirectory(path) ? path : path.getParent(); if (dir == null) { return FALLBACK_FILESTORE_ATTRIBUTES; } FileStoreAttributes cached = attrCacheByPath.get(dir); if (cached != null) { return cached; } FileStoreAttributes attrs = getFileStoreAttributes(dir); if (attrs == null) { // Don't cache, result might be late return FALLBACK_FILESTORE_ATTRIBUTES; } attrCacheByPath.put(dir, attrs); return attrs; } catch (SecurityException e) { return FALLBACK_FILESTORE_ATTRIBUTES; } } private static FileStoreAttributes getFileStoreAttributes(Path dir) { FileStore s; CompletableFuture> f = null; try { if (Files.exists(dir)) { s = Files.getFileStore(dir); FileStoreAttributes c = attributeCache.get(s); if (c != null) { return c; } if (!Files.isWritable(dir)) { // cannot measure resolution in a read-only directory LOG.debug( "{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$ Thread.currentThread(), dir); return FALLBACK_FILESTORE_ATTRIBUTES; } } else { // cannot determine FileStore of an unborn directory LOG.debug( "{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$ Thread.currentThread(), dir); return FALLBACK_FILESTORE_ATTRIBUTES; } f = CompletableFuture .supplyAsync(() -> { Lock lock = locks.computeIfAbsent(s, l -> new ReentrantLock()); if (!lock.tryLock()) { LOG.debug( "{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$ Thread.currentThread(), dir); return Optional.empty(); } Optional attributes = Optional .empty(); try { // Some earlier future might have set the value // and removed itself since we checked for the // value above. Hence check cache again. FileStoreAttributes c = attributeCache.get(s); if (c != null) { return Optional.of(c); } attributes = readFromConfig(s); if (attributes.isPresent()) { attributeCache.put(s, attributes.get()); return attributes; } Optional resolution = measureFsTimestampResolution( s, dir); if (resolution.isPresent()) { c = new FileStoreAttributes( resolution.get()); attributeCache.put(s, c); // for high timestamp resolution measure // minimal racy interval if (c.fsTimestampResolution .toNanos() < 100_000_000L) { c.minimalRacyInterval = measureMinimalRacyInterval( dir); } if (LOG.isDebugEnabled()) { LOG.debug(c.toString()); } FileStoreAttributes newAttrs = c; SAVE_RUNNER.execute( () -> saveToConfig(s, newAttrs)); } attributes = Optional.of(c); } finally { lock.unlock(); locks.remove(s); } return attributes; }); f = f.exceptionally(e -> { LOG.error(e.getLocalizedMessage(), e); return Optional.empty(); }); // even if measuring in background wait a little - if the result // arrives, it's better than returning the large fallback boolean runInBackground = background.get(); Optional d = runInBackground ? f.get( 100, TimeUnit.MILLISECONDS) : f.get(); if (d.isPresent()) { return d.get(); } else if (runInBackground) { // return null until measurement is finished return null; } // fall through and return fallback } catch (IOException | ExecutionException | CancellationException e) { cancel(f); LOG.error(e.getMessage(), e); } catch (TimeoutException | SecurityException e) { cancel(f); // use fallback } catch (InterruptedException e) { cancel(f); LOG.error(e.getMessage(), e); Thread.currentThread().interrupt(); } LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$ Thread.currentThread(), dir); return FALLBACK_FILESTORE_ATTRIBUTES; } private static void cancel( CompletableFuture> f) { if (f != null) { f.cancel(true); } } @SuppressWarnings("boxing") private static Duration measureMinimalRacyInterval(Path dir) { LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$ Thread.currentThread(), dir); int n = 0; int failures = 0; long racyNanos = 0; ArrayList deltas = new ArrayList<>(); Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ Instant end = Instant.now().plusSeconds(3); try { probe.toFile().deleteOnExit(); Files.createFile(probe); do { n++; write(probe, "a"); //$NON-NLS-1$ FileSnapshot snapshot = FileSnapshot.save(probe.toFile()); read(probe); write(probe, "b"); //$NON-NLS-1$ if (!snapshot.isModified(probe.toFile())) { deltas.add(Long.valueOf(snapshot.lastDelta())); racyNanos = snapshot.lastRacyThreshold(); failures++; } } while (Instant.now().compareTo(end) < 0); } catch (IOException e) { LOG.error(e.getMessage(), e); return FALLBACK_MIN_RACY_INTERVAL; } finally { deleteProbe(probe); } if (failures > 0) { Stats stats = new Stats(); for (Long d : deltas) { stats.add(d); } LOG.debug( "delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$ + "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$ + " delta max [ns], delta avg [ns]," //$NON-NLS-1$ + " delta stddev [ns]\n" //$NON-NLS-1$ + "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$ n, failures, racyNanos, stats.min(), stats.max(), stats.avg(), stats.stddev()); return Duration .ofNanos(Double.valueOf(stats.max()).longValue()); } // since no failures occurred using the measured filesystem // timestamp resolution there is no need for minimal racy interval LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$ Thread.currentThread()); return Duration.ZERO; } private static void write(Path p, String body) throws IOException { Path parent = p.getParent(); if (parent != null) { FileUtils.mkdirs(parent.toFile(), true); } try (Writer w = new OutputStreamWriter(Files.newOutputStream(p), UTF_8)) { w.write(body); } } private static String read(Path p) throws IOException { byte[] body = IO.readFully(p.toFile()); return new String(body, 0, body.length, UTF_8); } private static Optional measureFsTimestampResolution( FileStore s, Path dir) { if (LOG.isDebugEnabled()) { LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$ Thread.currentThread(), s, dir); } Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ try { probe.toFile().deleteOnExit(); Files.createFile(probe); Duration fsResolution = getFsResolution(s, dir, probe); Duration clockResolution = measureClockResolution(); fsResolution = fsResolution.plus(clockResolution); if (LOG.isDebugEnabled()) { LOG.debug( "{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$ Thread.currentThread(), s, dir, fsResolution); } return Optional.of(fsResolution); } catch (SecurityException e) { // Log it here; most likely deleteProbe() below will also run // into a SecurityException, and then this one will be lost // without trace. LOG.warn(e.getLocalizedMessage(), e); } catch (AccessDeniedException e) { LOG.warn(e.getLocalizedMessage(), e); // see bug 548648 } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); } finally { deleteProbe(probe); } return Optional.empty(); } private static Duration getFsResolution(FileStore s, Path dir, Path probe) throws IOException { File probeFile = probe.toFile(); FileTime t1 = Files.getLastModifiedTime(probe); Instant t1i = t1.toInstant(); FileTime t2; Duration last = FALLBACK_TIMESTAMP_RESOLUTION; long minScale = MINIMUM_RESOLUTION_NANOS; long scale = ONE_SECOND; long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis()); long low = 0; // Try up-front at microsecond and millisecond long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND }; for (long interval : tries) { if (interval >= ONE_MILLISECOND) { probeFile.setLastModified( t1i.plusNanos(interval).toEpochMilli()); } else { Files.setLastModifiedTime(probe, FileTime.from(t1i.plusNanos(interval))); } t2 = Files.getLastModifiedTime(probe); if (t2.compareTo(t1) > 0) { Duration diff = Duration.between(t1i, t2.toInstant()); if (!diff.isZero() && !diff.isNegative() && diff.compareTo(last) < 0) { scale = interval; high = 1; last = diff; break; } } else { // Makes no sense going below minScale = Math.max(minScale, interval); } } // Binary search loop while (high > low) { long mid = (high + low) / 2; if (mid == 0) { // Smaller than current scale. Adjust scale. long newScale = scale / 10; if (newScale < minScale) { break; } high *= scale / newScale; low *= scale / newScale; scale = newScale; mid = (high + low) / 2; } long delta = mid * scale; if (scale >= ONE_MILLISECOND) { probeFile.setLastModified( t1i.plusNanos(delta).toEpochMilli()); } else { Files.setLastModifiedTime(probe, FileTime.from(t1i.plusNanos(delta))); } t2 = Files.getLastModifiedTime(probe); int cmp = t2.compareTo(t1); if (cmp > 0) { high = mid; Duration diff = Duration.between(t1i, t2.toInstant()); if (diff.isZero() || diff.isNegative()) { LOG.warn(JGitText.get().logInconsistentFiletimeDiff, Thread.currentThread(), s, dir, t2, t1, diff, last); break; } else if (diff.compareTo(last) > 0) { LOG.warn(JGitText.get().logLargerFiletimeDiff, Thread.currentThread(), s, dir, diff, last); break; } last = diff; } else if (cmp < 0) { LOG.warn(JGitText.get().logSmallerFiletime, Thread.currentThread(), s, dir, t2, t1, last); break; } else { // No discernible difference low = mid + 1; } } return last; } private static Duration measureClockResolution() { Duration clockResolution = Duration.ZERO; for (int i = 0; i < 10; i++) { Instant t1 = Instant.now(); Instant t2 = t1; while (t2.compareTo(t1) <= 0) { t2 = Instant.now(); } Duration r = Duration.between(t1, t2); if (r.compareTo(clockResolution) > 0) { clockResolution = r; } } return clockResolution; } private static void deleteProbe(Path probe) { try { FileUtils.delete(probe.toFile(), FileUtils.SKIP_MISSING | FileUtils.RETRY); } catch (IOException e) { LOG.error(e.getMessage(), e); } } private static Optional readFromConfig( FileStore s) { StoredConfig userConfig; try { userConfig = SystemReader.getInstance().getUserConfig(); } catch (IOException | ConfigInvalidException e) { LOG.error(JGitText.get().readFileStoreAttributesFailed, e); return Optional.empty(); } String key = getConfigKey(s); Duration resolution = Duration.ofNanos(userConfig.getTimeUnit( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS)); if (UNDEFINED_DURATION.equals(resolution)) { return Optional.empty(); } Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD, UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS)); FileStoreAttributes c = new FileStoreAttributes(resolution); if (!UNDEFINED_DURATION.equals(minRacyThreshold)) { c.minimalRacyInterval = minRacyThreshold; } return Optional.of(c); } private static void saveToConfig(FileStore s, FileStoreAttributes c) { StoredConfig jgitConfig; try { jgitConfig = SystemReader.getInstance().getJGitConfig(); } catch (IOException | ConfigInvalidException e) { LOG.error(JGitText.get().saveFileStoreAttributesFailed, e); return; } long resolution = c.getFsTimestampResolution().toNanos(); TimeUnit resolutionUnit = getUnit(resolution); long resolutionValue = resolutionUnit.convert(resolution, TimeUnit.NANOSECONDS); long minRacyThreshold = c.getMinimalRacyInterval().toNanos(); TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold); long minRacyThresholdValue = minRacyThresholdUnit .convert(minRacyThreshold, TimeUnit.NANOSECONDS); final int max_retries = 5; int retries = 0; boolean succeeded = false; String key = getConfigKey(s); while (!succeeded && retries < max_retries) { try { jgitConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, String.format("%d %s", //$NON-NLS-1$ Long.valueOf(resolutionValue), resolutionUnit.name().toLowerCase())); jgitConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD, String.format("%d %s", //$NON-NLS-1$ Long.valueOf(minRacyThresholdValue), minRacyThresholdUnit.name().toLowerCase())); jgitConfig.save(); succeeded = true; } catch (LockFailedException e) { // race with another thread, wait a bit and try again try { retries++; if (retries < max_retries) { Thread.sleep(100); LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$ jgitConfig, Integer.valueOf(retries), Integer.valueOf(max_retries)); } else { LOG.warn(MessageFormat.format( JGitText.get().lockFailedRetry, jgitConfig, Integer.valueOf(retries))); } } catch (InterruptedException e1) { Thread.currentThread().interrupt(); break; } } catch (IOException e) { LOG.error(MessageFormat.format( JGitText.get().cannotSaveConfig, jgitConfig), e); break; } } } private static String getConfigKey(FileStore s) { String storeKey; if (SystemReader.getInstance().isWindows()) { Object attribute = null; try { attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$ } catch (IOException ignored) { // ignore } if (attribute instanceof Integer) { storeKey = attribute.toString(); } else { storeKey = s.name(); } } else { storeKey = s.name(); } return JAVA_VERSION_PREFIX + storeKey; } private static TimeUnit getUnit(long nanos) { TimeUnit unit; if (nanos < 200_000L) { unit = TimeUnit.NANOSECONDS; } else if (nanos < 200_000_000L) { unit = TimeUnit.MICROSECONDS; } else { unit = TimeUnit.MILLISECONDS; } return unit; } private final @NonNull Duration fsTimestampResolution; private Duration minimalRacyInterval; /** * Get the minimal racy interval * * @return the measured minimal interval after a file has been modified * in which we cannot rely on lastModified to detect * modifications */ public Duration getMinimalRacyInterval() { return minimalRacyInterval; } /** * Get the measured filesystem timestamp resolution * * @return the measured filesystem timestamp resolution */ @NonNull public Duration getFsTimestampResolution() { return fsTimestampResolution; } /** * Construct a FileStoreAttributeCache entry for the given filesystem * timestamp resolution * * @param fsTimestampResolution * resolution of filesystem timestamps */ public FileStoreAttributes( @NonNull Duration fsTimestampResolution) { this.fsTimestampResolution = fsTimestampResolution; this.minimalRacyInterval = Duration.ZERO; } @SuppressWarnings({ "nls", "boxing" }) @Override public String toString() { return String.format( "FileStoreAttributes[fsTimestampResolution=%,d µs, " + "minimalRacyInterval=%,d µs]", fsTimestampResolution.toNanos() / 1000, minimalRacyInterval.toNanos() / 1000); } } /** The auto-detected implementation selected for this operating system and JRE. */ public static final FS DETECTED = detect(); private static volatile FSFactory factory; /** * Auto-detect the appropriate file system abstraction. * * @return detected file system abstraction */ public static FS detect() { return detect(null); } /** * Auto-detect the appropriate file system abstraction, taking into account * the presence of a Cygwin installation on the system. Using jgit in * combination with Cygwin requires a more elaborate (and possibly slower) * resolution of file system paths. * * @param cygwinUsed *
    *
  • Boolean.TRUE to assume that Cygwin is used in * combination with jgit
  • *
  • Boolean.FALSE to assume that Cygwin is * not used with jgit
  • *
  • null to auto-detect whether a Cygwin * installation is present on the system and in this case assume * that Cygwin is used
  • *
* * Note: this parameter is only relevant on Windows. * @return detected file system abstraction */ public static FS detect(Boolean cygwinUsed) { if (factory == null) { factory = new FS.FSFactory(); } return factory.detect(cygwinUsed); } /** * Get cached FileStore attributes, if not yet available measure them using * a probe file under the given directory. * * @param dir * the directory under which the probe file will be created to * measure the timer resolution. * @return measured filesystem timestamp resolution * @since 5.1.9 */ public static FileStoreAttributes getFileStoreAttributes( @NonNull Path dir) { return FileStoreAttributes.get(dir); } private volatile Holder userHome; private volatile Holder gitSystemConfig; /** * Constructs a file system abstraction. */ protected FS() { // Do nothing by default. } /** * Initialize this FS using another's current settings. * * @param src * the source FS to copy from. */ protected FS(FS src) { userHome = src.userHome; gitSystemConfig = src.gitSystemConfig; } /** * Create a new instance of the same type of FS. * * @return a new instance of the same type of FS. */ public abstract FS newInstance(); /** * Does this operating system and JRE support the execute flag on files? * * @return true if this implementation can provide reasonably accurate * executable bit information; false otherwise. */ public abstract boolean supportsExecute(); /** * Does this file system support atomic file creation via * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is * not guaranteed that when two file system clients run createNewFile() in * parallel only one will succeed. In such cases both clients may think they * created a new file. * * @return true if this implementation support atomic creation of new Files * by {@link java.io.File#createNewFile()} * @since 4.5 */ public boolean supportsAtomicCreateNewFile() { return true; } /** * Does this operating system and JRE supports symbolic links. The * capability to handle symbolic links is detected at runtime. * * @return true if symbolic links may be used * @since 3.0 */ public boolean supportsSymlinks() { if (supportSymlinks == null) { detectSymlinkSupport(); } return Boolean.TRUE.equals(supportSymlinks); } private void detectSymlinkSupport() { File tempFile = null; try { tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$ File linkName = new File(tempFile.getPath() + "-tempsymlink"); //$NON-NLS-1$ createSymLink(linkName, tempFile.getPath()); supportSymlinks = Boolean.TRUE; linkName.delete(); } catch (IOException | UnsupportedOperationException | SecurityException | InternalError e) { supportSymlinks = Boolean.FALSE; } finally { if (tempFile != null) { try { FileUtils.delete(tempFile); } catch (IOException e) { LOG.error(JGitText.get().cannotDeleteFile, tempFile); } } } } /** * Is this file system case sensitive * * @return true if this implementation is case sensitive */ public abstract boolean isCaseSensitive(); /** * Determine if the file is executable (or not). *

* Not all platforms and JREs support executable flags on files. If the * feature is unsupported this method will always return false. *

* If the platform supports symbolic links and f is a symbolic link * this method returns false, rather than the state of the executable flags * on the target file. * * @param f * abstract path to test. * @return true if the file is believed to be executable by the user. */ public abstract boolean canExecute(File f); /** * Set a file to be executable by the user. *

* Not all platforms and JREs support executable flags on files. If the * feature is unsupported this method will always return false and no * changes will be made to the file specified. * * @param f * path to modify the executable status of. * @param canExec * true to enable execution; false to disable it. * @return true if the change succeeded; false otherwise. */ public abstract boolean setExecute(File f, boolean canExec); /** * Get the last modified time of a file system object. If the OS/JRE support * symbolic links, the modification time of the link is returned, rather * than that of the link target. * * @param p * a {@link Path} object. * @return last modified time of p * @since 5.1.9 */ public Instant lastModifiedInstant(Path p) { return FileUtils.lastModifiedInstant(p); } /** * Get the last modified time of a file system object. If the OS/JRE support * symbolic links, the modification time of the link is returned, rather * than that of the link target. * * @param f * a {@link File} object. * @return last modified time of p * @since 5.1.9 */ public Instant lastModifiedInstant(File f) { return FileUtils.lastModifiedInstant(f.toPath()); } /** * Set the last modified time of a file system object. *

* For symlinks it sets the modified time of the link target. * * @param p * a {@link Path} object. * @param time * last modified time * @throws java.io.IOException * if an IO error occurred * @since 5.1.9 */ public void setLastModified(Path p, Instant time) throws IOException { FileUtils.setLastModified(p, time); } /** * Get the length of a file or link, If the OS/JRE supports symbolic links * it's the length of the link, else the length of the target. * * @param path * a {@link java.io.File} object. * @return length of a file * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public long length(File path) throws IOException { return FileUtils.getLength(path); } /** * Delete a file. Throws an exception if delete fails. * * @param f * a {@link java.io.File} object. * @throws java.io.IOException * if an IO error occurred * @since 3.3 */ public void delete(File f) throws IOException { FileUtils.delete(f); } /** * Resolve this file to its actual path name that the JRE can use. *

* This method can be relatively expensive. Computing a translation may * require forking an external process per path name translated. Callers * should try to minimize the number of translations necessary by caching * the results. *

* Not all platforms and JREs require path name translation. Currently only * Cygwin on Win32 require translation for Cygwin based paths. * * @param dir * directory relative to which the path name is. * @param name * path name to translate. * @return the translated path. new File(dir,name) if this * platform does not require path name translation. */ public File resolve(File dir, String name) { File abspn = new File(name); if (abspn.isAbsolute()) return abspn; return new File(dir, name); } /** * Determine the user's home directory (location where preferences are). *

* This method can be expensive on the first invocation if path name * translation is required. Subsequent invocations return a cached result. *

* Not all platforms and JREs require path name translation. Currently only * Cygwin on Win32 requires translation of the Cygwin HOME directory. * * @return the user's home directory; null if the user does not have one. */ public File userHome() { Holder p = userHome; if (p == null) { p = new Holder<>(safeUserHomeImpl()); userHome = p; } return p.value; } private File safeUserHomeImpl() { File home; try { home = userHomeImpl(); if (home != null) { home.toPath(); return home; } } catch (RuntimeException e) { LOG.error(JGitText.get().exceptionWhileFindingUserHome, e); } home = defaultUserHomeImpl(); if (home != null) { try { home.toPath(); return home; } catch (InvalidPathException e) { LOG.error(MessageFormat .format(JGitText.get().invalidHomeDirectory, home), e); } } return null; } /** * Set the user's home directory location. * * @param path * the location of the user's preferences; null if there is no * home directory for the current user. * @return {@code this}. */ public FS setUserHome(File path) { userHome = new Holder<>(path); return this; } /** * Does this file system have problems with atomic renames? * * @return true if the caller should retry a failed rename of a lock file. */ public abstract boolean retryFailedLockFileCommit(); /** * Return all the attributes of a file, without following symbolic links. * * @param file * the file * @return {@link BasicFileAttributes} of the file * @throws IOException * in case of any I/O errors accessing the file * * @since 4.5.6 */ public BasicFileAttributes fileAttributes(File file) throws IOException { return FileUtils.fileAttributes(file); } /** * Determine the user's home directory (location where preferences are). * * @return the user's home directory; null if the user does not have one. */ protected File userHomeImpl() { return defaultUserHomeImpl(); } private File defaultUserHomeImpl() { String home = SystemReader.getInstance().getProperty("user.home"); //$NON-NLS-1$ if (StringUtils.isEmptyOrNull(home)) { return null; } return new File(home).getAbsoluteFile(); } /** * Searches the given path to see if it contains one of the given files. * Returns the first it finds which is executable. Returns null if not found * or if path is null. * * @param path * List of paths to search separated by File.pathSeparator * @param lookFor * Files to search for in the given path * @return the first match found, or null * @since 3.0 */ @SuppressWarnings("StringSplitter") protected static File searchPath(String path, String... lookFor) { if (StringUtils.isEmptyOrNull(path) || EMPTY_PATH.matcher(path).find()) { return null; } for (String p : path.split(File.pathSeparator)) { for (String command : lookFor) { File file = new File(p, command); try { if (file.isFile() && file.canExecute()) { return file.getAbsoluteFile(); } } catch (SecurityException e) { LOG.warn(MessageFormat.format( JGitText.get().skipNotAccessiblePath, file.getPath())); } } } return null; } /** * Execute a command and return a single line of output as a String * * @param dir * Working directory for the command * @param command * as component array * @param encoding * to be used to parse the command's output * @return the one-line output of the command or {@code null} if there is * none * @throws org.eclipse.jgit.errors.CommandFailedException * thrown when the command failed (return code was non-zero) */ @Nullable protected static String readPipe(File dir, String[] command, String encoding) throws CommandFailedException { return readPipe(dir, command, encoding, null); } /** * Execute a command and return a single line of output as a String * * @param dir * Working directory for the command * @param command * as component array * @param encoding * to be used to parse the command's output * @param env * Map of environment variables to be merged with those of the * current process * @return the one-line output of the command or {@code null} if there is * none * @throws org.eclipse.jgit.errors.CommandFailedException * thrown when the command failed (return code was non-zero) * @since 4.0 */ @Nullable protected static String readPipe(File dir, String[] command, String encoding, Map env) throws CommandFailedException { boolean debug = LOG.isDebugEnabled(); try { if (debug) { LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$ + dir); } ProcessBuilder pb = new ProcessBuilder(command); pb.directory(dir); if (env != null) { pb.environment().putAll(env); } Process p; try { p = pb.start(); } catch (IOException e) { // Process failed to start throw new CommandFailedException(-1, e.getMessage(), e); } p.getOutputStream().close(); GobblerThread gobbler = new GobblerThread(p, command, dir); gobbler.start(); String r = null; try (BufferedReader lineRead = new BufferedReader( new InputStreamReader(p.getInputStream(), encoding))) { r = lineRead.readLine(); if (debug) { LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$ LOG.debug("remaining output:\n"); //$NON-NLS-1$ String l; while ((l = lineRead.readLine()) != null) { LOG.debug(l); } } } for (;;) { try { int rc = p.waitFor(); gobbler.join(); if (rc == 0 && !gobbler.fail.get()) { return r; } if (debug) { LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$ } throw new CommandFailedException(rc, gobbler.errorMessage.get(), gobbler.exception.get()); } catch (InterruptedException ie) { // Stop bothering me, I have a zombie to reap. } } } catch (IOException e) { LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ } if (debug) { LOG.debug("readpipe returns null"); //$NON-NLS-1$ } return null; } private static class GobblerThread extends Thread { /* The process has 5 seconds to exit after closing stderr */ private static final int PROCESS_EXIT_TIMEOUT = 5; private final Process p; private final String desc; private final String dir; final AtomicBoolean fail = new AtomicBoolean(); final AtomicReference errorMessage = new AtomicReference<>(); final AtomicReference exception = new AtomicReference<>(); GobblerThread(Process p, String[] command, File dir) { this.p = p; this.desc = Arrays.toString(command); this.dir = Objects.toString(dir); } @Override public void run() { StringBuilder err = new StringBuilder(); try (InputStream is = p.getErrorStream()) { int ch; while ((ch = is.read()) != -1) { err.append((char) ch); } } catch (IOException e) { if (waitForProcessCompletion(e) && p.exitValue() != 0) { setError(e, e.getMessage(), p.exitValue()); fail.set(true); } else { // ignore. command terminated faster and stream was just closed // or the process didn't terminate within timeout } } finally { if (waitForProcessCompletion(null) && err.length() > 0) { setError(null, err.toString(), p.exitValue()); if (p.exitValue() != 0) { fail.set(true); } } } } @SuppressWarnings("boxing") private boolean waitForProcessCompletion(IOException originalError) { try { if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) { setError(originalError, MessageFormat.format( JGitText.get().commandClosedStderrButDidntExit, desc, PROCESS_EXIT_TIMEOUT), -1); fail.set(true); return false; } } catch (InterruptedException e) { setError(originalError, MessageFormat.format( JGitText.get().threadInterruptedWhileRunning, desc), -1); fail.set(true); return false; } return true; } private void setError(IOException e, String message, int exitCode) { exception.set(e); errorMessage.set(MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfCommand, desc, dir, Integer.valueOf(exitCode), message)); } } /** * Discover the path to the Git executable. * * @return the path to the Git executable or {@code null} if it cannot be * determined. * @since 4.0 */ protected abstract File discoverGitExe(); /** * Discover the path to the system-wide Git configuration file * * @return the path to the system-wide Git configuration file or * {@code null} if it cannot be determined. * @since 4.0 */ protected File discoverGitSystemConfig() { File gitExe = discoverGitExe(); if (gitExe == null) { return null; } // Bug 480782: Check if the discovered git executable is JGit CLI String v; try { v = readPipe(gitExe.getParentFile(), new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$ SystemReader.getInstance().getDefaultCharset().name()); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); return null; } if (StringUtils.isEmptyOrNull(v) || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$ return null; } if (parseVersion(v) < makeVersion(2, 8, 0)) { // --show-origin was introduced in git 2.8.0. For older git: trick // it into printing the path to the config file by using "echo" as // the editor. Map env = new HashMap<>(); env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$ String w; try { // This command prints the path even if it doesn't exist w = readPipe(gitExe.getParentFile(), new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$ "--edit" }, //$NON-NLS-1$ SystemReader.getInstance().getDefaultCharset().name(), env); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); return null; } if (StringUtils.isEmptyOrNull(w)) { return null; } return new File(w); } String w; try { w = readPipe(gitExe.getParentFile(), new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$ "--show-origin", "--list", "-z" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ SystemReader.getInstance().getDefaultCharset().name()); } catch (CommandFailedException e) { // This command fails if the system config doesn't exist if (LOG.isDebugEnabled()) { LOG.debug(e.getMessage()); } return null; } if (w == null) { return null; } // We get NUL-terminated items; the first one will be a file name, // prefixed by "file:". (Using -z is crucial, otherwise git quotes file // names with special characters.) int nul = w.indexOf(0); if (nul <= 0) { return null; } w = w.substring(0, nul); int colon = w.indexOf(':'); if (colon < 0) { return null; } w = w.substring(colon + 1); return w.isEmpty() ? null : new File(w); } private long parseVersion(String version) { Matcher m = VERSION.matcher(version); if (m.find()) { try { return makeVersion( Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3))); } catch (NumberFormatException e) { // Ignore } } return -1; } private long makeVersion(int major, int minor, int patch) { return ((major * 10_000L) + minor) * 10_000L + patch; } /** * Get the currently used path to the system-wide Git configuration file. * * @return the currently used path to the system-wide Git configuration file * or {@code null} if none has been set. * @since 4.0 */ public File getGitSystemConfig() { if (gitSystemConfig == null) { gitSystemConfig = new Holder<>(discoverGitSystemConfig()); } return gitSystemConfig.value; } /** * Set the path to the system-wide Git configuration file to use. * * @param configFile * the path to the config file. * @return {@code this} * @since 4.0 */ public FS setGitSystemConfig(File configFile) { gitSystemConfig = new Holder<>(configFile); return this; } /** * Get the parent directory of this file's parent directory * * @param grandchild * a {@link java.io.File} object. * @return the parent directory of this file's parent directory or * {@code null} in case there's no grandparent directory * @since 4.0 */ protected static File resolveGrandparentFile(File grandchild) { if (grandchild != null) { File parent = grandchild.getParentFile(); if (parent != null) return parent.getParentFile(); } return null; } /** * Check if a file is a symbolic link and read it * * @param path * a {@link java.io.File} object. * @return target of link or null * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public String readSymLink(File path) throws IOException { return FileUtils.readSymLink(path); } /** * Whether the path is a symbolic link (and we support these). * * @param path * a {@link java.io.File} object. * @return true if the path is a symbolic link (and we support these) * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public boolean isSymLink(File path) throws IOException { return FileUtils.isSymlink(path); } /** * Tests if the path exists, in case of a symbolic link, true even if the * target does not exist * * @param path * a {@link java.io.File} object. * @return true if path exists * @since 3.0 */ public boolean exists(File path) { return FileUtils.exists(path); } /** * Check if path is a directory. If the OS/JRE supports symbolic links and * path is a symbolic link to a directory, this method returns false. * * @param path * a {@link java.io.File} object. * @return true if file is a directory, * @since 3.0 */ public boolean isDirectory(File path) { return FileUtils.isDirectory(path); } /** * Examine if path represents a regular file. If the OS/JRE supports * symbolic links the test returns false if path represents a symbolic link. * * @param path * a {@link java.io.File} object. * @return true if path represents a regular file * @since 3.0 */ public boolean isFile(File path) { return FileUtils.isFile(path); } /** * Whether path is hidden, either starts with . on unix or has the hidden * attribute in windows * * @param path * a {@link java.io.File} object. * @return true if path is hidden, either starts with . on unix or has the * hidden attribute in windows * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public boolean isHidden(File path) throws IOException { return FileUtils.isHidden(path); } /** * Set the hidden attribute for file whose name starts with a period. * * @param path * a {@link java.io.File} object. * @param hidden * whether to set the file hidden * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public void setHidden(File path, boolean hidden) throws IOException { FileUtils.setHidden(path, hidden); } /** * Create a symbolic link * * @param path * a {@link java.io.File} object. * @param target * target path of the symlink * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public void createSymLink(File path, String target) throws IOException { FileUtils.createSymLink(path, target); } /** * A token representing a file created by * {@link #createNewFileAtomic(File)}. The token must be retained until the * file has been deleted in order to guarantee that the unique file was * created atomically. As soon as the file is no longer needed the lock * token must be closed. * * @since 4.7 */ public static class LockToken implements Closeable { private boolean isCreated; private Optional link; LockToken(boolean isCreated, Optional link) { this.isCreated = isCreated; this.link = link; } /** * Whether the file was created successfully * * @return {@code true} if the file was created successfully */ public boolean isCreated() { return isCreated; } @Override public void close() { if (!link.isPresent()) { return; } Path p = link.get(); if (!Files.exists(p)) { return; } try { Files.delete(p); } catch (IOException e) { LOG.error(MessageFormat .format(JGitText.get().closeLockTokenFailed, this), e); } } @Override public String toString() { return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$ ", link=" //$NON-NLS-1$ + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$ : "]"); //$NON-NLS-1$ } } /** * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses * of this class may take care to provide a safe implementation for this * even if {@link #supportsAtomicCreateNewFile()} is false * * @param path * the file to be created * @return LockToken this token must be closed after the created file was * deleted * @throws IOException * if an IO error occurred * @since 4.7 */ public LockToken createNewFileAtomic(File path) throws IOException { return new LockToken(path.createNewFile(), Optional.empty()); } /** * See * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * The path against which other should be * relativized. * @param other * The path that will be made relative to base. * @return A relative path that, when resolved against base, * will yield the original other. * @see FileUtils#relativizePath(String, String, String, boolean) * @since 3.7 */ public String relativize(String base, String other) { return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive()); } /** * Enumerates children of a directory. * * @param directory * to get the children of * @param fileModeStrategy * to use to calculate the git mode of a child * @return an array of entries for the children * * @since 5.0 */ public Entry[] list(File directory, FileModeStrategy fileModeStrategy) { File[] all = directory.listFiles(); if (all == null) { return NO_ENTRIES; } Entry[] result = new Entry[all.length]; for (int i = 0; i < result.length; i++) { result[i] = new FileEntry(all[i], this, fileModeStrategy); } return result; } /** * Checks whether the given hook is defined for the given repository, then * runs it with the given arguments. *

* The hook's standard output and error streams will be redirected to * System.out and System.err respectively. The * hook will have no stdin. *

* * @param repository * The repository for which a hook should be run. * @param hookName * The name of the hook to be executed. * @param args * Arguments to pass to this hook. Cannot be null, * but can be an empty array. * @return The ProcessResult describing this hook's execution. * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 */ public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args) throws JGitInternalException { return runHookIfPresent(repository, hookName, args, System.out, System.err, null); } /** * Checks whether the given hook is defined for the given repository, then * runs it with the given arguments. * * @param repository * The repository for which a hook should be run. * @param hookName * The name of the hook to be executed. * @param args * Arguments to pass to this hook. Cannot be null, * but can be an empty array. * @param outRedirect * A print stream on which to redirect the hook's stdout. Can be * null, in which case the hook's standard output * will be lost. * @param errRedirect * A print stream on which to redirect the hook's stderr. Can be * null, in which case the hook's standard error * will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. May be * null. * @return The ProcessResult describing this hook's execution. * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 5.11 */ public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { return new ProcessResult(Status.NOT_SUPPORTED); } /** * See * {@link #runHookIfPresent(Repository, String, String[], OutputStream, OutputStream, String)} * . Should only be called by FS supporting shell scripts execution. * * @param repository * The repository for which a hook should be run. * @param hookName * The name of the hook to be executed. * @param args * Arguments to pass to this hook. Cannot be null, * but can be an empty array. * @param outRedirect * A print stream on which to redirect the hook's stdout. Can be * null, in which case the hook's standard output * will be lost. * @param errRedirect * A print stream on which to redirect the hook's stderr. Can be * null, in which case the hook's standard error * will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. May be * null. * @return The ProcessResult describing this hook's execution. * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 5.11 */ protected ProcessResult internalRunHookIfPresent(Repository repository, String hookName, String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { File hookFile = findHook(repository, hookName); if (hookFile == null || hookName == null) { return new ProcessResult(Status.NOT_PRESENT); } File runDirectory = getRunDirectory(repository, hookName); if (runDirectory == null) { return new ProcessResult(Status.NOT_PRESENT); } String cmd = hookFile.getAbsolutePath(); ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args); hookProcess.directory(runDirectory.getAbsoluteFile()); Map environment = hookProcess.environment(); environment.put(Constants.GIT_DIR_KEY, repository.getDirectory().getAbsolutePath()); if (!repository.isBare()) { environment.put(Constants.GIT_COMMON_DIR_KEY, repository.getCommonDirectory().getAbsolutePath()); environment.put(Constants.GIT_WORK_TREE_KEY, repository.getWorkTree().getAbsolutePath()); } try { return new ProcessResult(runProcess(hookProcess, outRedirect, errRedirect, stdinArgs), Status.OK); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfHook, hookName), e); } catch (InterruptedException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionHookExecutionInterrupted, hookName), e); } } /** * Quote a string (such as a file system path obtained from a Java * {@link File} or {@link Path} object) such that it can be passed as first * argument to {@link #runInShell(String, String[])}. *

* This default implementation returns the string unchanged. *

* * @param cmd * the String to quote * @return the quoted string */ String shellQuote(String cmd) { return cmd; } /** * Tries to find a hook matching the given one in the given repository. * * @param repository * The repository within which to find a hook. * @param hookName * The name of the hook we're trying to find. * @return The {@link java.io.File} containing this particular hook if it * exists in the given repository, null otherwise. * @since 4.0 */ public File findHook(Repository repository, String hookName) { if (hookName == null) { return null; } File hookDir = getHooksDirectory(repository); if (hookDir == null) { return null; } File hookFile = new File(hookDir, hookName); if (hookFile.isAbsolute()) { if (!hookFile.exists() || (FS.DETECTED.supportsExecute() && !FS.DETECTED.canExecute(hookFile))) { return null; } } else { try { File runDirectory = getRunDirectory(repository, hookName); if (runDirectory == null) { return null; } Path hookPath = runDirectory.getAbsoluteFile().toPath() .resolve(hookFile.toPath()); FS fs = repository.getFS(); if (fs == null) { fs = FS.DETECTED; } if (!Files.exists(hookPath) || (fs.supportsExecute() && !fs.canExecute(hookPath.toFile()))) { return null; } hookFile = hookPath.toFile(); } catch (InvalidPathException e) { LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath, hookFile)); return null; } } return hookFile; } private File getRunDirectory(Repository repository, @NonNull String hookName) { if (repository.isBare()) { return repository.getDirectory(); } switch (hookName) { case "pre-receive": //$NON-NLS-1$ case "update": //$NON-NLS-1$ case "post-receive": //$NON-NLS-1$ case "post-update": //$NON-NLS-1$ case "push-to-checkout": //$NON-NLS-1$ return repository.getCommonDirectory(); default: return repository.getWorkTree(); } } private File getHooksDirectory(Repository repository) { Config config = repository.getConfig(); String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_HOOKS_PATH); if (hooksDir != null) { return new File(hooksDir); } File dir = repository.getCommonDirectory(); return dir == null ? null : new File(dir, Constants.HOOKS); } /** * Runs the given process until termination, clearing its stdout and stderr * streams on-the-fly. * * @param processBuilder * The process builder configured for this process. * @param outRedirect * A OutputStream on which to redirect the processes stdout. Can * be null, in which case the processes standard * output will be lost. * @param errRedirect * A OutputStream on which to redirect the processes stderr. Can * be null, in which case the processes standard * error will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. Can be * null. * @return the exit value of this process. * @throws java.io.IOException * if an I/O error occurs while executing this process. * @throws java.lang.InterruptedException * if the current thread is interrupted while waiting for the * process to end. * @since 4.2 */ public int runProcess(ProcessBuilder processBuilder, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws IOException, InterruptedException { InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream( stdinArgs.getBytes(UTF_8)); return runProcess(processBuilder, outRedirect, errRedirect, in); } /** * Runs the given process until termination, clearing its stdout and stderr * streams on-the-fly. * * @param processBuilder * The process builder configured for this process. * @param outRedirect * An OutputStream on which to redirect the processes stdout. Can * be null, in which case the processes standard * output will be lost. * @param errRedirect * An OutputStream on which to redirect the processes stderr. Can * be null, in which case the processes standard * error will be lost. * @param inRedirect * An InputStream from which to redirect the processes stdin. Can * be null, in which case the process doesn't get * any data over stdin. It is assumed that the whole InputStream * will be consumed by the process. The method will close the * inputstream after all bytes are read. * @return the return code of this process. * @throws java.io.IOException * if an I/O error occurs while executing this process. * @throws java.lang.InterruptedException * if the current thread is interrupted while waiting for the * process to end. * @since 4.2 */ public int runProcess(ProcessBuilder processBuilder, OutputStream outRedirect, OutputStream errRedirect, InputStream inRedirect) throws IOException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(2); Process process = null; // We'll record the first I/O exception that occurs, but keep on trying // to dispose of our open streams and file handles IOException ioException = null; try { process = processBuilder.start(); executor.execute( new StreamGobbler(process.getErrorStream(), errRedirect)); executor.execute( new StreamGobbler(process.getInputStream(), outRedirect)); @SuppressWarnings("resource") // Closed in the finally block OutputStream outputStream = process.getOutputStream(); try { if (inRedirect != null) { new StreamGobbler(inRedirect, outputStream).copy(); } } finally { try { outputStream.close(); } catch (IOException e) { // When the process exits before consuming the input, the OutputStream // is replaced with the null output stream. This null output stream // throws IOException for all write calls. When StreamGobbler fails to // flush the buffer because of this, this close call tries to flush it // again. This causes another IOException. Since we ignore the // IOException in StreamGobbler, we also ignore the exception here. } } return process.waitFor(); } catch (IOException e) { ioException = e; } finally { shutdownAndAwaitTermination(executor); if (process != null) { try { process.waitFor(); } catch (InterruptedException e) { // Thrown by the outer try. // Swallow this one to carry on our cleanup, and clear the // interrupted flag (processes throw the exception without // clearing the flag). Thread.interrupted(); } // A process doesn't clean its own resources even when destroyed // Explicitly try and close all three streams, preserving the // outer I/O exception if any. if (inRedirect != null) { inRedirect.close(); } try { process.getErrorStream().close(); } catch (IOException e) { ioException = ioException != null ? ioException : e; } try { process.getInputStream().close(); } catch (IOException e) { ioException = ioException != null ? ioException : e; } try { process.getOutputStream().close(); } catch (IOException e) { ioException = ioException != null ? ioException : e; } process.destroy(); } } // We can only be here if the outer try threw an IOException. throw ioException; } /** * Shuts down an {@link ExecutorService} in two phases, first by calling * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if * necessary, to cancel any lingering tasks. Returns true if the pool has * been properly shutdown, false otherwise. *

* * @param pool * the pool to shutdown * @return true if the pool has been properly shutdown, * false otherwise. */ private static boolean shutdownAndAwaitTermination(ExecutorService pool) { boolean hasShutdown = true; pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being canceled if (!pool.awaitTermination(60, TimeUnit.SECONDS)) hasShutdown = false; } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); hasShutdown = false; } return hasShutdown; } /** * Initialize a ProcessBuilder to run a command using the system shell. * * @param cmd * command to execute. This string should originate from the * end-user, and thus is platform specific. * @param args * arguments to pass to command. These should be protected from * shell evaluation. * @return a partially completed process builder. Caller should finish * populating directory, environment, and then start the process. */ public abstract ProcessBuilder runInShell(String cmd, String[] args); /** * Execute a command defined by a {@link java.lang.ProcessBuilder}. * * @param pb * The command to be executed * @param in * The standard input stream passed to the process * @return The result of the executed command * @throws java.lang.InterruptedException * if thread was interrupted * @throws java.io.IOException * if an IO error occurred * @since 4.2 */ public ExecutionResult execute(ProcessBuilder pb, InputStream in) throws IOException, InterruptedException { try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null); TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024)) { int rc = runProcess(pb, stdout, stderr, in); return new ExecutionResult(stdout, stderr, rc); } } private static class Holder { final V value; Holder(V value) { this.value = value; } } /** * File attributes we typically care for. * * @since 3.3 */ public static class Attributes { /** * Whether this are attributes of a directory * * @return true if this are the attributes of a directory */ public boolean isDirectory() { return isDirectory; } /** * Whether this are attributes of an executable file * * @return true if this are the attributes of an executable file */ public boolean isExecutable() { return isExecutable; } /** * Whether this are the attributes of a symbolic link * * @return true if this are the attributes of a symbolic link */ public boolean isSymbolicLink() { return isSymbolicLink; } /** * Whether this are the attributes of a regular file * * @return true if this are the attributes of a regular file */ public boolean isRegularFile() { return isRegularFile; } /** * Get the file creation time * * @return the time when the file was created */ public long getCreationTime() { return creationTime; } /** * Get the time when this object was last modified * * @return the time when this object was last modified * @since 5.1.9 */ public Instant getLastModifiedInstant() { return lastModifiedInstant; } private final boolean isDirectory; private final boolean isSymbolicLink; private final boolean isRegularFile; private final long creationTime; private final Instant lastModifiedInstant; private final boolean isExecutable; private final File file; private final boolean exists; /** * file length */ protected long length = -1; final FS fs; Attributes(FS fs, File file, boolean exists, boolean isDirectory, boolean isExecutable, boolean isSymbolicLink, boolean isRegularFile, long creationTime, Instant lastModifiedInstant, long length) { this.fs = fs; this.file = file; this.exists = exists; this.isDirectory = isDirectory; this.isExecutable = isExecutable; this.isSymbolicLink = isSymbolicLink; this.isRegularFile = isRegularFile; this.creationTime = creationTime; this.lastModifiedInstant = lastModifiedInstant; this.length = length; } /** * Constructor when there are issues with reading. All attributes except * given will be set to the default values. * * @param path * file path * @param fs * FS to use */ public Attributes(File path, FS fs) { this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L); } /** * Get the length of this file * * @return length of this file object */ public long getLength() { if (length == -1) return length = file.length(); return length; } /** * Get the filename * * @return the filename */ public String getName() { return file.getName(); } /** * Get the file the attributes apply to * * @return the file the attributes apply to */ public File getFile() { return file; } boolean exists() { return exists; } } /** * Get the file attributes we care for. * * @param path * a {@link java.io.File} object. * @return the file attributes we care for. * @since 3.3 */ public Attributes getAttributes(File path) { boolean isDirectory = isDirectory(path); boolean isFile = !isDirectory && path.isFile(); assert path.exists() == isDirectory || isFile; boolean exists = isDirectory || isFile; boolean canExecute = exists && !isDirectory && canExecute(path); boolean isSymlink = false; Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH; long createTime = 0L; return new Attributes(this, path, exists, isDirectory, canExecute, isSymlink, isFile, createTime, lastModified, -1); } /** * Normalize the unicode path to composed form. * * @param file * a {@link java.io.File} object. * @return NFC-format File * @since 3.3 */ public File normalize(File file) { return file; } /** * Normalize the unicode path to composed form. * * @param name * path name * @return NFC-format string * @since 3.3 */ public String normalize(String name) { return name; } /** * Get common dir path. * * @param dir * the .git folder * @return common dir path * @throws IOException * if commondir file can't be read * * @since 7.0 */ public File getCommonDir(File dir) throws IOException { // first the GIT_COMMON_DIR is same as GIT_DIR File commonDir = dir; // now check if commondir file exists (e.g. worktree repository) File commonDirFile = new File(dir, Constants.COMMONDIR_FILE); if (commonDirFile.isFile()) { String commonDirPath = new String(IO.readFully(commonDirFile)) .trim(); commonDir = new File(commonDirPath); if (!commonDir.isAbsolute()) { commonDir = new File(dir, commonDirPath).getCanonicalFile(); } } return commonDir; } /** * This runnable will consume an input stream's content into an output * stream as soon as it gets available. *

* Typically used to empty processes' standard output and error, preventing * them to choke. *

*

* Note that a {@link StreamGobbler} will never close either of its * streams. *

*/ private static class StreamGobbler implements Runnable { private InputStream in; private OutputStream out; public StreamGobbler(InputStream stream, OutputStream output) { this.in = stream; this.out = output; } @Override public void run() { try { copy(); } catch (IOException e) { // Do nothing on read failure; leave streams open. } } void copy() throws IOException { boolean writeFailure = false; byte[] buffer = new byte[4096]; int readBytes; while ((readBytes = in.read(buffer)) != -1) { // Do not try to write again after a failure, but keep // reading as long as possible to prevent the input stream // from choking. if (!writeFailure && out != null) { try { out.write(buffer, 0, readBytes); out.flush(); } catch (IOException e) { writeFailure = true; } } } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12509 Content-Disposition: inline; filename="FS_POSIX.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "db2b5b4f7143eaec04490fabc65f15eab6f4b2f4" /* * Copyright (C) 2010, 2024, 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.util; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base FS for POSIX based systems * * @since 3.0 */ public class FS_POSIX extends FS { private static final Logger LOG = LoggerFactory.getLogger(FS_POSIX.class); private static final String DEFAULT_GIT_LOCATION = "/usr/bin/git"; //$NON-NLS-1$ private static final int DEFAULT_UMASK = 0022; private volatile int umask = -1; private static final Map CAN_HARD_LINK = new ConcurrentHashMap<>(); private volatile AtomicFileCreation supportsAtomicFileCreation = AtomicFileCreation.UNDEFINED; private enum AtomicFileCreation { SUPPORTED, NOT_SUPPORTED, UNDEFINED } /** * Default constructor. */ protected FS_POSIX() { } /** * Constructor * * @param src * FS to copy some settings from */ protected FS_POSIX(FS src) { super(src); if (src instanceof FS_POSIX) { umask = ((FS_POSIX) src).umask; } } /** {@inheritDoc} */ @Override public FS newInstance() { return new FS_POSIX(this); } /** * Set the umask, overriding any value observed from the shell. * * @param umask * mask to apply when creating files. * @since 4.0 */ public void setUmask(int umask) { this.umask = umask; } private int umask() { int u = umask; if (u == -1) { u = readUmask(); umask = u; } return u; } /** @return mask returned from running {@code umask} command in shell. */ private static int readUmask() { try { Process p = Runtime.getRuntime().exec( new String[] { "sh", "-c", "umask" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ null, null); try (BufferedReader lineRead = new BufferedReader( new InputStreamReader(p.getInputStream(), SystemReader .getInstance().getDefaultCharset().name()))) { if (p.waitFor() == 0) { String s = lineRead.readLine(); if (s != null && s.matches("0?\\d{3}")) { //$NON-NLS-1$ return Integer.parseInt(s, 8); } } return DEFAULT_UMASK; } } catch (Exception e) { return DEFAULT_UMASK; } } /** {@inheritDoc} */ @Override protected File discoverGitExe() { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ File gitExe = searchPath(path, "git"); //$NON-NLS-1$ if (SystemReader.getInstance().isMacOS()) { if (gitExe == null || DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) { if (searchPath(path, "bash") != null) { //$NON-NLS-1$ // On MacOSX, PATH is shorter when Eclipse is launched from the // Finder than from a terminal. Therefore try to launch bash as a // login shell and search using that. try { String w = readPipe(userHome(), new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ SystemReader.getInstance().getDefaultCharset() .name()); if (!StringUtils.isEmptyOrNull(w)) { gitExe = new File(w); } } catch (CommandFailedException e) { LOG.warn(e.getMessage()); } } } if (gitExe != null && DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) { // If we still have the default git exe, it's an XCode wrapper // that may prompt the user to install the XCode command line // tools if not already present. Avoid the prompt by returning // null if no XCode git is there. try { String w = readPipe(userHome(), new String[] { "xcode-select", "-p" }, //$NON-NLS-1$ //$NON-NLS-2$ SystemReader.getInstance().getDefaultCharset() .name()); if (StringUtils.isEmptyOrNull(w)) { gitExe = null; } else { File realGitExe = new File(new File(w), DEFAULT_GIT_LOCATION.substring(1)); if (!realGitExe.exists()) { gitExe = null; } } } catch (CommandFailedException e) { gitExe = null; } } } return gitExe; } /** {@inheritDoc} */ @Override public boolean isCaseSensitive() { return !SystemReader.getInstance().isMacOS(); } /** {@inheritDoc} */ @Override public boolean supportsExecute() { return true; } /** {@inheritDoc} */ @Override public boolean canExecute(File f) { if (!isFile(f)) { return false; } try { Path path = FileUtils.toPath(f); Set pset = Files.getPosixFilePermissions(path); return pset.contains(PosixFilePermission.OWNER_EXECUTE); } catch (IOException ex) { return false; } } /** {@inheritDoc} */ @Override public boolean setExecute(File f, boolean canExecute) { if (!isFile(f)) return false; if (!canExecute) return f.setExecutable(false, false); try { Path path = FileUtils.toPath(f); Set pset = Files.getPosixFilePermissions(path); // owner (user) is always allowed to execute. pset.add(PosixFilePermission.OWNER_EXECUTE); int mask = umask(); apply(pset, mask, PosixFilePermission.GROUP_EXECUTE, 1 << 3); apply(pset, mask, PosixFilePermission.OTHERS_EXECUTE, 1); Files.setPosixFilePermissions(path, pset); return true; } catch (IOException e) { // The interface doesn't allow to throw IOException final boolean debug = Boolean.parseBoolean(SystemReader .getInstance().getProperty("jgit.fs.debug")); //$NON-NLS-1$ if (debug) System.err.println(e); return false; } } private static void apply(Set set, int umask, PosixFilePermission perm, int test) { if ((umask & test) == 0) { // If bit is clear in umask, permission is allowed. set.add(perm); } else { // If bit is set in umask, permission is denied. set.remove(perm); } } /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { List argv = new ArrayList<>(5 + args.length); argv.add("sh"); //$NON-NLS-1$ if (SystemReader.getInstance().isMacOS()) { // Use a login shell to get the full normal $PATH argv.add("-l"); //$NON-NLS-1$ } argv.add("-c"); //$NON-NLS-1$ argv.add(cmd + " \"$@\""); //$NON-NLS-1$ argv.add(cmd); argv.addAll(Arrays.asList(args)); ProcessBuilder proc = new ProcessBuilder(); proc.command(argv); return proc; } @Override String shellQuote(String cmd) { return QuotedString.BOURNE.quote(cmd); } /** {@inheritDoc} */ @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { return internalRunHookIfPresent(repository, hookName, args, outRedirect, errRedirect, stdinArgs); } /** {@inheritDoc} */ @Override public boolean retryFailedLockFileCommit() { return false; } /** {@inheritDoc} */ @Override public void setHidden(File path, boolean hidden) throws IOException { // no action on POSIX } /** {@inheritDoc} */ @Override public Attributes getAttributes(File path) { return FileUtils.getFileAttributesPosix(this, path); } /** {@inheritDoc} */ @Override public File normalize(File file) { return FileUtils.normalize(file); } /** {@inheritDoc} */ @Override public String normalize(String name) { return FileUtils.normalize(name); } /** {@inheritDoc} */ @Override public boolean supportsAtomicCreateNewFile() { if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) { try { StoredConfig config = SystemReader.getInstance().getUserConfig(); String value = config.getString(CONFIG_CORE_SECTION, null, CONFIG_KEY_SUPPORTSATOMICFILECREATION); if (value != null) { supportsAtomicFileCreation = StringUtils.toBoolean(value) ? AtomicFileCreation.SUPPORTED : AtomicFileCreation.NOT_SUPPORTED; } else { supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED; } } catch (IOException | ConfigInvalidException e) { LOG.warn(JGitText.get().assumeAtomicCreateNewFile, e); supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED; } } return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED; } /** * {@inheritDoc} *

* An implementation of the File#createNewFile() semantics which can create * a unique file atomically also on NFS. If the config option * {@code core.supportsAtomicCreateNewFile = true} (which is the default) * then simply Files#createFile() is called. * * But if {@code core.supportsAtomicCreateNewFile = false} then after * successful creation of the lock file a hard link to that lock file is * created and the attribute nlink of the lock file is checked to be 2. If * multiple clients manage to create the same lock file nlink would be * greater than 2 showing the error. The hard link needs to be retained * until the corresponding file is no longer needed in order to prevent that * another process can create the same file concurrently using another NFS * client which might not yet see the file due to caching. * * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html" * @param file * the unique file to be created atomically * @return LockToken this lock token must be held until the file is no * longer needed * @throws IOException * if an IO error occurred * @since 5.0 */ @Override public LockToken createNewFileAtomic(File file) throws IOException { Path path; try { path = file.toPath(); Files.createFile(path); } catch (FileAlreadyExistsException | InvalidPathException e) { return token(false, null); } if (supportsAtomicCreateNewFile()) { return token(true, null); } Path link = null; FileStore store = null; try { store = Files.getFileStore(path); } catch (SecurityException e) { return token(true, null); } try { Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store, s -> Boolean.TRUE); if (Boolean.FALSE.equals(canLink)) { return token(true, null); } link = Files.createLink(Paths.get(uniqueLinkPath(file)), path); Integer nlink = (Integer) Files.getAttribute(path, "unix:nlink"); //$NON-NLS-1$ if (nlink.intValue() > 2) { LOG.warn(MessageFormat.format( JGitText.get().failedAtomicFileCreation, path, nlink)); return token(false, link); } else if (nlink.intValue() < 2) { CAN_HARD_LINK.put(store, Boolean.FALSE); } return token(true, link); } catch (UnsupportedOperationException | IllegalArgumentException | FileSystemException | SecurityException e) { CAN_HARD_LINK.put(store, Boolean.FALSE); return token(true, link); } } private static LockToken token(boolean created, @Nullable Path p) { return ((p != null) && Files.exists(p)) ? new LockToken(created, Optional.of(p)) : new LockToken(created, Optional.empty()); } private static String uniqueLinkPath(File file) { UUID id = UUID.randomUUID(); return file.getAbsolutePath() + "." //$NON-NLS-1$ + Long.toHexString(id.getMostSignificantBits()) + Long.toHexString(id.getLeastSignificantBits()); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5304 Content-Disposition: inline; filename="FS_Win32.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "5926655b7b0665253d1b1e46e23d46614762e455" /* * Copyright (C) 2009, Google Inc. * 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.util; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy; import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * FS implementation for Windows * * @since 3.0 */ public class FS_Win32 extends FS { private static final Logger LOG = LoggerFactory.getLogger(FS_Win32.class); /** * Constructor */ public FS_Win32() { super(); } /** * Constructor * * @param src * instance whose attributes to copy */ protected FS_Win32(FS src) { super(src); } @Override public FS newInstance() { return new FS_Win32(this); } @Override public boolean supportsExecute() { return false; } @Override public boolean canExecute(File f) { return false; } @Override public boolean setExecute(File f, boolean canExec) { return false; } @Override public boolean isCaseSensitive() { return false; } @Override public boolean retryFailedLockFileCommit() { return true; } @Override public Entry[] list(File directory, FileModeStrategy fileModeStrategy) { if (!Files.isDirectory(directory.toPath(), LinkOption.NOFOLLOW_LINKS)) { return NO_ENTRIES; } List result = new ArrayList<>(); FS fs = this; boolean checkExecutable = fs.supportsExecute(); try { Files.walkFileTree(directory.toPath(), EnumSet.noneOf(FileVisitOption.class), 1, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { File f = file.toFile(); FS.Attributes attributes = new FS.Attributes(fs, f, true, attrs.isDirectory(), checkExecutable && f.canExecute(), attrs.isSymbolicLink(), attrs.isRegularFile(), attrs.creationTime().toMillis(), attrs.lastModifiedTime().toInstant(), attrs.size()); result.add(new FileEntry(f, fs, attributes, fileModeStrategy)); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { // Just ignore it return FileVisitResult.CONTINUE; } }); } catch (IOException e) { // Ignore } if (result.isEmpty()) { return NO_ENTRIES; } return result.toArray(new Entry[0]); } @Override protected File discoverGitExe() { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ File gitExe = searchPath(path, "git.exe", "git.cmd"); //$NON-NLS-1$ //$NON-NLS-2$ if (gitExe == null) { if (searchPath(path, "bash.exe") != null) { //$NON-NLS-1$ // This isn't likely to work, but its worth trying: // If bash is in $PATH, git should also be in $PATH. String w; try { w = readPipe(userHome(), new String[] { "bash", "--login", "-c", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ "which git" }, // //$NON-NLS-1$ SystemReader.getInstance().getDefaultCharset() .name()); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); return null; } if (!StringUtils.isEmptyOrNull(w)) { // The path may be in cygwin/msys notation so resolve it right away gitExe = resolve(null, w); } } } return gitExe; } @Override protected File userHomeImpl() { String home = SystemReader.getInstance().getenv("HOME"); //$NON-NLS-1$ if (home != null) { return resolve(null, home); } String homeDrive = SystemReader.getInstance().getenv("HOMEDRIVE"); //$NON-NLS-1$ if (homeDrive != null) { String homePath = SystemReader.getInstance().getenv("HOMEPATH"); //$NON-NLS-1$ if (homePath != null) { return new File(homeDrive, homePath); } } String homeShare = SystemReader.getInstance().getenv("HOMESHARE"); //$NON-NLS-1$ if (homeShare != null) { return new File(homeShare); } return super.userHomeImpl(); } @Override public ProcessBuilder runInShell(String cmd, String[] args) { List argv = new ArrayList<>(3 + args.length); argv.add("cmd.exe"); //$NON-NLS-1$ argv.add("/c"); //$NON-NLS-1$ argv.add(cmd); argv.addAll(Arrays.asList(args)); ProcessBuilder proc = new ProcessBuilder(); proc.command(argv); return proc; } @Override public Attributes getAttributes(File path) { return FileUtils.getFileAttributesBasic(this, path); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3470 Content-Disposition: inline; filename="FS_Win32_Cygwin.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "237879110a753bc281525d3da8b900dedb4baf6f" /* * 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.util; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.lib.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * FS implementation for Cygwin on Windows * * @since 3.0 */ public class FS_Win32_Cygwin extends FS_Win32 { private static final Logger LOG = LoggerFactory .getLogger(FS_Win32_Cygwin.class); private static String cygpath; /** * Whether cygwin is found * * @return true if cygwin is found */ public static boolean isCygwin() { final String path = System.getProperty("java.library.path"); //$NON-NLS-1$ if (path == null) return false; File found = FS.searchPath(path, "cygpath.exe"); //$NON-NLS-1$ if (found != null) cygpath = found.getPath(); return cygpath != null; } /** * Constructor */ public FS_Win32_Cygwin() { super(); } /** * Constructor * * @param src * instance whose attributes to copy */ protected FS_Win32_Cygwin(FS src) { super(src); } @Override public FS newInstance() { return new FS_Win32_Cygwin(this); } @Override public File resolve(File dir, String pn) { String useCygPath = System.getProperty("jgit.usecygpath"); //$NON-NLS-1$ if (useCygPath != null && useCygPath.equals("true")) { //$NON-NLS-1$ String w; try { w = readPipe(dir, // new String[] { cygpath, "--windows", "--absolute", pn }, // //$NON-NLS-1$ //$NON-NLS-2$ UTF_8.name()); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); return null; } if (!StringUtils.isEmptyOrNull(w)) { return new File(w); } } return super.resolve(dir, pn); } @Override protected File userHomeImpl() { final String home = System.getenv("HOME"); //$NON-NLS-1$ if (home == null || home.length() == 0) return super.userHomeImpl(); return resolve(new File("."), home); //$NON-NLS-1$ } @Override public ProcessBuilder runInShell(String cmd, String[] args) { List argv = new ArrayList<>(4 + args.length); argv.add("sh.exe"); //$NON-NLS-1$ argv.add("-c"); //$NON-NLS-1$ argv.add(cmd + " \"$@\""); //$NON-NLS-1$ argv.add(cmd); argv.addAll(Arrays.asList(args)); ProcessBuilder proc = new ProcessBuilder(); proc.command(argv); return proc; } @Override String shellQuote(String cmd) { return QuotedString.BOURNE.quote(cmd.replace(File.separatorChar, '/')); } @Override public String relativize(String base, String other) { final String relativized = super.relativize(base, other); return relativized.replace(File.separatorChar, '/'); } @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { return internalRunHookIfPresent(repository, hookName, args, outRedirect, errRedirect, stdinArgs); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 34264 Content-Disposition: inline; filename="FileUtils.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "39c67f1b86713d454848f9d44d2def0ea261424d" /* * Copyright (C) 2010, Google Inc. * Copyright (C) 2010, Matthias Sohn * Copyright (C) 2010, Jens Baumgart 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.util; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.channels.FileChannel; import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.CopyOption; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.text.MessageFormat; import java.text.Normalizer; import java.text.Normalizer.Form; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Random; import java.util.regex.Pattern; import java.util.stream.Stream; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.FS.Attributes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * File Utilities */ public class FileUtils { private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class); private static final Random RNG = new Random(); /** * Option to delete given {@code File} */ public static final int NONE = 0; /** * Option to recursively delete given {@code File} */ public static final int RECURSIVE = 1; /** * Option to retry deletion if not successful */ public static final int RETRY = 2; /** * Option to skip deletion if file doesn't exist */ public static final int SKIP_MISSING = 4; /** * Option not to throw exceptions when a deletion finally doesn't succeed. * @since 2.0 */ public static final int IGNORE_ERRORS = 8; /** * Option to only delete empty directories. This option can be combined with * {@link #RECURSIVE} * * @since 3.0 */ public static final int EMPTY_DIRECTORIES_ONLY = 16; /** * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}. * * @param f * {@code File} to be converted to {@code Path} * @return the path represented by the file * @throws java.io.IOException * in case the path represented by the file is not valid ( * {@link java.nio.file.InvalidPathException}) * @since 4.10 */ public static Path toPath(File f) throws IOException { try { return f.toPath(); } catch (InvalidPathException ex) { throw new IOException(ex); } } /** * Delete file or empty folder * * @param f * {@code File} to be deleted * @throws java.io.IOException * if deletion of {@code f} fails. This may occur if {@code f} * didn't exist when the method was called. This can therefore * cause java.io.IOExceptions during race conditions when * multiple concurrent threads all try to delete the same file. */ public static void delete(File f) throws IOException { delete(f, NONE); } /** * Delete file or folder * * @param f * {@code File} to be deleted * @param options * deletion options, {@code RECURSIVE} for recursive deletion of * a subtree, {@code RETRY} to retry when deletion failed. * Retrying may help if the underlying file system doesn't allow * deletion of files being read by another thread. * @throws java.io.IOException * if deletion of {@code f} fails. This may occur if {@code f} * didn't exist when the method was called. This can therefore * cause java.io.IOExceptions during race conditions when * multiple concurrent threads all try to delete the same file. * This exception is not thrown when IGNORE_ERRORS is set. */ public static void delete(File f, int options) throws IOException { FS fs = FS.DETECTED; if ((options & SKIP_MISSING) != 0 && !fs.exists(f)) return; if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) { final File[] items = f.listFiles(); if (items != null) { List files = new ArrayList<>(); List dirs = new ArrayList<>(); for (File c : items) if (c.isFile()) files.add(c); else dirs.add(c); // Try to delete files first, otherwise options // EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty // directories before aborting, depending on order. for (File file : files) delete(file, options); for (File d : dirs) delete(d, options); } } boolean delete = false; if ((options & EMPTY_DIRECTORIES_ONLY) != 0) { if (f.isDirectory()) { delete = true; } else if ((options & IGNORE_ERRORS) == 0) { throw new IOException(MessageFormat.format( JGitText.get().deleteFileFailed, f.getAbsolutePath())); } } else { delete = true; } if (delete) { IOException t = null; Path p = f.toPath(); boolean tryAgain; do { tryAgain = false; try { Files.delete(p); return; } catch (NoSuchFileException | FileNotFoundException e) { handleDeleteException(f, e, options, SKIP_MISSING | IGNORE_ERRORS); return; } catch (DirectoryNotEmptyException e) { handleDeleteException(f, e, options, IGNORE_ERRORS); return; } catch (IOException e) { if (!f.canWrite()) { tryAgain = f.setWritable(true); } if (!tryAgain) { t = e; } } } while (tryAgain); if ((options & RETRY) != 0) { for (int i = 1; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException ex) { // ignore } try { Files.deleteIfExists(p); return; } catch (IOException e) { t = e; } } } handleDeleteException(f, t, options, IGNORE_ERRORS); } } private static void handleDeleteException(File f, IOException e, int allOptions, int checkOptions) throws IOException { if (e != null && (allOptions & checkOptions) == 0) { throw new IOException(MessageFormat.format( JGitText.get().deleteFileFailed, f.getAbsolutePath()), e); } } /** * Rename a file or folder. If the rename fails and if we are running on a * filesystem where it makes sense to repeat a failing rename then repeat * the rename operation up to 9 times with 100ms sleep time between two * calls. Furthermore if the destination exists and is directory hierarchy * with only directories in it, the whole directory hierarchy will be * deleted. If the target represents a non-empty directory structure, empty * subdirectories within that structure may or may not be deleted even if * the method fails. Furthermore if the destination exists and is a file * then the file will be deleted and then the rename is retried. *

* This operation is not atomic. * * @see FS#retryFailedLockFileCommit() * @param src * the old {@code File} * @param dst * the new {@code File} * @throws java.io.IOException * if the rename has failed * @since 3.0 */ public static void rename(File src, File dst) throws IOException { rename(src, dst, StandardCopyOption.REPLACE_EXISTING); } /** * Rename a file or folder using the passed * {@link java.nio.file.CopyOption}s. If the rename fails and if we are * running on a filesystem where it makes sense to repeat a failing rename * then repeat the rename operation up to 9 times with 100ms sleep time * between two calls. Furthermore if the destination exists and is a * directory hierarchy with only directories in it, the whole directory * hierarchy will be deleted. If the target represents a non-empty directory * structure, empty subdirectories within that structure may or may not be * deleted even if the method fails. Furthermore if the destination exists * and is a file then the file will be replaced if * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set. * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the * rename will be done atomically or fail with an * {@link java.nio.file.AtomicMoveNotSupportedException} * * @param src * the old file * @param dst * the new file * @param options * options to pass to * {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)} * @throws java.nio.file.AtomicMoveNotSupportedException * if file cannot be moved as an atomic file system operation * @throws java.io.IOException * if an IO error occurred * @since 4.1 */ public static void rename(final File src, final File dst, CopyOption... options) throws AtomicMoveNotSupportedException, IOException { int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1; IOException finalError = null; while (--attempts >= 0) { try { Files.move(toPath(src), toPath(dst), options); return; } catch (AtomicMoveNotSupportedException e) { throw e; } catch (IOException e) { if (attempts == 0) { // Only delete on the last attempt. try { if (!dst.delete()) { delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); } // On *nix there is no try, you do or do not Files.move(toPath(src), toPath(dst), options); return; } catch (IOException e2) { e2.addSuppressed(e); finalError = e2; } } } if (attempts > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new IOException(MessageFormat.format( JGitText.get().renameFileFailed, src.getAbsolutePath(), dst.getAbsolutePath()), e); } } } throw new IOException( MessageFormat.format(JGitText.get().renameFileFailed, src.getAbsolutePath(), dst.getAbsolutePath()), finalError); } /** * Creates the directory named by this abstract pathname. * * @param d * directory to be created * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore * cause java.io.IOExceptions during race conditions when * multiple concurrent threads all try to create the same * directory. */ public static void mkdir(File d) throws IOException { mkdir(d, false); } /** * Creates the directory named by this abstract pathname. * * @param d * directory to be created * @param skipExisting * if {@code true} skip creation of the given directory if it * already exists in the file system * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore * cause java.io.IOExceptions during race conditions when * multiple concurrent threads all try to create the same * directory. */ public static void mkdir(File d, boolean skipExisting) throws IOException { if (!d.mkdir()) { if (skipExisting && d.isDirectory()) return; throw new IOException(MessageFormat.format( JGitText.get().mkDirFailed, d.getAbsolutePath())); } } /** * Creates the directory named by this abstract pathname, including any * necessary but nonexistent parent directories. Note that if this operation * fails it may have succeeded in creating some of the necessary parent * directories. * * @param d * directory to be created * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore * cause java.io.IOExceptions during race conditions when * multiple concurrent threads all try to create the same * directory. */ public static void mkdirs(File d) throws IOException { mkdirs(d, false); } /** * Creates the directory named by this abstract pathname, including any * necessary but nonexistent parent directories. Note that if this operation * fails it may have succeeded in creating some of the necessary parent * directories. * * @param d * directory to be created * @param skipExisting * if {@code true} skip creation of the given directory if it * already exists in the file system * @throws java.io.IOException * if creation of {@code d} fails. This may occur if {@code d} * did exist when the method was called. This can therefore * cause java.io.IOExceptions during race conditions when * multiple concurrent threads all try to create the same * directory. */ public static void mkdirs(File d, boolean skipExisting) throws IOException { if (!d.mkdirs()) { if (skipExisting && d.isDirectory()) return; throw new IOException(MessageFormat.format( JGitText.get().mkDirsFailed, d.getAbsolutePath())); } } /** * Atomically creates a new, empty file named by this abstract pathname if * and only if a file with this name does not yet exist. The check for the * existence of the file and the creation of the file if it does not exist * are a single operation that is atomic with respect to all other * filesystem activities that might affect the file. *

* Note: this method should not be used for file-locking, as the resulting * protocol cannot be made to work reliably. The * {@link java.nio.channels.FileLock} facility should be used instead. * * @param f * the file to be created * @throws java.io.IOException * if the named file already exists or if an I/O error occurred */ public static void createNewFile(File f) throws IOException { if (!f.createNewFile()) throw new IOException(MessageFormat.format( JGitText.get().createNewFileFailed, f)); } /** * Create a symbolic link * * @param path * the path of the symbolic link to create * @param target * the target of the symbolic link * @return the path to the symbolic link * @throws java.io.IOException * if an IO error occurred * @since 4.2 */ public static Path createSymLink(File path, String target) throws IOException { Path nioPath = toPath(path); if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) { BasicFileAttributes attrs = Files.readAttributes(nioPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); if (attrs.isRegularFile() || attrs.isSymbolicLink()) { delete(path); } else { delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE); } } if (SystemReader.getInstance().isWindows()) { target = target.replace('/', '\\'); } Path nioTarget = toPath(new File(target)); return Files.createSymbolicLink(nioPath, nioTarget); } /** * Read target path of the symlink. * * @param path * a {@link java.io.File} object. * @return target path of the symlink, or null if it is not a symbolic link * @throws java.io.IOException * if an IO error occurred * @since 3.0 */ public static String readSymLink(File path) throws IOException { Path nioPath = toPath(path); Path target = Files.readSymbolicLink(nioPath); String targetString = target.toString(); if (SystemReader.getInstance().isWindows()) { targetString = targetString.replace('\\', '/'); } else if (SystemReader.getInstance().isMacOS()) { targetString = Normalizer.normalize(targetString, Form.NFC); } return targetString; } /** * Create a temporary directory. * * @param prefix * prefix string * @param suffix * suffix string * @param dir * The parent dir, can be null to use system default temp dir. * @return the temp dir created. * @throws java.io.IOException * if an IO error occurred * @since 3.4 */ public static File createTempDir(String prefix, String suffix, File dir) throws IOException { final int RETRIES = 1; // When something bad happens, retry once. for (int i = 0; i < RETRIES; i++) { File tmp = File.createTempFile(prefix, suffix, dir); if (!tmp.delete()) continue; if (!tmp.mkdir()) continue; return tmp; } throw new IOException(JGitText.get().cannotCreateTempDir); } /** * Expresses other as a relative file path from * base. File-separator and case sensitivity are based on the * current file system. * * See also * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * Base path * @param other * Destination path * @return Relative path from base to other * @since 4.8 */ public static String relativizeNativePath(String base, String other) { return FS.DETECTED.relativize(base, other); } /** * Expresses other as a relative file path from * base. File-separator and case sensitivity are based on Git's * internal representation of files (which matches Unix). * * See also * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * Base path * @param other * Destination path * @return Relative path from base to other * @since 4.8 */ public static String relativizeGitPath(String base, String other) { return relativizePath(base, other, "/", false); //$NON-NLS-1$ } /** * Expresses other as a relative file path from base *

* For example, if called with the two following paths : * *

	 * base = "c:\\Users\\jdoe\\eclipse\\git\\project"
	 * other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"
	 * 
* * This will return "..\\another_project\\pom.xml". * *

* Note that this will return the empty String if base * and other are equal. *

* * @param base * The path against which other should be * relativized. This will be assumed to denote the path to a * folder and not a file. * @param other * The path that will be made relative to base. * @param dirSeparator * A string that separates components of the path. In practice, this is "/" or "\\". * @param caseSensitive * Whether to consider differently-cased directory names as distinct * @return A relative path that, when resolved against base, * will yield the original other. * @since 4.8 */ public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) { if (base.equals(other)) return ""; //$NON-NLS-1$ final String[] baseSegments = base.split(Pattern.quote(dirSeparator)); final String[] otherSegments = other.split(Pattern .quote(dirSeparator)); int commonPrefix = 0; while (commonPrefix < baseSegments.length && commonPrefix < otherSegments.length) { if (caseSensitive && baseSegments[commonPrefix] .equals(otherSegments[commonPrefix])) commonPrefix++; else if (!caseSensitive && baseSegments[commonPrefix] .equalsIgnoreCase(otherSegments[commonPrefix])) commonPrefix++; else break; } final StringBuilder builder = new StringBuilder(); for (int i = commonPrefix; i < baseSegments.length; i++) builder.append("..").append(dirSeparator); //$NON-NLS-1$ for (int i = commonPrefix; i < otherSegments.length; i++) { builder.append(otherSegments[i]); if (i < otherSegments.length - 1) builder.append(dirSeparator); } return builder.toString(); } /** * Determine if an IOException is a stale NFS file handle * * @param ioe * an {@link java.io.IOException} object. * @return a boolean true if the IOException is a stale NFS file handle * @since 4.1 */ public static boolean isStaleFileHandle(IOException ioe) { String msg = ioe.getMessage(); return msg != null && msg.toLowerCase(Locale.ROOT) .matches("stale .*file .*handle"); //$NON-NLS-1$ } /** * Determine if a throwable or a cause in its causal chain is a stale NFS * file handle * * @param throwable * a {@link java.lang.Throwable} object. * @return a boolean true if the throwable or a cause in its causal chain is * a stale NFS file handle * @since 4.7 */ public static boolean isStaleFileHandleInCausalChain(Throwable throwable) { while (throwable != null) { if (throwable instanceof IOException && isStaleFileHandle((IOException) throwable)) { return true; } throwable = throwable.getCause(); } return false; } /** * Like a {@link java.util.function.Function} but throwing an * {@link Exception}. * * @param * input type * @param * output type * @since 6.2 */ @FunctionalInterface public interface IOFunction { /** * Performs the function. * * @param t * input to operate on * @return the output * @throws Exception * if a problem occurs */ B apply(A t) throws Exception; } private static void backOff(long delay, IOException cause) throws IOException { try { Thread.sleep(delay); } catch (InterruptedException e) { IOException interruption = new InterruptedIOException(); interruption.initCause(e); interruption.addSuppressed(cause); Thread.currentThread().interrupt(); // Re-set flag throw interruption; } } /** * Invokes the given {@link IOFunction}, performing a limited number of * re-tries if exceptions occur that indicate either a stale NFS file handle * or that indicate that the file may be written concurrently. * * @param * result type * @param file * to read * @param reader * for reading the file and creating an instance of {@code T} * @return the result of the {@code reader}, or {@code null} if the file * does not exist * @throws Exception * if a problem occurs * @since 6.2 */ public static T readWithRetries(File file, IOFunction reader) throws Exception { int maxStaleRetries = 5; int retries = 0; long backoff = 50; while (true) { try { try { return reader.apply(file); } catch (IOException e) { if (FileUtils.isStaleFileHandleInCausalChain(e) && retries < maxStaleRetries) { if (LOG.isDebugEnabled()) { LOG.debug(MessageFormat.format( JGitText.get().packedRefsHandleIsStale, Integer.valueOf(retries)), e); } retries++; continue; } throw e; } } catch (FileNotFoundException noFile) { if (!file.isFile()) { return null; } // Probably Windows and some other thread is writing the file // concurrently. if (backoff > 1000) { throw noFile; } backOff(backoff, noFile); backoff *= 2; // 50, 100, 200, 400, 800 ms } } } /** * Check if file is a symlink * * @param file * the file to be checked if it is a symbolic link * @return {@code true} if the passed file is a symbolic link */ static boolean isSymlink(File file) { return Files.isSymbolicLink(file.toPath()); } /** * Get last modified timestamp of a file * * @param path * file path * @return lastModified attribute for given file, not following symbolic * links */ static Instant lastModifiedInstant(Path path) { try { return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS) .toInstant(); } catch (NoSuchFileException e) { LOG.debug( "Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$ path); return Instant.EPOCH; } catch (IOException e) { LOG.error(MessageFormat .format(JGitText.get().readLastModifiedFailed, path), e); return Instant.ofEpochMilli(path.toFile().lastModified()); } } /** * Return all the attributes of a file, without following symbolic links. * * @param file * the file * @return {@link BasicFileAttributes} of the file * @throws IOException * in case of any I/O errors accessing the file * * @since 4.5.6 */ static BasicFileAttributes fileAttributes(File file) throws IOException { return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); } /** * Set the last modified time of a file system object. * * @param path * file path * @param time * last modified timestamp of the file * @throws IOException * if an IO error occurred */ static void setLastModified(Path path, Instant time) throws IOException { Files.setLastModifiedTime(path, FileTime.from(time)); } /** * Whether the file exists * * @param file * the file * @return {@code true} if the given file exists, not following symbolic * links */ static boolean exists(File file) { return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS); } /** * Check if file is hidden (on Windows) * * @param file * the file * @return {@code true} if the given file is hidden * @throws IOException * if an IO error occurred */ static boolean isHidden(File file) throws IOException { return Files.isHidden(toPath(file)); } /** * Set a file hidden (on Windows) * * @param file * a {@link java.io.File} object. * @param hidden * a boolean. * @throws java.io.IOException * if an IO error occurred * @since 4.1 */ public static void setHidden(File file, boolean hidden) throws IOException { Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$ LinkOption.NOFOLLOW_LINKS); } /** * Get file length * * @param file * a {@link java.io.File}. * @return length of the given file * @throws java.io.IOException * if an IO error occurred * @since 4.1 */ public static long getLength(File file) throws IOException { Path nioPath = toPath(file); if (Files.isSymbolicLink(nioPath)) return Files.readSymbolicLink(nioPath).toString() .getBytes(UTF_8).length; return Files.size(nioPath); } /** * Check if file is directory * * @param file * the file * @return {@code true} if the given file is a directory, not following * symbolic links */ static boolean isDirectory(File file) { return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS); } /** * Check if File is a file * * @param file * the file * @return {@code true} if the given file is a file, not following symbolic * links */ static boolean isFile(File file) { return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS); } /** * Whether the path is a directory with files in it. * * @param dir * directory path * @return {@code true} if the given directory path contains files * @throws IOException * on any I/O errors accessing the path * * @since 5.11 */ public static boolean hasFiles(Path dir) throws IOException { try (Stream stream = Files.list(dir)) { return stream.findAny().isPresent(); } } /** * Whether the given file can be executed. * * @param file * a {@link java.io.File} object. * @return {@code true} if the given file can be executed. * @since 4.1 */ public static boolean canExecute(File file) { if (!isFile(file)) { return false; } return Files.isExecutable(file.toPath()); } /** * Get basic file attributes * * @param fs * a {@link org.eclipse.jgit.util.FS} object. * @param file * the file * @return non null attributes object */ static Attributes getFileAttributesBasic(FS fs, File file) { try { Path nioPath = toPath(file); BasicFileAttributes readAttributes = nioPath .getFileSystem() .provider() .getFileAttributeView(nioPath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS).readAttributes(); Attributes attributes = new Attributes(fs, file, true, readAttributes.isDirectory(), fs.supportsExecute() ? file.canExecute() : false, readAttributes.isSymbolicLink(), readAttributes.isRegularFile(), // readAttributes.creationTime().toMillis(), // readAttributes.lastModifiedTime().toInstant(), readAttributes.isSymbolicLink() ? Constants .encode(readSymLink(file)).length : readAttributes.size()); return attributes; } catch (IOException e) { return new Attributes(file, fs); } } /** * Get file system attributes for the given file. * * @param fs * a {@link org.eclipse.jgit.util.FS} object. * @param file * a {@link java.io.File}. * @return file system attributes for the given file. * @since 4.1 */ public static Attributes getFileAttributesPosix(FS fs, File file) { try { Path nioPath = toPath(file); PosixFileAttributes readAttributes = nioPath .getFileSystem() .provider() .getFileAttributeView(nioPath, PosixFileAttributeView.class, LinkOption.NOFOLLOW_LINKS).readAttributes(); Attributes attributes = new Attributes( fs, file, true, // readAttributes.isDirectory(), // readAttributes.permissions().contains( PosixFilePermission.OWNER_EXECUTE), readAttributes.isSymbolicLink(), readAttributes.isRegularFile(), // readAttributes.creationTime().toMillis(), // readAttributes.lastModifiedTime().toInstant(), readAttributes.size()); return attributes; } catch (IOException e) { return new Attributes(file, fs); } } /** * NFC normalize a file (on Mac), otherwise do nothing * * @param file * a {@link java.io.File}. * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed * file * @since 4.1 */ public static File normalize(File file) { if (SystemReader.getInstance().isMacOS()) { // TODO: Would it be faster to check with isNormalized first // assuming normalized paths are much more common String normalized = Normalizer.normalize(file.getPath(), Normalizer.Form.NFC); return new File(normalized); } return file; } /** * On Mac: get NFC normalized form of given name, otherwise the given name. * * @param name * a {@link java.lang.String} object. * @return on Mac: NFC normalized form of given name * @since 4.1 */ public static String normalize(String name) { if (SystemReader.getInstance().isMacOS()) { if (name == null) return null; return Normalizer.normalize(name, Normalizer.Form.NFC); } return name; } /** * Best-effort variation of {@link java.io.File#getCanonicalFile()} * returning the input file if the file cannot be canonicalized instead of * throwing {@link java.io.IOException}. * * @param file * to be canonicalized; may be {@code null} * @return canonicalized file, or the unchanged input file if * canonicalization failed or if {@code file == null} * @throws java.lang.SecurityException * if {@link java.io.File#getCanonicalFile()} throws one * @since 4.2 */ public static File canonicalize(File file) { if (file == null) { return null; } try { return file.getCanonicalFile(); } catch (IOException e) { return file; } } /** * Convert a path to String, replacing separators as necessary. * * @param file * a {@link java.io.File}. * @return file's path as a String * @since 4.10 */ public static String pathToString(File file) { final String path = file.getPath(); if (SystemReader.getInstance().isWindows()) { return path.replace('\\', '/'); } return path; } /** * Touch the given file * * @param f * the file to touch * @throws IOException * if an IO error occurred * @since 5.1.8 */ public static void touch(Path f) throws IOException { try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.SYNC)) { // touch } Files.setLastModifiedTime(f, FileTime.from(Instant.now())); } /** * Compute a delay in a {@code min..max} interval with random jitter. * * @param last * amount of delay waited before the last attempt. This is used * to seed the next delay interval. Should be 0 if there was no * prior delay. * @param min * shortest amount of allowable delay between attempts. * @param max * longest amount of allowable delay between attempts. * @return new amount of delay to wait before the next attempt. * * @since 5.6 */ public static long delay(long last, long min, long max) { long r = Math.max(0, last * 3 - min); if (r > 0) { int c = (int) Math.min(r + 1, Integer.MAX_VALUE); r = RNG.nextInt(c); } return Math.max(Math.min(min + r, max), min); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3447 Content-Disposition: inline; filename="GSSManagerFactory.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "ba0df932ce5c4b2536823ff88fe85c29ff9eb9cf" /* * Copyright (C) 2014 Laurent Goujon 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.util; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; import org.ietf.jgss.GSSManager; /** * Factory to detect which GSSManager implementation should be used. * * @since 3.4 */ public abstract class GSSManagerFactory { /** * Auto-detects the GSSManager factory to use based on system. * * @return detected GSSManager factory */ public static GSSManagerFactory detect() { return SunGSSManagerFactory.isSupported() ? new SunGSSManagerFactory() : new DefaultGSSManagerFactory(); } /** * Returns a GSS Manager instance for the provided url * * @param url * the repository url * @return a GSSManager instance */ public abstract GSSManager newInstance(URL url); /** * DefaultGSSManagerFactory uses @link {@link GSSManager#getInstance()} but * you might need to set * javax.security.auth.useSubjectCredsOnly system property to * false for authentication to work. */ private static class DefaultGSSManagerFactory extends GSSManagerFactory { private static final GSSManager INSTANCE = GSSManager.getInstance(); @Override public GSSManager newInstance(URL url) { return INSTANCE; } } private static class SunGSSManagerFactory extends GSSManagerFactory { private static boolean IS_SUPPORTED; private static Constructor HTTP_CALLER_INFO_CONSTRUCTOR; private static Constructor HTTP_CALLER_CONSTRUCTOR; private static Constructor GSS_MANAGER_IMPL_CONSTRUCTOR; static { try { init(); IS_SUPPORTED = true; } catch (Exception e) { IS_SUPPORTED = false; } } private static void init() throws ClassNotFoundException, NoSuchMethodException { Class httpCallerInfoClazz = Class .forName("sun.net.www.protocol.http.HttpCallerInfo"); //$NON-NLS-1$ HTTP_CALLER_INFO_CONSTRUCTOR = httpCallerInfoClazz .getConstructor(URL.class); Class httpCallerClazz = Class .forName("sun.security.jgss.HttpCaller"); //$NON-NLS-1$ HTTP_CALLER_CONSTRUCTOR = httpCallerClazz .getConstructor(httpCallerInfoClazz); Class gssCallerClazz = Class .forName("sun.security.jgss.GSSCaller"); //$NON-NLS-1$ Class gssManagerImplClazz = Class .forName("sun.security.jgss.GSSManagerImpl"); //$NON-NLS-1$ GSS_MANAGER_IMPL_CONSTRUCTOR = gssManagerImplClazz .getConstructor(gssCallerClazz); } /** * Detects if SunGSSManagerProvider is supported by the system * * @return true if it is supported */ public static boolean isSupported() { return IS_SUPPORTED; } @Override public GSSManager newInstance(URL url) { try { Object httpCallerInfo = HTTP_CALLER_INFO_CONSTRUCTOR .newInstance(url); Object httpCaller = HTTP_CALLER_CONSTRUCTOR .newInstance(httpCallerInfo); return (GSSManager) GSS_MANAGER_IMPL_CONSTRUCTOR .newInstance(httpCaller); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new Error(e); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4299 Content-Disposition: inline; filename="GitDateFormatter.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "332e65985e94e2d2d165c237b13693864b00febb" /* * Copyright (C) 2011, 2012 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.util; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Locale; import org.eclipse.jgit.lib.PersonIdent; /** * A utility for formatting dates according to the Git log.date formats plus * extensions. *

* The enum {@link org.eclipse.jgit.util.GitDateFormatter.Format} defines the * available types. */ public class GitDateFormatter { private DateTimeFormatter dateTimeFormat; private DateTimeFormatter dateTimeFormat2; private final Format format; /** * Git and JGit formats */ public enum Format { /** * Git format: Time and original time zone */ DEFAULT, /** * Git format: Relative time stamp */ RELATIVE, /** * Git format: Date and time in local time zone */ LOCAL, /** * Git format: ISO 8601 plus time zone */ ISO, /** * Git formt: RFC 2822 plus time zone */ RFC, /** * Git format: YYYY-MM-DD */ SHORT, /** * Git format: Seconds size 1970 in UTC plus time zone */ RAW, /** * Locale dependent formatting with original time zone */ LOCALE, /** * Locale dependent formatting in local time zone */ LOCALELOCAL } /** * Create a new Git oriented date formatter * * @param format * a {@link org.eclipse.jgit.util.GitDateFormatter.Format} * object. */ public GitDateFormatter(Format format) { this.format = format; switch (format) { default: break; case DEFAULT: // Not default: dateTimeFormat = DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss yyyy Z", Locale.US); //$NON-NLS-1$ break; case ISO: dateTimeFormat = DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$ Locale.US); break; case LOCAL: dateTimeFormat = DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$ Locale.US); break; case RFC: dateTimeFormat = DateTimeFormatter.ofPattern( "EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); //$NON-NLS-1$ break; case SHORT: dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd", //$NON-NLS-1$ Locale.US); break; case LOCALE: case LOCALELOCAL: dateTimeFormat = DateTimeFormatter .ofLocalizedDateTime(FormatStyle.MEDIUM) .withLocale(Locale.US); dateTimeFormat2 = DateTimeFormatter.ofPattern("Z", //$NON-NLS-1$ Locale.US); break; } } /** * Format committer, author or tagger ident according to this formatter's * specification. * * @param ident * a {@link org.eclipse.jgit.lib.PersonIdent} object. * @return formatted version of date, time and time zone */ @SuppressWarnings("boxing") public String formatDate(PersonIdent ident) { switch (format) { case RAW: { int offset = ident.getZoneOffset().getTotalSeconds(); String sign = offset < 0 ? "-" : "+"; //$NON-NLS-1$ //$NON-NLS-2$ int offset2; if (offset < 0) { offset2 = -offset; } else { offset2 = offset; } int minutes = (offset2 / 60) % 60; int hours = offset2 / 60 / 60; return String.format("%d %s%02d%02d", //$NON-NLS-1$ ident.getWhenAsInstant().getEpochSecond(), sign, hours, minutes); } case RELATIVE: return RelativeDateFormatter.format(ident.getWhenAsInstant()); case LOCALELOCAL: case LOCAL: return dateTimeFormat .withZone(SystemReader.getInstance().getTimeZoneId()) .format(ident.getWhenAsInstant()); case LOCALE: { ZoneId tz = ident.getZoneId(); if (tz == null) { tz = SystemReader.getInstance().getTimeZoneId(); } return dateTimeFormat.withZone(tz).format(ident.getWhenAsInstant()) + " " //$NON-NLS-1$ + dateTimeFormat2.withZone(tz) .format(ident.getWhenAsInstant()); } default: { ZoneId tz = ident.getZoneId(); if (tz == null) { tz = SystemReader.getInstance().getTimeZoneId(); } return dateTimeFormat.withZone(tz).format(ident.getWhenAsInstant()); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10405 Content-Disposition: inline; filename="GitDateParser.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "f080056546d02a5c12402fc1960c131ea4282fd6" /* * Copyright (C) 2012 Christian Halstrick 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.util; import java.text.MessageFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.eclipse.jgit.internal.JGitText; /** * Parses strings with time and date specifications into {@link java.util.Date}. * * When git needs to parse strings specified by the user this parser can be * used. One example is the parsing of the config parameter gc.pruneexpire. The * parser can handle only subset of what native gits approxidate parser * understands. * * @deprecated Use {@link GitTimeParser} instead. */ @Deprecated(since = "7.1") public class GitDateParser { /** * The Date representing never. Though this is a concrete value, most * callers are adviced to avoid depending on the actual value. */ public static final Date NEVER = new Date(Long.MAX_VALUE); // Since SimpleDateFormat instances are expensive to instantiate they should // be cached. Since they are also not threadsafe they are cached using // ThreadLocal. private static ThreadLocal>> formatCache = new ThreadLocal<>() { @Override protected Map> initialValue() { return new HashMap<>(); } }; // Gets an instance of a SimpleDateFormat for the specified locale. If there // is not already an appropriate instance in the (ThreadLocal) cache then // create one and put it into the cache. private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f, Locale locale) { Map> cache = formatCache .get(); Map map = cache .get(locale); if (map == null) { map = new HashMap<>(); cache.put(locale, map); return getNewSimpleDateFormat(f, locale, map); } SimpleDateFormat dateFormat = map.get(f); if (dateFormat != null) return dateFormat; SimpleDateFormat df = getNewSimpleDateFormat(f, locale, map); return df; } private static SimpleDateFormat getNewSimpleDateFormat( ParseableSimpleDateFormat f, Locale locale, Map map) { SimpleDateFormat df = SystemReader.getInstance().getSimpleDateFormat( f.formatStr, locale); map.put(f, df); return df; } // An enum of all those formats which this parser can parse with the help of // a SimpleDateFormat. There are other formats (e.g. the relative formats // like "yesterday" or "1 week ago") which this parser can parse but which // are not listed here because they are parsed without the help of a // SimpleDateFormat. enum ParseableSimpleDateFormat { ISO("yyyy-MM-dd HH:mm:ss Z"), // //$NON-NLS-1$ RFC("EEE, dd MMM yyyy HH:mm:ss Z"), // //$NON-NLS-1$ SHORT("yyyy-MM-dd"), // //$NON-NLS-1$ SHORT_WITH_DOTS_REVERSE("dd.MM.yyyy"), // //$NON-NLS-1$ SHORT_WITH_DOTS("yyyy.MM.dd"), // //$NON-NLS-1$ SHORT_WITH_SLASH("MM/dd/yyyy"), // //$NON-NLS-1$ DEFAULT("EEE MMM dd HH:mm:ss yyyy Z"), // //$NON-NLS-1$ LOCAL("EEE MMM dd HH:mm:ss yyyy"); //$NON-NLS-1$ private final String formatStr; private ParseableSimpleDateFormat(String formatStr) { this.formatStr = formatStr; } } /** * Parses a string into a {@link java.util.Date} using the default locale. * Since this parser also supports relative formats (e.g. "yesterday") the * caller can specify the reference date. These types of strings can be * parsed: *

    *
  • "never"
  • *
  • "now"
  • *
  • "yesterday"
  • *
  • "(x) years|months|weeks|days|hours|minutes|seconds ago"
    * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' * ' one can use '.' to separate the words
  • *
  • "yyyy-MM-dd HH:mm:ss Z" (ISO)
  • *
  • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
  • *
  • "yyyy-MM-dd"
  • *
  • "yyyy.MM.dd"
  • *
  • "MM/dd/yyyy",
  • *
  • "dd.MM.yyyy"
  • *
  • "EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)
  • *
  • "EEE MMM dd HH:mm:ss yyyy" (LOCAL)
  • *
* * @param dateStr * the string to be parsed * @param now * the base date which is used for the calculation of relative * formats. E.g. if baseDate is "25.8.2012" then parsing of the * string "1 week ago" would result in a date corresponding to * "18.8.2012". This is used when a JGit command calls this * parser often but wants a consistent starting point for * calls.
* If set to null then the current time will be used * instead. * @return the parsed {@link java.util.Date} * @throws java.text.ParseException * if the given dateStr was not recognized */ public static Date parse(String dateStr, Calendar now) throws ParseException { return parse(dateStr, now, Locale.getDefault()); } /** * Parses a string into a {@link java.util.Date} using the given locale. * Since this parser also supports relative formats (e.g. "yesterday") the * caller can specify the reference date. These types of strings can be * parsed: *
    *
  • "never"
  • *
  • "now"
  • *
  • "yesterday"
  • *
  • "(x) years|months|weeks|days|hours|minutes|seconds ago"
    * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' * ' one can use '.' to separate the words
  • *
  • "yyyy-MM-dd HH:mm:ss Z" (ISO)
  • *
  • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
  • *
  • "yyyy-MM-dd"
  • *
  • "yyyy.MM.dd"
  • *
  • "MM/dd/yyyy",
  • *
  • "dd.MM.yyyy"
  • *
  • "EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)
  • *
  • "EEE MMM dd HH:mm:ss yyyy" (LOCAL)
  • *
* * @param dateStr * the string to be parsed * @param now * the base date which is used for the calculation of relative * formats. E.g. if baseDate is "25.8.2012" then parsing of the * string "1 week ago" would result in a date corresponding to * "18.8.2012". This is used when a JGit command calls this * parser often but wants a consistent starting point for * calls.
* If set to null then the current time will be used * instead. * @param locale * locale to be used to parse the date string * @return the parsed {@link java.util.Date} * @throws java.text.ParseException * if the given dateStr was not recognized * @since 3.2 */ public static Date parse(String dateStr, Calendar now, Locale locale) throws ParseException { dateStr = dateStr.trim(); Date ret; if ("never".equalsIgnoreCase(dateStr)) //$NON-NLS-1$ return NEVER; ret = parse_relative(dateStr, now); if (ret != null) return ret; for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { try { return parse_simple(dateStr, f, locale); } catch (ParseException e) { // simply proceed with the next parser } } ParseableSimpleDateFormat[] values = ParseableSimpleDateFormat.values(); StringBuilder allFormats = new StringBuilder("\"") //$NON-NLS-1$ .append(values[0].formatStr); for (int i = 1; i < values.length; i++) allFormats.append("\", \"").append(values[i].formatStr); //$NON-NLS-1$ allFormats.append("\""); //$NON-NLS-1$ throw new ParseException(MessageFormat.format( JGitText.get().cannotParseDate, dateStr, allFormats.toString()), 0); } // tries to parse a string with the formats supported by SimpleDateFormat private static Date parse_simple(String dateStr, ParseableSimpleDateFormat f, Locale locale) throws ParseException { SimpleDateFormat dateFormat = getDateFormat(f, locale); dateFormat.setLenient(false); return dateFormat.parse(dateStr); } // tries to parse a string with a relative time specification @SuppressWarnings("nls") private static Date parse_relative(String dateStr, Calendar now) { Calendar cal; SystemReader sysRead = SystemReader.getInstance(); // check for the static words "yesterday" or "now" if ("now".equals(dateStr)) { return ((now == null) ? new Date(sysRead.getCurrentTime()) : now .getTime()); } if (now == null) { cal = new GregorianCalendar(sysRead.getTimeZone(), sysRead.getLocale()); cal.setTimeInMillis(sysRead.getCurrentTime()); } else cal = (Calendar) now.clone(); if ("yesterday".equals(dateStr)) { cal.add(Calendar.DATE, -1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } // parse constructs like "3 days ago", "5.week.2.day.ago" String[] parts = dateStr.split("\\.| "); int partsLength = parts.length; // check we have an odd number of parts (at least 3) and that the last // part is "ago" if (partsLength < 3 || (partsLength & 1) == 0 || !"ago".equals(parts[parts.length - 1])) return null; int number; for (int i = 0; i < parts.length - 2; i += 2) { try { number = Integer.parseInt(parts[i]); } catch (NumberFormatException e) { return null; } if (parts[i + 1] == null){ return null; } switch (parts[i + 1]) { case "year": case "years": cal.add(Calendar.YEAR, -number); break; case "month": case "months": cal.add(Calendar.MONTH, -number); break; case "week": case "weeks": cal.add(Calendar.WEEK_OF_YEAR, -number); break; case "day": case "days": cal.add(Calendar.DATE, -number); break; case "hour": case "hours": cal.add(Calendar.HOUR_OF_DAY, -number); break; case "minute": case "minutes": cal.add(Calendar.MINUTE, -number); break; case "second": case "seconds": cal.add(Calendar.SECOND, -number); break; default: return null; } } return cal.getTime(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7890 Content-Disposition: inline; filename="GitTimeParser.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "acaa1ce563e6f510bc19d9821544211301591895" /* * Copyright (C) 2024 Christian Halstrick 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.util; import java.text.MessageFormat; import java.text.ParseException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.util.EnumMap; import java.util.Map; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; /** * Parses strings with time and date specifications into * {@link java.time.Instant}. * * When git needs to parse strings specified by the user this parser can be * used. One example is the parsing of the config parameter gc.pruneexpire. The * parser can handle only subset of what native gits approxidate parser * understands. * * @since 7.1 */ public class GitTimeParser { private static final Map formatCache = new EnumMap<>( ParseableSimpleDateFormat.class); // An enum of all those formats which this parser can parse with the help of // a DateTimeFormatter. There are other formats (e.g. the relative formats // like "yesterday" or "1 week ago") which this parser can parse but which // are not listed here because they are parsed without the help of a // DateTimeFormatter. enum ParseableSimpleDateFormat { ISO("yyyy-MM-dd HH:mm:ss Z"), // //$NON-NLS-1$ RFC("EEE, dd MMM yyyy HH:mm:ss Z"), // //$NON-NLS-1$ SHORT("yyyy-MM-dd"), // //$NON-NLS-1$ SHORT_WITH_DOTS_REVERSE("dd.MM.yyyy"), // //$NON-NLS-1$ SHORT_WITH_DOTS("yyyy.MM.dd"), // //$NON-NLS-1$ SHORT_WITH_SLASH("MM/dd/yyyy"), // //$NON-NLS-1$ DEFAULT("EEE MMM dd HH:mm:ss yyyy Z"), // //$NON-NLS-1$ LOCAL("EEE MMM dd HH:mm:ss yyyy"); //$NON-NLS-1$ private final String formatStr; ParseableSimpleDateFormat(String formatStr) { this.formatStr = formatStr; } } private GitTimeParser() { // This class is not supposed to be instantiated } /** * Parses a string into a {@link java.time.LocalDateTime} using the default * locale. Since this parser also supports relative formats (e.g. * "yesterday") the caller can specify the reference date. These types of * strings can be parsed: *
    *
  • "never"
  • *
  • "now"
  • *
  • "yesterday"
  • *
  • "(x) years|months|weeks|days|hours|minutes|seconds ago"
    * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' * ' one can use '.' to separate the words
  • *
  • "yyyy-MM-dd HH:mm:ss Z" (ISO)
  • *
  • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
  • *
  • "yyyy-MM-dd"
  • *
  • "yyyy.MM.dd"
  • *
  • "MM/dd/yyyy",
  • *
  • "dd.MM.yyyy"
  • *
  • "EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)
  • *
  • "EEE MMM dd HH:mm:ss yyyy" (LOCAL)
  • *
* * @param dateStr * the string to be parsed * @return the parsed {@link java.time.LocalDateTime} * @throws java.text.ParseException * if the given dateStr was not recognized */ public static LocalDateTime parse(String dateStr) throws ParseException { return parse(dateStr, SystemReader.getInstance().civilNow()); } /** * Parses a string into a {@link java.time.Instant} using the default * locale. Since this parser also supports relative formats (e.g. * "yesterday") the caller can specify the reference date. These types of * strings can be parsed: *
    *
  • "never"
  • *
  • "now"
  • *
  • "yesterday"
  • *
  • "(x) years|months|weeks|days|hours|minutes|seconds ago"
    * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' * ' one can use '.' to separate the words
  • *
  • "yyyy-MM-dd HH:mm:ss Z" (ISO)
  • *
  • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
  • *
  • "yyyy-MM-dd"
  • *
  • "yyyy.MM.dd"
  • *
  • "MM/dd/yyyy",
  • *
  • "dd.MM.yyyy"
  • *
  • "EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)
  • *
  • "EEE MMM dd HH:mm:ss yyyy" (LOCAL)
  • *
* * @param dateStr * the string to be parsed * @return the parsed {@link java.time.Instant} * @throws java.text.ParseException * if the given dateStr was not recognized * @since 7.2 */ public static Instant parseInstant(String dateStr) throws ParseException { return parse(dateStr).atZone(SystemReader.getInstance().getTimeZoneId()) .toInstant(); } // Only tests seem to use this method static LocalDateTime parse(String dateStr, LocalDateTime now) throws ParseException { dateStr = dateStr.trim(); if (dateStr.equalsIgnoreCase("never")) { //$NON-NLS-1$ return LocalDateTime.MAX; } LocalDateTime ret = parseRelative(dateStr, now); if (ret != null) { return ret; } for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { try { return parseSimple(dateStr, f); } catch (DateTimeParseException e) { // simply proceed with the next parser } } ParseableSimpleDateFormat[] values = ParseableSimpleDateFormat.values(); StringBuilder allFormats = new StringBuilder("\"") //$NON-NLS-1$ .append(values[0].formatStr); for (int i = 1; i < values.length; i++) { allFormats.append("\", \"").append(values[i].formatStr); //$NON-NLS-1$ } allFormats.append("\""); //$NON-NLS-1$ throw new ParseException( MessageFormat.format(JGitText.get().cannotParseDate, dateStr, allFormats.toString()), 0); } // tries to parse a string with the formats supported by DateTimeFormatter private static LocalDateTime parseSimple(String dateStr, ParseableSimpleDateFormat f) throws DateTimeParseException { DateTimeFormatter dateFormat = formatCache.computeIfAbsent(f, format -> DateTimeFormatter .ofPattern(f.formatStr) .withLocale(SystemReader.getInstance().getLocale())); TemporalAccessor parsed = dateFormat.parse(dateStr); return parsed.isSupported(ChronoField.HOUR_OF_DAY) ? LocalDateTime.from(parsed) : LocalDate.from(parsed).atStartOfDay(); } // tries to parse a string with a relative time specification @SuppressWarnings("nls") @Nullable private static LocalDateTime parseRelative(String dateStr, LocalDateTime now) { // check for the static words "yesterday" or "now" if (dateStr.equals("now")) { return now; } if (dateStr.equals("yesterday")) { return now.minusDays(1); } // parse constructs like "3 days ago", "5.week.2.day.ago" String[] parts = dateStr.split("\\.| ", -1); int partsLength = parts.length; // check we have an odd number of parts (at least 3) and that the last // part is "ago" if (partsLength < 3 || (partsLength & 1) == 0 || !parts[parts.length - 1].equals("ago")) { return null; } int number; for (int i = 0; i < parts.length - 2; i += 2) { try { number = Integer.parseInt(parts[i]); } catch (NumberFormatException e) { return null; } if (parts[i + 1] == null) { return null; } switch (parts[i + 1]) { case "year": case "years": now = now.minusYears(number); break; case "month": case "months": now = now.minusMonths(number); break; case "week": case "weeks": now = now.minusWeeks(number); break; case "day": case "days": now = now.minusDays(number); break; case "hour": case "hours": now = now.minusHours(number); break; case "minute": case "minutes": now = now.minusMinutes(number); break; case "second": case "seconds": now = now.minusSeconds(number); break; default: return null; } } return now; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1734 Content-Disposition: inline; filename="Hex.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "925159830e21e12e3462927ae7d0f0178a7ddd13" /* * Copyright (C) 2020, Michael Dardis. 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.util; import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; /** * Encodes and decodes to and from hexadecimal notation. * * @since 5.7 */ public final class Hex { private static final char[] HEX = "0123456789abcdef".toCharArray(); //$NON-NLS-1$ /** Defeats instantiation. */ private Hex() { // empty } /** * Decode a hexadecimal string to a byte array. * * Note this method validates that characters in the given string are valid * as digits in a hex string. * * @param s * hexadecimal string * @return decoded array */ public static byte[] decode(String s) { int len = s.length(); byte[] b = new byte[len / 2]; for (int i = 0; i < len; i += 2) { int left = Character.digit(s.charAt(i), 16); int right = Character.digit(s.charAt(i + 1), 16); if (left == -1 || right == -1) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidHexString, s)); } b[i / 2] = (byte) (left << 4 | right); } return b; } /** * Encode a byte array to a hexadecimal string. * * @param b byte array * @return hexadecimal string */ public static String toHexString(byte[] b) { char[] c = new char[b.length * 2]; for (int i = 0; i < b.length; i++) { int v = b[i] & 0xFF; c[i * 2] = HEX[v >>> 4]; c[i * 2 + 1] = HEX[v & 0x0F]; } return new String(c); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2737 Content-Disposition: inline; filename="Holder.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "b4cc6ca33f75f47c92cb49a97c28dcf8024e37d1" /* * Copyright (C) 2015, Ivan Motsch * * 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.util; /** * Holder of an object. * * @param * the type of value held by this {@link org.eclipse.jgit.util.Holder} * @since 4.3 */ public class Holder { private T value; /** *

Constructor for Holder.

* * @param value * is the initial value that is {@link #set(Object)} */ public Holder(T value) { set(value); } /** * Get the value held by this {@link org.eclipse.jgit.util.Holder} * * @return the value held by this {@link org.eclipse.jgit.util.Holder} */ public T get() { return value; } /** * Set a new value held by this {@link org.eclipse.jgit.util.Holder} * * @param value * to be set as new value held by this * {@link org.eclipse.jgit.util.Holder} */ public void set(T value) { this.value = value; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 15144 Content-Disposition: inline; filename="HttpSupport.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "1942342c45af6f254096f7244319590223ba760f" /* * Copyright (C) 2010, Google Inc. * 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.util; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.ConnectException; import java.net.Proxy; import java.net.ProxySelector; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.transport.http.HttpConnection; import org.eclipse.jgit.transport.http.NoCheckX509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Extra utilities to support usage of HTTP. */ public class HttpSupport { private final static Logger LOG = LoggerFactory .getLogger(HttpSupport.class); /** The {@code GET} HTTP method. */ public static final String METHOD_GET = "GET"; //$NON-NLS-1$ /** The {@code HEAD} HTTP method. * @since 4.3 */ public static final String METHOD_HEAD = "HEAD"; //$NON-NLS-1$ /** The {@code POST} HTTP method. * @since 4.3 */ public static final String METHOD_PUT = "PUT"; //$NON-NLS-1$ /** The {@code POST} HTTP method. */ public static final String METHOD_POST = "POST"; //$NON-NLS-1$ /** The {@code Cache-Control} header. */ public static final String HDR_CACHE_CONTROL = "Cache-Control"; //$NON-NLS-1$ /** The {@code Pragma} header. */ public static final String HDR_PRAGMA = "Pragma"; //$NON-NLS-1$ /** The {@code User-Agent} header. */ public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$ /** * The {@code Server} header. * @since 4.0 */ public static final String HDR_SERVER = "Server"; //$NON-NLS-1$ /** The {@code Date} header. */ public static final String HDR_DATE = "Date"; //$NON-NLS-1$ /** The {@code Expires} header. */ public static final String HDR_EXPIRES = "Expires"; //$NON-NLS-1$ /** The {@code ETag} header. */ public static final String HDR_ETAG = "ETag"; //$NON-NLS-1$ /** The {@code If-None-Match} header. */ public static final String HDR_IF_NONE_MATCH = "If-None-Match"; //$NON-NLS-1$ /** The {@code Last-Modified} header. */ public static final String HDR_LAST_MODIFIED = "Last-Modified"; //$NON-NLS-1$ /** The {@code If-Modified-Since} header. */ public static final String HDR_IF_MODIFIED_SINCE = "If-Modified-Since"; //$NON-NLS-1$ /** The {@code Accept} header. */ public static final String HDR_ACCEPT = "Accept"; //$NON-NLS-1$ /** The {@code Content-Type} header. */ public static final String HDR_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$ /** The {@code Content-Length} header. */ public static final String HDR_CONTENT_LENGTH = "Content-Length"; //$NON-NLS-1$ /** The {@code Content-Encoding} header. */ public static final String HDR_CONTENT_ENCODING = "Content-Encoding"; //$NON-NLS-1$ /** The {@code Content-Range} header. */ public static final String HDR_CONTENT_RANGE = "Content-Range"; //$NON-NLS-1$ /** The {@code Accept-Ranges} header. */ public static final String HDR_ACCEPT_RANGES = "Accept-Ranges"; //$NON-NLS-1$ /** The {@code If-Range} header. */ public static final String HDR_IF_RANGE = "If-Range"; //$NON-NLS-1$ /** The {@code Range} header. */ public static final String HDR_RANGE = "Range"; //$NON-NLS-1$ /** The {@code Accept-Encoding} header. */ public static final String HDR_ACCEPT_ENCODING = "Accept-Encoding"; //$NON-NLS-1$ /** * The {@code Location} header. * @since 4.7 */ public static final String HDR_LOCATION = "Location"; //$NON-NLS-1$ /** The {@code gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. */ public static final String ENCODING_GZIP = "gzip"; //$NON-NLS-1$ /** * The {@code x-gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. * @since 4.6 */ public static final String ENCODING_X_GZIP = "x-gzip"; //$NON-NLS-1$ /** The standard {@code text/plain} MIME type. */ public static final String TEXT_PLAIN = "text/plain"; //$NON-NLS-1$ /** The {@code Authorization} header. */ public static final String HDR_AUTHORIZATION = "Authorization"; //$NON-NLS-1$ /** The {@code WWW-Authenticate} header. */ public static final String HDR_WWW_AUTHENTICATE = "WWW-Authenticate"; //$NON-NLS-1$ /** * The {@code Cookie} header. * * @since 5.4 */ public static final String HDR_COOKIE = "Cookie"; //$NON-NLS-1$ /** * The {@code Set-Cookie} header. * * @since 5.4 */ public static final String HDR_SET_COOKIE = "Set-Cookie"; //$NON-NLS-1$ /** * The {@code Set-Cookie2} header. * * @since 5.4 */ public static final String HDR_SET_COOKIE2 = "Set-Cookie2"; //$NON-NLS-1$ private static Set configuredHttpsProtocols; /** * URL encode a value string into an output buffer. * * @param urlstr * the output buffer. * @param key * value which must be encoded to protected special characters. */ public static void encode(StringBuilder urlstr, String key) { if (key == null || key.length() == 0) return; try { urlstr.append(URLEncoder.encode(key, UTF_8.name())); } catch (UnsupportedEncodingException e) { throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e); } } /** * Translates the provided URL into application/x-www-form-urlencoded * format. * * @param url * The URL to translate. * @param keepPathSlash * Whether or not to keep "/" in the URL (i.e. don't translate * them to "%2F"). * * @return The translated URL. * @since 5.13.1 */ public static String urlEncode(String url, boolean keepPathSlash) { String encoded; try { encoded = URLEncoder.encode(url, UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e); } if (keepPathSlash) { encoded = encoded.replace("%2F", "/"); //$NON-NLS-1$ //$NON-NLS-2$ } return encoded; } /** * Get the HTTP response code from the request. *

* Roughly the same as c.getResponseCode() but the * ConnectException is translated to be more understandable. * * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See * {@link org.eclipse.jgit.transport.http.HttpConnection} for other * defined constants. * @throws java.io.IOException * communications error prevented obtaining the response code. * @since 3.3 */ public static int response(HttpConnection c) throws IOException { try { return c.getResponseCode(); } catch (ConnectException ce) { final URL url = c.getURL(); final String host = (url == null) ? "" : url.getHost(); //$NON-NLS-1$ // The standard J2SE error message is not very useful. // if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$ throw new ConnectException(MessageFormat.format(JGitText.get().connectionTimeOut, host)); throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$ } } /** * Get the HTTP response code from the request. *

* Roughly the same as c.getResponseCode() but the * ConnectException is translated to be more understandable. * * @param c * connection the code should be obtained from. * @return r HTTP status code, usually 200 to indicate success. See * {@link org.eclipse.jgit.transport.http.HttpConnection} for other * defined constants. * @throws java.io.IOException * communications error prevented obtaining the response code. */ public static int response(java.net.HttpURLConnection c) throws IOException { try { return c.getResponseCode(); } catch (ConnectException ce) { final URL url = c.getURL(); final String host = (url == null) ? "" : url.getHost(); //$NON-NLS-1$ // The standard J2SE error message is not very useful. // if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$ throw new ConnectException(MessageFormat.format( JGitText.get().connectionTimeOut, host)); throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$ } } /** * Extract a HTTP header from the response. * * @param c * connection the header should be obtained from. * @param headerName * the header name * @return the header value * @throws java.io.IOException * communications error prevented obtaining the header. * @since 4.7 */ public static String responseHeader(final HttpConnection c, final String headerName) throws IOException { return c.getHeaderField(headerName); } /** * Determine the proxy server (if any) needed to obtain a URL. * * @param proxySelector * proxy support for the caller. * @param u * location of the server caller wants to talk to. * @return proxy to communicate with the supplied URL. * @throws java.net.ConnectException * the proxy could not be computed as the supplied URL could not * be read. This failure should never occur. */ public static Proxy proxyFor(ProxySelector proxySelector, URL u) throws ConnectException { try { URI uri = new URI(u.getProtocol(), null, u.getHost(), u.getPort(), null, null, null); return proxySelector.select(uri).get(0); } catch (URISyntaxException e) { final ConnectException err; err = new ConnectException(MessageFormat.format(JGitText.get().cannotDetermineProxyFor, u)); err.initCause(e); throw err; } } /** * Disable SSL and hostname verification for given HTTP connection * * @param conn * a {@link org.eclipse.jgit.transport.http.HttpConnection} * object. * @throws java.io.IOException * if an IO error occurred * @since 4.3 */ public static void disableSslVerify(HttpConnection conn) throws IOException { TrustManager[] trustAllCerts = { new NoCheckX509TrustManager() }; try { conn.configure(null, trustAllCerts, null); conn.setHostnameVerifier((name, session) -> true); } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new IOException(e.getMessage(), e); } } /** * Enables all supported TLS protocol versions on the socket given. If * system property "https.protocols" is set, only protocols specified there * are enabled. *

* This is primarily a mechanism to deal with using TLS on IBM JDK. IBM JDK * returns sockets that support all TLS protocol versions but have only the * one specified in the context enabled. Oracle or OpenJDK return sockets * that have all available protocols enabled already, up to the one * specified. *

* The input stream's position is moved forward by the number of requested * bytes, discarding them from the input. This method does not return until * the exact number of bytes requested has been skipped. * * @param fd * the stream to skip bytes from. * @param toSkip * total number of bytes to be discarded. Must be >= 0. * @throws EOFException * the stream ended before the requested number of bytes were * skipped. * @throws java.io.IOException * there was an error reading from the stream. */ public static void skipFully(InputStream fd, long toSkip) throws IOException { // same as fd.skipNBytes(toSkip) of JDK 12; while (toSkip > 0) { final long r = fd.skip(toSkip); if (r <= 0) throw new EOFException(JGitText.get().shortSkipOfBlock); toSkip -= r; } } /** * Divides the given string into lines. * * @param s * the string to read * @return the string divided into lines * @since 2.0 */ public static List readLines(String s) { List l = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '\n') { l.add(sb.toString()); sb.setLength(0); continue; } if (c == '\r') { if (i + 1 < s.length()) { c = s.charAt(++i); l.add(sb.toString()); sb.setLength(0); if (c != '\n') { sb.append(c); } continue; } // EOF l.add(sb.toString()); break; } sb.append(c); } l.add(sb.toString()); return l; } /** * Read the next line from a reader. *

* Like {@link java.io.BufferedReader#readLine()}, but only treats * {@code \n} as end-of-line, and includes the trailing newline. * * @param in * the reader to read from. * @param sizeHint * hint for buffer sizing; 0 or negative for default. * @return the next line from the input, always ending in {@code \n} unless * EOF was reached. * @throws java.io.IOException * there was an error reading from the stream. * @since 4.1 */ public static String readLine(Reader in, int sizeHint) throws IOException { if (in.markSupported()) { if (sizeHint <= 0) { sizeHint = 1024; } StringBuilder sb = new StringBuilder(sizeHint); char[] buf = new char[sizeHint]; while (true) { in.mark(sizeHint); int n = in.read(buf); if (n < 0) { in.reset(); return sb.toString(); } for (int i = 0; i < n; i++) { if (buf[i] == '\n') { resetAndSkipFully(in, ++i); sb.append(buf, 0, i); return sb.toString(); } } if (n > 0) { sb.append(buf, 0, n); } resetAndSkipFully(in, n); } } StringBuilder buf = sizeHint > 0 ? new StringBuilder(sizeHint) : new StringBuilder(); int i; while ((i = in.read()) != -1) { char c = (char) i; buf.append(c); if (c == '\n') { break; } } return buf.toString(); } private static void resetAndSkipFully(Reader fd, long toSkip) throws IOException { fd.reset(); while (toSkip > 0) { long r = fd.skip(toSkip); if (r <= 0) { throw new EOFException(JGitText.get().shortSkipOfBlock); } toSkip -= r; } } private IO() { // Don't create instances of a static only utility. } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5724 Content-Disposition: inline; filename="IntList.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "6a5190c6a2f713c55c94dd2d4dc7f6ea978f3217" /* * Copyright (C) 2008, Google Inc. * Copyright (C) 2009, Johannes Schindelin 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.util; /** * A more efficient List<Integer> using a primitive integer array. */ public class IntList { private int[] entries; private int count; /** * Create an empty list with a default capacity. */ public IntList() { this(10); } /** * Create an empty list with the specified capacity. * * @param capacity * number of entries the list can initially hold. */ public IntList(int capacity) { entries = new int[capacity]; } /** * Create a list initialized with the values of the given range. * * @param start * the beginning of the range, inclusive * @param end * the end of the range, exclusive * @return the list initialized with the given range * @since 6.6 */ public static IntList filledWithRange(int start, int end) { IntList list = new IntList(end - start); for (int val = start; val < end; val++) { list.add(val); } return list; } /** * Get number of entries in this list. * * @return number of entries in this list. */ public int size() { return count; } /** * Check if an entry appears in this collection. * * @param value * the value to search for. * @return true of {@code value} appears in this list. * @since 4.9 */ public boolean contains(int value) { for (int i = 0; i < count; i++) if (entries[i] == value) return true; return false; } /** * Get the value at the specified index * * @param i * index to read, must be in the range [0, {@link #size()}). * @return the number at the specified index * @throws java.lang.ArrayIndexOutOfBoundsException * the index outside the valid range */ public int get(int i) { if (count <= i) throw new ArrayIndexOutOfBoundsException(i); return entries[i]; } /** * Empty this list */ public void clear() { count = 0; } /** * Add an entry to the end of the list. * * @param n * the number to add. */ public void add(int n) { if (count == entries.length) grow(); entries[count++] = n; } /** * Assign an entry in the list. * * @param index * index to set, must be in the range [0, {@link #size()}). * @param n * value to store at the position. */ public void set(int index, int n) { if (count < index) throw new ArrayIndexOutOfBoundsException(index); else if (count == index) add(n); else entries[index] = n; } /** * Pad the list with entries. * * @param toIndex * index position to stop filling at. 0 inserts no filler. 1 * ensures the list has a size of 1, adding val if * the list is currently empty. * @param val * value to insert into padded positions. */ public void fillTo(int toIndex, int val) { while (count < toIndex) add(val); } /** * Sort the entries of the list in-place, according to the comparator. * * @param comparator * provides the comparison values for sorting the entries * @since 6.6 */ public void sort(IntComparator comparator) { quickSort(0, count - 1, comparator); } /** * Quick sort has average time complexity of O(n log n) and O(log n) space * complexity (for recursion on the stack). *

* Implementation based on https://www.baeldung.com/java-quicksort. * * @param begin * the index to begin partitioning at, inclusive * @param end * the index to end partitioning at, inclusive * @param comparator * provides the comparison values for sorting the entries */ private void quickSort(int begin, int end, IntComparator comparator) { if (begin < end) { int partitionIndex = partition(begin, end, comparator); quickSort(begin, partitionIndex - 1, comparator); quickSort(partitionIndex + 1, end, comparator); } } private int partition(int begin, int end, IntComparator comparator) { int pivot = entries[end]; int writeSmallerIdx = (begin - 1); for (int findSmallerIdx = begin; findSmallerIdx < end; findSmallerIdx++) { if (comparator.compare(entries[findSmallerIdx], pivot) <= 0) { writeSmallerIdx++; int biggerVal = entries[writeSmallerIdx]; entries[writeSmallerIdx] = entries[findSmallerIdx]; entries[findSmallerIdx] = biggerVal; } } int pivotIdx = writeSmallerIdx + 1; entries[end] = entries[pivotIdx]; entries[pivotIdx] = pivot; return pivotIdx; } private void grow() { final int[] n = new int[(entries.length + 16) * 3 / 2]; System.arraycopy(entries, 0, n, 0, count); entries = n; } @Override public String toString() { final StringBuilder r = new StringBuilder(); r.append('['); for (int i = 0; i < count; i++) { if (i > 0) r.append(", "); //$NON-NLS-1$ r.append(entries[i]); } r.append(']'); return r.toString(); } /** * A comparator of primitive ints. * * @since 6.6 */ public interface IntComparator { /** * Compares the two int arguments for order. * * @param first * the first int to compare * @param second * the second int to compare * @return a negative number if first < second, 0 if first == second, or * a positive number if first > second */ int compare(int first, int second); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1338 Content-Disposition: inline; filename="LRUMap.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "14f7f1f181cedee19e16ee944397c422c60c3f52" /* * Copyright (C) 2018, Konrad Windszus 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.util; import java.util.LinkedHashMap; /** * Map with only up to n entries. If a new entry is added so that the map * contains more than those n entries the least-recently used entry is removed * from the map. * * @param * the type of keys maintained by this map * @param * the type of mapped values * * @since 5.4 */ public class LRUMap extends LinkedHashMap { private static final long serialVersionUID = 4329609127403759486L; private final int limit; /** * Constructs an empty map which may contain at most the given amount of * entries. * * @param initialCapacity * the initial capacity * @param limit * the number of entries the map should have at most */ public LRUMap(int initialCapacity, int limit) { super(initialCapacity, 0.75f, true); this.limit = limit; } @Override protected boolean removeEldestEntry(java.util.Map.Entry eldest) { return size() > limit; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8627 Content-Disposition: inline; filename="LfsFactory.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "7b7c1d0886a6a21ced166a018737d4a9e4020a15" /* * Copyright (C) 2018, Markus Duft 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.util; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.text.MessageFormat; import java.util.concurrent.Callable; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.attributes.Attribute; import org.eclipse.jgit.attributes.Attributes; import org.eclipse.jgit.hooks.PrePushHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; /** * Represents an optionally present LFS support implementation * * @since 4.11 */ public class LfsFactory { private static LfsFactory instance = new LfsFactory(); /** * Constructor */ protected LfsFactory() { } /** * Get the LFS factory instance * * @return the current LFS implementation */ public static LfsFactory getInstance() { return instance; } /** * Set the LFS factory instance * * @param instance * register a {@link LfsFactory} instance as the * {@link LfsFactory} implementation to use. */ public static void setInstance(LfsFactory instance) { LfsFactory.instance = instance; } /** * Whether LFS support is available * * @return whether LFS support is available */ public boolean isAvailable() { return false; } /** * Apply clean filtering to the given stream, writing the file content to * the LFS storage if required and returning a stream to the LFS pointer * instead. * * @param db * the repository * @param input * the original input * @param length * the expected input stream length * @param attribute * the attribute used to check for LFS enablement (i.e. "merge", * "diff", "filter" from .gitattributes). * @return a stream to the content that should be written to the object * store along with the expected length of the stream. the original * stream is not applicable. * @throws IOException * in case of an error */ public LfsInputStream applyCleanFilter(Repository db, InputStream input, long length, Attribute attribute) throws IOException { return new LfsInputStream(input, length); } /** * Apply smudge filtering to a given loader, potentially redirecting it to a * LFS blob which is downloaded on demand. * * @param db * the repository * @param loader * the loader for the blob * @param attribute * the attribute used to check for LFS enablement (i.e. "merge", * "diff", "filter" from .gitattributes). * @return a loader for the actual data of a blob, or the original loader in * case LFS is not applicable. * @throws IOException * if an IO error occurred */ public ObjectLoader applySmudgeFilter(Repository db, ObjectLoader loader, Attribute attribute) throws IOException { return loader; } /** * Retrieve a pre-push hook to be applied using the default error stream. * * @param repo * the {@link Repository} the hook is applied to. * @param outputStream * output stream * @return a {@link PrePushHook} implementation or null */ @Nullable public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream) { return null; } /** * Retrieve a pre-push hook to be applied. * * @param repo * the {@link Repository} the hook is applied to. * @param outputStream * output stream * @param errorStream * error stream * @return a {@link PrePushHook} implementation or null * @since 5.6 */ @Nullable public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream, PrintStream errorStream) { return getPrePushHook(repo, outputStream); } /** * Retrieve an {@link LfsInstallCommand} which can be used to enable LFS * support (if available) either per repository or for the user. * * @return a command to install LFS support. */ @Nullable public LfsInstallCommand getInstallCommand() { return null; } /** * Whether LFS is enabled * * @param db * the repository to check * @return whether LFS is enabled for the given repository locally or * globally. */ public boolean isEnabled(Repository db) { return false; } /** * Get git attributes for given path * * @param db * the repository * @param path * the path to find attributes for * @return the {@link Attributes} for the given path. * @throws IOException * in case of an error */ public static Attributes getAttributesForPath(Repository db, String path) throws IOException { try (TreeWalk walk = new TreeWalk(db)) { walk.addTree(new FileTreeIterator(db)); PathFilter f = PathFilter.create(path); walk.setFilter(f); walk.setRecursive(false); Attributes attr = null; while (walk.next()) { if (f.isDone(walk)) { attr = walk.getAttributes(); break; } else if (walk.isSubtree()) { walk.enterSubtree(); } } if (attr == null) { throw new IOException(MessageFormat .format(JGitText.get().noPathAttributesFound, path)); } return attr; } } /** * Get attributes for given path and commit * * @param db * the repository * @param path * the path to find attributes for * @param commit * the commit to inspect. * @return the {@link Attributes} for the given path. * @throws IOException * in case of an error */ public static Attributes getAttributesForPath(Repository db, String path, RevCommit commit) throws IOException { if (commit == null) { return getAttributesForPath(db, path); } try (TreeWalk walk = TreeWalk.forPath(db, path, commit.getTree())) { Attributes attr = walk == null ? null : walk.getAttributes(); if (attr == null) { throw new IOException(MessageFormat .format(JGitText.get().noPathAttributesFound, path)); } return attr; } } /** * Encapsulate a potentially exchanged {@link InputStream} along with the * expected stream content length. */ public static final class LfsInputStream extends InputStream { /** * The actual stream. */ private InputStream stream; /** * The expected stream content length. */ private long length; /** * Create a new wrapper around a certain stream * * @param stream * the stream to wrap. the stream will be closed on * {@link #close()}. * @param length * the expected length of the stream */ public LfsInputStream(InputStream stream, long length) { this.stream = stream; this.length = length; } /** * Create a new wrapper around a temporary buffer. * * @param buffer * the buffer to initialize stream and length from. The * buffer will be destroyed on {@link #close()} * @throws IOException * in case of an error opening the stream to the buffer. */ public LfsInputStream(TemporaryBuffer buffer) throws IOException { this.stream = buffer.openInputStreamWithAutoDestroy(); this.length = buffer.length(); } @Override public void close() throws IOException { stream.close(); } @Override public int read() throws IOException { return stream.read(); } @Override public int read(byte[] b, int off, int len) throws IOException { return stream.read(b, off, len); } /** * Get stream length * * @return the length of the stream */ public long getLength() { return length; } } /** * A command to enable LFS. Optionally set a {@link Repository} to enable * locally on the repository only. */ public interface LfsInstallCommand extends Callable { /** * Set the repository to enable LFS for * * @param repo * the repository to enable support for. * @return The {@link LfsInstallCommand} for chaining. */ public LfsInstallCommand setRepository(Repository repo); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3365 Content-Disposition: inline; filename="LongList.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "47f38f4627b1f2cc98c3ee05188c6bf2289100de" /* * Copyright (C) 2009, Christian Halstrick * Copyright (C) 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.util; import java.util.Arrays; /** * A more efficient List<Long> using a primitive long array. */ public class LongList { private long[] entries; private int count; /** * Create an empty list with a default capacity. */ public LongList() { this(10); } /** * Create an empty list with the specified capacity. * * @param capacity * number of entries the list can initially hold. */ public LongList(int capacity) { entries = new long[capacity]; } /** * Get number of entries in this list * * @return number of entries in this list */ public int size() { return count; } /** * Get the value at the specified index * * @param i * index to read, must be in the range [0, {@link #size()}). * @return the number at the specified index * @throws java.lang.ArrayIndexOutOfBoundsException * the index outside the valid range */ public long get(int i) { if (count <= i) throw new ArrayIndexOutOfBoundsException(i); return entries[i]; } /** * Determine if an entry appears in this collection. * * @param value * the value to search for. * @return true of {@code value} appears in this list. */ public boolean contains(long value) { for (int i = 0; i < count; i++) if (entries[i] == value) return true; return false; } /** * Clear this list */ public void clear() { count = 0; } /** * Add an entry to the end of the list. * * @param n * the number to add. */ public void add(long n) { if (count == entries.length) grow(); entries[count++] = n; } /** * Assign an entry in the list. * * @param index * index to set, must be in the range [0, {@link #size()}). * @param n * value to store at the position. */ public void set(int index, long n) { if (count < index) throw new ArrayIndexOutOfBoundsException(index); else if (count == index) add(n); else entries[index] = n; } /** * Pad the list with entries. * * @param toIndex * index position to stop filling at. 0 inserts no filler. 1 * ensures the list has a size of 1, adding val if * the list is currently empty. * @param val * value to insert into padded positions. */ public void fillTo(int toIndex, long val) { while (count < toIndex) add(val); } /** * Sort the list of longs according to their natural ordering. */ public void sort() { Arrays.sort(entries, 0, count); } private void grow() { final long[] n = new long[(entries.length + 16) * 3 / 2]; System.arraycopy(entries, 0, n, 0, count); entries = n; } @Override public String toString() { final StringBuilder r = new StringBuilder(); r.append('['); for (int i = 0; i < count; i++) { if (i > 0) r.append(", "); //$NON-NLS-1$ r.append(entries[i]); } r.append(']'); return r.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3161 Content-Disposition: inline; filename="LongMap.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "31211e243ab76471858a7ab02aa71be86cd185b0" /* * Copyright (C) 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.util; /** * Simple Map<long, Object>. * * @param * type of the value instance. * @since 4.9 */ public class LongMap { private static final float LOAD_FACTOR = 0.75f; private Node[] table; /** Number of entries currently in the map. */ private int size; /** Next {@link #size} to trigger a {@link #grow()}. */ private int growAt; /** * Initialize an empty LongMap. */ public LongMap() { table = createArray(64); growAt = (int) (table.length * LOAD_FACTOR); } /** * Whether {@code key} is present in the map. * * @param key * the key to find. * @return {@code true} if {@code key} is present in the map. */ public boolean containsKey(long key) { return get(key) != null; } /** * Get value for this {@code key} * * @param key * the key to find. * @return stored value for this key, or {@code null}. */ public V get(long key) { for (Node n = table[index(key)]; n != null; n = n.next) { if (n.key == key) return n.value; } return null; } /** * Remove an entry from the map * * @param key * key to remove from the map. * @return old value of the key, or {@code null}. */ public V remove(long key) { Node n = table[index(key)]; Node prior = null; while (n != null) { if (n.key == key) { if (prior == null) table[index(key)] = n.next; else prior.next = n.next; size--; return n.value; } prior = n; n = n.next; } return null; } /** * Put a new entry into the map * * @param key * key to store {@code value} under. * @param value * new value. * @return prior value, or null. */ public V put(long key, V value) { for (Node n = table[index(key)]; n != null; n = n.next) { if (n.key == key) { final V o = n.value; n.value = value; return o; } } if (++size == growAt) grow(); insert(new Node<>(key, value)); return null; } private void insert(Node n) { final int idx = index(n.key); n.next = table[idx]; table[idx] = n; } private void grow() { final Node[] oldTable = table; final int oldSize = table.length; table = createArray(oldSize << 1); growAt = (int) (table.length * LOAD_FACTOR); for (int i = 0; i < oldSize; i++) { Node e = oldTable[i]; while (e != null) { final Node n = e.next; insert(e); e = n; } } } private final int index(long key) { int h = ((int) key) >>> 1; h ^= (h >>> 20) ^ (h >>> 12); return h & (table.length - 1); } @SuppressWarnings("unchecked") private static final Node[] createArray(int sz) { return new Node[sz]; } private static class Node { final long key; V value; Node next; Node(long k, V v) { key = k; value = v; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2786 Content-Disposition: inline; filename="Monitoring.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "500c23673014492e0beef95baa812f16d2cee013" /* * Copyright (c) 2019 Matthias Sohn * * 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.util; import java.io.IOException; import java.lang.management.ManagementFactory; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectInstance; import javax.management.ObjectName; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.ConfigConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Enables monitoring JGit via JMX * * @since 5.1.13 */ public class Monitoring { private static final Logger LOG = LoggerFactory.getLogger(Monitoring.class); /** * Register a MBean with the platform MBean server * * @param mbean * the mbean object to register * @param metricName * name of the JGit metric, will be prefixed with * "org.eclipse.jgit/" * @return the registered mbean's object instance */ public static @Nullable ObjectInstance registerMBean(Object mbean, String metricName) { boolean register = false; try { Class[] interfaces = mbean.getClass().getInterfaces(); for (Class i : interfaces) { register = SystemReader.getInstance().getUserConfig() .getBoolean( ConfigConstants.CONFIG_JMX_SECTION, i.getSimpleName(), false); if (register) { break; } } } catch (IOException | ConfigInvalidException e) { LOG.error(e.getMessage(), e); return null; } if (!register) { return null; } MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { ObjectName mbeanName = objectName(mbean.getClass(), metricName); if (server.isRegistered(mbeanName)) { server.unregisterMBean(mbeanName); } return server.registerMBean(mbean, mbeanName); } catch (MalformedObjectNameException | InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | InstanceNotFoundException e) { LOG.error(e.getMessage(), e); return null; } } private static ObjectName objectName(Class mbean, String metricName) throws MalformedObjectNameException { return new ObjectName(String.format("org.eclipse.jgit/%s:type=%s", //$NON-NLS-1$ metricName, mbean.getSimpleName())); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 512 Content-Disposition: inline; filename="MutableInteger.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "ebf3aa7a17ae828566d5ea71d8fcaa9e0bf7233a" /* * 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.util; /** * A boxed integer that can be modified. */ public final class MutableInteger { /** Current value of this boxed value. */ public int value; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8657 Content-Disposition: inline; filename="NB.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "fea7172d840289f4d7013e6f7de856009aadb8ef" /* * Copyright (C) 2008, 2015 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.util; /** * Conversion utilities for network byte order handling. */ public final class NB { /** * Compare a 32 bit unsigned integer stored in a 32 bit signed integer. *

* This function performs an unsigned compare operation, even though Java * does not natively support unsigned integer values. Negative numbers are * treated as larger than positive ones. * * @param a * the first value to compare. * @param b * the second value to compare. * @return < 0 if a < b; 0 if a == b; > 0 if a > b. */ public static int compareUInt32(final int a, final int b) { final int cmp = (a >>> 1) - (b >>> 1); if (cmp != 0) return cmp; return (a & 1) - (b & 1); } /** * Compare a 64 bit unsigned integer stored in a 64 bit signed integer. *

* This function performs an unsigned compare operation, even though Java * does not natively support unsigned integer values. Negative numbers are * treated as larger than positive ones. * * @param a * the first value to compare. * @param b * the second value to compare. * @return < 0 if a < b; 0 if a == b; > 0 if a > b. * @since 4.3 */ public static int compareUInt64(final long a, final long b) { long cmp = (a >>> 1) - (b >>> 1); if (cmp > 0) { return 1; } else if (cmp < 0) { return -1; } cmp = ((a & 1) - (b & 1)); if (cmp > 0) { return 1; } else if (cmp < 0) { return -1; } else { return 0; } } /** * Convert sequence of 2 bytes (network byte order) into unsigned value. * * @param intbuf * buffer to acquire the 2 bytes of data from. * @param offset * position within the buffer to begin reading from. This * position and the next byte after it (for a total of 2 bytes) * will be read. * @return unsigned integer value that matches the 16 bits read. */ public static int decodeUInt16(final byte[] intbuf, final int offset) { int r = (intbuf[offset] & 0xff) << 8; return r | (intbuf[offset + 1] & 0xff); } /** * Convert sequence of 3 bytes (network byte order) into unsigned value. * * @param intbuf * buffer to acquire the 3 bytes of data from. * @param offset * position within the buffer to begin reading from. This * position and the next 2 bytes after it (for a total of 3 * bytes) will be read. * @return signed integer value that matches the 24 bits read. * @since 4.9 */ public static int decodeUInt24(byte[] intbuf, int offset) { int r = (intbuf[offset] & 0xff) << 8; r |= intbuf[offset + 1] & 0xff; return (r << 8) | (intbuf[offset + 2] & 0xff); } /** * Convert sequence of 4 bytes (network byte order) into signed value. * * @param intbuf * buffer to acquire the 4 bytes of data from. * @param offset * position within the buffer to begin reading from. This * position and the next 3 bytes after it (for a total of 4 * bytes) will be read. * @return signed integer value that matches the 32 bits read. */ public static int decodeInt32(final byte[] intbuf, final int offset) { int r = intbuf[offset] << 8; r |= intbuf[offset + 1] & 0xff; r <<= 8; r |= intbuf[offset + 2] & 0xff; return (r << 8) | (intbuf[offset + 3] & 0xff); } /** * Convert sequence of 8 bytes (network byte order) into signed value. * * @param intbuf * buffer to acquire the 8 bytes of data from. * @param offset * position within the buffer to begin reading from. This * position and the next 7 bytes after it (for a total of 8 * bytes) will be read. * @return signed integer value that matches the 64 bits read. * @since 3.0 */ public static long decodeInt64(final byte[] intbuf, final int offset) { long r = (long) intbuf[offset] << 8; r |= intbuf[offset + 1] & 0xff; r <<= 8; r |= intbuf[offset + 2] & 0xff; r <<= 8; r |= intbuf[offset + 3] & 0xff; r <<= 8; r |= intbuf[offset + 4] & 0xff; r <<= 8; r |= intbuf[offset + 5] & 0xff; r <<= 8; r |= intbuf[offset + 6] & 0xff; return (r << 8) | (intbuf[offset + 7] & 0xff); } /** * Convert sequence of 4 bytes (network byte order) into unsigned value. * * @param intbuf * buffer to acquire the 4 bytes of data from. * @param offset * position within the buffer to begin reading from. This * position and the next 3 bytes after it (for a total of 4 * bytes) will be read. * @return unsigned integer value that matches the 32 bits read. */ public static long decodeUInt32(final byte[] intbuf, final int offset) { int low = (intbuf[offset + 1] & 0xff) << 8; low |= (intbuf[offset + 2] & 0xff); low <<= 8; low |= (intbuf[offset + 3] & 0xff); return ((long) (intbuf[offset] & 0xff)) << 24 | low; } /** * Convert sequence of 8 bytes (network byte order) into unsigned value. * * @param intbuf * buffer to acquire the 8 bytes of data from. * @param offset * position within the buffer to begin reading from. This * position and the next 7 bytes after it (for a total of 8 * bytes) will be read. * @return unsigned integer value that matches the 64 bits read. */ public static long decodeUInt64(final byte[] intbuf, final int offset) { return (decodeUInt32(intbuf, offset) << 32) | decodeUInt32(intbuf, offset + 4); } /** * Write a 16 bit integer as a sequence of 2 bytes (network byte order). * * @param intbuf * buffer to write the 2 bytes of data into. * @param offset * position within the buffer to begin writing to. This position * and the next byte after it (for a total of 2 bytes) will be * replaced. * @param v * the value to write. */ public static void encodeInt16(final byte[] intbuf, final int offset, int v) { intbuf[offset + 1] = (byte) v; v >>>= 8; intbuf[offset] = (byte) v; } /** * Write a 24 bit integer as a sequence of 3 bytes (network byte order). * * @param intbuf * buffer to write the 3 bytes of data into. * @param offset * position within the buffer to begin writing to. This position * and the next 2 bytes after it (for a total of 3 bytes) will be * replaced. * @param v * the value to write. * @since 4.9 */ public static void encodeInt24(byte[] intbuf, int offset, int v) { intbuf[offset + 2] = (byte) v; v >>>= 8; intbuf[offset + 1] = (byte) v; v >>>= 8; intbuf[offset] = (byte) v; } /** * Write a 32 bit integer as a sequence of 4 bytes (network byte order). * * @param intbuf * buffer to write the 4 bytes of data into. * @param offset * position within the buffer to begin writing to. This position * and the next 3 bytes after it (for a total of 4 bytes) will be * replaced. * @param v * the value to write. */ public static void encodeInt32(final byte[] intbuf, final int offset, int v) { intbuf[offset + 3] = (byte) v; v >>>= 8; intbuf[offset + 2] = (byte) v; v >>>= 8; intbuf[offset + 1] = (byte) v; v >>>= 8; intbuf[offset] = (byte) v; } /** * Write a 64 bit integer as a sequence of 8 bytes (network byte order). * * @param intbuf * buffer to write the 8 bytes of data into. * @param offset * position within the buffer to begin writing to. This position * and the next 7 bytes after it (for a total of 8 bytes) will be * replaced. * @param v * the value to write. */ public static void encodeInt64(final byte[] intbuf, final int offset, long v) { intbuf[offset + 7] = (byte) v; v >>>= 8; intbuf[offset + 6] = (byte) v; v >>>= 8; intbuf[offset + 5] = (byte) v; v >>>= 8; intbuf[offset + 4] = (byte) v; v >>>= 8; intbuf[offset + 3] = (byte) v; v >>>= 8; intbuf[offset + 2] = (byte) v; v >>>= 8; intbuf[offset + 1] = (byte) v; v >>>= 8; intbuf[offset] = (byte) v; } private NB() { // Don't create instances of a static only utility. } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5923 Content-Disposition: inline; filename="Paths.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "ae13ef7f3cc6c2857c04cd738546c47544f86a63" /* * 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.util; import static org.eclipse.jgit.lib.FileMode.TYPE_MASK; import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; /** * Utility functions for paths inside of a Git repository. * * @since 4.2 */ public final class Paths { /** * Remove trailing {@code '/'} if present. * * @param path * input path to potentially remove trailing {@code '/'} from. * @return null if {@code path == null}; {@code path} after removing a * trailing {@code '/'}. */ public static String stripTrailingSeparator(String path) { if (path == null || path.isEmpty()) { return path; } int i = path.length(); if (path.charAt(path.length() - 1) != '/') { return path; } do { i--; } while (path.charAt(i - 1) == '/'); return path.substring(0, i); } /** * Determines whether a git path {@code folder} is a prefix of another git * path {@code path}, or the same as {@code path}. An empty {@code folder} * is not not considered a prefix and matches only if {@code path} * is also empty. * * @param folder * a git path for a directory, without trailing slash * @param path * a git path * @return {@code true} if {@code folder} is a directory prefix of * {@code path}, or is equal to {@code path}, {@code false} * otherwise * @since 6.3 */ public static boolean isEqualOrPrefix(String folder, String path) { if (folder.isEmpty()) { return path.isEmpty(); } boolean isPrefix = path.startsWith(folder); if (isPrefix) { int length = folder.length(); return path.length() == length || path.charAt(length) == '/'; } return false; } /** * Compare two paths according to Git path sort ordering rules. * * @param aPath * first path buffer. The range {@code [aPos, aEnd)} is used. * @param aPos * index into {@code aPath} where the first path starts. * @param aEnd * 1 past last index of {@code aPath}. * @param aMode * mode of the first file. Trees are sorted as though * {@code aPath[aEnd] == '/'}, even if aEnd does not exist. * @param bPath * second path buffer. The range {@code [bPos, bEnd)} is used. * @param bPos * index into {@code bPath} where the second path starts. * @param bEnd * 1 past last index of {@code bPath}. * @param bMode * mode of the second file. Trees are sorted as though * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. * @return <0 if {@code aPath} sorts before {@code bPath}; 0 if the paths * are the same; >0 if {@code aPath} sorts after {@code bPath}. */ public static int compare(byte[] aPath, int aPos, int aEnd, int aMode, byte[] bPath, int bPos, int bEnd, int bMode) { int cmp = coreCompare( aPath, aPos, aEnd, aMode, bPath, bPos, bEnd, bMode); if (cmp == 0) { cmp = lastPathChar(aMode) - lastPathChar(bMode); } return cmp; } /** * Compare two paths, checking for identical name. *

* Unlike {@code compare} this method returns {@code 0} when the paths have * the same characters in their names, even if the mode differs. It is * intended for use in validation routines detecting duplicate entries. *

* Returns {@code 0} if the names are identical and a conflict exists * between {@code aPath} and {@code bPath}, as they share the same name. *

* Returns {@code <0} if all possibles occurrences of {@code aPath} sort * before {@code bPath} and no conflict can happen. In a properly sorted * tree there are no other occurrences of {@code aPath} and therefore there * are no duplicate names. *

* Returns {@code >0} when it is possible for a duplicate occurrence of * {@code aPath} to appear later, after {@code bPath}. Callers should * continue to examine candidates for {@code bPath} until the method returns * one of the other return values. * * @param aPath * first path buffer. The range {@code [aPos, aEnd)} is used. * @param aPos * index into {@code aPath} where the first path starts. * @param aEnd * 1 past last index of {@code aPath}. * @param bPath * second path buffer. The range {@code [bPos, bEnd)} is used. * @param bPos * index into {@code bPath} where the second path starts. * @param bEnd * 1 past last index of {@code bPath}. * @param bMode * mode of the second file. Trees are sorted as though * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. * @return <0 if no duplicate name could exist; * 0 if the paths have the same name; * >0 other {@code bPath} should still be checked by caller. */ public static int compareSameName( byte[] aPath, int aPos, int aEnd, byte[] bPath, int bPos, int bEnd, int bMode) { return coreCompare( aPath, aPos, aEnd, TYPE_TREE, bPath, bPos, bEnd, bMode); } private static int coreCompare( byte[] aPath, int aPos, int aEnd, int aMode, byte[] bPath, int bPos, int bEnd, int bMode) { while (aPos < aEnd && bPos < bEnd) { int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff); if (cmp != 0) { return cmp; } } if (aPos < aEnd) { return (aPath[aPos] & 0xff) - lastPathChar(bMode); } if (bPos < bEnd) { return lastPathChar(aMode) - (bPath[bPos] & 0xff); } return 0; } private static int lastPathChar(int mode) { if ((mode & TYPE_MASK) == TYPE_TREE) { return '/'; } return 0; } private Paths() { } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2247 Content-Disposition: inline; filename="ProcessResult.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "d2b4a0d84a4773f3a95320e405613353dca64bee" /* * Copyright (C) 2014 Obeo. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.util; /** * Describes the result of running an external process. * * @since 3.7 */ public class ProcessResult { /** * Status of a process' execution. */ public enum Status { /** * The script was found and launched properly. It may still have exited * with a non-zero {@link #exitCode}. */ OK, /** The script was not found on disk and thus could not be launched. */ NOT_PRESENT, /** * The script was found but could not be launched since it was not * supported by the current {@link FS}. */ NOT_SUPPORTED; } /** The exit code of the process. */ private final int exitCode; /** Status of the process' execution. */ private final Status status; /** * Instantiates a process result with the given status and an exit code of * -1. * * @param status * Status describing the execution of the external process. */ public ProcessResult(Status status) { this(-1, status); } /** *

Constructor for ProcessResult.

* * @param exitCode * Exit code of the process. * @param status * Status describing the execution of the external process. */ public ProcessResult(int exitCode, Status status) { this.exitCode = exitCode; this.status = status; } /** * Get exit code of the process. * * @return The exit code of the process. */ public int getExitCode() { return exitCode; } /** * Get the status of the process' execution. * * @return The status of the process' execution. */ public Status getStatus() { return status; } /** * Whether the execution occurred and resulted in an error * * @return true if the execution occurred and resulted in a * return code different from 0, false otherwise. * @since 4.0 */ public boolean isExecutedWithError() { return getStatus() == ProcessResult.Status.OK && getExitCode() != 0; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8945 Content-Disposition: inline; filename="QuotedString.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "493ca312d1fcc93fd8619bcbbd4f89b1dcda3f56" /* * Copyright (C) 2008, 2019 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.util; import static java.nio.charset.StandardCharsets.UTF_8; import java.util.Arrays; import org.eclipse.jgit.lib.Constants; /** * Utility functions related to quoted string handling. */ public abstract class QuotedString { /** Quoting style that obeys the rules Git applies to file names */ public static final GitPathStyle GIT_PATH = new GitPathStyle(true); /** * Quoting style that obeys the rules Git applies to file names when * {@code core.quotePath = false}. * * @since 5.6 */ public static final QuotedString GIT_PATH_MINIMAL = new GitPathStyle(false); /** * Quoting style used by the Bourne shell. *

* Quotes are unconditionally inserted during {@link #quote(String)}. This * protects shell meta-characters like $ or ~ from * being recognized as special. */ public static final BourneStyle BOURNE = new BourneStyle(); /** Bourne style, but permits ~user at the start of the string. */ public static final BourneUserPathStyle BOURNE_USER_PATH = new BourneUserPathStyle(); /** * Quote an input string by the quoting rules. *

* If the input string does not require any quoting, the same String * reference is returned to the caller. *

* Otherwise a quoted string is returned, including the opening and closing * quotation marks at the start and end of the string. If the style does not * permit raw Unicode characters then the string will first be encoded in * UTF-8, with unprintable sequences possibly escaped by the rules. * * @param in * any non-null Unicode string. * @return a quoted string. See above for details. */ public abstract String quote(String in); /** * Clean a previously quoted input, decoding the result via UTF-8. *

* This method must match quote such that: * *

	 * a.equals(dequote(quote(a)));
	 * 
* * is true for any a. * * @param in * a Unicode string to remove quoting from. * @return the cleaned string. * @see #dequote(byte[], int, int) */ public String dequote(String in) { final byte[] b = Constants.encode(in); return dequote(b, 0, b.length); } /** * Decode a previously quoted input, scanning a UTF-8 encoded buffer. *

* This method must match quote such that: * *

	 * a.equals(dequote(Constants.encode(quote(a))));
	 * 
* * is true for any a. *

* This method removes any opening/closing quotation marks added by * {@link #quote(String)}. * * @param in * the input buffer to parse. * @param offset * first position within in to scan. * @param end * one position past in in to scan. * @return the cleaned string. */ public abstract String dequote(byte[] in, int offset, int end); /** * Quoting style used by the Bourne shell. *

* Quotes are unconditionally inserted during {@link #quote(String)}. This * protects shell meta-characters like $ or ~ from * being recognized as special. */ public static class BourneStyle extends QuotedString { @Override public String quote(String in) { final StringBuilder r = new StringBuilder(); r.append('\''); int start = 0, i = 0; for (; i < in.length(); i++) { switch (in.charAt(i)) { case '\'': case '!': r.append(in, start, i); r.append('\''); r.append('\\'); r.append(in.charAt(i)); r.append('\''); start = i + 1; break; } } r.append(in, start, i); r.append('\''); return r.toString(); } @Override public String dequote(byte[] in, int ip, int ie) { boolean inquote = false; final byte[] r = new byte[ie - ip]; int rPtr = 0; while (ip < ie) { final byte b = in[ip++]; switch (b) { case '\'': inquote = !inquote; continue; case '\\': if (inquote || ip == ie) r[rPtr++] = b; // literal within a quote else r[rPtr++] = in[ip++]; continue; default: r[rPtr++] = b; continue; } } return RawParseUtils.decode(UTF_8, r, 0, rPtr); } } /** Bourne style, but permits ~user at the start of the string. */ public static class BourneUserPathStyle extends BourneStyle { @Override public String quote(String in) { if (in.matches("^~[A-Za-z0-9_-]+$")) { //$NON-NLS-1$ // If the string is just "~user" we can assume they // mean "~user/". // return in + "/"; //$NON-NLS-1$ } if (in.matches("^~[A-Za-z0-9_-]*/.*$")) { //$NON-NLS-1$ // If the string is of "~/path" or "~user/path" // we must not escape ~/ or ~user/ from the shell. // final int i = in.indexOf('/') + 1; if (i == in.length()) return in; return in.substring(0, i) + super.quote(in.substring(i)); } return super.quote(in); } } /** Quoting style that obeys the rules Git applies to file names */ public static final class GitPathStyle extends QuotedString { private static final byte[] quote; static { quote = new byte[128]; Arrays.fill(quote, (byte) -1); for (int i = '0'; i <= '9'; i++) quote[i] = 0; for (int i = 'a'; i <= 'z'; i++) quote[i] = 0; for (int i = 'A'; i <= 'Z'; i++) quote[i] = 0; quote[' '] = 0; quote['$'] = 0; quote['%'] = 0; quote['&'] = 0; quote['*'] = 0; quote['+'] = 0; quote[','] = 0; quote['-'] = 0; quote['.'] = 0; quote['/'] = 0; quote[':'] = 0; quote[';'] = 0; quote['='] = 0; quote['?'] = 0; quote['@'] = 0; quote['_'] = 0; quote['^'] = 0; quote['|'] = 0; quote['~'] = 0; quote['\u0007'] = 'a'; quote['\b'] = 'b'; quote['\f'] = 'f'; quote['\n'] = 'n'; quote['\r'] = 'r'; quote['\t'] = 't'; quote['\u000B'] = 'v'; quote['\\'] = '\\'; quote['"'] = '"'; } private final boolean quoteHigh; @Override public String quote(String instr) { if (instr.isEmpty()) { return "\"\""; //$NON-NLS-1$ } boolean reuse = true; final byte[] in = Constants.encode(instr); final byte[] out = new byte[4 * in.length + 2]; int o = 0; out[o++] = '"'; for (byte element : in) { final int c = element & 0xff; if (c < quote.length) { final byte style = quote[c]; if (style == 0) { out[o++] = (byte) c; continue; } if (style > 0) { reuse = false; out[o++] = '\\'; out[o++] = style; continue; } } else if (!quoteHigh) { out[o++] = (byte) c; continue; } reuse = false; out[o++] = '\\'; out[o++] = (byte) (((c >> 6) & 03) + '0'); out[o++] = (byte) (((c >> 3) & 07) + '0'); out[o++] = (byte) (((c >> 0) & 07) + '0'); } if (reuse) { return instr; } out[o++] = '"'; return new String(out, 0, o, UTF_8); } @Override public String dequote(byte[] in, int inPtr, int inEnd) { if (2 <= inEnd - inPtr && in[inPtr] == '"' && in[inEnd - 1] == '"') return dq(in, inPtr + 1, inEnd - 1); return RawParseUtils.decode(UTF_8, in, inPtr, inEnd); } private static String dq(byte[] in, int inPtr, int inEnd) { final byte[] r = new byte[inEnd - inPtr]; int rPtr = 0; while (inPtr < inEnd) { final byte b = in[inPtr++]; if (b != '\\') { r[rPtr++] = b; continue; } if (inPtr == inEnd) { // Lone trailing backslash. Treat it as a literal. // r[rPtr++] = '\\'; break; } switch (in[inPtr++]) { case 'a': r[rPtr++] = 0x07 /* \a = BEL */; continue; case 'b': r[rPtr++] = '\b'; continue; case 'f': r[rPtr++] = '\f'; continue; case 'n': r[rPtr++] = '\n'; continue; case 'r': r[rPtr++] = '\r'; continue; case 't': r[rPtr++] = '\t'; continue; case 'v': r[rPtr++] = 0x0B/* \v = VT */; continue; case '\\': case '"': r[rPtr++] = in[inPtr - 1]; continue; case '0': case '1': case '2': case '3': { int cp = in[inPtr - 1] - '0'; for (int n = 1; n < 3 && inPtr < inEnd; n++) { final byte c = in[inPtr]; if ('0' <= c && c <= '7') { cp <<= 3; cp |= c - '0'; inPtr++; } else { break; } } r[rPtr++] = (byte) cp; continue; } default: // Any other code is taken literally. // r[rPtr++] = '\\'; r[rPtr++] = in[inPtr - 1]; continue; } } return RawParseUtils.decode(UTF_8, r, 0, rPtr); } private GitPathStyle(boolean doQuote) { quoteHigh = doQuote; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1603 Content-Disposition: inline; filename="RawCharSequence.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "be4bd9e35624013cc0cd5dd486a8b8fe1b70eef5" /* * 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.util; /** * A rough character sequence around a raw byte buffer. *

* Characters are assumed to be 8-bit US-ASCII. */ public final class RawCharSequence implements CharSequence { /** A zero-length character sequence. */ public static final RawCharSequence EMPTY = new RawCharSequence(null, 0, 0); final byte[] buffer; final int startPtr; final int endPtr; /** * Create a rough character sequence around the raw byte buffer. * * @param buf * buffer to scan. * @param start * starting position for the sequence. * @param end * ending position for the sequence. */ public RawCharSequence(byte[] buf, int start, int end) { buffer = buf; startPtr = start; endPtr = end; } @Override public char charAt(int index) { return (char) (buffer[startPtr + index] & 0xff); } @Override public int length() { return endPtr - startPtr; } @Override public CharSequence subSequence(int start, int end) { return new RawCharSequence(buffer, startPtr + start, startPtr + end); } @Override public String toString() { final int n = length(); final StringBuilder b = new StringBuilder(n); for (int i = 0; i < n; i++) b.append(charAt(i)); return b.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2349 Content-Disposition: inline; filename="RawCharUtil.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "cb4236e9116db9d5f6f1fedb7b3deb6814ddec54" /* * 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.util; /** * Utility class for character functions on raw bytes *

* Characters are assumed to be 8-bit US-ASCII. */ public class RawCharUtil { private static final boolean[] WHITESPACE = new boolean[256]; static { WHITESPACE['\r'] = true; WHITESPACE['\n'] = true; WHITESPACE['\t'] = true; WHITESPACE[' '] = true; } /** * Determine if an 8-bit US-ASCII encoded character is represents whitespace * * @param c * the 8-bit US-ASCII encoded character * @return true if c represents a whitespace character in 8-bit US-ASCII */ public static boolean isWhitespace(byte c) { return WHITESPACE[c & 0xff]; } /** * Returns the new end point for the byte array passed in after trimming any * trailing whitespace characters, as determined by the isWhitespace() * function. start and end are assumed to be within the bounds of raw. * * @param raw * the byte array containing the portion to trim whitespace for * @param start * the start of the section of bytes * @param end * the end of the section of bytes * @return the new end point */ public static int trimTrailingWhitespace(byte[] raw, int start, int end) { int ptr = end - 1; while (start <= ptr && isWhitespace(raw[ptr])) ptr--; return ptr + 1; } /** * Returns the new start point for the byte array passed in after trimming * any leading whitespace characters, as determined by the isWhitespace() * function. start and end are assumed to be within the bounds of raw. * * @param raw * the byte array containing the portion to trim whitespace for * @param start * the start of the section of bytes * @param end * the end of the section of bytes * @return the new start point */ public static int trimLeadingWhitespace(byte[] raw, int start, int end) { while (start < end && isWhitespace(raw[start])) start++; return start; } private RawCharUtil() { // This will never be called } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 42764 Content-Disposition: inline; filename="RawParseUtils.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "3ed72516c70de5a894b5178637ca5ab9b927c870" /* * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2006-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.util; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.Instant.EPOCH; import static java.time.ZoneOffset.UTC; import static org.eclipse.jgit.lib.ObjectChecker.author; import static org.eclipse.jgit.lib.ObjectChecker.committer; import static org.eclipse.jgit.lib.ObjectChecker.encoding; import static org.eclipse.jgit.lib.ObjectChecker.object; import static org.eclipse.jgit.lib.ObjectChecker.parent; import static org.eclipse.jgit.lib.ObjectChecker.tag; import static org.eclipse.jgit.lib.ObjectChecker.tagger; import static org.eclipse.jgit.lib.ObjectChecker.tree; import static org.eclipse.jgit.lib.ObjectChecker.type; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.errors.BinaryBlobException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.PersonIdent; /** * Handy utility functions to parse raw object contents. */ public final class RawParseUtils { private static final byte[] digits10; private static final byte[] digits16; private static final byte[] footerLineKeyChars; private static final Map encodingAliases; static { encodingAliases = new HashMap<>(); encodingAliases.put("latin-1", ISO_8859_1); //$NON-NLS-1$ encodingAliases.put("iso-latin-1", ISO_8859_1); //$NON-NLS-1$ digits10 = new byte['9' + 1]; Arrays.fill(digits10, (byte) -1); for (char i = '0'; i <= '9'; i++) digits10[i] = (byte) (i - '0'); digits16 = new byte['f' + 1]; Arrays.fill(digits16, (byte) -1); for (char i = '0'; i <= '9'; i++) digits16[i] = (byte) (i - '0'); for (char i = 'a'; i <= 'f'; i++) digits16[i] = (byte) ((i - 'a') + 10); for (char i = 'A'; i <= 'F'; i++) digits16[i] = (byte) ((i - 'A') + 10); footerLineKeyChars = new byte['z' + 1]; footerLineKeyChars['-'] = 1; for (char i = '0'; i <= '9'; i++) footerLineKeyChars[i] = 1; for (char i = 'A'; i <= 'Z'; i++) footerLineKeyChars[i] = 1; for (char i = 'a'; i <= 'z'; i++) footerLineKeyChars[i] = 1; } /** * Determine if b[ptr] matches src. * * @param b * the buffer to scan. * @param ptr * first position within b, this should match src[0]. * @param src * the buffer to test for equality with b. * @return ptr + src.length if b[ptr..src.length] == src; else -1. */ public static final int match(byte[] b, int ptr, byte[] src) { if (ptr + src.length > b.length) return -1; for (int i = 0; i < src.length; i++, ptr++) if (b[ptr] != src[i]) return -1; return ptr; } private static final byte[] base10byte = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; /** * Format a base 10 numeric into a temporary buffer. *

* Formatting is performed backwards. The method starts at offset * o-1 and ends at o-1-digits, where * digits is the number of positions necessary to store the * base 10 value. *

* The argument and return values from this method make it easy to chain * writing, for example: *

* *
	 * final byte[] tmp = new byte[64];
	 * int ptr = tmp.length;
	 * tmp[--ptr] = '\n';
	 * ptr = RawParseUtils.formatBase10(tmp, ptr, 32);
	 * tmp[--ptr] = ' ';
	 * ptr = RawParseUtils.formatBase10(tmp, ptr, 18);
	 * tmp[--ptr] = 0;
	 * final String str = new String(tmp, ptr, tmp.length - ptr);
	 * 
* * @param b * buffer to write into. * @param o * one offset past the location where writing will begin; writing * proceeds towards lower index values. * @param value * the value to store. * @return the new offset value o. This is the position of * the last byte written. Additional writing should start at one * position earlier. */ public static int formatBase10(final byte[] b, int o, int value) { if (value == 0) { b[--o] = '0'; return o; } final boolean isneg = value < 0; if (isneg) value = -value; while (value != 0) { b[--o] = base10byte[value % 10]; value /= 10; } if (isneg) b[--o] = '-'; return o; } /** * Parse a base 10 numeric from a sequence of ASCII digits into an int. *

* Digit sequences can begin with an optional run of spaces before the * sequence, and may start with a '+' or a '-' to indicate sign position. * Any other characters will cause the method to stop and return the current * result to the caller. * * @param b * buffer to scan. * @param ptr * position within buffer to start parsing digits at. * @param ptrResult * optional location to return the new ptr value through. If null * the ptr value will be discarded. * @return the value at this location; 0 if the location is not a valid * numeric. */ public static final int parseBase10(final byte[] b, int ptr, final MutableInteger ptrResult) { int r = 0; int sign = 0; try { final int sz = b.length; while (ptr < sz && b[ptr] == ' ') ptr++; if (ptr >= sz) return 0; switch (b[ptr]) { case '-': sign = -1; ptr++; break; case '+': ptr++; break; } while (ptr < sz) { final byte v = digits10[b[ptr]]; if (v < 0) break; r = (r * 10) + v; ptr++; } } catch (ArrayIndexOutOfBoundsException e) { // Not a valid digit. } if (ptrResult != null) ptrResult.value = ptr; return sign < 0 ? -r : r; } /** * Parse a base 10 numeric from a sequence of ASCII digits into a long. *

* Digit sequences can begin with an optional run of spaces before the * sequence, and may start with a '+' or a '-' to indicate sign position. * Any other characters will cause the method to stop and return the current * result to the caller. * * @param b * buffer to scan. * @param ptr * position within buffer to start parsing digits at. * @param ptrResult * optional location to return the new ptr value through. If null * the ptr value will be discarded. * @return the value at this location; 0 if the location is not a valid * numeric. */ public static final long parseLongBase10(final byte[] b, int ptr, final MutableInteger ptrResult) { long r = 0; int sign = 0; try { final int sz = b.length; while (ptr < sz && b[ptr] == ' ') ptr++; if (ptr >= sz) return 0; switch (b[ptr]) { case '-': sign = -1; ptr++; break; case '+': ptr++; break; } while (ptr < sz) { final byte v = digits10[b[ptr]]; if (v < 0) break; r = (r * 10) + v; ptr++; } } catch (ArrayIndexOutOfBoundsException e) { // Not a valid digit. } if (ptrResult != null) ptrResult.value = ptr; return sign < 0 ? -r : r; } /** * Parse 4 character base 16 (hex) formatted string to unsigned integer. *

* The number is read in network byte order, that is, most significant * nybble first. * * @param bs * buffer to parse digits from; positions {@code [p, p+4)} will * be parsed. * @param p * first position within the buffer to parse. * @return the integer value. * @throws java.lang.ArrayIndexOutOfBoundsException * if the string is not hex formatted. */ public static final int parseHexInt16(final byte[] bs, final int p) { int r = digits16[bs[p]] << 4; r |= digits16[bs[p + 1]]; r <<= 4; r |= digits16[bs[p + 2]]; r <<= 4; r |= digits16[bs[p + 3]]; if (r < 0) throw new ArrayIndexOutOfBoundsException(); return r; } /** * Parse 8 character base 16 (hex) formatted string to unsigned integer. *

* The number is read in network byte order, that is, most significant * nybble first. * * @param bs * buffer to parse digits from; positions {@code [p, p+8)} will * be parsed. * @param p * first position within the buffer to parse. * @return the integer value. * @throws java.lang.ArrayIndexOutOfBoundsException * if the string is not hex formatted. */ public static final int parseHexInt32(final byte[] bs, final int p) { int r = digits16[bs[p]] << 4; r |= digits16[bs[p + 1]]; r <<= 4; r |= digits16[bs[p + 2]]; r <<= 4; r |= digits16[bs[p + 3]]; r <<= 4; r |= digits16[bs[p + 4]]; r <<= 4; r |= digits16[bs[p + 5]]; r <<= 4; r |= digits16[bs[p + 6]]; final int last = digits16[bs[p + 7]]; if (r < 0 || last < 0) throw new ArrayIndexOutOfBoundsException(); return (r << 4) | last; } /** * Parse 16 character base 16 (hex) formatted string to unsigned long. *

* The number is read in network byte order, that is, most significant * nibble first. * * @param bs * buffer to parse digits from; positions {@code [p, p+16)} will * be parsed. * @param p * first position within the buffer to parse. * @return the integer value. * @throws java.lang.ArrayIndexOutOfBoundsException * if the string is not hex formatted. * @since 4.3 */ @SuppressWarnings("IntLongMath") public static final long parseHexInt64(final byte[] bs, final int p) { long r = digits16[bs[p]] << 4; r |= digits16[bs[p + 1]]; r <<= 4; r |= digits16[bs[p + 2]]; r <<= 4; r |= digits16[bs[p + 3]]; r <<= 4; r |= digits16[bs[p + 4]]; r <<= 4; r |= digits16[bs[p + 5]]; r <<= 4; r |= digits16[bs[p + 6]]; r <<= 4; r |= digits16[bs[p + 7]]; r <<= 4; r |= digits16[bs[p + 8]]; r <<= 4; r |= digits16[bs[p + 9]]; r <<= 4; r |= digits16[bs[p + 10]]; r <<= 4; r |= digits16[bs[p + 11]]; r <<= 4; r |= digits16[bs[p + 12]]; r <<= 4; r |= digits16[bs[p + 13]]; r <<= 4; r |= digits16[bs[p + 14]]; final int last = digits16[bs[p + 15]]; if (r < 0 || last < 0) throw new ArrayIndexOutOfBoundsException(); return (r << 4) | last; } /** * Parse a single hex digit to its numeric value (0-15). * * @param digit * hex character to parse. * @return numeric value, in the range 0-15. * @throws java.lang.ArrayIndexOutOfBoundsException * if the input digit is not a valid hex digit. */ public static final int parseHexInt4(final byte digit) { final byte r = digits16[digit]; if (r < 0) throw new ArrayIndexOutOfBoundsException(); return r; } /** * Parse a Git style timezone string. *

* The sequence "-0315" will be parsed as the numeric value -195, as the * lower two positions count minutes, not 100ths of an hour. * * @param b * buffer to scan. * @param ptr * position within buffer to start parsing digits at. * @return the timezone at this location, expressed in minutes. */ public static final int parseTimeZoneOffset(byte[] b, int ptr) { return parseTimeZoneOffset(b, ptr, null); } /** * Parse a Git style timezone string. *

* The sequence "-0315" will be parsed as the numeric value -195, as the * lower two positions count minutes, not 100ths of an hour. * * @param b * buffer to scan. * @param ptr * position within buffer to start parsing digits at. * @param ptrResult * optional location to return the new ptr value through. If null * the ptr value will be discarded. * @return the timezone at this location, expressed in minutes. * @since 4.1 */ public static final int parseTimeZoneOffset(final byte[] b, int ptr, MutableInteger ptrResult) { final int v = parseBase10(b, ptr, ptrResult); final int tzMins = v % 100; final int tzHours = v / 100; return tzHours * 60 + tzMins; } /** * Parse a Git style timezone string in [+-]hhmm format * * @param b * buffer to scan. * @param ptr * position within buffer to start parsing digits at. * @param ptrResult * optional location to return the new ptr value through. If null * the ptr value will be discarded. * @return the ZoneOffset represention of the timezone offset string. * Invalid offsets default to UTC. */ private static ZoneId parseZoneOffset(final byte[] b, int ptr, MutableInteger ptrResult) { int hhmm = parseBase10(b, ptr, ptrResult); try { return ZoneOffset.ofHoursMinutes(hhmm / 100, hhmm % 100); } catch (DateTimeException e) { return UTC; } } /** * Locate the first position after a given character. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for chrA at. * @param chrA * character to find. * @return new position just after chrA. */ public static final int next(byte[] b, int ptr, char chrA) { final int sz = b.length; while (ptr < sz) { if (b[ptr++] == chrA) return ptr; } return ptr; } /** * Locate the first position after the next LF. *

* This method stops on the first '\n' it finds. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for LF at. * @return new position just after the first LF found. */ public static final int nextLF(byte[] b, int ptr) { return next(b, ptr, '\n'); } /** * Locate the first position after either the given character or LF. *

* This method stops on the first match it finds from either chrA or '\n'. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for chrA or LF at. * @param chrA * character to find. * @return new position just after the first chrA or LF to be found. */ public static final int nextLF(byte[] b, int ptr, char chrA) { final int sz = b.length; while (ptr < sz) { final byte c = b[ptr++]; if (c == chrA || c == '\n') return ptr; } return ptr; } /** * Locate the first end of line after the given position, while treating * following lines which are starting with spaces as part of the current * line. *

* For example, {@code nextLfSkippingSplitLines( * "row \n with space at beginning of a following line\nThe actual next line", * 0)} will return the position of {@code "\nThe actual next line"}. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for the next line. * @return new position just after the line end of the last line-split. This * is either b.length, or the index of the current split-line's * terminating newline. * @since 6.9 */ public static final int nextLfSkippingSplitLines(final byte[] b, int ptr) { final int sz = b.length; while (ptr < sz) { final byte c = b[ptr++]; if (c == '\n' && (ptr == sz || b[ptr] != ' ')) { return ptr - 1; } } return ptr; } /** * Extract a part of a buffer as a header value, removing the single blanks * at the front of continuation lines. * * @param b * buffer to extract the header from * @param start * of the header value, see * {@link #headerStart(byte[], byte[], int)} * @param end * of the header; see * {@link #nextLfSkippingSplitLines(byte[], int)} * @return the header value, with blanks indicating continuation lines * stripped * @since 6.9 */ public static final byte[] headerValue(final byte[] b, int start, int end) { byte[] data = new byte[end - start]; int out = 0; byte last = '\0'; for (int in = start; in < end; in++) { byte ch = b[in]; if (ch != ' ' || last != '\n') { data[out++] = ch; } last = ch; } if (out == data.length) { return data; } return Arrays.copyOf(data, out); } /** * Locate the first end of header after the given position. Note that * headers may be more than one line long. *

* Also note that there might be multiple headers. If you wish to find the * last header's end - call this in a loop. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for the header * (normally a new-line). * @return new position just after the line end. This is either b.length, or * the index of the header's terminating newline. * @since 5.1 * @deprecated use {{@link #nextLfSkippingSplitLines}} directly instead */ @Deprecated public static final int headerEnd(final byte[] b, int ptr) { return nextLfSkippingSplitLines(b, ptr); } /** * Find the start of the contents of a given header. * * @param b * buffer to scan. * @param headerName * header to search for * @param ptr * position within buffer to start looking for header at. * @return new position at the start of the header's contents, -1 for * not found * @since 5.1 */ public static final int headerStart(byte[] headerName, byte[] b, int ptr) { // Start by advancing to just past a LF or buffer start if (ptr != 0) { ptr = nextLF(b, ptr - 1); } while (ptr < b.length - (headerName.length + 1)) { boolean found = true; for (byte element : headerName) { if (element != b[ptr++]) { found = false; break; } } if (found && b[ptr++] == ' ') { return ptr; } ptr = nextLF(b, ptr); } return -1; } /** * Returns whether the message starts with any known headers. * * @param b * buffer to scan. * @return whether the message starts with any known headers * @since 6.9 */ public static final boolean hasAnyKnownHeaders(byte[] b) { return match(b, 0, tree) != -1 || match(b, 0, parent) != -1 || match(b, 0, author) != -1 || match(b, 0, committer) != -1 || match(b, 0, encoding) != -1 || match(b, 0, object) != -1 || match(b, 0, type) != -1 || match(b, 0, tag) != -1 || match(b, 0, tagger) != -1; } /** * Locate the first position before a given character. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for chrA at. * @param chrA * character to find. * @return new position just before chrA, -1 for not found */ public static final int prev(byte[] b, int ptr, char chrA) { if (ptr == b.length) --ptr; while (ptr >= 0) { if (b[ptr--] == chrA) return ptr; } return ptr; } /** * Locate the first position before the previous LF. *

* This method stops on the first '\n' it finds. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for LF at. * @return new position just before the first LF found, -1 for not found */ public static final int prevLF(byte[] b, int ptr) { return prev(b, ptr, '\n'); } /** * Locate the previous position before either the given character or LF. *

* This method stops on the first match it finds from either chrA or '\n'. * * @param b * buffer to scan. * @param ptr * position within buffer to start looking for chrA or LF at. * @param chrA * character to find. * @return new position just before the first chrA or LF to be found, -1 for * not found */ public static final int prevLF(byte[] b, int ptr, char chrA) { if (ptr == b.length) --ptr; while (ptr >= 0) { final byte c = b[ptr--]; if (c == chrA || c == '\n') return ptr; } return ptr; } /** * Index the region between [ptr, end) to find line starts. *

* The returned list is 1 indexed. Index 0 contains * {@link java.lang.Integer#MIN_VALUE} to pad the list out. *

* Using a 1 indexed list means that line numbers can be directly accessed * from the list, so list.get(1) (aka get line 1) returns * ptr. *

* The last element (index map.size()-1) always contains * end. * * @param buf * buffer to scan. * @param ptr * position within the buffer corresponding to the first byte of * line 1. * @param end * 1 past the end of the content within buf. * @return a line map indicating the starting position of each line. */ public static final IntList lineMap(byte[] buf, int ptr, int end) { IntList map = new IntList((end - ptr) / 36); map.fillTo(1, Integer.MIN_VALUE); for (; ptr < end; ptr = nextLF(buf, ptr)) { map.add(ptr); } map.add(end); return map; } /** * Like {@link #lineMap(byte[], int, int)} but throw * {@link BinaryBlobException} if a NUL byte is encountered. * * @param buf * buffer to scan. * @param ptr * position within the buffer corresponding to the first byte of * line 1. * @param end * 1 past the end of the content within buf. * @return a line map indicating the starting position of each line. * @throws BinaryBlobException * if a NUL byte or a lone CR is found. * @since 5.0 */ public static final IntList lineMapOrBinary(byte[] buf, int ptr, int end) throws BinaryBlobException { // Experimentally derived from multiple source repositories // the average number of bytes/line is 36. Its a rough guess // to initially size our map close to the target. IntList map = new IntList((end - ptr) / 36); map.add(Integer.MIN_VALUE); byte last = '\n'; // Must be \n to add the initial ptr for (; ptr < end; ptr++) { if (last == '\n') { map.add(ptr); } byte curr = buf[ptr]; if (RawText.isBinary(curr, last)) { throw new BinaryBlobException(); } last = curr; } if (last == '\r') { // Counts as binary throw new BinaryBlobException(); } map.add(end); return map; } /** * Locate the "author " header line data. * * @param b * buffer to scan. * @param ptr * position in buffer to start the scan at. Most callers should * pass 0 to ensure the scan starts from the beginning of the * commit buffer and does not accidentally look at message body. * @return position just after the space in "author ", so the first * character of the author's name. If no author header can be * located -1 is returned. */ public static final int author(byte[] b, int ptr) { final int sz = b.length; if (ptr == 0) ptr += 46; // skip the "tree ..." line. while (ptr < sz && b[ptr] == 'p') ptr += 48; // skip this parent. return match(b, ptr, author); } /** * Locate the "committer " header line data. * * @param b * buffer to scan. * @param ptr * position in buffer to start the scan at. Most callers should * pass 0 to ensure the scan starts from the beginning of the * commit buffer and does not accidentally look at message body. * @return position just after the space in "committer ", so the first * character of the committer's name. If no committer header can be * located -1 is returned. */ public static final int committer(byte[] b, int ptr) { final int sz = b.length; if (ptr == 0) ptr += 46; // skip the "tree ..." line. while (ptr < sz && b[ptr] == 'p') ptr += 48; // skip this parent. if (ptr < sz && b[ptr] == 'a') ptr = nextLF(b, ptr); return match(b, ptr, committer); } /** * Locate the "tagger " header line data. * * @param b * buffer to scan. * @param ptr * position in buffer to start the scan at. Most callers should * pass 0 to ensure the scan starts from the beginning of the tag * buffer and does not accidentally look at message body. * @return position just after the space in "tagger ", so the first * character of the tagger's name. If no tagger header can be * located -1 is returned. */ public static final int tagger(byte[] b, int ptr) { final int sz = b.length; if (ptr == 0) ptr += 48; // skip the "object ..." line. while (ptr < sz) { if (b[ptr] == '\n') return -1; final int m = match(b, ptr, tagger); if (m >= 0) return m; ptr = nextLF(b, ptr); } return -1; } /** * Locate the "encoding " header line. * * @param b * buffer to scan. * @param ptr * position in buffer to start the scan at. Most callers should * pass 0 to ensure the scan starts from the beginning of the * buffer and does not accidentally look at the message body. * @return position just after the space in "encoding ", so the first * character of the encoding's name. If no encoding header can be * located -1 is returned (and UTF-8 should be assumed). */ public static final int encoding(byte[] b, int ptr) { final int sz = b.length; while (ptr < sz) { if (b[ptr] == '\n') return -1; if (b[ptr] == 'e') break; ptr = nextLF(b, ptr); } return match(b, ptr, encoding); } /** * Parse the "encoding " header as a string. *

* Locates the "encoding " header (if present) and returns its value. * * @param b * buffer to scan. * @return the encoding header as specified in the commit; null if the * header was not present and should be assumed. * @since 4.2 */ @Nullable public static String parseEncodingName(byte[] b) { int enc = encoding(b, 0); if (enc < 0) { return null; } int lf = nextLF(b, enc); return decode(UTF_8, b, enc, lf - 1); } /** * Parse the "encoding " header into a character set reference. *

* Locates the "encoding " header (if present) by first calling * {@link #encoding(byte[], int)} and then returns the proper character set * to apply to this buffer to evaluate its contents as character data. *

* If no encoding header is present {@code UTF-8} is assumed. * * @param b * buffer to scan. * @return the Java character set representation. Never null. * @throws IllegalCharsetNameException * if the character set requested by the encoding header is * malformed and unsupportable. * @throws UnsupportedCharsetException * if the JRE does not support the character set requested by * the encoding header. */ public static Charset parseEncoding(byte[] b) { String enc = parseEncodingName(b); if (enc == null) { return UTF_8; } String name = enc.trim(); try { return Charset.forName(name); } catch (IllegalCharsetNameException | UnsupportedCharsetException badName) { Charset aliased = charsetForAlias(name); if (aliased != null) { return aliased; } throw badName; } } /** * Parse the "encoding " header into a character set reference. *

* If unsuccessful, return UTF-8. * * @param buffer * buffer to scan. * @return the Java character set representation. Never null. Default to * UTF-8. * @see #parseEncoding(byte[]) * @since 6.7 */ public static Charset guessEncoding(byte[] buffer) { try { return parseEncoding(buffer); } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { return UTF_8; } } /** * Parse a name string (e.g. author, committer, tagger) into a PersonIdent. *

* Leading spaces won't be trimmed from the string, i.e. will show up in the * parsed name afterwards. * * @param in * the string to parse a name from. * @return the parsed identity or null in case the identity could not be * parsed. */ public static PersonIdent parsePersonIdent(String in) { return parsePersonIdent(Constants.encode(in), 0); } /** * Parse a name line (e.g. author, committer, tagger) into a PersonIdent. *

* When passing in a value for nameB callers should use the * return value of {@link #author(byte[], int)} or * {@link #committer(byte[], int)}, as these methods provide the proper * position within the buffer. * * @param raw * the buffer to parse character data from. * @param nameB * first position of the identity information. This should be the * first position after the space which delimits the header field * name (e.g. "author" or "committer") from the rest of the * identity line. * @return the parsed identity or null in case the identity could not be * parsed. */ public static PersonIdent parsePersonIdent(byte[] raw, int nameB) { Charset cs; try { cs = parseEncoding(raw); } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { // Assume UTF-8 for person identities, usually this is correct. // If not decode() will fall back to the ISO-8859-1 encoding. cs = UTF_8; } final int emailB = nextLF(raw, nameB, '<'); final int emailE = nextLF(raw, emailB, '>'); if (emailB >= raw.length || raw[emailB] == '\n' || (emailE >= raw.length - 1 && raw[emailE - 1] != '>')) return null; final int nameEnd = emailB - 2 >= nameB && raw[emailB - 2] == ' ' ? emailB - 2 : emailB - 1; final String name = decode(cs, raw, nameB, nameEnd); final String email = decode(cs, raw, emailB, emailE - 1); // Start searching from end of line, as after first name-email pair, // another name-email pair may occur. We will ignore all kinds of // "junk" following the first email. // // We've to use (emailE - 1) for the case that raw[email] is LF, // otherwise we would run too far. "-2" is necessary to position // before the LF in case of LF termination resp. the penultimate // character if there is no trailing LF. final int tzBegin = lastIndexOfTrim(raw, ' ', nextLF(raw, emailE - 1) - 2) + 1; if (tzBegin <= emailE) { // No time/zone, still valid return new PersonIdent(name, email, EPOCH, UTC); } final int whenBegin = Math.max(emailE, lastIndexOfTrim(raw, ' ', tzBegin - 1) + 1); if (whenBegin >= tzBegin - 1) { // No time/zone, still valid return new PersonIdent(name, email, EPOCH, UTC); } long when = parseLongBase10(raw, whenBegin, null); return new PersonIdent(name, email, Instant.ofEpochSecond(when), parseZoneOffset(raw, tzBegin, null)); } /** * Parse a name data (e.g. as within a reflog) into a PersonIdent. *

* When passing in a value for nameB callers should use the * return value of {@link #author(byte[], int)} or * {@link #committer(byte[], int)}, as these methods provide the proper * position within the buffer. * * @param raw * the buffer to parse character data from. * @param nameB * first position of the identity information. This should be the * first position after the space which delimits the header field * name (e.g. "author" or "committer") from the rest of the * identity line. * @return the parsed identity. Never null. */ public static PersonIdent parsePersonIdentOnly(final byte[] raw, final int nameB) { int stop = nextLF(raw, nameB); int emailB = nextLF(raw, nameB, '<'); int emailE = nextLF(raw, emailB, '>'); final String name; final String email; if (emailE < stop) { email = decode(raw, emailB, emailE - 1); } else { email = "invalid"; //$NON-NLS-1$ } if (emailB < stop) name = decode(raw, nameB, emailB - 2); else name = decode(raw, nameB, stop); final MutableInteger ptrout = new MutableInteger(); Instant when; ZoneId tz; if (emailE < stop) { when = Instant.ofEpochSecond(parseLongBase10(raw, emailE + 1, ptrout)); tz = parseZoneOffset(raw, ptrout.value, null); } else { when = EPOCH; tz = UTC; } return new PersonIdent(name, email, when, tz); } /** * Locate the end of a footer line key string. *

* If the region at {@code raw[ptr]} matches {@code ^[A-Za-z0-9-]+:} (e.g. * "Signed-off-by: A. U. Thor\n") then this method returns the position of * the first ':'. *

* If the region at {@code raw[ptr]} does not match {@code ^[A-Za-z0-9-]+:} * then this method returns -1. * * @param raw * buffer to scan. * @param ptr * first position within raw to consider as a footer line key. * @return position of the ':' which terminates the footer line key if this * is otherwise a valid footer line key; otherwise -1. */ public static int endOfFooterLineKey(byte[] raw, int ptr) { try { for (;;) { final byte c = raw[ptr]; if (footerLineKeyChars[c] == 0) { if (c == ':') return ptr; return -1; } ptr++; } } catch (ArrayIndexOutOfBoundsException e) { return -1; } } /** * Decode a buffer under UTF-8, if possible. * * If the byte stream cannot be decoded that way, the platform default is tried * and if that too fails, the fail-safe ISO-8859-1 encoding is tried. * * @param buffer * buffer to pull raw bytes from. * @return a string representation of the range [start,end), * after decoding the region through the specified character set. */ public static String decode(byte[] buffer) { return decode(buffer, 0, buffer.length); } /** * Decode a buffer under UTF-8, if possible. * * If the byte stream cannot be decoded that way, the platform default is * tried and if that too fails, the fail-safe ISO-8859-1 encoding is tried. * * @param buffer * buffer to pull raw bytes from. * @param start * start position in buffer * @param end * one position past the last location within the buffer to take * data from. * @return a string representation of the range [start,end), * after decoding the region through the specified character set. */ public static String decode(final byte[] buffer, final int start, final int end) { return decode(UTF_8, buffer, start, end); } /** * Decode a buffer under the specified character set if possible. * * If the byte stream cannot be decoded that way, the platform default is tried * and if that too fails, the fail-safe ISO-8859-1 encoding is tried. * * @param cs * character set to use when decoding the buffer. * @param buffer * buffer to pull raw bytes from. * @return a string representation of the range [start,end), * after decoding the region through the specified character set. */ public static String decode(Charset cs, byte[] buffer) { return decode(cs, buffer, 0, buffer.length); } /** * Decode a region of the buffer under the specified character set if possible. * * If the byte stream cannot be decoded that way, the platform default is tried * and if that too fails, the fail-safe ISO-8859-1 encoding is tried. * * @param cs * character set to use when decoding the buffer. * @param buffer * buffer to pull raw bytes from. * @param start * first position within the buffer to take data from. * @param end * one position past the last location within the buffer to take * data from. * @return a string representation of the range [start,end), * after decoding the region through the specified character set. */ public static String decode(final Charset cs, final byte[] buffer, final int start, final int end) { try { return decodeNoFallback(cs, buffer, start, end); } catch (CharacterCodingException e) { // Fall back to an ISO-8859-1 style encoding. At least all of // the bytes will be present in the output. // return extractBinaryString(buffer, start, end); } } /** * Decode a region of the buffer under the specified character set if * possible. * * If the byte stream cannot be decoded that way, the platform default is * tried and if that too fails, an exception is thrown. * * @param cs * character set to use when decoding the buffer. * @param buffer * buffer to pull raw bytes from. * @param start * first position within the buffer to take data from. * @param end * one position past the last location within the buffer to take * data from. * @return a string representation of the range [start,end), * after decoding the region through the specified character set. * @throws java.nio.charset.CharacterCodingException * the input is not in any of the tested character sets. */ public static String decodeNoFallback(final Charset cs, final byte[] buffer, final int start, final int end) throws CharacterCodingException { ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start); b.mark(); // Try our built-in favorite. The assumption here is that // decoding will fail if the data is not actually encoded // using that encoder. try { return decode(b, UTF_8); } catch (CharacterCodingException e) { b.reset(); } if (!cs.equals(UTF_8)) { // Try the suggested encoding, it might be right since it was // provided by the caller. try { return decode(b, cs); } catch (CharacterCodingException e) { b.reset(); } } // Try the default character set. A small group of people // might actually use the same (or very similar) locale. Charset defcs = SystemReader.getInstance().getDefaultCharset(); if (!defcs.equals(cs) && !defcs.equals(UTF_8)) { try { return decode(b, defcs); } catch (CharacterCodingException e) { b.reset(); } } throw new CharacterCodingException(); } /** * Decode a region of the buffer under the ISO-8859-1 encoding. * * Each byte is treated as a single character in the 8859-1 character * encoding, performing a raw binary->char conversion. * * @param buffer * buffer to pull raw bytes from. * @param start * first position within the buffer to take data from. * @param end * one position past the last location within the buffer to take * data from. * @return a string representation of the range [start,end). */ public static String extractBinaryString(final byte[] buffer, final int start, final int end) { final StringBuilder r = new StringBuilder(end - start); for (int i = start; i < end; i++) r.append((char) (buffer[i] & 0xff)); return r.toString(); } private static String decode(ByteBuffer b, Charset charset) throws CharacterCodingException { final CharsetDecoder d = charset.newDecoder(); d.onMalformedInput(CodingErrorAction.REPORT); d.onUnmappableCharacter(CodingErrorAction.REPORT); return d.decode(b).toString(); } /** * Locate the position of the commit message body. * * @param b * buffer to scan. * @param ptr * position in buffer to start the scan at. Most callers should * pass 0 to ensure the scan starts from the beginning of the * commit buffer. * @return position of the user's message buffer. */ public static final int commitMessage(byte[] b, int ptr) { final int sz = b.length; if (ptr == 0) ptr += 46; // skip the "tree ..." line. while (ptr < sz && b[ptr] == 'p') ptr += 48; // skip this parent. // Skip any remaining header lines, ignoring what their actual // header line type is. This is identical to the logic for a tag. // return tagMessage(b, ptr); } /** * Locate the position of the tag message body. * * @param b * buffer to scan. * @param ptr * position in buffer to start the scan at. Most callers should * pass 0 to ensure the scan starts from the beginning of the tag * buffer. * @return position of the user's message buffer. */ public static final int tagMessage(byte[] b, int ptr) { final int sz = b.length; if (ptr == 0) ptr += 48; // skip the "object ..." line. // Assume the rest of the current paragraph is all headers. while (ptr < sz && b[ptr] != '\n') ptr = nextLF(b, ptr); if (ptr < sz && b[ptr] == '\n') return ptr + 1; return -1; } /** * Locate the end of a paragraph. *

* A paragraph is ended by two consecutive LF bytes or CRLF pairs * * @param b * buffer to scan. * @param start * position in buffer to start the scan at. Most callers will * want to pass the first position of the commit message (as * found by {@link #commitMessage(byte[], int)}. * @return position of the LF at the end of the paragraph; * b.length if no paragraph end could be located. */ public static final int endOfParagraph(byte[] b, int start) { int ptr = start; final int sz = b.length; while (ptr < sz && (b[ptr] != '\n' && b[ptr] != '\r')) ptr = nextLF(b, ptr); if (ptr > start && b[ptr - 1] == '\n') ptr--; if (ptr > start && b[ptr - 1] == '\r') ptr--; return ptr; } /** * Get last index of {@code ch} in raw, trimming spaces. * * @param raw * buffer to scan. * @param ch * character to find. * @param pos * starting position. * @return last index of {@code ch} in raw, trimming spaces. * @since 4.1 */ public static int lastIndexOfTrim(byte[] raw, char ch, int pos) { while (pos >= 0 && raw[pos] == ' ') pos--; while (pos >= 0 && raw[pos] != ch) pos--; return pos; } private static Charset charsetForAlias(String name) { return encodingAliases.get(StringUtils.toLowerCase(name)); } private RawParseUtils() { // Don't create instances of a static only utility. } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2837 Content-Disposition: inline; filename="RawSubStringPattern.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "04fdcd0fa49f4f6c64f09d328e97ed9e7a530231" /* * Copyright (C) 2009, Google Inc. * 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.util; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; /** * Searches text using only substring search. *

* Instances are thread-safe. Multiple concurrent threads may perform matches on * different character sequences at the same time. */ public class RawSubStringPattern { private final String needleString; private final byte[] needle; /** * Construct a new substring pattern. * * @param patternText * text to locate. This should be a literal string, as no * meta-characters are supported by this implementation. The * string may not be the empty string. */ public RawSubStringPattern(String patternText) { if (patternText.length() == 0) throw new IllegalArgumentException(JGitText.get().cannotMatchOnEmptyString); needleString = patternText; final byte[] b = Constants.encode(patternText); needle = new byte[b.length]; for (int i = 0; i < b.length; i++) needle[i] = lc(b[i]); } /** * Match a character sequence against this pattern. * * @param rcs * the sequence to match. Must not be null but the length of the * sequence is permitted to be 0. * @return offset within rcs of the first occurrence of this * pattern; -1 if this pattern does not appear at any position of * rcs. */ public int match(RawCharSequence rcs) { final int needleLen = needle.length; final byte first = needle[0]; final byte[] text = rcs.buffer; int matchPos = rcs.startPtr; final int maxPos = rcs.endPtr - needleLen; OUTER: for (; matchPos <= maxPos; matchPos++) { if (neq(first, text[matchPos])) { while (++matchPos <= maxPos && neq(first, text[matchPos])) { /* skip */ } if (matchPos > maxPos) return -1; } int si = matchPos + 1; for (int j = 1; j < needleLen; j++, si++) { if (neq(needle[j], text[si])) continue OUTER; } return matchPos; } return -1; } private static final boolean neq(byte a, byte b) { return a != b && a != lc(b); } private static final byte lc(byte q) { return (byte) StringUtils.toLowerCase((char) (q & 0xff)); } /** * Get the literal pattern string this instance searches for. * * @return the pattern string given to our constructor. */ public String pattern() { return needleString; } @Override public String toString() { return pattern(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12764 Content-Disposition: inline; filename="RefList.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "350ec76cf96ff10e1f157fdee3010949eaeb06b1" /* * 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.util; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.function.BinaryOperator; import java.util.stream.Collector; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; /** * Specialized variant of an ArrayList to support a {@code RefDatabase}. *

* This list is a hybrid of a Map<String,Ref> and of a List<Ref>. It * tracks reference instances by name by keeping them sorted and performing * binary search to locate an entry. Lookup time is O(log N), but addition and * removal is O(N + log N) due to the list expansion or contraction costs. *

* This list type is copy-on-write. Mutation methods return a new copy of the * list, leaving {@code this} unmodified. As a result we cannot easily implement * the {@link java.util.List} interface contract. * * @param * the type of reference being stored in the collection. */ public class RefList implements Iterable { private static final RefList EMPTY = new RefList<>(new Ref[0], 0); /** * Create an empty unmodifiable reference list. * * @param * type of reference being stored. * @return an empty unmodifiable reference list. */ @SuppressWarnings("unchecked") public static RefList emptyList() { return (RefList) EMPTY; } final Ref[] list; final int cnt; RefList(Ref[] list, int cnt) { this.list = list; this.cnt = cnt; } /** * Initialize this list to use the same backing array as another list. * * @param src * the source list. */ protected RefList(RefList src) { this.list = src.list; this.cnt = src.cnt; } @Override public Iterator iterator() { return new Iterator<>() { private int idx; @Override public boolean hasNext() { return idx < cnt; } @Override public Ref next() { if (idx < cnt) return list[idx++]; throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Cast {@code this} as an immutable, standard {@link java.util.List}. * * @return {@code this} as an immutable, standard {@link java.util.List}. */ public final List asList() { final List r = Arrays.asList(list).subList(0, cnt); return Collections.unmodifiableList(r); } /** * Get number of items in this list. * * @return number of items in this list. */ public final int size() { return cnt; } /** * Get if this list is empty. * * @return true if the size of this list is 0. */ public final boolean isEmpty() { return cnt == 0; } /** * Locate an entry by name. * * @param name * the name of the reference to find. * @return the index the reference is at. If the entry is not present * returns a negative value. The insertion position for the given * name can be computed from {@code -(index + 1)}. */ public final int find(String name) { int high = cnt; if (high == 0) return -1; int low = 0; do { final int mid = (low + high) >>> 1; final int cmp = RefComparator.compareTo(list[mid], name); if (cmp < 0) low = mid + 1; else if (cmp == 0) return mid; else high = mid; } while (low < high); return -(low + 1); } /** * Determine if a reference is present. * * @param name * name of the reference to find. * @return true if the reference is present; false if it is not. */ public final boolean contains(String name) { return 0 <= find(name); } /** * Get a reference object by name. * * @param name * the name of the reference. * @return the reference object; null if it does not exist in this list. */ public final T get(String name) { int idx = find(name); return 0 <= idx ? get(idx) : null; } /** * Get the reference at a particular index. * * @param idx * the index to obtain. Must be {@code 0 <= idx < size()}. * @return the reference value, never null. */ @SuppressWarnings("unchecked") public final T get(int idx) { return (T) list[idx]; } /** * Obtain a builder initialized with the first {@code n} elements. *

* Copies the first {@code n} elements from this list into a new builder, * which can be used by the caller to add additional elements. * * @param n * the number of elements to copy. * @return a new builder with the first {@code n} elements already added. */ public final Builder copy(int n) { Builder r = new Builder<>(Math.max(16, n)); r.addAll(list, 0, n); return r; } /** * Obtain a new copy of the list after changing one element. *

* This list instance is not affected by the replacement. Because this * method copies the entire list, it runs in O(N) time. * * @param idx * index of the element to change. * @param ref * the new value, must not be null. * @return copy of this list, after replacing {@code idx} with {@code ref} . */ public final RefList set(int idx, T ref) { Ref[] newList = new Ref[cnt]; System.arraycopy(list, 0, newList, 0, cnt); newList[idx] = ref; return new RefList<>(newList, cnt); } /** * Add an item at a specific index. *

* This list instance is not affected by the addition. Because this method * copies the entire list, it runs in O(N) time. * * @param idx * position to add the item at. If negative the method assumes it * was a direct return value from {@link #find(String)} and will * adjust it to the correct position. * @param ref * the new reference to insert. * @return copy of this list, after making space for and adding {@code ref}. */ public final RefList add(int idx, T ref) { if (idx < 0) idx = -(idx + 1); Ref[] newList = new Ref[cnt + 1]; if (0 < idx) System.arraycopy(list, 0, newList, 0, idx); newList[idx] = ref; if (idx < cnt) System.arraycopy(list, idx, newList, idx + 1, cnt - idx); return new RefList<>(newList, cnt + 1); } /** * Remove an item at a specific index. *

* This list instance is not affected by the addition. Because this method * copies the entire list, it runs in O(N) time. * * @param idx * position to remove the item from. * @return copy of this list, after making removing the item at {@code idx}. */ public final RefList remove(int idx) { if (cnt == 1) return emptyList(); Ref[] newList = new Ref[cnt - 1]; if (0 < idx) System.arraycopy(list, 0, newList, 0, idx); if (idx + 1 < cnt) System.arraycopy(list, idx + 1, newList, idx, cnt - (idx + 1)); return new RefList<>(newList, cnt - 1); } /** * Store a reference, adding or replacing as necessary. *

* This list instance is not affected by the store. The correct position is * determined, and the item is added if missing, or replaced if existing. * Because this method copies the entire list, it runs in O(N + log N) time. * * @param ref * the reference to store. * @return copy of this list, after performing the addition or replacement. */ public final RefList put(T ref) { int idx = find(ref.getName()); if (0 <= idx) return set(idx, ref); return add(idx, ref); } @Override public String toString() { StringBuilder r = new StringBuilder(); r.append('['); if (cnt > 0) { r.append(list[0]); for (int i = 1; i < cnt; i++) { r.append(", "); //$NON-NLS-1$ r.append(list[i]); } } r.append(']'); return r.toString(); } /** * Create a {@link Collector} for {@link Ref}. * * @param * type of reference being stored. * @param mergeFunction * if specified the result will be sorted and deduped. * @return {@link Collector} for {@link Ref} * @since 5.4 */ public static Collector> toRefList( @Nullable BinaryOperator mergeFunction) { return Collector.of( () -> new Builder<>(), Builder::add, (b1, b2) -> { Builder b = new Builder<>(); b.addAll(b1); b.addAll(b2); return b; }, (b) -> { if (mergeFunction != null) { b.sort(); b.dedupe(mergeFunction); } return b.toRefList(); }); } /** * Builder to facilitate fast construction of an immutable RefList. * * @param * type of reference being stored. */ public static class Builder { private Ref[] list; private int size; /** Create an empty list ready for items to be added. */ public Builder() { this(16); } /** * Create an empty list with at least the specified capacity. * * @param capacity * the new capacity; if zero or negative, behavior is the same as * {@link #Builder()}. */ public Builder(int capacity) { list = new Ref[Math.max(capacity, 16)]; } /** * Get size * * @return number of items in this builder's internal collection. */ public int size() { return size; } /** * Get the reference at a particular index. * * @param idx * the index to obtain. Must be {@code 0 <= idx < size()}. * @return the reference value, never null. */ @SuppressWarnings("unchecked") public T get(int idx) { return (T) list[idx]; } /** * Remove an item at a specific index. * * @param idx * position to remove the item from. */ public void remove(int idx) { System.arraycopy(list, idx + 1, list, idx, size - (idx + 1)); size--; } /** * Add the reference to the end of the array. *

* References must be added in sort order, or the array must be sorted * after additions are complete using {@link #sort()}. * * @param ref * reference to add */ public void add(T ref) { if (list.length == size) { Ref[] n = new Ref[size * 2]; System.arraycopy(list, 0, n, 0, size); list = n; } list[size++] = ref; } /** * Add all items from another builder. * * @param other * another builder * @since 5.4 */ public void addAll(Builder other) { addAll(other.list, 0, other.size); } /** * Add all items from a source array. *

* References must be added in sort order, or the array must be sorted * after additions are complete using {@link #sort()}. * * @param src * the source array. * @param off * position within {@code src} to start copying from. * @param cnt * number of items to copy from {@code src}. */ public void addAll(Ref[] src, int off, int cnt) { if (list.length < size + cnt) { Ref[] n = new Ref[Math.max(size * 2, size + cnt)]; System.arraycopy(list, 0, n, 0, size); list = n; } System.arraycopy(src, off, list, size, cnt); size += cnt; } /** * Replace a single existing element. * * @param idx * index, must have already been added previously. * @param ref * the new reference. */ public void set(int idx, T ref) { list[idx] = ref; } /** Sort the list's backing array in-place. */ public void sort() { Arrays.sort(list, 0, size, RefComparator.INSTANCE); } /** * Dedupe the refs in place. Must be called after {@link #sort}. * * @param mergeFunction * function used for de-duplication */ @SuppressWarnings("unchecked") void dedupe(BinaryOperator mergeFunction) { if (size == 0) { return; } int lastElement = 0; for (int i = 1; i < size; i++) { if (RefComparator.INSTANCE.compare(list[lastElement], list[i]) == 0) { list[lastElement] = mergeFunction .apply((T) list[lastElement], (T) list[i]); } else { list[lastElement + 1] = list[i]; lastElement++; } } size = lastElement + 1; Arrays.fill(list, size, list.length, null); } /** * Get unmodifiable list based on this list * * @return an unmodifiable list using this collection's backing array. */ public RefList toRefList() { return new RefList<>(list, size); } @Override public String toString() { return toRefList().toString(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10702 Content-Disposition: inline; filename="RefMap.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "a4d1fd5b70453a48b6997e0bd5faa029b1a11146" /* * 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.util; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.BinaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; /** * Specialized Map to present a {@code RefDatabase} namespace. *

* Although not declared as a {@link java.util.SortedMap}, iterators from this * map's projections always return references in * {@link org.eclipse.jgit.lib.RefComparator} ordering. The map's internal * representation is a sorted array of {@link org.eclipse.jgit.lib.Ref} objects, * which means lookup and replacement is O(log N), while insertion and removal * can be as expensive as O(N + log N) while the list expands or contracts. * Since this is not a general map implementation, all entries must be keyed by * the reference name. *

* This class is really intended as a helper for {@code RefDatabase}, which * needs to perform a merge-join of three sorted * {@link org.eclipse.jgit.util.RefList}s in order to present the unified * namespace of the packed-refs file, the loose refs/ directory tree, and the * resolved form of any symbolic references. */ public class RefMap extends AbstractMap { /** * Prefix denoting the reference subspace this map contains. *

* All reference names in this map must start with this prefix. If the * prefix is not the empty string, it must end with a '/'. */ final String prefix; /** Immutable collection of the packed references at construction time. */ RefList packed; /** * Immutable collection of the loose references at construction time. *

* If an entry appears here and in {@link #packed}, this entry must take * precedence, as its more current. Symbolic references in this collection * are typically unresolved, so they only tell us who their target is, but * not the current value of the target. */ RefList loose; /** * Immutable collection of resolved symbolic references. *

* This collection contains only the symbolic references we were able to * resolve at map construction time. Other loose references must be read * from {@link #loose}. Every entry in this list must be matched by an entry * in {@code loose}, otherwise it might be omitted by the map. */ RefList resolved; int size; boolean sizeIsValid; private Set> entrySet; /** * Construct an empty map with a small initial capacity. */ public RefMap() { prefix = ""; //$NON-NLS-1$ packed = RefList.emptyList(); loose = RefList.emptyList(); resolved = RefList.emptyList(); } /** * Construct a map to merge 3 collections together. * * @param prefix * prefix used to slice the lists down. Only references whose * names start with this prefix will appear to reside in the map. * Must not be null, use {@code ""} (the empty string) to select * all list items. * @param packed * items from the packed reference list, this is the last list * searched. * @param loose * items from the loose reference list, this list overrides * {@code packed} if a name appears in both. * @param resolved * resolved symbolic references. This list overrides the prior * list {@code loose}, if an item appears in both. Items in this * list must also appear in {@code loose}. */ @SuppressWarnings("unchecked") public RefMap(String prefix, RefList packed, RefList loose, RefList resolved) { this.prefix = prefix; this.packed = (RefList) packed; this.loose = (RefList) loose; this.resolved = (RefList) resolved; } @Override public boolean containsKey(Object name) { return get(name) != null; } @Override public Ref get(Object key) { String name = toRefName((String) key); Ref ref = resolved.get(name); if (ref == null) ref = loose.get(name); if (ref == null) ref = packed.get(name); return ref; } @Override public Ref put(String keyName, Ref value) { String name = toRefName(keyName); if (!name.equals(value.getName())) throw new IllegalArgumentException(); if (!resolved.isEmpty()) { // Collapse the resolved list into the loose list so we // can discard it and stop joining the two together. for (Ref ref : resolved) loose = loose.put(ref); resolved = RefList.emptyList(); } int idx = loose.find(name); if (0 <= idx) { Ref prior = loose.get(name); loose = loose.set(idx, value); return prior; } Ref prior = get(keyName); loose = loose.add(idx, value); sizeIsValid = false; return prior; } @Override public Ref remove(Object key) { String name = toRefName((String) key); Ref res = null; int idx; if (0 <= (idx = packed.find(name))) { res = packed.get(name); packed = packed.remove(idx); sizeIsValid = false; } if (0 <= (idx = loose.find(name))) { res = loose.get(name); loose = loose.remove(idx); sizeIsValid = false; } if (0 <= (idx = resolved.find(name))) { res = resolved.get(name); resolved = resolved.remove(idx); sizeIsValid = false; } return res; } @Override public boolean isEmpty() { return entrySet().isEmpty(); } @Override public Set> entrySet() { if (entrySet == null) { entrySet = new AbstractSet<>() { @Override public Iterator> iterator() { return new SetIterator(); } @Override public int size() { if (!sizeIsValid) { size = 0; Iterator i = entrySet().iterator(); for (; i.hasNext(); i.next()) size++; sizeIsValid = true; } return size; } @Override public boolean isEmpty() { if (sizeIsValid) return 0 == size; return !iterator().hasNext(); } @Override public void clear() { packed = RefList.emptyList(); loose = RefList.emptyList(); resolved = RefList.emptyList(); size = 0; sizeIsValid = true; } }; } return entrySet; } @Override public String toString() { StringBuilder r = new StringBuilder(); boolean first = true; r.append('['); for (Ref ref : values()) { if (first) first = false; else r.append(", "); //$NON-NLS-1$ r.append(ref); } r.append(']'); return r.toString(); } /** * Create a {@link Collector} for {@link Ref}. * * @param mergeFunction * merge function * @return {@link Collector} for {@link Ref} * @since 5.4 */ public static Collector toRefMap( BinaryOperator mergeFunction) { return Collectors.collectingAndThen(RefList.toRefList(mergeFunction), (refs) -> new RefMap("", //$NON-NLS-1$ refs, RefList.emptyList(), RefList.emptyList())); } private String toRefName(String name) { if (0 < prefix.length()) name = prefix + name; return name; } String toMapKey(Ref ref) { String name = ref.getName(); if (0 < prefix.length()) name = name.substring(prefix.length()); return name; } private class SetIterator implements Iterator> { private int packedIdx; private int looseIdx; private int resolvedIdx; private Entry next; SetIterator() { if (0 < prefix.length()) { packedIdx = -(packed.find(prefix) + 1); looseIdx = -(loose.find(prefix) + 1); resolvedIdx = -(resolved.find(prefix) + 1); } } @Override public boolean hasNext() { if (next == null) next = peek(); return next != null; } @Override public Entry next() { if (hasNext()) { Entry r = next; next = peek(); return r; } throw new NoSuchElementException(); } public Entry peek() { if (packedIdx < packed.size() && looseIdx < loose.size()) { Ref p = packed.get(packedIdx); Ref l = loose.get(looseIdx); int cmp = RefComparator.compareTo(p, l); if (cmp < 0) { packedIdx++; return toEntry(p); } if (cmp == 0) packedIdx++; looseIdx++; return toEntry(resolveLoose(l)); } if (looseIdx < loose.size()) return toEntry(resolveLoose(loose.get(looseIdx++))); if (packedIdx < packed.size()) return toEntry(packed.get(packedIdx++)); return null; } private Ref resolveLoose(Ref l) { if (resolvedIdx < resolved.size()) { Ref r = resolved.get(resolvedIdx); int cmp = RefComparator.compareTo(l, r); if (cmp == 0) { resolvedIdx++; return r; } else if (cmp > 0) { // WTF, we have a symbolic entry but no match // in the loose collection. That's an error. throw new IllegalStateException(); } } return l; } private Ent toEntry(Ref p) { if (p.getName().startsWith(prefix)) return new Ent(p); packedIdx = packed.size(); looseIdx = loose.size(); resolvedIdx = resolved.size(); return null; } @Override public void remove() { throw new UnsupportedOperationException(); } } private class Ent implements Entry { private Ref ref; Ent(Ref ref) { this.ref = ref; } @Override public String getKey() { return toMapKey(ref); } @Override public Ref getValue() { return ref; } @Override public Ref setValue(Ref value) { Ref prior = put(getKey(), value); ref = value; return prior; } @Override public int hashCode() { return getKey().hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Map.Entry) { final Object key = ((Map.Entry) obj).getKey(); final Object val = ((Map.Entry) obj).getValue(); if (key instanceof String && val instanceof Ref) { final Ref r = (Ref) val; if (r.getName().equals(ref.getName())) { final ObjectId a = r.getObjectId(); final ObjectId b = ref.getObjectId(); if (a != null && b != null && AnyObjectId.isEqual(a, b)) { return true; } } } } return false; } @Override public String toString() { return ref.toString(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 843 Content-Disposition: inline; filename="References.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "b75e591212d5aa32286e06e0e70bbc3bd196c4ee" /* * Copyright (C) 2019, 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.util; /** * Utility methods for object references * * @since 5.4 */ public interface References { /** * Compare two references * * @param * type of the references * @param ref1 * first reference * @param ref2 * second reference * @return {@code true} if both references refer to the same object */ @SuppressWarnings("ReferenceEquality") public static boolean isSameObject(T ref1, T ref2) { return ref1 == ref2; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4251 Content-Disposition: inline; filename="RelativeDateFormatter.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "b6b19e0e7b4550573099c984eb2be394ea521fe2" /* * Copyright (C) 2011, 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.util; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; import java.util.Date; import org.eclipse.jgit.internal.JGitText; /** * Formatter to format timestamps relative to the current time using time units * in the format defined by {@code git log --relative-date}. */ public class RelativeDateFormatter { static final long SECOND_IN_MILLIS = 1000; static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; static final long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS; static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS; static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS; static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS; /** * Get age of given {@link java.util.Date} compared to now formatted in the * same relative format as returned by {@code git log --relative-date} * * @param when * {@link java.util.Date} to format * @return age of given {@link java.util.Date} compared to now formatted in * the same relative format as returned by * {@code git log --relative-date} * @deprecated Use {@link #format(Instant)} instead. */ @Deprecated(since = "7.2") @SuppressWarnings("boxing") public static String format(Date when) { return format(when.toInstant()); } /** * Get age of given {@link java.time.Instant} compared to now formatted in the * same relative format as returned by {@code git log --relative-date} * * @param when * an instant to format * @return age of given instant compared to now formatted in * the same relative format as returned by * {@code git log --relative-date} * @since 7.2 */ @SuppressWarnings("boxing") public static String format(Instant when) { long ageMillis = Duration .between(when, SystemReader.getInstance().now()).toMillis(); // shouldn't happen in a perfect world if (ageMillis < 0) return JGitText.get().inTheFuture; // seconds if (ageMillis < upperLimit(MINUTE_IN_MILLIS)) return MessageFormat.format(JGitText.get().secondsAgo, round(ageMillis, SECOND_IN_MILLIS)); // minutes if (ageMillis < upperLimit(HOUR_IN_MILLIS)) return MessageFormat.format(JGitText.get().minutesAgo, round(ageMillis, MINUTE_IN_MILLIS)); // hours if (ageMillis < upperLimit(DAY_IN_MILLIS)) return MessageFormat.format(JGitText.get().hoursAgo, round(ageMillis, HOUR_IN_MILLIS)); // up to 14 days use days if (ageMillis < 14 * DAY_IN_MILLIS) return MessageFormat.format(JGitText.get().daysAgo, round(ageMillis, DAY_IN_MILLIS)); // up to 10 weeks use weeks if (ageMillis < 10 * WEEK_IN_MILLIS) return MessageFormat.format(JGitText.get().weeksAgo, round(ageMillis, WEEK_IN_MILLIS)); // months if (ageMillis < YEAR_IN_MILLIS) return MessageFormat.format(JGitText.get().monthsAgo, round(ageMillis, MONTH_IN_MILLIS)); // up to 5 years use "year, months" rounded to months if (ageMillis < 5 * YEAR_IN_MILLIS) { long years = round(ageMillis, MONTH_IN_MILLIS) / 12; String yearLabel = (years > 1) ? JGitText.get().years : // JGitText.get().year; long months = round(ageMillis - years * YEAR_IN_MILLIS, MONTH_IN_MILLIS); String monthLabel = (months > 1) ? JGitText.get().months : // (months == 1 ? JGitText.get().month : ""); //$NON-NLS-1$ return MessageFormat.format( months == 0 ? JGitText.get().years0MonthsAgo : JGitText .get().yearsMonthsAgo, new Object[] { years, yearLabel, months, monthLabel }); } // years return MessageFormat.format(JGitText.get().yearsAgo, round(ageMillis, YEAR_IN_MILLIS)); } private static long upperLimit(long unit) { long limit = unit + unit / 2; return limit; } private static long round(long n, long unit) { long rounded = (n + unit / 2) / unit; return rounded; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2788 Content-Disposition: inline; filename="SignatureUtils.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "e3e3e04fd9efcc9d6dd79ee340c6b311d7852f07" /* * 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.util; import java.text.MessageFormat; import java.util.Locale; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; import org.eclipse.jgit.lib.SignatureVerifier.TrustLevel; import org.eclipse.jgit.lib.PersonIdent; /** * Utilities for signature verification. * * @since 5.11 */ public final class SignatureUtils { private SignatureUtils() { // No instantiation } /** * Writes information about a signature verification to a string. * * @param verification * to show * @param creator * of the object verified; used for time zone information * @param formatter * to use for dates * @return a textual representation of the {@link SignatureVerification}, * using LF as line separator * * @since 7.0 */ public static String toString(SignatureVerification verification, PersonIdent creator, GitDateFormatter formatter) { StringBuilder result = new StringBuilder(); if (verification.creationDate() != null) { // Use the creator's timezone for the signature date PersonIdent dateId = new PersonIdent(creator, verification.creationDate().toInstant()); result.append( MessageFormat.format(JGitText.get().verifySignatureMade, formatter.formatDate(dateId))); result.append('\n'); } result.append(MessageFormat.format( JGitText.get().verifySignatureKey, verification.keyFingerprint().toUpperCase(Locale.ROOT))); result.append('\n'); if (!StringUtils.isEmptyOrNull(verification.signer())) { result.append( MessageFormat.format(JGitText.get().verifySignatureIssuer, verification.signer())); result.append('\n'); } String msg; if (verification.verified()) { if (verification.expired()) { msg = JGitText.get().verifySignatureExpired; } else { msg = JGitText.get().verifySignatureGood; } } else { msg = JGitText.get().verifySignatureBad; } result.append(MessageFormat.format(msg, verification.keyUser())); if (!TrustLevel.UNKNOWN.equals(verification.trustLevel())) { result.append(' ' + MessageFormat .format(JGitText.get().verifySignatureTrust, verification .trustLevel().name().toLowerCase(Locale.ROOT))); } result.append('\n'); msg = verification.message(); if (!StringUtils.isEmptyOrNull(msg)) { result.append(msg); result.append('\n'); } return result.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6381 Content-Disposition: inline; filename="SimpleLruCache.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "a715a26a9e2b51609c2975c5c4f0fa4bbbf26628" /* * Copyright (C) 2019, Marc Strapetz * Copyright (C) 2019, 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.util; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.internal.JGitText; /** * Simple limited size cache based on ConcurrentHashMap purging entries in LRU * order when reaching size limit * * @param * the type of keys maintained by this cache * @param * the type of mapped values * * @since 5.1.9 */ public class SimpleLruCache { private static class Entry { private final K key; private final V value; // pseudo clock timestamp of the last access to this entry private volatile long lastAccessed; private long lastAccessedSorting; Entry(K key, V value, long lastAccessed) { this.key = key; this.value = value; this.lastAccessed = lastAccessed; } void copyAccessTime() { lastAccessedSorting = lastAccessed; } @SuppressWarnings("nls") @Override public String toString() { return "Entry [lastAccessed=" + lastAccessed + ", key=" + key + ", value=" + value + "]"; } } private Lock lock = new ReentrantLock(); private Map> map = new ConcurrentHashMap<>(); private volatile int maximumSize; private int purgeSize; // pseudo clock to implement LRU order of access to entries private volatile long time = 0L; private static void checkPurgeFactor(float purgeFactor) { if (purgeFactor <= 0 || purgeFactor >= 1) { throw new IllegalArgumentException( MessageFormat.format(JGitText.get().invalidPurgeFactor, Float.valueOf(purgeFactor))); } } private static int purgeSize(int maxSize, float purgeFactor) { return (int) ((1 - purgeFactor) * maxSize); } /** * Create a new cache * * @param maxSize * maximum size of the cache, to reduce need for synchronization * this is not a hard limit. The real size of the cache could be * slightly above this maximum if multiple threads put new values * concurrently * @param purgeFactor * when the size of the map reaches maxSize the oldest entries * will be purged to free up some space for new entries, * {@code purgeFactor} is the fraction of {@code maxSize} to * purge when this happens */ public SimpleLruCache(int maxSize, float purgeFactor) { checkPurgeFactor(purgeFactor); this.maximumSize = maxSize; this.purgeSize = purgeSize(maxSize, purgeFactor); } /** * Returns the value to which the specified key is mapped, or {@code null} * if this map contains no mapping for the key. * *

* More formally, if this cache contains a mapping from a key {@code k} to a * value {@code v} such that {@code key.equals(k)}, then this method returns * {@code v}; otherwise it returns {@code null}. (There can be at most one * such mapping.) * * @param key * the key * * @throws NullPointerException * if the specified key is null * * @return value mapped for this key, or {@code null} if no value is mapped */ @SuppressWarnings("NonAtomicVolatileUpdate") public V get(Object key) { Entry entry = map.get(key); if (entry != null) { entry.lastAccessed = tick(); return entry.value; } return null; } /** * Maps the specified key to the specified value in this cache. Neither the * key nor the value can be null. * *

* The value can be retrieved by calling the {@code get} method with a key * that is equal to the original key. * * @param key * key with which the specified value is to be associated * @param value * value to be associated with the specified key * @return the previous value associated with {@code key}, or {@code null} * if there was no mapping for {@code key} * @throws NullPointerException * if the specified key or value is null */ @SuppressWarnings("NonAtomicVolatileUpdate") public V put(@NonNull K key, @NonNull V value) { map.put(key, new Entry<>(key, value, tick())); if (map.size() > maximumSize) { purge(); } return value; } @SuppressWarnings("NonAtomicVolatileUpdate") private long tick() { return ++time; } /** * Returns the current size of this cache * * @return the number of key-value mappings in this cache */ public int size() { return map.size(); } /** * Reconfigures the cache. If {@code maxSize} is reduced some entries will * be purged. * * @param maxSize * maximum size of the cache * * @param purgeFactor * when the size of the map reaches maxSize the oldest entries * will be purged to free up some space for new entries, * {@code purgeFactor} is the fraction of {@code maxSize} to * purge when this happens */ public void configure(int maxSize, float purgeFactor) { lock.lock(); try { checkPurgeFactor(purgeFactor); this.maximumSize = maxSize; this.purgeSize = purgeSize(maxSize, purgeFactor); if (map.size() >= maximumSize) { purge(); } } finally { lock.unlock(); } } private void purge() { // don't try to compete if another thread already has the lock if (lock.tryLock()) { try { List entriesToPurge = new ArrayList<>(map.values()); // copy access times to avoid other threads interfere with // sorting for (Entry e : entriesToPurge) { e.copyAccessTime(); } Collections.sort(entriesToPurge, Comparator.comparingLong(o -> -o.lastAccessedSorting)); for (int index = purgeSize; index < entriesToPurge .size(); index++) { map.remove(entriesToPurge.get(index).key); } } finally { lock.unlock(); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4991 Content-Disposition: inline; filename="SshSupport.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "42a76b5b16f37bd0e96eb2980d4f1d8d9312f094" /* * Copyright (C) 2018, Markus Duft 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.util; import java.io.IOException; import java.text.MessageFormat; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.RemoteSession; import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; /** * Extra utilities to support usage of SSH. * * @since 5.0 */ public class SshSupport { /** * Utility to execute a remote SSH command and read the first line of * output. * * @param sshUri * the SSH remote URI * @param provider * the {@link CredentialsProvider} or null. * @param fs * the {@link FS} implementation passed to * {@link SshSessionFactory} * @param command * the remote command to execute. * @param timeout * a timeout in seconds. The timeout may be exceeded in corner * cases. * @return The entire output read from stdout. * @throws IOException * if an IO error occurred * @throws CommandFailedException * if the ssh command execution failed, error message contains * the content of stderr. */ public static String runSshCommand(URIish sshUri, @Nullable CredentialsProvider provider, FS fs, String command, int timeout) throws IOException, CommandFailedException { RemoteSession session = null; Process process = null; StreamCopyThread errorThread = null; StreamCopyThread outThread = null; CommandFailedException failure = null; @SuppressWarnings("resource") MessageWriter stderr = new MessageWriter(); @SuppressWarnings("resource") MessageWriter stdout = new MessageWriter(); String out; try { long start = System.nanoTime(); session = SshSessionFactory.getInstance().getSession(sshUri, provider, fs, 1000 * timeout); int commandTimeout = timeout; if (timeout > 0) { commandTimeout = checkTimeout(command, timeout, start); } process = session.exec(command, commandTimeout); if (timeout > 0) { commandTimeout = checkTimeout(command, timeout, start); } errorThread = new StreamCopyThread(process.getErrorStream(), stderr.getRawStream()); errorThread.start(); outThread = new StreamCopyThread(process.getInputStream(), stdout.getRawStream()); outThread.start(); try { boolean finished = false; if (timeout <= 0) { process.waitFor(); finished = true; } else { finished = process.waitFor(commandTimeout, TimeUnit.SECONDS); } if (finished) { out = stdout.toString(); } else { out = null; // still running after timeout } } catch (InterruptedException e) { out = null; // error } } finally { if (errorThread != null) { try { errorThread.halt(); } catch (InterruptedException e) { // Stop waiting and return anyway. } finally { errorThread = null; } } if (outThread != null) { try { outThread.halt(); } catch (InterruptedException e) { // Stop waiting and return anyway. } finally { outThread = null; } } if (process != null) { try { if (process.exitValue() != 0) { failure = new CommandFailedException( process.exitValue(), MessageFormat.format( JGitText.get().sshCommandFailed, command, stderr.toString())); } // It was successful after all out = stdout.toString(); } catch (IllegalThreadStateException e) { failure = new CommandFailedException(0, MessageFormat.format( JGitText.get().sshCommandTimeout, command, Integer.valueOf(timeout))); } process.destroy(); } stderr.close(); stdout.close(); if (session != null) { SshSessionFactory.getInstance().releaseSession(session); } } if (failure != null) { throw failure; } return out; } private static int checkTimeout(String command, int timeout, long since) throws CommandFailedException { long elapsed = System.nanoTime() - since; int newTimeout = timeout - (int) TimeUnit.NANOSECONDS.toSeconds(elapsed); if (newTimeout <= 0) { // All time used up for connecting the session throw new CommandFailedException(0, MessageFormat.format(JGitText.get().sshCommandTimeout, command, Integer.valueOf(timeout))); } return newTimeout; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2051 Content-Disposition: inline; filename="Stats.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "efa6e7ddc3b0c1f8f2d9726a8a3f2fce683b38cb" /* * Copyright (C) 2019, 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.util; /** * Simple double statistics, computed incrementally, variance and standard * deviation using Welford's online algorithm, see * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm * * @since 5.1.9 */ public class Stats { private int n = 0; private double avg = 0.0; private double min = 0.0; private double max = 0.0; private double sum = 0.0; /** * Add a value * * @param x * value */ public void add(double x) { n++; min = n == 1 ? x : Math.min(min, x); max = n == 1 ? x : Math.max(max, x); double d = x - avg; avg += d / n; sum += d * d * (n - 1) / n; } /** * Returns the number of added values * * @return the number of added values */ public int count() { return n; } /** * Returns the smallest value added * * @return the smallest value added */ public double min() { if (n < 1) { return Double.NaN; } return min; } /** * Returns the biggest value added * * @return the biggest value added */ public double max() { if (n < 1) { return Double.NaN; } return max; } /** * Returns the average of the added values * * @return the average of the added values */ public double avg() { if (n < 1) { return Double.NaN; } return avg; } /** * Returns the variance of the added values * * @return the variance of the added values */ public double var() { if (n < 2) { return Double.NaN; } return sum / (n - 1); } /** * Returns the standard deviation of the added values * * @return the standard deviation of the added values */ public double stddev() { return Math.sqrt(this.var()); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 15090 Content-Disposition: inline; filename="StringUtils.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "e381a3bcc9290059d54192d01739028e1cd999d8" /* * Copyright (C) 2009-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.util; import java.text.MessageFormat; import java.util.Collection; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; /** * Miscellaneous string comparison utility methods. */ public final class StringUtils { private static final String EMPTY = ""; //$NON-NLS-1$ private static final long KiB = 1024; private static final long MiB = 1024 * KiB; private static final long GiB = 1024 * MiB; private static final char[] LC; static { LC = new char['Z' + 1]; for (char c = 0; c < LC.length; c++) LC[c] = c; for (char c = 'A'; c <= 'Z'; c++) LC[c] = (char) ('a' + (c - 'A')); } private StringUtils() { // Do not create instances } /** * Convert the input to lowercase. *

* This method does not honor the JVM locale, but instead always behaves as * though it is in the US-ASCII locale. Only characters in the range 'A' * through 'Z' are converted. All other characters are left as-is, even if * they otherwise would have a lowercase character equivalent. * * @param c * the input character. * @return lowercase version of the input. */ public static char toLowerCase(char c) { return c <= 'Z' ? LC[c] : c; } /** * Convert the input string to lower case, according to the "C" locale. *

* This method does not honor the JVM locale, but instead always behaves as * though it is in the US-ASCII locale. Only characters in the range 'A' * through 'Z' are converted, all other characters are left as-is, even if * they otherwise would have a lowercase character equivalent. * * @param in * the input string. Must not be null. * @return a copy of the input string, after converting characters in the * range 'A'..'Z' to 'a'..'z'. */ public static String toLowerCase(String in) { final StringBuilder r = new StringBuilder(in.length()); for (int i = 0; i < in.length(); i++) r.append(toLowerCase(in.charAt(i))); return r.toString(); } /** * Borrowed from commons-lang StringUtils.capitalize() method. * *

* Capitalizes a String changing the first letter to title case as per * {@link java.lang.Character#toTitleCase(char)}. No other letters are * changed. *

*

* A null input String returns null. *

* * @param str * the String to capitalize, may be null * @return the capitalized String, null if null String input * @since 4.0 */ public static String capitalize(String str) { int strLen; if (str == null || (strLen = str.length()) == 0) { return str; } return new StringBuilder(strLen) .append(Character.toTitleCase(str.charAt(0))) .append(str.substring(1)).toString(); } /** * Test if two strings are equal, ignoring case. *

* This method does not honor the JVM locale, but instead always behaves as * though it is in the US-ASCII locale. * * @param a * first string to compare. * @param b * second string to compare. * @return true if a equals b */ public static boolean equalsIgnoreCase(String a, String b) { if (References.isSameObject(a, b)) { return true; } if (a.length() != b.length()) return false; for (int i = 0; i < a.length(); i++) { if (toLowerCase(a.charAt(i)) != toLowerCase(b.charAt(i))) return false; } return true; } /** * Compare two strings, ignoring case. *

* This method does not honor the JVM locale, but instead always behaves as * though it is in the US-ASCII locale. * * @param a * first string to compare. * @param b * second string to compare. * @since 2.0 * @return an int. */ public static int compareIgnoreCase(String a, String b) { for (int i = 0; i < a.length() && i < b.length(); i++) { int d = toLowerCase(a.charAt(i)) - toLowerCase(b.charAt(i)); if (d != 0) return d; } return a.length() - b.length(); } /** * Compare two strings, honoring case. *

* This method does not honor the JVM locale, but instead always behaves as * though it is in the US-ASCII locale. * * @param a * first string to compare. * @param b * second string to compare. * @since 2.0 * @return an int. */ public static int compareWithCase(String a, String b) { for (int i = 0; i < a.length() && i < b.length(); i++) { int d = a.charAt(i) - b.charAt(i); if (d != 0) return d; } return a.length() - b.length(); } /** * Parse a string as a standard Git boolean value. See * {@link #toBooleanOrNull(String)}. * * @param stringValue * the string to parse. * @return the boolean interpretation of {@code stringValue}. * @throws java.lang.IllegalArgumentException * if {@code stringValue} is not recognized as one of the standard * boolean names. */ public static boolean toBoolean(String stringValue) { if (stringValue == null) throw new NullPointerException(JGitText.get().expectedBooleanStringValue); final Boolean bool = toBooleanOrNull(stringValue); if (bool == null) throw new IllegalArgumentException(MessageFormat.format(JGitText.get().notABoolean, stringValue)); return bool.booleanValue(); } /** * Parse a string as a standard Git boolean value. *

* The terms {@code yes}, {@code true}, {@code 1}, {@code on} can all be * used to mean {@code true}. *

* The terms {@code no}, {@code false}, {@code 0}, {@code off} can all be * used to mean {@code false}. *

* Comparisons ignore case, via {@link #equalsIgnoreCase(String, String)}. * * @param stringValue * the string to parse. * @return the boolean interpretation of {@code value} or null in case the * string does not represent a boolean value */ public static Boolean toBooleanOrNull(String stringValue) { if (stringValue == null) return null; if (equalsIgnoreCase("yes", stringValue) //$NON-NLS-1$ || equalsIgnoreCase("true", stringValue) //$NON-NLS-1$ || equalsIgnoreCase("1", stringValue) //$NON-NLS-1$ || equalsIgnoreCase("on", stringValue)) //$NON-NLS-1$ return Boolean.TRUE; else if (equalsIgnoreCase("no", stringValue) //$NON-NLS-1$ || equalsIgnoreCase("false", stringValue) //$NON-NLS-1$ || equalsIgnoreCase("0", stringValue) //$NON-NLS-1$ || equalsIgnoreCase("off", stringValue)) //$NON-NLS-1$ return Boolean.FALSE; else return null; } /** * Join a collection of Strings together using the specified separator. * * @param parts * Strings to join * @param separator * used to join * @return a String with all the joined parts */ public static String join(Collection parts, String separator) { return StringUtils.join(parts, separator, separator); } /** * Join a collection of Strings together using the specified separator and a * lastSeparator which is used for joining the second last and the last * part. * * @param parts * Strings to join * @param separator * separator used to join all but the two last elements * @param lastSeparator * separator to use for joining the last two elements * @return a String with all the joined parts */ public static String join(Collection parts, String separator, String lastSeparator) { StringBuilder sb = new StringBuilder(); int i = 0; int lastIndex = parts.size() - 1; for (String part : parts) { sb.append(part); if (i == lastIndex - 1) { sb.append(lastSeparator); } else if (i != lastIndex) { sb.append(separator); } i++; } return sb.toString(); } /** * Remove the specified character from beginning and end of a string *

* If the character repeats, all copies * * @param str input string * @param c character to remove * @return the input string with c * @since 7.2 */ public static String trim(String str, char c) { if (str == null || str.length() == 0) { return str; } int endPos = str.length()-1; while (endPos >= 0 && str.charAt(endPos) == c) { endPos--; } // Whole string is c if (endPos == -1) { return EMPTY; } int startPos = 0; while (startPos < endPos && str.charAt(startPos) == c) { startPos++; } if (startPos == 0 && endPos == str.length()-1) { // No need to copy return str; } return str.substring(startPos, endPos+1); } /** * Appends {@link Constants#DOT_GIT_EXT} unless the given name already ends * with that suffix. * * @param name * to complete * @return the name ending with {@link Constants#DOT_GIT_EXT} * @since 6.1 */ public static String nameWithDotGit(String name) { if (name.endsWith(Constants.DOT_GIT_EXT)) { return name; } return name + Constants.DOT_GIT_EXT; } /** * Test if a string is empty or null. * * @param stringValue * the string to check * @return true if the string is null or empty */ public static boolean isEmptyOrNull(String stringValue) { return stringValue == null || stringValue.length() == 0; } /** * Replace CRLF, CR or LF with a single space. * * @param in * A string with line breaks * @return in without line breaks * @since 3.1 */ public static String replaceLineBreaksWithSpace(String in) { char[] buf = new char[in.length()]; int o = 0; for (int i = 0; i < buf.length; ++i) { char ch = in.charAt(i); switch (ch) { case '\r': if (i + 1 < buf.length && in.charAt(i + 1) == '\n') { buf[o++] = ' '; ++i; } else buf[o++] = ' '; break; case '\n': buf[o++] = ' '; break; default: buf[o++] = ch; break; } } return new String(buf, 0, o); } /** * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g' * indicating KiB, MiB, and GiB, respectively. The suffix may follow the * number with optional separation by one or more blanks. * * @param value * {@link String} to parse; with leading and trailing whitespace * ignored * @param positiveOnly * {@code true} to only accept positive numbers, {@code false} to * allow negative numbers, too * @return the value parsed * @throws NumberFormatException * if the {@code value} is not parseable, or beyond the range of * {@link Long} * @throws StringIndexOutOfBoundsException * if the string is empty or contains only whitespace, or * contains only the letter 'k', 'm', or 'g' * @since 6.0 */ public static long parseLongWithSuffix(@NonNull String value, boolean positiveOnly) throws NumberFormatException, StringIndexOutOfBoundsException { String n = value.strip(); if (n.isEmpty()) { throw new StringIndexOutOfBoundsException(); } long mul = 1; switch (n.charAt(n.length() - 1)) { case 'g': case 'G': mul = GiB; break; case 'm': case 'M': mul = MiB; break; case 'k': case 'K': mul = KiB; break; default: break; } if (mul > 1) { n = n.substring(0, n.length() - 1).trim(); } if (n.isEmpty()) { throw new StringIndexOutOfBoundsException(); } long number; if (positiveOnly) { number = Long.parseUnsignedLong(n); if (number < 0) { throw new NumberFormatException( MessageFormat.format(JGitText.get().valueExceedsRange, value, Long.class.getSimpleName())); } } else { number = Long.parseLong(n); } if (mul == 1) { return number; } try { return Math.multiplyExact(mul, number); } catch (ArithmeticException e) { NumberFormatException nfe = new NumberFormatException( e.getLocalizedMessage()); nfe.initCause(e); throw nfe; } } /** * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g' * indicating KiB, MiB, and GiB, respectively. The suffix may follow the * number with optional separation by blanks. * * @param value * {@link String} to parse; with leading and trailing whitespace * ignored * @param positiveOnly * {@code true} to only accept positive numbers, {@code false} to * allow negative numbers, too * @return the value parsed * @throws NumberFormatException * if the {@code value} is not parseable or beyond the range of * {@link Integer} * @throws StringIndexOutOfBoundsException * if the string is empty or contains only whitespace, or * contains only the letter 'k', 'm', or 'g' * @since 6.0 */ public static int parseIntWithSuffix(@NonNull String value, boolean positiveOnly) throws NumberFormatException, StringIndexOutOfBoundsException { try { return Math.toIntExact(parseLongWithSuffix(value, positiveOnly)); } catch (ArithmeticException e) { NumberFormatException nfe = new NumberFormatException( MessageFormat.format(JGitText.get().valueExceedsRange, value, Integer.class.getSimpleName())); nfe.initCause(e); throw nfe; } } /** * Formats an integral value as a decimal number with 'k', 'm', or 'g' * suffix if it is an exact multiple of 1024, otherwise returns the value * representation as a decimal number without suffix. * * @param value * Value to format * @return the value's String representation * @since 6.0 */ public static String formatWithSuffix(long value) { if (value >= GiB && (value % GiB) == 0) { return String.valueOf(value / GiB) + 'g'; } if (value >= MiB && (value % MiB) == 0) { return String.valueOf(value / MiB) + 'm'; } if (value >= KiB && (value % KiB) == 0) { return String.valueOf(value / KiB) + 'k'; } return String.valueOf(value); } /** * Compares Strings and returns the initial sequence of characters that is * common to all of them. * * @param strings * Strings to consider * @return common prefix of all Strings * @since 6.8 */ public static @NonNull String commonPrefix(@Nullable String... strings) { if (strings == null || strings.length == 0) { return EMPTY; } String first = strings[0]; if (first == null) { return EMPTY; } if (strings.length == 1) { return first; } for (int i = 0; i < first.length(); i++) { char currentChar = first.charAt(i); for (int j = 1; j < strings.length; j++) { String str = strings[j]; if (str == null) { return EMPTY; } if (str.length() == i || currentChar != str.charAt(i)) { return str.substring(0, i); } } } return first; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 22395 Content-Disposition: inline; filename="SystemReader.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "0b7c6204f2fab9a71d8186f4b0c75f8bcae3fb78" /* * Copyright (C) 2009, Google Inc. * Copyright (C) 2009, Robin Rosenberg * Copyright (C) 2009, Yann Simon * Copyright (C) 2012, Daniel Megert 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.util; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.UserConfigFile; import org.eclipse.jgit.util.time.MonotonicClock; import org.eclipse.jgit.util.time.MonotonicSystemClock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Interface to read values from the system. *

* When writing unit tests, extending this interface with a custom class * permits to simulate an access to a system variable or property and * permits to control the user's global configuration. *

*/ public abstract class SystemReader { private static final Logger LOG = LoggerFactory .getLogger(SystemReader.class); private static final SystemReader DEFAULT; private static volatile Boolean isMacOS; private static volatile Boolean isWindows; private static volatile Boolean isLinux; private static final String GIT_TRACE_PERFORMANCE = "GIT_TRACE_PERFORMANCE"; //$NON-NLS-1$ private static final boolean performanceTrace = initPerformanceTrace(); private static boolean initPerformanceTrace() { String val = System.getenv(GIT_TRACE_PERFORMANCE); if (val == null) { val = System.getenv(GIT_TRACE_PERFORMANCE); } if (val != null) { return Boolean.valueOf(val).booleanValue(); } return false; } static { SystemReader r = new Default(); r.init(); DEFAULT = r; } private static class Default extends SystemReader { private volatile String hostname; @Override public String getenv(String variable) { return System.getenv(variable); } @Override public String getProperty(String key) { return System.getProperty(key); } @Override public FileBasedConfig openSystemConfig(Config parent, FS fs) { if (StringUtils .isEmptyOrNull(getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) { File configFile = fs.getGitSystemConfig(); if (configFile != null) { return new FileBasedConfig(parent, configFile, fs); } } return new FileBasedConfig(parent, null, fs) { @Override public void load() { // empty, do not load } @Override public boolean isOutdated() { // regular class would bomb here return false; } }; } @Override public FileBasedConfig openUserConfig(Config parent, FS fs) { File homeFile = new File(fs.userHome(), ".gitconfig"); //$NON-NLS-1$ Path xdgPath = getXdgConfigDirectory(fs); if (xdgPath != null) { Path configPath = xdgPath.resolve("git") //$NON-NLS-1$ .resolve(Constants.CONFIG); return new UserConfigFile(parent, homeFile, configPath.toFile(), fs); } return new FileBasedConfig(parent, homeFile, fs); } @Override public FileBasedConfig openJGitConfig(Config parent, FS fs) { Path xdgPath = getXdgConfigDirectory(fs); if (xdgPath != null) { Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$ .resolve(Constants.CONFIG); return new FileBasedConfig(parent, configPath.toFile(), fs); } return new FileBasedConfig(parent, new File(fs.userHome(), ".jgitconfig"), fs); //$NON-NLS-1$ } @Override public String getHostname() { if (hostname == null) { try { InetAddress localMachine = InetAddress.getLocalHost(); hostname = localMachine.getCanonicalHostName(); } catch (UnknownHostException e) { // we do nothing hostname = "localhost"; //$NON-NLS-1$ } assert hostname != null; } return hostname; } @Override public long getCurrentTime() { return System.currentTimeMillis(); } @Override public Instant now() { return Instant.now(); } @Override public int getTimezone(long when) { return getTimeZone().getOffset(when) / (60 * 1000); } } /** * Delegating SystemReader. Reduces boiler-plate code applications need to * implement when overriding only a few of the SystemReader's methods. * * @since 6.9 */ public static class Delegate extends SystemReader { private final SystemReader delegate; /** * Create a delegating system reader * * @param delegate * the system reader to delegate to */ public Delegate(SystemReader delegate) { this.delegate = delegate; } @Override public String getHostname() { return delegate.getHostname(); } @Override public String getenv(String variable) { return delegate.getenv(variable); } @Override public String getProperty(String key) { return delegate.getProperty(key); } @Override public FileBasedConfig openUserConfig(Config parent, FS fs) { return delegate.openUserConfig(parent, fs); } @Override public FileBasedConfig openSystemConfig(Config parent, FS fs) { return delegate.openSystemConfig(parent, fs); } @Override public FileBasedConfig openJGitConfig(Config parent, FS fs) { return delegate.openJGitConfig(parent, fs); } @Override public long getCurrentTime() { return delegate.getCurrentTime(); } @Override public Instant now() { return delegate.now(); } @Override public int getTimezone(long when) { return delegate.getTimezone(when); } @Override public ZoneOffset getTimeZoneAt(Instant when) { return delegate.getTimeZoneAt(when); } } private static volatile SystemReader INSTANCE = DEFAULT; /** * Get the current SystemReader instance * * @return the current SystemReader instance. */ public static SystemReader getInstance() { return INSTANCE; } /** * Set a new SystemReader instance to use when accessing properties. * * @param newReader * the new instance to use when accessing properties, or null for * the default instance. */ public static void setInstance(SystemReader newReader) { isMacOS = null; isWindows = null; isLinux = null; if (newReader == null) INSTANCE = DEFAULT; else { newReader.init(); INSTANCE = newReader; } } private ObjectChecker platformChecker; private AtomicReference systemConfig = new AtomicReference<>(); private AtomicReference userConfig = new AtomicReference<>(); private AtomicReference jgitConfig = new AtomicReference<>(); private volatile Charset defaultCharset; private void init() { // Creating ObjectChecker must be deferred. Unit tests change // behavior of is{Windows,MacOS} in constructor of subclass. if (platformChecker == null) setPlatformChecker(); } /** * Should be used in tests when the platform is explicitly changed. * * @since 3.6 */ protected final void setPlatformChecker() { platformChecker = new ObjectChecker() .setSafeForWindows(isWindows()) .setSafeForMacOS(isMacOS()); } /** * Gets the hostname of the local host. If no hostname can be found, the * hostname is set to the default value "localhost". * * @return the canonical hostname */ public abstract String getHostname(); /** * Get value of the system variable * * @param variable * system variable to read * @return value of the system variable */ public abstract String getenv(String variable); /** * Get value of the system property * * @param key * of the system property to read * @return value of the system property */ public abstract String getProperty(String key); /** * Open the git configuration found in the user home. Use * {@link #getUserConfig()} to get the current git configuration in the user * home since it manages automatic reloading when the gitconfig file was * modified and avoids unnecessary reloads. * * @param parent * a config with values not found directly in the returned config * @param fs * the file system abstraction which will be necessary to perform * certain file system operations. * @return the git configuration found in the user home */ public abstract FileBasedConfig openUserConfig(Config parent, FS fs); /** * Open the gitconfig configuration found in the system-wide "etc" * directory. Use {@link #getSystemConfig()} to get the current system-wide * git configuration since it manages automatic reloading when the gitconfig * file was modified and avoids unnecessary reloads. * * @param parent * a config with values not found directly in the returned * config. Null is a reasonable value here. * @param fs * the file system abstraction which will be necessary to perform * certain file system operations. * @return the gitconfig configuration found in the system-wide "etc" * directory */ public abstract FileBasedConfig openSystemConfig(Config parent, FS fs); /** * Open the jgit configuration located at $XDG_CONFIG_HOME/jgit/config. Use * {@link #getJGitConfig()} to get the current jgit configuration in the * user home since it manages automatic reloading when the jgit config file * was modified and avoids unnecessary reloads. * * @param parent * a config with values not found directly in the returned config * @param fs * the file system abstraction which will be necessary to perform * certain file system operations. * @return the jgit configuration located at $XDG_CONFIG_HOME/jgit/config * @since 5.5.2 */ public abstract FileBasedConfig openJGitConfig(Config parent, FS fs); /** * Get the git configuration found in the user home. The configuration will * be reloaded automatically if the configuration file was modified. Also * reloads the system config if the system config file was modified. If the * configuration file wasn't modified returns the cached configuration. * * @return the git configuration found in the user home * @throws ConfigInvalidException * if configuration is invalid * @throws IOException * if something went wrong when reading files * @since 5.1.9 */ public StoredConfig getUserConfig() throws ConfigInvalidException, IOException { FileBasedConfig c = userConfig.get(); if (c == null) { userConfig.compareAndSet(null, openUserConfig(getSystemConfig(), FS.DETECTED)); c = userConfig.get(); } // on the very first call this will check a second time if the system // config is outdated updateAll(c); return c; } /** * Get the jgit configuration located at $XDG_CONFIG_HOME/jgit/config. The * configuration will be reloaded automatically if the configuration file * was modified. If the configuration file wasn't modified returns the * cached configuration. * * @return the jgit configuration located at $XDG_CONFIG_HOME/jgit/config * @throws ConfigInvalidException * if configuration is invalid * @throws IOException * if something went wrong when reading files * @since 5.5.2 */ public StoredConfig getJGitConfig() throws ConfigInvalidException, IOException { FileBasedConfig c = jgitConfig.get(); if (c == null) { jgitConfig.compareAndSet(null, openJGitConfig(null, FS.DETECTED)); c = jgitConfig.get(); } updateAll(c); return c; } /** * Get the gitconfig configuration found in the system-wide "etc" directory. * The configuration will be reloaded automatically if the configuration * file was modified otherwise returns the cached system level config. * * @return the gitconfig configuration found in the system-wide "etc" * directory * @throws ConfigInvalidException * if configuration is invalid * @throws IOException * if something went wrong when reading files * @since 5.1.9 */ public StoredConfig getSystemConfig() throws ConfigInvalidException, IOException { FileBasedConfig c = systemConfig.get(); if (c == null) { systemConfig.compareAndSet(null, openSystemConfig(getJGitConfig(), FS.DETECTED)); c = systemConfig.get(); } updateAll(c); return c; } /** * Gets the directory denoted by environment variable XDG_CONFIG_HOME. If * the variable is not set or empty, return a path for * {@code $HOME/.config}. * * @param fileSystem * {@link FS} to get the user's home directory * @return a {@link Path} denoting the directory, which may exist or not, or * {@code null} if the environment variable is not set and there is * no home directory, or the path is invalid. * @since 6.7 */ public Path getXdgConfigDirectory(FS fileSystem) { String configHomePath = getenv(Constants.XDG_CONFIG_HOME); if (StringUtils.isEmptyOrNull(configHomePath)) { File home = fileSystem.userHome(); if (home == null) { return null; } configHomePath = new File(home, ".config").getAbsolutePath(); //$NON-NLS-1$ } try { return Paths.get(configHomePath); } catch (InvalidPathException e) { LOG.error(JGitText.get().logXDGConfigHomeInvalid, configHomePath, e); } return null; } /** * Gets the directory denoted by environment variable XDG_CACHE_HOME. If * the variable is not set or empty, return a path for * {@code $HOME/.cache}. * * @param fileSystem * {@link FS} to get the user's home directory * @return a {@link Path} denoting the directory, which may exist or not, or * {@code null} if the environment variable is not set and there is * no home directory, or the path is invalid. * @since 7.3 */ public Path getXdgCacheDirectory(FS fileSystem) { String cacheHomePath = getenv(Constants.XDG_CACHE_HOME); if (StringUtils.isEmptyOrNull(cacheHomePath)) { File home = fileSystem.userHome(); if (home == null) { return null; } cacheHomePath = new File(home, ".cache").getAbsolutePath(); //$NON-NLS-1$ } try { return Paths.get(cacheHomePath); } catch (InvalidPathException e) { LOG.error(JGitText.get().logXDGCacheHomeInvalid, cacheHomePath, e); } return null; } /** * Update config and its parents if they seem modified * * @param config * configuration to reload if outdated * @throws ConfigInvalidException * if configuration is invalid * @throws IOException * if something went wrong when reading files */ private void updateAll(Config config) throws ConfigInvalidException, IOException { if (config == null) { return; } updateAll(config.getBaseConfig()); if (config instanceof FileBasedConfig) { FileBasedConfig cfg = (FileBasedConfig) config; if (cfg.isOutdated()) { LOG.debug("loading config {}", cfg); //$NON-NLS-1$ cfg.load(); } } } /** * Get the current system time * * @return the current system time * * @deprecated Use {@link #now()} */ @Deprecated(since = "7.1") public abstract long getCurrentTime(); /** * Get the current system time * * @return the current system time * * @since 7.1 */ public Instant now() { // Subclasses overriding getCurrentTime should keep working // TODO(ifrade): Once we remove getCurrentTime, use Instant.now() return Instant.ofEpochMilli(getCurrentTime()); } /** * Get "now" as civil time, in the System timezone * * @return the current system time * * @since 7.1 */ public LocalDateTime civilNow() { return LocalDateTime.ofInstant(now(), getTimeZoneId()); } /** * Get clock instance preferred by this system. * * @return clock instance preferred by this system. * @since 4.6 */ public MonotonicClock getClock() { return new MonotonicSystemClock(); } /** * Get the local time zone * * @param when * a system timestamp * @return the local time zone * * @deprecated Use {@link #getTimeZoneAt(Instant)} instead. */ @Deprecated(since = "7.1") public abstract int getTimezone(long when); /** * Get the local time zone offset at "when" time * * @param when * a system timestamp * @return the local time zone * @since 7.1 */ public ZoneOffset getTimeZoneAt(Instant when) { return getTimeZoneId().getRules().getOffset(when); } /** * Get system time zone, possibly mocked for testing * * @return system time zone, possibly mocked for testing * @since 1.2 * * @deprecated Use {@link #getTimeZoneId()} */ @Deprecated(since = "7.1") public TimeZone getTimeZone() { return TimeZone.getDefault(); } /** * Get system time zone, possibly mocked for testing * * @return system time zone, possibly mocked for testing * @since 7.1 */ public ZoneId getTimeZoneId() { return ZoneId.systemDefault(); } /** * Get the locale to use * * @return the locale to use * @since 1.2 */ public Locale getLocale() { return Locale.getDefault(); } /** * Retrieves the default {@link Charset} depending on the system locale. * * @return the {@link Charset} * @since 6.0 * @see JEP 400 */ public Charset getDefaultCharset() { Charset result = defaultCharset; if (result == null) { // JEP 400: Java 18 populates this system property. String encoding = getProperty("native.encoding"); //$NON-NLS-1$ try { if (!StringUtils.isEmptyOrNull(encoding)) { result = Charset.forName(encoding); } } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { LOG.error(JGitText.get().logInvalidDefaultCharset, encoding); } if (result == null) { // This is always UTF-8 on Java >= 18. result = Charset.defaultCharset(); } defaultCharset = result; } return result; } /** * Returns a simple date format instance as specified by the given pattern. * * @param pattern * the pattern as defined in * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)} * @return the simple date format * @since 2.0 */ public SimpleDateFormat getSimpleDateFormat(String pattern) { return new SimpleDateFormat(pattern); } /** * Returns a simple date format instance as specified by the given pattern. * * @param pattern * the pattern as defined in * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)} * @param locale * locale to be used for the {@code SimpleDateFormat} * @return the simple date format * @since 3.2 */ public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) { return new SimpleDateFormat(pattern, locale); } /** * Returns a date/time format instance for the given styles. * * @param dateStyle * the date style as specified in * {@link java.text.DateFormat#getDateTimeInstance(int, int)} * @param timeStyle * the time style as specified in * {@link java.text.DateFormat#getDateTimeInstance(int, int)} * @return the date format * @since 2.0 */ public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) { return DateFormat.getDateTimeInstance(dateStyle, timeStyle); } /** * Whether we are running on Windows. * * @return true if we are running on Windows. */ public boolean isWindows() { if (isWindows == null) { String osDotName = getOsName(); isWindows = Boolean.valueOf(osDotName.startsWith("Windows")); //$NON-NLS-1$ } return isWindows.booleanValue(); } /** * Whether we are running on Mac OS X * * @return true if we are running on Mac OS X */ public boolean isMacOS() { if (isMacOS == null) { String osDotName = getOsName(); isMacOS = Boolean.valueOf( "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName)); //$NON-NLS-1$ //$NON-NLS-2$ } return isMacOS.booleanValue(); } /** * Whether we are running on Linux. * * @return true if we are running on Linux. * @since 6.3 */ public boolean isLinux() { if (isLinux == null) { String osname = getOsName(); isLinux = Boolean.valueOf(osname.toLowerCase().startsWith("linux")); //$NON-NLS-1$ } return isLinux.booleanValue(); } /** * Whether performance trace is enabled * * @return whether performance trace is enabled * @since 6.5 */ public boolean isPerformanceTraceEnabled() { return performanceTrace; } private String getOsName() { return getProperty("os.name"); //$NON-NLS-1$ } /** * Check tree path entry for validity. *

* Scans a multi-directory path string such as {@code "src/main.c"}. * * @param path path string to scan. * @throws org.eclipse.jgit.errors.CorruptObjectException path is invalid. * @since 3.6 */ public void checkPath(String path) throws CorruptObjectException { platformChecker.checkPath(path); } /** * Check tree path entry for validity. *

* Scans a multi-directory path string such as {@code "src/main.c"}. * * @param path * path string to scan. * @throws org.eclipse.jgit.errors.CorruptObjectException * path is invalid. * @since 4.2 */ public void checkPath(byte[] path) throws CorruptObjectException { platformChecker.checkPath(path, 0, path.length); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 18263 Content-Disposition: inline; filename="TemporaryBuffer.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "fb893a66f02fb5d91ad0b4a08f2e9785f5c19764" /* * Copyright (C) 2008-2009, Google Inc. * 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.util; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; import java.util.ArrayList; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; /** * A fully buffered output stream. *

* Subclasses determine the behavior when the in-memory buffer capacity has been * exceeded and additional bytes are still being received for output. */ public abstract class TemporaryBuffer extends OutputStream { /** Default limit for in-core storage. */ protected static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024; /** Chain of data, if we are still completely in-core; otherwise null. */ ArrayList blocks; /** * Maximum number of bytes we will permit storing in memory. *

* When this limit is reached the data will be shifted to a file on disk, * preventing the JVM heap from growing out of control. */ private int inCoreLimit; /** Initial size of block list. */ private int initialBlocks; /** If {@link #inCoreLimit} has been reached, remainder goes here. */ private OutputStream overflow; /** * Create a new empty temporary buffer. * * @param limit * maximum number of bytes to store in memory before entering the * overflow output path; also used as the estimated size. */ protected TemporaryBuffer(int limit) { this(limit, limit); } /** * Create a new empty temporary buffer. * * @param estimatedSize * estimated size of storage used, to size the initial list of * block pointers. * @param limit * maximum number of bytes to store in memory before entering the * overflow output path. * @since 4.0 */ protected TemporaryBuffer(int estimatedSize, int limit) { if (estimatedSize > limit) throw new IllegalArgumentException(); this.inCoreLimit = limit; this.initialBlocks = (estimatedSize - 1) / Block.SZ + 1; reset(); } /** {@inheritDoc} */ @Override public void write(int b) throws IOException { if (overflow != null) { overflow.write(b); return; } Block s = last(); if (s.isFull()) { if (reachedInCoreLimit()) { overflow.write(b); return; } s = new Block(); blocks.add(s); } s.buffer[s.count++] = (byte) b; } /** {@inheritDoc} */ @Override public void write(byte[] b, int off, int len) throws IOException { if (overflow == null) { while (len > 0) { Block s = last(); if (s.isFull()) { if (reachedInCoreLimit()) break; s = new Block(); blocks.add(s); } final int n = Math.min(s.buffer.length - s.count, len); System.arraycopy(b, off, s.buffer, s.count, n); s.count += n; len -= n; off += n; } } if (len > 0) overflow.write(b, off, len); } /** * Dumps the entire buffer into the overflow stream, and flushes it. * * @throws java.io.IOException * the overflow stream cannot be started, or the buffer contents * cannot be written to it, or it failed to flush. */ protected void doFlush() throws IOException { if (overflow == null) switchToOverflow(); overflow.flush(); } /** * Copy all bytes remaining on the input stream into this buffer. * * @param in * the stream to read from, until EOF is reached. * @throws java.io.IOException * an error occurred reading from the input stream, or while * writing to a local temporary file. */ public void copy(InputStream in) throws IOException { if (blocks != null) { for (;;) { Block s = last(); if (s.isFull()) { if (reachedInCoreLimit()) break; s = new Block(); blocks.add(s); } int n = in.read(s.buffer, s.count, s.buffer.length - s.count); if (n < 1) return; s.count += n; } } final byte[] tmp = new byte[Block.SZ]; int n; while ((n = in.read(tmp)) > 0) overflow.write(tmp, 0, n); } /** * Obtain the length (in bytes) of the buffer. *

* The length is only accurate after {@link #close()} has been invoked. * * @return total length of the buffer, in bytes. */ public long length() { return inCoreLength(); } private long inCoreLength() { final Block last = last(); return ((long) blocks.size() - 1) * Block.SZ + last.count; } /** * Convert this buffer's contents into a contiguous byte array. *

* The buffer is only complete after {@link #close()} has been invoked. * * @return the complete byte array; length matches {@link #length()}. * @throws java.io.IOException * an error occurred reading from a local temporary file */ public byte[] toByteArray() throws IOException { final long len = length(); if (Integer.MAX_VALUE < len) throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize); final byte[] out = new byte[(int) len]; int outPtr = 0; for (Block b : blocks) { System.arraycopy(b.buffer, 0, out, outPtr, b.count); outPtr += b.count; } return out; } /** * Convert first {@code limit} number of bytes of the buffer content to * String. * * @param limit * the maximum number of bytes to be converted to String * @return first {@code limit} number of bytes of the buffer content * converted to String. * @since 5.12 */ public String toString(int limit) { try { return RawParseUtils.decode(toByteArray(limit)); } catch (IOException e) { throw new UncheckedIOException(e); } } /** * Convert this buffer's contents into a contiguous byte array. If this size * of the buffer exceeds the limit only return the first {@code limit} bytes *

* The buffer is only complete after {@link #close()} has been invoked. * * @param limit * the maximum number of bytes to be returned * @return the byte array limited to {@code limit} bytes. * @throws java.io.IOException * an error occurred reading from a local temporary file * @since 4.2 */ public byte[] toByteArray(int limit) throws IOException { final long len = Math.min(length(), limit); if (Integer.MAX_VALUE < len) throw new OutOfMemoryError( JGitText.get().lengthExceedsMaximumArraySize); int length = (int) len; final byte[] out = new byte[length]; int outPtr = 0; for (Block b : blocks) { int toCopy = Math.min(length - outPtr, b.count); System.arraycopy(b.buffer, 0, out, outPtr, toCopy); outPtr += toCopy; if (outPtr == length) { break; } } return out; } /** * Send this buffer to an output stream. *

* This method may only be invoked after {@link #close()} has completed * normally, to ensure all data is completely transferred. * * @param os * stream to send this buffer's complete content to. * @param pm * if not null progress updates are sent here. Caller should * initialize the task and the number of work units to * {@link #length()}/1024. * @throws java.io.IOException * an error occurred reading from a temporary file on the local * system, or writing to the output stream. */ public void writeTo(OutputStream os, ProgressMonitor pm) throws IOException { if (pm == null) pm = NullProgressMonitor.INSTANCE; for (Block b : blocks) { os.write(b.buffer, 0, b.count); pm.update(b.count / 1024); } } /** * Open an input stream to read from the buffered data. *

* This method may only be invoked after {@link #close()} has completed * normally, to ensure all data is completely transferred. * * @return a stream to read from the buffer. The caller must close the * stream when it is no longer useful. * @throws java.io.IOException * an error occurred opening the temporary file. */ public InputStream openInputStream() throws IOException { return new BlockInputStream(); } /** * Same as {@link #openInputStream()} but handling destruction of any * associated resources automatically when closing the returned stream. * * @return an InputStream which will automatically destroy any associated * temporary file on {@link #close()} * @throws IOException * in case of an error. * @since 4.11 */ public InputStream openInputStreamWithAutoDestroy() throws IOException { return new BlockInputStream() { @Override public void close() throws IOException { super.close(); destroy(); } }; } /** * Reset this buffer for reuse, purging all buffered content. */ public void reset() { if (overflow != null) { destroy(); } if (blocks != null) blocks.clear(); else blocks = new ArrayList<>(initialBlocks); blocks.add(new Block(Math.min(inCoreLimit, Block.SZ))); } /** * Open the overflow output stream, so the remaining output can be stored. * * @return the output stream to receive the buffered content, followed by * the remaining output. * @throws java.io.IOException * the buffer cannot create the overflow stream. */ protected abstract OutputStream overflow() throws IOException; private Block last() { return blocks.get(blocks.size() - 1); } private boolean reachedInCoreLimit() throws IOException { if (inCoreLength() < inCoreLimit) return false; switchToOverflow(); return true; } private void switchToOverflow() throws IOException { overflow = overflow(); final Block last = blocks.remove(blocks.size() - 1); for (Block b : blocks) overflow.write(b.buffer, 0, b.count); blocks = null; overflow = new BufferedOutputStream(overflow, Block.SZ); overflow.write(last.buffer, 0, last.count); } /** {@inheritDoc} */ @Override public void close() throws IOException { if (overflow != null) { try { overflow.close(); } finally { overflow = null; } } } /** * Clear this buffer so it has no data, and cannot be used again. */ public void destroy() { blocks = null; if (overflow != null) { try { overflow.close(); } catch (IOException err) { // We shouldn't encounter an error closing the file. } finally { overflow = null; } } } /** * A fully buffered output stream using local disk storage for large data. *

* Initially this output stream buffers to memory and is therefore similar * to ByteArrayOutputStream, but it shifts to using an on disk temporary * file if the output gets too large. *

* The content of this buffered stream may be sent to another OutputStream * only after this stream has been properly closed by {@link #close()}. */ public static class LocalFile extends TemporaryBuffer { /** Directory to store the temporary file under. */ private final File directory; /** * Location of our temporary file if we are on disk; otherwise null. *

* If we exceeded the {@link #inCoreLimit} we nulled out {@link #blocks} * and created this file instead. All output goes here through * {@link #overflow}. */ private File onDiskFile; /** * Create a new temporary buffer, limiting memory usage. * * @param directory * if the buffer has to spill over into a temporary file, the * directory where the file should be saved. If null the * system default temporary directory (for example /tmp) will * be used instead. */ public LocalFile(File directory) { this(directory, DEFAULT_IN_CORE_LIMIT); } /** * Create a new temporary buffer, limiting memory usage. * * @param directory * if the buffer has to spill over into a temporary file, the * directory where the file should be saved. If null the * system default temporary directory (for example /tmp) will * be used instead. * @param inCoreLimit * maximum number of bytes to store in memory. Storage beyond * this limit will use the local file. */ public LocalFile(File directory, int inCoreLimit) { super(inCoreLimit); this.directory = directory; } @Override protected OutputStream overflow() throws IOException { onDiskFile = File.createTempFile("jgit_", ".buf", directory); //$NON-NLS-1$ //$NON-NLS-2$ return new BufferedOutputStream(new FileOutputStream(onDiskFile)); } @Override public long length() { if (onDiskFile == null) { return super.length(); } return onDiskFile.length(); } @Override public byte[] toByteArray() throws IOException { if (onDiskFile == null) { return super.toByteArray(); } final long len = length(); if (Integer.MAX_VALUE < len) throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize); final byte[] out = new byte[(int) len]; try (FileInputStream in = new FileInputStream(onDiskFile)) { IO.readFully(in, out, 0, (int) len); } return out; } @Override public byte[] toByteArray(int limit) throws IOException { if (onDiskFile == null) { return super.toByteArray(limit); } final long len = Math.min(length(), limit); if (Integer.MAX_VALUE < len) { throw new OutOfMemoryError( JGitText.get().lengthExceedsMaximumArraySize); } final byte[] out = new byte[(int) len]; try (FileInputStream in = new FileInputStream(onDiskFile)) { int read = 0; int chunk; while ((chunk = in.read(out, read, out.length - read)) >= 0) { read += chunk; if (read == out.length) { break; } } } return out; } @Override public void writeTo(OutputStream os, ProgressMonitor pm) throws IOException { if (onDiskFile == null) { super.writeTo(os, pm); return; } if (pm == null) pm = NullProgressMonitor.INSTANCE; try (FileInputStream in = new FileInputStream(onDiskFile)) { int cnt; final byte[] buf = new byte[Block.SZ]; while ((cnt = in.read(buf)) >= 0) { os.write(buf, 0, cnt); pm.update(cnt / 1024); } } } @Override public InputStream openInputStream() throws IOException { if (onDiskFile == null) return super.openInputStream(); return new FileInputStream(onDiskFile); } @Override public InputStream openInputStreamWithAutoDestroy() throws IOException { if (onDiskFile == null) { return super.openInputStreamWithAutoDestroy(); } return new FileInputStream(onDiskFile) { @Override public void close() throws IOException { super.close(); destroy(); } }; } @Override public void destroy() { super.destroy(); if (onDiskFile != null) { try { if (!onDiskFile.delete()) onDiskFile.deleteOnExit(); } finally { onDiskFile = null; } } } } /** * A temporary buffer that will never exceed its in-memory limit. *

* If the in-memory limit is reached an IOException is thrown, rather than * attempting to spool to local disk. */ public static class Heap extends TemporaryBuffer { /** * Create a new heap buffer with a maximum storage limit. * * @param limit * maximum number of bytes that can be stored in this buffer; * also used as the estimated size. Storing beyond this many * will cause an IOException to be thrown during write. */ public Heap(int limit) { super(limit); } /** * Create a new heap buffer with a maximum storage limit. * * @param estimatedSize * estimated size of storage used, to size the initial list of * block pointers. * @param limit * maximum number of bytes that can be stored in this buffer. * Storing beyond this many will cause an IOException to be * thrown during write. * @since 4.0 */ public Heap(int estimatedSize, int limit) { super(estimatedSize, limit); } @Override protected OutputStream overflow() throws IOException { throw new IOException(JGitText.get().inMemoryBufferLimitExceeded); } } static class Block { static final int SZ = 8 * 1024; final byte[] buffer; int count; Block() { buffer = new byte[SZ]; } Block(int sz) { buffer = new byte[sz]; } boolean isFull() { return count == buffer.length; } } private class BlockInputStream extends InputStream { private byte[] singleByteBuffer; private int blockIndex; private Block block; private int blockPos; BlockInputStream() { block = blocks.get(blockIndex); } @Override public int read() throws IOException { if (singleByteBuffer == null) singleByteBuffer = new byte[1]; int n = read(singleByteBuffer); return n == 1 ? singleByteBuffer[0] & 0xff : -1; } @Override public long skip(long cnt) throws IOException { long skipped = 0; while (0 < cnt) { int n = (int) Math.min(block.count - blockPos, cnt); if (0 < n) { blockPos += n; skipped += n; cnt -= n; } else if (nextBlock()) continue; else break; } return skipped; } @Override public int read(byte[] b, int off, int len) throws IOException { if (len == 0) return 0; int copied = 0; while (0 < len) { int c = Math.min(block.count - blockPos, len); if (0 < c) { System.arraycopy(block.buffer, blockPos, b, off, c); blockPos += c; off += c; len -= c; copied += c; } else if (nextBlock()) continue; else break; } return 0 < copied ? copied : -1; } private boolean nextBlock() { if (++blockIndex < blocks.size()) { block = blocks.get(blockIndex); blockPos = 0; return true; } return false; } } } Content-Type: text/plain; charset=UTF-8 Content-Length: 18263 Content-Disposition: inline; filename="TemporaryBuffer.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "21355b7b84d906f6fda5c19f510416eb1845e3f8" /org.eclipse.jgit/src/org/eclipse/jgit/util/io/

/org.eclipse.jgit/src/org/eclipse/jgit/util/io/

*
* * * * * * * * * * * * * * * * * *
TLS versions
SSLContext.getInstance()OpenJDKIDM JDK
"TLS"Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)
* Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)
Supported: TLSv1, TLSV1.1, TLSv1.2
* Enabled: TLSv1
"TLSv1.2"Supported: TLSv1, TLSV1.1, TLSv1.2
* Enabled: TLSv1, TLSV1.1, TLSv1.2
Supported: TLSv1, TLSV1.1, TLSv1.2
* Enabled: TLSv1.2
* * @param socket * to configure * @see
Behavior * of SSLContext.getInstance("TLS") on IBM JDK * @see Customizing * JSSE about https.protocols * @since 5.7 */ public static void configureTLS(SSLSocket socket) { // 1. Enable all available TLS protocol versions Set enabled = new LinkedHashSet<>( Arrays.asList(socket.getEnabledProtocols())); for (String s : socket.getSupportedProtocols()) { if (s.startsWith("TLS")) { //$NON-NLS-1$ enabled.add(s); } } // 2. Respect the https.protocols system property Set configured = getConfiguredProtocols(); if (!configured.isEmpty()) { enabled.retainAll(configured); } if (!enabled.isEmpty()) { socket.setEnabledProtocols(enabled.toArray(new String[0])); } } private static Set getConfiguredProtocols() { Set result = configuredHttpsProtocols; if (result == null) { String configured = getProperty("https.protocols"); //$NON-NLS-1$ if (StringUtils.isEmptyOrNull(configured)) { result = Collections.emptySet(); } else { result = new LinkedHashSet<>( Arrays.asList(configured.split("\\s*,\\s*"))); //$NON-NLS-1$ } configuredHttpsProtocols = result; } return result; } private static String getProperty(String property) { try { return SystemReader.getInstance().getProperty(property); } catch (SecurityException e) { LOG.warn(JGitText.get().failedReadHttpsProtocols, e); return null; } } /** * Scan a RFC 7230 token as it appears in HTTP headers. * * @param header * to scan in * @param from * index in {@code header} to start scanning at * @return the index after the token, that is, on the first non-token * character or {@code header.length} * @throws IndexOutOfBoundsException * if {@code from < 0} or {@code from > header.length()} * * @see RFC 7230, * Appendix B: Collected Grammar; "token" production * @since 5.10 */ public static int scanToken(String header, int from) { int length = header.length(); int i = from; if (i < 0 || i > length) { throw new IndexOutOfBoundsException(); } while (i < length) { char c = header.charAt(i); switch (c) { case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+': case '-': case '.': case '^': case '_': case '`': case '|': case '~': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': i++; break; default: if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { i++; break; } return i; } } return i; } private HttpSupport() { // Utility class only. } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10509 Content-Disposition: inline; filename="IO.java" Last-Modified: Tue, 08 Jul 2025 04:57:10 GMT Expires: Tue, 08 Jul 2025 05:02:10 GMT ETag: "8cc53162712bd8a2c72ebb7f9930dfb6f0dadb8b" /* * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2009, Robin Rosenberg * Copyright (C) 2006-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.util; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.io.SilentFileInputStream; /** * Input/Output utilities */ public class IO { /** * Read an entire local file into memory as a byte array. * * @param path * location of the file to read. * @return complete contents of the requested local file. * @throws java.io.FileNotFoundException * the file does not exist. * @throws java.io.IOException * the file exists, but its contents cannot be read. */ public static final byte[] readFully(File path) throws FileNotFoundException, IOException { return IO.readFully(path, Integer.MAX_VALUE); } /** * Read at most limit bytes from the local file into memory as a byte array. * * @param path * location of the file to read. * @param limit * maximum number of bytes to read, if the file is larger than * only the first limit number of bytes are returned * @return complete contents of the requested local file. If the contents * exceeds the limit, then only the limit is returned. * @throws java.io.FileNotFoundException * the file does not exist. * @throws java.io.IOException * the file exists, but its contents cannot be read. */ public static final byte[] readSome(File path, int limit) throws FileNotFoundException, IOException { try (SilentFileInputStream in = new SilentFileInputStream(path)) { return in.readNBytes(limit); } } /** * Read an entire local file into memory as a byte array. * * @param path * location of the file to read. * @param max * maximum number of bytes to read, if the file is larger than * this limit an IOException is thrown. * @return complete contents of the requested local file. * @throws java.io.FileNotFoundException * the file does not exist. * @throws java.io.IOException * the file exists, but its contents cannot be read. */ public static final byte[] readFully(File path, int max) throws FileNotFoundException, IOException { try (SilentFileInputStream in = new SilentFileInputStream(path)) { byte[] buf = in.readNBytes(max); if (in.read() != -1) { throw new IOException(MessageFormat.format( JGitText.get().fileIsTooLarge, path)); } return buf; } } /** * Read an entire input stream into memory as a ByteBuffer. * * Note: The stream is read to its end and is not usable after calling this * method. The caller is responsible for closing the stream. * * @param in * input stream to be read. * @param sizeHint * a hint on the approximate number of bytes contained in the * stream, used to allocate temporary buffers more efficiently * @return complete contents of the input stream. The ByteBuffer always has * a writable backing array, with {@code position() == 0} and * {@code limit()} equal to the actual length read. Callers may rely * on obtaining the underlying array for efficient data access. If * {@code sizeHint} was too large, the array may be over-allocated, * resulting in {@code limit() < array().length}. * @throws java.io.IOException * there was an error reading from the stream. */ public static ByteBuffer readWholeStream(InputStream in, int sizeHint) throws IOException { return ByteBuffer.wrap(in.readAllBytes()); } /** * Read the entire byte array into memory, or throw an exception. * * @param fd * input stream to read the data from. * @param dst * buffer that must be fully populated, [off, off+len). * @param off * position within the buffer to start writing to. * @param len * number of bytes that must be read. * @throws EOFException * the stream ended before dst was fully populated. * @throws java.io.IOException * there was an error reading from the stream. */ public static void readFully(final InputStream fd, final byte[] dst, int off, int len) throws IOException { int read = fd.readNBytes(dst, off, len); if (read != len) throw new EOFException(JGitText.get().shortReadOfBlock); } /** * Read from input until the entire byte array filled, or throw an exception * if stream ends first. * * @param fd * input stream to read the data from. * @param dst * buffer that must be fully populated * @throws EOFException * the stream ended before dst was fully populated. * @throws java.io.IOException * there was an error reading from the stream. * @since 6.5 */ public static void readFully(InputStream fd, byte[] dst) throws IOException { readFully(fd, dst, 0, dst.length); } /** * Read as much of the array as possible from a channel. * * @param channel * channel to read data from. * @param dst * buffer that must be fully populated, [off, off+len). * @param off * position within the buffer to start writing to. * @param len * number of bytes that should be read. * @return number of bytes actually read. * @throws java.io.IOException * there was an error reading from the channel. */ public static int read(ReadableByteChannel channel, byte[] dst, int off, int len) throws IOException { if (len == 0) return 0; int cnt = 0; while (0 < len) { int r = channel.read(ByteBuffer.wrap(dst, off, len)); if (r <= 0) break; off += r; len -= r; cnt += r; } return cnt != 0 ? cnt : -1; } /** * Read the entire byte array into memory, unless input is shorter * * @param fd * input stream to read the data from. * @param dst * buffer that must be fully populated, [off, off+len). * @param off * position within the buffer to start writing to. * @return number of bytes read * @throws java.io.IOException * there was an error reading from the stream. */ public static int readFully(InputStream fd, byte[] dst, int off) throws IOException { return fd.readNBytes(dst, off, dst.length - off); } /** * Skip an entire region of an input stream. *