Merge "DfsReaderIoStats: Add Commit Graph fields into DfsReaderIoStats"
diff --git a/Documentation/config-options.md b/Documentation/config-options.md
index 5b61df0..612b845 100644
--- a/Documentation/config-options.md
+++ b/Documentation/config-options.md
@@ -87,6 +87,7 @@
 | `pack.bitmapContiguousCommitCount` | `100` | ⃞ | Count of most recent commits for which to build bitmaps. |
 | `pack.bitmapDistantCommitSpan` | `5000` | ⃞ | Span of commits when building bitmaps for distant history. |
 | `pack.bitmapExcessiveBranchCount` | `100` | ⃞ | The count of branches deemed "excessive". If the count of branches in a repository exceeds this number and bitmaps are enabled, "inactive" branches will have fewer bitmaps than "active" branches. |
+| `pack.bitmapExcludedRefsPrefixes` | | ⃞ | The refs prefixes to be excluded when building bitmaps. May be specified more than once to exclude multiple prefixes. |
 | `pack.bitmapInactiveBranchAgeInDays` | `90` | ⃞ | Age in days that marks a branch as "inactive" for bitmap creation. |
 | `pack.bitmapRecentCommitCount` | `20000`  | ⃞ | Count at which to switch from `bitmapRecentCommitSpan` to `bitmapDistantCommitSpan`. |
 | `pack.bitmapRecentCommitSpan` | `100` | ⃞ | Span of commits when building bitmaps for recent history. |
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
index 5e87b8f..12773c2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
@@ -209,6 +209,20 @@
 		assertTrue(fs2.equals(fs1));
 	}
 
+	@Test
+	public void snapshotAndFileMissingIsNotModified() throws Exception {
+		File doesNotExist = trash.resolve("DOES_NOT_EXIST").toFile();
+		FileSnapshot missing = FileSnapshot.save(doesNotExist);
+		assertFalse(missing.isModified(doesNotExist));
+	}
+
+	@Test
+	public void missingFileEquals() throws Exception {
+		FileSnapshot missing = FileSnapshot.save(
+				trash.resolve("DOES_NOT_EXIST").toFile());
+		assertTrue(missing.equals(FileSnapshot.MISSING_FILE));
+	}
+
 	@SuppressWarnings("boxing")
 	@Test
 	public void detectFileModified() throws IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java
index 358e19e..dbc9dba 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java
@@ -90,7 +90,7 @@
 		bb.update(tip);
 
 		assertTrue(gc.shouldWriteCommitGraphWhenGc());
-		gc.gc();
+		gc.gc().get();
 		File graphFile = new File(repo.getObjectsDirectory(),
 				Constants.INFO_COMMIT_GRAPH);
 		assertGraphFile(graphFile);
@@ -103,7 +103,7 @@
 		bb.update(tip);
 
 		assertFalse(gc.shouldWriteCommitGraphWhenGc());
-		gc.gc();
+		gc.gc().get();
 		File graphFile = new File(repo.getObjectsDirectory(),
 				Constants.INFO_COMMIT_GRAPH);
 		assertFalse(graphFile.exists());
@@ -123,21 +123,21 @@
 		config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
 				ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
 
-		gc.gc();
+		gc.gc().get();
 		assertFalse(graphFile.exists());
 
 		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_COMMIT_GRAPH, true);
 		config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
 				ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, false);
-		gc.gc();
+		gc.gc().get();
 		assertFalse(graphFile.exists());
 
 		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_COMMIT_GRAPH, false);
 		config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
 				ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, false);
-		gc.gc();
+		gc.gc().get();
 		assertFalse(graphFile.exists());
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index ddc4a9d..d74766d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -247,7 +247,7 @@
 		assertTrue(curs.getCommitGraph().isEmpty());
 		commitFile("file.txt", "content", "master");
 		GC gc = new GC(db);
-		gc.gc();
+		gc.gc().get();
 		assertTrue(curs.getCommitGraph().isPresent());
 
 		db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
@@ -286,7 +286,7 @@
 		// add commit-graph
 		commitFile("file.txt", "content", "master");
 		GC gc = new GC(db);
-		gc.gc();
+		gc.gc().get();
 		File file = new File(db.getObjectsDirectory(),
 				Constants.INFO_COMMIT_GRAPH);
 		assertTrue(file.exists());
@@ -296,7 +296,7 @@
 
 		// update commit-graph
 		commitFile("file2.txt", "content", "master");
-		gc.gc();
+		gc.gc().get();
 		assertEquals(2, dir.getCommitGraph().get().getCommitCnt());
 
 		// delete commit-graph
