diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/util')
56 files changed, 1372 insertions, 656 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java index 119c96e02e..48d1c50c7d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java @@ -96,6 +96,7 @@ public class Base64 { * @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 @@ -201,6 +202,7 @@ public class Base64 { * 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== diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java index 527c5a69df..557e2cde3b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java @@ -75,13 +75,11 @@ public class BlockList<T> extends AbstractList<T> { tailBlock = directory[0]; } - /** {@inheritDoc} */ @Override public int size() { return size; } - /** {@inheritDoc} */ @Override public void clear() { for (T[] block : directory) { @@ -94,7 +92,6 @@ public class BlockList<T> extends AbstractList<T> { tailBlock = directory[0]; } - /** {@inheritDoc} */ @Override public T get(int index) { if (index < 0 || size <= index) @@ -102,7 +99,6 @@ public class BlockList<T> extends AbstractList<T> { return directory[toDirectoryIndex(index)][toBlockIndex(index)]; } - /** {@inheritDoc} */ @Override public T set(int index, T element) { if (index < 0 || size <= index) @@ -160,7 +156,6 @@ public class BlockList<T> extends AbstractList<T> { } } - /** {@inheritDoc} */ @Override public boolean add(T element) { int i = tailBlkIdx; @@ -191,7 +186,6 @@ public class BlockList<T> extends AbstractList<T> { return true; } - /** {@inheritDoc} */ @Override public void add(int index, T element) { if (index == size) { @@ -213,7 +207,6 @@ public class BlockList<T> extends AbstractList<T> { } } - /** {@inheritDoc} */ @Override public T remove(int index) { if (index == size - 1) { @@ -253,7 +246,6 @@ public class BlockList<T> extends AbstractList<T> { tailBlock = directory[tailDirIdx]; } - /** {@inheritDoc} */ @Override public Iterator<T> iterator() { return new MyIterator(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java index 5815c62e89..d8183eb8be 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java @@ -32,7 +32,6 @@ public abstract class CachedAuthenticator extends Authenticator { cached.add(ca); } - /** {@inheritDoc} */ @Override protected final PasswordAuthentication getPasswordAuthentication() { final String host = getRequestingHost(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java index 12af374b2e..c8421d6012 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java @@ -86,8 +86,8 @@ public class ChangeIdUtil { } } - private static final Pattern issuePattern = Pattern - .compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$"); //$NON-NLS-1$ + 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$ @@ -159,7 +159,7 @@ public class ChangeIdUtil { int footerFirstLine = indexOfFirstFooterLine(lines); int insertAfter = footerFirstLine; for (int i = footerFirstLine; i < lines.length; ++i) { - if (issuePattern.matcher(lines[i]).matches()) { + if (!signedOffByPattern.matcher(lines[i]).matches()) { insertAfter = i + 1; continue; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java index da1684630b..ff136f7b3b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java @@ -13,22 +13,24 @@ package org.eclipse.jgit.util; /** * Equality utilities. * - * @since: 6.2 + * @since 6.2 */ public class Equality { /** - * Compare by reference - * - * @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 - */ + * Compare by reference + * + * @param <T> + * 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 <T> boolean isSameInstance(T a, T b) { return a == b; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index aef9e64e02..6a40fad1db 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -30,9 +30,6 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; -import java.security.AccessControlException; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; @@ -69,6 +66,7 @@ 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; @@ -98,6 +96,9 @@ public abstract class FS { 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; /** @@ -118,6 +119,7 @@ public abstract class FS { * Detect the file system * * @param cygwinUsed + * whether cygwin is used * @return FS instance */ public FS detect(Boolean cygwinUsed) { @@ -149,8 +151,11 @@ public abstract class FS { /** * @param stdout + * stdout stream * @param stderr + * stderr stream * @param rc + * return code */ public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr, int rc) { @@ -160,6 +165,8 @@ public abstract class FS { } /** + * Get buffered standard output stream + * * @return buffered standard output stream */ public TemporaryBuffer getStdout() { @@ -167,6 +174,8 @@ public abstract class FS { } /** + * Get buffered standard error stream + * * @return buffered standard error stream */ public TemporaryBuffer getStderr() { @@ -174,6 +183,8 @@ public abstract class FS { } /** + * Get the return code of the process + * * @return the return code of the process */ public int getRc() { @@ -202,7 +213,7 @@ public abstract class FS { * </p> */ public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration - .ofMillis(2000); + .ofSeconds(2); /** * Fallback FileStore attributes used when we can't measure the @@ -250,31 +261,6 @@ public abstract class FS { private static final AtomicInteger threadNumber = new AtomicInteger(1); /** - * Don't use the default thread factory of the ForkJoinPool for the - * CompletableFuture; it runs without any privileges, which causes - * trouble if a SecurityManager is present. - * <p> - * Instead use normal daemon threads. They'll belong to the - * SecurityManager's thread group, or use the one of the calling thread, - * as appropriate. - * </p> - * - * @see java.util.concurrent.Executors#newCachedThreadPool() - */ - private static final ExecutorService FUTURE_RUNNER = new ThreadPoolExecutor( - 0, 5, 30L, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>(), - runnable -> { - Thread t = new Thread(runnable, - "JGit-FileStoreAttributeReader-" //$NON-NLS-1$ - + threadNumber.getAndIncrement()); - // Make sure these threads don't prevent application/JVM - // shutdown. - t.setDaemon(true); - return t; - }); - - /** * 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 @@ -287,7 +273,7 @@ public abstract class FS { */ private static final ExecutorService SAVE_RUNNER = new ThreadPoolExecutor( 0, 1, 1L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<Runnable>(), + new LinkedBlockingQueue<>(), runnable -> { Thread t = new Thread(runnable, "JGit-FileStoreAttributeWriter-" //$NON-NLS-1$ @@ -299,18 +285,16 @@ public abstract class FS { static { // Shut down the SAVE_RUNNER on System.exit() + ShutdownHook.INSTANCE + .register(FileStoreAttributes::shutdownSafeRunner); + } + + private static void shutdownSafeRunner() { try { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - SAVE_RUNNER.shutdownNow(); - SAVE_RUNNER.awaitTermination(100, - TimeUnit.MILLISECONDS); - } catch (Exception e) { - // Ignore; we're shutting down - } - })); - } catch (IllegalStateException e) { - // ignore - may fail if shutdown is already in progress + SAVE_RUNNER.shutdownNow(); + SAVE_RUNNER.awaitTermination(100, TimeUnit.MILLISECONDS); + } catch (Exception e) { + // Ignore; we're shutting down } } @@ -379,6 +363,7 @@ public abstract class FS { private static FileStoreAttributes getFileStoreAttributes(Path dir) { FileStore s; + CompletableFuture<Optional<FileStoreAttributes>> f = null; try { if (Files.exists(dir)) { s = Files.getFileStore(dir); @@ -401,7 +386,7 @@ public abstract class FS { return FALLBACK_FILESTORE_ATTRIBUTES; } - CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture + f = CompletableFuture .supplyAsync(() -> { Lock lock = locks.computeIfAbsent(s, l -> new ReentrantLock()); @@ -453,7 +438,7 @@ public abstract class FS { locks.remove(s); } return attributes; - }, FUTURE_RUNNER); + }); f = f.exceptionally(e -> { LOG.error(e.getLocalizedMessage(), e); return Optional.empty(); @@ -471,10 +456,13 @@ public abstract class FS { } // 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(); } @@ -483,6 +471,13 @@ public abstract class FS { return FALLBACK_FILESTORE_ATTRIBUTES; } + private static void cancel( + CompletableFuture<Optional<FileStoreAttributes>> 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$ @@ -828,6 +823,8 @@ public abstract class FS { 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 @@ -837,6 +834,8 @@ public abstract class FS { } /** + * Get the measured filesystem timestamp resolution + * * @return the measured filesystem timestamp resolution */ @NonNull @@ -849,6 +848,7 @@ public abstract class FS { * timestamp resolution * * @param fsTimestampResolution + * resolution of filesystem timestamps */ public FileStoreAttributes( @NonNull Duration fsTimestampResolution) { @@ -883,21 +883,6 @@ public abstract class FS { } /** - * Whether FileStore attributes should be determined asynchronously - * - * @param asynch - * 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.1.9 - * @deprecated Use {@link FileStoreAttributes#setBackground} instead - */ - @Deprecated - public static void setAsyncFileStoreAttributes(boolean asynch) { - FileStoreAttributes.setBackground(asynch); - } - - /** * 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) @@ -1009,7 +994,7 @@ public abstract class FS { File tempFile = null; try { tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$ - File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$ + File linkName = new File(tempFile.getPath() + "-tempsymlink"); //$NON-NLS-1$ createSymLink(linkName, tempFile.getPath()); supportSymlinks = Boolean.TRUE; linkName.delete(); @@ -1070,23 +1055,6 @@ public abstract class FS { * symbolic links, the modification time of the link is returned, rather * than that of the link target. * - * @param f - * a {@link java.io.File} object. - * @return last modified time of f - * @throws java.io.IOException - * @since 3.0 - * @deprecated use {@link #lastModifiedInstant(Path)} instead - */ - @Deprecated - public long lastModified(File f) throws IOException { - return FileUtils.lastModified(f); - } - - /** - * 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 @@ -1115,29 +1083,12 @@ public abstract class FS { * <p> * For symlinks it sets the modified time of the link target. * - * @param f - * a {@link java.io.File} object. - * @param time - * last modified time - * @throws java.io.IOException - * @since 3.0 - * @deprecated use {@link #setLastModified(Path, Instant)} instead - */ - @Deprecated - public void setLastModified(File f, long time) throws IOException { - FileUtils.setLastModified(f, time); - } - - /** - * Set the last modified time of a file system object. - * <p> - * 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 { @@ -1152,6 +1103,7 @@ public abstract class FS { * 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 { @@ -1164,7 +1116,7 @@ public abstract class FS { * @param f * a {@link java.io.File} object. * @throws java.io.IOException - * this may be a Java7 subclass with detailed information + * if an IO error occurred * @since 3.3 */ public void delete(File f) throws IOException { @@ -1264,8 +1216,10 @@ public abstract class FS { * 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 + * @throws IOException + * in case of any I/O errors accessing the file * * @since 4.5.6 */ @@ -1283,11 +1237,10 @@ public abstract class FS { } private File defaultUserHomeImpl() { - String home = AccessController.doPrivileged( - (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$ - ); - if (home == null || home.length() == 0) + String home = SystemReader.getInstance().getProperty("user.home"); //$NON-NLS-1$ + if (StringUtils.isEmptyOrNull(home)) { return null; + } return new File(home).getAbsoluteFile(); } @@ -1303,8 +1256,10 @@ public abstract class FS { * @return the first match found, or null * @since 3.0 */ + @SuppressWarnings("StringSplitter") protected static File searchPath(String path, String... lookFor) { - if (path == null) { + if (StringUtils.isEmptyOrNull(path) + || EMPTY_PATH.matcher(path).find()) { return null; } @@ -1421,13 +1376,6 @@ public abstract class FS { } } catch (IOException e) { LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ - } catch (AccessControlException e) { - LOG.warn(MessageFormat.format( - JGitText.get().readPipeIsNotAllowedRequiredPermission, - command, dir, e.getPermission())); - } catch (SecurityException e) { - LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed, - command, dir)); } if (debug) { LOG.debug("readpipe returns null"); //$NON-NLS-1$ @@ -1671,6 +1619,7 @@ public abstract class FS { * 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 { @@ -1684,6 +1633,7 @@ public abstract class FS { * 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 { @@ -1738,6 +1688,7 @@ public abstract class FS { * @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 { @@ -1752,6 +1703,7 @@ public abstract class FS { * @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 { @@ -1766,6 +1718,7 @@ public abstract class FS { * @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 { @@ -1773,24 +1726,6 @@ public abstract class FS { } /** - * 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 <code>false</code> - * - * @param path - * the file to be created - * @return <code>true</code> if the file was created, <code>false</code> if - * the file already existed - * @throws java.io.IOException - * @deprecated use {@link #createNewFileAtomic(File)} instead - * @since 4.5 - */ - @Deprecated - public boolean createNewFile(File path) throws IOException { - return path.createNewFile(); - } - - /** * 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 @@ -1810,6 +1745,8 @@ public abstract class FS { } /** + * Whether the file was created successfully + * * @return {@code true} if the file was created successfully */ public boolean isCreated() { @@ -1852,6 +1789,7 @@ public abstract class FS { * @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 { @@ -2011,6 +1949,8 @@ public abstract class FS { 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()); } @@ -2106,7 +2046,7 @@ public abstract class FS { case "post-receive": //$NON-NLS-1$ case "post-update": //$NON-NLS-1$ case "push-to-checkout": //$NON-NLS-1$ - return repository.getDirectory(); + return repository.getCommonDirectory(); default: return repository.getWorkTree(); } @@ -2119,7 +2059,7 @@ public abstract class FS { if (hooksDir != null) { return new File(hooksDir); } - File dir = repository.getDirectory(); + File dir = repository.getCommonDirectory(); return dir == null ? null : new File(dir, Constants.HOOKS); } @@ -2317,7 +2257,9 @@ public abstract class FS { * 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) @@ -2346,6 +2288,8 @@ public abstract class FS { public static class Attributes { /** + * Whether this are attributes of a directory + * * @return true if this are the attributes of a directory */ public boolean isDirectory() { @@ -2353,6 +2297,8 @@ public abstract class FS { } /** + * Whether this are attributes of an executable file + * * @return true if this are the attributes of an executable file */ public boolean isExecutable() { @@ -2360,6 +2306,8 @@ public abstract class FS { } /** + * Whether this are the attributes of a symbolic link + * * @return true if this are the attributes of a symbolic link */ public boolean isSymbolicLink() { @@ -2367,6 +2315,8 @@ public abstract class FS { } /** + * Whether this are the attributes of a regular file + * * @return true if this are the attributes of a regular file */ public boolean isRegularFile() { @@ -2374,6 +2324,8 @@ public abstract class FS { } /** + * Get the file creation time + * * @return the time when the file was created */ public long getCreationTime() { @@ -2381,16 +2333,8 @@ public abstract class FS { } /** - * @return the time (milliseconds since 1970-01-01) when this object was - * last modified - * @deprecated use getLastModifiedInstant instead - */ - @Deprecated - public long getLastModifiedTime() { - return lastModifiedInstant.toEpochMilli(); - } - - /** + * Get the time when this object was last modified + * * @return the time when this object was last modified * @since 5.1.9 */ @@ -2441,14 +2385,18 @@ public abstract class FS { * Constructor when there are issues with reading. All attributes except * given will be set to the default values. * - * @param fs * @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() { @@ -2458,6 +2406,8 @@ public abstract class FS { } /** + * Get the filename + * * @return the filename */ public String getName() { @@ -2465,6 +2415,8 @@ public abstract class FS { } /** + * Get the file the attributes apply to + * * @return the file the attributes apply to */ public File getFile() { @@ -2522,6 +2474,33 @@ public abstract class FS { } /** + * 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. * <p> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index 1c113617f8..db2b5b4f71 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Robin Rosenberg and others + * 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 @@ -203,7 +203,16 @@ public class FS_POSIX extends FS { /** {@inheritDoc} */ @Override public boolean canExecute(File f) { - return FileUtils.canExecute(f); + if (!isFile(f)) { + return false; + } + try { + Path path = FileUtils.toPath(f); + Set<PosixFilePermission> pset = Files.getPosixFilePermissions(path); + return pset.contains(PosixFilePermission.OWNER_EXECUTE); + } catch (IOException ex) { + return false; + } } /** {@inheritDoc} */ @@ -250,8 +259,12 @@ public class FS_POSIX extends FS { /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { - List<String> argv = new ArrayList<>(4 + args.length); + List<String> 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); @@ -328,73 +341,6 @@ public class FS_POSIX extends FS { return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED; } - @Override - @SuppressWarnings("boxing") - /** - * {@inheritDoc} - * <p> - * An implementation of the File#createNewFile() semantics which works also - * on NFS. If the config option - * {@code core.supportsAtomicCreateNewFile = true} (which is the default) - * then simply File#createNewFile() 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. - * - * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html" - * - * @deprecated use {@link FS_POSIX#createNewFileAtomic(File)} instead - * @since 4.5 - */ - @Deprecated - public boolean createNewFile(File lock) throws IOException { - if (!lock.createNewFile()) { - return false; - } - if (supportsAtomicCreateNewFile()) { - return true; - } - Path lockPath = lock.toPath(); - Path link = null; - FileStore store = null; - try { - store = Files.getFileStore(lockPath); - } catch (SecurityException e) { - return true; - } - try { - Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store, - s -> Boolean.TRUE); - if (Boolean.FALSE.equals(canLink)) { - return true; - } - link = Files.createLink( - Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$ - lockPath); - Integer nlink = (Integer) (Files.getAttribute(lockPath, - "unix:nlink")); //$NON-NLS-1$ - if (nlink > 2) { - LOG.warn(MessageFormat.format( - JGitText.get().failedAtomicFileCreation, lockPath, - nlink)); - return false; - } else if (nlink < 2) { - CAN_HARD_LINK.put(store, Boolean.FALSE); - } - return true; - } catch (UnsupportedOperationException | IllegalArgumentException e) { - CAN_HARD_LINK.put(store, Boolean.FALSE); - return true; - } finally { - if (link != null) { - Files.delete(link); - } - } - } - /** * {@inheritDoc} * <p> @@ -418,6 +364,7 @@ public class FS_POSIX extends FS { * @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 @@ -446,8 +393,7 @@ public class FS_POSIX extends FS { return token(true, null); } link = Files.createLink(Paths.get(uniqueLinkPath(file)), path); - Integer nlink = (Integer) (Files.getAttribute(path, - "unix:nlink")); //$NON-NLS-1$ + Integer nlink = (Integer) Files.getAttribute(path, "unix:nlink"); //$NON-NLS-1$ if (nlink.intValue() > 2) { LOG.warn(MessageFormat.format( JGitText.get().failedAtomicFileCreation, path, nlink)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java index ae73d3feb8..5926655b7b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java @@ -58,43 +58,36 @@ public class FS_Win32 extends FS { super(src); } - /** {@inheritDoc} */ @Override public FS newInstance() { return new FS_Win32(this); } - /** {@inheritDoc} */ @Override public boolean supportsExecute() { return false; } - /** {@inheritDoc} */ @Override public boolean canExecute(File f) { return false; } - /** {@inheritDoc} */ @Override public boolean setExecute(File f, boolean canExec) { return false; } - /** {@inheritDoc} */ @Override public boolean isCaseSensitive() { return false; } - /** {@inheritDoc} */ @Override public boolean retryFailedLockFileCommit() { return true; } - /** {@inheritDoc} */ @Override public Entry[] list(File directory, FileModeStrategy fileModeStrategy) { if (!Files.isDirectory(directory.toPath(), LinkOption.NOFOLLOW_LINKS)) { @@ -140,7 +133,6 @@ public class FS_Win32 extends FS { return result.toArray(new Entry[0]); } - /** {@inheritDoc} */ @Override protected File discoverGitExe() { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ @@ -171,7 +163,6 @@ public class FS_Win32 extends FS { return gitExe; } - /** {@inheritDoc} */ @Override protected File userHomeImpl() { String home = SystemReader.getInstance().getenv("HOME"); //$NON-NLS-1$ @@ -194,7 +185,6 @@ public class FS_Win32 extends FS { return super.userHomeImpl(); } - /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { List<String> argv = new ArrayList<>(3 + args.length); @@ -207,7 +197,6 @@ public class FS_Win32 extends FS { return proc; } - /** {@inheritDoc} */ @Override public Attributes getAttributes(File path) { return FileUtils.getFileAttributesBasic(this, path); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java index add5498175..237879110a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java @@ -14,8 +14,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.OutputStream; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,10 +41,7 @@ public class FS_Win32_Cygwin extends FS_Win32 { * @return true if cygwin is found */ public static boolean isCygwin() { - final String path = AccessController - .doPrivileged((PrivilegedAction<String>) () -> System - .getProperty("java.library.path") //$NON-NLS-1$ - ); + 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$ @@ -72,13 +67,11 @@ public class FS_Win32_Cygwin extends FS_Win32 { super(src); } - /** {@inheritDoc} */ @Override public FS newInstance() { return new FS_Win32_Cygwin(this); } - /** {@inheritDoc} */ @Override public File resolve(File dir, String pn) { String useCygPath = System.getProperty("jgit.usecygpath"); //$NON-NLS-1$ @@ -99,18 +92,14 @@ public class FS_Win32_Cygwin extends FS_Win32 { return super.resolve(dir, pn); } - /** {@inheritDoc} */ @Override protected File userHomeImpl() { - final String home = AccessController.doPrivileged( - (PrivilegedAction<String>) () -> System.getenv("HOME") //$NON-NLS-1$ - ); + 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$ } - /** {@inheritDoc} */ @Override public ProcessBuilder runInShell(String cmd, String[] args) { List<String> argv = new ArrayList<>(4 + args.length); @@ -129,14 +118,12 @@ public class FS_Win32_Cygwin extends FS_Win32 { return QuotedString.BOURNE.quote(cmd.replace(File.separatorChar, '/')); } - /** {@inheritDoc} */ @Override public String relativize(String base, String other) { final String relativized = super.relativize(base, other); return relativized.replace(File.separatorChar, '/'); } - /** {@inheritDoc} */ @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, String[] args, OutputStream outRedirect, OutputStream errRedirect, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index f013e7e095..39c67f1b86 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -288,12 +288,14 @@ public class FileUtils { * @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); @@ -301,29 +303,35 @@ public class FileUtils { } catch (AtomicMoveNotSupportedException e) { throw e; } catch (IOException e) { - try { - if (!dst.delete()) { - delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); + 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; } - // On *nix there is no try, you do or do not - Files.move(toPath(src), toPath(dst), options); - return; - } catch (IOException e2) { - // ignore and continue retry } } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new IOException( - MessageFormat.format(JGitText.get().renameFileFailed, - src.getAbsolutePath(), dst.getAbsolutePath()), - e); + 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())); + src.getAbsolutePath(), dst.getAbsolutePath()), + finalError); } /** @@ -446,6 +454,7 @@ public class FileUtils { * 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) @@ -474,6 +483,7 @@ public class FileUtils { * 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 { @@ -499,6 +509,7 @@ public class FileUtils { * 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) @@ -620,11 +631,11 @@ public class FileUtils { } /** - * Determine if an IOException is a Stale NFS File Handle + * 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 + * @return a boolean true if the IOException is a stale NFS file handle * @since 4.1 */ public static boolean isStaleFileHandle(IOException ioe) { @@ -635,13 +646,13 @@ public class FileUtils { } /** - * Determine if a throwable or a cause in its causal chain is a Stale NFS - * File Handle + * 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 + * a stale NFS file handle * @since 4.7 */ public static boolean isStaleFileHandleInCausalChain(Throwable throwable) { @@ -749,7 +760,10 @@ public class FileUtils { } /** + * 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) { @@ -757,21 +771,10 @@ public class FileUtils { } /** - * @param file - * @return lastModified attribute for given file, not following symbolic - * links - * @throws IOException - * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns - * FileTime - */ - @Deprecated - static long lastModified(File file) throws IOException { - return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS) - .toMillis(); - } - - /** + * Get last modified timestamp of a file + * * @param path + * file path * @return lastModified attribute for given file, not following symbolic * links */ @@ -795,8 +798,10 @@ public class FileUtils { * 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 + * @throws IOException + * in case of any I/O errors accessing the file * * @since 4.5.6 */ @@ -807,21 +812,12 @@ public class FileUtils { /** * Set the last modified time of a file system object. * - * @param file - * @param time - * @throws IOException - */ - @Deprecated - static void setLastModified(File file, long time) throws IOException { - Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time)); - } - - /** - * 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 { @@ -829,7 +825,10 @@ public class FileUtils { } /** + * Whether the file exists + * * @param file + * the file * @return {@code true} if the given file exists, not following symbolic * links */ @@ -838,9 +837,13 @@ public class FileUtils { } /** + * 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)); @@ -854,6 +857,7 @@ public class FileUtils { * @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 { @@ -868,6 +872,7 @@ public class FileUtils { * 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 { @@ -879,7 +884,10 @@ public class FileUtils { } /** + * Check if file is directory + * * @param file + * the file * @return {@code true} if the given file is a directory, not following * symbolic links */ @@ -888,7 +896,10 @@ public class FileUtils { } /** + * Check if File is a file + * * @param file + * the file * @return {@code true} if the given file is a file, not following symbolic * links */ @@ -929,8 +940,12 @@ public class FileUtils { } /** + * 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) { @@ -1079,6 +1094,7 @@ public class FileUtils { * @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 { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java index be56e5ecf5..ba0df932ce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java @@ -28,7 +28,7 @@ public abstract class GSSManagerFactory { * @return detected GSSManager factory */ public static GSSManagerFactory detect() { - return (SunGSSManagerFactory.isSupported()) ? new SunGSSManagerFactory() + return SunGSSManagerFactory.isSupported() ? new SunGSSManagerFactory() : new DefaultGSSManagerFactory(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java index e6bf497ac4..332e65985e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java @@ -10,10 +10,10 @@ package org.eclipse.jgit.util; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.Locale; -import java.util.TimeZone; import org.eclipse.jgit.lib.PersonIdent; @@ -26,9 +26,9 @@ import org.eclipse.jgit.lib.PersonIdent; */ public class GitDateFormatter { - private DateFormat dateTimeInstance; + private DateTimeFormatter dateTimeFormat; - private DateFormat dateTimeInstance2; + private DateTimeFormatter dateTimeFormat2; private final Format format; @@ -96,30 +96,34 @@ public class GitDateFormatter { default: break; case DEFAULT: // Not default: - dateTimeInstance = new SimpleDateFormat( + dateTimeFormat = DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss yyyy Z", Locale.US); //$NON-NLS-1$ break; case ISO: - dateTimeInstance = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$ + dateTimeFormat = DateTimeFormatter.ofPattern( + "yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$ Locale.US); break; case LOCAL: - dateTimeInstance = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$ + dateTimeFormat = DateTimeFormatter.ofPattern( + "EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$ Locale.US); break; case RFC: - dateTimeInstance = new SimpleDateFormat( + dateTimeFormat = DateTimeFormatter.ofPattern( "EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); //$NON-NLS-1$ break; case SHORT: - dateTimeInstance = new SimpleDateFormat("yyyy-MM-dd", Locale.US); //$NON-NLS-1$ + dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd", //$NON-NLS-1$ + Locale.US); break; case LOCALE: case LOCALELOCAL: - SystemReader systemReader = SystemReader.getInstance(); - dateTimeInstance = systemReader.getDateTimeInstance( - DateFormat.DEFAULT, DateFormat.DEFAULT); - dateTimeInstance2 = systemReader.getSimpleDateFormat("Z"); //$NON-NLS-1$ + dateTimeFormat = DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.MEDIUM) + .withLocale(Locale.US); + dateTimeFormat2 = DateTimeFormatter.ofPattern("Z", //$NON-NLS-1$ + Locale.US); break; } } @@ -135,39 +139,45 @@ public class GitDateFormatter { @SuppressWarnings("boxing") public String formatDate(PersonIdent ident) { switch (format) { - case RAW: - int offset = ident.getTimeZoneOffset(); + case RAW: { + int offset = ident.getZoneOffset().getTotalSeconds(); String sign = offset < 0 ? "-" : "+"; //$NON-NLS-1$ //$NON-NLS-2$ int offset2; - if (offset < 0) + if (offset < 0) { offset2 = -offset; - else + } else { offset2 = offset; - int hours = offset2 / 60; - int minutes = offset2 % 60; + } + int minutes = (offset2 / 60) % 60; + int hours = offset2 / 60 / 60; return String.format("%d %s%02d%02d", //$NON-NLS-1$ - ident.getWhen().getTime() / 1000, sign, hours, minutes); + ident.getWhenAsInstant().getEpochSecond(), sign, hours, + minutes); + } case RELATIVE: - return RelativeDateFormatter.format(ident.getWhen()); + return RelativeDateFormatter.format(ident.getWhenAsInstant()); case LOCALELOCAL: case LOCAL: - dateTimeInstance.setTimeZone(SystemReader.getInstance() - .getTimeZone()); - return dateTimeInstance.format(ident.getWhen()); - case LOCALE: - TimeZone tz = ident.getTimeZone(); - if (tz == null) - tz = SystemReader.getInstance().getTimeZone(); - dateTimeInstance.setTimeZone(tz); - dateTimeInstance2.setTimeZone(tz); - return dateTimeInstance.format(ident.getWhen()) + " " //$NON-NLS-1$ - + dateTimeInstance2.format(ident.getWhen()); - default: - tz = ident.getTimeZone(); - if (tz == null) - tz = SystemReader.getInstance().getTimeZone(); - dateTimeInstance.setTimeZone(ident.getTimeZone()); - return dateTimeInstance.format(ident.getWhen()); + 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()); + } } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java index 6a4b39652a..f080056546 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java @@ -28,7 +28,10 @@ import org.eclipse.jgit.internal.JGitText; * 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 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java new file mode 100644 index 0000000000..acaa1ce563 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java @@ -0,0 +1,248 @@ +/* + * 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<ParseableSimpleDateFormat, DateTimeFormatter> 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: + * <ul> + * <li>"never"</li> + * <li>"now"</li> + * <li>"yesterday"</li> + * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br> + * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' + * ' one can use '.' to separate the words</li> + * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li> + * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li> + * <li>"yyyy-MM-dd"</li> + * <li>"yyyy.MM.dd"</li> + * <li>"MM/dd/yyyy",</li> + * <li>"dd.MM.yyyy"</li> + * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li> + * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li> + * </ul> + * + * @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: + * <ul> + * <li>"never"</li> + * <li>"now"</li> + * <li>"yesterday"</li> + * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br> + * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' + * ' one can use '.' to separate the words</li> + * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li> + * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li> + * <li>"yyyy-MM-dd"</li> + * <li>"yyyy.MM.dd"</li> + * <li>"MM/dd/yyyy",</li> + * <li>"dd.MM.yyyy"</li> + * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li> + * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li> + * </ul> + * + * @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; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java index e3ba606346..1942342c45 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java @@ -321,6 +321,7 @@ public class HttpSupport { * 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) @@ -346,7 +347,9 @@ public class HttpSupport { * that have all available protocols enabled already, up to the one * specified. * <p> + * <br> * <table> + * <caption>TLS versions</caption> * <tr> * <td>SSLContext.getInstance()</td> * <td>OpenJDK</td> @@ -354,16 +357,16 @@ public class HttpSupport { * </tr> * <tr> * <td>"TLS"</td> - * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br /> + * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br> * Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)</td> - * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> + * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br> * Enabled: TLSv1</td> * </tr> * <tr> * <td>"TLSv1.2"</td> - * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> + * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br> * Enabled: TLSv1, TLSV1.1, TLSv1.2</td> - * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br /> + * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br> * Enabled: TLSv1.2</td> * </tr> * </table> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java index 80877bbdc6..8cc5316271 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java @@ -66,19 +66,7 @@ public class IO { public static final byte[] readSome(File path, int limit) throws FileNotFoundException, IOException { try (SilentFileInputStream in = new SilentFileInputStream(path)) { - byte[] buf = new byte[limit]; - int cnt = 0; - for (;;) { - int n = in.read(buf, cnt, buf.length - cnt); - if (n <= 0) - break; - cnt += n; - } - if (cnt == buf.length) - return buf; - byte[] res = new byte[cnt]; - System.arraycopy(buf, 0, res, 0, cnt); - return res; + return in.readNBytes(limit); } } @@ -99,37 +87,10 @@ public class IO { public static final byte[] readFully(File path, int max) throws FileNotFoundException, IOException { try (SilentFileInputStream in = new SilentFileInputStream(path)) { - long sz = Math.max(path.length(), 1); - if (sz > max) + byte[] buf = in.readNBytes(max); + if (in.read() != -1) { throw new IOException(MessageFormat.format( JGitText.get().fileIsTooLarge, path)); - - byte[] buf = new byte[(int) sz]; - int valid = 0; - for (;;) { - if (buf.length == valid) { - if (buf.length == max) { - int next = in.read(); - if (next < 0) - break; - - throw new IOException(MessageFormat.format( - JGitText.get().fileIsTooLarge, path)); - } - - byte[] nb = new byte[Math.min(buf.length * 2, max)]; - System.arraycopy(buf, 0, nb, 0, valid); - buf = nb; - } - int n = in.read(buf, valid, buf.length - valid); - if (n < 0) - break; - valid += n; - } - if (valid < buf.length) { - byte[] nb = new byte[valid]; - System.arraycopy(buf, 0, nb, 0, valid); - buf = nb; } return buf; } @@ -157,26 +118,7 @@ public class IO { */ public static ByteBuffer readWholeStream(InputStream in, int sizeHint) throws IOException { - byte[] out = new byte[sizeHint]; - int pos = 0; - while (pos < out.length) { - int read = in.read(out, pos, out.length - pos); - if (read < 0) - return ByteBuffer.wrap(out, 0, pos); - pos += read; - } - - int last = in.read(); - if (last < 0) - return ByteBuffer.wrap(out, 0, pos); - - try (TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap( - Integer.MAX_VALUE)) { - tmp.write(out); - tmp.write(last); - tmp.copy(in); - return ByteBuffer.wrap(tmp.toByteArray()); - } + return ByteBuffer.wrap(in.readAllBytes()); } /** @@ -197,13 +139,9 @@ public class IO { */ public static void readFully(final InputStream fd, final byte[] dst, int off, int len) throws IOException { - while (len > 0) { - final int r = fd.read(dst, off, len); - if (r <= 0) - throw new EOFException(JGitText.get().shortReadOfBlock); - off += r; - len -= r; - } + int read = fd.readNBytes(dst, off, len); + if (read != len) + throw new EOFException(JGitText.get().shortReadOfBlock); } /** @@ -271,14 +209,7 @@ public class IO { */ public static int readFully(InputStream fd, byte[] dst, int off) throws IOException { - int r; - int len = 0; - while (off < dst.length - && (r = fd.read(dst, off, dst.length - off)) >= 0) { - off += r; - len += r; - } - return len; + return fd.readNBytes(dst, off, dst.length - off); } /** @@ -300,6 +231,7 @@ public class IO { */ 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) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java index cc4f0a46fe..6a5190c6a2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java @@ -204,7 +204,6 @@ public class IntList { entries = n; } - /** {@inheritDoc} */ @Override public String toString() { final StringBuilder r = new StringBuilder(); @@ -220,6 +219,8 @@ public class IntList { /** * A comparator of primitive ints. + * + * @since 6.6 */ public interface IntComparator { @@ -230,8 +231,8 @@ public class IntList { * 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 + * @return a negative number if first < second, 0 if first == second, or + * a positive number if first > second */ int compare(int first, int second); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java new file mode 100644 index 0000000000..74b728bdf7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025, NVIDIA Corporation. + * + * 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.Iterator; + +/** + * Utility class for Iterators + * + * @since 6.10.2 + */ +public class Iterators { + /** + * Create an iterator which traverses an array in reverse. + * + * @param array T[] + * @return Iterator<T> + */ + public static <T> Iterator<T> reverseIterator(T[] array) { + return new Iterator<>() { + int index = array.length; + + @Override + public boolean hasNext() { + return index > 0; + } + + @Override + public T next() { + return array[--index]; + } + }; + } + + /** + * Make an iterable for easy use in modern for loops. + * + * @param iterator Iterator<T> + * @return Iterable<T> + */ + public static <T> Iterable<T> iterable(Iterator<T> iterator) { + return new Iterable<>() { + @Override + public Iterator<T> iterator() { + return iterator; + } + }; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java index 7456c71f5f..7b7c1d0886 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java @@ -43,6 +43,8 @@ public class LfsFactory { } /** + * Get the LFS factory instance + * * @return the current LFS implementation */ public static LfsFactory getInstance() { @@ -50,6 +52,8 @@ public class LfsFactory { } /** + * Set the LFS factory instance + * * @param instance * register a {@link LfsFactory} instance as the * {@link LfsFactory} implementation to use. @@ -59,6 +63,8 @@ public class LfsFactory { } /** + * Whether LFS support is available + * * @return whether LFS support is available */ public boolean isAvailable() { @@ -105,6 +111,7 @@ public class LfsFactory { * @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 { @@ -117,6 +124,7 @@ public class LfsFactory { * @param repo * the {@link Repository} the hook is applied to. * @param outputStream + * output stream * @return a {@link PrePushHook} implementation or <code>null</code> */ @Nullable @@ -131,7 +139,9 @@ public class LfsFactory { * @param repo * the {@link Repository} the hook is applied to. * @param outputStream + * output stream * @param errorStream + * error stream * @return a {@link PrePushHook} implementation or <code>null</code> * @since 5.6 */ @@ -153,6 +163,8 @@ public class LfsFactory { } /** + * Whether LFS is enabled + * * @param db * the repository to check * @return whether LFS is enabled for the given repository locally or @@ -163,6 +175,8 @@ public class LfsFactory { } /** + * Get git attributes for given path + * * @param db * the repository * @param path @@ -285,6 +299,8 @@ public class LfsFactory { } /** + * Get stream length + * * @return the length of the stream */ public long getLength() { @@ -298,6 +314,8 @@ public class LfsFactory { */ public interface LfsInstallCommand extends Callable<Void> { /** + * Set the repository to enable LFS for + * * @param repo * the repository to enable support for. * @return The {@link LfsInstallCommand} for chaining. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java index b2bdfc1fd7..47f38f4627 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java @@ -140,7 +140,6 @@ public class LongList { entries = n; } - /** {@inheritDoc} */ @Override public String toString() { final StringBuilder r = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java index 7b4ff7ff16..fea7172d84 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java @@ -133,7 +133,7 @@ public final class NB { * @since 3.0 */ public static long decodeInt64(final byte[] intbuf, final int offset) { - long r = intbuf[offset] << 8; + long r = (long) intbuf[offset] << 8; r |= intbuf[offset + 1] & 0xff; r <<= 8; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java index 3de7a1587c..be4bd9e356 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java @@ -41,25 +41,21 @@ public final class RawCharSequence implements CharSequence { endPtr = end; } - /** {@inheritDoc} */ @Override public char charAt(int index) { return (char) (buffer[startPtr + index] & 0xff); } - /** {@inheritDoc} */ @Override public int length() { return endPtr - startPtr; } - /** {@inheritDoc} */ @Override public CharSequence subSequence(int start, int end) { return new RawCharSequence(buffer, startPtr + start, startPtr + end); } - /** {@inheritDoc} */ @Override public String toString() { final int n = length(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java index 0e8e9b3d84..3ed72516c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -13,10 +13,17 @@ 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; @@ -25,6 +32,10 @@ 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; @@ -39,14 +50,6 @@ import org.eclipse.jgit.lib.PersonIdent; * Handy utility functions to parse raw object contents. */ public final class RawParseUtils { - /** - * UTF-8 charset constant. - * - * @since 2.2 - * @deprecated use {@link java.nio.charset.StandardCharsets#UTF_8} instead - */ - @Deprecated - public static final Charset UTF8_CHARSET = UTF_8; private static final byte[] digits10; @@ -354,6 +357,7 @@ public final class RawParseUtils { * 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; @@ -461,6 +465,29 @@ public final class RawParseUtils { } /** + * 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 @@ -519,17 +546,24 @@ public final class RawParseUtils { } /** - * Locate the end of the header. Note that headers may be - * more than one line long. + * 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. + * <p> + * 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 end-of-header. - * @return new position just after the header. This is either - * b.length, or the index of the header's terminating newline. - * @since 5.1 + * 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 headerEnd(final byte[] b, int ptr) { + public static final int nextLfSkippingSplitLines(final byte[] b, int ptr) { final int sz = b.length; while (ptr < sz) { final byte c = b[ptr++]; @@ -537,7 +571,62 @@ public final class RawParseUtils { return ptr - 1; } } - 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. + * <p> + * 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); } /** @@ -575,6 +664,22 @@ public final class RawParseUtils { } /** + * 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 @@ -868,6 +973,26 @@ public final class RawParseUtils { } /** + * Parse the "encoding " header into a character set reference. + * <p> + * 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. * <p> * Leading spaces won't be trimmed from the string, i.e. will show up in the @@ -931,17 +1056,19 @@ public final class RawParseUtils { // 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, 0, 0); + 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, 0, 0); + if (whenBegin >= tzBegin - 1) { // No time/zone, still valid + return new PersonIdent(name, email, EPOCH, UTC); + } - final long when = parseLongBase10(raw, whenBegin, null); - final int tz = parseTimeZoneOffset(raw, tzBegin); - return new PersonIdent(name, email, when * 1000L, tz); + long when = parseLongBase10(raw, whenBegin, null); + return new PersonIdent(name, email, Instant.ofEpochSecond(when), + parseZoneOffset(raw, tzBegin, null)); } /** @@ -979,16 +1106,16 @@ public final class RawParseUtils { name = decode(raw, nameB, stop); final MutableInteger ptrout = new MutableInteger(); - long when; - int tz; + Instant when; + ZoneId tz; if (emailE < stop) { - when = parseLongBase10(raw, emailE + 1, ptrout); - tz = parseTimeZoneOffset(raw, ptrout.value); + when = Instant.ofEpochSecond(parseLongBase10(raw, emailE + 1, ptrout)); + tz = parseZoneOffset(raw, ptrout.value, null); } else { - when = 0; - tz = 0; + when = EPOCH; + tz = UTC; } - return new PersonIdent(name, email, when * 1000L, tz); + return new PersonIdent(name, email, when, tz); } /** @@ -1237,6 +1364,7 @@ public final class RawParseUtils { 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') diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java index 57464f3c41..04fdcd0fa4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java @@ -98,7 +98,6 @@ public class RawSubStringPattern { return needleString; } - /** {@inheritDoc} */ @Override public String toString() { return pattern(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java index 462bab081a..350ec76cf9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java @@ -43,6 +43,8 @@ public class RefList<T extends Ref> implements Iterable<Ref> { /** * Create an empty unmodifiable reference list. * + * @param <T> + * type of reference being stored. * @return an empty unmodifiable reference list. */ @SuppressWarnings("unchecked") @@ -70,7 +72,6 @@ public class RefList<T extends Ref> implements Iterable<Ref> { this.cnt = src.cnt; } - /** {@inheritDoc} */ @Override public Iterator<Ref> iterator() { return new Iterator<>() { @@ -286,7 +287,6 @@ public class RefList<T extends Ref> implements Iterable<Ref> { return add(idx, ref); } - /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); @@ -305,6 +305,8 @@ public class RefList<T extends Ref> implements Iterable<Ref> { /** * Create a {@link Collector} for {@link Ref}. * + * @param <T> + * type of reference being stored. * @param mergeFunction * if specified the result will be sorted and deduped. * @return {@link Collector} for {@link Ref} @@ -355,7 +357,11 @@ public class RefList<T extends Ref> implements Iterable<Ref> { list = new Ref[Math.max(capacity, 16)]; } - /** @return number of items in this builder's internal collection. */ + /** + * Get size + * + * @return number of items in this builder's internal collection. + */ public int size() { return size; } @@ -390,6 +396,7 @@ public class RefList<T extends Ref> implements Iterable<Ref> { * after additions are complete using {@link #sort()}. * * @param ref + * reference to add */ public void add(T ref) { if (list.length == size) { @@ -404,6 +411,7 @@ public class RefList<T extends Ref> implements Iterable<Ref> { * Add all items from another builder. * * @param other + * another builder * @since 5.4 */ public void addAll(Builder other) { @@ -454,6 +462,7 @@ public class RefList<T extends Ref> implements Iterable<Ref> { * Dedupe the refs in place. Must be called after {@link #sort}. * * @param mergeFunction + * function used for de-duplication */ @SuppressWarnings("unchecked") void dedupe(BinaryOperator<T> mergeFunction) { @@ -475,7 +484,11 @@ public class RefList<T extends Ref> implements Iterable<Ref> { Arrays.fill(list, size, list.length, null); } - /** @return an unmodifiable list using this collection's backing array. */ + /** + * Get unmodifiable list based on this list + * + * @return an unmodifiable list using this collection's backing array. + */ public RefList<T> toRefList() { return new RefList<>(list, size); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java index c68a76cef4..a4d1fd5b70 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java @@ -119,13 +119,11 @@ public class RefMap extends AbstractMap<String, Ref> { this.resolved = (RefList<Ref>) resolved; } - /** {@inheritDoc} */ @Override public boolean containsKey(Object name) { return get(name) != null; } - /** {@inheritDoc} */ @Override public Ref get(Object key) { String name = toRefName((String) key); @@ -137,7 +135,6 @@ public class RefMap extends AbstractMap<String, Ref> { return ref; } - /** {@inheritDoc} */ @Override public Ref put(String keyName, Ref value) { String name = toRefName(keyName); @@ -165,7 +162,6 @@ public class RefMap extends AbstractMap<String, Ref> { return prior; } - /** {@inheritDoc} */ @Override public Ref remove(Object key) { String name = toRefName((String) key); @@ -189,13 +185,11 @@ public class RefMap extends AbstractMap<String, Ref> { return res; } - /** {@inheritDoc} */ @Override public boolean isEmpty() { return entrySet().isEmpty(); } - /** {@inheritDoc} */ @Override public Set<Entry<String, Ref>> entrySet() { if (entrySet == null) { @@ -238,7 +232,6 @@ public class RefMap extends AbstractMap<String, Ref> { return entrySet; } - /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); @@ -259,6 +252,7 @@ public class RefMap extends AbstractMap<String, Ref> { * Create a {@link Collector} for {@link Ref}. * * @param mergeFunction + * merge function * @return {@link Collector} for {@link Ref} * @since 5.4 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java index 5611b1e78e..b6b19e0e7b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java @@ -10,6 +10,8 @@ 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; @@ -42,12 +44,29 @@ public class RelativeDateFormatter { * @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()); + } - long ageMillis = SystemReader.getInstance().getCurrentTime() - - when.getTime(); + /** + * 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) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java index cf06172c17..e3e3e04fd9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java @@ -13,8 +13,8 @@ import java.text.MessageFormat; import java.util.Locale; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; -import org.eclipse.jgit.lib.GpgSignatureVerifier.TrustLevel; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifier.TrustLevel; import org.eclipse.jgit.lib.PersonIdent; /** @@ -39,29 +39,34 @@ public final class SignatureUtils { * 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(); - // Use the creator's timezone for the signature date - PersonIdent dateId = new PersonIdent(creator, - verification.getCreationDate()); - result.append(MessageFormat.format(JGitText.get().verifySignatureMade, - formatter.formatDate(dateId))); - result.append('\n'); + 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.getKeyFingerprint().toUpperCase(Locale.ROOT))); + verification.keyFingerprint().toUpperCase(Locale.ROOT))); result.append('\n'); - if (!StringUtils.isEmptyOrNull(verification.getSigner())) { + if (!StringUtils.isEmptyOrNull(verification.signer())) { result.append( MessageFormat.format(JGitText.get().verifySignatureIssuer, - verification.getSigner())); + verification.signer())); result.append('\n'); } String msg; - if (verification.getVerified()) { - if (verification.isExpired()) { + if (verification.verified()) { + if (verification.expired()) { msg = JGitText.get().verifySignatureExpired; } else { msg = JGitText.get().verifySignatureGood; @@ -69,14 +74,14 @@ public final class SignatureUtils { } else { msg = JGitText.get().verifySignatureBad; } - result.append(MessageFormat.format(msg, verification.getKeyUser())); - if (!TrustLevel.UNKNOWN.equals(verification.getTrustLevel())) { + result.append(MessageFormat.format(msg, verification.keyUser())); + if (!TrustLevel.UNKNOWN.equals(verification.trustLevel())) { result.append(' ' + MessageFormat .format(JGitText.get().verifySignatureTrust, verification - .getTrustLevel().name().toLowerCase(Locale.ROOT))); + .trustLevel().name().toLowerCase(Locale.ROOT))); } result.append('\n'); - msg = verification.getMessage(); + msg = verification.message(); if (!StringUtils.isEmptyOrNull(msg)) { result.append(msg); result.append('\n'); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java index e29704158d..42a76b5b16 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java @@ -48,6 +48,7 @@ public class SshSupport { * 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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java index d957deb34c..efa6e7ddc3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java @@ -43,14 +43,18 @@ public class Stats { } /** - * @return number of the added values + * Returns the number of added values + * + * @return the number of added values */ public int count() { return n; } /** - * @return minimum of the added values + * Returns the smallest value added + * + * @return the smallest value added */ public double min() { if (n < 1) { @@ -60,7 +64,9 @@ public class Stats { } /** - * @return maximum of the added values + * Returns the biggest value added + * + * @return the biggest value added */ public double max() { if (n < 1) { @@ -70,9 +76,10 @@ public class Stats { } /** - * @return average of the added values + * Returns the average of the added values + * + * @return the average of the added values */ - public double avg() { if (n < 1) { return Double.NaN; @@ -81,7 +88,9 @@ public class Stats { } /** - * @return variance of the added values + * Returns the variance of the added values + * + * @return the variance of the added values */ public double var() { if (n < 2) { @@ -91,7 +100,9 @@ public class Stats { } /** - * @return standard deviation of the added values + * Returns the standard deviation of the added values + * + * @return the standard deviation of the added values */ public double stddev() { return Math.sqrt(this.var()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java index 917add3609..e381a3bcc9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java @@ -14,6 +14,7 @@ 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; @@ -22,6 +23,8 @@ import org.eclipse.jgit.lib.Constants; */ 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; @@ -181,9 +184,9 @@ public final class StringUtils { * * @param stringValue * the string to parse. - * @return the boolean interpretation of {@code value}. + * @return the boolean interpretation of {@code stringValue}. * @throws java.lang.IllegalArgumentException - * if {@code value} is not recognized as one of the standard + * if {@code stringValue} is not recognized as one of the standard * boolean names. */ public static boolean toBoolean(String stringValue) { @@ -275,6 +278,44 @@ public final class StringUtils { } /** + * Remove the specified character from beginning and end of a string + * <p> + * 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. * @@ -346,7 +387,7 @@ public final class StringUtils { * allow negative numbers, too * @return the value parsed * @throws NumberFormatException - * if the {@value} is not parseable, or beyond the range of + * 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 @@ -420,7 +461,7 @@ public final class StringUtils { * allow negative numbers, too * @return the value parsed * @throws NumberFormatException - * if the {@value} is not parseable or beyond the range of + * 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 @@ -463,4 +504,39 @@ public final class StringUtils { } 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; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index a8a77904a2..0b7c6204f2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -23,10 +23,12 @@ import java.nio.charset.UnsupportedCharsetException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.AccessController; -import java.security.PrivilegedAction; 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; @@ -39,6 +41,7 @@ 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; @@ -124,28 +127,20 @@ public abstract class SystemReader { @Override public FileBasedConfig openUserConfig(Config parent, FS fs) { - return new FileBasedConfig(parent, new File(fs.userHome(), ".gitconfig"), //$NON-NLS-1$ - fs); - } - - private Path getXDGConfigHome(FS fs) { - String configHomePath = getenv(Constants.XDG_CONFIG_HOME); - if (StringUtils.isEmptyOrNull(configHomePath)) { - configHomePath = new File(fs.userHome(), ".config") //$NON-NLS-1$ - .getAbsolutePath(); - } - try { - return Paths.get(configHomePath); - } catch (InvalidPathException e) { - LOG.error(JGitText.get().logXDGConfigHomeInvalid, - configHomePath, e); + 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 null; + return new FileBasedConfig(parent, homeFile, fs); } @Override public FileBasedConfig openJGitConfig(Config parent, FS fs) { - Path xdgPath = getXDGConfigHome(fs); + Path xdgPath = getXdgConfigDirectory(fs); if (xdgPath != null) { Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$ .resolve(Constants.CONFIG); @@ -176,11 +171,87 @@ public abstract class SystemReader { } @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; /** @@ -391,6 +462,66 @@ public abstract class SystemReader { } /** + * 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 @@ -419,10 +550,37 @@ public abstract class SystemReader { * 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. @@ -438,20 +596,48 @@ public abstract class SystemReader { * @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 @@ -586,9 +772,7 @@ public abstract class SystemReader { } private String getOsName() { - return AccessController.doPrivileged( - (PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$ - ); + return getProperty("os.name"); //$NON-NLS-1$ } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java index cedb159827..ccc19691b1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java @@ -57,14 +57,12 @@ public class AutoCRLFInputStream extends InputStream { this.detectBinary = detectBinary; } - /** {@inheritDoc} */ @Override public int read() throws IOException { final int read = read(single, 0, 1); return read == 1 ? single[0] & 0xff : -1; } - /** {@inheritDoc} */ @Override public int read(byte[] bs, int off, int len) throws IOException { if (len == 0) @@ -103,7 +101,6 @@ public class AutoCRLFInputStream extends InputStream { return n; } - /** {@inheritDoc} */ @Override public void close() throws IOException { in.close(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java index 305ccbd7e6..9fb316f28d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java @@ -65,14 +65,12 @@ public class AutoCRLFOutputStream extends OutputStream { this.detectBinary = detectBinary; } - /** {@inheritDoc} */ @Override public void write(int b) throws IOException { onebytebuf[0] = (byte) b; write(onebytebuf, 0, 1); } - /** {@inheritDoc} */ @Override public void write(byte[] b) throws IOException { int overflow = buffer(b, 0, b.length); @@ -80,7 +78,6 @@ public class AutoCRLFOutputStream extends OutputStream { write(b, b.length - overflow, overflow); } - /** {@inheritDoc} */ @Override public void write(byte[] b, int startOff, int startLen) throws IOException { @@ -151,7 +148,6 @@ public class AutoCRLFOutputStream extends OutputStream { write(binbuf, 0, cachedLen); } - /** {@inheritDoc} */ @Override public void flush() throws IOException { if (binbufcnt <= binbuf.length) { @@ -161,7 +157,6 @@ public class AutoCRLFOutputStream extends OutputStream { out.flush(); } - /** {@inheritDoc} */ @Override public void close() throws IOException { flush(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java index 7db882c074..4b9706a3ce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java @@ -147,52 +147,12 @@ public class AutoLFInputStream extends InputStream { && flags.contains(StreamFlag.FOR_CHECKOUT); } - /** - * Creates a new InputStream, wrapping the specified stream. - * - * @param in - * raw input stream - * @param detectBinary - * whether binaries should be detected - * @since 2.0 - * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)} - * instead - */ - @Deprecated - public AutoLFInputStream(InputStream in, boolean detectBinary) { - this(in, detectBinary, false); - } - - /** - * Creates a new InputStream, wrapping the specified stream. - * - * @param in - * raw input stream - * @param detectBinary - * whether binaries should be detected - * @param abortIfBinary - * throw an IOException if the file is binary - * @since 3.3 - * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)} - * instead - */ - @Deprecated - public AutoLFInputStream(InputStream in, boolean detectBinary, - boolean abortIfBinary) { - this.in = in; - this.detectBinary = detectBinary; - this.abortIfBinary = abortIfBinary; - this.forCheckout = false; - } - - /** {@inheritDoc} */ @Override public int read() throws IOException { final int read = read(single, 0, 1); return read == 1 ? single[0] & 0xff : -1; } - /** {@inheritDoc} */ @Override public int read(byte[] bs, int off, int len) throws IOException { @@ -242,7 +202,6 @@ public class AutoLFInputStream extends InputStream { return isBinary; } - /** {@inheritDoc} */ @Override public void close() throws IOException { in.close(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java index a0e9fb68c5..e56991d43a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java @@ -69,14 +69,12 @@ public class AutoLFOutputStream extends OutputStream { this.detectBinary = detectBinary; } - /** {@inheritDoc} */ @Override public void write(int b) throws IOException { onebytebuf[0] = (byte) b; write(onebytebuf, 0, 1); } - /** {@inheritDoc} */ @Override public void write(byte[] b) throws IOException { int overflow = buffer(b, 0, b.length); @@ -85,7 +83,6 @@ public class AutoLFOutputStream extends OutputStream { } } - /** {@inheritDoc} */ @Override public void write(byte[] b, int startOff, int startLen) throws IOException { @@ -164,7 +161,6 @@ public class AutoLFOutputStream extends OutputStream { write(binbuf, 0, cachedLen); } - /** {@inheritDoc} */ @Override public void flush() throws IOException { if (binbufcnt <= binbuf.length) { @@ -173,7 +169,6 @@ public class AutoLFOutputStream extends OutputStream { out.flush(); } - /** {@inheritDoc} */ @Override public void close() throws IOException { flush(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java new file mode 100644 index 0000000000..804f7f860a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023, SAP SE 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.InvalidMarkException; +import java.util.Objects; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.JGitText; + +/** + * An {@link InputStream} backed by a {@link ByteBuffer}. + * + * @since 6.8 + */ +public class ByteBufferInputStream extends InputStream { + + private ByteBuffer buf; + + /** + * Creates a {@link ByteBufferInputStream} + * + * @param buf + * the ByteBuffer backing the stream + */ + public ByteBufferInputStream(@NonNull ByteBuffer buf) { + this.buf = buf; + } + + @Override + public int read() throws IOException { + nullCheck(); + if (buf.hasRemaining()) { + return buf.get() & 0xFF; + } + return -1; + } + + @Override + public int read(byte[] b) throws IOException { + nullCheck(); + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + nullCheck(); + Objects.checkFromIndexSize(off, len, b.length); + if (len == 0) { + return 0; + } + int length = Math.min(buf.remaining(), len); + if (length == 0) { + return -1; + } + buf.get(b, off, length); + return length; + } + + @Override + public byte[] readAllBytes() throws IOException { + return readNBytes(buf.remaining()); + } + + @Override + public byte[] readNBytes(int len) throws IOException { + int l = Math.min(len, buf.remaining()); + byte[] b = new byte[l]; + read(b); + return b; + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + return read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + nullCheck(); + if (n <= 0) { + return 0; + } + // ByteBuffer index has type int + int delta = n > Integer.MAX_VALUE ? buf.remaining() + : Math.min((int) n, buf.remaining()); + buf.position(buf.position() + delta); + return delta; + } + + @Override + public int available() throws IOException { + nullCheck(); + return buf.remaining(); + } + + @Override + public void close() { + buf = null; + } + + @Override + public synchronized void mark(int readlimit) { + buf.mark(); + } + + @Override + public synchronized void reset() throws IOException { + try { + buf.reset(); + } catch (InvalidMarkException e) { + throw new IOException(e); + } + } + + @Override + public boolean markSupported() { + return true; + } + + private void nullCheck() throws IOException { + if (buf == null) { + throw new IOException(JGitText.get().inputStreamClosed); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java index 782f3f4ca6..d0049d29de 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java @@ -39,27 +39,23 @@ public class CountingOutputStream extends OutputStream { return cnt; } - /** {@inheritDoc} */ @Override public void write(int val) throws IOException { out.write(val); cnt++; } - /** {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) throws IOException { out.write(buf, off, len); cnt += len; } - /** {@inheritDoc} */ @Override public void flush() throws IOException { out.flush(); } - /** {@inheritDoc} */ @Override public void close() throws IOException { out.close(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java index f1bfbe29a5..03c25bb702 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java @@ -27,7 +27,6 @@ public final class DisabledOutputStream extends OutputStream { // more than one instance from being created. } - /** {@inheritDoc} */ @Override public void write(int b) throws IOException { // We shouldn't be writing output at this stage, there diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java index 7e46afbc4b..888b8fbb09 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java @@ -104,10 +104,20 @@ public final class InterruptTimer { */ public void terminate() { state.terminate(); + boolean interrupted = false; try { - thread.join(); - } catch (InterruptedException e) { - // + while (true) { + try { + thread.join(); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java index 2bbdbefd38..1faf6ea9aa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java @@ -58,13 +58,11 @@ public class IsolatedOutputStream extends OutputStream { new ArrayBlockingQueue<>(1), new NamedThreadFactory()); } - /** {@inheritDoc} */ @Override public void write(int ch) throws IOException { write(new byte[] { (byte) ch }, 0, 1); } - /** {@inheritDoc} */ @Override public void write(byte[] buf, int pos, int cnt) throws IOException { @@ -75,7 +73,6 @@ public class IsolatedOutputStream extends OutputStream { }); } - /** {@inheritDoc} */ @Override public void flush() throws IOException { checkClosed(); @@ -85,7 +82,6 @@ public class IsolatedOutputStream extends OutputStream { }); } - /** {@inheritDoc} */ @Override public void close() throws IOException { if (!copier.isShutdown()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java index 88006242d9..681a52988e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java @@ -47,21 +47,18 @@ public abstract class LimitedInputStream extends FilterInputStream { this.limit = limit; } - /** {@inheritDoc} */ @Override public int available() throws IOException { return (int) Math.min(in.available(), left); } // it's okay to mark even if mark isn't supported, as reset won't work - /** {@inheritDoc} */ @Override public synchronized void mark(int readLimit) { in.mark(readLimit); mark = left; } - /** {@inheritDoc} */ @Override public int read() throws IOException { if (left == 0) { @@ -78,7 +75,6 @@ public abstract class LimitedInputStream extends FilterInputStream { return result; } - /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { if (left == 0) { @@ -96,7 +92,6 @@ public abstract class LimitedInputStream extends FilterInputStream { return result; } - /** {@inheritDoc} */ @Override public synchronized void reset() throws IOException { if (!in.markSupported()) @@ -109,7 +104,6 @@ public abstract class LimitedInputStream extends FilterInputStream { left = mark; } - /** {@inheritDoc} */ @Override public long skip(long n) throws IOException { n = Math.min(n, left); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java index 2637766153..8d5b8fdcba 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java @@ -51,7 +51,6 @@ public class MessageWriter extends Writer { enc = new OutputStreamWriter(getRawStream(), UTF_8); } - /** {@inheritDoc} */ @Override public void write(char[] cbuf, int off, int len) throws IOException { synchronized (buf) { @@ -71,20 +70,17 @@ public class MessageWriter extends Writer { return buf; } - /** {@inheritDoc} */ @Override public void close() throws IOException { // Do nothing, we are buffered with no resources. } - /** {@inheritDoc} */ @Override public void flush() throws IOException { // Do nothing, we are buffered with no resources. } /** @return string version of all buffered data. */ - /** {@inheritDoc} */ @Override public String toString() { return RawParseUtils.decode(buf.toByteArray()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java new file mode 100644 index 0000000000..8c2c61a434 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2025 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.util.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An {@link InputStream} that swallows exceptions on {@link #close()}. + * + * @since 7.4 + */ +public class SilentInputStream extends FilterInputStream { + + private static final Logger LOG = LoggerFactory + .getLogger(SilentInputStream.class); + + /** + * Wraps an existing {@link InputStream}. + * + * @param in + * {@link InputStream} to wrap + */ + public SilentInputStream(InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } catch (IOException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Exception ignored while closing input stream", e); //$NON-NLS-1$ + } + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java index c0724e43f2..ed412fa6f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java @@ -67,7 +67,6 @@ public class StreamCopyThread extends Thread { } } - /** {@inheritDoc} */ @Override public void run() { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java index 56d0169f7d..96376bfc6a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java @@ -46,7 +46,6 @@ public class TeeInputStream extends InputStream { this.dst = dst; } - /** {@inheritDoc} */ @Override public int read() throws IOException { byte[] b = skipBuffer(); @@ -54,7 +53,6 @@ public class TeeInputStream extends InputStream { return n == 1 ? b[0] & 0xff : -1; } - /** {@inheritDoc} */ @Override public long skip(long count) throws IOException { long skipped = 0; @@ -71,7 +69,6 @@ public class TeeInputStream extends InputStream { return skipped; } - /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { if (len == 0) @@ -83,7 +80,6 @@ public class TeeInputStream extends InputStream { return n; } - /** {@inheritDoc} */ @Override public void close() throws IOException { byte[] b = skipBuffer(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java index e6fdd709b2..ab084a66f2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java @@ -34,35 +34,30 @@ public class TeeOutputStream extends OutputStream { this.stream2 = stream2; } - /** {@inheritDoc} */ @Override public void write(byte[] buf) throws IOException { this.stream1.write(buf); this.stream2.write(buf); } - /** {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) throws IOException { this.stream1.write(buf, off, len); this.stream2.write(buf, off, len); } - /** {@inheritDoc} */ @Override public void write(int b) throws IOException { this.stream1.write(b); this.stream2.write(b); } - /** {@inheritDoc} */ @Override public void flush() throws IOException { this.stream1.flush(); this.stream2.flush(); } - /** {@inheritDoc} */ @Override public void close() throws IOException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java index 3bc92d5bcd..13982b133c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java @@ -11,8 +11,6 @@ package org.eclipse.jgit.util.io; import java.io.IOException; import java.io.Writer; -import java.security.AccessController; -import java.security.PrivilegedAction; import org.eclipse.jgit.util.SystemReader; @@ -35,25 +33,19 @@ public class ThrowingPrintWriter extends Writer { */ public ThrowingPrintWriter(Writer out) { this.out = out; - LF = AccessController - .doPrivileged((PrivilegedAction<String>) () -> SystemReader - .getInstance().getProperty("line.separator") //$NON-NLS-1$ - ); + LF = SystemReader.getInstance().getProperty("line.separator"); //$NON-NLS-1$ } - /** {@inheritDoc} */ @Override public void write(char[] cbuf, int off, int len) throws IOException { out.write(cbuf, off, len); } - /** {@inheritDoc} */ @Override public void flush() throws IOException { out.flush(); } - /** {@inheritDoc} */ @Override public void close() throws IOException { out.close(); @@ -62,8 +54,10 @@ public class ThrowingPrintWriter extends Writer { /** * Print a string and terminate with a line feed. * - * @param s a {@link java.lang.String} object. + * @param s + * a {@link java.lang.String} object. * @throws java.io.IOException + * if an IO error occurred */ public void println(String s) throws IOException { print(s + LF); @@ -73,6 +67,7 @@ public class ThrowingPrintWriter extends Writer { * Print a platform dependent new line * * @throws java.io.IOException + * if an IO error occurred */ public void println() throws IOException { print(LF); @@ -81,8 +76,10 @@ public class ThrowingPrintWriter extends Writer { /** * Print a char * - * @param value a char. + * @param value + * a char. * @throws java.io.IOException + * if an IO error occurred */ public void print(char value) throws IOException { print(String.valueOf(value)); @@ -94,6 +91,7 @@ public class ThrowingPrintWriter extends Writer { * @param value * an int. * @throws java.io.IOException + * if an IO error occurred */ public void print(int value) throws IOException { print(String.valueOf(value)); @@ -102,8 +100,10 @@ public class ThrowingPrintWriter extends Writer { /** * Print a long as string * - * @param value a long. + * @param value + * a long. * @throws java.io.IOException + * if an IO error occurred */ public void print(long value) throws IOException { print(String.valueOf(value)); @@ -112,8 +112,10 @@ public class ThrowingPrintWriter extends Writer { /** * Print a short as string * - * @param value a short. + * @param value + * a short. * @throws java.io.IOException + * if an IO error occurred */ public void print(short value) throws IOException { print(String.valueOf(value)); @@ -128,6 +130,7 @@ public class ThrowingPrintWriter extends Writer { * @param args * objects. * @throws java.io.IOException + * if an IO error occurred */ public void format(String fmt, Object... args) throws IOException { print(String.format(fmt, args)); @@ -139,6 +142,7 @@ public class ThrowingPrintWriter extends Writer { * @param any * an object. * @throws java.io.IOException + * if an IO error occurred */ public void print(Object any) throws IOException { out.write(String.valueOf(any)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java index 1947b3bf04..4d9f83d233 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java @@ -63,7 +63,6 @@ public class TimeoutInputStream extends FilterInputStream { timeout = millis; } - /** {@inheritDoc} */ @Override public int read() throws IOException { try { @@ -76,13 +75,11 @@ public class TimeoutInputStream extends FilterInputStream { } } - /** {@inheritDoc} */ @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } - /** {@inheritDoc} */ @Override public int read(byte[] buf, int off, int cnt) throws IOException { try { @@ -95,7 +92,6 @@ public class TimeoutInputStream extends FilterInputStream { } } - /** {@inheritDoc} */ @Override public long skip(long cnt) throws IOException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java index 3fbf6ffdcb..afd798a1a2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java @@ -65,7 +65,6 @@ public class TimeoutOutputStream extends OutputStream { timeout = millis; } - /** {@inheritDoc} */ @Override public void write(int b) throws IOException { try { @@ -78,13 +77,11 @@ public class TimeoutOutputStream extends OutputStream { } } - /** {@inheritDoc} */ @Override public void write(byte[] buf) throws IOException { write(buf, 0, buf.length); } - /** {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) throws IOException { try { @@ -97,7 +94,6 @@ public class TimeoutOutputStream extends OutputStream { } } - /** {@inheritDoc} */ @Override public void flush() throws IOException { try { @@ -110,7 +106,6 @@ public class TimeoutOutputStream extends OutputStream { } } - /** {@inheritDoc} */ @Override public void close() throws IOException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java index 459888190f..7e950f6529 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java @@ -12,8 +12,8 @@ package org.eclipse.jgit.util.io; import java.io.IOException; import java.io.InputStream; -import java.util.Iterator; -import java.util.LinkedList; +import java.util.ArrayDeque; +import java.util.Deque; /** * An InputStream which reads from one or more InputStreams. @@ -34,7 +34,7 @@ public class UnionInputStream extends InputStream { } }; - private final LinkedList<InputStream> streams = new LinkedList<>(); + private final Deque<InputStream> streams = new ArrayDeque<>(); /** * Create an empty InputStream that is currently at EOF state. @@ -91,7 +91,6 @@ public class UnionInputStream extends InputStream { return streams.isEmpty(); } - /** {@inheritDoc} */ @Override public int read() throws IOException { for (;;) { @@ -106,7 +105,6 @@ public class UnionInputStream extends InputStream { } } - /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { if (len == 0) @@ -123,13 +121,11 @@ public class UnionInputStream extends InputStream { } } - /** {@inheritDoc} */ @Override public int available() throws IOException { return head().available(); } - /** {@inheritDoc} */ @Override public long skip(long count) throws IOException { long skipped = 0; @@ -163,19 +159,18 @@ public class UnionInputStream extends InputStream { return skipped; } - /** {@inheritDoc} */ @Override public void close() throws IOException { IOException err = null; - for (Iterator<InputStream> i = streams.iterator(); i.hasNext();) { + for (InputStream stream : streams) { try { - i.next().close(); + stream.close(); } catch (IOException closeError) { err = closeError; } - i.remove(); } + streams.clear(); if (err != null) throw err; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java index 56e90d0636..0a56c83040 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java @@ -193,7 +193,6 @@ public abstract class SHA1 { * <p> * Implementations not supporting collision detection always return * {@code false}. - * <p> * * @return {@code true} if a likely collision was detected. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java index 213ee97531..33e6875883 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java @@ -407,7 +407,7 @@ class SHA1Java extends SHA1 { private static int s1(int a, int b, int c, int d, int w_t) { return rotateLeft(a, 5) // f: 0 <= t <= 19 - + ((b & c) | ((~b) & d)) + + ((b & c) | (~b & d)) + 0x5A827999 + w_t; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java index cebdbee27a..91ee3cc9be 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java @@ -89,6 +89,7 @@ final class UbcCheck { private static final int DV_II_55_0_bit = 1 << 30; private static final int DV_II_56_0_bit = 1 << 31; + @SuppressWarnings("UnnecessaryParentheses") static int check(int[] w) { int mask = ~0; mask &= (((((w[44] ^ w[45]) >>> 29) & 1) - 1) | ~(DV_I_48_0_bit diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java index 66857b5bf8..4e079f08b5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java @@ -37,7 +37,6 @@ public class MonotonicSystemClock implements MonotonicClock { } } - /** {@inheritDoc} */ @Override public ProposedTimestamp propose() { final long u = nowMicros(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java index 8c20423bc6..a20eaaf908 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java @@ -138,7 +138,10 @@ public abstract class ProposedTimestamp implements AutoCloseable { * Get time since epoch, with up to microsecond resolution. * * @return time since epoch, with up to microsecond resolution. + * + * @deprecated Use instant() instead */ + @Deprecated(since = "7.2") public Timestamp timestamp() { return Timestamp.from(instant()); } @@ -147,7 +150,10 @@ public abstract class ProposedTimestamp implements AutoCloseable { * Get time since epoch, with up to millisecond resolution. * * @return time since epoch, with up to millisecond resolution. + * + * @deprecated Use instant() instead */ + @Deprecated(since = "7.2") public Date date() { return new Date(millis()); } @@ -162,7 +168,6 @@ public abstract class ProposedTimestamp implements AutoCloseable { // Do nothing by default. } - /** {@inheritDoc} */ @Override public String toString() { return instant().toString(); |