diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryFiles.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryFiles.java
index f6da0f99053b..9fa49dadeecf 100644
--- a/plugins/git4idea/src/git4idea/repo/GitRepositoryFiles.java
+++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryFiles.java
@@ -19,10 +19,13 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.util.GitFileUtils;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import java.io.File;
import java.util.Arrays;
import java.util.Collection;
+import static com.intellij.psi.impl.SyntheticFileSystemItem.LOG;
import static git4idea.GitUtil.DOT_GIT;
/**
@@ -45,18 +48,16 @@ public class GitRepositoryFiles {
public static final String REBASE_APPLY = "rebase-apply";
public static final String REBASE_MERGE = "rebase-merge";
public static final String PACKED_REFS = "packed-refs";
- public static final String REFS_HEADS = "refs/heads";
- public static final String REFS_REMOTES = "refs/remotes";
- public static final String REFS_TAGS = "refs/tags";
+ public static final String REFS = "refs";
+ public static final String HEADS = "heads";
+ public static final String TAGS = "tags";
+ public static final String REMOTES = "remotes";
public static final String SQUASH_MSG = "SQUASH_MSG";
public static final String GIT_HEAD = DOT_GIT + slash(HEAD);
- public static final String GIT_REFS_REMOTES = DOT_GIT + slash(REFS_REMOTES);
- public static final String GIT_PACKED_REFS = DOT_GIT + slash(PACKED_REFS);
public static final String GIT_MERGE_HEAD = DOT_GIT + slash(MERGE_HEAD);
public static final String GIT_MERGE_MSG = DOT_GIT + slash(MERGE_MSG);
public static final String GIT_SQUASH_MSG = DOT_GIT + slash(SQUASH_MSG);
- public static final String GIT_COMMIT_EDITMSG = DOT_GIT + slash(COMMIT_EDITMSG);
private final String myGitDirPath;
private final String myConfigFilePath;
@@ -71,32 +72,64 @@ public class GitRepositoryFiles {
private final String myRefsRemotesDirPath;
private final String myRefsTagsPath;
private final String myCommitMessagePath;
+ private final String myInfoDirPath;
private final String myExcludePath;
- public static GitRepositoryFiles getInstance(@NotNull VirtualFile gitDir) {
- // maybe will be cached later to store a single GitRepositoryFiles for a root.
- return new GitRepositoryFiles(gitDir);
- }
-
- private GitRepositoryFiles(@NotNull VirtualFile gitDir) {
- // add .git/ and .git/refs/heads to the VFS
- // save paths of the files, that we will watch
+ private GitRepositoryFiles(@NotNull VirtualFile gitDir, @NotNull File headFile, @NotNull File refsDir, @NotNull File packedRefsFile) {
myGitDirPath = GitFileUtils.stripFileProtocolPrefix(gitDir.getPath());
myConfigFilePath = myGitDirPath + slash(CONFIG);
- myHeadFilePath = myGitDirPath + slash(HEAD);
+ myHeadFilePath = FileUtil.toSystemIndependentName(headFile.getPath());
myIndexFilePath = myGitDirPath + slash(INDEX);
myMergeHeadPath = myGitDirPath + slash(MERGE_HEAD);
myOrigHeadPath = myGitDirPath + slash(ORIG_HEAD);
myCommitMessagePath = myGitDirPath + slash(COMMIT_EDITMSG);
myRebaseApplyPath = myGitDirPath + slash(REBASE_APPLY);
myRebaseMergePath = myGitDirPath + slash(REBASE_MERGE);
- myPackedRefsPath = myGitDirPath + slash(PACKED_REFS);
- myRefsHeadsDirPath = myGitDirPath + slash(REFS_HEADS);
- myRefsTagsPath = myGitDirPath + slash(REFS_TAGS);
- myRefsRemotesDirPath = myGitDirPath + slash(REFS_REMOTES);
+ myPackedRefsPath = FileUtil.toSystemIndependentName(packedRefsFile.getPath());
+ String refsPath = FileUtil.toSystemIndependentName(refsDir.getPath());
+ myRefsHeadsDirPath = refsPath + slash(HEADS);
+ myRefsTagsPath = refsPath + slash(TAGS);
+ myRefsRemotesDirPath = refsPath + slash(REMOTES);
+ myInfoDirPath = myGitDirPath + slash(INFO);
myExcludePath = myGitDirPath + slash(INFO_EXCLUDE);
}
+ @NotNull
+ public static GitRepositoryFiles getInstance(@NotNull VirtualFile gitDir) {
+ VirtualFile gitDirForWorktree = getMainGitDirForWorktree(gitDir);
+ File headFile = new File(gitDir.getPath(), HEAD);
+ File refsDir;
+ File packedRefsFile;
+ if (gitDirForWorktree == null) {
+ refsDir = new File(gitDir.getPath(), REFS);
+ packedRefsFile = new File(gitDir.getPath(), PACKED_REFS);
+ }
+ else {
+ refsDir = new File(gitDirForWorktree.getPath(), REFS);
+ packedRefsFile = new File(gitDirForWorktree.getPath(), PACKED_REFS);
+ }
+ return new GitRepositoryFiles(gitDir, headFile, refsDir, packedRefsFile);
+ }
+
+ /**
+ * Checks if the given .git directory is actually a worktree's git directory, and returns the main .git directory if it is true.
+ * If it is not a worktree, returns null.
+ *
+ * Worktree's ".git" file references {@code /.git/worktrees/}
+ */
+ @Nullable
+ private static VirtualFile getMainGitDirForWorktree(@NotNull VirtualFile gitDir) {
+ VirtualFile parent = gitDir.getParent();
+ if (parent == null) return null;
+ VirtualFile grandParent = parent.getParent();
+ if (grandParent == null) return null;
+ if (!gitDir.getName().equals(DOT_GIT) && parent.getName().equals("worktrees") && grandParent.getName().equals(DOT_GIT)) {
+ LOG.info("git dir " + gitDir.getPath() + " is a worktree");
+ return grandParent;
+ }
+ return null;
+ }
+
@NotNull
private static String slash(@NotNull String s) {
return "/" + s;
@@ -106,10 +139,15 @@ public class GitRepositoryFiles {
* Returns subdirectories of .git which we are interested in - they should be watched by VFS.
*/
@NotNull
- static Collection getSubDirRelativePaths() {
- return Arrays.asList(slash(REFS_HEADS), slash(REFS_REMOTES), slash(REFS_TAGS), slash(INFO));
+ Collection getDirsToWatch() {
+ return Arrays.asList(myRefsHeadsDirPath, myRefsRemotesDirPath, myRefsTagsPath, myInfoDirPath);
}
-
+
+ @NotNull
+ String getGitDirPath() {
+ return myGitDirPath;
+ }
+
@NotNull
String getRefsHeadsPath() {
return myRefsHeadsDirPath;
@@ -125,6 +163,16 @@ public class GitRepositoryFiles {
return myRefsTagsPath;
}
+ @NotNull
+ public String getPackedRefsPath() {
+ return myPackedRefsPath;
+ }
+
+ @NotNull
+ public String getHeadPath() {
+ return myHeadFilePath;
+ }
+
/**
* {@code .git/config}
*/
diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java
index ea72f96a1824..1ea380ee8a83 100644
--- a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java
+++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java
@@ -57,7 +57,7 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository {
myPlatformFacade = facade;
myVcs = assertNotNull(GitVcs.getInstance(project));
myGitDir = gitDir;
- myReader = new GitRepositoryReader(VfsUtilCore.virtualToIoFile(myGitDir));
+ myReader = new GitRepositoryReader(myGitDir);
myInfo = readRepoInfo();
if (!light) {
myUntrackedFilesHolder = new GitUntrackedFilesHolder(this);
diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java
index 3fbcd6d9bf76..cb00cb66e907 100644
--- a/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java
+++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java
@@ -23,6 +23,7 @@ import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.LineTokenizer;
+import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
@@ -67,14 +68,15 @@ class GitRepositoryReader {
@NotNull private final File myRefsRemotesDir; // .git/refs/remotes/
@NotNull private final File myPackedRefsFile; // .git/packed-refs
- GitRepositoryReader(@NotNull File gitDir) {
- myGitDir = gitDir;
- DvcsUtil.assertFileExists(myGitDir, ".git directory not found in " + gitDir);
- myHeadFile = new File(myGitDir, "HEAD");
- DvcsUtil.assertFileExists(myHeadFile, ".git/HEAD file not found in " + gitDir);
- myRefsHeadsDir = new File(new File(myGitDir, "refs"), "heads");
- myRefsRemotesDir = new File(new File(myGitDir, "refs"), "remotes");
- myPackedRefsFile = new File(myGitDir, "packed-refs");
+ GitRepositoryReader(@NotNull VirtualFile gitDir) {
+ GitRepositoryFiles gitFiles = GitRepositoryFiles.getInstance(gitDir);
+ myGitDir = new File(FileUtil.toSystemDependentName(gitFiles.getGitDirPath()));
+ DvcsUtil.assertFileExists(myGitDir, ".git directory not found in " + myGitDir);
+ myHeadFile = new File(FileUtil.toSystemDependentName(gitFiles.getHeadPath()));
+ DvcsUtil.assertFileExists(myHeadFile, ".git/HEAD file not found in " + myGitDir);
+ myRefsHeadsDir = new File(FileUtil.toSystemDependentName(gitFiles.getRefsHeadsPath()));
+ myRefsRemotesDir = new File(FileUtil.toSystemDependentName(gitFiles.getRefsRemotesPath()));
+ myPackedRefsFile = new File(FileUtil.toSystemDependentName(gitFiles.getPackedRefsPath()));
}
@NotNull
@@ -229,8 +231,8 @@ class GitRepositoryReader {
@NotNull
private Map readBranchRefsFromFiles() {
Map result = ContainerUtil.newHashMap(readPackedBranches()); // reading from packed-refs first to overwrite values by values from unpacked refs
- result.putAll(readFromBranchFiles(myRefsHeadsDir));
- result.putAll(readFromBranchFiles(myRefsRemotesDir));
+ result.putAll(readFromBranchFiles(myRefsHeadsDir, REFS_HEADS_PREFIX));
+ result.putAll(readFromBranchFiles(myRefsRemotesDir, REFS_REMOTES_PREFIX));
result.remove(REFS_REMOTES_PREFIX + GitUtil.ORIGIN_HEAD);
return result;
}
@@ -265,18 +267,18 @@ class GitRepositoryReader {
}
@NotNull
- private Map readFromBranchFiles(@NotNull File rootDir) {
- if (!rootDir.exists()) {
+ private static Map readFromBranchFiles(@NotNull final File refsRootDir, @NotNull final String prefix) {
+ if (!refsRootDir.exists()) {
return Collections.emptyMap();
}
final Map result = new HashMap();
- FileUtil.processFilesRecursively(rootDir, new Processor() {
+ FileUtil.processFilesRecursively(refsRootDir, new Processor() {
@Override
public boolean process(File file) {
if (!file.isDirectory() && !isHidden(file)) {
- String relativePath = FileUtil.getRelativePath(myGitDir, file);
+ String relativePath = FileUtil.getRelativePath(refsRootDir, file);
if (relativePath != null) {
- String branchName = FileUtil.toSystemIndependentName(relativePath);
+ String branchName = prefix + FileUtil.toSystemIndependentName(relativePath);
String hash = loadHashFromBranchFile(file);
if (hash != null) {
result.put(branchName, hash);
diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java
index b3e9c0ce6b22..54bb7088df23 100644
--- a/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java
+++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java
@@ -53,7 +53,7 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener {
myWatchRequest = LocalFileSystem.getInstance().addRootToWatch(gitDir.getPath(), true);
myRepositoryFiles = GitRepositoryFiles.getInstance(gitDir);
- DvcsUtil.visitVcsDirVfs(gitDir, GitRepositoryFiles.getSubDirRelativePaths());
+ visitSubDirsInVfs(gitDir);
myHeadsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsHeadsPath());
myRemotesDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsRemotesPath());
myTagsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsTagsPath());
@@ -128,4 +128,10 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener {
}
}
+ private void visitSubDirsInVfs(@NotNull VirtualFile gitDir) {
+ gitDir.getChildren();
+ for (String path : myRepositoryFiles.getDirsToWatch()) {
+ DvcsUtil.ensureAllChildrenInVfs(LocalFileSystem.getInstance().refreshAndFindFileByPath(path));
+ }
+ }
}
diff --git a/plugins/git4idea/tests/git4idea/repo/GitConfigTest.java b/plugins/git4idea/tests/git4idea/repo/GitConfigTest.java
index 813ec29cf482..2d95c099c31b 100644
--- a/plugins/git4idea/tests/git4idea/repo/GitConfigTest.java
+++ b/plugins/git4idea/tests/git4idea/repo/GitConfigTest.java
@@ -22,8 +22,8 @@ import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsTestUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.util.containers.ContainerUtil;
-import git4idea.GitBranch;
import git4idea.GitLocalBranch;
import git4idea.GitRemoteBranch;
import git4idea.GitStandardRemoteBranch;
@@ -64,7 +64,7 @@ public class GitConfigTest extends GitPlatformTest {
File gitDir = new File(myProjectPath, ".git");
GitConfig config = GitConfig.read(myPlatformFacade, new File(gitDir, "config"));
- GitBranchState state = new GitRepositoryReader(gitDir).readState(config.parseRemotes());
+ GitBranchState state = new GitRepositoryReader(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(gitDir)).readState(config.parseRemotes());
Collection trackInfos = config.parseTrackInfos(state.getLocalBranches().keySet(), state.getRemoteBranches().keySet());
assertTrue("Couldn't find correct a#branch tracking information among: [" + trackInfos + "]",
ContainerUtil.exists(trackInfos, new Condition() {
diff --git a/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderNewTest.java b/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderNewTest.java
index 114c58edcab9..bf6da7298c27 100644
--- a/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderNewTest.java
+++ b/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderNewTest.java
@@ -17,6 +17,7 @@ package git4idea.repo;
import com.intellij.dvcs.repo.Repository;
import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import git4idea.GitLocalBranch;
@@ -115,8 +116,8 @@ public class GitRepositoryReaderNewTest extends GitSingleRepoTest {
@NotNull
private GitBranchState readState() {
- File gitDir = new File(myRepo.getRoot().getPath(), ".git");
- GitConfig config = GitConfig.read(myPlatformFacade, new File(gitDir, "config"));
+ VirtualFile gitDir = myRepo.getGitDir();
+ GitConfig config = GitConfig.read(myPlatformFacade, new File(gitDir.getPath(), "config"));
GitRepositoryReader reader = new GitRepositoryReader(gitDir);
Collection remotes = config.parseRemotes();
return reader.readState(remotes);
diff --git a/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderTest.java b/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderTest.java
index dc56a6eb1f84..26f848b97a85 100644
--- a/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderTest.java
+++ b/plugins/git4idea/tests/git4idea/repo/GitRepositoryReaderTest.java
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsTestUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.testFramework.EdtTestUtil;
import com.intellij.util.Function;
import com.intellij.util.ThrowableRunnable;
@@ -114,7 +115,7 @@ public class GitRepositoryReaderTest extends GitPlatformTest {
}
FileUtil.rename(dotGit, myGitDir);
TestCase.assertTrue(myGitDir.exists());
- myRepositoryReader = new GitRepositoryReader(myGitDir);
+ myRepositoryReader = new GitRepositoryReader(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myGitDir));
}
diff --git a/plugins/git4idea/tests/git4idea/repo/GitWorkTreeTest.kt b/plugins/git4idea/tests/git4idea/repo/GitWorkTreeTest.kt
new file mode 100644
index 000000000000..057cbaf2cdf2
--- /dev/null
+++ b/plugins/git4idea/tests/git4idea/repo/GitWorkTreeTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.repo
+
+import com.intellij.openapi.vfs.LocalFileSystem
+import git4idea.GitUtil
+import git4idea.branch.GitBranchesCollection
+import git4idea.config.GitVersion
+import git4idea.test.GitExecutor.*
+import git4idea.test.GitPlatformTest
+import git4idea.test.GitTestUtil
+import git4idea.test.GitTestUtil.initRepo
+import org.junit.Assume.assumeTrue
+import java.io.File
+
+class GitWorkTreeTest : GitPlatformTest() {
+
+ private lateinit var myMainRoot: String
+ private lateinit var myRepo : GitRepository
+
+ override fun setUp() {
+ super.setUp()
+ cd(myTestRoot)
+ assumeTrue(GitVersion.parse(git("version")).isLaterOrEqual(GitVersion(2, 5, 0, 0)))
+
+ val mainDir = File(myTestRoot, "main")
+ assertTrue(mainDir.mkdir())
+ myMainRoot = mainDir.path
+ initRepo(myMainRoot, true)
+
+ git("worktree add $myProjectPath")
+ val gitDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(myProjectPath, GitUtil.DOT_GIT))
+ assertNotNull(gitDir)
+ myRepo = GitTestUtil.registerRepo(project, myProjectPath)
+ assertEquals(1, myGitRepositoryManager.repositories.size)
+ assertNotNull(myGitRepositoryManager.getRepositoryForRoot(myProjectRoot))
+ }
+
+ fun `test local branches`() {
+ cd(myMainRoot)
+ val masterHead = last()
+ git("checkout -b feature")
+ val featureHead = tac("f.txt")
+
+ myRepo.update()
+
+ val branches = myRepo.branches
+ val expectedBranches = listOf("master", "feature", "project") // 'project' is created automatically by `git worktree add`
+ assertSameElements(branches.localBranches.map {it.name}, expectedBranches)
+ assertBranchHash(masterHead, branches, "master")
+ assertBranchHash(featureHead, branches, "feature")
+ }
+
+ fun `test remote branches`() {
+ cd(myTestRoot)
+ git("clone --bare $myMainRoot parent.git")
+ cd(myMainRoot)
+ val parentPath = File(myTestRoot, "parent.git").path
+ git("remote add origin $parentPath")
+ git("push origin master")
+
+ val masterHead = last()
+ git("checkout -b feature")
+ val featureHead = tac("f.txt")
+ git("push origin feature")
+
+ myRepo.update()
+
+ val branches = myRepo.branches
+ assertSameElements(branches.remoteBranches.map {it.nameForLocalOperations}, listOf("origin/master", "origin/feature"))
+ assertBranchHash(masterHead, branches, "origin/master")
+ assertBranchHash(featureHead, branches, "origin/feature")
+ }
+
+ fun `test HEAD`() {
+ cd(myRepo)
+ git("checkout -b feature")
+ val featureHead = tac("f.txt")
+ myRepo.update()
+
+ assertEquals("Incorrect current branch", "feature", myRepo.currentBranchName)
+ assertEquals("Incorrect current revision", featureHead, myRepo.currentRevision)
+ }
+
+ private fun assertBranchHash(expectedHash: String, branches: GitBranchesCollection, branchName: String) {
+ assertEquals(expectedHash, branches.getHash(branches.findBranchByName(branchName)!!)!!.asString())
+ }
+}
diff --git a/plugins/git4idea/tests/git4idea/test/GitTestUtil.java b/plugins/git4idea/tests/git4idea/test/GitTestUtil.java
index 4e17719bd23c..39cb8865a693 100644
--- a/plugins/git4idea/tests/git4idea/test/GitTestUtil.java
+++ b/plugins/git4idea/tests/git4idea/test/GitTestUtil.java
@@ -78,7 +78,7 @@ public class GitTestUtil {
}
}
- private static void initRepo(@NotNull String repoRoot, boolean makeInitialCommit) {
+ public static void initRepo(@NotNull String repoRoot, boolean makeInitialCommit) {
cd(repoRoot);
git("init");
setupUsername();