]> source.dussan.org Git - jgit.git/commitdiff
Add worktrees read support 00/1194900/7
authorJanne Valkealahti <janne.valkealahti@broadcom.com>
Thu, 4 Jul 2024 06:44:49 +0000 (07:44 +0100)
committerMatthias Sohn <matthias.sohn@sap.com>
Sun, 14 Jul 2024 21:39:07 +0000 (23:39 +0200)
Based on deritative work done in Andre's work in [1].

This change focuses on adding support for reading the repository
state when branches are checked out using git's worktrees.

I've refactored original work by removing all unrelevant
changes which were mostly around refactoring to extract
i.e. constants which mostly created noise for a review.

I've tried to address original review comments:
- Not adding non-behavioral changes
- "HEAD" should get resolved from gitDir
- Reftable recently landed in cgit 2.45,
  see https://github.com/git/git/blob/master/Documentation/RelNotes/2.45.0.txt#L8
  We can add worktree support for reftable in a later change.
- Some new tests to read from a linked worktree which
  is created manually as there's no write support.

[1] https://git.eclipse.org/r/c/jgit/jgit/+/163940/18

Change-Id: Id077d58fb6c09ecb090eb09d5dbc7edc351a581d

29 files changed:
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LinkedWorktreeTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java

index 52f40c2957ba67b013861ed56961d1bbb50e4f80..f5de7045d09ffa8aaaa0ed245eb5fbf3cdb88542 100644 (file)
@@ -94,7 +94,7 @@ class Config extends TextBuiltin {
                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));
        }
 
index b937b1f6a9ffdc0cb752cee79b039996e3aa07f2..4c971ffb6b8e15c111074ca8a1d00e5088d4b4b1 100644 (file)
@@ -559,7 +559,7 @@ public class EolRepositoryTest extends RepositoryTestCase {
 
                }
                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 (file)
index 0000000..3b60e1b
--- /dev/null
@@ -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());
+       }
+
+}
index 7fb98ec53b3554d9df02af2605715927e0ca3c33..c41dd81add2fdf6ede19d30040c29c39be40cafc 100644 (file)
@@ -584,7 +584,7 @@ public class AttributesHandlerTest extends RepositoryTestCase {
 
                }
                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();
index daf4382719acb4fe0bb8db77f4a64c25935f16f5..1af42cb22980dcea54c2faa7bc2e9ccf943179e5 100644 (file)
@@ -171,7 +171,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
                        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);
 
index 8baa3cc3413744165fcb516a27edc93902d345fa..c57295518d632a42463ae21dd13cc7ec001941dd 100644 (file)
@@ -58,7 +58,7 @@ public class GcPackRefsTest extends GcTestCase {
                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));
index e6c1ee5fd6c7850c380c5fdaac00f1ae6a99647a..29f180d76b1ff1ca21713e67e461836a99b91f30 100644 (file)
@@ -30,7 +30,7 @@ public class GcReflogTest extends GcTestCase {
                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);
index 2bafde65d38517891f3c00477e619f5e779673f2..baa0182b876448230f254c245c9b415c71dbb401 100644 (file)
@@ -90,25 +90,26 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
        @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 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
        }
 
        private void deleteLooseRef(String name) {
-               File path = new File(diskRepo.getDirectory(), name);
+               File path = new File(diskRepo.getCommonDirectory(), name);
                assertTrue("deleted " + name, path.delete());
        }
 }