@@ -311,7 +311,7 @@
 		assertTrue(dir.getCommitGraph().isEmpty());
 
 		// add commit-graph again
-		gc.gc();
+		gc.gc().get();
 		assertTrue(dir.getCommitGraph().isPresent());
 		assertEquals(2, dir.getCommitGraph().get().getCommitCnt());
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
index bb56c84..0c09ad1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
@@ -83,6 +83,40 @@
 	}
 
 	@Test
+	public void testBitmapDoesNotIncludeAnnotatedTags() throws Exception {
+		/*
+		 * Make sure that the bitmap generated for the following commit
+		 * graph does not include commit2 because it is not reachable by any
+		 * heads, despite being reachable from tag1 through the annotated-tag1.
+		 *
+		 * refs/heads/main
+		 *   ^
+		 *   |
+		 *  commit1 <-- commit2 <- annotated-tag1 <- tag1
+		 *   ^
+		 *   |
+		 *  commit0
+		 */
+		String mainBranch = "refs/heads/main";
+		BranchBuilder bb = tr.branch(mainBranch);
+
+		String commitMsg = "commit msg";
+		String fileBody = "file body";
+		String tagName = "tag1";
+		bb.commit().message(commitMsg + " 1").add("file1", fileBody).create();
+		RevCommit commit1 = bb.commit().message(commitMsg + " 2").add("file2", fileBody).create();
+		RevCommit commit2 = bb.commit().message(commitMsg + " 3").add("file3", fileBody).create();
+		tr.lightweightTag(tagName, tr.tag(tagName, commit2));
+		tr.branch(mainBranch).update(commit1);
+
+		gc.setExpireAgeMillis(0);
+		gc.gc();
+
+		// Create only 2 bitmaps, for commit0 and commit1, excluding commit2
+		assertEquals(2, gc.getStatistics().numberOfBitmaps);
+	}
+
+	@Test
 	public void testBitmapSpansWithMerges() throws Exception {
 		/*
 		 * Commits that are merged. Since 55 is in the oldest history it is
@@ -187,6 +221,24 @@
 	}
 
 	@Test
+	public void testBitmapsForExcludedBranches() throws Exception {
+		createNewCommitOnNewBranch("main");
+		createNewCommitOnNewBranch("other");
+		PackConfig packConfig = new PackConfig();
+		packConfig.setBitmapExcludedRefsPrefixes(new String[] { "refs/heads/other" });
+		gc.setPackConfig(packConfig);
+		gc.gc();
+		assertEquals(1,
+			gc.getStatistics().numberOfBitmaps);
+	}
+
+	private void createNewCommitOnNewBranch(String branchName) throws Exception {
+		BranchBuilder bb = tr.branch("refs/heads/" + branchName);
+		String msg = "New branch " + branchName;
+		bb.commit().message(msg).add("some-filename.txt", msg).create();
+	}
+
+	@Test
 	public void testSelectionOrderingWithChains() throws Exception {
 		/*-
 		 * Create a history like this, where 'N' is the number of seconds from
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TextProgressMonitorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TextProgressMonitorTest.java
new file mode 100644
index 0000000..55ca2cd
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TextProgressMonitorTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023, SAP SE or an SAP affiliate company and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TextProgressMonitorTest {
+
+	private TextProgressMonitor m;
+
+	private ByteArrayOutputStream buf;
+
+	@Before
+	public void setup() {
+		buf = new ByteArrayOutputStream();
+		m = new TextProgressMonitor(
+				new OutputStreamWriter(buf, StandardCharsets.UTF_8));
+	}
+
+	@Test
+	public void testSimple() throws Exception {
+		m.beginTask("task", 10);
+		for (int i = 0; i < 10; i++) {
+			m.update(1);
+		}
+		m.endTask();
+		Assert.assertArrayEquals(
+				new String[] { "", "task:                    10% ( 1/10)",
+						"task:                    20% ( 2/10)",
+						"task:                    30% ( 3/10)",
+						"task:                    40% ( 4/10)",
+						"task:                    50% ( 5/10)",
+						"task:                    60% ( 6/10)",
+						"task:                    70% ( 7/10)",
+						"task:                    80% ( 8/10)",
+						"task:                    90% ( 9/10)",
+						"task:                   100% (10/10)",
+						"task:                   100% (10/10)\n" },
+				bufLines());
+	}
+
+	@Test
+	public void testLargeNumbers() throws Exception {
+		m.beginTask("task", 1_000_000_000);
+		for (int i = 0; i < 10; i++) {
+			m.update(100_000_000);
+		}
+		m.endTask();
+		Assert.assertArrayEquals(
+				new String[] { "",
+						"task:                    10% ( 100000000/1000000000)",
+						"task:                    20% ( 200000000/1000000000)",
+						"task:                    30% ( 300000000/1000000000)",
+						"task:                    40% ( 400000000/1000000000)",
+						"task:                    50% ( 500000000/1000000000)",
+						"task:                    60% ( 600000000/1000000000)",
+						"task:                    70% ( 700000000/1000000000)",
+						"task:                    80% ( 800000000/1000000000)",
+						"task:                    90% ( 900000000/1000000000)",
+						"task:                   100% (1000000000/1000000000)",
+						"task:                   100% (1000000000/1000000000)\n" },
+				bufLines());
+	}
+
+	String[] bufLines() throws UnsupportedEncodingException {
+		String s = new String(buf.toString(StandardCharsets.UTF_8.name()));
+		return s.split("\r");
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
index 05e023b..6c80145 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
@@ -309,7 +309,7 @@
 		db.getConfig().setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
 				ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
 		GC gc = new GC(db);
-		gc.gc();
+		gc.gc().get();
 	}
 
 	private void reinitializeRevWalk() {
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index cc152f9..107b7d5 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,6 +3,12 @@
     <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
         <filter id="1142947843">
             <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
                 <message_argument value="6.1.1"/>
                 <message_argument value="CONFIG_KEY_TRUST_PACKED_REFS_STAT"/>
             </message_arguments>
@@ -32,4 +38,38 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="getReflogReader(Ref)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
+                <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="getBitmapExcludedRefsPrefixes()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="setBitmapExcludedRefsPrefixes(String[])"/>
+            </message_arguments>
+        </filter>
+    </resource>
 </component>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index e7a8be0..3c772c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -151,6 +151,9 @@
 			if (fetchHead == null) {
 				return;
 			}
+			if (revWalk.parseAny(fetchHead).getType() == Constants.OBJ_BLOB) {
+				return;
+			}
 			walk.setTree(revWalk.parseTree(fetchHead));
 			while (walk.next()) {
 				try (Repository submoduleRepo = walk.getRepository()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 5a8207e..583b8b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -66,7 +66,16 @@
 	InMemoryRepository(Builder builder) {
 		super(builder);
 		objdb = new MemObjDatabase(this);
-		refdb = new MemRefDatabase();
+		refdb = createRefDatabase();
+	}
+
+	/**
+	 * Creates a new in-memory ref database.
+	 *
+	 * @return a new in-memory reference database.
+	 */
+	protected MemRefDatabase createRefDatabase() {
+		return new MemRefDatabase();
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 3ebce6c..3e92cdd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -31,6 +31,7 @@
 import java.util.Objects;
 import java.util.Set;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.attributes.AttributesNode;
@@ -530,6 +531,12 @@
 		return new ReflogReaderImpl(this, ref.getName());
 	}
 
+	@Override
+	public @NonNull ReflogReader getReflogReader(@NonNull Ref ref)
+			throws IOException {
+		return new ReflogReaderImpl(this, ref.getName());
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public AttributesNodeProvider createAttributesNodeProvider() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 273d658..d41aea4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -800,6 +800,10 @@
 		Set<ObjectId> tagTargets = new HashSet<>();
 		Set<ObjectId> indexObjects = listNonHEADIndexObjects();
 
+		Set<ObjectId> refsToExcludeFromBitmap = repo.getRefDatabase()
+				.getRefsByPrefix(pconfig.getBitmapExcludedRefsPrefixes())
+				.stream().map(Ref::getObjectId).collect(Collectors.toSet());
+
 		for (Ref ref : refsBefore) {
 			checkCancelled();
 			nonHeads.addAll(listRefLogObjects(ref, 0));
@@ -844,7 +848,7 @@
 		Pack heads = null;
 		if (!allHeadsAndTags.isEmpty()) {
 			heads = writePack(allHeadsAndTags, PackWriter.NONE, allTags,
-					tagTargets, excluded);
+					refsToExcludeFromBitmap, tagTargets, excluded);
 			if (heads != null) {
 				ret.add(heads);
 				excluded.add(0, heads.getIndex());
@@ -852,13 +856,13 @@
 		}
 		if (!nonHeads.isEmpty()) {
 			Pack rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE,
-					tagTargets, excluded);
+					PackWriter.NONE, tagTargets, excluded);
 			if (rest != null)
 				ret.add(rest);
 		}
 		if (!txnHeads.isEmpty()) {
 			Pack txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE,
-					null, excluded);
+					PackWriter.NONE, null, excluded);
 			if (txn != null)
 				ret.add(txn);
 		}
