Merge branch 'stable-7.0' into stable-7.1

* stable-7.0:
  FileSnapshot: fix warnings
  Optionally.Hard: avoid Optional creation on every use,
  Pack: fix threading bug getting idx
  Fix potential NPE in TreeWalk#getFilterCommandDefinition
  Advertise "agent" capability when using protocol v2
  FileSnapshot: silence "Stale file handle" exceptions

Change-Id: I4fdea7450f27eebfa7ae08002fd51e67b58bf6bb
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
index 064d863..aaecfd2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
@@ -502,15 +502,15 @@ public void testV2Capabilities() throws Exception {
 		assertThat(hook.capabilitiesRequest, notNullValue());
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-				Arrays.asList(pckIn.readString(), pckIn.readString(),
-						pckIn.readString()),
+				Arrays.asList(pckIn.readString(),pckIn.readString(),
+						pckIn.readString(), pckIn.readString()),
 				// TODO(jonathantanmy) This check is written this way
 				// to make it simple to see that we expect this list of
 				// capabilities, but probably should be loosened to
 				// allow additional commands to be added to the list,
 				// and additional capabilities to be added to existing
 				// commands without requiring test changes.
-				hasItems("ls-refs", "fetch=shallow", "server-option"));
+				hasItems("agent=" + UserAgent.get() ,"ls-refs", "fetch=shallow", "server-option"));
 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
@@ -536,7 +536,7 @@ private void checkAdvertisedIfAllowed(String configSection, String configName,
 				lines.add(line);
 			}
 		}
-		assertThat(lines, containsInAnyOrder("ls-refs", "fetch", "server-option"));
+		assertThat(lines, containsInAnyOrder("ls-refs", "fetch", "server-option", "agent=" + UserAgent.get()));
 	}
 
 	private void checkUnadvertisedIfUnallowed(String configSection,
@@ -643,9 +643,9 @@ public void testV2CapabilitiesRefInWantNotAdvertisedIfAdvertisingForbidden() thr
 
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-				Arrays.asList(pckIn.readString(), pckIn.readString(),
+				Arrays.asList(pckIn.readString(),pckIn.readString(), pckIn.readString(),
 						pckIn.readString()),
-				hasItems("ls-refs", "fetch=shallow", "server-option"));
+				hasItems("agent="+ UserAgent.get(),"ls-refs", "fetch=shallow", "server-option"));
 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index 8f83a37..b8911f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -28,6 +28,7 @@
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FS.FileStoreAttributes;
 import org.slf4j.Logger;
