Remember the cause for invalidating a packfile

Keep track of the original cause for a packfile invalidation.
It is needed for the sysadmin to understand if there is a real
underlying filesystem problem and repository corruption or if it is
simply a consequence of a concurrency of Git operations (e.g. repack
or GC).

Change-Id: I06ddda9ec847844ec31616ab6d17f153a5a34e33
Signed-off-by: Luca Milanesio <luca.milanesio@gmail.com>
Signed-off-by: David Pursehouse <david.pursehouse@gmail.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 0b5acc2..eb31d56 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -8,6 +8,20 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="4.5.7"/>
+                <message_argument value="PackInvalidException(File, Throwable)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="4.5.7"/>
+                <message_argument value="PackInvalidException(String, Throwable)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
         <filter id="336658481">
             <message_arguments>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
index 8c216c3..09be9ec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
@@ -58,9 +58,24 @@ public class PackInvalidException extends IOException {
 	 *
 	 * @param path
 	 *            path of the invalid pack file.
+	 * @deprecated Use {@link #PackInvalidException(File, Throwable)}.
 	 */
+	@Deprecated
 	public PackInvalidException(final File path) {
-		this(path.getAbsolutePath());
+		this(path, null);
+	}
+
+	/**
+	 * Construct a pack invalid error with cause.
+	 *
+	 * @param path
+	 *            path of the invalid pack file.
+	 * @param cause
+	 *            cause of the pack file becoming invalid.
+	 * @since 4.5.7
+	 */
+	public PackInvalidException(final File path, Throwable cause) {
+		this(path.getAbsolutePath(), cause);
 	}
 
 	/**
@@ -68,8 +83,23 @@ public PackInvalidException(final File path) {
 	 *
 	 * @param path
 	 *            path of the invalid pack file.
+	 * @deprecated Use {@link #PackInvalidException(String, Throwable)}.
 	 */
+	@Deprecated
 	public PackInvalidException(final String path) {
-		super(MessageFormat.format(JGitText.get().packFileInvalid, path));
+		this(path, null);
+	}
+
+	/**
+	 * Construct a pack invalid error with cause.
+	 *
+	 * @param path
+	 *            path of the invalid pack file.
+	 * @param cause
+	 *            cause of the pack file becoming invalid.
+	 * @since 4.5.7
+	 */
+	public PackInvalidException(final String path, Throwable cause) {
+		super(MessageFormat.format(JGitText.get().packFileInvalid, path), cause);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index 4eabb03..f845453 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -135,6 +135,9 @@ public final class DfsPackFile {
 	/** True once corruption has been detected that cannot be worked around. */
 	private volatile boolean invalid;
 
+	/** Exception that caused the packfile to be flagged as invalid */
+	private volatile Exception invalidatingCause;
+
 	/**
 	 * Lock for initialization of {@link #index} and {@link #corruptObjects}.
 	 * <p>
@@ -236,8 +239,9 @@ private PackIndex idx(DfsReader ctx) throws IOException {
 				return idx;
 		}
 
-		if (invalid)
-			throw new PackInvalidException(getPackName());
+		if (invalid) {
+			throw new PackInvalidException(getPackName(), invalidatingCause);
+		}
 
 		Repository.getGlobalListenerList()
 				.dispatch(new BeforeDfsPackIndexLoadedEvent(this));
@@ -268,6 +272,7 @@ else if (bs <= 0)
 				}
 			} catch (EOFException e) {
 				invalid = true;
+				invalidatingCause = e;
 				IOException e2 = new IOException(MessageFormat.format(
 						DfsText.get().shortReadOfIndex,
 						packDesc.getFileName(INDEX)));
@@ -275,6 +280,7 @@ else if (bs <= 0)
 				throw e2;
 			} catch (IOException e) {
 				invalid = true;
+				invalidatingCause = e;
 				IOException e2 = new IOException(MessageFormat.format(
 						DfsText.get().cannotReadIndex,
 						packDesc.getFileName(INDEX)));
@@ -743,8 +749,10 @@ void setInvalid() {
 
 	private IOException packfileIsTruncated() {
 		invalid = true;
-		return new IOException(MessageFormat.format(
+		IOException exc = new IOException(MessageFormat.format(
 				JGitText.get().packfileIsTruncated, getPackName()));
+		invalidatingCause = exc;
+		return exc;
 	}
 
 	private void readFully(long position, byte[] dstbuf, int dstoff, int cnt,
@@ -766,8 +774,9 @@ DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
 
 	DfsBlock readOneBlock(long pos, DfsReader ctx)
 			throws IOException {
-		if (invalid)
-			throw new PackInvalidException(getPackName());
+		if (invalid) {
+			throw new PackInvalidException(getPackName(), invalidatingCause);
+		}
 
 		ReadableChannel rc = ctx.db.openFile(packDesc, PACK);
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 7c7a39e..019d3ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -581,13 +581,8 @@ private void handlePackError(IOException e, PackFile p) {
 			transientErrorCount = p.incrementTransientErrorCount();
 		}
 		if (warnTmpl != null) {
-			if (LOG.isDebugEnabled()) {
-				LOG.debug(MessageFormat.format(warnTmpl,
-						p.getPackFile().getAbsolutePath()), e);
-			} else {
-				LOG.warn(MessageFormat.format(warnTmpl,
-						p.getPackFile().getAbsolutePath()));
-			}
+			LOG.warn(MessageFormat.format(warnTmpl,
+					p.getPackFile().getAbsolutePath()), e);
 		} else {
 			if (doLogExponentialBackoff(transientErrorCount)) {
 				// Don't remove the pack from the list, as the error may be
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index 7a51a5a..f49e57d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -133,6 +133,8 @@ public int compare(final PackFile a, final PackFile b) {
 
 	private volatile boolean invalid;
 
+	private volatile Exception invalidatingCause;
+
 	private boolean invalidBitmap;
 
 	private AtomicInteger transientErrorCount = new AtomicInteger();
@@ -177,8 +179,9 @@ public PackFile(final File packFile, int extensions) {
 
 	private synchronized PackIndex idx() throws IOException {
 		if (loadedIdx == null) {
-			if (invalid)
-				throw new PackInvalidException(packFile);
+			if (invalid) {
+				throw new PackInvalidException(packFile, invalidatingCause);
+			}
 
 			try {
 				final PackIndex idx = PackIndex.open(extFile(INDEX));
@@ -196,6 +199,7 @@ private synchronized PackIndex idx() throws IOException {
 				throw e;
 			} catch (IOException e) {
 				invalid = true;
+				invalidatingCause = e;
 				throw e;
 			}
 		}
@@ -644,7 +648,7 @@ synchronized boolean endWindowCache() {
 
 	private void doOpen() throws IOException {
 		if (invalid) {
-			throw new PackInvalidException(packFile);
+			throw new PackInvalidException(packFile, invalidatingCause);
 		}
 		try {
 			synchronized (readLock) {
@@ -654,13 +658,13 @@ private void doOpen() throws IOException {
 			}
 		} catch (InterruptedIOException e) {
 			// don't invalidate the pack, we are interrupted from another thread
-			openFail(false);
+			openFail(false, e);
 			throw e;
 		} catch (FileNotFoundException fn) {
 			// don't invalidate the pack if opening an existing file failed
 			// since it may be related to a temporary lack of resources (e.g.
 			// max open files)
-			openFail(!packFile.exists());
+			openFail(!packFile.exists(), fn);
 			throw fn;
 		} catch (EOFException | AccessDeniedException | NoSuchFileException
 				| CorruptObjectException | NoPackSignatureException
@@ -668,20 +672,21 @@ private void doOpen() throws IOException {
 				| UnsupportedPackIndexVersionException
 				| UnsupportedPackVersionException pe) {
 			// exceptions signaling permanent problems with a pack
-			openFail(true);
+			openFail(true, pe);
 			throw pe;
 		} catch (IOException | RuntimeException ge) {
 			// generic exceptions could be transient so we should not mark the
 			// pack invalid to avoid false MissingObjectExceptions
-			openFail(false);
+			openFail(false, ge);
 			throw ge;
 		}
 	}
 
-	private void openFail(boolean invalidate) {
+	private void openFail(boolean invalidate, Exception cause) {
 		activeWindows = 0;
 		activeCopyRawData = 0;
 		invalid = invalidate;
+		invalidatingCause = cause;
 		doClose();
 	}
 
@@ -708,7 +713,7 @@ ByteArrayWindow read(final long pos, int size) throws IOException {
 				// Detect the situation and throw a proper exception so that can be properly
 				// managed by the main packfile search loop and the Git client won't receive
 				// any failures.
-				throw new PackInvalidException(packFile);
+				throw new PackInvalidException(packFile, invalidatingCause);
 			}
 			if (length < pos + size)
 				size = (int) (length - pos);