index dc0e7493733e510963329771ced9b684e065b890..eb521ff9eb6d228105f7190dc6c62cf3d64c8b3f 100644 (file)
@@ -238,7 +238,7 @@ public class ReflogReaderTest extends SampleDataRepositoryTestCase {
 
        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(
index 8d0e99dea03500b273bd947fd21ab91e467526d6..8e9b7b84bd2c470d622dacb6e5053b6e0c0e898e 100644 (file)
@@ -48,7 +48,7 @@ public class ReflogWriterTest extends SampleDataRepositoryTestCase {
 
        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(
index 8fb5d60b8582d4afd2002d3a297f7e608fa11f67..401f069e4e791c4da74b4c7230acc2fcb7af6019 100644 (file)
@@ -176,7 +176,7 @@ public class SubmoduleAddCommand extends
                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)
index df7316416150ab096a4e826dd323ce54ec726a54..751dabcd6b2a543ed41c1d8f2fb9203e79af4efa 100644 (file)
@@ -128,7 +128,7 @@ public class SubmoduleUpdateCommand extends
                        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);
index 6ae5153c1227d85a9b42b098ea6cc43879b84811..fa0a82fd58c194c29828900306b333125e3f8d96 100644 (file)
@@ -1647,6 +1647,8 @@ public class DirCacheCheckout {
                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 {
index ed2516ddd04a1a445fd40408fb0f16538809d038..80240e5062be9eee26d636b2c8da9f33157397c4 100644 (file)
@@ -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 class FileReftableDatabase extends RefDatabase {
        @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 class FileReftableDatabase extends RefDatabase {
                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()) {
index e5a00d39258cd0a7fb0d480e11787da78085309f..b5d29a3fc8b6126eb77a060aaab68469691e8545 100644 (file)
@@ -165,7 +165,7 @@ public class FileRepository extends Repository {
                        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 class FileRepository extends Repository {
                                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 class FileRepository extends Repository {
         *             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 @@ public class FileRepository extends Repository {
                }
 
                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 @@ public class FileRepository extends Repository {
        @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 @@ public class FileRepository extends Repository {
                // 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 @@ public class FileRepository extends Repository {
                        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 @@ public class FileRepository extends Repository {
 
                // 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,
index cf26f8d284eeae93997e972b7f88ff0a775d2721..4fafc5a0886cb0fb1420460abee1999b4a338df9 100644 (file)
@@ -1047,7 +1047,7 @@ public class GC {
        }
 
        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);
index 628bf5db0c7d019f9d4bb3ddebdaf1679cd9155f..8647b3e66426c49f635d221e8db25f84b8b74ec9 100644 (file)
@@ -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);
        }
 
index 11d842b246d7a04c6408597e3509395321d37951..e8d442b8fb663f6708d05375c0e27e806b7a1d93 100644 (file)
@@ -46,7 +46,7 @@ public class InfoAttributesNode extends AttributesNode {
 
                FS fs = repository.getFS();
 
-               File attributes = fs.resolve(repository.getDirectory(),
+               File attributes = fs.resolve(repository.getCommonDirectory(),
                                Constants.INFO_ATTRIBUTES);
                FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
 
index 8e57bf9f2fcf887248e8c411a1913d2403fa033c..604868133e5e6425efab80339ec7a36c34ac7d5d 100644 (file)
@@ -16,6 +16,7 @@ package org.eclipse.jgit.internal.storage.file;
 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 @@ public class RefDirectory extends RefDatabase {
                        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) {
index 21b5a54eb7ade1d84f8f7541fed2614071ac5f05..f1888eb90f7cf75835882848c6731a0467f54959 100644 (file)
@@ -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)
index 5dfb648faa605cd1f351008d39568772aa41e9c4..d232be6276734ab9c24ae9baf734691e53d7967a 100644 (file)
@@ -13,13 +13,17 @@ package org.eclipse.jgit.lib;
 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 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
                                && 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 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
 
        private File gitDir;
 
+       private File gitCommonDir;
+
        private File objectDirectory;
 
        private List<File> alternateObjectDirectories;
@@ -171,6 +191,30 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
                return gitDir;
        }
 
+       /**
+        * 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.
         *
@@ -396,9 +440,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
         * 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 class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
         * 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 class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
                                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 class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
        public B setup() throws IllegalArgumentException, IOException {
                requireGitDirOrWorkTree();
                setupGitDir();
+               setupCommonDir();
                setupWorkTree();
                setupInternals();
                return self();
@@ -657,6 +709,20 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
                }
        }
 
+       /**
+        * 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>
@@ -695,8 +761,12 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
         *             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 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
         *             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 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
                //
                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.
index 1835dc76a2a4fe8b37fce7ec1c43ad5b827aa752..b9c90bda30ef0cf3850f3b05ec849c4f9c150457 100644 (file)
@@ -272,6 +272,20 @@ public final class Constants {
        /** Info refs folder */
        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
@@ -357,6 +371,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.
@@ -458,6 +480,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
         *
index 8e965c5e9d560bef454013232c280ded84027155..a99c64701fd9282fc27f2b83ab67ea2a87cc66de 100644 (file)
@@ -639,7 +639,7 @@ public class IndexDiff {
                                                        // 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()) {
index 4722e29b4ca2cbffc0cac211b1f49a62e0fe76c1..9dde99fada9f165d78577138db364fc8d30b6b3b 100644 (file)
@@ -113,9 +113,12 @@ public abstract class Repository implements AutoCloseable {
 
        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 abstract class Repository implements AutoCloseable {
         */
        protected Repository(BaseRepositoryBuilder options) {
                gitDir = options.getGitDir();
+               gitCommonDir = options.getGitCommonDir();
                fs = options.getFS();
                workTree = options.getWorkTree();
                indexFile = options.getIndexFile();
@@ -219,6 +223,16 @@ public abstract class Repository implements AutoCloseable {
         */
        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.
         *
index 6288447a8d64b4287833b5c12af6b2d3ecba9357..18366541da8412656ba4df02cb899b42bcb41ad3 100644 (file)
@@ -450,10 +450,21 @@ public class RepositoryCache {
                 *         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 @@ public class RepositoryCache {
                 *         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;
                }
        }
index 0fc9710ecb2cd3044e21a00e533c299c6d18217b..f77b04110d20a0b71d4feaf78af2b5524bd1fdb9 100644 (file)
@@ -254,6 +254,12 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
                                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;
                }
 
index 3a06ce5b6332e273fd694bc969a9314ddf4461fa..1b9431ce6ea68538aa3d0019f3eaf6f400d1c5e7 100644 (file)
@@ -225,6 +225,7 @@ class TransportLocal extends Transport implements PackTransport {
                        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$
index 73a3ddaae7bd555017340526640cf9cdcd736e1c..95e9964192bcbe566a7fe4cd5ac01d6ad6385963 100644 (file)
@@ -498,6 +498,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
                        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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 
                        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);
index a8e1dae10e76fcc151de777f12ceabf33e1205bd..6933a6c5671a8001d21d511a421e6de557ab6dda 100644 (file)
@@ -2042,6 +2042,8 @@ public abstract class FS {
                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 @@ public abstract class FS {
                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 @@ public abstract class FS {
                if (hooksDir != null) {
                        return new File(hooksDir);
                }
-               File dir = repository.getDirectory();
+               File dir = repository.getCommonDirectory();
                return dir == null ? null : new File(dir, Constants.HOOKS);
        }
 
@@ -2577,6 +2579,33 @@ public abstract class FS {
                return 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.