@@ -546,11 +547,19 @@ private FileStoreAttributes fileStoreAttributeCache() {
 
 	private static BasicFileAttributes getFileAttributes(File path) throws NoSuchElementException {
 		try {
-			return FS.DETECTED.fileAttributes(path);
+			try {
+				return FS.DETECTED.fileAttributes(path);
+			} catch (IOException e) {
+				if (!FileUtils.isStaleFileHandle(e)) {
+					throw e;
+				}
+			}
 		} catch (NoSuchFileException e) {
+			// ignore
 		} catch (FileSystemException e) {
-			if (!e.getMessage().endsWith("Not a directory")) {
-				LOG.error(e.getMessage(), e);
+			String msg = e.getMessage();
+			if (!msg.endsWith("Not a directory")) { //$NON-NLS-1$
+				LOG.error(msg, e);
 			}
 		} catch (IOException e) {
 			LOG.error(e.getMessage(), e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
index 61577a9..8d2a863 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
@@ -119,7 +119,7 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
 
 	private byte[] packChecksum;
 
-	private volatile Optionally<PackIndex> loadedIdx = Optionally.empty();
+	private Optionally<PackIndex> loadedIdx = Optionally.empty();
 
 	private Optionally<PackReverseIndex> reverseIdx = Optionally.empty();
 
@@ -159,60 +159,54 @@ public Pack(Config cfg, File packFile, @Nullable PackFile bitmapIdxFile) {
 		length = Long.MAX_VALUE;
 	}
 
-	private PackIndex idx() throws IOException {
+	private synchronized PackIndex idx() throws IOException {
 		Optional<PackIndex> optional = loadedIdx.getOptional();
 		if (optional.isPresent()) {
 			return optional.get();
 		}
-		synchronized (this) {
-			optional = loadedIdx.getOptional();
-			if (optional.isPresent()) {
-				return optional.get();
+		if (invalid) {
+			throw new PackInvalidException(packFile, invalidatingCause);
+		}
+		try {
+			long start = System.currentTimeMillis();
+			PackFile idxFile = packFile.create(INDEX);
+			PackIndex idx = PackIndex.open(idxFile);
+			if (LOG.isDebugEnabled()) {
+				LOG.debug(String.format(
+						"Opening pack index %s, size %.3f MB took %d ms", //$NON-NLS-1$
+						idxFile.getAbsolutePath(),
+						Float.valueOf(idxFile.length()
+								/ (1024f * 1024)),
+						Long.valueOf(System.currentTimeMillis()
+								- start)));
 			}
-			if (invalid) {
-				throw new PackInvalidException(packFile, invalidatingCause);
-			}
-			try {
-				long start = System.currentTimeMillis();
-				PackFile idxFile = packFile.create(INDEX);
-				PackIndex idx = PackIndex.open(idxFile);
-				if (LOG.isDebugEnabled()) {
-					LOG.debug(String.format(
-							"Opening pack index %s, size %.3f MB took %d ms", //$NON-NLS-1$
-							idxFile.getAbsolutePath(),
-							Float.valueOf(idxFile.length()
-									/ (1024f * 1024)),
-							Long.valueOf(System.currentTimeMillis()
-									- start)));
-				}
-
 				if (packChecksum == null) {
-					packChecksum = idx.getChecksum();
-					fileSnapshot.setChecksum(
-							ObjectId.fromRaw(packChecksum));
-				} else if (!Arrays.equals(packChecksum,
-						idx.getChecksum())) {
-					throw new PackMismatchException(MessageFormat
-							.format(JGitText.get().packChecksumMismatch,
-									packFile.getPath(),
-									PackExt.PACK.getExtension(),
-									Hex.toHexString(packChecksum),
-									PackExt.INDEX.getExtension(),
-									Hex.toHexString(idx.getChecksum())));
-				}
-				loadedIdx = optionally(idx);
-				return idx;
-			} catch (InterruptedIOException e) {
-				// don't invalidate the pack, we are interrupted from
-				// another thread
-				throw e;
-			} catch (IOException e) {
-				invalid = true;
-				invalidatingCause = e;
-				throw e;
+				packChecksum = idx.getChecksum();
+				fileSnapshot.setChecksum(
+						ObjectId.fromRaw(packChecksum));
+			} else if (!Arrays.equals(packChecksum,
+					idx.getChecksum())) {
+				throw new PackMismatchException(MessageFormat
+						.format(JGitText.get().packChecksumMismatch,
+								packFile.getPath(),
+								PackExt.PACK.getExtension(),
+								Hex.toHexString(packChecksum),
+								PackExt.INDEX.getExtension(),
+							Hex.toHexString(idx.getChecksum())));
 			}
+			loadedIdx = optionally(idx);
+			return idx;
+		} catch (InterruptedIOException e) {
+			// don't invalidate the pack, we are interrupted from
+			// another thread
+			throw e;
+		} catch (IOException e) {
+			invalid = true;
+			invalidatingCause = e;
+			throw e;
 		}
 	}
+
 	/**
 	 * Get the File object which locates this pack on disk.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
index 3447f66..270b760 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
@@ -53,24 +53,24 @@ public class Hard<T> implements Optionally<T> {
 		/**
 		 * The mutable optional object
 		 */
-		protected T element;
+		protected Optional<T> optional;
 
 		/**
 		 * @param element
 		 *            the mutable optional object
 		 */
 		public Hard(T element) {
-			this.element = element;
+			optional = Optional.ofNullable(element);
 		}
 
 		@Override
 		public void clear() {
-			element = null;
+			optional = Optional.empty();
 		}
 
 		@Override
 		public Optional<T> getOptional() {
-			return Optional.ofNullable(element);
+			return optional;
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 6013832..d972067 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -1394,6 +1394,7 @@ private List<String> getV2CapabilityAdvertisement() {
 		if (transferConfig.isAdvertiseObjectInfo()) {
 			caps.add(COMMAND_OBJECT_INFO);
 		}
+		caps.add(OPTION_AGENT + "=" + UserAgent.get());
 
 		return caps;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index aaac2a7..b789c51 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -1587,6 +1587,9 @@ public String getSmudgeCommand(Attributes attributes) throws IOException {
 	 */
 	private String getFilterCommandDefinition(String filterDriverName,
 			String filterCommandType) {
+		if (config == null) {
+			return null;
+		}
 		String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$
 		String filterCommand = filterCommandsByNameDotType.get(key);
 		if (filterCommand != null)