@@ -1127,10 +1131,7 @@
 	 * @throws IOException
 	 */
 	private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException {
-		ReflogReader reflogReader = repo.getReflogReader(ref.getName());
-		if (reflogReader == null) {
-			return Collections.emptySet();
-		}
+		ReflogReader reflogReader = repo.getReflogReader(ref);
 		List<ReflogEntry> rlEntries = reflogReader
 				.getReverseEntries();
 		if (rlEntries == null || rlEntries.isEmpty())
@@ -1234,6 +1235,7 @@
 
 	private Pack writePack(@NonNull Set<? extends ObjectId> want,
 			@NonNull Set<? extends ObjectId> have, @NonNull Set<ObjectId> tags,
+			@NonNull Set<ObjectId> excludedRefsTips,
 			Set<ObjectId> tagTargets, List<ObjectIdSet> excludeObjects)
 			throws IOException {
 		checkCancelled();
@@ -1265,7 +1267,8 @@
 			if (excludeObjects != null)
 				for (ObjectIdSet idx : excludeObjects)
 					pw.excludeObjects(idx);
-			pw.preparePack(pm, want, have, PackWriter.NONE, tags);
+			pw.preparePack(pm, want, have, PackWriter.NONE,
+					union(tags, excludedRefsTips));
 			if (pw.getObjectCount() == 0)
 				return null;
 			checkCancelled();
@@ -1378,6 +1381,15 @@
 		}
 	}
 
