* stable-5.4: (82 commits) Export all packages of o.e.j.ant and o.e.j.archive bundles Do not require test bundles to export all packages Fix API problem filters Increase severity of AmbiguousMethodReference to ERROR [error prone] suppress AmbiguousMethodReference in AnyLongObjectId [error prone] fix ReferenceEquality warning in CommitBuilder [error prone] suppress NonAtomicVolatileUpdate warning in SimpleLruCache [error prone] fix ReferenceEquality warning in CommitGraphPane#authorFor [error prone] fix ReferenceEquality warning in RevWalk#isMergedInto [error prone] fix ReferenceEquality warning in RefUpdate#updateImpl [error prone] fix ReferenceEquality warning in static equals methods [error prone] suppress AmbiguousMethodReference in AnyObjectId [error prone] fix "FutureReturnValueIgnored" error in FS Fix formatting and add missing braces in Repository#stripWorkDir Repository: fix reference comparison of Files MergeAlgorithm: Suppress Error Prone warning about reference equality Fix NarrowingCompoundAssignment warnings from Error Prone FS_POSIX: handle Files.getFileStore() failures Fix OpenSshConfigTest#config FileSnapshot: fix bug with timestamp thresholding In LockFile#waitForStatChange wait in units of file time resolution Cache FileStoreAttributeCache per directory Fix FileSnapshot#save(long) and FileSnapshot#save(Instant) Persist minimal racy threshold and allow manual configuration Measure minimum racy interval to auto-configure FileSnapshot Reuse FileUtils to recursively delete files created by tests Fix FileAttributeCache.toString() Add test for racy git detection in FileSnapshot Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit Add missing javadoc in org.eclipse.jgit.junit Enhance RepeatRule to report number of failures at the end Fix FileSnapshotTests for filesystem with high timestamp resolution Retry deleting test files in FileBasedConfigTest Measure filesystem timestamp resolution already in test setup Refactor FileSnapshotTest to use NIO APIs Measure stored timestamp resolution instead of time to touch file Handle CancellationException in FileStoreAttributeCache Fix FileSnapshot#saveNoConfig Use Instant for smudge time in DirCache and DirCacheEntry Use Instant instead of milliseconds for filesystem timestamp handling Workaround SecurityException in FS#getFsTimestampResolution Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution FS: ignore AccessDeniedException when measuring timestamp resolution Add debug trace for FileSnapshot Use FileChannel.open to touch file and set mtime to now Persist filesystem timestamp resolution and allow manual configuration Increase bazel timeout for long running tests Bazel: Fix lint warning flagged by buildifier Update bazlets to latest version Bazel: Add missing dependencies for ArchiveCommandTest Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file Add support for nanoseconds and microseconds for Config#getTimeUnit Optionally measure filesystem timestamp resolution asynchronously Delete unused FileTreeIteratorWithTimeControl FileSnapshot#equals: consider UNKNOWN_SIZE Timeout measuring file timestamp resolution after 2 seconds Fix RacyGitTests#testRacyGitDetection GlobalBundleCache: Fix ClassNewInstance warning from Error Prone IncorrectObjectTypeException: Fix typos in constructors' Javadoc Change RacyGitTests to create a racy git situation in a stable way Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8 Fix non-deterministic hash of archives created by ArchiveCommand Update Maven plugins ecj, plexus, error-prone Update Maven plugins and cleanup Maven warnings Make inner classes static where possible Error Prone: Increase severity of NonOverridingEquals to ERROR Error Prone: Increase severity of ImmutableEnumChecker to ERROR GitDateParser#ParseableSimpleDateFormat: Make formatStr private final BatchRefUpdateTest: Suppress ImmutableEnumChecker warning PacketLineIn: Suppress comparison warnings for END and DELIM FileSnapshot#toString: Suppress ReferenceEquality warnings Blame: Suppress ReferenceEquality warning for RevCommit instances Fix API problem filters pgm: add missing optional dependency to org.tukaani:xz NetscapeCookieFile: Make hash static and group overloaded write NetscapeCookieFile: Javadoc fixes Config: Handle reference-equality warning (and empty javadoc) Error Prone: Increase severity of ShortCircuitBoolean to ERROR ObjectWalk: Prefer boolean operators over logical operators in comparisons BasePackFetchConnection: Prefer boolean operators over logical operators in comparisons PackWriter: Prefer boolean operators over logical operators in comparisons Change-Id: I825fd55bcb5345fb7afe066bf54ca50325f40acb Signed-off-by: David Pursehouse <david.pursehouse@gmail.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.5.0.201908280940-m3
visibility = [ | visibility = [ | ||||
"//org.eclipse.jgit.archive:__pkg__", | "//org.eclipse.jgit.archive:__pkg__", | ||||
"//org.eclipse.jgit.pgm.test:__pkg__", | "//org.eclipse.jgit.pgm.test:__pkg__", | ||||
"//org.eclipse.jgit.test:__pkg__", | |||||
], | ], | ||||
exports = ["@commons-compress//jar"], | exports = ["@commons-compress//jar"], | ||||
) | ) |
@Before | @Before | ||||
public void before() throws IOException { | public void before() throws IOException { | ||||
dest = createTempFile(); | |||||
FS.getFileStoreAttributes(dest.toPath().getParent()); | |||||
project = new Project(); | project = new Project(); | ||||
project.init(); | project.init(); | ||||
enableLogging(); | enableLogging(); | ||||
project.addTaskDefinition("git-clone", GitCloneTask.class); | project.addTaskDefinition("git-clone", GitCloneTask.class); | ||||
task = (GitCloneTask) project.createTask("git-clone"); | task = (GitCloneTask) project.createTask("git-clone"); | ||||
dest = createTempFile(); | |||||
task.setDest(dest); | task.setDest(dest); | ||||
} | } | ||||
org.eclipse.jgit.storage.file;version="[5.5.0,5.6.0)" | org.eclipse.jgit.storage.file;version="[5.5.0,5.6.0)" | ||||
Bundle-Localization: plugin | Bundle-Localization: plugin | ||||
Bundle-Vendor: %Bundle-Vendor | Bundle-Vendor: %Bundle-Vendor | ||||
Export-Package: org.eclipse.jgit.ant, | |||||
org.eclipse.jgit.ant.tasks;version="5.5.0";uses:="org.apache.tools.ant.types,org.apache.tools.ant" | |||||
Export-Package: org.eclipse.jgit.ant;version="5.5.0", | |||||
org.eclipse.jgit.ant.tasks;version="5.5.0"; | |||||
uses:="org.apache.tools.ant, | |||||
org.apache.tools.ant.types" |
org.eclipse.jgit.api, | org.eclipse.jgit.api, | ||||
org.apache.commons.compress.archivers, | org.apache.commons.compress.archivers, | ||||
org.osgi.framework", | org.osgi.framework", | ||||
org.eclipse.jgit.archive.internal;x-internal:=true | |||||
org.eclipse.jgit.archive.internal;version="5.5.0";x-internal:=true |
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import java.io.RandomAccessFile; | import java.io.RandomAccessFile; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Instant; | |||||
import java.util.Enumeration; | import java.util.Enumeration; | ||||
import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
import javax.servlet.http.HttpServletResponse; | import javax.servlet.http.HttpServletResponse; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.util.FS; | |||||
/** | /** | ||||
* Dumps a file over HTTP GET (or its information via HEAD). | * Dumps a file over HTTP GET (or its information via HEAD). | ||||
private final RandomAccessFile source; | private final RandomAccessFile source; | ||||
private final long lastModified; | |||||
private final Instant lastModified; | |||||
private final long fileLen; | private final long fileLen; | ||||
this.source = new RandomAccessFile(path, "r"); | this.source = new RandomAccessFile(path, "r"); | ||||
try { | try { | ||||
this.lastModified = path.lastModified(); | |||||
this.lastModified = FS.DETECTED.lastModifiedInstant(path); | |||||
this.fileLen = source.getChannel().size(); | this.fileLen = source.getChannel().size(); | ||||
this.end = fileLen; | this.end = fileLen; | ||||
} catch (IOException e) { | } catch (IOException e) { | ||||
} | } | ||||
} | } | ||||
long getLastModified() { | |||||
Instant getLastModified() { | |||||
return lastModified; | return lastModified; | ||||
} | } | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.time.Instant; | |||||
import javax.servlet.ServletException; | import javax.servlet.ServletException; | ||||
import javax.servlet.http.HttpServlet; | import javax.servlet.http.HttpServlet; | ||||
@Override | @Override | ||||
String etag(FileSender sender) throws IOException { | String etag(FileSender sender) throws IOException { | ||||
return Long.toHexString(sender.getLastModified()); | |||||
Instant lastModified = sender.getLastModified(); | |||||
return Long.toHexString(lastModified.getEpochSecond()) | |||||
+ Long.toHexString(lastModified.getNano()); | |||||
} | } | ||||
} | } | ||||
try { | try { | ||||
final String etag = etag(sender); | final String etag = etag(sender); | ||||
final long lastModified = (sender.getLastModified() / 1000) * 1000; | |||||
// HTTP header Last-Modified header has a resolution of 1 sec, see | |||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29 | |||||
final long lastModified = sender.getLastModified().getEpochSecond(); | |||||
String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH); | String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH); | ||||
if (etag != null && etag.equals(ifNoneMatch)) { | if (etag != null && etag.equals(ifNoneMatch)) { |
eclipse.preferences.version=1 | eclipse.preferences.version=1 | ||||
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled | |||||
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled | |||||
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore | ||||
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull | |||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault | |||||
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable | |||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled | |||||
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull | |||||
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= | |||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault | |||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= | |||||
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable | |||||
org.eclipse.jdt.core.compiler.annotation.nullable.secondary= | |||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled | |||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled | ||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate | ||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 | ||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate | org.eclipse.jdt.core.compiler.debug.localVariable=generate | ||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate | org.eclipse.jdt.core.compiler.debug.sourceFile=generate | ||||
org.eclipse.jdt.core.compiler.doc.comment.support=enabled | org.eclipse.jdt.core.compiler.doc.comment.support=enabled | ||||
org.eclipse.jdt.core.compiler.problem.APILeak=warning | |||||
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning | ||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error | ||||
org.eclipse.jdt.core.compiler.problem.autoboxing=warning | org.eclipse.jdt.core.compiler.problem.autoboxing=warning | ||||
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore | ||||
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled | ||||
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore | |||||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error | |||||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | ||||
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error | ||||
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore | ||||
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning | ||||
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning | |||||
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error | ||||
org.eclipse.jdt.core.compiler.problem.nullReference=error | org.eclipse.jdt.core.compiler.problem.nullReference=error | ||||
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error | ||||
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning | |||||
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore | |||||
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning | ||||
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore | ||||
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning | |||||
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error | ||||
org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning | |||||
org.eclipse.jdt.core.compiler.problem.potentialNullReference=error | |||||
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore | ||||
org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore | org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore | ||||
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning | ||||
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled | ||||
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled | ||||
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore | ||||
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning | |||||
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning | ||||
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled | ||||
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning | ||||
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning | ||||
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning | ||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore | ||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning | |||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning | |||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled | |||||
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info | |||||
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore | ||||
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error | ||||
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore | ||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error | |||||
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning | |||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning | |||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled | ||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled | ||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled |
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.PrintStream; | |||||
import java.time.Instant; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
if (!tmp.delete() || !tmp.mkdir()) | if (!tmp.delete() || !tmp.mkdir()) | ||||
throw new IOException("Cannot create " + tmp); | throw new IOException("Cannot create " + tmp); | ||||
// measure timer resolution before the test to avoid time critical tests | |||||
// are affected by time needed for measurement | |||||
FS.getFileStoreAttributes(tmp.toPath().getParent()); | |||||
mockSystemReader = new MockSystemReader(); | mockSystemReader = new MockSystemReader(); | ||||
mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, | mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, | ||||
"usergitconfig"), FS.DETECTED); | "usergitconfig"), FS.DETECTED); | ||||
private static boolean recursiveDelete(final File dir, | private static boolean recursiveDelete(final File dir, | ||||
boolean silent, boolean failOnError) { | boolean silent, boolean failOnError) { | ||||
assert !(silent && failOnError); | assert !(silent && failOnError); | ||||
if (!dir.exists()) | |||||
return silent; | |||||
final File[] ls = dir.listFiles(); | |||||
if (ls != null) { | |||||
for (File f : ls) { | |||||
if (f.isDirectory()) { | |||||
silent = recursiveDelete(f, silent, failOnError); | |||||
} else if (!f.delete()) { | |||||
if (!silent) { | |||||
reportDeleteFailure(failOnError, f); | |||||
} | |||||
silent = !failOnError; | |||||
} | |||||
} | |||||
int options = FileUtils.RECURSIVE | FileUtils.RETRY | |||||
| FileUtils.SKIP_MISSING; | |||||
if (silent) { | |||||
options |= FileUtils.IGNORE_ERRORS; | |||||
} | } | ||||
if (!dir.delete()) { | |||||
if (!silent) | |||||
reportDeleteFailure(failOnError, dir); | |||||
silent = !failOnError; | |||||
try { | |||||
FileUtils.delete(dir, options); | |||||
} catch (IOException e) { | |||||
reportDeleteFailure(failOnError, dir, e); | |||||
return !failOnError; | |||||
} | } | ||||
return silent; | |||||
return true; | |||||
} | } | ||||
private static void reportDeleteFailure(boolean failOnError, File e) { | |||||
private static void reportDeleteFailure(boolean failOnError, File f, | |||||
Exception cause) { | |||||
String severity = failOnError ? "ERROR" : "WARNING"; | String severity = failOnError ? "ERROR" : "WARNING"; | ||||
String msg = severity + ": Failed to delete " + e; | |||||
if (failOnError) | |||||
String msg = severity + ": Failed to delete " + f; | |||||
if (failOnError) { | |||||
fail(msg); | fail(msg); | ||||
else | |||||
} else { | |||||
System.err.println(msg); | System.err.println(msg); | ||||
} | |||||
cause.printStackTrace(new PrintStream(System.err)); | |||||
} | } | ||||
/** Constant <code>MOD_TIME=1</code> */ | /** Constant <code>MOD_TIME=1</code> */ | ||||
throws IllegalStateException, IOException { | throws IllegalStateException, IOException { | ||||
DirCache dc = repo.readDirCache(); | DirCache dc = repo.readDirCache(); | ||||
StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
TreeSet<Long> timeStamps = new TreeSet<>(); | |||||
TreeSet<Instant> timeStamps = new TreeSet<>(); | |||||
// iterate once over the dircache just to collect all time stamps | // iterate once over the dircache just to collect all time stamps | ||||
if (0 != (includedOptions & MOD_TIME)) { | if (0 != (includedOptions & MOD_TIME)) { | ||||
for (int i=0; i<dc.getEntryCount(); ++i) | |||||
timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); | |||||
for (int i = 0; i < dc.getEntryCount(); ++i) { | |||||
timeStamps.add(dc.getEntry(i).getLastModifiedInstant()); | |||||
} | |||||
} | } | ||||
// iterate again, now produce the result string | // iterate again, now produce the result string | ||||
sb.append(", stage:" + stage); | sb.append(", stage:" + stage); | ||||
if (0 != (includedOptions & MOD_TIME)) { | if (0 != (includedOptions & MOD_TIME)) { | ||||
sb.append(", time:t"+ | sb.append(", time:t"+ | ||||
timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); | |||||
timeStamps.headSet(entry.getLastModifiedInstant()) | |||||
.size()); | |||||
} | } | ||||
if (0 != (includedOptions & SMUDGE)) | if (0 != (includedOptions & SMUDGE)) | ||||
if (entry.isSmudged()) | if (entry.isSmudged()) |
* Number of repetitions | * Number of repetitions | ||||
*/ | */ | ||||
public abstract int n(); | public abstract int n(); | ||||
/** | |||||
* Whether to abort execution on first test failure | |||||
* | |||||
* @return {@code true} if execution should be aborted on the first failure, | |||||
* otherwise count failures and continue execution | |||||
* @since 5.1.9 | |||||
*/ | |||||
public boolean abortOnFailure() default true; | |||||
} | } |
private static Logger LOG = Logger | private static Logger LOG = Logger | ||||
.getLogger(RepeatRule.class.getName()); | .getLogger(RepeatRule.class.getName()); | ||||
/** | |||||
* Exception thrown if repeated execution of a test annotated with | |||||
* {@code @Repeat} failed. | |||||
*/ | |||||
public static class RepeatedTestException extends RuntimeException { | public static class RepeatedTestException extends RuntimeException { | ||||
private static final long serialVersionUID = 1L; | private static final long serialVersionUID = 1L; | ||||
/** | |||||
* Constructor | |||||
* | |||||
* @param message | |||||
* the error message | |||||
*/ | |||||
public RepeatedTestException(String message) { | |||||
super(message); | |||||
} | |||||
/** | |||||
* Constructor | |||||
* | |||||
* @param message | |||||
* the error message | |||||
* @param cause | |||||
* exception causing this exception | |||||
*/ | |||||
public RepeatedTestException(String message, Throwable cause) { | public RepeatedTestException(String message, Throwable cause) { | ||||
super(message, cause); | super(message, cause); | ||||
} | } | ||||
private final int repetitions; | private final int repetitions; | ||||
private boolean abortOnFailure; | |||||
private final Statement statement; | private final Statement statement; | ||||
private RepeatStatement(int repetitions, Statement statement) { | |||||
private RepeatStatement(int repetitions, boolean abortOnFailure, | |||||
Statement statement) { | |||||
this.repetitions = repetitions; | this.repetitions = repetitions; | ||||
this.abortOnFailure = abortOnFailure; | |||||
this.statement = statement; | this.statement = statement; | ||||
} | } | ||||
@Override | @Override | ||||
public void evaluate() throws Throwable { | public void evaluate() throws Throwable { | ||||
int failures = 0; | |||||
for (int i = 0; i < repetitions; i++) { | for (int i = 0; i < repetitions; i++) { | ||||
try { | try { | ||||
statement.evaluate(); | statement.evaluate(); | ||||
} catch (Throwable e) { | } catch (Throwable e) { | ||||
failures += 1; | |||||
RepeatedTestException ex = new RepeatedTestException( | RepeatedTestException ex = new RepeatedTestException( | ||||
MessageFormat.format( | MessageFormat.format( | ||||
"Repeated test failed when run for the {0}. time", | "Repeated test failed when run for the {0}. time", | ||||
Integer.valueOf(i + 1)), | Integer.valueOf(i + 1)), | ||||
e); | e); | ||||
LOG.log(Level.SEVERE, ex.getMessage(), ex); | LOG.log(Level.SEVERE, ex.getMessage(), ex); | ||||
throw ex; | |||||
if (abortOnFailure) { | |||||
throw ex; | |||||
} | |||||
} | } | ||||
} | } | ||||
if (failures > 0) { | |||||
RepeatedTestException e = new RepeatedTestException( | |||||
MessageFormat.format( | |||||
"Test failed {0} times out of {1} repeated executions", | |||||
Integer.valueOf(failures), | |||||
Integer.valueOf(repetitions))); | |||||
LOG.log(Level.SEVERE, e.getMessage(), e); | |||||
throw e; | |||||
} | |||||
} | } | ||||
} | } | ||||
Repeat repeat = description.getAnnotation(Repeat.class); | Repeat repeat = description.getAnnotation(Repeat.class); | ||||
if (repeat != null) { | if (repeat != null) { | ||||
int n = repeat.n(); | int n = repeat.n(); | ||||
result = new RepeatStatement(n, statement); | |||||
boolean abortOnFailure = repeat.abortOnFailure(); | |||||
result = new RepeatStatement(n, abortOnFailure, statement); | |||||
} | } | ||||
return result; | return result; | ||||
} | } |
import java.io.InputStreamReader; | import java.io.InputStreamReader; | ||||
import java.io.Reader; | import java.io.Reader; | ||||
import java.nio.file.Path; | import java.nio.file.Path; | ||||
import java.time.Instant; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.concurrent.TimeUnit; | |||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.api.errors.GitAPIException; | import org.eclipse.jgit.api.errors.GitAPIException; | ||||
dce = new DirCacheEntry(treeItr.getEntryPathString()); | dce = new DirCacheEntry(treeItr.getEntryPathString()); | ||||
dce.setFileMode(treeItr.getEntryFileMode()); | dce.setFileMode(treeItr.getEntryFileMode()); | ||||
dce.setLastModified(treeItr.getEntryLastModified()); | |||||
dce.setLastModified(treeItr.getEntryLastModifiedInstant()); | |||||
dce.setLength((int) len); | dce.setLength((int) len); | ||||
try (FileInputStream in = new FileInputStream( | try (FileInputStream in = new FileInputStream( | ||||
treeItr.getEntryFile())) { | treeItr.getEntryFile())) { | ||||
* @throws InterruptedException | * @throws InterruptedException | ||||
* @throws IOException | * @throws IOException | ||||
*/ | */ | ||||
public static long fsTick(File lastFile) throws InterruptedException, | |||||
public static Instant fsTick(File lastFile) | |||||
throws InterruptedException, | |||||
IOException { | IOException { | ||||
File tmp; | File tmp; | ||||
FS fs = FS.DETECTED; | FS fs = FS.DETECTED; | ||||
tmp = File.createTempFile("fsTickTmpFile", null, | tmp = File.createTempFile("fsTickTmpFile", null, | ||||
lastFile.getParentFile()); | lastFile.getParentFile()); | ||||
} | } | ||||
long res = FS.getFsTimerResolution(tmp.toPath()).toMillis(); | |||||
long res = FS.getFileStoreAttributes(tmp.toPath()) | |||||
.getFsTimestampResolution().toNanos(); | |||||
long sleepTime = res / 10; | long sleepTime = res / 10; | ||||
try { | try { | ||||
long startTime = fs.lastModified(lastFile); | |||||
long actTime = fs.lastModified(tmp); | |||||
while (actTime <= startTime) { | |||||
Thread.sleep(sleepTime); | |||||
Instant startTime = fs.lastModifiedInstant(lastFile); | |||||
Instant actTime = fs.lastModifiedInstant(tmp); | |||||
while (actTime.compareTo(startTime) <= 0) { | |||||
TimeUnit.NANOSECONDS.sleep(sleepTime); | |||||
FileUtils.touch(tmp.toPath()); | FileUtils.touch(tmp.toPath()); | ||||
actTime = fs.lastModified(tmp); | |||||
actTime = fs.lastModifiedInstant(tmp); | |||||
} | } | ||||
return actTime; | return actTime; | ||||
} finally { | } finally { |
ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true); | ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true); | ||||
merger.setBase(parent.getTree()); | merger.setBase(parent.getTree()); | ||||
if (merger.merge(head, commit)) { | if (merger.merge(head, commit)) { | ||||
if (AnyObjectId.equals(head.getTree(), merger.getResultTreeId())) | |||||
if (AnyObjectId.isEqual(head.getTree(), merger.getResultTreeId())) | |||||
return null; | return null; | ||||
tick(1); | tick(1); | ||||
org.eclipse.jgit.lib.CommitBuilder b = | org.eclipse.jgit.lib.CommitBuilder b = | ||||
parents.add(prior.create()); | parents.add(prior.create()); | ||||
} | } | ||||
/** | |||||
* set parent commit | |||||
* | |||||
* @param p | |||||
* parent commit | |||||
* @return this commit builder | |||||
* @throws Exception | |||||
*/ | |||||
public CommitBuilder parent(RevCommit p) throws Exception { | public CommitBuilder parent(RevCommit p) throws Exception { | ||||
if (parents.isEmpty()) { | if (parents.isEmpty()) { | ||||
DirCacheBuilder b = tree.builder(); | DirCacheBuilder b = tree.builder(); | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Get parent commits | |||||
* | |||||
* @return parent commits | |||||
*/ | |||||
public List<RevCommit> parents() { | public List<RevCommit> parents() { | ||||
return Collections.unmodifiableList(parents); | return Collections.unmodifiableList(parents); | ||||
} | } | ||||
/** | |||||
* Remove parent commits | |||||
* | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder noParents() { | public CommitBuilder noParents() { | ||||
parents.clear(); | parents.clear(); | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Remove files | |||||
* | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder noFiles() { | public CommitBuilder noFiles() { | ||||
tree.clear(); | tree.clear(); | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Set top level tree | |||||
* | |||||
* @param treeId | |||||
* the top level tree | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder setTopLevelTree(ObjectId treeId) { | public CommitBuilder setTopLevelTree(ObjectId treeId) { | ||||
topLevelTree = treeId; | topLevelTree = treeId; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Add file with given content | |||||
* | |||||
* @param path | |||||
* path of the file | |||||
* @param content | |||||
* the file content | |||||
* @return this commit builder | |||||
* @throws Exception | |||||
*/ | |||||
public CommitBuilder add(String path, String content) throws Exception { | public CommitBuilder add(String path, String content) throws Exception { | ||||
return add(path, blob(content)); | return add(path, blob(content)); | ||||
} | } | ||||
/** | |||||
* Add file with given path and blob | |||||
* | |||||
* @param path | |||||
* path of the file | |||||
* @param id | |||||
* blob for this file | |||||
* @return this commit builder | |||||
* @throws Exception | |||||
*/ | |||||
public CommitBuilder add(String path, RevBlob id) | public CommitBuilder add(String path, RevBlob id) | ||||
throws Exception { | throws Exception { | ||||
return edit(new PathEdit(path) { | return edit(new PathEdit(path) { | ||||
}); | }); | ||||
} | } | ||||
/** | |||||
* Edit the index | |||||
* | |||||
* @param edit | |||||
* the index record update | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder edit(PathEdit edit) { | public CommitBuilder edit(PathEdit edit) { | ||||
DirCacheEditor e = tree.editor(); | DirCacheEditor e = tree.editor(); | ||||
e.add(edit); | e.add(edit); | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Remove a file | |||||
* | |||||
* @param path | |||||
* path of the file | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder rm(String path) { | public CommitBuilder rm(String path) { | ||||
DirCacheEditor e = tree.editor(); | DirCacheEditor e = tree.editor(); | ||||
e.add(new DeletePath(path)); | e.add(new DeletePath(path)); | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Set commit message | |||||
* | |||||
* @param m | |||||
* the message | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder message(String m) { | public CommitBuilder message(String m) { | ||||
message = m; | message = m; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Get the commit message | |||||
* | |||||
* @return the commit message | |||||
*/ | |||||
public String message() { | public String message() { | ||||
return message; | return message; | ||||
} | } | ||||
/** | |||||
* Tick the clock | |||||
* | |||||
* @param secs | |||||
* number of seconds | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder tick(int secs) { | public CommitBuilder tick(int secs) { | ||||
tick = secs; | tick = secs; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Set author and committer identity | |||||
* | |||||
* @param ident | |||||
* identity to set | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder ident(PersonIdent ident) { | public CommitBuilder ident(PersonIdent ident) { | ||||
author = ident; | author = ident; | ||||
committer = ident; | committer = ident; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Set the author identity | |||||
* | |||||
* @param a | |||||
* the author's identity | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder author(PersonIdent a) { | public CommitBuilder author(PersonIdent a) { | ||||
author = a; | author = a; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Get the author identity | |||||
* | |||||
* @return the author identity | |||||
*/ | |||||
public PersonIdent author() { | public PersonIdent author() { | ||||
return author; | return author; | ||||
} | } | ||||
/** | |||||
* Set the committer identity | |||||
* | |||||
* @param c | |||||
* the committer identity | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder committer(PersonIdent c) { | public CommitBuilder committer(PersonIdent c) { | ||||
committer = c; | committer = c; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Get the committer identity | |||||
* | |||||
* @return the committer identity | |||||
*/ | |||||
public PersonIdent committer() { | public PersonIdent committer() { | ||||
return committer; | return committer; | ||||
} | } | ||||
/** | |||||
* Insert changeId | |||||
* | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder insertChangeId() { | public CommitBuilder insertChangeId() { | ||||
changeId = ""; | changeId = ""; | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Insert given changeId | |||||
* | |||||
* @param c | |||||
* changeId | |||||
* @return this commit builder | |||||
*/ | |||||
public CommitBuilder insertChangeId(String c) { | public CommitBuilder insertChangeId(String c) { | ||||
// Validate, but store as a string so we can use "" as a sentinel. | // Validate, but store as a string so we can use "" as a sentinel. | ||||
ObjectId.fromString(c); | ObjectId.fromString(c); | ||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Create the commit | |||||
* | |||||
* @return the new commit | |||||
* @throws Exception | |||||
* if creation failed | |||||
*/ | |||||
public RevCommit create() throws Exception { | public RevCommit create() throws Exception { | ||||
if (self == null) { | if (self == null) { | ||||
TestRepository.this.tick(tick); | TestRepository.this.tick(tick); | ||||
+ cid.getName() + "\n"); //$NON-NLS-1$ | + cid.getName() + "\n"); //$NON-NLS-1$ | ||||
} | } | ||||
/** | |||||
* Create child commit builder | |||||
* | |||||
* @return child commit builder | |||||
* @throws Exception | |||||
*/ | |||||
public CommitBuilder child() throws Exception { | public CommitBuilder child() throws Exception { | ||||
return new CommitBuilder(this); | return new CommitBuilder(this); | ||||
} | } |
/* | |||||
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> | |||||
* and other copyright owners as documented in the project's IP log. | |||||
* | |||||
* This program and the accompanying materials are made available | |||||
* under the terms of the Eclipse Distribution License v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
package org.eclipse.jgit.junit.time; | |||||
import java.io.IOException; | |||||
import java.io.UncheckedIOException; | |||||
import java.nio.file.Files; | |||||
import java.nio.file.Path; | |||||
import java.nio.file.attribute.FileTime; | |||||
import java.time.Instant; | |||||
import org.eclipse.jgit.util.FS; | |||||
/** | |||||
* Utility methods for handling timestamps | |||||
*/ | |||||
public class TimeUtil { | |||||
/** | |||||
* Set the lastModified time of a given file by adding a given offset to the | |||||
* current lastModified time | |||||
* | |||||
* @param path | |||||
* path of a file to set last modified | |||||
* @param offsetMillis | |||||
* offset in milliseconds, if negative the new lastModified time | |||||
* is offset before the original lastModified time, otherwise | |||||
* after the original time | |||||
* @return the new lastModified time | |||||
*/ | |||||
public static Instant setLastModifiedWithOffset(Path path, | |||||
long offsetMillis) { | |||||
Instant mTime = FS.DETECTED.lastModifiedInstant(path) | |||||
.plusMillis(offsetMillis); | |||||
try { | |||||
Files.setLastModifiedTime(path, FileTime.from(mTime)); | |||||
return mTime; | |||||
} catch (IOException e) { | |||||
throw new UncheckedIOException(e); | |||||
} | |||||
} | |||||
/** | |||||
* Set the lastModified time of file a to the one from file b | |||||
* | |||||
* @param a | |||||
* file to set lastModified time | |||||
* @param b | |||||
* file to read lastModified time from | |||||
*/ | |||||
public static void setLastModifiedOf(Path a, Path b) { | |||||
Instant mTime = FS.DETECTED.lastModifiedInstant(b); | |||||
try { | |||||
Files.setLastModifiedTime(a, FileTime.from(mTime)); | |||||
} catch (IOException e) { | |||||
throw new UncheckedIOException(e); | |||||
} | |||||
} | |||||
} |
import org.eclipse.jgit.lfs.server.LargeFileRepository; | import org.eclipse.jgit.lfs.server.LargeFileRepository; | ||||
import org.eclipse.jgit.lfs.server.LfsProtocolServlet; | import org.eclipse.jgit.lfs.server.LfsProtocolServlet; | ||||
import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; | import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.FileUtils; | import org.eclipse.jgit.util.FileUtils; | ||||
import org.eclipse.jgit.util.IO; | import org.eclipse.jgit.util.IO; | ||||
import org.junit.After; | import org.junit.After; | ||||
@Before | @Before | ||||
public void setup() throws Exception { | public void setup() throws Exception { | ||||
tmp = Files.createTempDirectory("jgit_test_"); | tmp = Files.createTempDirectory("jgit_test_"); | ||||
// measure timer resolution before the test to avoid time critical tests | |||||
// are affected by time needed for measurement | |||||
FS.getFileStoreAttributes(tmp.getParent()); | |||||
server = new AppServer(); | server = new AppServer(); | ||||
ServletContextHandler app = server.addContext("/lfs"); | ServletContextHandler app = server.addContext("/lfs"); | ||||
dir = Paths.get(tmp.toString(), "lfs"); | dir = Paths.get(tmp.toString(), "lfs"); |
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<component id="org.eclipse.jgit.lfs" version="2"> | |||||
<resource path="src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java" type="org.eclipse.jgit.lfs.lib.AnyLongObjectId"> | |||||
<filter id="1141899266"> | |||||
<message_arguments> | |||||
<message_argument value="5.4"/> | |||||
<message_argument value="5.5"/> | |||||
<message_argument value="isEqual(AnyLongObjectId, AnyLongObjectId)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
</component> |
import org.eclipse.jgit.lib.AnyObjectId; | import org.eclipse.jgit.lib.AnyObjectId; | ||||
import org.eclipse.jgit.util.NB; | import org.eclipse.jgit.util.NB; | ||||
import org.eclipse.jgit.util.References; | |||||
/** | /** | ||||
* A (possibly mutable) SHA-256 abstraction. | * A (possibly mutable) SHA-256 abstraction. | ||||
* @param secondObjectId | * @param secondObjectId | ||||
* the second identifier to compare. Must not be null. | * the second identifier to compare. Must not be null. | ||||
* @return true if the two identifiers are the same. | * @return true if the two identifiers are the same. | ||||
* @deprecated use {@link #isEqual(AnyLongObjectId, AnyLongObjectId)} | |||||
* instead. | |||||
*/ | */ | ||||
@Deprecated | |||||
@SuppressWarnings("AmbiguousMethodReference") | |||||
public static boolean equals(final AnyLongObjectId firstObjectId, | public static boolean equals(final AnyLongObjectId firstObjectId, | ||||
final AnyLongObjectId secondObjectId) { | final AnyLongObjectId secondObjectId) { | ||||
if (firstObjectId == secondObjectId) | |||||
return isEqual(firstObjectId, secondObjectId); | |||||
} | |||||
/** | |||||
* Compare two object identifier byte sequences for equality. | |||||
* | |||||
* @param firstObjectId | |||||
* the first identifier to compare. Must not be null. | |||||
* @param secondObjectId | |||||
* the second identifier to compare. Must not be null. | |||||
* @return true if the two identifiers are the same. | |||||
* @since 5.4 | |||||
*/ | |||||
public static boolean isEqual(final AnyLongObjectId firstObjectId, | |||||
final AnyLongObjectId secondObjectId) { | |||||
if (References.isSameObject(firstObjectId, secondObjectId)) { | |||||
return true; | return true; | ||||
} | |||||
// We test word 2 first as odds are someone already used our | // We test word 2 first as odds are someone already used our | ||||
// word 1 as a hash code, and applying that came up with these | // word 1 as a hash code, and applying that came up with these | ||||
* the other id to compare to. May be null. | * the other id to compare to. May be null. | ||||
* @return true only if both LongObjectIds have identical bits. | * @return true only if both LongObjectIds have identical bits. | ||||
*/ | */ | ||||
@SuppressWarnings({ "NonOverridingEquals", "AmbiguousMethodReference" }) | |||||
public final boolean equals(AnyLongObjectId other) { | public final boolean equals(AnyLongObjectId other) { | ||||
return other != null ? equals(this, other) : false; | return other != null ? equals(this, other) : false; | ||||
} | } |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||||
<modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
<prerequisites> | |||||
<maven>3.0</maven> | |||||
</prerequisites> | |||||
<groupId>org.eclipse.jgit</groupId> | <groupId>org.eclipse.jgit</groupId> | ||||
<artifactId>jgit.tycho.parent</artifactId> | <artifactId>jgit.tycho.parent</artifactId> | ||||
<version>5.5.0-SNAPSHOT</version> | <version>5.5.0-SNAPSHOT</version> | ||||
<artifactId>tycho-p2-plugin</artifactId> | <artifactId>tycho-p2-plugin</artifactId> | ||||
<version>${tycho-version}</version> | <version>${tycho-version}</version> | ||||
</plugin> | </plugin> | ||||
<plugin> | |||||
<groupId>org.eclipse.tycho</groupId> | |||||
<artifactId>tycho-p2-publisher-plugin</artifactId> | |||||
<version>${tycho-version}</version> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.eclipse.tycho</groupId> | |||||
<artifactId>tycho-p2-repository-plugin</artifactId> | |||||
<version>${tycho-version}</version> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.eclipse.tycho</groupId> | |||||
<artifactId>tycho-packaging-plugin</artifactId> | |||||
<version>${tycho-version}</version> | |||||
</plugin> | |||||
<plugin> | <plugin> | ||||
<groupId>org.eclipse.tycho.extras</groupId> | <groupId>org.eclipse.tycho.extras</groupId> | ||||
<artifactId>tycho-pack200a-plugin</artifactId> | <artifactId>tycho-pack200a-plugin</artifactId> | ||||
<artifactId>build-helper-maven-plugin</artifactId> | <artifactId>build-helper-maven-plugin</artifactId> | ||||
<version>3.0.0</version> | <version>3.0.0</version> | ||||
</plugin> | </plugin> | ||||
<plugin> | |||||
<artifactId>maven-clean-plugin</artifactId> | |||||
<version>3.1.0</version> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-deploy-plugin</artifactId> | |||||
<version>3.0.0-M1</version> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-install-plugin</artifactId> | |||||
<version>3.0.0-M1</version> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-site-plugin</artifactId> | |||||
<version>3.7.1</version> | |||||
</plugin> | |||||
</plugins> | </plugins> | ||||
</pluginManagement> | </pluginManagement> | ||||
</build> | </build> |
<artifactId>org.eclipse.jgit.lfs.server</artifactId> | <artifactId>org.eclipse.jgit.lfs.server</artifactId> | ||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.tukaani</groupId> | |||||
<artifactId>xz</artifactId> | |||||
<optional>true</optional> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
<build> | <build> |
dateWidth = Math.max(dateWidth, date(line).length()); | dateWidth = Math.max(dateWidth, date(line).length()); | ||||
pathWidth = Math.max(pathWidth, path(line).length()); | pathWidth = Math.max(pathWidth, path(line).length()); | ||||
} | } | ||||
while (line + 1 < end && blame.getSourceCommit(line + 1) == c) { | |||||
while (line + 1 < end | |||||
&& sameCommit(blame.getSourceCommit(line + 1), c)) { | |||||
line++; | line++; | ||||
} | } | ||||
maxSourceLine = Math.max(maxSourceLine, blame.getSourceLine(line)); | maxSourceLine = Math.max(maxSourceLine, blame.getSourceLine(line)); | ||||
blame.getResultContents().writeLine(outs, line); | blame.getResultContents().writeLine(outs, line); | ||||
outs.flush(); | outs.flush(); | ||||
outw.print('\n'); | outw.print('\n'); | ||||
} while (++line < end && blame.getSourceCommit(line) == c); | |||||
} while (++line < end | |||||
&& sameCommit(blame.getSourceCommit(line), c)); | |||||
} | } | ||||
} catch (NoWorkTreeException | IOException e) { | } catch (NoWorkTreeException | IOException e) { | ||||
throw die(e.getMessage(), e); | throw die(e.getMessage(), e); | ||||
} | } | ||||
} | } | ||||
@SuppressWarnings("ReferenceEquality") | |||||
private static boolean sameCommit(RevCommit a, RevCommit b) { | |||||
// Reference comparison is intentional; BlameGenerator uses a single | |||||
// RevWalk which caches the RevCommit objects, and if a given commit | |||||
// is cached the RevWalk returns the same instance. | |||||
return a == b; | |||||
} | |||||
private int uniqueAbbrevLen(ObjectReader reader, RevCommit commit) | private int uniqueAbbrevLen(ObjectReader reader, RevCommit commit) | ||||
throws IOException { | throws IOException { | ||||
return reader.abbreviate(commit, abbrev).length(); | return reader.abbreviate(commit, abbrev).length(); |
package org.eclipse.jgit.pgm; | package org.eclipse.jgit.pgm; | ||||
import static java.nio.charset.StandardCharsets.UTF_8; | import static java.nio.charset.StandardCharsets.UTF_8; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SECTION_I18N; | |||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOG_OUTPUT_ENCODING; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOG_OUTPUT_ENCODING; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SECTION_I18N; | |||||
import static org.eclipse.jgit.lib.Constants.R_HEADS; | import static org.eclipse.jgit.lib.Constants.R_HEADS; | ||||
import static org.eclipse.jgit.lib.Constants.R_REMOTES; | import static org.eclipse.jgit.lib.Constants.R_REMOTES; | ||||
import static org.eclipse.jgit.lib.Constants.R_TAGS; | import static org.eclipse.jgit.lib.Constants.R_TAGS; |
import static java.lang.Integer.valueOf; | import static java.lang.Integer.valueOf; | ||||
import java.text.SimpleDateFormat; | |||||
import java.util.Date; | |||||
import java.time.Instant; | |||||
import java.time.ZoneId; | |||||
import java.time.format.DateTimeFormatter; | |||||
import java.util.Locale; | |||||
import org.eclipse.jgit.dircache.DirCache; | import org.eclipse.jgit.dircache.DirCache; | ||||
import org.eclipse.jgit.dircache.DirCacheEntry; | import org.eclipse.jgit.dircache.DirCacheEntry; | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
protected void run() throws Exception { | protected void run() throws Exception { | ||||
final SimpleDateFormat fmt; | |||||
fmt = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss.SSS"); //$NON-NLS-1$ | |||||
final DateTimeFormatter fmt = DateTimeFormatter | |||||
.ofPattern("yyyy-MM-dd,HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$ | |||||
.withLocale(Locale.getDefault()) | |||||
.withZone(ZoneId.systemDefault()); | |||||
final DirCache cache = db.readDirCache(); | final DirCache cache = db.readDirCache(); | ||||
for (int i = 0; i < cache.getEntryCount(); i++) { | for (int i = 0; i < cache.getEntryCount(); i++) { | ||||
final DirCacheEntry ent = cache.getEntry(i); | final DirCacheEntry ent = cache.getEntry(i); | ||||
final FileMode mode = FileMode.fromBits(ent.getRawMode()); | final FileMode mode = FileMode.fromBits(ent.getRawMode()); | ||||
final int len = ent.getLength(); | final int len = ent.getLength(); | ||||
long lastModified = ent.getLastModified(); | |||||
final Date mtime = new Date(lastModified); | |||||
Instant mtime = ent.getLastModifiedInstant(); | |||||
final int stage = ent.getStage(); | final int stage = ent.getStage(); | ||||
outw.print(mode); | outw.print(mode); | ||||
outw.format(" %6d", valueOf(len)); //$NON-NLS-1$ | outw.format(" %6d", valueOf(len)); //$NON-NLS-1$ | ||||
outw.print(' '); | outw.print(' '); | ||||
if (millis) | |||||
outw.print(lastModified); | |||||
else | |||||
if (millis) { | |||||
outw.print(mtime.toEpochMilli()); | |||||
} else { | |||||
outw.print(fmt.format(mtime)); | outw.print(fmt.format(mtime)); | ||||
} | |||||
outw.print(' '); | outw.print(' '); | ||||
outw.print(ent.getObjectId().name()); | outw.print(ent.getObjectId().name()); | ||||
outw.print(' '); | outw.print(' '); |
return; | return; | ||||
} | } | ||||
if (!AnyObjectId.equals(exp.getObjectId(), act.getObjectId())) { | |||||
if (!AnyObjectId.isEqual(exp.getObjectId(), act.getObjectId())) { | |||||
throw die(String.format("expected %s to be %s, found %s", | throw die(String.format("expected %s to be %s, found %s", | ||||
exp.getName(), | exp.getName(), | ||||
id(exp.getObjectId()), | id(exp.getObjectId()), | ||||
} | } | ||||
if (exp.getPeeledObjectId() != null | if (exp.getPeeledObjectId() != null | ||||
&& !AnyObjectId.equals(exp.getPeeledObjectId(), act.getPeeledObjectId())) { | |||||
&& !AnyObjectId.isEqual(exp.getPeeledObjectId(), | |||||
act.getPeeledObjectId())) { | |||||
throw die(String.format("expected %s to be %s, found %s", | throw die(String.format("expected %s to be %s, found %s", | ||||
exp.getName(), | exp.getName(), | ||||
id(exp.getPeeledObjectId()), | id(exp.getPeeledObjectId()), |
"revwalk/RevWalkTestCase.java", | "revwalk/RevWalkTestCase.java", | ||||
"transport/ObjectIdMatcher.java", | "transport/ObjectIdMatcher.java", | ||||
"transport/SpiTransport.java", | "transport/SpiTransport.java", | ||||
"treewalk/FileTreeIteratorWithTimeControl.java", | |||||
"treewalk/filter/AlwaysCloneTreeFilter.java", | "treewalk/filter/AlwaysCloneTreeFilter.java", | ||||
"test/resources/SampleDataRepositoryTestCase.java", | "test/resources/SampleDataRepositoryTestCase.java", | ||||
"util/CPUTimeStopWatch.java", | "util/CPUTimeStopWatch.java", |
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | ||||
com.jcraft.jsch;version="[0.1.54,0.2.0)", | com.jcraft.jsch;version="[0.1.54,0.2.0)", | ||||
net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)", | net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)", | ||||
org.apache.commons.compress.archivers;version="[1.15.0,2.0)", | |||||
org.apache.commons.compress.archivers.tar;version="[1.15.0,2.0)", | |||||
org.apache.commons.compress.archivers.zip;version="[1.15.0,2.0)", | |||||
org.apache.commons.compress.compressors.bzip2;version="[1.15.0,2.0)", | |||||
org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)", | |||||
org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)", | |||||
org.bouncycastle.util.encoders;version="[1.61.0,2.0.0)", | org.bouncycastle.util.encoders;version="[1.61.0,2.0.0)", | ||||
org.eclipse.jgit.annotations;version="[5.5.0,5.6.0)", | org.eclipse.jgit.annotations;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.api;version="[5.5.0,5.6.0)", | org.eclipse.jgit.api;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.api.errors;version="[5.5.0,5.6.0)", | org.eclipse.jgit.api.errors;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.archive;version="[5.5.0,5.6.0)", | |||||
org.eclipse.jgit.attributes;version="[5.5.0,5.6.0)", | org.eclipse.jgit.attributes;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.awtui;version="[5.5.0,5.6.0)", | org.eclipse.jgit.awtui;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.blame;version="[5.5.0,5.6.0)", | org.eclipse.jgit.blame;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.internal.transport.parser;version="[5.5.0,5.6.0)", | org.eclipse.jgit.internal.transport.parser;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.junit;version="[5.5.0,5.6.0)", | org.eclipse.jgit.junit;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.junit.ssh;version="[5.5.0,5.6.0)", | org.eclipse.jgit.junit.ssh;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.junit.time;version="[5.5.0,5.6.0)", | |||||
org.eclipse.jgit.lfs;version="[5.5.0,5.6.0)", | org.eclipse.jgit.lfs;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.lib;version="[5.5.0,5.6.0)", | org.eclipse.jgit.lib;version="[5.5.0,5.6.0)", | ||||
org.eclipse.jgit.merge;version="[5.5.0,5.6.0)", | org.eclipse.jgit.merge;version="[5.5.0,5.6.0)", | ||||
org.mockito.junit;version="[2.13.0,3.0.0)", | org.mockito.junit;version="[2.13.0,3.0.0)", | ||||
org.mockito.stubbing;version="[2.13.0,3.0.0)", | org.mockito.stubbing;version="[2.13.0,3.0.0)", | ||||
org.objenesis;version="[2.6.0,3.0.0)", | org.objenesis;version="[2.6.0,3.0.0)", | ||||
org.slf4j;version="[1.7.0,2.0.0)" | |||||
org.slf4j;version="[1.7.0,2.0.0)", | |||||
org.tukaani.xz;version="[1.6.0,2.0)" | |||||
Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", | Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", | ||||
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" | org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" | ||||
Export-Package: org.eclipse.jgit.transport.ssh;version="5.5.0";x-friends:="org.eclipse.jgit.ssh.apache.test" | Export-Package: org.eclipse.jgit.transport.ssh;version="5.5.0";x-friends:="org.eclipse.jgit.ssh.apache.test" |
<artifactId>org.eclipse.jgit.pgm</artifactId> | <artifactId>org.eclipse.jgit.pgm</artifactId> | ||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.tukaani</groupId> | |||||
<artifactId>xz</artifactId> | |||||
<optional>true</optional> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
<profiles> | <profiles> | ||||
<plugin> | <plugin> | ||||
<groupId>org.apache.maven.plugins</groupId> | <groupId>org.apache.maven.plugins</groupId> | ||||
<artifactId>maven-surefire-plugin</artifactId> | <artifactId>maven-surefire-plugin</artifactId> | ||||
<version>${maven-surefire-plugin-version}</version> | |||||
<configuration> | <configuration> | ||||
<argLine>@{argLine} -Djgit.test.long=true</argLine> | <argLine>@{argLine} -Djgit.test.long=true</argLine> | ||||
</configuration> | </configuration> |
additional_deps = [ | additional_deps = [ | ||||
"//lib:mockito", | "//lib:mockito", | ||||
] | ] | ||||
if src.endswith("ArchiveCommandTest.java"): | |||||
additional_deps = [ | |||||
"//lib:commons-compress", | |||||
"//lib:xz", | |||||
"//org.eclipse.jgit.archive:jgit-archive", | |||||
] | |||||
heap_size = "-Xmx256m" | heap_size = "-Xmx256m" | ||||
if src.endswith("HugeCommitMessageTest.java"): | if src.endswith("HugeCommitMessageTest.java"): | ||||
heap_size = "-Xmx512m" | heap_size = "-Xmx512m" |
log4j.appender.stdout.Target=System.out | log4j.appender.stdout.Target=System.out | ||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout | ||||
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n | ||||
#log4j.appender.fileLogger.bufferedIO = true | |||||
#log4j.appender.fileLogger.bufferSize = 4096 | |||||
#log4j.logger.org.eclipse.jgit.util.FS = DEBUG | |||||
#log4j.logger.org.eclipse.jgit.internal.storage.file.FileSnapshot = DEBUG |
DirCacheEntry entry = new DirCacheEntry(path, stage); | DirCacheEntry entry = new DirCacheEntry(path, stage); | ||||
entry.setObjectId(id); | entry.setObjectId(id); | ||||
entry.setFileMode(FileMode.REGULAR_FILE); | entry.setFileMode(FileMode.REGULAR_FILE); | ||||
entry.setLastModified(file.lastModified()); | |||||
entry.setLastModified(FS.DETECTED.lastModifiedInstant(file)); | |||||
entry.setLength((int) file.length()); | entry.setLength((int) file.length()); | ||||
builder.add(entry); | builder.add(entry); |
import static org.junit.Assert.assertNull; | import static org.junit.Assert.assertNull; | ||||
import java.beans.Statement; | import java.beans.Statement; | ||||
import java.io.BufferedInputStream; | |||||
import java.io.File; | |||||
import java.io.FileNotFoundException; | |||||
import java.io.FileOutputStream; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import java.nio.file.Files; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import org.apache.commons.compress.archivers.ArchiveEntry; | |||||
import org.apache.commons.compress.archivers.ArchiveInputStream; | |||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; | |||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; | |||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; | |||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; | |||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; | |||||
import org.eclipse.jgit.api.errors.AbortedByHookException; | |||||
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; | |||||
import org.eclipse.jgit.api.errors.GitAPIException; | import org.eclipse.jgit.api.errors.GitAPIException; | ||||
import org.eclipse.jgit.api.errors.NoFilepatternException; | |||||
import org.eclipse.jgit.api.errors.NoHeadException; | |||||
import org.eclipse.jgit.api.errors.NoMessageException; | |||||
import org.eclipse.jgit.api.errors.UnmergedPathsException; | |||||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; | |||||
import org.eclipse.jgit.archive.ArchiveFormats; | |||||
import org.eclipse.jgit.errors.AmbiguousObjectException; | |||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.lib.FileMode; | import org.eclipse.jgit.lib.FileMode; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.lib.ObjectLoader; | import org.eclipse.jgit.lib.ObjectLoader; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | import org.eclipse.jgit.revwalk.RevCommit; | ||||
import org.eclipse.jgit.util.IO; | |||||
import org.eclipse.jgit.util.StringUtils; | import org.eclipse.jgit.util.StringUtils; | ||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
public class ArchiveCommandTest extends RepositoryTestCase { | public class ArchiveCommandTest extends RepositoryTestCase { | ||||
// archives store timestamp with 1 second resolution | |||||
private static final int WAIT = 2000; | |||||
private static final String UNEXPECTED_ARCHIVE_SIZE = "Unexpected archive size"; | private static final String UNEXPECTED_ARCHIVE_SIZE = "Unexpected archive size"; | ||||
private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents"; | private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents"; | ||||
private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents"; | private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents"; | ||||
private static final String UNEXPECTED_LAST_MODIFIED = | |||||
"Unexpected lastModified mocked by MockSystemReader, truncated to 1 second"; | |||||
private static final String UNEXPECTED_DIFFERENT_HASH = "Unexpected different hash"; | |||||
private MockFormat format = null; | private MockFormat format = null; | ||||
public void setup() { | public void setup() { | ||||
format = new MockFormat(); | format = new MockFormat(); | ||||
ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format); | ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format); | ||||
ArchiveFormats.registerAll(); | |||||
} | } | ||||
@Override | @Override | ||||
@After | @After | ||||
public void tearDown() { | public void tearDown() { | ||||
ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0)); | ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0)); | ||||
ArchiveFormats.unregisterAll(); | |||||
} | } | ||||
@Test | @Test | ||||
public void archiveHeadAllFiles() throws IOException, GitAPIException { | public void archiveHeadAllFiles() throws IOException, GitAPIException { | ||||
try (Git git = new Git(db)) { | try (Git git = new Git(db)) { | ||||
writeTrashFile("file_1.txt", "content_1_1"); | |||||
git.add().addFilepattern("file_1.txt").call(); | |||||
git.commit().setMessage("create file").call(); | |||||
writeTrashFile("file_1.txt", "content_1_2"); | |||||
writeTrashFile("file_2.txt", "content_2_2"); | |||||
git.add().addFilepattern(".").call(); | |||||
git.commit().setMessage("updated file").call(); | |||||
createTestContent(git); | |||||
git.archive().setOutputStream(new MockOutputStream()) | git.archive().setOutputStream(new MockOutputStream()) | ||||
.setFormat(format.SUFFIXES.get(0)) | .setFormat(format.SUFFIXES.get(0)) | ||||
} | } | ||||
} | } | ||||
@Test | |||||
public void archiveHeadAllFilesTarTimestamps() throws Exception { | |||||
try (Git git = new Git(db)) { | |||||
createTestContent(git); | |||||
String fmt = "tar"; | |||||
File archive = new File(getTemporaryDirectory(), | |||||
"archive." + format); | |||||
archive(git, archive, fmt); | |||||
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive)); | |||||
try (InputStream fi = Files.newInputStream(archive.toPath()); | |||||
InputStream bi = new BufferedInputStream(fi); | |||||
ArchiveInputStream o = new TarArchiveInputStream(bi)) { | |||||
assertEntries(o); | |||||
} | |||||
Thread.sleep(WAIT); | |||||
archive(git, archive, fmt); | |||||
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1, | |||||
ObjectId.fromRaw(IO.readFully(archive))); | |||||
} | |||||
} | |||||
@Test | |||||
public void archiveHeadAllFilesTgzTimestamps() throws Exception { | |||||
try (Git git = new Git(db)) { | |||||
createTestContent(git); | |||||
String fmt = "tgz"; | |||||
File archive = new File(getTemporaryDirectory(), | |||||
"archive." + fmt); | |||||
archive(git, archive, fmt); | |||||
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive)); | |||||
try (InputStream fi = Files.newInputStream(archive.toPath()); | |||||
InputStream bi = new BufferedInputStream(fi); | |||||
InputStream gzi = new GzipCompressorInputStream(bi); | |||||
ArchiveInputStream o = new TarArchiveInputStream(gzi)) { | |||||
assertEntries(o); | |||||
} | |||||
Thread.sleep(WAIT); | |||||
archive(git, archive, fmt); | |||||
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1, | |||||
ObjectId.fromRaw(IO.readFully(archive))); | |||||
} | |||||
} | |||||
@Test | |||||
public void archiveHeadAllFilesTbz2Timestamps() throws Exception { | |||||
try (Git git = new Git(db)) { | |||||
createTestContent(git); | |||||
String fmt = "tbz2"; | |||||
File archive = new File(getTemporaryDirectory(), | |||||
"archive." + fmt); | |||||
archive(git, archive, fmt); | |||||
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive)); | |||||
try (InputStream fi = Files.newInputStream(archive.toPath()); | |||||
InputStream bi = new BufferedInputStream(fi); | |||||
InputStream gzi = new BZip2CompressorInputStream(bi); | |||||
ArchiveInputStream o = new TarArchiveInputStream(gzi)) { | |||||
assertEntries(o); | |||||
} | |||||
Thread.sleep(WAIT); | |||||
archive(git, archive, fmt); | |||||
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1, | |||||
ObjectId.fromRaw(IO.readFully(archive))); | |||||
} | |||||
} | |||||
@Test | |||||
public void archiveHeadAllFilesTxzTimestamps() throws Exception { | |||||
try (Git git = new Git(db)) { | |||||
createTestContent(git); | |||||
String fmt = "txz"; | |||||
File archive = new File(getTemporaryDirectory(), "archive." + fmt); | |||||
archive(git, archive, fmt); | |||||
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive)); | |||||
try (InputStream fi = Files.newInputStream(archive.toPath()); | |||||
InputStream bi = new BufferedInputStream(fi); | |||||
InputStream gzi = new XZCompressorInputStream(bi); | |||||
ArchiveInputStream o = new TarArchiveInputStream(gzi)) { | |||||
assertEntries(o); | |||||
} | |||||
Thread.sleep(WAIT); | |||||
archive(git, archive, fmt); | |||||
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1, | |||||
ObjectId.fromRaw(IO.readFully(archive))); | |||||
} | |||||
} | |||||
@Test | |||||
public void archiveHeadAllFilesZipTimestamps() throws Exception { | |||||
try (Git git = new Git(db)) { | |||||
createTestContent(git); | |||||
String fmt = "zip"; | |||||
File archive = new File(getTemporaryDirectory(), "archive." + fmt); | |||||
archive(git, archive, fmt); | |||||
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive)); | |||||
try (InputStream fi = Files.newInputStream(archive.toPath()); | |||||
InputStream bi = new BufferedInputStream(fi); | |||||
ArchiveInputStream o = new ZipArchiveInputStream(bi)) { | |||||
assertEntries(o); | |||||
} | |||||
Thread.sleep(WAIT); | |||||
archive(git, archive, fmt); | |||||
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1, | |||||
ObjectId.fromRaw(IO.readFully(archive))); | |||||
} | |||||
} | |||||
private void createTestContent(Git git) throws IOException, GitAPIException, | |||||
NoFilepatternException, NoHeadException, NoMessageException, | |||||
UnmergedPathsException, ConcurrentRefUpdateException, | |||||
WrongRepositoryStateException, AbortedByHookException { | |||||
writeTrashFile("file_1.txt", "content_1_1"); | |||||
git.add().addFilepattern("file_1.txt").call(); | |||||
git.commit().setMessage("create file").call(); | |||||
writeTrashFile("file_1.txt", "content_1_2"); | |||||
writeTrashFile("file_2.txt", "content_2_2"); | |||||
git.add().addFilepattern(".").call(); | |||||
git.commit().setMessage("updated file").call(); | |||||
} | |||||
private static void archive(Git git, File archive, String fmt) | |||||
throws GitAPIException, | |||||
FileNotFoundException, AmbiguousObjectException, | |||||
IncorrectObjectTypeException, IOException { | |||||
git.archive().setOutputStream(new FileOutputStream(archive)) | |||||
.setFormat(fmt) | |||||
.setTree(git.getRepository().resolve("HEAD")).call(); | |||||
} | |||||
private static void assertEntries(ArchiveInputStream o) throws IOException { | |||||
ArchiveEntry e; | |||||
int n = 0; | |||||
while ((e = o.getNextEntry()) != null) { | |||||
n++; | |||||
assertEquals(UNEXPECTED_LAST_MODIFIED, | |||||
(1250379778668L / 1000L) * 1000L, | |||||
e.getLastModifiedDate().getTime()); | |||||
} | |||||
assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, n); | |||||
} | |||||
private static class MockFormat | private static class MockFormat | ||||
implements ArchiveCommand.Format<MockOutputStream> { | implements ArchiveCommand.Format<MockOutputStream> { | ||||
*/ | */ | ||||
package org.eclipse.jgit.api; | package org.eclipse.jgit.api; | ||||
import static java.time.Instant.EPOCH; | |||||
import static org.eclipse.jgit.lib.Constants.MASTER; | import static org.eclipse.jgit.lib.Constants.MASTER; | ||||
import static org.eclipse.jgit.lib.Constants.R_HEADS; | import static org.eclipse.jgit.lib.Constants.R_HEADS; | ||||
import static org.hamcrest.CoreMatchers.is; | import static org.hamcrest.CoreMatchers.is; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.net.MalformedURLException; | import java.net.MalformedURLException; | ||||
import java.net.URISyntaxException; | import java.net.URISyntaxException; | ||||
import java.nio.file.Files; | |||||
import java.nio.file.attribute.FileTime; | |||||
import java.time.Instant; | |||||
import org.eclipse.jgit.api.CheckoutResult.Status; | import org.eclipse.jgit.api.CheckoutResult.Status; | ||||
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; | import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; | ||||
import org.eclipse.jgit.dircache.DirCacheEntry; | import org.eclipse.jgit.dircache.DirCacheEntry; | ||||
import org.eclipse.jgit.junit.JGitTestUtil; | import org.eclipse.jgit.junit.JGitTestUtil; | ||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.junit.time.TimeUtil; | |||||
import org.eclipse.jgit.lfs.BuiltinLFS; | import org.eclipse.jgit.lfs.BuiltinLFS; | ||||
import org.eclipse.jgit.lib.ConfigConstants; | import org.eclipse.jgit.lib.ConfigConstants; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.eclipse.jgit.storage.file.FileBasedConfig; | import org.eclipse.jgit.storage.file.FileBasedConfig; | ||||
import org.eclipse.jgit.transport.RemoteConfig; | import org.eclipse.jgit.transport.RemoteConfig; | ||||
import org.eclipse.jgit.transport.URIish; | import org.eclipse.jgit.transport.URIish; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.FileUtils; | import org.eclipse.jgit.util.FileUtils; | ||||
import org.eclipse.jgit.util.SystemReader; | import org.eclipse.jgit.util.SystemReader; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
File file = new File(db.getWorkTree(), "Test.txt"); | File file = new File(db.getWorkTree(), "Test.txt"); | ||||
long size = file.length(); | long size = file.length(); | ||||
long mTime = file.lastModified() - 5000L; | |||||
assertTrue(file.setLastModified(mTime)); | |||||
Instant mTime = TimeUtil.setLastModifiedWithOffset(file.toPath(), | |||||
-5000L); | |||||
DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS()); | DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS()); | ||||
DirCacheEntry entry = cache.getEntry("Test.txt"); | DirCacheEntry entry = cache.getEntry("Test.txt"); | ||||
assertNotNull(entry); | assertNotNull(entry); | ||||
entry.setLength(0); | entry.setLength(0); | ||||
entry.setLastModified(0); | |||||
entry.setLastModified(EPOCH); | |||||
cache.write(); | cache.write(); | ||||
assertTrue(cache.commit()); | assertTrue(cache.commit()); | ||||
entry = cache.getEntry("Test.txt"); | entry = cache.getEntry("Test.txt"); | ||||
assertNotNull(entry); | assertNotNull(entry); | ||||
assertEquals(0, entry.getLength()); | assertEquals(0, entry.getLength()); | ||||
assertEquals(0, entry.getLastModified()); | |||||
assertEquals(EPOCH, entry.getLastModifiedInstant()); | |||||
db.getIndexFile().setLastModified( | |||||
db.getIndexFile().lastModified() - 5000); | |||||
Files.setLastModifiedTime(db.getIndexFile().toPath(), | |||||
FileTime.from(FS.DETECTED | |||||
.lastModifiedInstant(db.getIndexFile()) | |||||
.minusMillis(5000L))); | |||||
assertNotNull(git.checkout().setName("test").call()); | assertNotNull(git.checkout().setName("test").call()); | ||||
entry = cache.getEntry("Test.txt"); | entry = cache.getEntry("Test.txt"); | ||||
assertNotNull(entry); | assertNotNull(entry); | ||||
assertEquals(size, entry.getLength()); | assertEquals(size, entry.getLength()); | ||||
assertEquals(mTime, entry.getLastModified()); | |||||
assertEquals(mTime, entry.getLastModifiedInstant()); | |||||
} | } | ||||
@Test | @Test |
import org.eclipse.jgit.dircache.DirCacheBuilder; | import org.eclipse.jgit.dircache.DirCacheBuilder; | ||||
import org.eclipse.jgit.dircache.DirCacheEntry; | import org.eclipse.jgit.dircache.DirCacheEntry; | ||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.junit.time.TimeUtil; | |||||
import org.eclipse.jgit.lib.CommitBuilder; | import org.eclipse.jgit.lib.CommitBuilder; | ||||
import org.eclipse.jgit.lib.ConfigConstants; | import org.eclipse.jgit.lib.ConfigConstants; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
public void commitUpdatesSmudgedEntries() throws Exception { | public void commitUpdatesSmudgedEntries() throws Exception { | ||||
try (Git git = new Git(db)) { | try (Git git = new Git(db)) { | ||||
File file1 = writeTrashFile("file1.txt", "content1"); | File file1 = writeTrashFile("file1.txt", "content1"); | ||||
assertTrue(file1.setLastModified(file1.lastModified() - 5000)); | |||||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L); | |||||
File file2 = writeTrashFile("file2.txt", "content2"); | File file2 = writeTrashFile("file2.txt", "content2"); | ||||
assertTrue(file2.setLastModified(file2.lastModified() - 5000)); | |||||
TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L); | |||||
File file3 = writeTrashFile("file3.txt", "content3"); | File file3 = writeTrashFile("file3.txt", "content3"); | ||||
assertTrue(file3.setLastModified(file3.lastModified() - 5000)); | |||||
TimeUtil.setLastModifiedWithOffset(file3.toPath(), -5000L); | |||||
assertNotNull(git.add().addFilepattern("file1.txt") | assertNotNull(git.add().addFilepattern("file1.txt") | ||||
.addFilepattern("file2.txt").addFilepattern("file3.txt").call()); | .addFilepattern("file2.txt").addFilepattern("file3.txt").call()); | ||||
assertEquals(0, cache.getEntry("file2.txt").getLength()); | assertEquals(0, cache.getEntry("file2.txt").getLength()); | ||||
assertEquals(0, cache.getEntry("file3.txt").getLength()); | assertEquals(0, cache.getEntry("file3.txt").getLength()); | ||||
long indexTime = db.getIndexFile().lastModified(); | |||||
db.getIndexFile().setLastModified(indexTime - 5000); | |||||
TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(), | |||||
-5000L); | |||||
write(file1, "content4"); | write(file1, "content4"); | ||||
assertTrue(file1.setLastModified(file1.lastModified() + 2500)); | |||||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), 2500L); | |||||
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") | assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") | ||||
.call()); | .call()); | ||||
public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception { | public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception { | ||||
try (Git git = new Git(db)) { | try (Git git = new Git(db)) { | ||||
File file1 = writeTrashFile("file1.txt", "content1"); | File file1 = writeTrashFile("file1.txt", "content1"); | ||||
assertTrue(file1.setLastModified(file1.lastModified() - 5000)); | |||||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L); | |||||
File file2 = writeTrashFile("file2.txt", "content2"); | File file2 = writeTrashFile("file2.txt", "content2"); | ||||
assertTrue(file2.setLastModified(file2.lastModified() - 5000)); | |||||
TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L); | |||||
assertNotNull(git.add().addFilepattern("file1.txt") | assertNotNull(git.add().addFilepattern("file1.txt") | ||||
.addFilepattern("file2.txt").call()); | .addFilepattern("file2.txt").call()); | ||||
assertEquals(0, cache.getEntry("file1.txt").getLength()); | assertEquals(0, cache.getEntry("file1.txt").getLength()); | ||||
assertEquals(0, cache.getEntry("file2.txt").getLength()); | assertEquals(0, cache.getEntry("file2.txt").getLength()); | ||||
long indexTime = db.getIndexFile().lastModified(); | |||||
db.getIndexFile().setLastModified(indexTime - 5000); | |||||
TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(), | |||||
-5000L); | |||||
write(file1, "content5"); | write(file1, "content5"); | ||||
assertTrue(file1.setLastModified(file1.lastModified() + 1000)); | |||||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), 1000L); | |||||
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") | assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") | ||||
.call()); | .call()); |
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertNotNull; | import static org.junit.Assert.assertNotNull; | ||||
import static org.junit.Assert.assertTrue; | |||||
import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||
import java.io.File; | import java.io.File; | ||||
import org.eclipse.jgit.diff.DiffEntry; | import org.eclipse.jgit.diff.DiffEntry; | ||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType; | import org.eclipse.jgit.diff.DiffEntry.ChangeType; | ||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.junit.time.TimeUtil; | |||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.lib.ObjectReader; | import org.eclipse.jgit.lib.ObjectReader; | ||||
import org.eclipse.jgit.revwalk.RevWalk; | import org.eclipse.jgit.revwalk.RevWalk; | ||||
@Test | @Test | ||||
public void testNoOutputStreamSet() throws Exception { | public void testNoOutputStreamSet() throws Exception { | ||||
File file = writeTrashFile("test.txt", "a"); | File file = writeTrashFile("test.txt", "a"); | ||||
assertTrue(file.setLastModified(file.lastModified() - 5000)); | |||||
TimeUtil.setLastModifiedWithOffset(file.toPath(), -5000L); | |||||
try (Git git = new Git(db)) { | try (Git git = new Git(db)) { | ||||
git.add().addFilepattern(".").call(); | git.add().addFilepattern(".").call(); | ||||
write(file, "b"); | write(file, "b"); |
*/ | */ | ||||
package org.eclipse.jgit.api; | package org.eclipse.jgit.api; | ||||
import static java.time.Instant.EPOCH; | |||||
import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD; | import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.file.Files; | |||||
import java.nio.file.attribute.FileTime; | |||||
import java.time.Instant; | |||||
import org.eclipse.jgit.api.ResetCommand.ResetType; | import org.eclipse.jgit.api.ResetCommand.ResetType; | ||||
import org.eclipse.jgit.api.errors.GitAPIException; | import org.eclipse.jgit.api.errors.GitAPIException; | ||||
public void testMixedResetRetainsSizeAndModifiedTime() throws Exception { | public void testMixedResetRetainsSizeAndModifiedTime() throws Exception { | ||||
git = new Git(db); | git = new Git(db); | ||||
writeTrashFile("a.txt", "a").setLastModified( | |||||
System.currentTimeMillis() - 60 * 1000); | |||||
Files.setLastModifiedTime(writeTrashFile("a.txt", "a").toPath(), | |||||
FileTime.from(Instant.now().minusSeconds(60))); | |||||
assertNotNull(git.add().addFilepattern("a.txt").call()); | assertNotNull(git.add().addFilepattern("a.txt").call()); | ||||
assertNotNull(git.commit().setMessage("a commit").call()); | assertNotNull(git.commit().setMessage("a commit").call()); | ||||
writeTrashFile("b.txt", "b").setLastModified( | |||||
System.currentTimeMillis() - 60 * 1000); | |||||
Files.setLastModifiedTime(writeTrashFile("b.txt", "b").toPath(), | |||||
FileTime.from(Instant.now().minusSeconds(60))); | |||||
assertNotNull(git.add().addFilepattern("b.txt").call()); | assertNotNull(git.add().addFilepattern("b.txt").call()); | ||||
RevCommit commit2 = git.commit().setMessage("b commit").call(); | RevCommit commit2 = git.commit().setMessage("b commit").call(); | ||||
assertNotNull(commit2); | assertNotNull(commit2); | ||||
DirCacheEntry aEntry = cache.getEntry("a.txt"); | DirCacheEntry aEntry = cache.getEntry("a.txt"); | ||||
assertNotNull(aEntry); | assertNotNull(aEntry); | ||||
assertTrue(aEntry.getLength() > 0); | assertTrue(aEntry.getLength() > 0); | ||||
assertTrue(aEntry.getLastModified() > 0); | |||||
assertTrue(aEntry.getLastModifiedInstant().compareTo(EPOCH) > 0); | |||||
DirCacheEntry bEntry = cache.getEntry("b.txt"); | DirCacheEntry bEntry = cache.getEntry("b.txt"); | ||||
assertNotNull(bEntry); | assertNotNull(bEntry); | ||||
assertTrue(bEntry.getLength() > 0); | assertTrue(bEntry.getLength() > 0); | ||||
assertTrue(bEntry.getLastModified() > 0); | |||||
assertTrue(bEntry.getLastModifiedInstant().compareTo(EPOCH) > 0); | |||||
assertSameAsHead(git.reset().setMode(ResetType.MIXED) | assertSameAsHead(git.reset().setMode(ResetType.MIXED) | ||||
.setRef(commit2.getName()).call()); | .setRef(commit2.getName()).call()); | ||||
DirCacheEntry mixedAEntry = cache.getEntry("a.txt"); | DirCacheEntry mixedAEntry = cache.getEntry("a.txt"); | ||||
assertNotNull(mixedAEntry); | assertNotNull(mixedAEntry); | ||||
assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified()); | |||||
assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified()); | |||||
assertEquals(aEntry.getLastModifiedInstant(), | |||||
mixedAEntry.getLastModifiedInstant()); | |||||
assertEquals(aEntry.getLastModifiedInstant(), | |||||
mixedAEntry.getLastModifiedInstant()); | |||||
DirCacheEntry mixedBEntry = cache.getEntry("b.txt"); | DirCacheEntry mixedBEntry = cache.getEntry("b.txt"); | ||||
assertNotNull(mixedBEntry); | assertNotNull(mixedBEntry); | ||||
assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified()); | |||||
assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified()); | |||||
assertEquals(bEntry.getLastModifiedInstant(), | |||||
mixedBEntry.getLastModifiedInstant()); | |||||
assertEquals(bEntry.getLastModifiedInstant(), | |||||
mixedBEntry.getLastModifiedInstant()); | |||||
} | } | ||||
@Test | @Test |
import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||
import java.io.File; | import java.io.File; | ||||
import java.time.Instant; | |||||
import org.eclipse.jgit.events.IndexChangedEvent; | import org.eclipse.jgit.events.IndexChangedEvent; | ||||
import org.eclipse.jgit.events.IndexChangedListener; | import org.eclipse.jgit.events.IndexChangedListener; | ||||
public void testBuildOneFile_FinishWriteCommit() throws Exception { | public void testBuildOneFile_FinishWriteCommit() throws Exception { | ||||
final String path = "a-file-path"; | final String path = "a-file-path"; | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final long lastModified = 1218123387057L; | |||||
final Instant lastModified = Instant.ofEpochMilli(1218123387057L); | |||||
final int length = 1342; | final int length = 1342; | ||||
final DirCacheEntry entOrig; | final DirCacheEntry entOrig; | ||||
{ | { | ||||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | ||||
assertEquals(mode.getBits(), entOrig.getRawMode()); | assertEquals(mode.getBits(), entOrig.getRawMode()); | ||||
assertEquals(0, entOrig.getStage()); | assertEquals(0, entOrig.getStage()); | ||||
assertEquals(lastModified, entOrig.getLastModified()); | |||||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||||
assertEquals(length, entOrig.getLength()); | assertEquals(length, entOrig.getLength()); | ||||
assertFalse(entOrig.isAssumeValid()); | assertFalse(entOrig.isAssumeValid()); | ||||
b.add(entOrig); | b.add(entOrig); | ||||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | ||||
assertEquals(mode.getBits(), entOrig.getRawMode()); | assertEquals(mode.getBits(), entOrig.getRawMode()); | ||||
assertEquals(0, entOrig.getStage()); | assertEquals(0, entOrig.getStage()); | ||||
assertEquals(lastModified, entOrig.getLastModified()); | |||||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||||
assertEquals(length, entOrig.getLength()); | assertEquals(length, entOrig.getLength()); | ||||
assertFalse(entOrig.isAssumeValid()); | assertFalse(entOrig.isAssumeValid()); | ||||
} | } | ||||
public void testBuildOneFile_Commit() throws Exception { | public void testBuildOneFile_Commit() throws Exception { | ||||
final String path = "a-file-path"; | final String path = "a-file-path"; | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final long lastModified = 1218123387057L; | |||||
final Instant lastModified = Instant.ofEpochMilli(1218123387057L); | |||||
final int length = 1342; | final int length = 1342; | ||||
final DirCacheEntry entOrig; | final DirCacheEntry entOrig; | ||||
{ | { | ||||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | ||||
assertEquals(mode.getBits(), entOrig.getRawMode()); | assertEquals(mode.getBits(), entOrig.getRawMode()); | ||||
assertEquals(0, entOrig.getStage()); | assertEquals(0, entOrig.getStage()); | ||||
assertEquals(lastModified, entOrig.getLastModified()); | |||||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||||
assertEquals(length, entOrig.getLength()); | assertEquals(length, entOrig.getLength()); | ||||
assertFalse(entOrig.isAssumeValid()); | assertFalse(entOrig.isAssumeValid()); | ||||
b.add(entOrig); | b.add(entOrig); | ||||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | ||||
assertEquals(mode.getBits(), entOrig.getRawMode()); | assertEquals(mode.getBits(), entOrig.getRawMode()); | ||||
assertEquals(0, entOrig.getStage()); | assertEquals(0, entOrig.getStage()); | ||||
assertEquals(lastModified, entOrig.getLastModified()); | |||||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||||
assertEquals(length, entOrig.getLength()); | assertEquals(length, entOrig.getLength()); | ||||
assertFalse(entOrig.isAssumeValid()); | assertFalse(entOrig.isAssumeValid()); | ||||
} | } | ||||
final String path = "a-file-path"; | final String path = "a-file-path"; | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
// "old" date in 2008 | // "old" date in 2008 | ||||
final long lastModified = 1218123387057L; | |||||
final Instant lastModified = Instant.ofEpochMilli(1218123387057L); | |||||
final int length = 1342; | final int length = 1342; | ||||
DirCacheEntry entOrig; | DirCacheEntry entOrig; | ||||
boolean receivedEvent = false; | boolean receivedEvent = false; |
package org.eclipse.jgit.dircache; | package org.eclipse.jgit.dircache; | ||||
import static java.time.Instant.EPOCH; | |||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import static org.junit.Assert.assertSame; | import static org.junit.Assert.assertSame; | ||||
e.setAssumeValid(false); | e.setAssumeValid(false); | ||||
e.setCreationTime(2L); | e.setCreationTime(2L); | ||||
e.setFileMode(FileMode.EXECUTABLE_FILE); | e.setFileMode(FileMode.EXECUTABLE_FILE); | ||||
e.setLastModified(3L); | |||||
e.setLastModified(EPOCH.plusMillis(3L)); | |||||
e.setLength(100L); | e.setLength(100L); | ||||
e.setObjectId(ObjectId | e.setObjectId(ObjectId | ||||
.fromString("0123456789012345678901234567890123456789")); | .fromString("0123456789012345678901234567890123456789")); | ||||
f.setAssumeValid(true); | f.setAssumeValid(true); | ||||
f.setCreationTime(10L); | f.setCreationTime(10L); | ||||
f.setFileMode(FileMode.SYMLINK); | f.setFileMode(FileMode.SYMLINK); | ||||
f.setLastModified(20L); | |||||
f.setLastModified(EPOCH.plusMillis(20L)); | |||||
f.setLength(100000000L); | f.setLength(100000000L); | ||||
f.setObjectId(ObjectId | f.setObjectId(ObjectId | ||||
.fromString("1234567890123456789012345678901234567890")); | .fromString("1234567890123456789012345678901234567890")); | ||||
ObjectId.fromString("1234567890123456789012345678901234567890"), | ObjectId.fromString("1234567890123456789012345678901234567890"), | ||||
e.getObjectId()); | e.getObjectId()); | ||||
assertEquals(FileMode.SYMLINK, e.getFileMode()); | assertEquals(FileMode.SYMLINK, e.getFileMode()); | ||||
assertEquals(20L, e.getLastModified()); | |||||
assertEquals(EPOCH.plusMillis(20L), e.getLastModifiedInstant()); | |||||
assertEquals(100000000L, e.getLength()); | assertEquals(100000000L, e.getLength()); | ||||
if (keepStage) | if (keepStage) | ||||
assertEquals(DirCacheEntry.STAGE_2, e.getStage()); | assertEquals(DirCacheEntry.STAGE_2, e.getStage()); |
rw.markStart(rw.parseCommit(ref.getObjectId())); | rw.markStart(rw.parseCommit(ref.getObjectId())); | ||||
} | } | ||||
for (RevCommit next; (next = rw.next()) != null;) { | for (RevCommit next; (next = rw.next()) != null;) { | ||||
if (AnyObjectId.equals(next, id)) { | |||||
if (AnyObjectId.isEqual(next, id)) { | |||||
return true; | return true; | ||||
} | } | ||||
} | } |
REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT), | REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT), | ||||
TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted); | TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted); | ||||
@SuppressWarnings("ImmutableEnumChecker") | |||||
final Predicate<? super ReceiveCommand> p; | final Predicate<? super ReceiveCommand> p; | ||||
private Result(Predicate<? super ReceiveCommand> p) { | private Result(Predicate<? super ReceiveCommand> p) { |
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import java.time.Instant; | |||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | import org.eclipse.jgit.errors.IncorrectObjectTypeException; | ||||
import org.eclipse.jgit.errors.MissingObjectException; | import org.eclipse.jgit.errors.MissingObjectException; | ||||
import org.eclipse.jgit.revwalk.RevObject; | import org.eclipse.jgit.revwalk.RevObject; | ||||
import org.eclipse.jgit.revwalk.RevWalk; | import org.eclipse.jgit.revwalk.RevWalk; | ||||
import org.eclipse.jgit.storage.file.WindowCacheConfig; | import org.eclipse.jgit.storage.file.WindowCacheConfig; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.FileUtils; | import org.eclipse.jgit.util.FileUtils; | ||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
private static void write(File[] files, PackWriter pw) | private static void write(File[] files, PackWriter pw) | ||||
throws IOException { | throws IOException { | ||||
final long begin = files[0].getParentFile().lastModified(); | |||||
final Instant begin = FS.DETECTED | |||||
.lastModifiedInstant(files[0].getParentFile()); | |||||
NullProgressMonitor m = NullProgressMonitor.INSTANCE; | NullProgressMonitor m = NullProgressMonitor.INSTANCE; | ||||
try (OutputStream out = new BufferedOutputStream( | try (OutputStream out = new BufferedOutputStream( | ||||
} | } | ||||
private static void delete(File[] list) throws IOException { | private static void delete(File[] list) throws IOException { | ||||
final long begin = list[0].getParentFile().lastModified(); | |||||
final Instant begin = FS.DETECTED | |||||
.lastModifiedInstant(list[0].getParentFile()); | |||||
for (File f : list) { | for (File f : list) { | ||||
FileUtils.delete(f); | FileUtils.delete(f); | ||||
assertFalse(f + " was removed", f.exists()); | assertFalse(f + " was removed", f.exists()); | ||||
touch(begin, list[0].getParentFile()); | touch(begin, list[0].getParentFile()); | ||||
} | } | ||||
private static void touch(long begin, File dir) { | |||||
while (begin >= dir.lastModified()) { | |||||
private static void touch(Instant begin, File dir) throws IOException { | |||||
while (begin.compareTo(FS.DETECTED.lastModifiedInstant(dir)) >= 0) { | |||||
try { | try { | ||||
Thread.sleep(25); | Thread.sleep(25); | ||||
} catch (InterruptedException ie) { | } catch (InterruptedException ie) { | ||||
// | // | ||||
} | } | ||||
dir.setLastModified(System.currentTimeMillis()); | |||||
FS.DETECTED.setLastModified(dir.toPath(), Instant.now()); | |||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
package org.eclipse.jgit.internal.storage.file; | package org.eclipse.jgit.internal.storage.file; | ||||
import static org.eclipse.jgit.junit.JGitTestUtil.read; | |||||
import static org.eclipse.jgit.junit.JGitTestUtil.write; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import static org.junit.Assert.fail; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileOutputStream; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStream; | |||||
import java.nio.file.Files; | import java.nio.file.Files; | ||||
import java.nio.file.Path; | |||||
import java.nio.file.StandardCopyOption; | import java.nio.file.StandardCopyOption; | ||||
import java.nio.file.StandardOpenOption; | |||||
import java.nio.file.attribute.FileTime; | import java.nio.file.attribute.FileTime; | ||||
import java.time.Duration; | |||||
import java.time.Instant; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | |||||
import java.util.concurrent.TimeUnit; | |||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.FS.FileStoreAttributes; | |||||
import org.eclipse.jgit.util.FileUtils; | import org.eclipse.jgit.util.FileUtils; | ||||
import org.eclipse.jgit.util.Stats; | |||||
import org.eclipse.jgit.util.SystemReader; | import org.eclipse.jgit.util.SystemReader; | ||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Assume; | import org.junit.Assume; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
public class FileSnapshotTest { | public class FileSnapshotTest { | ||||
private static final Logger LOG = LoggerFactory | |||||
.getLogger(FileSnapshotTest.class); | |||||
private List<File> files = new ArrayList<>(); | |||||
private Path trash; | |||||
private File trash; | |||||
private FileStoreAttributes fsAttrCache; | |||||
@Before | @Before | ||||
public void setUp() throws Exception { | public void setUp() throws Exception { | ||||
trash = File.createTempFile("tmp_", ""); | |||||
trash.delete(); | |||||
assertTrue("mkdir " + trash, trash.mkdir()); | |||||
trash = Files.createTempDirectory("tmp_"); | |||||
// measure timer resolution before the test to avoid time critical tests | |||||
// are affected by time needed for measurement | |||||
fsAttrCache = FS | |||||
.getFileStoreAttributes(trash.getParent()); | |||||
} | } | ||||
@Before | @Before | ||||
@After | @After | ||||
public void tearDown() throws Exception { | public void tearDown() throws Exception { | ||||
FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); | |||||
FileUtils.delete(trash.toFile(), | |||||
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); | |||||
} | } | ||||
private static void waitNextSec(File f) { | |||||
long initialLastModified = f.lastModified(); | |||||
private static void waitNextTick(Path f) throws IOException { | |||||
Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f); | |||||
do { | do { | ||||
f.setLastModified(System.currentTimeMillis()); | |||||
} while (f.lastModified() == initialLastModified); | |||||
FS.DETECTED.setLastModified(f, Instant.now()); | |||||
} while (FS.DETECTED.lastModifiedInstant(f) | |||||
.equals(initialLastModified)); | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
@Test | @Test | ||||
public void testActuallyIsModifiedTrivial() throws Exception { | public void testActuallyIsModifiedTrivial() throws Exception { | ||||
File f1 = createFile("simple"); | |||||
waitNextSec(f1); | |||||
FileSnapshot save = FileSnapshot.save(f1); | |||||
Path f1 = createFile("simple"); | |||||
waitNextTick(f1); | |||||
FileSnapshot save = FileSnapshot.save(f1.toFile()); | |||||
append(f1, (byte) 'x'); | append(f1, (byte) 'x'); | ||||
waitNextSec(f1); | |||||
assertTrue(save.isModified(f1)); | |||||
waitNextTick(f1); | |||||
assertTrue(save.isModified(f1.toFile())); | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
@Test | @Test | ||||
public void testNewFileWithWait() throws Exception { | public void testNewFileWithWait() throws Exception { | ||||
File f1 = createFile("newfile"); | |||||
waitNextSec(f1); | |||||
FileSnapshot save = FileSnapshot.save(f1); | |||||
Thread.sleep(1500); | |||||
assertTrue(save.isModified(f1)); | |||||
// if filesystem timestamp resolution is high the snapshot won't be | |||||
// racily clean | |||||
Assume.assumeTrue( | |||||
fsAttrCache.getFsTimestampResolution() | |||||
.compareTo(Duration.ofMillis(10)) > 0); | |||||
Path f1 = createFile("newfile"); | |||||
waitNextTick(f1); | |||||
FileSnapshot save = FileSnapshot.save(f1.toFile()); | |||||
TimeUnit.NANOSECONDS.sleep( | |||||
fsAttrCache.getFsTimestampResolution().dividedBy(2).toNanos()); | |||||
assertTrue(save.isModified(f1.toFile())); | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
@Test | @Test | ||||
public void testNewFileNoWait() throws Exception { | public void testNewFileNoWait() throws Exception { | ||||
File f1 = createFile("newfile"); | |||||
FileSnapshot save = FileSnapshot.save(f1); | |||||
assertTrue(save.isModified(f1)); | |||||
// if filesystem timestamp resolution is smaller than time needed to | |||||
// create a file and FileSnapshot the snapshot won't be racily clean | |||||
Assume.assumeTrue(fsAttrCache.getFsTimestampResolution() | |||||
.compareTo(Duration.ofMillis(10)) > 0); | |||||
for (int i = 0; i < 50; i++) { | |||||
Instant start = Instant.now(); | |||||
Path f1 = createFile("newfile"); | |||||
FileSnapshot save = FileSnapshot.save(f1.toFile()); | |||||
Duration res = FS.getFileStoreAttributes(f1) | |||||
.getFsTimestampResolution(); | |||||
Instant end = Instant.now(); | |||||
if (Duration.between(start, end) | |||||
.compareTo(res.multipliedBy(2)) > 0) { | |||||
// This test is racy: under load, there may be a delay between createFile() and | |||||
// FileSnapshot.save(). This can stretch the time between the read TS and FS | |||||
// creation TS to the point that it exceeds the FS granularity, and we | |||||
// conclude it cannot be racily clean, and therefore must be really clean. | |||||
// | |||||
// This should be relatively uncommon. | |||||
continue; | |||||
} | |||||
// The file wasn't really modified, but it looks just like a "maybe racily clean" | |||||
// file. | |||||
assertTrue(save.isModified(f1.toFile())); | |||||
return; | |||||
} | |||||
fail("too much load for this test"); | |||||
} | } | ||||
/** | /** | ||||
@Test | @Test | ||||
public void testSimulatePackfileReplacement() throws Exception { | public void testSimulatePackfileReplacement() throws Exception { | ||||
Assume.assumeFalse(SystemReader.getInstance().isWindows()); | Assume.assumeFalse(SystemReader.getInstance().isWindows()); | ||||
File f1 = createFile("file"); // inode y | |||||
File f2 = createFile("fool"); // Guarantees new inode x | |||||
Path f1 = createFile("file"); // inode y | |||||
Path f2 = createFile("fool"); // Guarantees new inode x | |||||
// wait on f2 since this method resets lastModified of the file | // wait on f2 since this method resets lastModified of the file | ||||
// and leaves lastModified of f1 untouched | // and leaves lastModified of f1 untouched | ||||
waitNextSec(f2); | |||||
waitNextSec(f2); | |||||
FileTime timestamp = Files.getLastModifiedTime(f1.toPath()); | |||||
FileSnapshot save = FileSnapshot.save(f1); | |||||
Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x | |||||
waitNextTick(f2); | |||||
waitNextTick(f2); | |||||
FileTime timestamp = Files.getLastModifiedTime(f1); | |||||
FileSnapshot save = FileSnapshot.save(f1.toFile()); | |||||
Files.move(f2, f1, // Now "file" is inode x | |||||
StandardCopyOption.REPLACE_EXISTING, | StandardCopyOption.REPLACE_EXISTING, | ||||
StandardCopyOption.ATOMIC_MOVE); | StandardCopyOption.ATOMIC_MOVE); | ||||
Files.setLastModifiedTime(f1.toPath(), timestamp); | |||||
assertTrue(save.isModified(f1)); | |||||
Files.setLastModifiedTime(f1, timestamp); | |||||
assertTrue(save.isModified(f1.toFile())); | |||||
assertTrue("unexpected change of fileKey", save.wasFileKeyChanged()); | assertTrue("unexpected change of fileKey", save.wasFileKeyChanged()); | ||||
assertFalse("unexpected size change", save.wasSizeChanged()); | assertFalse("unexpected size change", save.wasSizeChanged()); | ||||
assertFalse("unexpected lastModified change", | assertFalse("unexpected lastModified change", | ||||
*/ | */ | ||||
@Test | @Test | ||||
public void testFileSizeChanged() throws Exception { | public void testFileSizeChanged() throws Exception { | ||||
File f = createFile("file"); | |||||
FileTime timestamp = Files.getLastModifiedTime(f.toPath()); | |||||
FileSnapshot save = FileSnapshot.save(f); | |||||
Path f = createFile("file"); | |||||
FileTime timestamp = Files.getLastModifiedTime(f); | |||||
FileSnapshot save = FileSnapshot.save(f.toFile()); | |||||
append(f, (byte) 'x'); | append(f, (byte) 'x'); | ||||
Files.setLastModifiedTime(f.toPath(), timestamp); | |||||
assertTrue(save.isModified(f)); | |||||
Files.setLastModifiedTime(f, timestamp); | |||||
assertTrue(save.isModified(f.toFile())); | |||||
assertTrue(save.wasSizeChanged()); | assertTrue(save.wasSizeChanged()); | ||||
} | } | ||||
private File createFile(String string) throws IOException { | |||||
trash.mkdirs(); | |||||
File f = File.createTempFile(string, "tdat", trash); | |||||
files.add(f); | |||||
return f; | |||||
@Test | |||||
public void fileSnapshotEquals() throws Exception { | |||||
// 0 sized FileSnapshot. | |||||
FileSnapshot fs1 = FileSnapshot.MISSING_FILE; | |||||
// UNKNOWN_SIZE FileSnapshot. | |||||
FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant()); | |||||
assertTrue(fs1.equals(fs2)); | |||||
assertTrue(fs2.equals(fs1)); | |||||
} | |||||
@SuppressWarnings("boxing") | |||||
@Test | |||||
public void detectFileModified() throws IOException { | |||||
int failures = 0; | |||||
long racyNanos = 0; | |||||
final int COUNT = 10000; | |||||
ArrayList<Long> deltas = new ArrayList<>(); | |||||
File f = createFile("test").toFile(); | |||||
for (int i = 0; i < COUNT; i++) { | |||||
write(f, "a"); | |||||
FileSnapshot snapshot = FileSnapshot.save(f); | |||||
assertEquals("file should contain 'a'", "a", read(f)); | |||||
write(f, "b"); | |||||
if (!snapshot.isModified(f)) { | |||||
deltas.add(snapshot.lastDelta()); | |||||
racyNanos = snapshot.lastRacyThreshold(); | |||||
failures++; | |||||
} | |||||
assertEquals("file should contain 'b'", "b", read(f)); | |||||
} | |||||
if (failures > 0) { | |||||
Stats stats = new Stats(); | |||||
LOG.debug( | |||||
"delta [ns] since modification FileSnapshot failed to detect"); | |||||
for (Long d : deltas) { | |||||
stats.add(d); | |||||
LOG.debug(String.format("%,d", d)); | |||||
} | |||||
LOG.error( | |||||
"count, failures, eff. racy threshold [ns], delta min [ns]," | |||||
+ " delta max [ns], delta avg [ns]," | |||||
+ " delta stddev [ns]"); | |||||
LOG.error(String.format( | |||||
"%,d, %,d, %,d, %,.0f, %,.0f, %,.0f, %,.0f", COUNT, | |||||
failures, racyNanos, stats.min(), stats.max(), | |||||
stats.avg(), stats.stddev())); | |||||
} | |||||
assertTrue( | |||||
String.format( | |||||
"FileSnapshot: failures to detect file modifications" | |||||
+ " %d out of %d\n" | |||||
+ "timestamp resolution %d µs" | |||||
+ " min racy threshold %d µs" | |||||
, failures, COUNT, | |||||
fsAttrCache.getFsTimestampResolution().toNanos() / 1000, | |||||
fsAttrCache.getMinimalRacyInterval().toNanos() / 1000), | |||||
failures == 0); | |||||
} | |||||
private Path createFile(String string) throws IOException { | |||||
Files.createDirectories(trash); | |||||
return Files.createTempFile(trash, string, "tdat"); | |||||
} | } | ||||
private static void append(File f, byte b) throws IOException { | |||||
try (FileOutputStream os = new FileOutputStream(f, true)) { | |||||
private static void append(Path f, byte b) throws IOException { | |||||
try (OutputStream os = Files.newOutputStream(f, | |||||
StandardOpenOption.APPEND)) { | |||||
os.write(b); | os.write(b); | ||||
} | } | ||||
} | } |
return tip; | return tip; | ||||
} | } | ||||
protected long lastModified(AnyObjectId objectId) throws IOException { | |||||
return repo.getFS().lastModified( | |||||
repo.getObjectDatabase().fileFor(objectId)); | |||||
protected long lastModified(AnyObjectId objectId) { | |||||
return repo.getFS() | |||||
.lastModifiedInstant(repo.getObjectDatabase().fileFor(objectId)) | |||||
.toEpochMilli(); | |||||
} | } | ||||
protected static void fsTick() throws InterruptedException, IOException { | protected static void fsTick() throws InterruptedException, IOException { |
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | import org.eclipse.jgit.revwalk.RevCommit; | ||||
import org.eclipse.jgit.storage.file.FileBasedConfig; | import org.eclipse.jgit.storage.file.FileBasedConfig; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.junit.Assume; | import org.junit.Assume; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
// To deal with racy-git situations JGit's Filesnapshot class will | // To deal with racy-git situations JGit's Filesnapshot class will | ||||
// report a file/folder potentially dirty if | // report a file/folder potentially dirty if | ||||
// cachedLastReadTime-cachedLastModificationTime < 2500ms. This | |||||
// causes JGit to always rescan a file after modification. But: | |||||
// this was true only if the difference between current system time | |||||
// and cachedLastModification time was less than 2500ms. If the | |||||
// modification is more than 2500ms ago we may have reported a | |||||
// file/folder to be clean although it has not been rescanned. A | |||||
// Bug. To show the bug we sleep for more than 2500ms | |||||
// cachedLastReadTime-cachedLastModificationTime < filesystem | |||||
// timestamp resolution. This causes JGit to always rescan a file | |||||
// after modification. But: this was true only if the difference | |||||
// between current system time and cachedLastModification time was | |||||
// less than 2500ms. If the modification is more than 2500ms ago we | |||||
// may have reported a file/folder to be clean although it has not | |||||
// been rescanned. A bug. To show the bug we sleep for more than | |||||
// 2500ms | |||||
Thread.sleep(2600); | Thread.sleep(2600); | ||||
File[] ret = packsFolder.listFiles( | File[] ret = packsFolder.listFiles( | ||||
(File dir, String name) -> name.endsWith(".pack")); | (File dir, String name) -> name.endsWith(".pack")); | ||||
assertTrue(ret != null && ret.length == 1); | assertTrue(ret != null && ret.length == 1); | ||||
Assume.assumeTrue(tmpFile.lastModified() == ret[0].lastModified()); | |||||
FS fs = db.getFS(); | |||||
Assume.assumeTrue(fs.lastModifiedInstant(tmpFile) | |||||
.equals(fs.lastModifiedInstant(ret[0]))); | |||||
// all objects are in a new packfile but we will not detect it | // all objects are in a new packfile but we will not detect it | ||||
assertFalse(receivingDB.getObjectDatabase().has(unknownID)); | assertFalse(receivingDB.getObjectDatabase().has(unknownID)); |
import java.nio.file.StandardOpenOption; | import java.nio.file.StandardOpenOption; | ||||
//import java.nio.file.attribute.BasicFileAttributes; | //import java.nio.file.attribute.BasicFileAttributes; | ||||
import java.text.ParseException; | import java.text.ParseException; | ||||
import java.time.Instant; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import java.util.Random; | import java.util.Random; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.storage.file.FileBasedConfig; | import org.eclipse.jgit.storage.file.FileBasedConfig; | ||||
import org.eclipse.jgit.storage.pack.PackConfig; | import org.eclipse.jgit.storage.pack.PackConfig; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
public class PackFileSnapshotTest extends RepositoryTestCase { | public class PackFileSnapshotTest extends RepositoryTestCase { | ||||
AnyObjectId chk1 = pf.getPackChecksum(); | AnyObjectId chk1 = pf.getPackChecksum(); | ||||
String name = pf.getPackName(); | String name = pf.getPackName(); | ||||
Long length = Long.valueOf(pf.getPackFile().length()); | Long length = Long.valueOf(pf.getPackFile().length()); | ||||
long m1 = packFilePath.toFile().lastModified(); | |||||
FS fs = db.getFS(); | |||||
Instant m1 = fs.lastModifiedInstant(packFilePath); | |||||
// Wait for a filesystem timer tick to enhance probability the rest of | // Wait for a filesystem timer tick to enhance probability the rest of | ||||
// this test is done before the filesystem timer ticks again. | // this test is done before the filesystem timer ticks again. | ||||
// content and checksum are different since compression level differs | // content and checksum are different since compression level differs | ||||
AnyObjectId chk2 = repackAndCheck(6, name, length, chk1) | AnyObjectId chk2 = repackAndCheck(6, name, length, chk1) | ||||
.getPackChecksum(); | .getPackChecksum(); | ||||
long m2 = packFilePath.toFile().lastModified(); | |||||
assumeFalse(m2 == m1); | |||||
Instant m2 = fs.lastModifiedInstant(packFilePath); | |||||
assumeFalse(m2.equals(m1)); | |||||
// Repack to create packfile with same name, length. Lastmodified is | // Repack to create packfile with same name, length. Lastmodified is | ||||
// equal to the previous one because we are in the same filesystem timer | // equal to the previous one because we are in the same filesystem timer | ||||
// slot. Content and its checksum are different | // slot. Content and its checksum are different | ||||
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) | AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) | ||||
.getPackChecksum(); | .getPackChecksum(); | ||||
long m3 = packFilePath.toFile().lastModified(); | |||||
Instant m3 = fs.lastModifiedInstant(packFilePath); | |||||
// ask for an unknown git object to force jgit to rescan the list of | // ask for an unknown git object to force jgit to rescan the list of | ||||
// available packs. If we would ask for a known objectid then JGit would | // available packs. If we would ask for a known objectid then JGit would | ||||
db.getObjectDatabase().has(unknownID); | db.getObjectDatabase().has(unknownID); | ||||
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) | assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) | ||||
.getPackChecksum()); | .getPackChecksum()); | ||||
assumeTrue(m3 == m2); | |||||
assumeTrue(m3.equals(m2)); | |||||
} | } | ||||
// Try repacking so fast that we get two new packs which differ only in | // Try repacking so fast that we get two new packs which differ only in | ||||
// Repack to create third packfile | // Repack to create third packfile | ||||
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) | AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) | ||||
.getPackChecksum(); | .getPackChecksum(); | ||||
long m3 = packFilePath.toFile().lastModified(); | |||||
FS fs = db.getFS(); | |||||
Instant m3 = fs.lastModifiedInstant(packFilePath); | |||||
db.getObjectDatabase().has(unknownID); | db.getObjectDatabase().has(unknownID); | ||||
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) | assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) | ||||
.getPackChecksum()); | .getPackChecksum()); | ||||
// Copy copy2 to packfile data to force modification of packfile without | // Copy copy2 to packfile data to force modification of packfile without | ||||
// changing the packfile's filekey. | // changing the packfile's filekey. | ||||
copyPack(packFileBasePath, ".copy2", ""); | copyPack(packFileBasePath, ".copy2", ""); | ||||
long m2 = packFilePath.toFile().lastModified(); | |||||
assumeFalse(m3 == m2); | |||||
Instant m2 = fs.lastModifiedInstant(packFilePath); | |||||
assumeFalse(m3.equals(m2)); | |||||
db.getObjectDatabase().has(unknownID); | db.getObjectDatabase().has(unknownID); | ||||
assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks()) | assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks()) | ||||
// Copy copy2 to packfile data to force modification of packfile without | // Copy copy2 to packfile data to force modification of packfile without | ||||
// changing the packfile's filekey. | // changing the packfile's filekey. | ||||
copyPack(packFileBasePath, ".copy1", ""); | copyPack(packFileBasePath, ".copy1", ""); | ||||
long m1 = packFilePath.toFile().lastModified(); | |||||
assumeTrue(m2 == m1); | |||||
Instant m1 = fs.lastModifiedInstant(packFilePath); | |||||
assumeTrue(m2.equals(m1)); | |||||
db.getObjectDatabase().has(unknownID); | db.getObjectDatabase().has(unknownID); | ||||
assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks()) | assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks()) | ||||
.getPackChecksum()); | .getPackChecksum()); |
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.time.Instant; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.List; | import java.util.List; | ||||
import org.eclipse.jgit.events.ListenerHandle; | import org.eclipse.jgit.events.ListenerHandle; | ||||
import org.eclipse.jgit.events.RefsChangedEvent; | import org.eclipse.jgit.events.RefsChangedEvent; | ||||
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; | import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; | ||||
import org.eclipse.jgit.junit.Repeat; | |||||
import org.eclipse.jgit.junit.TestRepository; | import org.eclipse.jgit.junit.TestRepository; | ||||
import org.eclipse.jgit.lib.AnyObjectId; | import org.eclipse.jgit.lib.AnyObjectId; | ||||
import org.eclipse.jgit.lib.Ref; | import org.eclipse.jgit.lib.Ref; | ||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | import org.eclipse.jgit.revwalk.RevCommit; | ||||
import org.eclipse.jgit.revwalk.RevTag; | import org.eclipse.jgit.revwalk.RevTag; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
assertEquals(B, all.get(HEAD).getObjectId()); | assertEquals(B, all.get(HEAD).getObjectId()); | ||||
} | } | ||||
@Repeat(n = 100, abortOnFailure = false) | |||||
@Test | @Test | ||||
public void testFindRef_DiscoversModifiedLoose() throws IOException { | public void testFindRef_DiscoversModifiedLoose() throws IOException { | ||||
Map<String, Ref> all; | Map<String, Ref> all; | ||||
private void writePackedRefs(String content) throws IOException { | private void writePackedRefs(String content) throws IOException { | ||||
File pr = new File(diskRepo.getDirectory(), "packed-refs"); | File pr = new File(diskRepo.getDirectory(), "packed-refs"); | ||||
write(pr, content); | write(pr, content); | ||||
final long now = System.currentTimeMillis(); | |||||
final int oneHourAgo = 3600 * 1000; | |||||
pr.setLastModified(now - oneHourAgo); | |||||
FS fs = diskRepo.getFS(); | |||||
fs.setLastModified(pr.toPath(), Instant.now().minusSeconds(3600)); | |||||
} | } | ||||
private void deleteLooseRef(String name) { | private void deleteLooseRef(String name) { |
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.UnsupportedEncodingException; | import java.io.UnsupportedEncodingException; | ||||
import java.time.Instant; | |||||
import org.eclipse.jgit.errors.ConfigInvalidException; | import org.eclipse.jgit.errors.ConfigInvalidException; | ||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | import org.eclipse.jgit.errors.IncorrectObjectTypeException; | ||||
import org.eclipse.jgit.storage.file.FileBasedConfig; | import org.eclipse.jgit.storage.file.FileBasedConfig; | ||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder; | import org.eclipse.jgit.storage.file.FileRepositoryBuilder; | ||||
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; | import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.FileUtils; | import org.eclipse.jgit.util.FileUtils; | ||||
import org.eclipse.jgit.util.IO; | import org.eclipse.jgit.util.IO; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
* | * | ||||
* @param name | * @param name | ||||
* the file in the repository to force a time change on. | * the file in the repository to force a time change on. | ||||
* @throws IOException | |||||
*/ | */ | ||||
private void BUG_WorkAroundRacyGitIssues(String name) { | |||||
private void BUG_WorkAroundRacyGitIssues(String name) throws IOException { | |||||
File path = new File(db.getDirectory(), name); | File path = new File(db.getDirectory(), name); | ||||
long old = path.lastModified(); | |||||
FS fs = db.getFS(); | |||||
Instant old = fs.lastModifiedInstant(path); | |||||
long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 | long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 | ||||
path.setLastModified(set); | |||||
assertTrue("time changed", old != path.lastModified()); | |||||
fs.setLastModified(path.toPath(), Instant.ofEpochMilli(set)); | |||||
assertFalse("time changed", old.equals(fs.lastModifiedInstant(path))); | |||||
} | } | ||||
} | } |
import static java.nio.charset.StandardCharsets.UTF_8; | import static java.nio.charset.StandardCharsets.UTF_8; | ||||
import static java.util.concurrent.TimeUnit.DAYS; | import static java.util.concurrent.TimeUnit.DAYS; | ||||
import static java.util.concurrent.TimeUnit.HOURS; | import static java.util.concurrent.TimeUnit.HOURS; | ||||
import static java.util.concurrent.TimeUnit.MICROSECONDS; | |||||
import static java.util.concurrent.TimeUnit.MILLISECONDS; | import static java.util.concurrent.TimeUnit.MILLISECONDS; | ||||
import static java.util.concurrent.TimeUnit.MINUTES; | import static java.util.concurrent.TimeUnit.MINUTES; | ||||
import static java.util.concurrent.TimeUnit.NANOSECONDS; | |||||
import static java.util.concurrent.TimeUnit.SECONDS; | import static java.util.concurrent.TimeUnit.SECONDS; | ||||
import static org.eclipse.jgit.util.FileUtils.pathToString; | import static org.eclipse.jgit.util.FileUtils.pathToString; | ||||
import static org.junit.Assert.assertArrayEquals; | import static org.junit.Assert.assertArrayEquals; | ||||
@Test | @Test | ||||
public void testTimeUnit() throws ConfigInvalidException { | public void testTimeUnit() throws ConfigInvalidException { | ||||
assertEquals(0, parseTime("0", NANOSECONDS)); | |||||
assertEquals(2, parseTime("2ns", NANOSECONDS)); | |||||
assertEquals(200, parseTime("200 nanoseconds", NANOSECONDS)); | |||||
assertEquals(0, parseTime("0", MICROSECONDS)); | |||||
assertEquals(2, parseTime("2us", MICROSECONDS)); | |||||
assertEquals(2, parseTime("2000 nanoseconds", MICROSECONDS)); | |||||
assertEquals(200, parseTime("200 microseconds", MICROSECONDS)); | |||||
assertEquals(0, parseTime("0", MILLISECONDS)); | assertEquals(0, parseTime("0", MILLISECONDS)); | ||||
assertEquals(2, parseTime("2ms", MILLISECONDS)); | assertEquals(2, parseTime("2ms", MILLISECONDS)); | ||||
assertEquals(2, parseTime("2000microseconds", MILLISECONDS)); | |||||
assertEquals(200, parseTime("200 milliseconds", MILLISECONDS)); | assertEquals(200, parseTime("200 milliseconds", MILLISECONDS)); | ||||
assertEquals(0, parseTime("0s", SECONDS)); | assertEquals(0, parseTime("0s", SECONDS)); |
*/ | */ | ||||
package org.eclipse.jgit.lib; | package org.eclipse.jgit.lib; | ||||
import static java.time.Instant.EPOCH; | |||||
import static org.junit.Assert.assertFalse; | |||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import java.time.Instant; | |||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.dircache.DirCache; | import org.eclipse.jgit.dircache.DirCache; | ||||
import org.eclipse.jgit.dircache.DirCacheEntry; | import org.eclipse.jgit.dircache.DirCacheEntry; | ||||
DirCacheEntry entry = dc.getEntry(path); | DirCacheEntry entry = dc.getEntry(path); | ||||
DirCacheEntry entry2 = dc.getEntry(path); | DirCacheEntry entry2 = dc.getEntry(path); | ||||
assertTrue("last modified shall not be zero!", | |||||
entry.getLastModified() != 0); | |||||
assertFalse("last modified shall not be the epoch!", | |||||
entry.getLastModifiedInstant().equals(EPOCH)); | |||||
assertTrue("last modified shall not be zero!", | |||||
entry2.getLastModified() != 0); | |||||
assertFalse("last modified shall not be the epoch!", | |||||
entry2.getLastModifiedInstant().equals(EPOCH)); | |||||
writeTrashFile(path, "new content"); | writeTrashFile(path, "new content"); | ||||
git.add().addFilepattern(path).call(); | git.add().addFilepattern(path).call(); | ||||
entry = dc.getEntry(path); | entry = dc.getEntry(path); | ||||
entry2 = dc.getEntry(path); | entry2 = dc.getEntry(path); | ||||
assertTrue("last modified shall not be zero!", | |||||
entry.getLastModified() != 0); | |||||
assertFalse("last modified shall not be the epoch!", | |||||
entry.getLastModifiedInstant().equals(EPOCH)); | |||||
assertTrue("last modified shall not be zero!", | |||||
entry2.getLastModified() != 0); | |||||
assertFalse("last modified shall not be the epoch!", | |||||
entry2.getLastModifiedInstant().equals(EPOCH)); | |||||
} | } | ||||
} | } | ||||
DirCache dc = db.readDirCache(); | DirCache dc = db.readDirCache(); | ||||
DirCacheEntry entry = dc.getEntry(path); | DirCacheEntry entry = dc.getEntry(path); | ||||
long masterLastMod = entry.getLastModified(); | |||||
Instant masterLastMod = entry.getLastModifiedInstant(); | |||||
git.checkout().setCreateBranch(true).setName("side").call(); | git.checkout().setCreateBranch(true).setName("side").call(); | ||||
dc = db.readDirCache(); | dc = db.readDirCache(); | ||||
entry = dc.getEntry(path); | entry = dc.getEntry(path); | ||||
long sideLastMode = entry.getLastModified(); | |||||
Instant sideLastMod = entry.getLastModifiedInstant(); | |||||
Thread.sleep(2000); | Thread.sleep(2000); | ||||
dc = db.readDirCache(); | dc = db.readDirCache(); | ||||
entry = dc.getEntry(path); | entry = dc.getEntry(path); | ||||
assertTrue("shall have equal mod time!", masterLastMod == sideLastMode); | |||||
assertTrue("shall not equal master timestamp!", | |||||
entry.getLastModified() == masterLastMod); | |||||
assertTrue("shall have equal mod time!", | |||||
masterLastMod.equals(sideLastMod)); | |||||
assertTrue("shall have equal master timestamp!", | |||||
entry.getLastModifiedInstant().equals(masterLastMod)); | |||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
package org.eclipse.jgit.lib; | package org.eclipse.jgit.lib; | ||||
import static java.lang.Long.valueOf; | |||||
import static java.nio.charset.StandardCharsets.UTF_8; | import static java.nio.charset.StandardCharsets.UTF_8; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | |||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.TreeSet; | |||||
import java.time.Instant; | |||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.dircache.DirCache; | |||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.junit.time.TimeUtil; | |||||
import org.eclipse.jgit.treewalk.FileTreeIterator; | import org.eclipse.jgit.treewalk.FileTreeIterator; | ||||
import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl; | |||||
import org.eclipse.jgit.treewalk.NameConflictTreeWalk; | |||||
import org.eclipse.jgit.util.FileUtils; | |||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions; | |||||
import org.eclipse.jgit.util.FS; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
public class RacyGitTests extends RepositoryTestCase { | public class RacyGitTests extends RepositoryTestCase { | ||||
@Test | |||||
public void testIterator() | |||||
throws IllegalStateException, IOException, InterruptedException { | |||||
TreeSet<Long> modTimes = new TreeSet<>(); | |||||
File lastFile = null; | |||||
for (int i = 0; i < 10; i++) { | |||||
lastFile = new File(db.getWorkTree(), "0." + i); | |||||
FileUtils.createNewFile(lastFile); | |||||
if (i == 5) | |||||
fsTick(lastFile); | |||||
} | |||||
modTimes.add(valueOf(fsTick(lastFile))); | |||||
for (int i = 0; i < 10; i++) { | |||||
lastFile = new File(db.getWorkTree(), "1." + i); | |||||
FileUtils.createNewFile(lastFile); | |||||
} | |||||
modTimes.add(valueOf(fsTick(lastFile))); | |||||
for (int i = 0; i < 10; i++) { | |||||
lastFile = new File(db.getWorkTree(), "2." + i); | |||||
FileUtils.createNewFile(lastFile); | |||||
if (i % 4 == 0) | |||||
fsTick(lastFile); | |||||
} | |||||
FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl( | |||||
db, modTimes); | |||||
try (NameConflictTreeWalk tw = new NameConflictTreeWalk(db)) { | |||||
tw.addTree(fileIt); | |||||
tw.setRecursive(true); | |||||
FileTreeIterator t; | |||||
long t0 = 0; | |||||
for (int i = 0; i < 10; i++) { | |||||
assertTrue(tw.next()); | |||||
t = tw.getTree(0, FileTreeIterator.class); | |||||
if (i == 0) { | |||||
t0 = t.getEntryLastModified(); | |||||
} else { | |||||
assertEquals(t0, t.getEntryLastModified()); | |||||
} | |||||
} | |||||
long t1 = 0; | |||||
for (int i = 0; i < 10; i++) { | |||||
assertTrue(tw.next()); | |||||
t = tw.getTree(0, FileTreeIterator.class); | |||||
if (i == 0) { | |||||
t1 = t.getEntryLastModified(); | |||||
assertTrue(t1 > t0); | |||||
} else { | |||||
assertEquals(t1, t.getEntryLastModified()); | |||||
} | |||||
} | |||||
long t2 = 0; | |||||
for (int i = 0; i < 10; i++) { | |||||
assertTrue(tw.next()); | |||||
t = tw.getTree(0, FileTreeIterator.class); | |||||
if (i == 0) { | |||||
t2 = t.getEntryLastModified(); | |||||
assertTrue(t2 > t1); | |||||
} else { | |||||
assertEquals(t2, t.getEntryLastModified()); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
@Test | @Test | ||||
public void testRacyGitDetection() throws Exception { | public void testRacyGitDetection() throws Exception { | ||||
fsTick(db.getIndexFile()); | fsTick(db.getIndexFile()); | ||||
// create two files | // create two files | ||||
File a = addToWorkDir("a", "a"); | |||||
File b = addToWorkDir("b", "b"); | |||||
assertTrue(a.setLastModified(b.lastModified())); | |||||
assertTrue(b.setLastModified(b.lastModified())); | |||||
File a = writeToWorkDir("a", "a"); | |||||
File b = writeToWorkDir("b", "b"); | |||||
TimeUtil.setLastModifiedOf(a.toPath(), b.toPath()); | |||||
TimeUtil.setLastModifiedOf(b.toPath(), b.toPath()); | |||||
// wait to ensure that file-modTimes and therefore index entry modTime | // wait to ensure that file-modTimes and therefore index entry modTime | ||||
// doesn't match the modtime of index-file after next persistance | // doesn't match the modtime of index-file after next persistance | ||||
fsTick(db.getIndexFile()); | fsTick(db.getIndexFile()); | ||||
// Create a racy git situation. This is a situation that the index is | // Create a racy git situation. This is a situation that the index is | ||||
// updated and then a file is modified within a second. By changing the | |||||
// index file artificially, we create a fake racy situation. | |||||
File updatedA = addToWorkDir("a", "a2"); | |||||
assertTrue(updatedA.setLastModified(updatedA.lastModified() + 100)); | |||||
// updated and then a file is modified within the same tick of the | |||||
// filesystem timestamp resolution. By changing the index file | |||||
// artificially, we create a fake racy situation. | |||||
File updatedA = writeToWorkDir("a", "a2"); | |||||
Instant newLastModified = TimeUtil | |||||
.setLastModifiedWithOffset(updatedA.toPath(), 100L); | |||||
resetIndex(new FileTreeIterator(db)); | resetIndex(new FileTreeIterator(db)); | ||||
assertTrue(db.getIndexFile() | |||||
.setLastModified(updatedA.lastModified() + 90)); | |||||
FS.DETECTED.setLastModified(db.getIndexFile().toPath(), | |||||
newLastModified); | |||||
db.readDirCache(); | |||||
// although racily clean a should not be reported as being dirty | |||||
DirCache dc = db.readDirCache(); | |||||
// check index state: although racily clean a should not be reported as | |||||
// being dirty since we forcefully reset the index to match the working | |||||
// tree | |||||
assertEquals( | assertEquals( | ||||
"[a, mode:100644, time:t1, smudged, length:0, content:a2]" | "[a, mode:100644, time:t1, smudged, length:0, content:a2]" | ||||
+ "[b, mode:100644, time:t0, length:1, content:b]", | + "[b, mode:100644, time:t0, length:1, content:b]", | ||||
indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT)); | indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT)); | ||||
// compare state of files in working tree with index to check that | |||||
// FileTreeIterator.isModified() works as expected | |||||
FileTreeIterator f = new FileTreeIterator(db.getWorkTree(), db.getFS(), | |||||
db.getConfig().get(WorkingTreeOptions.KEY)); | |||||
assertTrue(f.findFile("a")); | |||||
try (ObjectReader reader = db.newObjectReader()) { | |||||
assertFalse(f.isModified(dc.getEntry("a"), false, reader)); | |||||
} | |||||
} | } | ||||
private File addToWorkDir(String path, String content) throws IOException { | |||||
private File writeToWorkDir(String path, String content) throws IOException { | |||||
File f = new File(db.getWorkTree(), path); | File f = new File(db.getWorkTree(), path); | ||||
try (FileOutputStream fos = new FileOutputStream(f)) { | try (FileOutputStream fos = new FileOutputStream(f)) { | ||||
fos.write(content.getBytes(UTF_8)); | fos.write(content.getBytes(UTF_8)); |
package org.eclipse.jgit.merge; | package org.eclipse.jgit.merge; | ||||
import static java.nio.charset.StandardCharsets.UTF_8; | import static java.nio.charset.StandardCharsets.UTF_8; | ||||
import static java.time.Instant.EPOCH; | |||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; | import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.time.Instant; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Map; | import java.util.Map; | ||||
* Throws an exception if reading beyond limit. | * Throws an exception if reading beyond limit. | ||||
*/ | */ | ||||
static class BigReadForbiddenStream extends ObjectStream.Filter { | static class BigReadForbiddenStream extends ObjectStream.Filter { | ||||
int limit; | |||||
long limit; | |||||
BigReadForbiddenStream(ObjectStream orig, int limit) { | |||||
BigReadForbiddenStream(ObjectStream orig, long limit) { | |||||
super(orig.getType(), orig.getSize(), orig); | super(orig.getType(), orig.getSize(), orig); | ||||
this.limit = limit; | this.limit = limit; | ||||
} | } | ||||
@Theory | @Theory | ||||
public void checkForCorrectIndex(MergeStrategy strategy) throws Exception { | public void checkForCorrectIndex(MergeStrategy strategy) throws Exception { | ||||
File f; | File f; | ||||
long lastTs4, lastTsIndex; | |||||
Instant lastTs4, lastTsIndex; | |||||
Git git = Git.wrap(db); | Git git = Git.wrap(db); | ||||
File indexFile = db.getIndexFile(); | File indexFile = db.getIndexFile(); | ||||
// Create initial content and remember when the last file was written. | // Create initial content and remember when the last file was written. | ||||
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); | f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); | ||||
lastTs4 = FS.DETECTED.lastModified(f); | |||||
lastTs4 = FS.DETECTED.lastModifiedInstant(f); | |||||
// add all files, commit and check this doesn't update any working tree | // add all files, commit and check this doesn't update any working tree | ||||
// files and that the index is in a new file system timer tick. Make | // files and that the index is in a new file system timer tick. Make | ||||
checkConsistentLastModified("0", "1", "2", "3", "4"); | checkConsistentLastModified("0", "1", "2", "3", "4"); | ||||
checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index"); | checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index"); | ||||
assertEquals("Commit should not touch working tree file 4", lastTs4, | assertEquals("Commit should not touch working tree file 4", lastTs4, | ||||
FS.DETECTED.lastModified(new File(db.getWorkTree(), "4"))); | |||||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||||
FS.DETECTED | |||||
.lastModifiedInstant(new File(db.getWorkTree(), "4"))); | |||||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||||
// Do modifications on the master branch. Then add and commit. This | // Do modifications on the master branch. Then add and commit. This | ||||
// should touch only "0", "2 and "3" | // should touch only "0", "2 and "3" | ||||
checkConsistentLastModified("0", "1", "2", "3", "4"); | checkConsistentLastModified("0", "1", "2", "3", "4"); | ||||
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" | checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" | ||||
+ lastTsIndex, "<0", "2", "3", "<.git/index"); | + lastTsIndex, "<0", "2", "3", "<.git/index"); | ||||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||||
// Checkout a side branch. This should touch only "0", "2 and "3" | // Checkout a side branch. This should touch only "0", "2 and "3" | ||||
fsTick(indexFile); | fsTick(indexFile); | ||||
checkConsistentLastModified("0", "1", "2", "3", "4"); | checkConsistentLastModified("0", "1", "2", "3", "4"); | ||||
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" | checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" | ||||
+ lastTsIndex, "<0", "2", "3", ".git/index"); | + lastTsIndex, "<0", "2", "3", ".git/index"); | ||||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||||
// This checkout may have populated worktree and index so fast that we | // This checkout may have populated worktree and index so fast that we | ||||
// may have smudged entries now. Check that we have the right content | // may have smudged entries now. Check that we have the right content | ||||
indexState(CONTENT)); | indexState(CONTENT)); | ||||
fsTick(indexFile); | fsTick(indexFile); | ||||
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); | f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); | ||||
lastTs4 = FS.DETECTED.lastModified(f); | |||||
lastTs4 = FS.DETECTED.lastModifiedInstant(f); | |||||
fsTick(f); | fsTick(f); | ||||
git.add().addFilepattern(".").call(); | git.add().addFilepattern(".").call(); | ||||
checkConsistentLastModified("0", "1", "2", "3", "4"); | checkConsistentLastModified("0", "1", "2", "3", "4"); | ||||
checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3", | checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3", | ||||
"4", "<.git/index"); | "4", "<.git/index"); | ||||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||||
// Do modifications on the side branch. Touch only "1", "2 and "3" | // Do modifications on the side branch. Touch only "1", "2 and "3" | ||||
fsTick(indexFile); | fsTick(indexFile); | ||||
checkConsistentLastModified("0", "1", "2", "3", "4"); | checkConsistentLastModified("0", "1", "2", "3", "4"); | ||||
checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*" | checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*" | ||||
+ lastTsIndex, "<1", "2", "3", "<.git/index"); | + lastTsIndex, "<1", "2", "3", "<.git/index"); | ||||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||||
// merge master and side. Should only touch "0," "2" and "3" | // merge master and side. Should only touch "0," "2" and "3" | ||||
fsTick(indexFile); | fsTick(indexFile); | ||||
assertEquals( | assertEquals( | ||||
"IndexEntry with path " | "IndexEntry with path " | ||||
+ path | + path | ||||
+ " has lastmodified with is different from the worktree file", | |||||
FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path) | |||||
.getLastModified()); | |||||
+ " has lastmodified which is different from the worktree file", | |||||
FS.DETECTED.lastModifiedInstant(new File(workTree, path)), | |||||
dc.getEntry(path) | |||||
.getLastModifiedInstant()); | |||||
} | } | ||||
// Assert that modification timestamps of working tree files are as | // Assert that modification timestamps of working tree files are as | ||||
// then this file must be younger then file i. A path "*<modtime>" | // then this file must be younger then file i. A path "*<modtime>" | ||||
// represents a file with a modification time of <modtime> | // represents a file with a modification time of <modtime> | ||||
// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt | // E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt | ||||
private void checkModificationTimeStampOrder(String... pathes) | |||||
throws IOException { | |||||
long lastMod = Long.MIN_VALUE; | |||||
private void checkModificationTimeStampOrder(String... pathes) { | |||||
Instant lastMod = EPOCH; | |||||
for (String p : pathes) { | for (String p : pathes) { | ||||
boolean strong = p.startsWith("<"); | boolean strong = p.startsWith("<"); | ||||
boolean fixed = p.charAt(strong ? 1 : 0) == '*'; | boolean fixed = p.charAt(strong ? 1 : 0) == '*'; | ||||
p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0)); | p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0)); | ||||
long curMod = fixed ? Long.valueOf(p).longValue() | |||||
: FS.DETECTED.lastModified(new File(db.getWorkTree(), p)); | |||||
if (strong) | |||||
Instant curMod = fixed ? Instant.parse(p) | |||||
: FS.DETECTED | |||||
.lastModifiedInstant(new File(db.getWorkTree(), p)); | |||||
if (strong) { | |||||
assertTrue("path " + p + " is not younger than predecesssor", | assertTrue("path " + p + " is not younger than predecesssor", | ||||
curMod > lastMod); | |||||
else | |||||
curMod.compareTo(lastMod) > 0); | |||||
} else { | |||||
assertTrue("path " + p + " is older than predecesssor", | assertTrue("path " + p + " is older than predecesssor", | ||||
curMod >= lastMod); | |||||
curMod.compareTo(lastMod) >= 0); | |||||
} | |||||
} | } | ||||
} | } | ||||
assertEquals(a1.hashCode(), a2.hashCode()); | assertEquals(a1.hashCode(), a2.hashCode()); | ||||
assertEquals(b1.hashCode(), b2.hashCode()); | assertEquals(b1.hashCode(), b2.hashCode()); | ||||
assertTrue(AnyObjectId.equals(a1, a2)); | |||||
assertTrue(AnyObjectId.equals(b1, b2)); | |||||
assertTrue(AnyObjectId.isEqual(a1, a2)); | |||||
assertTrue(AnyObjectId.isEqual(b1, b2)); | |||||
} | } | ||||
@Test | @Test |
import static org.eclipse.jgit.util.FileUtils.pathToString; | import static org.eclipse.jgit.util.FileUtils.pathToString; | ||||
import static org.junit.Assert.assertArrayEquals; | import static org.junit.Assert.assertArrayEquals; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertTrue; | |||||
import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||
import java.io.File; | |||||
import java.io.FileOutputStream; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStream; | |||||
import java.nio.file.Files; | |||||
import java.nio.file.Path; | |||||
import java.nio.file.StandardOpenOption; | |||||
import java.util.StringTokenizer; | import java.util.StringTokenizer; | ||||
import org.eclipse.jgit.errors.ConfigInvalidException; | import org.eclipse.jgit.errors.ConfigInvalidException; | ||||
private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = " | private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = " | ||||
+ ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL; | + ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL; | ||||
private File trash; | |||||
private Path trash; | |||||
@Before | @Before | ||||
public void setUp() throws Exception { | public void setUp() throws Exception { | ||||
trash = File.createTempFile("tmp_", ""); | |||||
trash.delete(); | |||||
assertTrue("mkdir " + trash, trash.mkdir()); | |||||
trash = Files.createTempDirectory("tmp_"); | |||||
FS.getFileStoreAttributes(trash.getParent()); | |||||
} | } | ||||
@After | @After | ||||
public void tearDown() throws Exception { | public void tearDown() throws Exception { | ||||
FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); | |||||
FileUtils.delete(trash.toFile(), | |||||
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING | FileUtils.RETRY); | |||||
} | } | ||||
@Test | @Test | ||||
public void testSystemEncoding() throws IOException, ConfigInvalidException { | public void testSystemEncoding() throws IOException, ConfigInvalidException { | ||||
final File file = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
config.setString(USER, null, NAME, BOB); | config.setString(USER, null, NAME, BOB); | ||||
config.save(); | config.save(); | ||||
assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file)); | |||||
assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile())); | |||||
} | } | ||||
@Test | @Test | ||||
public void testUTF8withoutBOM() throws IOException, ConfigInvalidException { | public void testUTF8withoutBOM() throws IOException, ConfigInvalidException { | ||||
final File file = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
config.setString(USER, null, NAME, BOB); | config.setString(USER, null, NAME, BOB); | ||||
config.save(); | config.save(); | ||||
assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file)); | |||||
assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile())); | |||||
} | } | ||||
@Test | @Test | ||||
bos1.write(0xBF); | bos1.write(0xBF); | ||||
bos1.write(CONTENT1.getBytes(UTF_8)); | bos1.write(CONTENT1.getBytes(UTF_8)); | ||||
final File file = createFile(bos1.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(bos1.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
bos2.write(0xBB); | bos2.write(0xBB); | ||||
bos2.write(0xBF); | bos2.write(0xBF); | ||||
bos2.write(CONTENT2.getBytes(UTF_8)); | bos2.write(CONTENT2.getBytes(UTF_8)); | ||||
assertArrayEquals(bos2.toByteArray(), IO.readFully(file)); | |||||
assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile())); | |||||
} | } | ||||
@Test | @Test | ||||
bos1.write(" \n\t".getBytes(UTF_8)); | bos1.write(" \n\t".getBytes(UTF_8)); | ||||
bos1.write(CONTENT1.getBytes(UTF_8)); | bos1.write(CONTENT1.getBytes(UTF_8)); | ||||
final File file = createFile(bos1.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(bos1.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
final ByteArrayOutputStream bos2 = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos2 = new ByteArrayOutputStream(); | ||||
bos2.write(" \n\t".getBytes(UTF_8)); | bos2.write(" \n\t".getBytes(UTF_8)); | ||||
bos2.write(CONTENT2.getBytes(UTF_8)); | bos2.write(CONTENT2.getBytes(UTF_8)); | ||||
assertArrayEquals(bos2.toByteArray(), IO.readFully(file)); | |||||
assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile())); | |||||
} | } | ||||
@Test | @Test | ||||
public void testIncludeAbsolute() | public void testIncludeAbsolute() | ||||
throws IOException, ConfigInvalidException { | throws IOException, ConfigInvalidException { | ||||
final File includedFile = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final Path includedFile = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
bos.write("[include]\npath=".getBytes(UTF_8)); | bos.write("[include]\npath=".getBytes(UTF_8)); | ||||
bos.write(pathToString(includedFile).getBytes(UTF_8)); | |||||
bos.write(pathToString(includedFile.toFile()).getBytes(UTF_8)); | |||||
final File file = createFile(bos.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(bos.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
} | } | ||||
@Test | @Test | ||||
public void testIncludeRelativeDot() | public void testIncludeRelativeDot() | ||||
throws IOException, ConfigInvalidException { | throws IOException, ConfigInvalidException { | ||||
final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1"); | |||||
final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1"); | |||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
bos.write("[include]\npath=".getBytes(UTF_8)); | bos.write("[include]\npath=".getBytes(UTF_8)); | ||||
bos.write(("./" + includedFile.getName()).getBytes(UTF_8)); | |||||
bos.write(("./" + includedFile.getFileName()).getBytes(UTF_8)); | |||||
final File file = createFile(bos.toByteArray(), "dir1"); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(bos.toByteArray(), "dir1"); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
} | } | ||||
@Test | @Test | ||||
public void testIncludeRelativeDotDot() | public void testIncludeRelativeDotDot() | ||||
throws IOException, ConfigInvalidException { | throws IOException, ConfigInvalidException { | ||||
final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1"); | |||||
final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1"); | |||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
bos.write("[include]\npath=".getBytes(UTF_8)); | bos.write("[include]\npath=".getBytes(UTF_8)); | ||||
bos.write(("../" + includedFile.getParentFile().getName() + "/" | |||||
+ includedFile.getName()).getBytes(UTF_8)); | |||||
bos.write(("../" + includedFile.getParent().getFileName() + "/" | |||||
+ includedFile.getFileName()).getBytes(UTF_8)); | |||||
final File file = createFile(bos.toByteArray(), "dir2"); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(bos.toByteArray(), "dir2"); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
} | } | ||||
@Test | @Test | ||||
public void testIncludeRelativeDotDotNotFound() | public void testIncludeRelativeDotDotNotFound() | ||||
throws IOException, ConfigInvalidException { | throws IOException, ConfigInvalidException { | ||||
final File includedFile = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final Path includedFile = createFile(CONTENT1.getBytes(UTF_8)); | |||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
bos.write("[include]\npath=".getBytes(UTF_8)); | bos.write("[include]\npath=".getBytes(UTF_8)); | ||||
bos.write(("../" + includedFile.getName()).getBytes(UTF_8)); | |||||
bos.write(("../" + includedFile.getFileName()).getBytes(UTF_8)); | |||||
final File file = createFile(bos.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(bos.toByteArray()); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
assertEquals(null, config.getString(USER, null, NAME)); | assertEquals(null, config.getString(USER, null, NAME)); | ||||
} | } | ||||
@Test | @Test | ||||
public void testIncludeWithTilde() | public void testIncludeWithTilde() | ||||
throws IOException, ConfigInvalidException { | throws IOException, ConfigInvalidException { | ||||
final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "home"); | |||||
final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "home"); | |||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
bos.write("[include]\npath=".getBytes(UTF_8)); | bos.write("[include]\npath=".getBytes(UTF_8)); | ||||
bos.write(("~/" + includedFile.getName()).getBytes(UTF_8)); | |||||
bos.write(("~/" + includedFile.getFileName()).getBytes(UTF_8)); | |||||
final File file = createFile(bos.toByteArray(), "repo"); | |||||
final Path file = createFile(bos.toByteArray(), "repo"); | |||||
final FS fs = FS.DETECTED.newInstance(); | final FS fs = FS.DETECTED.newInstance(); | ||||
fs.setUserHome(includedFile.getParentFile()); | |||||
fs.setUserHome(includedFile.getParent().toFile()); | |||||
final FileBasedConfig config = new FileBasedConfig(file, fs); | |||||
final FileBasedConfig config = new FileBasedConfig(file.toFile(), fs); | |||||
config.load(); | config.load(); | ||||
assertEquals(ALICE, config.getString(USER, null, NAME)); | assertEquals(ALICE, config.getString(USER, null, NAME)); | ||||
} | } | ||||
throws IOException, ConfigInvalidException { | throws IOException, ConfigInvalidException { | ||||
// use a content with multiple sections and multiple key/value pairs | // use a content with multiple sections and multiple key/value pairs | ||||
// because code for first line works different than for subsequent lines | // because code for first line works different than for subsequent lines | ||||
final File includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1"); | |||||
final Path includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1"); | |||||
final File file = createFile(new byte[0], "dir2"); | |||||
FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); | |||||
final Path file = createFile(new byte[0], "dir2"); | |||||
FileBasedConfig config = new FileBasedConfig(file.toFile(), | |||||
FS.DETECTED); | |||||
config.setString("include", null, "path", | config.setString("include", null, "path", | ||||
("../" + includedFile.getParentFile().getName() + "/" | |||||
+ includedFile.getName())); | |||||
("../" + includedFile.getParent().getFileName() + "/" | |||||
+ includedFile.getFileName())); | |||||
// just by setting the include.path, it won't be included | // just by setting the include.path, it won't be included | ||||
assertEquals(null, config.getString(USER, null, NAME)); | assertEquals(null, config.getString(USER, null, NAME)); | ||||
assertEquals(2, | assertEquals(2, | ||||
new StringTokenizer(expectedText, "\n", false).countTokens()); | new StringTokenizer(expectedText, "\n", false).countTokens()); | ||||
config = new FileBasedConfig(file, FS.DETECTED); | |||||
config = new FileBasedConfig(file.toFile(), FS.DETECTED); | |||||
config.load(); | config.load(); | ||||
String actualText = config.toText(); | String actualText = config.toText(); | ||||
assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL)); | assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL)); | ||||
} | } | ||||
private File createFile(byte[] content) throws IOException { | |||||
private Path createFile(byte[] content) throws IOException { | |||||
return createFile(content, null); | return createFile(content, null); | ||||
} | } | ||||
private File createFile(byte[] content, String subdir) throws IOException { | |||||
File dir = subdir != null ? new File(trash, subdir) : trash; | |||||
dir.mkdirs(); | |||||
private Path createFile(byte[] content, String subdir) throws IOException { | |||||
Path dir = subdir != null ? trash.resolve(subdir) : trash; | |||||
Files.createDirectories(dir); | |||||
File f = File.createTempFile(getClass().getName(), null, dir); | |||||
try (FileOutputStream os = new FileOutputStream(f, true)) { | |||||
Path f = Files.createTempFile(dir, getClass().getName(), null); | |||||
try (OutputStream os = Files.newOutputStream(f, | |||||
StandardOpenOption.APPEND)) { | |||||
os.write(content); | os.write(content); | ||||
} | } | ||||
return f; | return f; |
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStreamWriter; | import java.io.OutputStreamWriter; | ||||
import java.time.Instant; | |||||
import java.util.concurrent.TimeUnit; | |||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.eclipse.jgit.transport.OpenSshConfig.Host; | import org.eclipse.jgit.transport.OpenSshConfig.Host; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.FileUtils; | import org.eclipse.jgit.util.FileUtils; | ||||
import org.eclipse.jgit.util.SystemReader; | import org.eclipse.jgit.util.SystemReader; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
} | } | ||||
private void config(String data) throws IOException { | private void config(String data) throws IOException { | ||||
long lastMtime = configFile.lastModified(); | |||||
FS fs = FS.DETECTED; | |||||
long resolution = FS.getFileStoreAttributes(configFile.toPath()) | |||||
.getFsTimestampResolution().toNanos(); | |||||
Instant lastMtime = fs.lastModifiedInstant(configFile); | |||||
do { | do { | ||||
try (final OutputStreamWriter fw = new OutputStreamWriter( | try (final OutputStreamWriter fw = new OutputStreamWriter( | ||||
new FileOutputStream(configFile), UTF_8)) { | new FileOutputStream(configFile), UTF_8)) { | ||||
fw.write(data); | fw.write(data); | ||||
TimeUnit.NANOSECONDS.sleep(resolution); | |||||
} catch (InterruptedException e) { | |||||
Thread.interrupted(); | |||||
} | } | ||||
} while (lastMtime == configFile.lastModified()); | |||||
} while (lastMtime.equals(fs.lastModifiedInstant(configFile))); | |||||
} | } | ||||
@Test | @Test |
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.file.InvalidPathException; | import java.nio.file.InvalidPathException; | ||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.time.Instant; | |||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.api.ResetCommand.ResetType; | import org.eclipse.jgit.api.ResetCommand.ResetType; | ||||
public class FileTreeIteratorTest extends RepositoryTestCase { | public class FileTreeIteratorTest extends RepositoryTestCase { | ||||
private final String[] paths = { "a,", "a,b", "a/b", "a0b" }; | private final String[] paths = { "a,", "a,b", "a/b", "a0b" }; | ||||
private long[] mtime; | |||||
private Instant[] mtime; | |||||
@Override | @Override | ||||
@Before | @Before | ||||
// This should stress the sorting code better than doing it in | // This should stress the sorting code better than doing it in | ||||
// the correct order. | // the correct order. | ||||
// | // | ||||
mtime = new long[paths.length]; | |||||
mtime = new Instant[paths.length]; | |||||
for (int i = paths.length - 1; i >= 0; i--) { | for (int i = paths.length - 1; i >= 0; i--) { | ||||
final String s = paths[i]; | final String s = paths[i]; | ||||
writeTrashFile(s, s); | writeTrashFile(s, s); | ||||
mtime[i] = FS.DETECTED.lastModified(new File(trash, s)); | |||||
mtime[i] = db.getFS().lastModifiedInstant(new File(trash, s)); | |||||
} | } | ||||
} | } | ||||
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | ||||
assertEquals(paths[0], nameOf(top)); | assertEquals(paths[0], nameOf(top)); | ||||
assertEquals(paths[0].length(), top.getEntryLength()); | assertEquals(paths[0].length(), top.getEntryLength()); | ||||
assertEquals(mtime[0], top.getEntryLastModified()); | |||||
assertEquals(mtime[0], top.getEntryLastModifiedInstant()); | |||||
top.next(1); | top.next(1); | ||||
assertFalse(top.first()); | assertFalse(top.first()); | ||||
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | ||||
assertEquals(paths[1], nameOf(top)); | assertEquals(paths[1], nameOf(top)); | ||||
assertEquals(paths[1].length(), top.getEntryLength()); | assertEquals(paths[1].length(), top.getEntryLength()); | ||||
assertEquals(mtime[1], top.getEntryLastModified()); | |||||
assertEquals(mtime[1], top.getEntryLastModifiedInstant()); | |||||
top.next(1); | top.next(1); | ||||
assertFalse(top.first()); | assertFalse(top.first()); | ||||
assertFalse(sub.eof()); | assertFalse(sub.eof()); | ||||
assertEquals(paths[2], nameOf(sub)); | assertEquals(paths[2], nameOf(sub)); | ||||
assertEquals(paths[2].length(), subfti.getEntryLength()); | assertEquals(paths[2].length(), subfti.getEntryLength()); | ||||
assertEquals(mtime[2], subfti.getEntryLastModified()); | |||||
assertEquals(mtime[2], subfti.getEntryLastModifiedInstant()); | |||||
sub.next(1); | sub.next(1); | ||||
assertTrue(sub.eof()); | assertTrue(sub.eof()); | ||||
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | ||||
assertEquals(paths[3], nameOf(top)); | assertEquals(paths[3], nameOf(top)); | ||||
assertEquals(paths[3].length(), top.getEntryLength()); | assertEquals(paths[3].length(), top.getEntryLength()); | ||||
assertEquals(mtime[3], top.getEntryLastModified()); | |||||
assertEquals(mtime[3], top.getEntryLastModifiedInstant()); | |||||
top.next(1); | top.next(1); | ||||
assertTrue(top.eof()); | assertTrue(top.eof()); | ||||
@Test | @Test | ||||
public void testIsModifiedFileSmudged() throws Exception { | public void testIsModifiedFileSmudged() throws Exception { | ||||
File f = writeTrashFile("file", "content"); | File f = writeTrashFile("file", "content"); | ||||
FS fs = db.getFS(); | |||||
try (Git git = new Git(db)) { | try (Git git = new Git(db)) { | ||||
// The idea of this test is to check the smudged handling | // The idea of this test is to check the smudged handling | ||||
// Hopefully fsTick will make sure our entry gets smudged | // Hopefully fsTick will make sure our entry gets smudged | ||||
fsTick(f); | fsTick(f); | ||||
writeTrashFile("file", "content"); | writeTrashFile("file", "content"); | ||||
long lastModified = f.lastModified(); | |||||
Instant lastModified = fs.lastModifiedInstant(f); | |||||
git.add().addFilepattern("file").call(); | git.add().addFilepattern("file").call(); | ||||
writeTrashFile("file", "conten2"); | writeTrashFile("file", "conten2"); | ||||
f.setLastModified(lastModified); | |||||
fs.setLastModified(f.toPath(), lastModified); | |||||
// We cannot trust this to go fast enough on | // We cannot trust this to go fast enough on | ||||
// a system with less than one-second lastModified | // a system with less than one-second lastModified | ||||
// resolution, so we force the index to have the | // resolution, so we force the index to have the | ||||
// same timestamp as the file we look at. | // same timestamp as the file we look at. | ||||
db.getIndexFile().setLastModified(lastModified); | |||||
fs.setLastModified(db.getIndexFile().toPath(), lastModified); | |||||
} | } | ||||
DirCacheEntry dce = db.readDirCache().getEntry("file"); | DirCacheEntry dce = db.readDirCache().getEntry("file"); | ||||
FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db | FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db |
/* | |||||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> | |||||
* and other copyright owners as documented in the project's IP log. | |||||
* | |||||
* This program and the accompanying materials are made available | |||||
* under the terms of the Eclipse Distribution License v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
package org.eclipse.jgit.treewalk; | |||||
import java.io.File; | |||||
import java.util.SortedSet; | |||||
import java.util.TreeSet; | |||||
import org.eclipse.jgit.lib.Config; | |||||
import org.eclipse.jgit.lib.ObjectReader; | |||||
import org.eclipse.jgit.lib.Repository; | |||||
import org.eclipse.jgit.util.FS; | |||||
/** | |||||
* A {@link FileTreeIterator} used in tests which allows to specify explicitly | |||||
* what will be returned by {@link #getEntryLastModified()}. This allows to | |||||
* write tests where certain files have to have the same modification time. | |||||
* <p> | |||||
* This iterator is configured by a list of strictly increasing long values | |||||
* t(0), t(1), ..., t(n). For each file with a modification between t(x) and | |||||
* t(x+1) [ t(x) <= time < t(x+1) ] this iterator will report t(x). For | |||||
* files with a modification time smaller t(0) a modification time of 0 is | |||||
* returned. For files with a modification time greater or equal t(n) t(n) will | |||||
* be returned. | |||||
* <p> | |||||
* This class was written especially to test racy-git problems | |||||
*/ | |||||
public class FileTreeIteratorWithTimeControl extends FileTreeIterator { | |||||
private TreeSet<Long> modTimes; | |||||
public FileTreeIteratorWithTimeControl(FileTreeIterator p, Repository repo, | |||||
TreeSet<Long> modTimes) { | |||||
super(p, repo.getWorkTree(), repo.getFS()); | |||||
this.modTimes = modTimes; | |||||
} | |||||
public FileTreeIteratorWithTimeControl(FileTreeIterator p, File f, FS fs, | |||||
TreeSet<Long> modTimes) { | |||||
super(p, f, fs); | |||||
this.modTimes = modTimes; | |||||
} | |||||
public FileTreeIteratorWithTimeControl(Repository repo, | |||||
TreeSet<Long> modTimes) { | |||||
super(repo); | |||||
this.modTimes = modTimes; | |||||
} | |||||
public FileTreeIteratorWithTimeControl(File f, FS fs, | |||||
TreeSet<Long> modTimes) { | |||||
super(f, fs, new Config().get(WorkingTreeOptions.KEY)); | |||||
this.modTimes = modTimes; | |||||
} | |||||
@Override | |||||
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader) { | |||||
return new FileTreeIteratorWithTimeControl(this, | |||||
((FileEntry) current()).getFile(), fs, modTimes); | |||||
} | |||||
@Override | |||||
public long getEntryLastModified() { | |||||
if (modTimes == null) | |||||
return 0; | |||||
Long cutOff = Long.valueOf(super.getEntryLastModified() + 1); | |||||
SortedSet<Long> head = modTimes.headSet(cutOff); | |||||
return head.isEmpty() ? 0 : head.last().longValue(); | |||||
} | |||||
} |
package org.eclipse.jgit.util; | package org.eclipse.jgit.util; | ||||
import static java.time.Instant.EPOCH; | |||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import org.eclipse.jgit.errors.CommandFailedException; | import org.eclipse.jgit.errors.CommandFailedException; | ||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
import org.eclipse.jgit.lib.RepositoryCache; | |||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Assume; | import org.junit.Assume; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
assertTrue(fs.exists(link)); | assertTrue(fs.exists(link)); | ||||
String targetName = fs.readSymLink(link); | String targetName = fs.readSymLink(link); | ||||
assertEquals("b", targetName); | assertEquals("b", targetName); | ||||
assertTrue(fs.lastModified(link) > 0); | |||||
assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0); | |||||
assertTrue(fs.exists(link)); | assertTrue(fs.exists(link)); | ||||
assertFalse(fs.canExecute(link)); | assertFalse(fs.canExecute(link)); | ||||
// The length of a symbolic link is a length of the target file path. | // The length of a symbolic link is a length of the target file path. | ||||
// Now create the link target | // Now create the link target | ||||
FileUtils.createNewFile(target); | FileUtils.createNewFile(target); | ||||
assertTrue(fs.exists(link)); | assertTrue(fs.exists(link)); | ||||
assertTrue(fs.lastModified(link) > 0); | |||||
assertTrue(fs.lastModified(target) > fs.lastModified(link)); | |||||
assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0); | |||||
assertTrue(fs.lastModifiedInstant(target) | |||||
.compareTo(fs.lastModifiedInstant(link)) > 0); | |||||
assertFalse(fs.canExecute(link)); | assertFalse(fs.canExecute(link)); | ||||
fs.setExecute(target, true); | fs.setExecute(target, true); | ||||
assertFalse(fs.canExecute(link)); | assertFalse(fs.canExecute(link)); | ||||
.ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH) | .ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH) | ||||
.withZone(ZoneId.systemDefault()); | .withZone(ZoneId.systemDefault()); | ||||
Path dir = Files.createTempDirectory("probe-filesystem"); | Path dir = Files.createTempDirectory("probe-filesystem"); | ||||
Duration resolution = FS.getFsTimerResolution(dir); | |||||
Duration resolution = FS.getFileStoreAttributes(dir) | |||||
.getFsTimestampResolution(); | |||||
long resolutionNs = resolution.toNanos(); | long resolutionNs = resolution.toNanos(); | ||||
assertTrue(resolutionNs > 0); | assertTrue(resolutionNs > 0); | ||||
for (int i = 0; i < 10; i++) { | for (int i = 0; i < 10; i++) { | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// bug 548682 | |||||
@Test | |||||
public void testRepoCacheRelativePathUnbornRepo() { | |||||
assertFalse(RepositoryCache.FileKey | |||||
.isGitRepository(new File("repo.git"), FS.DETECTED)); | |||||
} | |||||
} | } |
/* | |||||
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> | |||||
* and other copyright owners as documented in the project's IP log. | |||||
* | |||||
* This program and the accompanying materials are made available | |||||
* under the terms of the Eclipse Distribution License v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
package org.eclipse.jgit.util; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.junit.Assert.assertNotNull; | |||||
import static org.junit.Assert.assertNull; | |||||
import java.io.IOException; | |||||
import java.nio.file.Files; | |||||
import java.nio.file.Path; | |||||
import org.junit.After; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
public class SimpleLruCacheTest { | |||||
private Path trash; | |||||
private SimpleLruCache<String, String> cache; | |||||
@Before | |||||
public void setup() throws IOException { | |||||
trash = Files.createTempDirectory("tmp_"); | |||||
cache = new SimpleLruCache<>(100, 0.2f); | |||||
} | |||||
@Before | |||||
@After | |||||
public void tearDown() throws Exception { | |||||
FileUtils.delete(trash.toFile(), | |||||
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); | |||||
} | |||||
@Test | |||||
public void testPutGet() { | |||||
cache.put("a", "A"); | |||||
cache.put("z", "Z"); | |||||
assertEquals("A", cache.get("a")); | |||||
assertEquals("Z", cache.get("z")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void testPurgeFactorTooLarge() { | |||||
cache.configure(5, 1.01f); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void testPurgeFactorTooLarge2() { | |||||
cache.configure(5, 100); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void testPurgeFactorTooSmall() { | |||||
cache.configure(5, 0); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void testPurgeFactorTooSmall2() { | |||||
cache.configure(5, -100); | |||||
} | |||||
@Test | |||||
public void testGetMissing() { | |||||
assertEquals(null, cache.get("a")); | |||||
} | |||||
@Test | |||||
public void testPurge() { | |||||
for (int i = 0; i < 101; i++) { | |||||
cache.put("a" + i, "a" + i); | |||||
} | |||||
assertEquals(80, cache.size()); | |||||
assertNull(cache.get("a0")); | |||||
assertNull(cache.get("a20")); | |||||
assertNotNull(cache.get("a21")); | |||||
assertNotNull(cache.get("a99")); | |||||
} | |||||
@Test | |||||
public void testConfigure() { | |||||
for (int i = 0; i < 100; i++) { | |||||
cache.put("a" + i, "a" + i); | |||||
} | |||||
assertEquals(100, cache.size()); | |||||
cache.configure(10, 0.3f); | |||||
assertEquals(7, cache.size()); | |||||
assertNull(cache.get("a0")); | |||||
assertNull(cache.get("a92")); | |||||
assertNotNull(cache.get("a93")); | |||||
assertNotNull(cache.get("a99")); | |||||
} | |||||
} |
/* | |||||
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> | |||||
* and other copyright owners as documented in the project's IP log. | |||||
* | |||||
* This program and the accompanying materials are made available | |||||
* under the terms of the Eclipse Distribution License v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
package org.eclipse.jgit.util; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.junit.Assert.assertTrue; | |||||
import org.eclipse.jgit.util.Stats; | |||||
import org.junit.Test; | |||||
public class StatsTest { | |||||
@Test | |||||
public void testStatsTrivial() { | |||||
Stats s = new Stats(); | |||||
s.add(1); | |||||
s.add(1); | |||||
s.add(1); | |||||
assertEquals(3, s.count()); | |||||
assertEquals(1.0, s.min(), 1E-6); | |||||
assertEquals(1.0, s.max(), 1E-6); | |||||
assertEquals(1.0, s.avg(), 1E-6); | |||||
assertEquals(0.0, s.var(), 1E-6); | |||||
assertEquals(0.0, s.stddev(), 1E-6); | |||||
} | |||||
@Test | |||||
public void testStats() { | |||||
Stats s = new Stats(); | |||||
s.add(1); | |||||
s.add(2); | |||||
s.add(3); | |||||
s.add(4); | |||||
assertEquals(4, s.count()); | |||||
assertEquals(1.0, s.min(), 1E-6); | |||||
assertEquals(4.0, s.max(), 1E-6); | |||||
assertEquals(2.5, s.avg(), 1E-6); | |||||
assertEquals(1.666667, s.var(), 1E-6); | |||||
assertEquals(1.290994, s.stddev(), 1E-6); | |||||
} | |||||
@Test | |||||
/** | |||||
* see | |||||
* https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Example | |||||
*/ | |||||
public void testStatsCancellationExample1() { | |||||
Stats s = new Stats(); | |||||
s.add(1E8 + 4); | |||||
s.add(1E8 + 7); | |||||
s.add(1E8 + 13); | |||||
s.add(1E8 + 16); | |||||
assertEquals(4, s.count()); | |||||
assertEquals(1E8 + 4, s.min(), 1E-6); | |||||
assertEquals(1E8 + 16, s.max(), 1E-6); | |||||
assertEquals(1E8 + 10, s.avg(), 1E-6); | |||||
assertEquals(30, s.var(), 1E-6); | |||||
assertEquals(5.477226, s.stddev(), 1E-6); | |||||
} | |||||
@Test | |||||
/** | |||||
* see | |||||
* https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Example | |||||
*/ | |||||
public void testStatsCancellationExample2() { | |||||
Stats s = new Stats(); | |||||
s.add(1E9 + 4); | |||||
s.add(1E9 + 7); | |||||
s.add(1E9 + 13); | |||||
s.add(1E9 + 16); | |||||
assertEquals(4, s.count()); | |||||
assertEquals(1E9 + 4, s.min(), 1E-6); | |||||
assertEquals(1E9 + 16, s.max(), 1E-6); | |||||
assertEquals(1E9 + 10, s.avg(), 1E-6); | |||||
assertEquals(30, s.var(), 1E-6); | |||||
assertEquals(5.477226, s.stddev(), 1E-6); | |||||
} | |||||
@Test | |||||
public void testNoValues() { | |||||
Stats s = new Stats(); | |||||
assertTrue(Double.isNaN(s.var())); | |||||
assertTrue(Double.isNaN(s.stddev())); | |||||
assertTrue(Double.isNaN(s.avg())); | |||||
assertTrue(Double.isNaN(s.min())); | |||||
assertTrue(Double.isNaN(s.max())); | |||||
s.add(42.3); | |||||
assertTrue(Double.isNaN(s.var())); | |||||
assertTrue(Double.isNaN(s.stddev())); | |||||
assertEquals(42.3, s.avg(), 1E-6); | |||||
assertEquals(42.3, s.max(), 1E-6); | |||||
assertEquals(42.3, s.min(), 1E-6); | |||||
s.add(42.3); | |||||
assertEquals(0, s.var(), 1E-6); | |||||
assertEquals(0, s.stddev(), 1E-6); | |||||
} | |||||
} |
import org.eclipse.jgit.lib.PersonIdent; | import org.eclipse.jgit.lib.PersonIdent; | ||||
import org.eclipse.jgit.revplot.PlotCommit; | import org.eclipse.jgit.revplot.PlotCommit; | ||||
import org.eclipse.jgit.revplot.PlotCommitList; | import org.eclipse.jgit.revplot.PlotCommitList; | ||||
import org.eclipse.jgit.util.References; | |||||
/** | /** | ||||
* Draws a commit graph in a JTable. | * Draws a commit graph in a JTable. | ||||
} | } | ||||
PersonIdent authorFor(PlotCommit<SwingLane> c) { | PersonIdent authorFor(PlotCommit<SwingLane> c) { | ||||
if (c != lastCommit) { | |||||
if (!References.isSameObject(c, lastCommit)) { | |||||
lastCommit = c; | lastCommit = c; | ||||
lastAuthor = c.getAuthorIdent(); | lastAuthor = c.getAuthorIdent(); | ||||
} | } |
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<component id="org.eclipse.jgit" version="2"> | |||||
<resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry"> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="getLastModifiedInstant()"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="mightBeRacilyClean(Instant)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="setLastModified(Instant)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/lib/AnyObjectId.java" type="org.eclipse.jgit.lib.AnyObjectId"> | |||||
<filter id="1141899266"> | |||||
<message_arguments> | |||||
<message_argument value="5.4"/> | |||||
<message_argument value="5.5"/> | |||||
<message_argument value="isEqual(AnyObjectId, AnyObjectId)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants"> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="CONFIG_FILESYSTEM_SECTION"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="CONFIG_KEY_MIN_RACY_THRESHOLD"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="CONFIG_KEY_TIMESTAMP_RESOLUTION"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/storage/file/FileBasedConfig.java" type="org.eclipse.jgit.storage.file.FileBasedConfig"> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="load(boolean)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator"> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="getEntryLastModifiedInstant()"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator$Entry"> | |||||
<filter id="336695337"> | |||||
<message_arguments> | |||||
<message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry"/> | |||||
<message_argument value="getLastModifiedInstant()"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="getLastModifiedInstant()"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS"> | |||||
<filter id="338792546"> | |||||
<message_arguments> | |||||
<message_argument value="org.eclipse.jgit.util.FS"/> | |||||
<message_argument value="getFsTimerResolution(Path)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="getFileStoreAttributes(Path)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="lastModifiedInstant(File)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="lastModifiedInstant(Path)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="setAsyncFileStoreAttributes(boolean)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="setLastModified(Path, Instant)"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$Attributes"> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="getLastModifiedInstant()"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$FileStoreAttributes"> | |||||
<filter id="1142947843"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="FileStoreAttributes"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/util/References.java" type="org.eclipse.jgit.util.References"> | |||||
<filter id="1108344834"> | |||||
<message_arguments> | |||||
<message_argument value="5.4"/> | |||||
<message_argument value="5.5"/> | |||||
<message_argument value="org.eclipse.jgit.util.References"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/util/SimpleLruCache.java" type="org.eclipse.jgit.util.SimpleLruCache"> | |||||
<filter id="1109393411"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="org.eclipse.jgit.util.SimpleLruCache"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/util/Stats.java" type="org.eclipse.jgit.util.Stats"> | |||||
<filter id="1109393411"> | |||||
<message_arguments> | |||||
<message_argument value="5.1.9"/> | |||||
<message_argument value="org.eclipse.jgit.util.Stats"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
</component> |
<plugin> | <plugin> | ||||
<groupId>com.github.spotbugs</groupId> | <groupId>com.github.spotbugs</groupId> | ||||
<artifactId>spotbugs-maven-plugin</artifactId> | <artifactId>spotbugs-maven-plugin</artifactId> | ||||
<version>${spotbugs-maven-plugin-version}</version> | |||||
<configuration> | <configuration> | ||||
<excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile> | <excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile> | ||||
</configuration> | </configuration> |
cannotReadTree=Cannot read tree {0} | cannotReadTree=Cannot read tree {0} | ||||
cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD | cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD | ||||
cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating. | cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating. | ||||
cannotSaveConfig=Cannot save config file ''{0}'' | |||||
cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit. | cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit. | ||||
cannotStoreObjects=cannot store objects | cannotStoreObjects=cannot store objects | ||||
cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID | cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID | ||||
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0} | invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0} | ||||
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0} | invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0} | ||||
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} | invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} | ||||
invalidPurgeFactor=Invalid purgeFactor {0}, values have to be in range between 0 and 1 | |||||
invalidRedirectLocation=Invalid redirect location {0} -> {1} | invalidRedirectLocation=Invalid redirect location {0} -> {1} | ||||
invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}'' | invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}'' | ||||
invalidReflogRevision=Invalid reflog revision: {0} | invalidReflogRevision=Invalid reflog revision: {0} | ||||
pushNotPermitted=push not permitted | pushNotPermitted=push not permitted | ||||
pushOptionsNotSupported=Push options not supported; received {0} | pushOptionsNotSupported=Push options not supported; received {0} | ||||
rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry | rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry | ||||
readConfigFailed=Reading config file ''{0}'' failed | |||||
readerIsRequired=Reader is required | readerIsRequired=Reader is required | ||||
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} | readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} | ||||
readLastModifiedFailed=Reading lastModified of {0} failed | |||||
readTimedOut=Read timed out after {0} ms | readTimedOut=Read timed out after {0} ms | ||||
receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes. | receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes. | ||||
receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes. | receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes. | ||||
threadInterruptedWhileRunning="Current thread interrupted while running {0}" | threadInterruptedWhileRunning="Current thread interrupted while running {0}" | ||||
timeIsUncertain=Time is uncertain | timeIsUncertain=Time is uncertain | ||||
timerAlreadyTerminated=Timer already terminated | timerAlreadyTerminated=Timer already terminated | ||||
timeoutMeasureFsTimestampResolution=measuring filesystem timestamp resolution for ''{0}'' timed out, fall back to resolution of 2 seconds | |||||
tooManyCommands=Too many commands | tooManyCommands=Too many commands | ||||
tooManyFilters=Too many "filter" lines in request | tooManyFilters=Too many "filter" lines in request | ||||
tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)? | tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)? |
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.time.Instant; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.LinkedList; | import java.util.LinkedList; | ||||
if (GITLINK != mode) { | if (GITLINK != mode) { | ||||
entry.setLength(f.getEntryLength()); | entry.setLength(f.getEntryLength()); | ||||
entry.setLastModified(f.getEntryLastModified()); | |||||
entry.setLastModified(f.getEntryLastModifiedInstant()); | |||||
long len = f.getEntryContentLength(); | long len = f.getEntryContentLength(); | ||||
// We read and filter the content multiple times. | // We read and filter the content multiple times. | ||||
// f.getEntryContentLength() reads and filters the input and | // f.getEntryContentLength() reads and filters the input and | ||||
} | } | ||||
} else { | } else { | ||||
entry.setLength(0); | entry.setLength(0); | ||||
entry.setLastModified(0); | |||||
entry.setLastModified(Instant.ofEpochSecond(0)); | |||||
entry.setObjectId(f.getEntryObjectId()); | entry.setObjectId(f.getEntryObjectId()); | ||||
} | } | ||||
builder.add(entry); | builder.add(entry); |
import org.eclipse.jgit.api.errors.GitAPIException; | import org.eclipse.jgit.api.errors.GitAPIException; | ||||
import org.eclipse.jgit.api.errors.JGitInternalException; | import org.eclipse.jgit.api.errors.JGitInternalException; | ||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.lib.Constants; | |||||
import org.eclipse.jgit.lib.FileMode; | import org.eclipse.jgit.lib.FileMode; | ||||
import org.eclipse.jgit.lib.MutableObjectId; | import org.eclipse.jgit.lib.MutableObjectId; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.lib.ObjectLoader; | import org.eclipse.jgit.lib.ObjectLoader; | ||||
import org.eclipse.jgit.lib.ObjectReader; | import org.eclipse.jgit.lib.ObjectReader; | ||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | |||||
import org.eclipse.jgit.revwalk.RevObject; | |||||
import org.eclipse.jgit.revwalk.RevTree; | |||||
import org.eclipse.jgit.revwalk.RevWalk; | import org.eclipse.jgit.revwalk.RevWalk; | ||||
import org.eclipse.jgit.treewalk.TreeWalk; | import org.eclipse.jgit.treewalk.TreeWalk; | ||||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup; | import org.eclipse.jgit.treewalk.filter.PathFilterGroup; | ||||
MutableObjectId idBuf = new MutableObjectId(); | MutableObjectId idBuf = new MutableObjectId(); | ||||
ObjectReader reader = walk.getObjectReader(); | ObjectReader reader = walk.getObjectReader(); | ||||
walk.reset(rw.parseTree(tree)); | |||||
if (!paths.isEmpty()) | |||||
RevObject o = rw.peel(rw.parseAny(tree)); | |||||
walk.reset(getTree(o)); | |||||
if (!paths.isEmpty()) { | |||||
walk.setFilter(PathFilterGroup.createFromStrings(paths)); | walk.setFilter(PathFilterGroup.createFromStrings(paths)); | ||||
} | |||||
// Put base directory into archive | // Put base directory into archive | ||||
if (pfx.endsWith("/")) { //$NON-NLS-1$ | if (pfx.endsWith("/")) { //$NON-NLS-1$ | ||||
fmt.putEntry(outa, tree, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$ | |||||
fmt.putEntry(outa, o, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$ | |||||
FileMode.TREE, null); | FileMode.TREE, null); | ||||
} | } | ||||
if (walk.isSubtree()) | if (walk.isSubtree()) | ||||
walk.enterSubtree(); | walk.enterSubtree(); | ||||
if (mode == FileMode.GITLINK) | |||||
if (mode == FileMode.GITLINK) { | |||||
// TODO(jrn): Take a callback to recurse | // TODO(jrn): Take a callback to recurse | ||||
// into submodules. | // into submodules. | ||||
mode = FileMode.TREE; | mode = FileMode.TREE; | ||||
} | |||||
if (mode == FileMode.TREE) { | if (mode == FileMode.TREE) { | ||||
fmt.putEntry(outa, tree, name + "/", mode, null); //$NON-NLS-1$ | |||||
fmt.putEntry(outa, o, name + "/", mode, null); //$NON-NLS-1$ | |||||
continue; | continue; | ||||
} | } | ||||
walk.getObjectId(idBuf, 0); | walk.getObjectId(idBuf, 0); | ||||
fmt.putEntry(outa, tree, name, mode, reader.open(idBuf)); | |||||
fmt.putEntry(outa, o, name, mode, reader.open(idBuf)); | |||||
} | } | ||||
return out; | return out; | ||||
} finally { | } finally { | ||||
this.paths = Arrays.asList(paths); | this.paths = Arrays.asList(paths); | ||||
return this; | return this; | ||||
} | } | ||||
private RevTree getTree(RevObject o) | |||||
throws IncorrectObjectTypeException { | |||||
final RevTree t; | |||||
if (o instanceof RevCommit) { | |||||
t = ((RevCommit) o).getTree(); | |||||
} else if (!(o instanceof RevTree)) { | |||||
throw new IncorrectObjectTypeException(tree.toObjectId(), | |||||
Constants.TYPE_TREE); | |||||
} else { | |||||
t = (RevTree) o; | |||||
} | |||||
return t; | |||||
} | |||||
} | } |
merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$ | merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$ | ||||
cherryPickName }); | cherryPickName }); | ||||
if (merger.merge(newHead, srcCommit)) { | if (merger.merge(newHead, srcCommit)) { | ||||
if (AnyObjectId.equals(newHead.getTree().getId(), merger | |||||
.getResultTreeId())) | |||||
if (AnyObjectId.isEqual(newHead.getTree().getId(), | |||||
merger.getResultTreeId())) | |||||
continue; | continue; | ||||
DirCacheCheckout dco = new DirCacheCheckout(repo, | DirCacheCheckout dco = new DirCacheCheckout(repo, | ||||
newHead.getTree(), repo.lockDirCache(), | newHead.getTree(), repo.lockDirCache(), |
final DirCacheEntry dcEntry = new DirCacheEntry(path); | final DirCacheEntry dcEntry = new DirCacheEntry(path); | ||||
long entryLength = fTree.getEntryLength(); | long entryLength = fTree.getEntryLength(); | ||||
dcEntry.setLength(entryLength); | dcEntry.setLength(entryLength); | ||||
dcEntry.setLastModified(fTree.getEntryLastModified()); | |||||
dcEntry.setLastModified(fTree.getEntryLastModifiedInstant()); | |||||
dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); | dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); | ||||
boolean objectExists = (dcTree != null | boolean objectExists = (dcTree != null |
ObjectId headId = getHead().getObjectId(); | ObjectId headId = getHead().getObjectId(); | ||||
// getHead() checks for null | // getHead() checks for null | ||||
assert headId != null; | assert headId != null; | ||||
if (!AnyObjectId.equals(headId, newParents.get(0))) | |||||
if (!AnyObjectId.isEqual(headId, newParents.get(0))) | |||||
checkoutCommit(headId.getName(), newParents.get(0)); | checkoutCommit(headId.getName(), newParents.get(0)); | ||||
// Use the cherry-pick strategy if all non-first parents did not | // Use the cherry-pick strategy if all non-first parents did not |
DirCacheIterator.class); | DirCacheIterator.class); | ||||
if (dcIter != null && dcIter.idEqual(cIter)) { | if (dcIter != null && dcIter.idEqual(cIter)) { | ||||
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); | DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); | ||||
entry.setLastModified(indexEntry.getLastModified()); | |||||
entry.setLastModified(indexEntry.getLastModifiedInstant()); | |||||
entry.setLength(indexEntry.getLength()); | entry.setLength(indexEntry.getLength()); | ||||
} | } | ||||
+ "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$ | + "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$ | ||||
+ ".\n"; //$NON-NLS-1$ | + ".\n"; //$NON-NLS-1$ | ||||
if (merger.merge(headCommit, srcParent)) { | if (merger.merge(headCommit, srcParent)) { | ||||
if (AnyObjectId.equals(headCommit.getTree().getId(), merger | |||||
.getResultTreeId())) | |||||
if (AnyObjectId.isEqual(headCommit.getTree().getId(), | |||||
merger.getResultTreeId())) | |||||
continue; | continue; | ||||
DirCacheCheckout dco = new DirCacheCheckout(repo, | DirCacheCheckout dco = new DirCacheCheckout(repo, | ||||
headCommit.getTree(), repo.lockDirCache(), | headCommit.getTree(), repo.lockDirCache(), |
DirCacheIterator.class); | DirCacheIterator.class); | ||||
if (dcIter != null && dcIter.idEqual(cIter)) { | if (dcIter != null && dcIter.idEqual(cIter)) { | ||||
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); | DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); | ||||
entry.setLastModified(indexEntry.getLastModified()); | |||||
entry.setLastModified(indexEntry.getLastModifiedInstant()); | |||||
entry.setLength(indexEntry.getLength()); | entry.setLength(indexEntry.getLength()); | ||||
} | } | ||||
final DirCacheEntry entry = new DirCacheEntry( | final DirCacheEntry entry = new DirCacheEntry( | ||||
treeWalk.getRawPath()); | treeWalk.getRawPath()); | ||||
entry.setLength(wtIter.getEntryLength()); | entry.setLength(wtIter.getEntryLength()); | ||||
entry.setLastModified(wtIter.getEntryLastModified()); | |||||
entry.setLastModified( | |||||
wtIter.getEntryLastModifiedInstant()); | |||||
entry.setFileMode(wtIter.getEntryFileMode()); | entry.setFileMode(wtIter.getEntryFileMode()); | ||||
long contentLength = wtIter.getEntryContentLength(); | long contentLength = wtIter.getEntryContentLength(); | ||||
try (InputStream in = wtIter.openEntryStream()) { | try (InputStream in = wtIter.openEntryStream()) { |
import java.security.DigestOutputStream; | import java.security.DigestOutputStream; | ||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Instant; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Comparator; | import java.util.Comparator; | ||||
throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries); | throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries); | ||||
snapshot = FileSnapshot.save(liveFile); | snapshot = FileSnapshot.save(liveFile); | ||||
int smudge_s = (int) (snapshot.lastModified() / 1000); | |||||
int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; | |||||
Instant smudge = snapshot.lastModifiedInstant(); | |||||
// Load the individual file entries. | // Load the individual file entries. | ||||
// | // | ||||
sortedEntries = new DirCacheEntry[entryCnt]; | sortedEntries = new DirCacheEntry[entryCnt]; | ||||
final MutableInteger infoAt = new MutableInteger(); | final MutableInteger infoAt = new MutableInteger(); | ||||
for (int i = 0; i < entryCnt; i++) | |||||
sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge_s, smudge_ns); | |||||
for (int i = 0; i < entryCnt; i++) { | |||||
sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge); | |||||
} | |||||
// After the file entries are index extensions, and then a footer. | // After the file entries are index extensions, and then a footer. | ||||
// | // | ||||
// so we use the current timestamp as a approximation. | // so we use the current timestamp as a approximation. | ||||
myLock.createCommitSnapshot(); | myLock.createCommitSnapshot(); | ||||
snapshot = myLock.getCommitSnapshot(); | snapshot = myLock.getCommitSnapshot(); | ||||
smudge_s = (int) (snapshot.lastModified() / 1000); | |||||
smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; | |||||
smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond()); | |||||
smudge_ns = snapshot.lastModifiedInstant().getNano(); | |||||
} else { | } else { | ||||
// Used in unit tests only | // Used in unit tests only | ||||
smudge_ns = 0; | smudge_ns = 0; | ||||
DirCacheEntry entry = iIter.getDirCacheEntry(); | DirCacheEntry entry = iIter.getDirCacheEntry(); | ||||
if (entry.isSmudged() && iIter.idEqual(fIter)) { | if (entry.isSmudged() && iIter.idEqual(fIter)) { | ||||
entry.setLength(fIter.getEntryLength()); | entry.setLength(fIter.getEntryLength()); | ||||
entry.setLastModified(fIter.getEntryLastModified()); | |||||
entry.setLastModified(fIter.getEntryLastModifiedInstant()); | |||||
} | } | ||||
} | } | ||||
} | } |
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import java.nio.file.StandardCopyOption; | import java.nio.file.StandardCopyOption; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Instant; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
// update the timestamp of the index with the one from the | // update the timestamp of the index with the one from the | ||||
// file if not set, as we are sure to be in sync here. | // file if not set, as we are sure to be in sync here. | ||||
DirCacheEntry entry = i.getDirCacheEntry(); | DirCacheEntry entry = i.getDirCacheEntry(); | ||||
if (entry.getLastModified() == 0) | |||||
entry.setLastModified(f.getEntryLastModified()); | |||||
Instant mtime = entry.getLastModifiedInstant(); | |||||
if (mtime == null || mtime.equals(Instant.EPOCH)) { | |||||
entry.setLastModified(f.getEntryLastModifiedInstant()); | |||||
} | |||||
keep(entry, f); | keep(entry, f); | ||||
} | } | ||||
} else | } else | ||||
File gitlinkDir = new File(repo.getWorkTree(), path); | File gitlinkDir = new File(repo.getWorkTree(), path); | ||||
FileUtils.mkdirs(gitlinkDir, true); | FileUtils.mkdirs(gitlinkDir, true); | ||||
FS fs = repo.getFS(); | FS fs = repo.getFS(); | ||||
entry.setLastModified(fs.lastModified(gitlinkDir)); | |||||
entry.setLastModified(fs.lastModifiedInstant(gitlinkDir)); | |||||
} | } | ||||
private static ArrayList<String> filterOut(ArrayList<String> strings, | private static ArrayList<String> filterOut(ArrayList<String> strings, | ||||
} | } | ||||
fs.createSymLink(f, target); | fs.createSymLink(f, target); | ||||
entry.setLength(bytes.length); | entry.setLength(bytes.length); | ||||
entry.setLastModified(fs.lastModified(f)); | |||||
entry.setLastModified(fs.lastModifiedInstant(f)); | |||||
return; | return; | ||||
} | } | ||||
FileUtils.delete(tmpFile); | FileUtils.delete(tmpFile); | ||||
} | } | ||||
} | } | ||||
entry.setLastModified(fs.lastModified(f)); | |||||
entry.setLastModified(fs.lastModifiedInstant(f)); | |||||
} | } | ||||
// Run an external filter command | // Run an external filter command |
import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Instant; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import org.eclipse.jgit.errors.CorruptObjectException; | import org.eclipse.jgit.errors.CorruptObjectException; | ||||
private byte inCoreFlags; | private byte inCoreFlags; | ||||
DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, | DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, | ||||
final InputStream in, final MessageDigest md, final int smudge_s, | |||||
final int smudge_ns) throws IOException { | |||||
final InputStream in, final MessageDigest md, final Instant smudge) | |||||
throws IOException { | |||||
info = sharedInfo; | info = sharedInfo; | ||||
infoOffset = infoAt.value; | infoOffset = infoAt.value; | ||||
md.update(nullpad, 0, padLen); | md.update(nullpad, 0, padLen); | ||||
} | } | ||||
if (mightBeRacilyClean(smudge_s, smudge_ns)) | |||||
if (mightBeRacilyClean(smudge)) { | |||||
smudgeRacilyClean(); | smudgeRacilyClean(); | ||||
} | |||||
} | } | ||||
/** | /** | ||||
* @param smudge_ns | * @param smudge_ns | ||||
* nanoseconds component of the index's last modified time. | * nanoseconds component of the index's last modified time. | ||||
* @return true if extra careful checks should be used. | * @return true if extra careful checks should be used. | ||||
* @deprecated use {@link #mightBeRacilyClean(Instant)} instead | |||||
*/ | */ | ||||
@Deprecated | |||||
public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) { | public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) { | ||||
return mightBeRacilyClean(Instant.ofEpochSecond(smudge_s, smudge_ns)); | |||||
} | |||||
/** | |||||
* Is it possible for this entry to be accidentally assumed clean? | |||||
* <p> | |||||
* The "racy git" problem happens when a work file can be updated faster | |||||
* than the filesystem records file modification timestamps. It is possible | |||||
* for an application to edit a work file, update the index, then edit it | |||||
* again before the filesystem will give the work file a new modification | |||||
* timestamp. This method tests to see if file was written out at the same | |||||
* time as the index. | |||||
* | |||||
* @param smudge | |||||
* index's last modified time. | |||||
* @return true if extra careful checks should be used. | |||||
* @since 5.1.9 | |||||
*/ | |||||
public final boolean mightBeRacilyClean(Instant smudge) { | |||||
// If the index has a modification time then it came from disk | // If the index has a modification time then it came from disk | ||||
// and was not generated from scratch in memory. In such cases | // and was not generated from scratch in memory. In such cases | ||||
// the entry is 'racily clean' if the entry's cached modification | // the entry is 'racily clean' if the entry's cached modification | ||||
// | // | ||||
final int base = infoOffset + P_MTIME; | final int base = infoOffset + P_MTIME; | ||||
final int mtime = NB.decodeInt32(info, base); | final int mtime = NB.decodeInt32(info, base); | ||||
if (smudge_s == mtime) | |||||
return smudge_ns <= NB.decodeInt32(info, base + 4); | |||||
if (smudge.getEpochSecond() == mtime) { | |||||
return smudge.getNano() <= NB.decodeInt32(info, base + 4); | |||||
} | |||||
return false; | return false; | ||||
} | } | ||||
*/ | */ | ||||
public void setAssumeValid(boolean assume) { | public void setAssumeValid(boolean assume) { | ||||
if (assume) | if (assume) | ||||
info[infoOffset + P_FLAGS] |= ASSUME_VALID; | |||||
info[infoOffset + P_FLAGS] |= (byte) ASSUME_VALID; | |||||
else | else | ||||
info[infoOffset + P_FLAGS] &= ~ASSUME_VALID; | |||||
info[infoOffset + P_FLAGS] &= (byte) ~ASSUME_VALID; | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
public void setUpdateNeeded(boolean updateNeeded) { | public void setUpdateNeeded(boolean updateNeeded) { | ||||
if (updateNeeded) | if (updateNeeded) | ||||
inCoreFlags |= UPDATE_NEEDED; | |||||
inCoreFlags |= (byte) UPDATE_NEEDED; | |||||
else | else | ||||
inCoreFlags &= ~UPDATE_NEEDED; | |||||
inCoreFlags &= (byte) ~UPDATE_NEEDED; | |||||
} | } | ||||
/** | /** | ||||
* | * | ||||
* @return last modification time of this file, in milliseconds since the | * @return last modification time of this file, in milliseconds since the | ||||
* Java epoch (midnight Jan 1, 1970 UTC). | * Java epoch (midnight Jan 1, 1970 UTC). | ||||
* @deprecated use {@link #getLastModifiedInstant()} instead | |||||
*/ | */ | ||||
@Deprecated | |||||
public long getLastModified() { | public long getLastModified() { | ||||
return decodeTS(P_MTIME); | return decodeTS(P_MTIME); | ||||
} | } | ||||
/** | |||||
* Get the cached last modification date of this file. | |||||
* <p> | |||||
* One of the indicators that the file has been modified by an application | |||||
* changing the working tree is if the last modification time for the file | |||||
* differs from the time stored in this entry. | |||||
* | |||||
* @return last modification time of this file. | |||||
* @since 5.1.9 | |||||
*/ | |||||
public Instant getLastModifiedInstant() { | |||||
return decodeTSInstant(P_MTIME); | |||||
} | |||||
/** | /** | ||||
* Set the cached last modification date of this file, using milliseconds. | * Set the cached last modification date of this file, using milliseconds. | ||||
* | * | ||||
* @param when | * @param when | ||||
* new cached modification date of the file, in milliseconds. | * new cached modification date of the file, in milliseconds. | ||||
* @deprecated use {@link #setLastModified(Instant)} instead | |||||
*/ | */ | ||||
@Deprecated | |||||
public void setLastModified(long when) { | public void setLastModified(long when) { | ||||
encodeTS(P_MTIME, when); | encodeTS(P_MTIME, when); | ||||
} | } | ||||
/** | |||||
* Set the cached last modification date of this file. | |||||
* | |||||
* @param when | |||||
* new cached modification date of the file. | |||||
* @since 5.1.9 | |||||
*/ | |||||
public void setLastModified(Instant when) { | |||||
encodeTS(P_MTIME, when); | |||||
} | |||||
/** | /** | ||||
* Get the cached size (mod 4 GB) (in bytes) of this file. | * Get the cached size (mod 4 GB) (in bytes) of this file. | ||||
* <p> | * <p> | ||||
@SuppressWarnings("nls") | @SuppressWarnings("nls") | ||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
return getFileMode() + " " + getLength() + " " + getLastModified() | |||||
return getFileMode() + " " + getLength() + " " | |||||
+ getLastModifiedInstant() | |||||
+ " " + getObjectId() + " " + getStage() + " " | + " " + getObjectId() + " " + getStage() + " " | ||||
+ getPathString() + "\n"; | + getPathString() + "\n"; | ||||
} | } | ||||
return 1000L * sec + ms; | return 1000L * sec + ms; | ||||
} | } | ||||
private Instant decodeTSInstant(int pIdx) { | |||||
final int base = infoOffset + pIdx; | |||||
final int sec = NB.decodeInt32(info, base); | |||||
final int nano = NB.decodeInt32(info, base + 4); | |||||
return Instant.ofEpochSecond(sec, nano); | |||||
} | |||||
private void encodeTS(int pIdx, long when) { | private void encodeTS(int pIdx, long when) { | ||||
final int base = infoOffset + pIdx; | final int base = infoOffset + pIdx; | ||||
NB.encodeInt32(info, base, (int) (when / 1000)); | NB.encodeInt32(info, base, (int) (when / 1000)); | ||||
NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); | NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); | ||||
} | } | ||||
private void encodeTS(int pIdx, Instant when) { | |||||
final int base = infoOffset + pIdx; | |||||
NB.encodeInt32(info, base, (int) when.getEpochSecond()); | |||||
NB.encodeInt32(info, base + 4, when.getNano()); | |||||
} | |||||
private int getExtendedFlags() { | private int getExtendedFlags() { | ||||
if (isExtended()) | if (isExtended()) | ||||
return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; | return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; |
private static final long serialVersionUID = 1L; | private static final long serialVersionUID = 1L; | ||||
/** | /** | ||||
* Construct and IncorrectObjectTypeException for the specified object id. | |||||
* Construct an IncorrectObjectTypeException for the specified object id. | |||||
* | * | ||||
* Provide the type to make it easier to track down the problem. | * Provide the type to make it easier to track down the problem. | ||||
* | * | ||||
} | } | ||||
/** | /** | ||||
* Construct and IncorrectObjectTypeException for the specified object id. | |||||
* Construct an IncorrectObjectTypeException for the specified object id. | |||||
* | * | ||||
* Provide the type to make it easier to track down the problem. | * Provide the type to make it easier to track down the problem. | ||||
* | * |
/***/ public String cannotReadTree; | /***/ public String cannotReadTree; | ||||
/***/ public String cannotRebaseWithoutCurrentHead; | /***/ public String cannotRebaseWithoutCurrentHead; | ||||
/***/ public String cannotResolveLocalTrackingRefForUpdating; | /***/ public String cannotResolveLocalTrackingRefForUpdating; | ||||
/***/ public String cannotSaveConfig; | |||||
/***/ public String cannotSquashFixupWithoutPreviousCommit; | /***/ public String cannotSquashFixupWithoutPreviousCommit; | ||||
/***/ public String cannotStoreObjects; | /***/ public String cannotStoreObjects; | ||||
/***/ public String cannotResolveUniquelyAbbrevObjectId; | /***/ public String cannotResolveUniquelyAbbrevObjectId; | ||||
/***/ public String invalidPathPeriodAtEndWindows; | /***/ public String invalidPathPeriodAtEndWindows; | ||||
/***/ public String invalidPathSpaceAtEndWindows; | /***/ public String invalidPathSpaceAtEndWindows; | ||||
/***/ public String invalidPathReservedOnWindows; | /***/ public String invalidPathReservedOnWindows; | ||||
/***/ public String invalidPurgeFactor; | |||||
/***/ public String invalidRedirectLocation; | /***/ public String invalidRedirectLocation; | ||||
/***/ public String invalidRefAdvertisementLine; | /***/ public String invalidRefAdvertisementLine; | ||||
/***/ public String invalidReflogRevision; | /***/ public String invalidReflogRevision; | ||||
/***/ public String pushNotPermitted; | /***/ public String pushNotPermitted; | ||||
/***/ public String pushOptionsNotSupported; | /***/ public String pushOptionsNotSupported; | ||||
/***/ public String rawLogMessageDoesNotParseAsLogEntry; | /***/ public String rawLogMessageDoesNotParseAsLogEntry; | ||||
/***/ public String readConfigFailed; | |||||
/***/ public String readerIsRequired; | /***/ public String readerIsRequired; | ||||
/***/ public String readingObjectsFromLocalRepositoryFailed; | /***/ public String readingObjectsFromLocalRepositoryFailed; | ||||
/***/ public String readLastModifiedFailed; | |||||
/***/ public String readTimedOut; | /***/ public String readTimedOut; | ||||
/***/ public String receivePackObjectTooLarge1; | /***/ public String receivePackObjectTooLarge1; | ||||
/***/ public String receivePackObjectTooLarge2; | /***/ public String receivePackObjectTooLarge2; | ||||
/***/ public String tagAlreadyExists; | /***/ public String tagAlreadyExists; | ||||
/***/ public String tagNameInvalid; | /***/ public String tagNameInvalid; | ||||
/***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported; | /***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported; | ||||
/***/ public String timeoutMeasureFsTimestampResolution; | |||||
/***/ public String transactionAborted; | /***/ public String transactionAborted; | ||||
/***/ public String theFactoryMustNotBeNull; | /***/ public String theFactoryMustNotBeNull; | ||||
/***/ public String threadInterruptedWhileRunning; | /***/ public String threadInterruptedWhileRunning; |
} | } | ||||
private static boolean equals(@Nullable ObjectId a, LogIndex b) { | private static boolean equals(@Nullable ObjectId a, LogIndex b) { | ||||
return a != null && b != null && AnyObjectId.equals(a, b); | |||||
return a != null && b != null && AnyObjectId.isEqual(a, b); | |||||
} | } | ||||
/** | /** | ||||
Ref oldRef = remote.remove(name); | Ref oldRef = remote.remove(name); | ||||
ObjectId oldId = getId(oldRef); | ObjectId oldId = getId(oldRef); | ||||
ObjectId newId = tw.getObjectId(0); | ObjectId newId = tw.getObjectId(0); | ||||
if (!AnyObjectId.equals(oldId, newId)) { | |||||
if (!AnyObjectId.isEqual(oldId, newId)) { | |||||
delta.add(new ReceiveCommand(oldId, newId, name)); | delta.add(new ReceiveCommand(oldId, newId, name)); | ||||
} | } | ||||
} | } |
return UNKNOWN; | return UNKNOWN; | ||||
} | } | ||||
if (AnyObjectId.equals(remoteId, ObjectId.zeroId())) { | |||||
if (AnyObjectId.isEqual(remoteId, ObjectId.zeroId())) { | |||||
// Replica does not have the txnAccepted reference. | // Replica does not have the txnAccepted reference. | ||||
return LAGGING; | return LAGGING; | ||||
} | } |
private static boolean isExpectedValue(Map<String, Ref> adv, | private static boolean isExpectedValue(Map<String, Ref> adv, | ||||
RemoteRefUpdate u) { | RemoteRefUpdate u) { | ||||
Ref r = adv.get(u.getRemoteName()); | Ref r = adv.get(u.getRemoteName()); | ||||
if (!AnyObjectId.equals(getId(r), u.getExpectedOldObjectId())) { | |||||
if (!AnyObjectId.isEqual(getId(r), u.getExpectedOldObjectId())) { | |||||
((RemoteCommand) u).cmd.setResult(LOCK_FAILURE); | ((RemoteCommand) u).cmd.setResult(LOCK_FAILURE); | ||||
return false; | return false; | ||||
} | } |
try (RevWalk rw = new RevWalk(git); | try (RevWalk rw = new RevWalk(git); | ||||
TreeWalk tw = new TreeWalk(rw.getObjectReader()); | TreeWalk tw = new TreeWalk(rw.getObjectReader()); | ||||
ObjectInserter ins = git.newObjectInserter()) { | ObjectInserter ins = git.newObjectInserter()) { | ||||
if (AnyObjectId.equals(oldTree, ObjectId.zeroId())) { | |||||
if (AnyObjectId.isEqual(oldTree, ObjectId.zeroId())) { | |||||
tw.addTree(new EmptyTreeIterator()); | tw.addTree(new EmptyTreeIterator()); | ||||
} else { | } else { | ||||
tw.addTree(rw.parseTree(oldTree)); | tw.addTree(rw.parseTree(oldTree)); |
private int objectsBefore() { | private int objectsBefore() { | ||||
int cnt = 0; | int cnt = 0; | ||||
for (DfsPackFile p : packsBefore) | for (DfsPackFile p : packsBefore) | ||||
cnt += p.getPackDescription().getObjectCount(); | |||||
cnt += (int) p.getPackDescription().getObjectCount(); | |||||
return cnt; | return cnt; | ||||
} | } | ||||
buf[len++] = (byte) ((typeCode << 4) | (sz & 15)); | buf[len++] = (byte) ((typeCode << 4) | (sz & 15)); | ||||
sz >>>= 4; | sz >>>= 4; | ||||
while (sz > 0) { | while (sz > 0) { | ||||
buf[len - 1] |= 0x80; | |||||
buf[len - 1] |= (byte) 0x80; | |||||
buf[len++] = (byte) (sz & 0x7f); | buf[len++] = (byte) (sz & 0x7f); | ||||
sz >>>= 7; | sz >>>= 7; | ||||
} | } |
private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) { | private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) { | ||||
if (ref == null) { | if (ref == null) { | ||||
return AnyObjectId.equals(ObjectId.zeroId(), cmd.getOldId()) | |||||
return AnyObjectId.isEqual(ObjectId.zeroId(), cmd.getOldId()) | |||||
&& cmd.getOldSymref() == null; | && cmd.getOldSymref() == null; | ||||
} else if (ref.isSymbolic()) { | } else if (ref.isSymbolic()) { | ||||
return ref.getTarget().getName().equals(cmd.getOldSymref()); | return ref.getTarget().getName().equals(cmd.getOldSymref()); | ||||
String name = cmd.getRefName(); | String name = cmd.getRefName(); | ||||
ObjectId newId = cmd.getNewId(); | ObjectId newId = cmd.getNewId(); | ||||
String newSymref = cmd.getNewSymref(); | String newSymref = cmd.getNewSymref(); | ||||
if (AnyObjectId.equals(ObjectId.zeroId(), newId) | |||||
if (AnyObjectId.isEqual(ObjectId.zeroId(), newId) | |||||
&& newSymref == null) { | && newSymref == null) { | ||||
refs.add(new ObjectIdRef.Unpeeled(NEW, name, null)); | refs.add(new ObjectIdRef.Unpeeled(NEW, name, null)); | ||||
continue; | continue; |
package org.eclipse.jgit.internal.storage.file; | package org.eclipse.jgit.internal.storage.file; | ||||
import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_FILESTORE_ATTRIBUTES; | |||||
import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_TIMESTAMP_RESOLUTION; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.file.attribute.BasicFileAttributes; | import java.nio.file.attribute.BasicFileAttributes; | ||||
import java.text.DateFormat; | |||||
import java.text.SimpleDateFormat; | |||||
import java.time.Duration; | import java.time.Duration; | ||||
import java.util.Date; | |||||
import java.time.Instant; | |||||
import java.time.ZoneId; | |||||
import java.time.format.DateTimeFormatter; | |||||
import java.util.Locale; | import java.util.Locale; | ||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
import org.eclipse.jgit.annotations.NonNull; | import org.eclipse.jgit.annotations.NonNull; | ||||
import org.eclipse.jgit.util.FS; | import org.eclipse.jgit.util.FS; | ||||
import org.eclipse.jgit.util.FS.FileStoreAttributes; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
/** | /** | ||||
* Caches when a file was last read, making it possible to detect future edits. | * Caches when a file was last read, making it possible to detect future edits. | ||||
* file is less than 3 seconds ago. | * file is less than 3 seconds ago. | ||||
*/ | */ | ||||
public class FileSnapshot { | public class FileSnapshot { | ||||
private static final Logger LOG = LoggerFactory | |||||
.getLogger(FileSnapshot.class); | |||||
/** | /** | ||||
* An unknown file size. | * An unknown file size. | ||||
* | * | ||||
*/ | */ | ||||
public static final long UNKNOWN_SIZE = -1; | public static final long UNKNOWN_SIZE = -1; | ||||
private static final Instant UNKNOWN_TIME = Instant.ofEpochMilli(-1); | |||||
private static final Object MISSING_FILEKEY = new Object(); | private static final Object MISSING_FILEKEY = new Object(); | ||||
private static final DateTimeFormatter dateFmt = DateTimeFormatter | |||||
.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$ | |||||
.withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault()); | |||||
/** | /** | ||||
* A FileSnapshot that is considered to always be modified. | * A FileSnapshot that is considered to always be modified. | ||||
* <p> | * <p> | ||||
* file, but only after {@link #isModified(File)} gets invoked. The returned | * file, but only after {@link #isModified(File)} gets invoked. The returned | ||||
* snapshot contains only invalid status information. | * snapshot contains only invalid status information. | ||||
*/ | */ | ||||
public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, | |||||
UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); | |||||
public static final FileSnapshot DIRTY = new FileSnapshot(UNKNOWN_TIME, | |||||
UNKNOWN_TIME, UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); | |||||
/** | /** | ||||
* A FileSnapshot that is clean if the file does not exist. | * A FileSnapshot that is clean if the file does not exist. | ||||
* file to be clean. {@link #isModified(File)} will return false if the file | * file to be clean. {@link #isModified(File)} will return false if the file | ||||
* path does not exist. | * path does not exist. | ||||
*/ | */ | ||||
public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0, | |||||
Duration.ZERO, MISSING_FILEKEY) { | |||||
public static final FileSnapshot MISSING_FILE = new FileSnapshot( | |||||
Instant.EPOCH, Instant.EPOCH, 0, Duration.ZERO, MISSING_FILEKEY) { | |||||
@Override | @Override | ||||
public boolean isModified(File path) { | public boolean isModified(File path) { | ||||
return FS.DETECTED.exists(path); | return FS.DETECTED.exists(path); | ||||
return new FileSnapshot(path); | return new FileSnapshot(path); | ||||
} | } | ||||
/** | |||||
* Record a snapshot for a specific file path without using config file to | |||||
* get filesystem timestamp resolution. | |||||
* <p> | |||||
* This method should be invoked before the file is accessed. It is used by | |||||
* FileBasedConfig to avoid endless recursion. | |||||
* | |||||
* @param path | |||||
* the path to later remember. The path's current status | |||||
* information is saved. | |||||
* @return the snapshot. | |||||
*/ | |||||
public static FileSnapshot saveNoConfig(File path) { | |||||
return new FileSnapshot(path, false); | |||||
} | |||||
private static Object getFileKey(BasicFileAttributes fileAttributes) { | private static Object getFileKey(BasicFileAttributes fileAttributes) { | ||||
Object fileKey = fileAttributes.fileKey(); | Object fileKey = fileAttributes.fileKey(); | ||||
return fileKey == null ? MISSING_FILEKEY : fileKey; | return fileKey == null ? MISSING_FILEKEY : fileKey; | ||||
* @param modified | * @param modified | ||||
* the last modification time of the file | * the last modification time of the file | ||||
* @return the snapshot. | * @return the snapshot. | ||||
* @deprecated use {@link #save(Instant)} instead. | |||||
*/ | */ | ||||
@Deprecated | |||||
public static FileSnapshot save(long modified) { | public static FileSnapshot save(long modified) { | ||||
final long read = System.currentTimeMillis(); | |||||
return new FileSnapshot(read, modified, -1, Duration.ZERO, | |||||
MISSING_FILEKEY); | |||||
final Instant read = Instant.now(); | |||||
return new FileSnapshot(read, Instant.ofEpochMilli(modified), | |||||
UNKNOWN_SIZE, FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY); | |||||
} | |||||
/** | |||||
* Record a snapshot for a file for which the last modification time is | |||||
* already known. | |||||
* <p> | |||||
* This method should be invoked before the file is accessed. | |||||
* <p> | |||||
* Note that this method cannot rely on measuring file timestamp resolution | |||||
* to avoid racy git issues caused by finite file timestamp resolution since | |||||
* it's unknown in which filesystem the file is located. Hence the worst | |||||
* case fallback for timestamp resolution is used. | |||||
* | |||||
* @param modified | |||||
* the last modification time of the file | |||||
* @return the snapshot. | |||||
*/ | |||||
public static FileSnapshot save(Instant modified) { | |||||
final Instant read = Instant.now(); | |||||
return new FileSnapshot(read, modified, UNKNOWN_SIZE, | |||||
FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY); | |||||
} | } | ||||
/** Last observed modification time of the path. */ | /** Last observed modification time of the path. */ | ||||
private final long lastModified; | |||||
private final Instant lastModified; | |||||
/** Last wall-clock time the path was read. */ | /** Last wall-clock time the path was read. */ | ||||
private volatile long lastRead; | |||||
private volatile Instant lastRead; | |||||
/** True once {@link #lastRead} is far later than {@link #lastModified}. */ | /** True once {@link #lastRead} is far later than {@link #lastModified}. */ | ||||
private boolean cannotBeRacilyClean; | private boolean cannotBeRacilyClean; | ||||
* When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */ | * When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */ | ||||
private final long size; | private final long size; | ||||
/** measured filesystem timestamp resolution */ | |||||
private Duration fsTimestampResolution; | |||||
/** measured FileStore attributes */ | |||||
private FileStoreAttributes fileStoreAttributeCache; | |||||
/** | /** | ||||
* Object that uniquely identifies the given file, or {@code | * Object that uniquely identifies the given file, or {@code | ||||
*/ | */ | ||||
private final Object fileKey; | private final Object fileKey; | ||||
private final File file; | |||||
/** | /** | ||||
* Record a snapshot for a specific file path. | * Record a snapshot for a specific file path. | ||||
* <p> | * <p> | ||||
* This method should be invoked before the file is accessed. | * This method should be invoked before the file is accessed. | ||||
* | * | ||||
* @param path | |||||
* the path to later remember. The path's current status | |||||
* @param file | |||||
* the path to remember meta data for. The path's current status | |||||
* information is saved. | * information is saved. | ||||
*/ | */ | ||||
protected FileSnapshot(File path) { | |||||
this.lastRead = System.currentTimeMillis(); | |||||
this.fsTimestampResolution = FS | |||||
.getFsTimerResolution(path.toPath().getParent()); | |||||
protected FileSnapshot(File file) { | |||||
this(file, true); | |||||
} | |||||
/** | |||||
* Record a snapshot for a specific file path. | |||||
* <p> | |||||
* This method should be invoked before the file is accessed. | |||||
* | |||||
* @param file | |||||
* the path to remember meta data for. The path's current status | |||||
* information is saved. | |||||
* @param useConfig | |||||
* if {@code true} read filesystem time resolution from | |||||
* configuration file otherwise use fallback resolution | |||||
*/ | |||||
protected FileSnapshot(File file, boolean useConfig) { | |||||
this.file = file; | |||||
this.lastRead = Instant.now(); | |||||
this.fileStoreAttributeCache = useConfig | |||||
? FS.getFileStoreAttributes(file.toPath().getParent()) | |||||
: FALLBACK_FILESTORE_ATTRIBUTES; | |||||
BasicFileAttributes fileAttributes = null; | BasicFileAttributes fileAttributes = null; | ||||
try { | try { | ||||
fileAttributes = FS.DETECTED.fileAttributes(path); | |||||
fileAttributes = FS.DETECTED.fileAttributes(file); | |||||
} catch (IOException e) { | } catch (IOException e) { | ||||
this.lastModified = path.lastModified(); | |||||
this.size = path.length(); | |||||
this.lastModified = Instant.ofEpochMilli(file.lastModified()); | |||||
this.size = file.length(); | |||||
this.fileKey = MISSING_FILEKEY; | this.fileKey = MISSING_FILEKEY; | ||||
return; | return; | ||||
} | } | ||||
this.lastModified = fileAttributes.lastModifiedTime().toMillis(); | |||||
this.lastModified = fileAttributes.lastModifiedTime().toInstant(); | |||||
this.size = fileAttributes.size(); | this.size = fileAttributes.size(); | ||||
this.fileKey = getFileKey(fileAttributes); | this.fileKey = getFileKey(fileAttributes); | ||||
if (LOG.isDebugEnabled()) { | |||||
LOG.debug("file={}, create new FileSnapshot: lastRead={}, lastModified={}, size={}, fileKey={}", //$NON-NLS-1$ | |||||
file, dateFmt.format(lastRead), | |||||
dateFmt.format(lastModified), Long.valueOf(size), | |||||
fileKey.toString()); | |||||
} | |||||
} | } | ||||
private boolean sizeChanged; | private boolean sizeChanged; | ||||
private boolean wasRacyClean; | private boolean wasRacyClean; | ||||
private FileSnapshot(long read, long modified, long size, | |||||
private long delta; | |||||
private long racyThreshold; | |||||
private FileSnapshot(Instant read, Instant modified, long size, | |||||
@NonNull Duration fsTimestampResolution, @NonNull Object fileKey) { | @NonNull Duration fsTimestampResolution, @NonNull Object fileKey) { | ||||
this.file = null; | |||||
this.lastRead = read; | this.lastRead = read; | ||||
this.lastModified = modified; | this.lastModified = modified; | ||||
this.fsTimestampResolution = fsTimestampResolution; | |||||
this.fileStoreAttributeCache = new FileStoreAttributes( | |||||
fsTimestampResolution); | |||||
this.size = size; | this.size = size; | ||||
this.fileKey = fileKey; | this.fileKey = fileKey; | ||||
} | } | ||||
* Get time of last snapshot update | * Get time of last snapshot update | ||||
* | * | ||||
* @return time of last snapshot update | * @return time of last snapshot update | ||||
* @deprecated use {@link #lastModifiedInstant()} instead | |||||
*/ | */ | ||||
@Deprecated | |||||
public long lastModified() { | public long lastModified() { | ||||
return lastModified.toEpochMilli(); | |||||
} | |||||
/** | |||||
* Get time of last snapshot update | |||||
* | |||||
* @return time of last snapshot update | |||||
*/ | |||||
public Instant lastModifiedInstant() { | |||||
return lastModified; | return lastModified; | ||||
} | } | ||||
* @return true if the path needs to be read again. | * @return true if the path needs to be read again. | ||||
*/ | */ | ||||
public boolean isModified(File path) { | public boolean isModified(File path) { | ||||
long currLastModified; | |||||
Instant currLastModified; | |||||
long currSize; | long currSize; | ||||
Object currFileKey; | Object currFileKey; | ||||
try { | try { | ||||
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); | BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); | ||||
currLastModified = fileAttributes.lastModifiedTime().toMillis(); | |||||
currLastModified = fileAttributes.lastModifiedTime().toInstant(); | |||||
currSize = fileAttributes.size(); | currSize = fileAttributes.size(); | ||||
currFileKey = getFileKey(fileAttributes); | currFileKey = getFileKey(fileAttributes); | ||||
} catch (IOException e) { | } catch (IOException e) { | ||||
currLastModified = path.lastModified(); | |||||
currLastModified = Instant.ofEpochMilli(path.lastModified()); | |||||
currSize = path.length(); | currSize = path.length(); | ||||
currFileKey = MISSING_FILEKEY; | currFileKey = MISSING_FILEKEY; | ||||
} | } | ||||
* the other snapshot. | * the other snapshot. | ||||
*/ | */ | ||||
public void setClean(FileSnapshot other) { | public void setClean(FileSnapshot other) { | ||||
final long now = other.lastRead; | |||||
final Instant now = other.lastRead; | |||||
if (!isRacyClean(now)) { | if (!isRacyClean(now)) { | ||||
cannotBeRacilyClean = true; | cannotBeRacilyClean = true; | ||||
} | } | ||||
* if sleep was interrupted | * if sleep was interrupted | ||||
*/ | */ | ||||
public void waitUntilNotRacy() throws InterruptedException { | public void waitUntilNotRacy() throws InterruptedException { | ||||
while (isRacyClean(System.currentTimeMillis())) { | |||||
TimeUnit.NANOSECONDS | |||||
.sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10); | |||||
long timestampResolution = fileStoreAttributeCache | |||||
.getFsTimestampResolution().toNanos(); | |||||
while (isRacyClean(Instant.now())) { | |||||
TimeUnit.NANOSECONDS.sleep(timestampResolution); | |||||
} | } | ||||
} | } | ||||
* the other snapshot. | * the other snapshot. | ||||
* @return true if the two snapshots share the same information. | * @return true if the two snapshots share the same information. | ||||
*/ | */ | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(FileSnapshot other) { | public boolean equals(FileSnapshot other) { | ||||
return lastModified == other.lastModified && size == other.size | |||||
boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size; | |||||
return lastModified.equals(other.lastModified) && sizeEq | |||||
&& Objects.equals(fileKey, other.fileKey); | && Objects.equals(fileKey, other.fileKey); | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public int hashCode() { | public int hashCode() { | ||||
return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size), | |||||
fileKey); | |||||
return Objects.hash(lastModified, Long.valueOf(size), fileKey); | |||||
} | } | ||||
/** | /** | ||||
return wasRacyClean; | return wasRacyClean; | ||||
} | } | ||||
/** | |||||
* @return the delta in nanoseconds between lastModified and lastRead during | |||||
* last racy check | |||||
*/ | |||||
public long lastDelta() { | |||||
return delta; | |||||
} | |||||
/** | |||||
* @return the racyLimitNanos threshold in nanoseconds during last racy | |||||
* check | |||||
*/ | |||||
public long lastRacyThreshold() { | |||||
return racyThreshold; | |||||
} | |||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@SuppressWarnings("nls") | |||||
@SuppressWarnings({ "nls", "ReferenceEquality" }) | |||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
if (this == DIRTY) { | if (this == DIRTY) { | ||||
if (this == MISSING_FILE) { | if (this == MISSING_FILE) { | ||||
return "MISSING_FILE"; | return "MISSING_FILE"; | ||||
} | } | ||||
DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", | |||||
Locale.US); | |||||
return "FileSnapshot[modified: " + f.format(new Date(lastModified)) | |||||
+ ", read: " + f.format(new Date(lastRead)) + ", size:" + size | |||||
return "FileSnapshot[modified: " + dateFmt.format(lastModified) | |||||
+ ", read: " + dateFmt.format(lastRead) + ", size:" + size | |||||
+ ", fileKey: " + fileKey + "]"; | + ", fileKey: " + fileKey + "]"; | ||||
} | } | ||||
private boolean isRacyClean(long read) { | |||||
// add a 10% safety margin | |||||
long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10; | |||||
return wasRacyClean = (read - lastModified) * 1_000_000 <= racyNanos; | |||||
private boolean isRacyClean(Instant read) { | |||||
racyThreshold = getEffectiveRacyThreshold(); | |||||
delta = Duration.between(lastModified, read).toNanos(); | |||||
wasRacyClean = delta <= racyThreshold; | |||||
if (LOG.isDebugEnabled()) { | |||||
LOG.debug( | |||||
"file={}, isRacyClean={}, read={}, lastModified={}, delta={} ns, racy<={} ns", //$NON-NLS-1$ | |||||
file, Boolean.valueOf(wasRacyClean), dateFmt.format(read), | |||||
dateFmt.format(lastModified), Long.valueOf(delta), | |||||
Long.valueOf(racyThreshold)); | |||||
} | |||||
return wasRacyClean; | |||||
} | |||||
private long getEffectiveRacyThreshold() { | |||||
long timestampResolution = fileStoreAttributeCache | |||||
.getFsTimestampResolution().toNanos(); | |||||
long minRacyInterval = fileStoreAttributeCache.getMinimalRacyInterval() | |||||
.toNanos(); | |||||
long max = Math.max(timestampResolution, minRacyInterval); | |||||
// safety margin: factor 2.5 below 100ms otherwise 1.25 | |||||
return max < 100_000_000L ? max * 5 / 2 : max * 5 / 4; | |||||
} | } | ||||
private boolean isModified(long currLastModified) { | |||||
private boolean isModified(Instant currLastModified) { | |||||
// Any difference indicates the path was modified. | // Any difference indicates the path was modified. | ||||
lastModifiedChanged = lastModified != currLastModified; | |||||
lastModifiedChanged = !lastModified.equals(currLastModified); | |||||
if (lastModifiedChanged) { | if (lastModifiedChanged) { | ||||
if (LOG.isDebugEnabled()) { | |||||
LOG.debug( | |||||
"file={}, lastModified changed from {} to {}", //$NON-NLS-1$ | |||||
file, dateFmt.format(lastModified), | |||||
dateFmt.format(currLastModified)); | |||||
} | |||||
return true; | return true; | ||||
} | } | ||||
// after the last modification that any new modifications | // after the last modification that any new modifications | ||||
// are certain to change the last modified time. | // are certain to change the last modified time. | ||||
if (cannotBeRacilyClean) { | if (cannotBeRacilyClean) { | ||||
LOG.debug("file={}, cannot be racily clean", file); //$NON-NLS-1$ | |||||
return false; | return false; | ||||
} | } | ||||
if (!isRacyClean(lastRead)) { | if (!isRacyClean(lastRead)) { | ||||
// Our last read should have marked cannotBeRacilyClean, | // Our last read should have marked cannotBeRacilyClean, | ||||
// but this thread may not have seen the change. The read | // but this thread may not have seen the change. The read | ||||
// of the volatile field lastRead should have fixed that. | // of the volatile field lastRead should have fixed that. | ||||
LOG.debug("file={}, is unmodified", file); //$NON-NLS-1$ | |||||
return false; | return false; | ||||
} | } | ||||
// We last read this path too close to its last observed | // We last read this path too close to its last observed | ||||
// modification time. We may have missed a modification. | // modification time. We may have missed a modification. | ||||
// Scan again, to ensure we still see the same state. | // Scan again, to ensure we still see the same state. | ||||
LOG.debug("file={}, is racily clean", file); //$NON-NLS-1$ | |||||
return true; | return true; | ||||
} | } | ||||
private boolean isFileKeyChanged(Object currFileKey) { | private boolean isFileKeyChanged(Object currFileKey) { | ||||
return currFileKey != MISSING_FILEKEY && !currFileKey.equals(fileKey); | |||||
boolean changed = currFileKey != MISSING_FILEKEY | |||||
&& !currFileKey.equals(fileKey); | |||||
if (changed) { | |||||
LOG.debug("file={}, FileKey changed from {} to {}", //$NON-NLS-1$ | |||||
file, fileKey, currFileKey); | |||||
} | |||||
return changed; | |||||
} | } | ||||
private boolean isSizeChanged(long currSize) { | private boolean isSizeChanged(long currSize) { | ||||
return currSize != UNKNOWN_SIZE && currSize != size; | |||||
boolean changed = (currSize != UNKNOWN_SIZE) && (currSize != size); | |||||
if (changed) { | |||||
LOG.debug("file={}, size changed from {} to {} bytes", //$NON-NLS-1$ | |||||
file, Long.valueOf(size), Long.valueOf(currSize)); | |||||
} | |||||
return changed; | |||||
} | } | ||||
} | } |
continue oldPackLoop; | continue oldPackLoop; | ||||
if (!oldPack.shouldBeKept() | if (!oldPack.shouldBeKept() | ||||
&& repo.getFS().lastModified( | |||||
oldPack.getPackFile()) < packExpireDate) { | |||||
&& repo.getFS() | |||||
.lastModifiedInstant(oldPack.getPackFile()) | |||||
.toEpochMilli() < packExpireDate) { | |||||
oldPack.close(); | oldPack.close(); | ||||
if (shouldLoosen) { | if (shouldLoosen) { | ||||
loosen(inserter, reader, oldPack, ids); | loosen(inserter, reader, oldPack, ids); | ||||
String fName = f.getName(); | String fName = f.getName(); | ||||
if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) | if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) | ||||
continue; | continue; | ||||
if (repo.getFS().lastModified(f) >= expireDate) | |||||
if (repo.getFS().lastModifiedInstant(f) | |||||
.toEpochMilli() >= expireDate) { | |||||
continue; | continue; | ||||
} | |||||
try { | try { | ||||
ObjectId id = ObjectId.fromString(d + fName); | ObjectId id = ObjectId.fromString(d + fName); | ||||
if (objectsToKeep.contains(id)) | if (objectsToKeep.contains(id)) |
import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||
import java.nio.channels.Channels; | import java.nio.channels.Channels; | ||||
import java.nio.channels.FileChannel; | import java.nio.channels.FileChannel; | ||||
import java.nio.file.Files; | |||||
import java.nio.file.StandardCopyOption; | import java.nio.file.StandardCopyOption; | ||||
import java.nio.file.attribute.FileTime; | |||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Instant; | |||||
import java.util.concurrent.TimeUnit; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
public void waitForStatChange() throws InterruptedException { | public void waitForStatChange() throws InterruptedException { | ||||
FileSnapshot o = FileSnapshot.save(ref); | FileSnapshot o = FileSnapshot.save(ref); | ||||
FileSnapshot n = FileSnapshot.save(lck); | FileSnapshot n = FileSnapshot.save(lck); | ||||
long fsTimeResolution = FS.getFileStoreAttributes(lck.toPath()) | |||||
.getFsTimestampResolution().toNanos(); | |||||
while (o.equals(n)) { | while (o.equals(n)) { | ||||
Thread.sleep(25 /* milliseconds */); | |||||
lck.setLastModified(System.currentTimeMillis()); | |||||
TimeUnit.NANOSECONDS.sleep(fsTimeResolution); | |||||
try { | |||||
Files.setLastModifiedTime(lck.toPath(), | |||||
FileTime.from(Instant.now())); | |||||
} catch (IOException e) { | |||||
n.waitUntilNotRacy(); | |||||
} | |||||
n = FileSnapshot.save(lck); | n = FileSnapshot.save(lck); | ||||
} | } | ||||
} | } | ||||
* Get the modification time of the output file when it was committed. | * Get the modification time of the output file when it was committed. | ||||
* | * | ||||
* @return modification time of the lock file right before we committed it. | * @return modification time of the lock file right before we committed it. | ||||
* @deprecated use {@link #getCommitLastModifiedInstant()} instead | |||||
*/ | */ | ||||
@Deprecated | |||||
public long getCommitLastModified() { | public long getCommitLastModified() { | ||||
return commitSnapshot.lastModified(); | return commitSnapshot.lastModified(); | ||||
} | } | ||||
/** | |||||
* Get the modification time of the output file when it was committed. | |||||
* | |||||
* @return modification time of the lock file right before we committed it. | |||||
*/ | |||||
public Instant getCommitLastModifiedInstant() { | |||||
return commitSnapshot.lastModifiedInstant(); | |||||
} | |||||
/** | /** | ||||
* Get the {@link FileSnapshot} just before commit. | * Get the {@link FileSnapshot} just before commit. | ||||
* | * |
buf[len++] = (byte) ((typeCode << 4) | (sz & 15)); | buf[len++] = (byte) ((typeCode << 4) | (sz & 15)); | ||||
sz >>>= 4; | sz >>>= 4; | ||||
while (sz > 0) { | while (sz > 0) { | ||||
buf[len - 1] |= 0x80; | |||||
buf[len - 1] |= (byte) 0x80; | |||||
buf[len++] = (byte) (sz & 0x7f); | buf[len++] = (byte) (sz & 0x7f); | ||||
sz >>>= 7; | sz >>>= 7; | ||||
} | } |
import java.nio.file.AccessDeniedException; | import java.nio.file.AccessDeniedException; | ||||
import java.nio.file.NoSuchFileException; | import java.nio.file.NoSuchFileException; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Instant; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.Comparator; | import java.util.Comparator; | ||||
public class PackFile implements Iterable<PackIndex.MutableEntry> { | public class PackFile implements Iterable<PackIndex.MutableEntry> { | ||||
private final static Logger LOG = LoggerFactory.getLogger(PackFile.class); | private final static Logger LOG = LoggerFactory.getLogger(PackFile.class); | ||||
/** Sorts PackFiles to be most recently created to least recently created. */ | /** Sorts PackFiles to be most recently created to least recently created. */ | ||||
public static final Comparator<PackFile> SORT = (PackFile a, | |||||
PackFile b) -> b.packLastModified - a.packLastModified; | |||||
public static final Comparator<PackFile> SORT = new Comparator<PackFile>() { | |||||
@Override | |||||
public int compare(PackFile a, PackFile b) { | |||||
return b.packLastModified.compareTo(a.packLastModified); | |||||
} | |||||
}; | |||||
private final File packFile; | private final File packFile; | ||||
private int activeCopyRawData; | private int activeCopyRawData; | ||||
int packLastModified; | |||||
Instant packLastModified; | |||||
private PackFileSnapshot fileSnapshot; | private PackFileSnapshot fileSnapshot; | ||||
public PackFile(File packFile, int extensions) { | public PackFile(File packFile, int extensions) { | ||||
this.packFile = packFile; | this.packFile = packFile; | ||||
this.fileSnapshot = PackFileSnapshot.save(packFile); | this.fileSnapshot = PackFileSnapshot.save(packFile); | ||||
this.packLastModified = (int) (fileSnapshot.lastModified() >> 10); | |||||
this.packLastModified = fileSnapshot.lastModifiedInstant(); | |||||
this.extensions = extensions; | this.extensions = extensions; | ||||
// Multiply by 31 here so we can more directly combine with another | // Multiply by 31 here so we can more directly combine with another |
if (obj == null) | if (obj == null) | ||||
break; | break; | ||||
if (AnyObjectId.equals(obj, toFind)) | |||||
if (AnyObjectId.isEqual(obj, toFind)) | |||||
return true; | return true; | ||||
if (++i == ids.length()) | if (++i == ids.length()) | ||||
continue; | continue; | ||||
} | } | ||||
if (AnyObjectId.equals(obj, toAdd)) | |||||
if (AnyObjectId.isEqual(obj, toAdd)) | |||||
return true; | return true; | ||||
if (++i == ids.length()) | if (++i == ids.length()) |
int c, shift = 0; | int c, shift = 0; | ||||
do { | do { | ||||
c = delta[deltaPtr++] & 0xff; | c = delta[deltaPtr++] & 0xff; | ||||
baseLen |= ((long) (c & 0x7f)) << shift; | |||||
baseLen |= (c & 0x7f) << shift; | |||||
shift += 7; | shift += 7; | ||||
} while ((c & 0x80) != 0); | } while ((c & 0x80) != 0); | ||||
if (base.length != baseLen) | if (base.length != baseLen) | ||||
shift = 0; | shift = 0; | ||||
do { | do { | ||||
c = delta[deltaPtr++] & 0xff; | c = delta[deltaPtr++] & 0xff; | ||||
resLen |= ((long) (c & 0x7f)) << shift; | |||||
resLen |= (c & 0x7f) << shift; | |||||
shift += 7; | shift += 7; | ||||
} while ((c & 0x80) != 0); | } while ((c & 0x80) != 0); | ||||
if (!cachedPacks.isEmpty()) { | if (!cachedPacks.isEmpty()) { | ||||
if (otp.isEdge()) | if (otp.isEdge()) | ||||
return; | return; | ||||
if ((nFmt == PACK_WHOLE) | (nFmt == PACK_DELTA)) { | |||||
if (nFmt == PACK_WHOLE || nFmt == PACK_DELTA) { | |||||
for (CachedPack pack : cachedPacks) { | for (CachedPack pack : cachedPacks) { | ||||
if (pack.hasObject(otp, next)) { | if (pack.hasObject(otp, next)) { | ||||
otp.setEdge(); | otp.setEdge(); | ||||
otp.clearReuseAsIs(); | otp.clearReuseAsIs(); | ||||
} | } | ||||
otp.setDeltaAttempted(reuseDeltas & next.wasDeltaAttempted()); | |||||
otp.setDeltaAttempted(reuseDeltas && next.wasDeltaAttempted()); | |||||
otp.select(next); | otp.select(next); | ||||
} | } | ||||
* Wraps all cookies persisted in a <strong>Netscape Cookie File Format</strong> | * Wraps all cookies persisted in a <strong>Netscape Cookie File Format</strong> | ||||
* being referenced via the git config <a href= | * being referenced via the git config <a href= | ||||
* "https://git-scm.com/docs/git-config#git-config-httpcookieFile">http.cookieFile</a>. | * "https://git-scm.com/docs/git-config#git-config-httpcookieFile">http.cookieFile</a>. | ||||
* | |||||
* <p> | |||||
* It will only load the cookies lazily, i.e. before calling | * It will only load the cookies lazily, i.e. before calling | ||||
* {@link #getCookies(boolean)} the file is not evaluated. This class also | * {@link #getCookies(boolean)} the file is not evaluated. This class also | ||||
* allows persisting cookies in that file format. | * allows persisting cookies in that file format. | ||||
/** | /** | ||||
* @param path | * @param path | ||||
* where to find the cookie file | |||||
*/ | */ | ||||
public NetscapeCookieFile(Path path) { | public NetscapeCookieFile(Path path) { | ||||
this(path, new Date()); | this(path, new Date()); | ||||
} | } | ||||
/** | /** | ||||
* @return the path to the underlying cookie file | |||||
* Path to the underlying cookie file. | |||||
* | |||||
* @return the path | |||||
*/ | */ | ||||
public Path getPath() { | public Path getPath() { | ||||
return path; | return path; | ||||
} | } | ||||
/** | /** | ||||
* Return all cookies from the underlying cookie file. | |||||
* | |||||
* @param refresh | * @param refresh | ||||
* if {@code true} updates the list from the underlying cookie | * if {@code true} updates the list from the underlying cookie | ||||
* file if it has been modified since the last read otherwise | * file if it has been modified since the last read otherwise | ||||
* @throws IOException | * @throws IOException | ||||
* if the given file could not be read for some reason | * if the given file could not be read for some reason | ||||
* @throws IllegalArgumentException | * @throws IllegalArgumentException | ||||
* if the given file does not have a proper format. | |||||
* if the given file does not have a proper format | |||||
*/ | */ | ||||
private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input, | private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input, | ||||
@NonNull Date creationDate) | @NonNull Date creationDate) | ||||
return cookie; | return cookie; | ||||
} | } | ||||
/** | |||||
* Writes all the cookies being maintained in the set being returned by | |||||
* {@link #getCookies(boolean)} to the underlying file. | |||||
* | |||||
* Session-cookies will not be persisted. | |||||
* | |||||
* @param url | |||||
* url for which to write the cookies (important to derive | |||||
* default values for non-explicitly set attributes) | |||||
* @throws IOException | |||||
* @throws IllegalArgumentException | |||||
* @throws InterruptedException | |||||
*/ | |||||
public void write(URL url) | |||||
throws IllegalArgumentException, IOException, InterruptedException { | |||||
try { | |||||
byte[] cookieFileContent = getFileContentIfModified(); | |||||
if (cookieFileContent != null) { | |||||
LOG.debug( | |||||
"Reading the underlying cookie file '{}' as it has been modified since the last access", //$NON-NLS-1$ | |||||
path); | |||||
// reread new changes if necessary | |||||
Set<HttpCookie> cookiesFromFile = NetscapeCookieFile | |||||
.parseCookieFile(cookieFileContent, creationDate); | |||||
this.cookies = mergeCookies(cookiesFromFile, cookies); | |||||
} | |||||
} catch (FileNotFoundException e) { | |||||
// ignore if file previously did not exist yet! | |||||
} | |||||
ByteArrayOutputStream output = new ByteArrayOutputStream(); | |||||
try (Writer writer = new OutputStreamWriter(output, | |||||
StandardCharsets.US_ASCII)) { | |||||
write(writer, cookies, url, creationDate); | |||||
} | |||||
LockFile lockFile = new LockFile(path.toFile()); | |||||
for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) { | |||||
if (lockFile.lock()) { | |||||
try { | |||||
lockFile.setNeedSnapshot(true); | |||||
lockFile.write(output.toByteArray()); | |||||
if (!lockFile.commit()) { | |||||
throw new IOException(MessageFormat.format( | |||||
JGitText.get().cannotCommitWriteTo, path)); | |||||
} | |||||
} finally { | |||||
lockFile.unlock(); | |||||
} | |||||
return; | |||||
} | |||||
Thread.sleep(LOCK_ACQUIRE_RETRY_SLEEP); | |||||
} | |||||
throw new IOException( | |||||
MessageFormat.format(JGitText.get().cannotLock, lockFile)); | |||||
} | |||||
/** | /** | ||||
* Read the underying file and return its content but only in case it has | * Read the underying file and return its content but only in case it has | ||||
* been modified since the last access. Internally calculates the hash and | |||||
* maintains {@link FileSnapshot}s to prevent issues described as <a href= | |||||
* been modified since the last access. | |||||
* <p> | |||||
* Internally calculates the hash and maintains {@link FileSnapshot}s to | |||||
* prevent issues described as <a href= | |||||
* "https://github.com/git/git/blob/master/Documentation/technical/racy-git.txt">"Racy | * "https://github.com/git/git/blob/master/Documentation/technical/racy-git.txt">"Racy | ||||
* Git problem"</a>. Inspired by {@link FileBasedConfig#load()}. | * Git problem"</a>. Inspired by {@link FileBasedConfig#load()}. | ||||
* | * | ||||
* @return the file contents in case the file has been modified since the | * @return the file contents in case the file has been modified since the | ||||
* last access, otherwise {@code null} | * last access, otherwise {@code null} | ||||
* @throws IOException | * @throws IOException | ||||
* if the file is not found or cannot be read | |||||
*/ | */ | ||||
private byte[] getFileContentIfModified() throws IOException { | private byte[] getFileContentIfModified() throws IOException { | ||||
final int maxStaleRetries = 5; | final int maxStaleRetries = 5; | ||||
} | } | ||||
private byte[] hash(final byte[] in) { | |||||
private static byte[] hash(final byte[] in) { | |||||
return Constants.newMessageDigest().digest(in); | return Constants.newMessageDigest().digest(in); | ||||
} | } | ||||
/** | |||||
* Writes all the cookies being maintained in the set being returned by | |||||
* {@link #getCookies(boolean)} to the underlying file. | |||||
* <p> | |||||
* Session-cookies will not be persisted. | |||||
* | |||||
* @param url | |||||
* url for which to write the cookies (important to derive | |||||
* default values for non-explicitly set attributes) | |||||
* @throws IOException | |||||
* if the underlying cookie file could not be read or written or | |||||
* a problem with the lock file | |||||
* @throws InterruptedException | |||||
* if the thread is interrupted while waiting for the lock | |||||
*/ | |||||
public void write(URL url) throws IOException, InterruptedException { | |||||
try { | |||||
byte[] cookieFileContent = getFileContentIfModified(); | |||||
if (cookieFileContent != null) { | |||||
LOG.debug("Reading the underlying cookie file '{}' " //$NON-NLS-1$ | |||||
+ "as it has been modified since " //$NON-NLS-1$ | |||||
+ "the last access", //$NON-NLS-1$ | |||||
path); | |||||
// reread new changes if necessary | |||||
Set<HttpCookie> cookiesFromFile = NetscapeCookieFile | |||||
.parseCookieFile(cookieFileContent, creationDate); | |||||
this.cookies = mergeCookies(cookiesFromFile, cookies); | |||||
} | |||||
} catch (FileNotFoundException e) { | |||||
// ignore if file previously did not exist yet! | |||||
} | |||||
ByteArrayOutputStream output = new ByteArrayOutputStream(); | |||||
try (Writer writer = new OutputStreamWriter(output, | |||||
StandardCharsets.US_ASCII)) { | |||||
write(writer, cookies, url, creationDate); | |||||
} | |||||
LockFile lockFile = new LockFile(path.toFile()); | |||||
for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) { | |||||
if (lockFile.lock()) { | |||||
try { | |||||
lockFile.setNeedSnapshot(true); | |||||
lockFile.write(output.toByteArray()); | |||||
if (!lockFile.commit()) { | |||||
throw new IOException(MessageFormat.format( | |||||
JGitText.get().cannotCommitWriteTo, path)); | |||||
} | |||||
} finally { | |||||
lockFile.unlock(); | |||||
} | |||||
return; | |||||
} | |||||
Thread.sleep(LOCK_ACQUIRE_RETRY_SLEEP); | |||||
} | |||||
throw new IOException( | |||||
MessageFormat.format(JGitText.get().cannotLock, lockFile)); | |||||
} | |||||
/** | /** | ||||
* Writes the given cookies to the file in the Netscape Cookie File Format | * Writes the given cookies to the file in the Netscape Cookie File Format | ||||
* (also used by curl) | |||||
* (also used by curl). | |||||
* | * | ||||
* @param writer | * @param writer | ||||
* the writer to use to persist the cookies. | |||||
* the writer to use to persist the cookies | |||||
* @param cookies | * @param cookies | ||||
* the cookies to write into the file | * the cookies to write into the file | ||||
* @param url | * @param url | ||||
* @param creationDate | * @param creationDate | ||||
* the date when the cookie has been created. Important for | * the date when the cookie has been created. Important for | ||||
* calculation the cookie expiration time (calculated from | * calculation the cookie expiration time (calculated from | ||||
* cookie's maxAge and this creation time). | |||||
* cookie's maxAge and this creation time) | |||||
* @throws IOException | * @throws IOException | ||||
* if an I/O error occurs | |||||
*/ | */ | ||||
static void write(@NonNull Writer writer, | static void write(@NonNull Writer writer, | ||||
@NonNull Collection<HttpCookie> cookies, @NonNull URL url, | @NonNull Collection<HttpCookie> cookies, @NonNull URL url, | ||||
* the entry from set {@code cookies1} ends up in the resulting set. | * the entry from set {@code cookies1} ends up in the resulting set. | ||||
* | * | ||||
* @param cookies1 | * @param cookies1 | ||||
* first set of cookies | |||||
* @param cookies2 | * @param cookies2 | ||||
* second set of cookies | |||||
* | * | ||||
* @return the merged cookies | * @return the merged cookies | ||||
*/ | */ |
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.file.Files; | import java.nio.file.Files; | ||||
import java.time.Instant; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import org.eclipse.jgit.errors.InvalidPatternException; | import org.eclipse.jgit.errors.InvalidPatternException; | ||||
import org.eclipse.jgit.fnmatch.FileNameMatcher; | import org.eclipse.jgit.fnmatch.FileNameMatcher; | ||||
import org.eclipse.jgit.transport.SshConstants; | import org.eclipse.jgit.transport.SshConstants; | ||||
import org.eclipse.jgit.util.FS; | |||||
import org.eclipse.jgit.util.StringUtils; | import org.eclipse.jgit.util.StringUtils; | ||||
import org.eclipse.jgit.util.SystemReader; | import org.eclipse.jgit.util.SystemReader; | ||||
private final String localUserName; | private final String localUserName; | ||||
/** Modification time of {@link #configFile} when it was last loaded. */ | /** Modification time of {@link #configFile} when it was last loaded. */ | ||||
private long lastModified; | |||||
private Instant lastModified; | |||||
/** | /** | ||||
* Encapsulates entries read out of the configuration file, and a cache of | * Encapsulates entries read out of the configuration file, and a cache of | ||||
} | } | ||||
private synchronized State refresh() { | private synchronized State refresh() { | ||||
final long mtime = configFile.lastModified(); | |||||
if (mtime != lastModified) { | |||||
final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile); | |||||
if (!mtime.equals(lastModified)) { | |||||
State newState = new State(); | State newState = new State(); | ||||
try (BufferedReader br = Files | try (BufferedReader br = Files | ||||
.newBufferedReader(configFile.toPath(), UTF_8)) { | .newBufferedReader(configFile.toPath(), UTF_8)) { |
import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||
import org.eclipse.jgit.util.NB; | import org.eclipse.jgit.util.NB; | ||||
import org.eclipse.jgit.util.References; | |||||
/** | /** | ||||
* A (possibly mutable) SHA-1 abstraction. | * A (possibly mutable) SHA-1 abstraction. | ||||
public abstract class AnyObjectId implements Comparable<AnyObjectId> { | public abstract class AnyObjectId implements Comparable<AnyObjectId> { | ||||
/** | /** | ||||
* Compare to object identifier byte sequences for equality. | |||||
* Compare two object identifier byte sequences for equality. | |||||
* | * | ||||
* @param firstObjectId | * @param firstObjectId | ||||
* the first identifier to compare. Must not be null. | * the first identifier to compare. Must not be null. | ||||
* @param secondObjectId | * @param secondObjectId | ||||
* the second identifier to compare. Must not be null. | * the second identifier to compare. Must not be null. | ||||
* @return true if the two identifiers are the same. | * @return true if the two identifiers are the same. | ||||
* @deprecated use {@link #isEqual(AnyObjectId, AnyObjectId)} instead | |||||
*/ | */ | ||||
@Deprecated | |||||
@SuppressWarnings("AmbiguousMethodReference") | |||||
public static boolean equals(final AnyObjectId firstObjectId, | public static boolean equals(final AnyObjectId firstObjectId, | ||||
final AnyObjectId secondObjectId) { | final AnyObjectId secondObjectId) { | ||||
if (firstObjectId == secondObjectId) | |||||
return true; | |||||
return isEqual(firstObjectId, secondObjectId); | |||||
} | |||||
/** | |||||
* Compare two object identifier byte sequences for equality. | |||||
* | |||||
* @param firstObjectId | |||||
* the first identifier to compare. Must not be null. | |||||
* @param secondObjectId | |||||
* the second identifier to compare. Must not be null. | |||||
* @return true if the two identifiers are the same. | |||||
* @since 5.4 | |||||
*/ | |||||
public static boolean isEqual(final AnyObjectId firstObjectId, | |||||
final AnyObjectId secondObjectId) { | |||||
if (References.isSameObject(firstObjectId, secondObjectId)) { | |||||
return true; | |||||
} | |||||
// We test word 3 first since the git file-based ODB | // We test word 3 first since the git file-based ODB | ||||
// uses the first byte of w1, and we use w2 as the | // uses the first byte of w1, and we use w2 as the | ||||
// hash code, one of those probably came up with these | // hash code, one of those probably came up with these | ||||
// Therefore the first two words are very likely to be | // Therefore the first two words are very likely to be | ||||
// identical. We want to break away from collisions as | // identical. We want to break away from collisions as | ||||
// quickly as possible. | // quickly as possible. | ||||
// | |||||
return firstObjectId.w3 == secondObjectId.w3 | return firstObjectId.w3 == secondObjectId.w3 | ||||
&& firstObjectId.w4 == secondObjectId.w4 | && firstObjectId.w4 == secondObjectId.w4 | ||||
&& firstObjectId.w5 == secondObjectId.w5 | && firstObjectId.w5 == secondObjectId.w5 | ||||
* the other id to compare to. May be null. | * the other id to compare to. May be null. | ||||
* @return true only if both ObjectIds have identical bits. | * @return true only if both ObjectIds have identical bits. | ||||
*/ | */ | ||||
@SuppressWarnings({ "NonOverridingEquals", "AmbiguousMethodReference" }) | |||||
public final boolean equals(AnyObjectId other) { | public final boolean equals(AnyObjectId other) { | ||||
return other != null ? equals(this, other) : false; | |||||
return other != null ? isEqual(this, other) : false; | |||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ |
import java.util.List; | import java.util.List; | ||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.util.References; | |||||
/** | /** | ||||
* Mutable builder to construct a commit recording the state of a project. | * Mutable builder to construct a commit recording the state of a project. | ||||
os.write('\n'); | os.write('\n'); | ||||
} | } | ||||
if (getEncoding() != UTF_8) { | |||||
if (!References.isSameObject(getEncoding(), UTF_8)) { | |||||
os.write(hencoding); | os.write(hencoding); | ||||
os.write(' '); | os.write(' '); | ||||
os.write(Constants.encodeASCII(getEncoding().name())); | os.write(Constants.encodeASCII(getEncoding().name())); | ||||
r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET"); | r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET"); | ||||
r.append("\n"); | r.append("\n"); | ||||
if (encoding != null && encoding != UTF_8) { | |||||
if (encoding != null && !References.isSameObject(encoding, UTF_8)) { | |||||
r.append("encoding "); | r.append("encoding "); | ||||
r.append(encoding.name()); | r.append(encoding.name()); | ||||
r.append("\n"); | r.append("\n"); |
/** | /** | ||||
* Check if a given string is the "missing" value. | * Check if a given string is the "missing" value. | ||||
* | * | ||||
* @param value | |||||
* @param value string to be checked. | |||||
* @return true if the given string is the "missing" value. | * @return true if the given string is the "missing" value. | ||||
* @since 5.4 | * @since 5.4 | ||||
*/ | */ | ||||
@SuppressWarnings({ "ReferenceEquality", "StringEquality" }) | |||||
public static boolean isMissing(String value) { | public static boolean isMissing(String value) { | ||||
return value == MISSING_ENTRY; | return value == MISSING_ENTRY; | ||||
} | } | ||||
if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$ | if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$ | ||||
out.append('\t'); | out.append('\t'); | ||||
out.append(e.name); | out.append(e.name); | ||||
if (MISSING_ENTRY != e.value) { | |||||
if (!isMissing(e.value)) { | |||||
out.append(" ="); //$NON-NLS-1$ | out.append(" ="); //$NON-NLS-1$ | ||||
if (e.value != null) { | if (e.value != null) { | ||||
out.append(' '); | out.append(' '); |
* @since 5.2 | * @since 5.2 | ||||
*/ | */ | ||||
public static final String CONFIG_KEY_LOG_OUTPUT_ENCODING = "logOutputEncoding"; | public static final String CONFIG_KEY_LOG_OUTPUT_ENCODING = "logOutputEncoding"; | ||||
/** | |||||
* The "filesystem" section | |||||
* @since 5.1.9 | |||||
*/ | |||||
public static final String CONFIG_FILESYSTEM_SECTION = "filesystem"; | |||||
/** | |||||
* The "timestampResolution" key | |||||
* @since 5.1.9 | |||||
*/ | |||||
public static final String CONFIG_KEY_TIMESTAMP_RESOLUTION = "timestampResolution"; | |||||
/** | |||||
* The "minRacyThreshold" key | |||||
* | |||||
* @since 5.1.9 | |||||
*/ | |||||
public static final String CONFIG_KEY_MIN_RACY_THRESHOLD = "minRacyThreshold"; | |||||
} | } |
inputUnit = wantUnit; | inputUnit = wantUnit; | ||||
inputMul = 1; | inputMul = 1; | ||||
} else if (match(unitName, "ns", "nanoseconds")) { //$NON-NLS-1$ //$NON-NLS-2$ | |||||
inputUnit = TimeUnit.NANOSECONDS; | |||||
inputMul = 1; | |||||
} else if (match(unitName, "us", "microseconds")) { //$NON-NLS-1$ //$NON-NLS-2$ | |||||
inputUnit = TimeUnit.MICROSECONDS; | |||||
inputMul = 1; | |||||
} else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$ | } else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$ | ||||
inputUnit = TimeUnit.MILLISECONDS; | inputUnit = TimeUnit.MILLISECONDS; | ||||
inputMul = 1; | inputMul = 1; |
public static final FileMode TREE = new FileMode(TYPE_TREE, | public static final FileMode TREE = new FileMode(TYPE_TREE, | ||||
Constants.OBJ_TREE) { | Constants.OBJ_TREE) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int modeBits) { | public boolean equals(int modeBits) { | ||||
return (modeBits & TYPE_MASK) == TYPE_TREE; | return (modeBits & TYPE_MASK) == TYPE_TREE; | ||||
} | } | ||||
public static final FileMode SYMLINK = new FileMode(TYPE_SYMLINK, | public static final FileMode SYMLINK = new FileMode(TYPE_SYMLINK, | ||||
Constants.OBJ_BLOB) { | Constants.OBJ_BLOB) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int modeBits) { | public boolean equals(int modeBits) { | ||||
return (modeBits & TYPE_MASK) == TYPE_SYMLINK; | return (modeBits & TYPE_MASK) == TYPE_SYMLINK; | ||||
} | } | ||||
public static final FileMode REGULAR_FILE = new FileMode(0100644, | public static final FileMode REGULAR_FILE = new FileMode(0100644, | ||||
Constants.OBJ_BLOB) { | Constants.OBJ_BLOB) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int modeBits) { | public boolean equals(int modeBits) { | ||||
return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) == 0; | return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) == 0; | ||||
} | } | ||||
public static final FileMode EXECUTABLE_FILE = new FileMode(0100755, | public static final FileMode EXECUTABLE_FILE = new FileMode(0100755, | ||||
Constants.OBJ_BLOB) { | Constants.OBJ_BLOB) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int modeBits) { | public boolean equals(int modeBits) { | ||||
return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) != 0; | return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) != 0; | ||||
} | } | ||||
public static final FileMode GITLINK = new FileMode(TYPE_GITLINK, | public static final FileMode GITLINK = new FileMode(TYPE_GITLINK, | ||||
Constants.OBJ_COMMIT) { | Constants.OBJ_COMMIT) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int modeBits) { | public boolean equals(int modeBits) { | ||||
return (modeBits & TYPE_MASK) == TYPE_GITLINK; | return (modeBits & TYPE_MASK) == TYPE_GITLINK; | ||||
} | } | ||||
public static final FileMode MISSING = new FileMode(TYPE_MISSING, | public static final FileMode MISSING = new FileMode(TYPE_MISSING, | ||||
Constants.OBJ_BAD) { | Constants.OBJ_BAD) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int modeBits) { | public boolean equals(int modeBits) { | ||||
return modeBits == 0; | return modeBits == 0; | ||||
} | } | ||||
return new FileMode(bits, Constants.OBJ_BAD) { | return new FileMode(bits, Constants.OBJ_BAD) { | ||||
@Override | @Override | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public boolean equals(int a) { | public boolean equals(int a) { | ||||
return bits == a; | return bits == a; | ||||
} | } | ||||
* a int. | * a int. | ||||
* @return true if the mode bits represent the same mode as this object | * @return true if the mode bits represent the same mode as this object | ||||
*/ | */ | ||||
@SuppressWarnings("NonOverridingEquals") | |||||
public abstract boolean equals(int modebits); | public abstract boolean equals(int modebits); | ||||
/** | /** |
private void addConflict(String path, int stage) { | private void addConflict(String path, int stage) { | ||||
StageState existingStageStates = conflicts.get(path); | StageState existingStageStates = conflicts.get(path); | ||||
byte stageMask = 0; | byte stageMask = 0; | ||||
if (existingStageStates != null) | |||||
stageMask |= existingStageStates.getStageMask(); | |||||
if (existingStageStates != null) { | |||||
stageMask |= (byte) existingStageStates.getStageMask(); | |||||
} | |||||
// stage 1 (base) should be shifted 0 times | // stage 1 (base) should be shifted 0 times | ||||
int shifts = stage - 1; | int shifts = stage - 1; | ||||
stageMask |= (1 << shifts); | |||||
stageMask |= (byte) (1 << shifts); | |||||
StageState stageState = StageState.fromMask(stageMask); | StageState stageState = StageState.fromMask(stageMask); | ||||
conflicts.put(path, stageState); | conflicts.put(path, stageState); | ||||
} | } |
V obj; | V obj; | ||||
while ((obj = tbl[i]) != null) { | while ((obj = tbl[i]) != null) { | ||||
if (AnyObjectId.equals(obj, toFind)) | |||||
if (AnyObjectId.isEqual(obj, toFind)) { | |||||
return obj; | return obj; | ||||
} | |||||
i = (i + 1) & msk; | i = (i + 1) & msk; | ||||
} | } | ||||
return null; | return null; | ||||
V obj; | V obj; | ||||
while ((obj = tbl[i]) != null) { | while ((obj = tbl[i]) != null) { | ||||
if (AnyObjectId.equals(obj, newValue)) | |||||
if (AnyObjectId.isEqual(obj, newValue)) | |||||
return obj; | return obj; | ||||
i = (i + 1) & msk; | i = (i + 1) & msk; | ||||
} | } |