Merge branch 'master' into stable-7.3

* master:
  FS.getFileStoreAttributes: cancel failed task executed asynchronously
  DfsReader/PackFile: Move represention to the packfile
  DfsPackFile: Remove unused getObjectCount method
  Prepare 7.2.2-SNAPSHOT builds
  JGit v7.2.1.202505142326-r
  AmazonS3: Do not accept DOCTYPE and entities
  ManifestParser: Do not accept DOCTYPE and entities
  FileReftableStack: ensure new reftable files aren't missed on NFS
  Encapsulate layout of reftable stack in FileReftableStack

Change-Id: I599cd8de2580552cec309f75bfaca9d574a67451
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
index f156b8c..b7a7ec2 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
@@ -107,13 +107,12 @@ private void printf(String fmt, Object... args) throws IOException {
 	@SuppressWarnings({ "nls", "boxing" })
 	private void writeStack() throws Exception {
 		File dir = new File(reftablePath);
-		File stackFile = new File(reftablePath + ".stack");
 
 		dir.mkdirs();
 
 		long start = System.currentTimeMillis();
-		try (FileReftableStack stack = new FileReftableStack(stackFile, dir,
-				null, () -> new Config())) {
+		try (FileReftableStack stack = new FileReftableStack(dir, null,
+				() -> new Config())) {
 
 			List<Ref> refs = readLsRemote().asList();
 			for (Ref r : refs) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
index fca27d3..0949d04 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
@@ -12,12 +12,16 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -221,4 +225,33 @@ public void testNormalizeEmptyPath() {
 		testNormalize("", "");
 		testNormalize("a/b", "a/b");
 	}
+
+	@Test
+	public void testXXE() throws Exception {
+		File externalEntity = File.createTempFile("injected", "xml");
+		externalEntity.deleteOnExit();
+		Files.write(externalEntity.toPath(),
+				"<evil>injected xml</evil>"
+						.getBytes(UTF_8),
+				StandardOpenOption.WRITE);
+		String baseUrl = "https://git.google.com/";
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<!DOCTYPE booo [ <!ENTITY foobar SYSTEM \"")
+				.append(externalEntity.getPath()).append("\"> ]>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("&foobar;")
+				.append("<project path=\"foo\" name=\"foo\" groups=\"a,test\" />")
+				.append("</manifest>");
+
+		IOException e = assertThrows(IOException.class,
+				() -> new ManifestParser(null, null, "master", baseUrl, null,
+						null)
+						.read(new ByteArrayInputStream(
+								xmlContent.toString().getBytes(UTF_8))));
+		assertTrue(e.getCause().getMessage().contains("DOCTYPE"));
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
index 6c79927..e8363ce 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
@@ -81,8 +81,7 @@ void writeBranches(FileReftableStack stack, String template, int start,
 	}
 
 	public void testCompaction(int N) throws Exception {
-		try (FileReftableStack stack = new FileReftableStack(
-				new File(reftableDir, "refs"), reftableDir, null,
+		try (FileReftableStack stack = new FileReftableStack(reftableDir, null,
 				() -> new Config())) {
 			writeBranches(stack, "refs/heads/branch%d", 0, N);
 			MergedReftable table = stack.getMergedReftable();
@@ -124,8 +123,7 @@ public void missingReftable() throws Exception {
 		// Can't delete in-use files on Windows.
 		assumeFalse(SystemReader.getInstance().isWindows());
 
-		try (FileReftableStack stack = new FileReftableStack(
-				new File(reftableDir, "refs"), reftableDir, null,
+		try (FileReftableStack stack = new FileReftableStack(reftableDir, null,
 				() -> new Config())) {
 			outer: for (int i = 0; i < 10; i++) {
 				final long next = stack.getMergedReftable().maxUpdateIndex()
@@ -152,8 +150,8 @@ public void missingReftable() throws Exception {
 			}
 		}
 		assertThrows(FileNotFoundException.class,
-				() -> new FileReftableStack(new File(reftableDir, "refs"),
-						reftableDir, null, () -> new Config()));
+				() -> new FileReftableStack(reftableDir, null,
+						() -> new Config()));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index b033177..58b4d3d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -142,7 +142,17 @@ public void read(InputStream inputStream) throws IOException {
 		xmlInRead++;
 		final XMLReader xr;
 		try {
-			xr = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+			SAXParserFactory spf = SAXParserFactory.newInstance();
+			spf.setFeature(
+					"http://xml.org/sax/features/external-general-entities", //$NON-NLS-1$
+					false);
+			spf.setFeature(
+					"http://xml.org/sax/features/external-parameter-entities", //$NON-NLS-1$
+					false);
+			spf.setFeature(
+					"http://apache.org/xml/features/disallow-doctype-decl", //$NON-NLS-1$
+					true);
+			xr = spf.newSAXParser().getXMLReader();
 		} catch (SAXException | ParserConfigurationException e) {
 			throw new IOException(JGitText.get().noXMLParserAvailable, e);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index f9c01b9..6339b03 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -405,7 +405,7 @@ private void addObjectsToPack(PackWriter pw, DfsReader ctx,
 				pw.addObject(obj);
 				obj.add(added);
 
-				src.representation(rep, id.offset, ctx, rev);
+				src.fillRepresentation(rep, id.offset, ctx, rev);
 				if (rep.getFormat() != PACK_DELTA)
 					continue;
 
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 9a95ddc..618969f 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
@@ -27,6 +27,9 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
@@ -49,6 +52,7 @@
 import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
 import org.eclipse.jgit.internal.storage.file.PackReverseIndexFactory;
 import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
+import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
 import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
 import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -59,6 +63,7 @@
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.BlockList;
 import org.eclipse.jgit.util.LongList;
 
 /**
@@ -71,6 +76,10 @@ public final class DfsPackFile extends BlockBasedFile {
 
 	private static final long REF_POSITION = 0;
 
+	private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
+			DfsObjectToPack a,
+			DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());
+
 	/**
 	 * Loader for the default file-based {@link PackBitmapIndex} implementation.
 	 */
@@ -455,25 +464,45 @@ long findOffset(DfsReader ctx, AnyObjectId id) throws IOException {
 		return idx(ctx).findOffset(id);
 	}
 
+	/**
+	 * Return objects in the list available in this pack, sorted in (pack,
+	 * offset) order.
+	 *
+	 * @param ctx
+	 *            a reader
+	 * @param objects
+	 *            objects we are looking for
+	 * @param skipFound
+	 *            ignore objects already found.
+	 * @return list of objects with pack and offset set.
+	 * @throws IOException
+	 *             an error occurred
+	 */
+	List<DfsObjectToPack> findAllFromPack(DfsReader ctx,
+			Iterable<ObjectToPack> objects, boolean skipFound)
+			throws IOException {
+		List<DfsObjectToPack> tmp = new BlockList<>();
+		for (ObjectToPack obj : objects) {
+			DfsObjectToPack otp = (DfsObjectToPack) obj;
+			if (skipFound && otp.isFound()) {
+				continue;
+			}
+			long p = idx(ctx).findOffset(otp);
+			if (p <= 0 || isCorrupt(p)) {
+				continue;
+			}
+			otp.setOffset(p);
+			tmp.add(otp);
+		}
+		Collections.sort(tmp, OFFSET_SORT);
+		return tmp;
+	}
+
 	void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
 			int matchLimit) throws IOException {
 		idx(ctx).resolve(matches, id, matchLimit);
 	}
 
-	/**
-	 * Obtain the total number of objects available in this pack. This method
-	 * relies on pack index, giving number of effectively available objects.
-	 *
-	 * @param ctx
-	 *            current reader for the calling thread.
-	 * @return number of objects in index of this pack, likewise in this pack
-	 * @throws IOException
-	 *             the index file cannot be loaded into memory.
-	 */
-	long getObjectCount(DfsReader ctx) throws IOException {
-		return idx(ctx).getObjectCount();
-	}
-
 	private byte[] decompress(long position, int sz, DfsReader ctx)
 			throws IOException, DataFormatException {
 		byte[] dstbuf;
@@ -1169,12 +1198,47 @@ long getIndexedObjectSize(DfsReader ctx, AnyObjectId id)
 		return sizeIdx.getSize(idxPosition);
 	}
 
-	void representation(DfsObjectRepresentation r, final long pos,
+	/**
+	 * Populates the representation object with the details of how the object at
+	 * "pos" is stored in this pack (e.g. whole or deltified, its packed
+	 * length).
+	 *
+	 * @param r
+	 *            represention object to carry data
+	 * @param offset
+	 *            offset in this pack of the object
+	 * @param ctx
+	 *            a reader
+	 * @throws IOException
+	 *             an error reading the object from disk
+	 */
+	void fillRepresentation(DfsObjectRepresentation r, long offset,
+			DfsReader ctx) throws IOException {
+		fillRepresentation(r, offset, ctx, getReverseIdx(ctx));
+	}
+
+	/**
+	 * Populates the representation object with the details of how the object at
+	 * "pos" is stored in this pack (e.g. whole or deltified, its packed
+	 * length).
+	 *
+	 * @param r
+	 *            represention object to carry data
+	 * @param offset
+	 *            offset in this pack of the object
+	 * @param ctx
+	 *            a reader
+	 * @param rev
+	 *            reverse index of this pack
+	 * @throws IOException
+	 *             an error reading the object from disk
+	 */
+	void fillRepresentation(DfsObjectRepresentation r, long offset,
 			DfsReader ctx, PackReverseIndex rev)
 			throws IOException {
-		r.offset = pos;
+		r.offset = offset;
 		final byte[] ib = ctx.tempId;
-		readFully(pos, ib, 0, 20, ctx);
+		readFully(offset, ib, 0, 20, ctx);
 		int c = ib[0] & 0xff;
 		int p = 1;
 		final int typeCode = (c >> 4) & 7;
@@ -1182,7 +1246,7 @@ void representation(DfsObjectRepresentation r, final long pos,
 			c = ib[p++] & 0xff;
 		}
 
-		long len = rev.findNextOffset(pos, length - 20) - pos;
+		long len = rev.findNextOffset(offset, length - 20) - offset;
 		switch (typeCode) {
 		case Constants.OBJ_COMMIT:
 		case Constants.OBJ_TREE:
@@ -1203,13 +1267,13 @@ void representation(DfsObjectRepresentation r, final long pos,
 				ofs += (c & 127);
 			}
 			r.format = StoredObjectRepresentation.PACK_DELTA;
-			r.baseId = rev.findObject(pos - ofs);
+			r.baseId = rev.findObject(offset - ofs);
 			r.length = len - p;
 			return;
 		}
 
 		case Constants.OBJ_REF_DELTA: {
-			readFully(pos + p, ib, 0, 20, ctx);
+			readFully(offset + p, ib, 0, 20, ctx);
 			r.format = StoredObjectRepresentation.PACK_DELTA;
 			r.baseId = ObjectId.fromRaw(ib);
 			r.length = len - p - 20;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index 62f6753..04288bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -38,8 +38,6 @@
 import org.eclipse.jgit.internal.storage.dfs.DfsReader.PackLoadListener.DfsBlockData;
 import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
-import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
 import org.eclipse.jgit.internal.storage.pack.CachedPack;
 import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
@@ -58,7 +56,6 @@
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.util.BlockList;
 
 /**
  * Reader to access repository content through.
@@ -619,10 +616,6 @@ public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
 		return new DfsObjectToPack(objectId, type);
 	}
 
-	private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
-			DfsObjectToPack a,
-			DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());
-
 	@Override
 	public void selectObjectRepresentation(PackWriter packer,
 			ProgressMonitor monitor, Iterable<ObjectToPack> objects)
@@ -642,16 +635,15 @@ private void trySelectRepresentation(PackWriter packer,
 			ProgressMonitor monitor, Iterable<ObjectToPack> objects,
 			List<DfsPackFile> packs, boolean skipFound) throws IOException {
 		for (DfsPackFile pack : packs) {
-			List<DfsObjectToPack> tmp = findAllFromPack(pack, objects, skipFound);
-			if (tmp.isEmpty())
+			List<DfsObjectToPack> inPack = pack.findAllFromPack(this, objects, skipFound);
+			if (inPack.isEmpty())
 				continue;
-			Collections.sort(tmp, OFFSET_SORT);
-			PackReverseIndex rev = pack.getReverseIdx(this);
 			DfsObjectRepresentation rep = new DfsObjectRepresentation(pack);
-			for (DfsObjectToPack otp : tmp) {
-				pack.representation(rep, otp.getOffset(), this, rev);
+			for (DfsObjectToPack otp : inPack) {
+				// Populate rep.{offset,length} from the pack
+				pack.fillRepresentation(rep, otp.getOffset(), this);
 				otp.setOffset(0);
-				packer.select(otp, rep);
+				packer.select(otp, rep); // Set otp.offset from rep
 				if (!otp.isFound()) {
 					otp.setFound();
 					monitor.update(1);
@@ -698,24 +690,7 @@ private static boolean checkGarbagePacks(Iterable<ObjectToPack> objects) {
 		return false;
 	}
 
-	private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
-			Iterable<ObjectToPack> objects, boolean skipFound)
-					throws IOException {
-		List<DfsObjectToPack> tmp = new BlockList<>();
-		PackIndex idx = pack.getPackIndex(this);
-		for (ObjectToPack obj : objects) {
-			DfsObjectToPack otp = (DfsObjectToPack) obj;
-			if (skipFound && otp.isFound()) {
-				continue;
-			}
-			long p = idx.findOffset(otp);
-			if (0 < p && !pack.isCorrupt(p)) {
-				otp.setOffset(p);
-				tmp.add(otp);
-			}
-		}
-		return tmp;
-	}
+
 
 	@Override
 	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
index b9e9e66..559d5a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
@@ -75,16 +75,11 @@ public class FileReftableDatabase extends RefDatabase {
 	private volatile boolean autoRefresh;
 
 	FileReftableDatabase(FileRepository repo) throws IOException {
-		this(repo, new File(new File(repo.getCommonDirectory(), Constants.REFTABLE),
-				Constants.TABLES_LIST));
-	}
-
-	FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
 		this.fileRepository = repo;
 		this.autoRefresh = repo.getConfig().getBoolean(
 				ConfigConstants.CONFIG_REFTABLE_SECTION,
 				ConfigConstants.CONFIG_KEY_AUTOREFRESH, false);
-		this.reftableStack = new FileReftableStack(refstackName,
+		this.reftableStack = new FileReftableStack(
 				new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
 			() -> fileRepository.fireEvent(new RefsChangedEvent()),
 			() -> fileRepository.getConfig());
@@ -683,32 +678,20 @@ private static Ref refForWrite(RevWalk rw, Ref r) throws IOException {
 	 *            the repository
 	 * @param writeLogs
 	 *            whether to write reflogs
-	 * @return a reftable based RefDB from an existing repository.
 	 * @throws IOException
 	 *             on IO error
 	 */
-	public static FileReftableDatabase convertFrom(FileRepository repo,
-			boolean writeLogs) throws IOException {
-		FileReftableDatabase newDb = null;
-		File reftableList = null;
-		try {
-			File reftableDir = new File(repo.getCommonDirectory(),
-					Constants.REFTABLE);
-			reftableList = new File(reftableDir, Constants.TABLES_LIST);
-			if (!reftableDir.isDirectory()) {
-				reftableDir.mkdir();
-			}
-
-			try (FileReftableStack stack = new FileReftableStack(reftableList,
-					reftableDir, null, () -> repo.getConfig())) {
-				stack.addReftable(rw -> writeConvertTable(repo, rw, writeLogs));
-			}
-			reftableList = null;
-		} finally {
-			if (reftableList != null) {
-				reftableList.delete();
-			}
+	public static void convertFrom(FileRepository repo, boolean writeLogs)
+			throws IOException {
+		File reftableDir = new File(repo.getCommonDirectory(),
+				Constants.REFTABLE);
+		if (!reftableDir.isDirectory()) {
+			reftableDir.mkdir();
 		}
-		return newDb;
+
+		try (FileReftableStack stack = new FileReftableStack(reftableDir, null,
+				() -> repo.getConfig())) {
+			stack.addReftable(rw -> writeConvertTable(repo, rw, writeLogs));
+		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
index b2c8892..6658575 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
@@ -42,6 +42,7 @@
 import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig;
 import org.eclipse.jgit.lib.CoreConfig.TrustStat;
 import org.eclipse.jgit.util.FileUtils;
@@ -69,7 +70,7 @@ private static class StackEntry {
 
 	private long lastNextUpdateIndex;
 
-	private final File stackPath;
+	private final File tablesListFile;
 
 	private final File reftableDir;
 
@@ -111,8 +112,6 @@ static class CompactionStats {
 	/**
 	 * Creates a stack corresponding to the list of reftables in the argument
 	 *
-	 * @param stackPath
-	 *            the filename for the stack.
 	 * @param reftableDir
 	 *            the dir holding the tables.
 	 * @param onChange
@@ -122,10 +121,10 @@ static class CompactionStats {
 	 * @throws IOException
 	 *             on I/O problems
 	 */
-	public FileReftableStack(File stackPath, File reftableDir,
+	public FileReftableStack(File reftableDir,
 			@Nullable Runnable onChange, Supplier<Config> configSupplier)
 			throws IOException {
-		this.stackPath = stackPath;
+		this.tablesListFile = new File(reftableDir, Constants.TABLES_LIST);
 		this.reftableDir = reftableDir;
 		this.stack = new ArrayList<>();
 		this.configSupplier = configSupplier;
@@ -244,7 +243,7 @@ void reload() throws IOException {
 		}
 
 		if (!success) {
-			throw new LockFailedException(stackPath);
+			throw new LockFailedException(tablesListFile);
 		}
 
 		mergedReftable = new MergedReftable(stack.stream()
@@ -288,14 +287,14 @@ private List<String> readTableNames() throws IOException {
 		List<String> names = new ArrayList<>(stack.size() + 1);
 		old = snapshot.get();
 		try (BufferedReader br = new BufferedReader(
-				new InputStreamReader(new FileInputStream(stackPath), UTF_8))) {
+				new InputStreamReader(new FileInputStream(tablesListFile), UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				if (!line.isEmpty()) {
 					names.add(line);
 				}
 			}
-			snapshot.compareAndSet(old, FileSnapshot.save(stackPath));
+			snapshot.compareAndSet(old, FileSnapshot.save(tablesListFile));
 		} catch (FileNotFoundException e) {
 			// file isn't there: empty repository.
 			snapshot.compareAndSet(old, FileSnapshot.MISSING_FILE);
@@ -315,15 +314,16 @@ boolean isUpToDate() throws IOException {
 				break;
 			case AFTER_OPEN:
 				try (InputStream stream = Files
-						.newInputStream(stackPath.toPath())) {
-					// open the tables.list file to refresh attributes (on some
-					// NFS clients)
+						.newInputStream(reftableDir.toPath())) {
+					// open the refs/reftable/ directory to refresh attributes
+					// of reftable files and the tables.list file listing their
+					// names (on some NFS clients)
 				} catch (FileNotFoundException | NoSuchFileException e) {
 					// ignore
 				}
 				//$FALL-THROUGH$
 			case ALWAYS:
-				if (!snapshot.get().isModified(stackPath)) {
+				if (!snapshot.get().isModified(tablesListFile)) {
 					return true;
 				}
 				break;
@@ -387,7 +387,7 @@ private String filename(long low, long high) {
 	 */
 	@SuppressWarnings("nls")
 	public boolean addReftable(Writer w) throws IOException {
-		LockFile lock = new LockFile(stackPath);
+		LockFile lock = new LockFile(tablesListFile);
 		try {
 			if (!lock.lockForAppend()) {
 				return false;
@@ -398,8 +398,7 @@ public boolean addReftable(Writer w) throws IOException {
 
 			String fn = filename(nextUpdateIndex(), nextUpdateIndex());
 
-			File tmpTable = File.createTempFile(fn + "_", ".ref",
-					stackPath.getParentFile());
+			File tmpTable = File.createTempFile(fn + "_", ".ref", reftableDir);
 
 			ReftableWriter.Stats s;
 			try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
@@ -453,7 +452,7 @@ private File compactLocked(int first, int last) throws IOException {
 		String fn = filename(first, last);
 
 		File tmpTable = File.createTempFile(fn + "_", ".ref", //$NON-NLS-1$//$NON-NLS-2$
-				stackPath.getParentFile());
+				reftableDir);
 		try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
 			ReftableCompactor c = new ReftableCompactor(fos)
 					.setConfig(reftableConfig())
@@ -497,7 +496,7 @@ boolean compactRange(int first, int last) throws IOException {
 		if (first >= last) {
 			return true;
 		}
-		LockFile lock = new LockFile(stackPath);
+		LockFile lock = new LockFile(tablesListFile);
 
 		File tmpTable = null;
 		List<LockFile> subtableLocks = new ArrayList<>();
@@ -526,7 +525,7 @@ boolean compactRange(int first, int last) throws IOException {
 
 			tmpTable = compactLocked(first, last);
 
-			lock = new LockFile(stackPath);
+			lock = new LockFile(tablesListFile);
 			if (!lock.lock()) {
 				return false;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index aaf9f8a..9d9f549 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -760,6 +760,15 @@ void list() throws IOException {
 						SAXParserFactory saxParserFactory = SAXParserFactory
 								.newInstance();
 						saxParserFactory.setNamespaceAware(true);
+						saxParserFactory.setFeature(
+								"http://xml.org/sax/features/external-general-entities", //$NON-NLS-1$
+								false);
+						saxParserFactory.setFeature(
+								"http://xml.org/sax/features/external-parameter-entities", //$NON-NLS-1$
+								false);
+						saxParserFactory.setFeature(
+								"http://apache.org/xml/features/disallow-doctype-decl", //$NON-NLS-1$
+								true);
 						xr = saxParserFactory.newSAXParser().getXMLReader();
 					} catch (SAXException | ParserConfigurationException e) {
 						throw new IOException(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 59bbacf..6a40fad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -363,6 +363,7 @@ public static FileStoreAttributes get(Path path) {
 
 		private static FileStoreAttributes getFileStoreAttributes(Path dir) {
 			FileStore s;
+			CompletableFuture<Optional<FileStoreAttributes>> f = null;
 			try {
 				if (Files.exists(dir)) {
 					s = Files.getFileStore(dir);
@@ -385,7 +386,7 @@ private static FileStoreAttributes getFileStoreAttributes(Path dir) {
 					return FALLBACK_FILESTORE_ATTRIBUTES;
 				}
 
-				CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
+				f = CompletableFuture
 						.supplyAsync(() -> {
 							Lock lock = locks.computeIfAbsent(s,
 									l -> new ReentrantLock());
@@ -455,10 +456,13 @@ private static FileStoreAttributes getFileStoreAttributes(Path dir) {
 				}
 				// fall through and return fallback
 			} catch (IOException | ExecutionException | CancellationException e) {
+				cancel(f);
 				LOG.error(e.getMessage(), e);
 			} catch (TimeoutException | SecurityException e) {
+				cancel(f);
 				// use fallback
 			} catch (InterruptedException e) {
+				cancel(f);
 				LOG.error(e.getMessage(), e);
 				Thread.currentThread().interrupt();
 			}
@@ -467,6 +471,13 @@ private static FileStoreAttributes getFileStoreAttributes(Path dir) {
 			return FALLBACK_FILESTORE_ATTRIBUTES;
 		}
 
+		private static void cancel(
+				CompletableFuture<Optional<FileStoreAttributes>> f) {
+			if (f != null) {
+				f.cancel(true);
+			}
+		}
+
 		@SuppressWarnings("boxing")
 		private static Duration measureMinimalRacyInterval(Path dir) {
 			LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$