Merge "PackExtBlockCacheTable: spread extensions over multiple dfs tables"
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target
new file mode 100644
index 0000000..fac43dc
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.33" sequenceNumber="1721077009">
+  <locations>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20230916-1400"/>
+      <unit id="com.jcraft.jzlib" version="1.1.3.v20230916-1400"/>
+      <unit id="com.jcraft.jzlib.source" version="1.1.3.v20230916-1400"/>
+      <unit id="net.i2p.crypto.eddsa" version="0.3.0"/>
+      <unit id="net.i2p.crypto.eddsa.source" version="0.3.0"/>
+      <unit id="org.apache.ant" version="1.10.14.v20230922-1200"/>
+      <unit id="org.apache.ant.source" version="1.10.14.v20230922-1200"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.14"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.14"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.16"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.16"/>
+      <unit id="org.hamcrest.core" version="1.3.0.v20230809-1000"/>
+      <unit id="org.hamcrest.core.source" version="1.3.0.v20230809-1000"/>
+      <unit id="org.hamcrest.library" version="1.3.0.v20230809-1000"/>
+      <unit id="org.hamcrest.library.source" version="1.3.0.v20230809-1000"/>
+      <unit id="org.junit" version="4.13.2.v20230809-1000"/>
+      <unit id="org.junit.source" version="4.13.2.v20230809-1000"/>
+      <unit id="org.objenesis" version="3.4.0"/>
+      <unit id="org.objenesis.source" version="3.4.0"/>
+      <unit id="org.osgi.service.cm" version="1.6.1.202109301733"/>
+      <unit id="org.osgi.service.cm.source" version="1.6.1.202109301733"/>
+      <repository location="https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2024-09"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.osgi" version="0.0.0"/>
+      <repository location="https://download.eclipse.org/staging/2024-09/"/>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="xz">
+      <dependencies>
+        <dependency>
+          <groupId>org.tukaani</groupId>
+          <artifactId>xz</artifactId>
+          <version>1.9</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="slf4j">
+      <dependencies>
+        <dependency>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-api</artifactId>
+          <version>1.7.36</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-simple</artifactId>
+          <version>1.7.36</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="sshd">
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.sshd</groupId>
+          <artifactId>sshd-osgi</artifactId>
+          <version>2.13.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.sshd</groupId>
+          <artifactId>sshd-sftp</artifactId>
+          <version>2.13.1</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="mockito">
+      <dependencies>
+        <dependency>
+          <groupId>org.mockito</groupId>
+          <artifactId>mockito-core</artifactId>
+          <version>5.12.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="jna">
+      <dependencies>
+        <dependency>
+          <groupId>net.java.dev.jna</groupId>
+          <artifactId>jna</artifactId>
+          <version>5.14.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>net.java.dev.jna</groupId>
+          <artifactId>jna-platform</artifactId>
+          <version>5.14.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="jetty">
+      <dependencies>
+        <dependency>
+          <groupId>org.eclipse.jetty.ee10</groupId>
+          <artifactId>jetty-ee10-servlet</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-http</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-io</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-security</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-server</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-session</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-util</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-util-ajax</artifactId>
+          <version>12.0.10</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>jakarta.servlet</groupId>
+          <artifactId>jakarta.servlet-api</artifactId>
+          <version>6.0.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="javaewah">
+      <dependencies>
+        <dependency>
+          <groupId>com.googlecode.javaewah</groupId>
+          <artifactId>JavaEWAH</artifactId>
+          <version>1.2.3</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="hamcrest">
+      <dependencies>
+        <dependency>
+          <groupId>org.hamcrest</groupId>
+          <artifactId>hamcrest</artifactId>
+          <version>2.2</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="gson">
+      <dependencies>
+        <dependency>
+          <groupId>com.google.code.gson</groupId>
+          <artifactId>gson</artifactId>
+          <version>2.11.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="bytebuddy">
+      <dependencies>
+        <dependency>
+          <groupId>net.bytebuddy</groupId>
+          <artifactId>byte-buddy</artifactId>
+          <version>1.14.17</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>net.bytebuddy</groupId>
+          <artifactId>byte-buddy-agent</artifactId>
+          <version>1.14.17</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="bouncycastle">
+      <dependencies>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcpg-jdk18on</artifactId>
+          <version>1.78.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcprov-jdk18on</artifactId>
+          <version>1.78.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcpkix-jdk18on</artifactId>
+          <version>1.78.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcutil-jdk18on</artifactId>
+          <version>1.78.1</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="assertj">
+      <dependencies>
+        <dependency>
+          <groupId>org.assertj</groupId>
+          <artifactId>assertj-core</artifactId>
+          <version>3.26.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="args4j">
+      <dependencies>
+        <dependency>
+          <groupId>args4j</groupId>
+          <artifactId>args4j</artifactId>
+          <version>2.37</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="apache">
+      <dependencies>
+        <dependency>
+          <groupId>commons-codec</groupId>
+          <artifactId>commons-codec</artifactId>
+          <version>1.17.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-compress</artifactId>
+          <version>1.26.2</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-lang3</artifactId>
+          <version>3.14.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>commons-io</groupId>
+          <artifactId>commons-io</artifactId>
+          <version>2.16.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>commons-logging</groupId>
+          <artifactId>commons-logging</artifactId>
+          <version>1.3.2</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+  </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd
new file mode 100644
index 0000000..d01a8a9
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.33" with source configurePhase
+
+include "orbit/orbit-4.33.tpd"
+include "maven/dependencies.tpd"
+
+location "https://download.eclipse.org/staging/2024-09/" {
+	org.eclipse.osgi lazy
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd
index 0554a85..9d00cb4 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd
@@ -1,4 +1,4 @@
-target "orbit-4.30" with source configurePhase
+target "orbit-4.31" with source configurePhase
 // see https://download.eclipse.org/tools/orbit/downloads/
 
 location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2023-12" {
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd
new file mode 100644
index 0000000..8dca4cb
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd
@@ -0,0 +1,27 @@
+target "orbit-4.33" with source configurePhase
+// see https://download.eclipse.org/tools/orbit/downloads/
+
+location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2024-09" {
+	com.jcraft.jsch [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
+	com.jcraft.jsch.source [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
+	com.jcraft.jzlib [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
+	com.jcraft.jzlib.source [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
+	net.i2p.crypto.eddsa [0.3.0,0.3.0]
+	net.i2p.crypto.eddsa.source [0.3.0,0.3.0]
+	org.apache.ant [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
+	org.apache.ant.source [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
+	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
+	org.apache.httpcomponents.httpclient.source [4.5.14,4.5.14]
+	org.apache.httpcomponents.httpcore [4.4.16,4.4.16]
+	org.apache.httpcomponents.httpcore.source [4.4.16,4.4.16]
+	org.hamcrest.core [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
+	org.hamcrest.core.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
+	org.hamcrest.library [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
+	org.hamcrest.library.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
+	org.junit [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
+	org.junit.source [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
+	org.objenesis [3.4,3.4]
+	org.objenesis.source [3.4,3.4]
+	org.osgi.service.cm [1.6.1.202109301733,1.6.1.202109301733]
+	org.osgi.service.cm.source [1.6.1.202109301733,1.6.1.202109301733]
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
index 52f40c2..f5de704 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
@@ -94,7 +94,7 @@ private void list() throws IOException, ConfigInvalidException {
 		if (global || isListAll())
 			list(SystemReader.getInstance().openUserConfig(null, fs));
 		if (local || isListAll())
-			list(new FileBasedConfig(fs.resolve(getRepository().getDirectory(),
+			list(new FileBasedConfig(fs.resolve(getRepository().getCommonDirectory(),
 					Constants.CONFIG), fs));
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
index b937b1f..4c971ff 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
@@ -559,7 +559,7 @@ private void setupGitAndDoHardReset(AutoCRLF autoCRLF, EOL eol,
 
 		}
 		if (infoAttributesContent != null) {
-			File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
+			File f = new File(db.getCommonDirectory(), Constants.INFO_ATTRIBUTES);
 			write(f, infoAttributesContent);
 		}
 		config.save();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LinkedWorktreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LinkedWorktreeTest.java
new file mode 100644
index 0000000..3b60e1b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LinkedWorktreeTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024, Broadcom 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.api;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.Test;
+
+public class LinkedWorktreeTest extends RepositoryTestCase {
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("Initial commit").call();
+		}
+	}
+
+	@Test
+	public void testWeCanReadFromLinkedWorktreeFromBare() throws Exception {
+		FS fs = db.getFS();
+		File directory = trash.getParentFile();
+		String dbDirName = db.getWorkTree().getName();
+		cloneBare(fs, directory, dbDirName, "bare");
+		File bareDirectory = new File(directory, "bare");
+		worktreeAddExisting(fs, bareDirectory, "master");
+
+		File worktreesDir = new File(bareDirectory, "worktrees");
+		File masterWorktreesDir = new File(worktreesDir, "master");
+
+		FileRepository repository = new FileRepository(masterWorktreesDir);
+		try (Git git = new Git(repository)) {
+			ObjectId objectId = repository.resolve(HEAD);
+			assertNotNull(objectId);
+
+			Iterator<RevCommit> log = git.log().all().call().iterator();
+			assertTrue(log.hasNext());
+			assertTrue("Initial commit".equals(log.next().getShortMessage()));
+
+			// we have reflog entry
+			// depending on git version we either have one or
+			// two entries where extra is zeroid entry with
+			// same message or no message
+			Collection<ReflogEntry> reflog = git.reflog().call();
+			assertNotNull(reflog);
+			assertTrue(reflog.size() > 0);
+			ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
+			assertEquals(reflogs[reflogs.length - 1].getComment(),
+					"reset: moving to HEAD");
+
+			// index works with file changes
+			File masterDir = new File(directory, "master");
+			File testFile = new File(masterDir, "test");
+
+			Status status = git.status().call();
+			assertTrue(status.getUncommittedChanges().size() == 0);
+			assertTrue(status.getUntracked().size() == 0);
+
+			JGitTestUtil.write(testFile, "test");
+			status = git.status().call();
+			assertTrue(status.getUncommittedChanges().size() == 0);
+			assertTrue(status.getUntracked().size() == 1);
+
+			git.add().addFilepattern("test").call();
+			status = git.status().call();
+			assertTrue(status.getUncommittedChanges().size() == 1);
+			assertTrue(status.getUntracked().size() == 0);
+		}
+	}
+
+	@Test
+	public void testWeCanReadFromLinkedWorktreeFromNonBare() throws Exception {
+		FS fs = db.getFS();
+		worktreeAddNew(fs, db.getWorkTree(), "wt");
+
+		File worktreesDir = new File(db.getDirectory(), "worktrees");
+		File masterWorktreesDir = new File(worktreesDir, "wt");
+
+		FileRepository repository = new FileRepository(masterWorktreesDir);
+		try (Git git = new Git(repository)) {
+			ObjectId objectId = repository.resolve(HEAD);
+			assertNotNull(objectId);
+
+			Iterator<RevCommit> log = git.log().all().call().iterator();
+			assertTrue(log.hasNext());
+			assertTrue("Initial commit".equals(log.next().getShortMessage()));
+
+			// we have reflog entry
+			Collection<ReflogEntry> reflog = git.reflog().call();
+			assertNotNull(reflog);
+			assertTrue(reflog.size() > 0);
+			ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
+			assertEquals(reflogs[reflogs.length - 1].getComment(),
+					"reset: moving to HEAD");
+
+			// index works with file changes
+			File directory = trash.getParentFile();
+			File wtDir = new File(directory, "wt");
+			File testFile = new File(wtDir, "test");
+
+			Status status = git.status().call();
+			assertTrue(status.getUncommittedChanges().size() == 0);
+			assertTrue(status.getUntracked().size() == 0);
+
+			JGitTestUtil.write(testFile, "test");
+			status = git.status().call();
+			assertTrue(status.getUncommittedChanges().size() == 0);
+			assertTrue(status.getUntracked().size() == 1);
+
+			git.add().addFilepattern("test").call();
+			status = git.status().call();
+			assertTrue(status.getUncommittedChanges().size() == 1);
+			assertTrue(status.getUntracked().size() == 0);
+		}
+
+	}
+
+	private static void cloneBare(FS fs, File directory, String from, String to) throws IOException, InterruptedException {
+		ProcessBuilder builder = fs.runInShell("git",
+				new String[] { "clone", "--bare", from, to });
+		builder.directory(directory);
+		builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+		StringBuilder input = new StringBuilder();
+		ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
+				input.toString().getBytes(StandardCharsets.UTF_8)));
+		String stdOut = toString(result.getStdout());
+		String errorOut = toString(result.getStderr());
+		assertNotNull(stdOut);
+		assertNotNull(errorOut);
+	}
+
+	private static void worktreeAddExisting(FS fs, File directory, String name) throws IOException, InterruptedException {
+		ProcessBuilder builder = fs.runInShell("git",
+				new String[] { "worktree", "add", "../" + name, name });
+		builder.directory(directory);
+		builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+		StringBuilder input = new StringBuilder();
+		ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
+				input.toString().getBytes(StandardCharsets.UTF_8)));
+		String stdOut = toString(result.getStdout());
+		String errorOut = toString(result.getStderr());
+		assertNotNull(stdOut);
+		assertNotNull(errorOut);
+	}
+
+	private static void worktreeAddNew(FS fs, File directory, String name) throws IOException, InterruptedException {
+		ProcessBuilder builder = fs.runInShell("git",
+				new String[] { "worktree", "add", "-b", name, "../" + name, "master"});
+		builder.directory(directory);
+		builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+		StringBuilder input = new StringBuilder();
+		ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
+				input.toString().getBytes(StandardCharsets.UTF_8)));
+		String stdOut = toString(result.getStdout());
+		String errorOut = toString(result.getStderr());
+		assertNotNull(stdOut);
+		assertNotNull(errorOut);
+	}
+
+	private static String toString(TemporaryBuffer b) throws IOException {
+		return RawParseUtils.decode(b.toByteArray());
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
index 7fb98ec..c41dd81 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
@@ -584,7 +584,7 @@ private void setupRepo(
 
 		}
 		if (infoAttributesContent != null) {
-			File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
+			File f = new File(db.getCommonDirectory(), Constants.INFO_ATTRIBUTES);
 			write(f, infoAttributesContent);
 		}
 		config.save();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index daf4382..1af42cb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -171,7 +171,7 @@ public void packedRefsFileIsSorted() throws IOException {
 			assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
 		}
 
-		File packed = new File(diskRepo.getDirectory(), "packed-refs");
+		File packed = new File(diskRepo.getCommonDirectory(), "packed-refs");
 		String packedStr = new String(Files.readAllBytes(packed.toPath()),
 				UTF_8);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index 8baa3cc..c572955 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -58,7 +58,7 @@ public void emptyRefDirectoryDeleted() throws Exception {
 		String ref = "dir/ref";
 		tr.branch(ref).commit().create();
 		String name = repo.findRef(ref).getName();
-		Path dir = repo.getDirectory().toPath().resolve(name).getParent();
+		Path dir = repo.getCommonDirectory().toPath().resolve(name).getParent();
 		assertNotNull(dir);
 		gc.packRefs();
 		assertFalse(Files.exists(dir));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
index e6c1ee5..29f180d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
@@ -30,7 +30,7 @@ public void testPruneNone() throws Exception {
 		BranchBuilder bb = tr.branch("refs/heads/master");
 		bb.commit().add("A", "A").add("B", "B").create();
 		bb.commit().add("A", "A2").add("B", "B2").create();
-		new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master")
+		new File(repo.getCommonDirectory(), Constants.LOGS + "/refs/heads/master")
 				.delete();
 		stats = gc.getStatistics();
 		assertEquals(8, stats.numberOfLooseObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 2bafde6..baa0182 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -90,25 +90,26 @@ public void refDirectorySetup() throws Exception {
 	@Test
 	public void testCreate() throws IOException {
 		// setUp above created the directory. We just have to test it.
-		File d = diskRepo.getDirectory();
+		File gitDir = diskRepo.getDirectory();
+		File commonDir = diskRepo.getCommonDirectory();
 		assertSame(diskRepo, refdir.getRepository());
 
-		assertTrue(new File(d, "refs").isDirectory());
-		assertTrue(new File(d, "logs").isDirectory());
-		assertTrue(new File(d, "logs/refs").isDirectory());
-		assertFalse(new File(d, "packed-refs").exists());
+		assertTrue(new File(commonDir, "refs").isDirectory());
+		assertTrue(new File(commonDir, "logs").isDirectory());
+		assertTrue(new File(commonDir, "logs/refs").isDirectory());
+		assertFalse(new File(commonDir, "packed-refs").exists());
 
-		assertTrue(new File(d, "refs/heads").isDirectory());
-		assertTrue(new File(d, "refs/tags").isDirectory());
-		assertEquals(2, new File(d, "refs").list().length);
-		assertEquals(0, new File(d, "refs/heads").list().length);
-		assertEquals(0, new File(d, "refs/tags").list().length);
+		assertTrue(new File(commonDir, "refs/heads").isDirectory());
+		assertTrue(new File(commonDir, "refs/tags").isDirectory());
+		assertEquals(2, new File(commonDir, "refs").list().length);
+		assertEquals(0, new File(commonDir, "refs/heads").list().length);
+		assertEquals(0, new File(commonDir, "refs/tags").list().length);
 
-		assertTrue(new File(d, "logs/refs/heads").isDirectory());
-		assertFalse(new File(d, "logs/HEAD").exists());
-		assertEquals(0, new File(d, "logs/refs/heads").list().length);
+		assertTrue(new File(commonDir, "logs/refs/heads").isDirectory());
+		assertFalse(new File(gitDir, "logs/HEAD").exists());
+		assertEquals(0, new File(commonDir, "logs/refs/heads").list().length);
 
-		assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD)));
+		assertEquals("ref: refs/heads/master\n", read(new File(gitDir, HEAD)));
 	}
 
 	@Test(expected = UnsupportedOperationException.class)
@@ -1382,7 +1383,7 @@ private void writeLooseRef(String name, String content) throws IOException {
 	}
 
 	private void deleteLooseRef(String name) {
-		File path = new File(diskRepo.getDirectory(), name);
+		File path = new File(diskRepo.getCommonDirectory(), name);
 		assertTrue("deleted " + name, path.delete());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
index dc0e749..eb521ff 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
@@ -238,7 +238,7 @@ public void testSpecificEntryNumber() throws Exception {
 
 	private void setupReflog(String logName, byte[] data)
 			throws FileNotFoundException, IOException {
-		File logfile = new File(db.getDirectory(), logName);
+		File logfile = new File(db.getCommonDirectory(), logName);
 		if (!logfile.getParentFile().mkdirs()
 				&& !logfile.getParentFile().isDirectory()) {
 			throw new IOException(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
index 8d0e99d..8e9b7b8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
@@ -48,7 +48,7 @@ public void shouldFilterLineFeedFromMessage() throws Exception {
 
 	private void readReflog(byte[] buffer)
 			throws FileNotFoundException, IOException {
-		File logfile = new File(db.getDirectory(), "logs/refs/heads/master");
+		File logfile = new File(db.getCommonDirectory(), "logs/refs/heads/master");
 		if (!logfile.getParentFile().mkdirs()
 				&& !logfile.getParentFile().isDirectory()) {
 			throw new IOException(
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index a15fe1f..c9f7336 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -808,6 +808,7 @@
 tSizeMustBeGreaterOrEqual1=tSize must be >= 1
 unableToCheckConnectivity=Unable to check connectivity.
 unableToCreateNewObject=Unable to create new object: {0}
+unableToReadFullArray=Unable to read an array with {0} elements from the stream
 unableToReadFullInt=Unable to read a full int from the stream
 unableToReadPackfile=Unable to read packfile {0}
 unableToRemovePath=Unable to remove path ''{0}''
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
index 8fb5d60..401f069 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -176,7 +176,7 @@ public Repository call() throws GitAPIException {
 		CloneCommand clone = Git.cloneRepository();
 		configure(clone);
 		clone.setDirectory(moduleDirectory);
-		clone.setGitDir(new File(new File(repo.getDirectory(),
+		clone.setGitDir(new File(new File(repo.getCommonDirectory(),
 				Constants.MODULES), path));
 		clone.setURI(resolvedUri);
 		if (monitor != null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index df73164..751dabc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -128,7 +128,7 @@ private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
 			clone.setURI(url);
 			clone.setDirectory(generator.getDirectory());
 			clone.setGitDir(
-					new File(new File(repo.getDirectory(), Constants.MODULES),
+					new File(new File(repo.getCommonDirectory(), Constants.MODULES),
 							generator.getPath()));
 			if (monitor != null) {
 				clone.setProgressMonitor(monitor);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 6ae5153..fa0a82f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -1647,6 +1647,8 @@ private static void runExternalFilterCommand(Repository repo, String path,
 		filterProcessBuilder.directory(repo.getWorkTree());
 		filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
 				repo.getDirectory().getAbsolutePath());
+		filterProcessBuilder.environment().put(Constants.GIT_COMMON_DIR_KEY,
+				repo.getCommonDirectory().getAbsolutePath());
 		ExecutionResult result;
 		int rc;
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index e31533a..8a5f2b2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -838,6 +838,7 @@ public static JGitText get() {
 	/***/ public String unableToCheckConnectivity;
 	/***/ public String unableToCreateNewObject;
 	/***/ public String unableToReadFullInt;
+	/***/ public String unableToReadFullArray;
 	/***/ public String unableToReadPackfile;
 	/***/ public String unableToRemovePath;
 	/***/ public String unableToWrite;
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 b94a84a..cdd1061 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
@@ -330,18 +330,27 @@ public PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException {
 
 		PackIndex idx = idx(ctx);
 		DfsStreamKey revKey = desc.getStreamKey(REVERSE_INDEX);
-		AtomicBoolean cacheHit = new AtomicBoolean(true);
-		DfsBlockCache.Ref<PackReverseIndex> revref = cache.getOrLoadRef(revKey,
-				REF_POSITION, () -> {
-					cacheHit.set(false);
-					return loadReverseIdx(ctx, revKey, idx);
+		// Keep the value parsed in the loader, in case the Ref<> is
+		// nullified in ClockBlockCacheTable#reserveSpace
+		// before we read its value.
+		AtomicReference<PackReverseIndex> loadedRef = new AtomicReference<>(
+				null);
+		DfsBlockCache.Ref<PackReverseIndex> cachedRef = cache
+				.getOrLoadRef(revKey, REF_POSITION, () -> {
+					RefWithSize<PackReverseIndex> ridx = loadReverseIdx(ctx,
+							idx);
+					loadedRef.set(ridx.ref);
+					return new DfsBlockCache.Ref<>(revKey, REF_POSITION,
+							ridx.size, ridx.ref);
 				});
-		if (cacheHit.get()) {
+		if (loadedRef.get() == null) {
 			ctx.stats.ridxCacheHit++;
 		}
-		PackReverseIndex revidx = revref.get();
-		if (reverseIndex == null && revidx != null) {
-			reverseIndex = revidx;
+		reverseIndex = cachedRef.get() != null ? cachedRef.get()
+				: loadedRef.get();
+		if (reverseIndex == null) {
+			throw new IOException(
+					"Couldn't get a reference to the reverse index"); //$NON-NLS-1$
 		}
 		ctx.emitIndexLoad(desc, REVERSE_INDEX, reverseIndex);
 		return reverseIndex;
@@ -1241,18 +1250,13 @@ private RefWithSize<PackIndex> loadPackIndex(DfsReader ctx)
 		}
 	}
 
-	private DfsBlockCache.Ref<PackReverseIndex> loadReverseIdx(
-			DfsReader ctx, DfsStreamKey revKey, PackIndex idx) {
+	private static RefWithSize<PackReverseIndex> loadReverseIdx(DfsReader ctx,
+			PackIndex idx) {
 		ctx.stats.readReverseIdx++;
 		long start = System.nanoTime();
 		PackReverseIndex revidx = PackReverseIndexFactory.computeFromIndex(idx);
-		reverseIndex = revidx;
 		ctx.stats.readReverseIdxMicros += elapsedMicros(start);
-		return new DfsBlockCache.Ref<>(
-				revKey,
-				REF_POSITION,
-				idx.getObjectCount() * 8,
-				revidx);
+		return new RefWithSize<>(revidx, idx.getObjectCount() * 8);
 	}
 
 	private DfsBlockCache.Ref<PackObjectSizeIndex> loadObjectSizeIndex(
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 ed2516d..80240e5 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
@@ -68,14 +68,14 @@ public class FileReftableDatabase extends RefDatabase {
 	private final FileReftableStack reftableStack;
 
 	FileReftableDatabase(FileRepository repo) throws IOException {
-		this(repo, new File(new File(repo.getDirectory(), Constants.REFTABLE),
+		this(repo, new File(new File(repo.getCommonDirectory(), Constants.REFTABLE),
 				Constants.TABLES_LIST));
 	}
 
 	FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
 		this.fileRepository = repo;
 		this.reftableStack = new FileReftableStack(refstackName,
-			new File(fileRepository.getDirectory(), Constants.REFTABLE),
+				new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
 			() -> fileRepository.fireEvent(new RefsChangedEvent()),
 			() -> fileRepository.getConfig());
 		this.reftableDatabase = new ReftableDatabase() {
@@ -318,7 +318,7 @@ public void close() {
 	@Override
 	public void create() throws IOException {
 		FileUtils.mkdir(
-				new File(fileRepository.getDirectory(), Constants.REFTABLE),
+				new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
 				true);
 	}
 
@@ -615,7 +615,7 @@ public static FileReftableDatabase convertFrom(FileRepository repo,
 		FileReftableDatabase newDb = null;
 		File reftableList = null;
 		try {
-			File reftableDir = new File(repo.getDirectory(),
+			File reftableDir = new File(repo.getCommonDirectory(),
 					Constants.REFTABLE);
 			reftableList = new File(reftableDir, Constants.TABLES_LIST);
 			if (!reftableDir.isDirectory()) {
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 e5a00d3..b5d29a3 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
@@ -165,7 +165,7 @@ public FileRepository(BaseRepositoryBuilder options) throws IOException {
 			throw new IOException(e.getMessage(), e);
 		}
 		repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
-				getDirectory(), Constants.CONFIG),
+				getCommonDirectory(), Constants.CONFIG),
 				getFS());
 		loadRepoConfig();
 
@@ -193,7 +193,7 @@ public FileRepository(BaseRepositoryBuilder options) throws IOException {
 				options.getObjectDirectory(), //
 				options.getAlternateObjectDirectories(), //
 				getFS(), //
-				new File(getDirectory(), Constants.SHALLOW));
+				new File(getCommonDirectory(), Constants.SHALLOW));
 
 		if (objectDatabase.exists()) {
 			if (repositoryFormatVersion > 1)
@@ -622,16 +622,17 @@ public void autoGC(ProgressMonitor monitor) {
 	 *             on IO problem
 	 */
 	void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
+		File commonDirectory = getCommonDirectory();
 		List<Ref> all = refs.getRefs();
-		File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
+		File packedRefs = new File(commonDirectory, Constants.PACKED_REFS);
 		if (packedRefs.exists()) {
 			throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists,
 				packedRefs.getName()));
 		}
 
-		File refsFile = new File(getDirectory(), "refs"); //$NON-NLS-1$
+		File refsFile = new File(commonDirectory, "refs"); //$NON-NLS-1$
 		File refsHeadsFile = new File(refsFile, "heads");//$NON-NLS-1$
-		File headFile = new File(getDirectory(), Constants.HEAD);
+		File headFile = new File(commonDirectory, Constants.HEAD);
 		FileReftableDatabase oldDb = (FileReftableDatabase) refs;
 
 		// Remove the dummy files that ensure compatibility with older git
@@ -701,7 +702,7 @@ void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
 		}
 
 		if (!backup) {
-			File reftableDir = new File(getDirectory(), Constants.REFTABLE);
+			File reftableDir = new File(commonDirectory, Constants.REFTABLE);
 			FileUtils.delete(reftableDir,
 					FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
 		}
@@ -730,8 +731,10 @@ void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
 	@SuppressWarnings("nls")
 	void convertToReftable(boolean writeLogs, boolean backup)
 			throws IOException {
-		File reftableDir = new File(getDirectory(), Constants.REFTABLE);
-		File headFile = new File(getDirectory(), Constants.HEAD);
+		File commonDirectory = getCommonDirectory();
+		File directory = getDirectory();
+		File reftableDir = new File(commonDirectory, Constants.REFTABLE);
+		File headFile = new File(directory, Constants.HEAD);
 		if (reftableDir.exists() && FileUtils.hasFiles(reftableDir.toPath())) {
 			throw new IOException(JGitText.get().reftableDirExists);
 		}
@@ -739,28 +742,28 @@ void convertToReftable(boolean writeLogs, boolean backup)
 		// Ignore return value, as it is tied to temporary newRefs file.
 		FileReftableDatabase.convertFrom(this, writeLogs);
 
-		File refsFile = new File(getDirectory(), "refs");
+		File refsFile = new File(commonDirectory, "refs");
 
 		// non-atomic: remove old data.
-		File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
-		File logsDir = new File(getDirectory(), Constants.LOGS);
+		File packedRefs = new File(commonDirectory, Constants.PACKED_REFS);
+		File logsDir = new File(commonDirectory, Constants.LOGS);
 
 		List<String> additional = getRefDatabase().getAdditionalRefs().stream()
 				.map(Ref::getName).collect(toList());
 		additional.add(Constants.HEAD);
 		if (backup) {
-			FileUtils.rename(refsFile, new File(getDirectory(), "refs.old"));
+			FileUtils.rename(refsFile, new File(commonDirectory, "refs.old"));
 			if (packedRefs.exists()) {
-				FileUtils.rename(packedRefs, new File(getDirectory(),
+				FileUtils.rename(packedRefs, new File(commonDirectory,
 						Constants.PACKED_REFS + ".old"));
 			}
 			if (logsDir.exists()) {
 				FileUtils.rename(logsDir,
-						new File(getDirectory(), Constants.LOGS + ".old"));
+						new File(commonDirectory, Constants.LOGS + ".old"));
 			}
 			for (String r : additional) {
-				FileUtils.rename(new File(getDirectory(), r),
-					new File(getDirectory(), r + ".old"));
+				FileUtils.rename(new File(commonDirectory, r),
+						new File(commonDirectory, r + ".old"));
 			}
 		} else {
 			FileUtils.delete(packedRefs, FileUtils.SKIP_MISSING);
@@ -770,7 +773,7 @@ void convertToReftable(boolean writeLogs, boolean backup)
 			FileUtils.delete(refsFile,
 					FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
 			for (String r : additional) {
-				new File(getDirectory(), r).delete();
+				new File(commonDirectory, r).delete();
 			}
 		}
 
@@ -784,7 +787,7 @@ void convertToReftable(boolean writeLogs, boolean backup)
 
 		// Some tools might write directly into .git/refs/heads/BRANCH. By
 		// putting a file here, this fails spectacularly.
-		FileUtils.createNewFile(new File(refsFile, "heads"));
+		FileUtils.createNewFile(new File(refsFile, Constants.HEADS));
 
 		repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
 				ConfigConstants.CONFIG_KEY_REF_STORAGE,
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 cf26f8d..4fafc5a 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
@@ -1047,7 +1047,7 @@ private static boolean isTag(Ref ref) {
 	}
 
 	private void deleteEmptyRefsFolders() throws IOException {
-		Path refs = repo.getDirectory().toPath().resolve(Constants.R_REFS);
+		Path refs = repo.getCommonDirectory().toPath().resolve(Constants.R_REFS);
 		// Avoid deleting a folder that was created after the threshold so that concurrent
 		// operations trying to create a reference are not impacted
 		Instant threshold = Instant.now().minus(30, ChronoUnit.SECONDS);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
index 628bf5d..8647b3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
@@ -50,7 +50,7 @@ class GcLog {
 	 */
 	GcLog(FileRepository repo) {
 		this.repo = repo;
-		logFile = new File(repo.getDirectory(), "gc.log"); //$NON-NLS-1$
+		logFile = new File(repo.getCommonDirectory(), "gc.log"); //$NON-NLS-1$
 		lock = new LockFile(logFile);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
index 11d842b..e8d442b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -46,7 +46,7 @@ public AttributesNode load() throws IOException {
 
 		FS fs = repository.getFS();
 
-		File attributes = fs.resolve(repository.getDirectory(),
+		File attributes = fs.resolve(repository.getCommonDirectory(),
 				Constants.INFO_ATTRIBUTES);
 		FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java
index a3d74be..e172f14 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java
@@ -12,7 +12,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
+import java.text.MessageFormat;
 
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.util.NB;
@@ -35,7 +35,7 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
 
 	private final UInt24Array positions24;
 
-	private final int[] positions32;
+	private final IntArray positions32;
 
 	/**
 	 * Parallel array to concat(positions24, positions32) with the size of the
@@ -45,35 +45,37 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
 	 * doesn't fit in an int and |value|-1 is the position for the size in the
 	 * size64 array e.g. a value of -1 is sizes64[0], -2 = sizes64[1], ...
 	 */
-	private final int[] sizes32;
+	private final IntArray sizes32;
 
-	private final long[] sizes64;
+	private final LongArray sizes64;
 
 	static PackObjectSizeIndex parse(InputStream in) throws IOException {
 		/** Header and version already out of the input */
-		IndexInputStreamReader stream = new IndexInputStreamReader(in);
-		int threshold = stream.readInt(); // minSize
-		int objCount = stream.readInt();
+		byte[] buffer = new byte[8];
+		in.readNBytes(buffer, 0, 8);
+		int threshold = NB.decodeInt32(buffer, 0); // minSize
+		int objCount = NB.decodeInt32(buffer, 4);
 		if (objCount == 0) {
 			return new EmptyPackObjectSizeIndex(threshold);
 		}
-		return new PackObjectSizeIndexV1(stream, threshold, objCount);
+		return new PackObjectSizeIndexV1(in, threshold, objCount);
 	}
 
-	private PackObjectSizeIndexV1(IndexInputStreamReader stream, int threshold,
+	private PackObjectSizeIndexV1(InputStream stream, int threshold,
 			int objCount) throws IOException {
 		this.threshold = threshold;
 		UInt24Array pos24 = null;
-		int[] pos32 = null;
+		IntArray pos32 = null;
 
+		StreamHelper helper = new StreamHelper();
 		byte positionEncoding;
-		while ((positionEncoding = stream.readByte()) != 0) {
+		while ((positionEncoding = helper.readByte(stream)) != 0) {
 			if (Byte.compareUnsigned(positionEncoding, BITS_24) == 0) {
-				int sz = stream.readInt();
+				int sz = helper.readInt(stream);
 				pos24 = new UInt24Array(stream.readNBytes(sz * 3));
 			} else if (Byte.compareUnsigned(positionEncoding, BITS_32) == 0) {
-				int sz = stream.readInt();
-				pos32 = stream.readIntArray(sz);
+				int sz = helper.readInt(stream);
+				pos32 = IntArray.from(stream, sz);
 			} else {
 				throw new UnsupportedEncodingException(
 						String.format(JGitText.get().unknownPositionEncoding,
@@ -81,16 +83,16 @@ private PackObjectSizeIndexV1(IndexInputStreamReader stream, int threshold,
 			}
 		}
 		positions24 = pos24 != null ? pos24 : UInt24Array.EMPTY;
-		positions32 = pos32 != null ? pos32 : new int[0];
+		positions32 = pos32 != null ? pos32 : IntArray.EMPTY;
 
-		sizes32 = stream.readIntArray(objCount);
-		int c64sizes = stream.readInt();
+		sizes32 = IntArray.from(stream, objCount);
+		int c64sizes = helper.readInt(stream);
 		if (c64sizes == 0) {
-			sizes64 = new long[0];
+			sizes64 = LongArray.EMPTY;
 			return;
 		}
-		sizes64 = stream.readLongArray(c64sizes);
-		int c128sizes = stream.readInt();
+		sizes64 = LongArray.from(stream, c64sizes);
+		int c128sizes = helper.readInt(stream);
 		if (c128sizes != 0) {
 			// this MUST be 0 (we don't support 128 bits sizes yet)
 			throw new IOException(JGitText.get().unsupportedSizesObjSizeIndex);
@@ -102,8 +104,8 @@ public long getSize(int idxOffset) {
 		int pos = -1;
 		if (!positions24.isEmpty() && idxOffset <= positions24.getLastValue()) {
 			pos = positions24.binarySearch(idxOffset);
-		} else if (positions32.length > 0 && idxOffset >= positions32[0]) {
-			int pos32 = Arrays.binarySearch(positions32, idxOffset);
+		} else if (!positions32.empty() && idxOffset >= positions32.get(0)) {
+			int pos32 = positions32.binarySearch(idxOffset);
 			if (pos32 >= 0) {
 				pos = pos32 + positions24.size();
 			}
@@ -112,17 +114,17 @@ public long getSize(int idxOffset) {
 			return -1;
 		}
 
-		int objSize = sizes32[pos];
+		int objSize = sizes32.get(pos);
 		if (objSize < 0) {
 			int secondPos = Math.abs(objSize) - 1;
-			return sizes64[secondPos];
+			return sizes64.get(secondPos);
 		}
 		return objSize;
 	}
 
 	@Override
 	public long getObjectCount() {
-		return (long) positions24.size() + positions32.length;
+		return (long) positions24.size() + positions32.size();
 	}
 
 	@Override
@@ -131,19 +133,112 @@ public int getThreshold() {
 	}
 
 	/**
-	 * Wrapper to read parsed content from the byte stream
+	 * A byte[] that should be interpreted as an int[]
 	 */
-	private static class IndexInputStreamReader {
+	private static class IntArray {
+		private static final IntArray EMPTY = new IntArray(new byte[0]);
 
-		private final byte[] buffer = new byte[8];
+		private static final int INT_SIZE = 4;
 
-		private final InputStream in;
+		private final byte[] data;
 
-		IndexInputStreamReader(InputStream in) {
-			this.in = in;
+		private final int size;
+
+		static IntArray from(InputStream in, int ints) throws IOException {
+			int expectedBytes = ints * INT_SIZE;
+			byte[] data = in.readNBytes(expectedBytes);
+			if (data.length < expectedBytes) {
+				throw new IOException(MessageFormat
+						.format(JGitText.get().unableToReadFullArray, ints));
+			}
+			return new IntArray(data);
 		}
 
-		int readInt() throws IOException {
+		private IntArray(byte[] data) {
+			this.data = data;
+			size = data.length / INT_SIZE;
+		}
+
+		/**
+		 * Returns position of element in array, -1 if not there
+		 *
+		 * @param needle
+		 *            element to look for
+		 * @return position of the element in the array or -1 if not found
+		 */
+		int binarySearch(int needle) {
+			if (size == 0) {
+				return -1;
+			}
+			int high = size;
+			int low = 0;
+			do {
+				int mid = (low + high) >>> 1;
+				int cmp = Integer.compare(needle, get(mid));
+				if (cmp < 0)
+					high = mid;
+				else if (cmp == 0) {
+					return mid;
+				} else
+					low = mid + 1;
+			} while (low < high);
+			return -1;
+		}
+
+		int get(int position) {
+			if (position < 0 || position >= size) {
+				throw new IndexOutOfBoundsException(position);
+			}
+			return NB.decodeInt32(data, position * INT_SIZE);
+		}
+
+		boolean empty() {
+			return size == 0;
+		}
+
+		int size() {
+			return size;
+		}
+	}
+
+	/**
+	 * A byte[] that should be interpreted as an long[]
+	 */
+	private static class LongArray {
+		private static final LongArray EMPTY = new LongArray(new byte[0]);
+
+		private static final int LONG_SIZE = 8; // bytes
+
+		private final byte[] data;
+
+		private final int size;
+
+		static LongArray from(InputStream in, int longs) throws IOException {
+			byte[] data = in.readNBytes(longs * LONG_SIZE);
+			if (data.length < longs * LONG_SIZE) {
+				throw new IOException(MessageFormat
+						.format(JGitText.get().unableToReadFullArray, longs));
+			}
+			return new LongArray(data);
+		}
+
+		private LongArray(byte[] data) {
+			this.data = data;
+			size = data.length / LONG_SIZE;
+		}
+
+		long get(int position) {
+			if (position < 0 || position >= size) {
+				throw new IndexOutOfBoundsException(position);
+			}
+			return NB.decodeInt64(data, position * LONG_SIZE);
+		}
+	}
+
+	private static class StreamHelper {
+		private final byte[] buffer = new byte[8];
+
+		int readInt(InputStream in) throws IOException {
 			int n = in.readNBytes(buffer, 0, 4);
 			if (n < 4) {
 				throw new IOException(JGitText.get().unableToReadFullInt);
@@ -151,49 +246,13 @@ int readInt() throws IOException {
 			return NB.decodeInt32(buffer, 0);
 		}
 
-		int[] readIntArray(int intsCount) throws IOException {
-			if (intsCount == 0) {
-				return new int[0];
-			}
-
-			int[] dest = new int[intsCount];
-			for (int i = 0; i < intsCount; i++) {
-				dest[i] = readInt();
-			}
-			return dest;
-		}
-
-		long readLong() throws IOException {
-			int n = in.readNBytes(buffer, 0, 8);
-			if (n < 8) {
-				throw new IOException(JGitText.get().unableToReadFullInt);
-			}
-			return NB.decodeInt64(buffer, 0);
-		}
-
-		long[] readLongArray(int longsCount) throws IOException {
-			if (longsCount == 0) {
-				return new long[0];
-			}
-
-			long[] dest = new long[longsCount];
-			for (int i = 0; i < longsCount; i++) {
-				dest[i] = readLong();
-			}
-			return dest;
-		}
-
-		byte readByte() throws IOException {
+		byte readByte(InputStream in) throws IOException {
 			int n = in.readNBytes(buffer, 0, 1);
 			if (n != 1) {
 				throw new IOException(JGitText.get().cannotReadByte);
 			}
 			return buffer[0];
 		}
-
-		byte[] readNBytes(int sz) throws IOException {
-			return in.readNBytes(sz);
-		}
 	}
 
 	private static class EmptyPackObjectSizeIndex
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 8e57bf9..6048681 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -16,6 +16,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.eclipse.jgit.lib.Constants.LOGS;
+import static org.eclipse.jgit.lib.Constants.L_LOGS;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
 import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -124,6 +125,8 @@ public class RefDirectory extends RefDatabase {
 
 	private final File gitDir;
 
+	private final File gitCommonDir;
+
 	final File refsDir;
 
 	final File packedRefsFile;
@@ -188,6 +191,7 @@ public class RefDirectory extends RefDatabase {
 	RefDirectory(RefDirectory refDb) {
 		parent = refDb.parent;
 		gitDir = refDb.gitDir;
+		gitCommonDir = refDb.gitCommonDir;
 		refsDir = refDb.refsDir;
 		logsDir = refDb.logsDir;
 		logsRefsDir = refDb.logsRefsDir;
@@ -204,10 +208,11 @@ public class RefDirectory extends RefDatabase {
 		final FS fs = db.getFS();
 		parent = db;
 		gitDir = db.getDirectory();
-		refsDir = fs.resolve(gitDir, R_REFS);
-		logsDir = fs.resolve(gitDir, LOGS);
-		logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
-		packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
+		gitCommonDir = db.getCommonDirectory();
+		refsDir = fs.resolve(gitCommonDir, R_REFS);
+		logsDir = fs.resolve(gitCommonDir, LOGS);
+		logsRefsDir = fs.resolve(gitCommonDir, L_LOGS + R_REFS);
+		packedRefsFile = fs.resolve(gitCommonDir, PACKED_REFS);
 
 		looseRefs.set(RefList.<LooseRef> emptyList());
 		packedRefs.set(NO_PACKED_REFS);
@@ -1329,7 +1334,12 @@ File fileFor(String name) {
 			name = name.substring(R_REFS.length());
 			return new File(refsDir, name);
 		}
-		return new File(gitDir, name);
+		// HEAD needs to get resolved from git dir as resolving it from common dir
+		// would always lead back to current default branch
+		if (name.equals(HEAD)) {
+			return new File(gitDir, name);
+		}
+		return new File(gitCommonDir, name);
 	}
 
 	static int levelsIn(String name) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
index 21b5a54..f1888eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
@@ -10,6 +10,8 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -37,7 +39,9 @@ class ReflogReaderImpl implements ReflogReader {
 	 *            {@code Ref} name
 	 */
 	ReflogReaderImpl(Repository db, String refname) {
-		logName = new File(db.getDirectory(), Constants.LOGS + '/' + refname);
+		File logBaseDir = refname.equals(HEAD) ? db.getDirectory()
+				: db.getCommonDirectory();
+		logName = new File(logBaseDir, Constants.L_LOGS + refname);
 	}
 
 	/* (non-Javadoc)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index 5dfb648..d232be6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -13,13 +13,17 @@
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE;
+import static org.eclipse.jgit.lib.Constants.CONFIG;
 import static org.eclipse.jgit.lib.Constants.DOT_GIT;
 import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY;
 import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_COMMON_DIR_KEY;
 import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY;
 import static org.eclipse.jgit.lib.Constants.GIT_INDEX_FILE_KEY;
 import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY;
 import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY;
+import static org.eclipse.jgit.lib.Constants.OBJECTS;
+import static org.eclipse.jgit.lib.Constants.GITDIR_FILE;
 
 import java.io.File;
 import java.io.IOException;
@@ -70,7 +74,21 @@ private static boolean isSymRef(byte[] ref) {
 				&& ref[7] == ' ';
 	}
 
-	private static File getSymRef(File workTree, File dotGit, FS fs)
+	/**
+	 * Read symbolic reference file
+	 *
+	 * @param workTree
+	 *            the work tree path
+	 * @param dotGit
+	 *            the .git file
+	 * @param fs
+	 *            th FS util
+	 * @return the file read from symbolic reference file
+	 * @throws java.io.IOException
+	 *             the dotGit file is invalid reference
+	 * @since 7.0
+	 */
+	static File getSymRef(File workTree, File dotGit, FS fs)
 			throws IOException {
 		byte[] content = IO.readFully(dotGit);
 		if (!isSymRef(content)) {
@@ -102,6 +120,8 @@ private static File getSymRef(File workTree, File dotGit, FS fs)
 
 	private File gitDir;
 
+	private File gitCommonDir;
+
 	private File objectDirectory;
 
 	private List<File> alternateObjectDirectories;
@@ -172,6 +192,30 @@ public File getGitDir() {
 	}
 
 	/**
+	 * Set common dir.
+	 *
+	 * @param gitCommonDir
+	 *            {@code GIT_COMMON_DIR}, the common repository meta directory.
+	 * @return {@code this} (for chaining calls).
+	 * @since 7.0
+	 */
+	public B setGitCommonDir(File gitCommonDir) {
+		this.gitCommonDir = gitCommonDir;
+		this.config = null;
+		return self();
+	}
+
+	/**
+	 * Get common dir.
+	 *
+	 * @return common dir; null if not set.
+	 * @since 7.0
+	 */
+	public File getGitCommonDir() {
+		return gitCommonDir;
+	}
+
+	/**
 	 * Set the directory storing the repository's objects.
 	 *
 	 * @param objectDirectory
@@ -396,9 +440,9 @@ public B setInitialBranch(String branch) throws InvalidRefNameException {
 	 * Read standard Git environment variables and configure from those.
 	 * <p>
 	 * This method tries to read the standard Git environment variables, such as
-	 * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
-	 * instance. If an environment variable is set, it overrides the value
-	 * already set in this builder.
+	 * {@code GIT_DIR}, {@code GIT_COMMON_DIR}, {@code GIT_WORK_TREE} etc. to
+	 * configure this builder instance. If an environment variable is set, it
+	 * overrides the value already set in this builder.
 	 *
 	 * @return {@code this} (for chaining calls).
 	 */
@@ -410,9 +454,9 @@ public B readEnvironment() {
 	 * Read standard Git environment variables and configure from those.
 	 * <p>
 	 * This method tries to read the standard Git environment variables, such as
-	 * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
-	 * instance. If a property is already set in the builder, the environment
-	 * variable is not used.
+	 * {@code GIT_DIR}, {@code GIT_COMMON_DIR}, {@code GIT_WORK_TREE} etc. to
+	 * configure this builder instance. If a property is already set in the
+	 * builder, the environment variable is not used.
 	 *
 	 * @param sr
 	 *            the SystemReader abstraction to access the environment.
@@ -425,6 +469,13 @@ public B readEnvironment(SystemReader sr) {
 				setGitDir(new File(val));
 		}
 
+		if (getGitCommonDir() == null) {
+			String val = sr.getenv(GIT_COMMON_DIR_KEY);
+			if (val != null) {
+				setGitCommonDir(new File(val));
+			}
+		}
+
 		if (getObjectDirectory() == null) {
 			String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY);
 			if (val != null)
@@ -601,6 +652,7 @@ public B findGitDir(File current) {
 	public B setup() throws IllegalArgumentException, IOException {
 		requireGitDirOrWorkTree();
 		setupGitDir();
+		setupCommonDir();
 		setupWorkTree();
 		setupInternals();
 		return self();
@@ -658,6 +710,20 @@ protected void setupGitDir() throws IOException {
 	}
 
 	/**
+	 * Perform standard common dir initialization.
+	 *
+	 * @throws java.io.IOException
+	 *             the repository could not be accessed
+	 * @since 7.0
+	 */
+	protected void setupCommonDir() throws IOException {
+		// no gitCommonDir? Try to get it from gitDir
+		if (getGitCommonDir() == null) {
+			setGitCommonDir(safeFS().getCommonDir(getGitDir()));
+		}
+	}
+
+	/**
 	 * Perform standard work-tree initialization.
 	 * <p>
 	 * This is a method typically invoked inside of {@link #setup()}, near the
@@ -695,8 +761,12 @@ protected void setupWorkTree() throws IOException {
 	 *             the repository could not be accessed
 	 */
 	protected void setupInternals() throws IOException {
-		if (getObjectDirectory() == null && getGitDir() != null)
-			setObjectDirectory(safeFS().resolve(getGitDir(), Constants.OBJECTS));
+		if (getObjectDirectory() == null) {
+			File commonDir = getGitCommonDir();
+			if (commonDir != null) {
+				setObjectDirectory(safeFS().resolve(commonDir, OBJECTS));
+			}
+		}
 	}
 
 	/**
@@ -723,12 +793,13 @@ protected Config getConfig() throws IOException {
 	 *             the configuration is not available.
 	 */
 	protected Config loadConfig() throws IOException {
-		if (getGitDir() != null) {
+		File commonDir = getGitCommonDir();
+		if (commonDir != null) {
 			// We only want the repository's configuration file, and not
 			// the user file, as these parameters must be unique to this
 			// repository and not inherited from other files.
 			//
-			File path = safeFS().resolve(getGitDir(), Constants.CONFIG);
+			File path = safeFS().resolve(commonDir, CONFIG);
 			FileBasedConfig cfg = new FileBasedConfig(path, safeFS());
 			try {
 				cfg.load();
@@ -749,8 +820,29 @@ private File guessWorkTreeOrFail() throws IOException {
 		//
 		String path = cfg.getString(CONFIG_CORE_SECTION, null,
 				CONFIG_KEY_WORKTREE);
-		if (path != null)
+		if (path != null) {
 			return safeFS().resolve(getGitDir(), path).getCanonicalFile();
+		}
+
+		/*
+		 * We are in worktree's $GIT_DIR folder
+		 * ".git/worktrees/&lt;worktree-name&gt;" and want to get the working
+		 * tree (checkout) path; so here we have an opposite link in file
+		 * "gitdir" showing to the ".git" file located in the working tree read
+		 * it and convert it to absolute path if it's relative
+		 */
+		File gitDirFile = new File(getGitDir(), GITDIR_FILE);
+		if (gitDirFile.isFile()) {
+			String workDirPath = new String(IO.readFully(gitDirFile)).trim();
+			File workTreeDotGitFile = new File(workDirPath);
+			if (!workTreeDotGitFile.isAbsolute()) {
+				workTreeDotGitFile = new File(getGitDir(), workDirPath)
+						.getCanonicalFile();
+			}
+			if (workTreeDotGitFile != null) {
+				return workTreeDotGitFile.getParentFile();
+			}
+		}
 
 		// If core.bare is set, honor its value. Assume workTree is
 		// the parent directory of the repository.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 1835dc7..b9c90bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -273,6 +273,20 @@ public final class Constants {
 	public static final String INFO_REFS = "info/refs";
 
 	/**
+	 * Name of heads folder or file in refs.
+	 *
+	 * @since 7.0
+	 */
+	public static final String HEADS = "heads";
+
+	/**
+	 * Prefix for any log.
+	 *
+	 * @since 7.0
+	 */
+	public static final String L_LOGS = LOGS + "/";
+
+	/**
 	 * Info alternates file (goes under OBJECTS)
 	 * @since 5.5
 	 */
@@ -358,6 +372,14 @@ public final class Constants {
 	public static final String GIT_DIR_KEY = "GIT_DIR";
 
 	/**
+	 * The environment variable that tells us which directory is the common
+	 * ".git" directory.
+	 *
+	 * @since 7.0
+	 */
+	public static final String GIT_COMMON_DIR_KEY = "GIT_COMMON_DIR";
+
+	/**
 	 * The environment variable that tells us which directory is the working
 	 * directory.
 	 */
@@ -459,6 +481,36 @@ public final class Constants {
 	public static final String GITDIR = "gitdir: ";
 
 	/**
+	 * Name of the file (inside gitDir) that references the worktree's .git
+	 * file (opposite link).
+	 *
+	 * .git/worktrees/&lt;worktree-name&gt;/gitdir
+	 *
+	 * A text file containing the absolute path back to the .git file that
+	 * points here. This file is used to verify if the linked repository has been
+	 * manually removed in which case this directory is no longer needed.
+	 * The modification time (mtime) of this file should be updated each time
+	 * the linked repository is accessed.
+	 *
+	 * @since 7.0
+	 */
+	public static final String GITDIR_FILE = "gitdir";
+
+	/**
+	 * Name of the file (inside gitDir) that has reference to $GIT_COMMON_DIR.
+	 *
+	 * .git/worktrees/&lt;worktree-name&gt;/commondir
+	 *
+	 * If this file exists, $GIT_COMMON_DIR will be set to the path specified in
+	 * this file unless it is explicitly set. If the specified path is relative,
+	 * it is relative to $GIT_DIR. The repository with commondir is incomplete
+	 * without the repository pointed by "commondir".
+	 *
+	 * @since 7.0
+	 */
+	public static final String COMMONDIR_FILE = "commondir";
+
+	/**
 	 * Name of the folder (inside gitDir) where submodules are stored
 	 *
 	 * @since 3.6
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 8e965c5..a99c647 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -639,7 +639,7 @@ public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
 							// submodule repository in .git/modules doesn't
 							// exist yet it isn't "missing".
 							File gitDir = new File(
-									new File(repository.getDirectory(),
+									new File(repository.getCommonDirectory(),
 											Constants.MODULES),
 									subRepoPath);
 							if (!gitDir.isDirectory()) {
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 4722e29..9dde99f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -113,9 +113,12 @@ public static ListenerList getGlobalListenerList() {
 
 	final AtomicLong closedAt = new AtomicLong();
 
-	/** Metadata directory holding the repository's critical files. */
+	/** $GIT_DIR: metadata directory holding the repository's critical files. */
 	private final File gitDir;
 
+	/** $GIT_COMMON_DIR: metadata directory holding the common repository's critical files.  */
+	private final File gitCommonDir;
+
 	/** File abstraction used to resolve paths. */
 	private final FS fs;
 
@@ -137,6 +140,7 @@ public static ListenerList getGlobalListenerList() {
 	 */
 	protected Repository(BaseRepositoryBuilder options) {
 		gitDir = options.getGitDir();
+		gitCommonDir = options.getGitCommonDir();
 		fs = options.getFS();
 		workTree = options.getWorkTree();
 		indexFile = options.getIndexFile();
@@ -220,6 +224,16 @@ public File getDirectory() {
 	public abstract String getIdentifier();
 
 	/**
+	 * Get common dir.
+	 *
+	 * @return $GIT_COMMON_DIR: local common metadata directory;
+	 * @since 7.0
+	 */
+	public File getCommonDirectory() {
+		return gitCommonDir;
+	}
+
+	/**
 	 * Get the object database which stores this repository's data.
 	 *
 	 * @return the object database which stores this repository's data.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 6288447..1836654 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -450,10 +450,21 @@ public String toString() {
 		 *         Git directory.
 		 */
 		public static boolean isGitRepository(File dir, FS fs) {
-			return fs.resolve(dir, Constants.OBJECTS).exists()
-					&& fs.resolve(dir, "refs").exists() //$NON-NLS-1$
-					&& (fs.resolve(dir, Constants.REFTABLE).exists()
-							|| isValidHead(new File(dir, Constants.HEAD)));
+			// check if common-dir available or fallback to git-dir
+			File commonDir;
+			try {
+				commonDir = fs.getCommonDir(dir);
+			} catch (IOException e) {
+				commonDir = null;
+			}
+			if (commonDir == null) {
+				commonDir = dir;
+			}
+			return fs.resolve(commonDir, Constants.OBJECTS).exists()
+					&& fs.resolve(commonDir, "refs").exists() //$NON-NLS-1$
+					&& (fs.resolve(commonDir, Constants.REFTABLE).exists()
+							|| isValidHead(
+									new File(commonDir, Constants.HEAD)));
 		}
 
 		private static boolean isValidHead(File head) {
@@ -496,15 +507,31 @@ private static String readFirstLine(File head) {
 		 *         null if there is no suitable match.
 		 */
 		public static File resolve(File directory, FS fs) {
-			if (isGitRepository(directory, fs))
+			// the folder itself
+			if (isGitRepository(directory, fs)) {
 				return directory;
-			if (isGitRepository(new File(directory, Constants.DOT_GIT), fs))
-				return new File(directory, Constants.DOT_GIT);
-
-			final String name = directory.getName();
-			final File parent = directory.getParentFile();
-			if (isGitRepository(new File(parent, name + Constants.DOT_GIT_EXT), fs))
-				return new File(parent, name + Constants.DOT_GIT_EXT);
+			}
+			// the .git subfolder or file (reference)
+			File dotDir = new File(directory, Constants.DOT_GIT);
+			if (dotDir.isFile()) {
+				try {
+					File refDir = BaseRepositoryBuilder.getSymRef(directory,
+							dotDir, fs);
+					if (refDir != null && isGitRepository(refDir, fs)) {
+						return refDir;
+					}
+				} catch (IOException ignored) {
+					// Continue searching if gitdir ref isn't found
+				}
+			} else if (isGitRepository(dotDir, fs)) {
+				return dotDir;
+			}
+			// the folder extended with .git (bare)
+			File bareDir = new File(directory.getParentFile(),
+					directory.getName() + Constants.DOT_GIT_EXT);
+			if (isGitRepository(bareDir, fs)) {
+				return bareDir;
+			}
 			return null;
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
index 0fc9710..f77b041 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -254,6 +254,12 @@ private ProcessBuilder createProcess(List<String> args,
 				pb.environment().put(Constants.GIT_DIR_KEY,
 						directory.getPath());
 			}
+			File commonDirectory = local != null ? local.getCommonDirectory()
+					: null;
+			if (commonDirectory != null) {
+				pb.environment().put(Constants.GIT_COMMON_DIR_KEY,
+						commonDirectory.getPath());
+			}
 			return pb;
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
index 3a06ce5..1b9431c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
@@ -225,6 +225,7 @@ private Process spawn(String cmd,
 			env.remove("GIT_CONFIG"); //$NON-NLS-1$
 			env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
 			env.remove("GIT_DIR"); //$NON-NLS-1$
+			env.remove("GIT_COMMON_DIR"); //$NON-NLS-1$
 			env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
 			env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
 			env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 73a3dda..95e9964 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -498,6 +498,8 @@ private InputStream filterClean(InputStream in)
 			filterProcessBuilder.directory(repository.getWorkTree());
 			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
 					repository.getDirectory().getAbsolutePath());
+			filterProcessBuilder.environment().put(Constants.GIT_COMMON_DIR_KEY,
+					repository.getCommonDirectory().getAbsolutePath());
 			ExecutionResult result;
 			try {
 				result = fs.execute(filterProcessBuilder, in);
@@ -1332,7 +1334,7 @@ IgnoreNode load(IgnoreNode parent) throws IOException {
 
 			IgnoreNode infoExclude = new IgnoreNodeWithParent(
 					coreExclude);
-			File exclude = fs.resolve(repository.getDirectory(),
+			File exclude = fs.resolve(repository.getCommonDirectory(),
 					Constants.INFO_EXCLUDE);
 			if (fs.exists(exclude)) {
 				loadRulesFromFile(infoExclude, exclude);
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 a8e1dae..6933a6c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -2042,6 +2042,8 @@ protected ProcessResult internalRunHookIfPresent(Repository repository,
 		environment.put(Constants.GIT_DIR_KEY,
 				repository.getDirectory().getAbsolutePath());
 		if (!repository.isBare()) {
+			environment.put(Constants.GIT_COMMON_DIR_KEY,
+					repository.getCommonDirectory().getAbsolutePath());
 			environment.put(Constants.GIT_WORK_TREE_KEY,
 					repository.getWorkTree().getAbsolutePath());
 		}
@@ -2137,7 +2139,7 @@ private File getRunDirectory(Repository repository,
 		case "post-receive": //$NON-NLS-1$
 		case "post-update": //$NON-NLS-1$
 		case "push-to-checkout": //$NON-NLS-1$
-			return repository.getDirectory();
+			return repository.getCommonDirectory();
 		default:
 			return repository.getWorkTree();
 		}
@@ -2150,7 +2152,7 @@ private File getHooksDirectory(Repository repository) {
 		if (hooksDir != null) {
 			return new File(hooksDir);
 		}
-		File dir = repository.getDirectory();
+		File dir = repository.getCommonDirectory();
 		return dir == null ? null : new File(dir, Constants.HOOKS);
 	}
 
@@ -2578,6 +2580,33 @@ public String normalize(String name) {
 	}
 
 	/**
+	 * Get common dir path.
+	 *
+	 * @param dir
+	 *            the .git folder
+	 * @return common dir path
+	 * @throws IOException
+	 *             if commondir file can't be read
+	 *
+	 * @since 7.0
+	 */
+	public File getCommonDir(File dir) throws IOException {
+		// first the GIT_COMMON_DIR is same as GIT_DIR
+		File commonDir = dir;
+		// now check if commondir file exists (e.g. worktree repository)
+		File commonDirFile = new File(dir, Constants.COMMONDIR_FILE);
+		if (commonDirFile.isFile()) {
+			String commonDirPath = new String(IO.readFully(commonDirFile))
+					.trim();
+			commonDir = new File(commonDirPath);
+			if (!commonDir.isAbsolute()) {
+				commonDir = new File(dir, commonDirPath).getCanonicalFile();
+			}
+		}
+		return commonDir;
+	}
+
+	/**
 	 * This runnable will consume an input stream's content into an output
 	 * stream as soon as it gets available.
 	 * <p>