+	private Set<? extends ObjectId> union(Set<ObjectId> tags,
+			Set<ObjectId> excludedRefsHeadsTips) {
+		HashSet<ObjectId> unionSet = new HashSet<>(
+				tags.size() + excludedRefsHeadsTips.size());
+		unionSet.addAll(tags);
+		unionSet.addAll(excludedRefsHeadsTips);
+		return unionSet;
+	}
+
 	private void checkCancelled() throws CancelledException {
 		if (pm.isCancelled() || Thread.currentThread().isInterrupted()) {
 			throw new CancelledException(JGitText.get().operationCanceled);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
index b9af83d..326c5f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
@@ -254,7 +254,7 @@
 					// refresh directory to work around NFS caching issue
 				}
 				return getSizeWithoutRefresh(curs, id);
-			} catch (FileNotFoundException e) {
+			} catch (FileNotFoundException unused) {
 				if (fileFor(id).exists()) {
 					throw noFile;
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
index 771d574..5a18f1e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
@@ -408,6 +408,9 @@
 		List<RevCommit> newWantsByNewest = new ArrayList<>(want.size());
 		Set<RevCommit> newWants = new HashSet<>(want.size());
 		for (AnyObjectId objectId : want) {
+			if(excludeFromBitmapSelection.contains(objectId)) {
+				continue;
+			}
 			RevObject ro = rw.peel(rw.parseAny(objectId));
 			if (!(ro instanceof RevCommit) || reuse.contains(ro)
 					|| excludeFromBitmapSelection.contains(ro)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
index 2caefa4..49e295a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
@@ -176,7 +176,7 @@
 				}
 			} else {
 				// Display once per second or when 1% is done.
-				int currPercent = lastWork * 100 / totalWork;
+				int currPercent = Math.round(lastWork * 100F / totalWork);
 				if (display) {
 					pm.onUpdate(taskName, lastWork, totalWork, currPercent);
 					output = true;
@@ -201,8 +201,8 @@
 				if (totalWork == UNKNOWN) {
 					pm.onEndTask(taskName, lastWork);
 				} else {
-					int pDone = lastWork * 100 / totalWork;
-					pm.onEndTask(taskName, lastWork, totalWork, pDone);
+					int currPercent = Math.round(lastWork * 100F / totalWork);
+					pm.onEndTask(taskName, lastWork, totalWork, currPercent);
 				}
 			}
 			if (timerFuture != null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index f63f310..ed4bf31 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -721,6 +721,12 @@
 	public static final String CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT = "bitmapexcessivebranchcount";
 
 	/**
+	 * The "pack.bitmapExcludedRefsPrefixes" key
+	 * @since 5.13.2
+	 */
+	public static final String CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES = "bitmapexcludedrefsprefixes";
+
+	/**
 	 * The "pack.bitmapInactiveBranchAgeInDays" key
 	 * @since 5.8
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index e594e52..db2571c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -1695,6 +1695,22 @@
 			throws IOException;
 
 	/**
+	 * Get the reflog reader. Subclasses should override this method and provide
+	 * a more efficient implementation.
+	 *
+	 * @param ref
+	 *            a Ref
+	 * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref,
+	 *         or {@code null} if the ref does not exist.
+	 * @throws IOException
+	 * @since 5.13.2
+	 */
+	public @Nullable ReflogReader getReflogReader(@NonNull	Ref ref)
+			throws IOException {
+		return getReflogReader(ref.getName());
+	}
+
+	/**
 	 * Return the information stored in the file $GIT_DIR/MERGE_MSG. In this
 	 * file operations triggering a merge will store a template for the commit
 	 * message of the merge commit.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index 6aa8be6..a10f6cf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -16,6 +16,7 @@
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BUILD_BITMAPS;
@@ -226,6 +227,14 @@
 	public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90;
 
 	/**
+	 * Default refs prefixes excluded from the calculation of pack bitmaps.
+	 *
+	 * @see #setBitmapExcludedRefsPrefixes(String[])
+	 * @since 5.13.2
+	 */
+	public static final String[] DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES = new String[0];
+
+	/**
 	 * Default max time to spend during the search for reuse phase. This
 	 * optimization is disabled by default: {@value}
 	 *
@@ -285,6 +294,8 @@
 
 	private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS;
 
+	private String[] bitmapExcludedRefsPrefixes = DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES;
+
 	private Duration searchForReuseTimeout = DEFAULT_SEARCH_FOR_REUSE_TIMEOUT;
 
 	private boolean cutDeltaChains;
@@ -1145,6 +1156,27 @@
 	}
 
 	/**
+	 * Get the refs prefixes excluded from the Bitmap.
+	 *
+	 * @return the refs prefixes excluded from the Bitmap.
+	 * @since 5.13.2
+	 */
+	public String[] getBitmapExcludedRefsPrefixes() {
+		return bitmapExcludedRefsPrefixes;
+	}
+
+	/**
+	 * Set the refs prefixes excluded from the Bitmap.
+	 *
+	 * @param excludedRefsPrefixes
+	 *            the refs prefixes excluded from the Bitmap.
+	 * @since 5.13.2
+	 */
+	public void setBitmapExcludedRefsPrefixes(String[] excludedRefsPrefixes) {
+		bitmapExcludedRefsPrefixes = excludedRefsPrefixes;
+	}
+
+	/**
 	 * Set the max time to spend during the search for reuse phase.
 	 *
 	 * @param timeout
@@ -1220,6 +1252,12 @@
 		setBitmapInactiveBranchAgeInDays(rc.getInt(CONFIG_PACK_SECTION,
 				CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS,
 				getBitmapInactiveBranchAgeInDays()));
+		String[] excludedRefsPrefixesArray = rc.getStringList(CONFIG_PACK_SECTION,
+			null,
+			CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES);
+		if(excludedRefsPrefixesArray.length > 0) {
+			setBitmapExcludedRefsPrefixes(excludedRefsPrefixesArray);
+		}
 		setSearchForReuseTimeout(Duration.ofSeconds(rc.getTimeUnit(
 				CONFIG_PACK_SECTION, null,
 				CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index 28c3b6a..e0eb126 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -47,6 +47,7 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.StringUtils;
 
@@ -387,11 +388,19 @@
 	private boolean askForIsComplete() throws TransportException {
 		try {
 			try (ObjectWalk ow = new ObjectWalk(transport.local)) {
-				for (ObjectId want : askFor.keySet())
-					ow.markStart(ow.parseAny(want));
-				for (Ref ref : localRefs().values())
-					ow.markUninteresting(ow.parseAny(ref.getObjectId()));
-				ow.checkConnectivity();
+				boolean hasCommitObject = false;
+				for (ObjectId want : askFor.keySet()) {
+					RevObject obj = ow.parseAny(want);
+					ow.markStart(obj);
+					hasCommitObject |= obj.getType() == Constants.OBJ_COMMIT;
+				}
+				// Checking connectivity makes sense on commits only
+				if (hasCommitObject) {
+					for (Ref ref : localRefs().values()) {
+						ow.markUninteresting(ow.parseAny(ref.getObjectId()));
+					}
+					ow.checkConnectivity();
+				}
 			}
 			return transport.getDepth() == null; // if depth is set we need to request objects that are already available
 		} catch (MissingObjectException e) {
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 a7ce1d7..38c7cb9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -1386,6 +1386,9 @@
 		if (transferConfig.isAllowReceiveClientSID()) {
 			caps.add(OPTION_SESSION_ID);
 		}
+		if (transferConfig.isAdvertiseObjectInfo()) {
+			caps.add(COMMAND_OBJECT_INFO);
+		}
 
 		return caps;
 	}