From 2f79cf9900288991e51b8da408511ead05f4d085 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Feb 2012 23:10:28 +0100 Subject: [PATCH] Support gitdir references in working tree .git file A '.git' file in a repository's working tree root is now parsed as a ref to a folder located elsewhere. This supports submodules having their repository location outside of the parent repository's working directory such as in the parent repository's '.git/modules' directory. This adds support to BaseRepositoryBuilder for repositories created with the '--separate-git-dir' option specified to 'git init'. Change-Id: I73c538f6d845bdbc0c4e2bce5a77f900cf36e1a9 Signed-off-by: Matthias Sohn --- .../jgit/submodule/SubmoduleWalkTest.java | 103 +++++++++++++++++- .../org/eclipse/jgit/JGitText.properties | 1 + .../src/org/eclipse/jgit/JGitText.java | 1 + .../jgit/lib/BaseRepositoryBuilder.java | 50 ++++++++- .../eclipse/jgit/submodule/SubmoduleWalk.java | 53 ++++----- .../jgit/treewalk/FileTreeIterator.java | 2 +- .../jgit/treewalk/WorkingTreeIterator.java | 14 +-- 7 files changed, 173 insertions(+), 51 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java index c4bf33b8b..89843fc24 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java @@ -44,10 +44,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import org.eclipse.jgit.dircache.DirCache; @@ -58,7 +60,9 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.junit.Test; @@ -97,8 +101,6 @@ public void apply(DirCacheEntry ent) { assertEquals(path, gen.getPath()); assertEquals(id, gen.getObjectId()); assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); - assertEquals(new File(db.getWorkTree(), path + File.separatorChar - + Constants.DOT_GIT), gen.getGitDirectory()); assertNull(gen.getConfigUpdate()); assertNull(gen.getConfigUrl()); assertNull(gen.getModulesPath()); @@ -108,6 +110,101 @@ public void apply(DirCacheEntry ent) { assertFalse(gen.next()); } + @Test + public void repositoryWithRootLevelSubmoduleAbsoluteRef() + throws IOException, ConfigInvalidException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + File dotGit = new File(db.getWorkTree(), path + File.separatorChar + + Constants.DOT_GIT); + if (!dotGit.getParentFile().exists()) + dotGit.getParentFile().mkdirs(); + + File modulesGitDir = new File(db.getDirectory(), "modules" + + File.separatorChar + path); + new FileWriter(dotGit).append( + "gitdir: " + modulesGitDir.getAbsolutePath()).close(); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + builder.setWorkTree(new File(db.getWorkTree(), path)); + builder.build().create(); + + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleWalk gen = SubmoduleWalk.forIndex(db); + assertTrue(gen.next()); + assertEquals(path, gen.getPath()); + assertEquals(id, gen.getObjectId()); + assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); + assertNull(gen.getConfigUpdate()); + assertNull(gen.getConfigUrl()); + assertNull(gen.getModulesPath()); + assertNull(gen.getModulesUpdate()); + assertNull(gen.getModulesUrl()); + Repository subRepo = gen.getRepository(); + assertNotNull(subRepo); + assertEquals(modulesGitDir, subRepo.getDirectory()); + assertEquals(new File(db.getWorkTree(), path), subRepo.getWorkTree()); + assertFalse(gen.next()); + } + + @Test + public void repositoryWithRootLevelSubmoduleRelativeRef() + throws IOException, ConfigInvalidException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + File dotGit = new File(db.getWorkTree(), path + File.separatorChar + + Constants.DOT_GIT); + if (!dotGit.getParentFile().exists()) + dotGit.getParentFile().mkdirs(); + + File modulesGitDir = new File(db.getDirectory(), "modules" + + File.separatorChar + path); + new FileWriter(dotGit).append( + "gitdir: " + "../" + Constants.DOT_GIT + "/modules/" + path) + .close(); + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + builder.setWorkTree(new File(db.getWorkTree(), path)); + builder.build().create(); + + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleWalk gen = SubmoduleWalk.forIndex(db); + assertTrue(gen.next()); + assertEquals(path, gen.getPath()); + assertEquals(id, gen.getObjectId()); + assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); + assertNull(gen.getConfigUpdate()); + assertNull(gen.getConfigUrl()); + assertNull(gen.getModulesPath()); + assertNull(gen.getModulesUpdate()); + assertNull(gen.getModulesUrl()); + Repository subRepo = gen.getRepository(); + assertNotNull(subRepo); + assertEquals(modulesGitDir, subRepo.getDirectory()); + assertEquals(new File(db.getWorkTree(), path), subRepo.getWorkTree()); + assertFalse(gen.next()); + } + @Test public void repositoryWithNestedSubmodule() throws IOException, ConfigInvalidException { @@ -130,8 +227,6 @@ public void apply(DirCacheEntry ent) { assertEquals(path, gen.getPath()); assertEquals(id, gen.getObjectId()); assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); - assertEquals(new File(db.getWorkTree(), path + File.separatorChar - + Constants.DOT_GIT), gen.getGitDirectory()); assertNull(gen.getConfigUpdate()); assertNull(gen.getConfigUrl()); assertNull(gen.getModulesPath()); diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 5b2280134..8a5e02310 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -234,6 +234,7 @@ invalidChannel=Invalid channel {0} invalidCharacterInBase64Data=Invalid character in Base64 data. invalidCommitParentNumber=Invalid commit parent number invalidEncryption=Invalid encryption +invalidGitdirRef = Invalid .git reference in file ''{0}'' invalidGitType=invalid git type: {0} invalidId=Invalid id {0} invalidIdLength=Invalid id length {0}; should be {1} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index dbc3bcae6..bcd14c6a4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -294,6 +294,7 @@ public static JGitText get() { /***/ public String invalidCharacterInBase64Data; /***/ public String invalidCommitParentNumber; /***/ public String invalidEncryption; + /***/ public String invalidGitdirRef; /***/ public String invalidGitType; /***/ public String invalidId; /***/ public String invalidIdLength; 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 22a2ae505..7db0b9f87 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -69,6 +69,8 @@ import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; /** @@ -85,6 +87,19 @@ * @see FileRepositoryBuilder */ public class BaseRepositoryBuilder { + private static boolean isSymRef(byte[] ref) { + if (ref.length < 9) + return false; + return /**/ref[0] == 'g' // + && ref[1] == 'i' // + && ref[2] == 't' // + && ref[3] == 'd' // + && ref[4] == 'i' // + && ref[5] == 'r' // + && ref[6] == ':' // + && ref[7] == ' '; + } + private FS fs; private File gitDir; @@ -546,10 +561,37 @@ protected void requireGitDirOrWorkTree() { * the repository could not be accessed */ protected void setupGitDir() throws IOException { - // No gitDir? Try to assume its under the workTree. - // - if (getGitDir() == null && getWorkTree() != null) - setGitDir(new File(getWorkTree(), DOT_GIT)); + // No gitDir? Try to assume its under the workTree or a ref to another + // location + if (getGitDir() == null && getWorkTree() != null) { + File dotGit = new File(getWorkTree(), DOT_GIT); + if (!dotGit.isFile()) + setGitDir(dotGit); + else { + byte[] content = IO.readFully(dotGit); + if (!isSymRef(content)) + throw new IOException(MessageFormat.format( + JGitText.get().invalidGitdirRef, + dotGit.getAbsolutePath())); + int pathStart = 8; + int lineEnd = RawParseUtils.nextLF(content, pathStart); + if (content[lineEnd - 1] == '\n') + lineEnd--; + if (lineEnd == pathStart) + throw new IOException(MessageFormat.format( + JGitText.get().invalidGitdirRef, + dotGit.getAbsolutePath())); + + String gitdirPath = RawParseUtils.decode(content, pathStart, + lineEnd); + File gitdirFile = new File(gitdirPath); + if (gitdirFile.isAbsolute()) + setGitDir(gitdirFile); + else + setGitDir(new File(getWorkTree(), gitdirPath) + .getCanonicalFile()); + } + } } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java index 78896f8a5..f07f4d6c6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java @@ -155,27 +155,32 @@ public static File getSubmoduleDirectory(final Repository parent, */ public static Repository getSubmoduleRepository(final Repository parent, final String path) throws IOException { - File directory = getSubmoduleGitDirectory(parent, path); - if (!directory.isDirectory()) - return null; - try { - return new RepositoryBuilder().setMustExist(true) - .setFS(FS.DETECTED).setGitDir(directory).build(); - } catch (RepositoryNotFoundException e) { - return null; - } + return getSubmoduleRepository(parent.getWorkTree(), path); } /** - * Get the .git directory for a repository submodule path + * Get submodule repository at path * * @param parent * @param path - * @return .git for submodule repository + * @return repository or null if repository doesn't exist + * @throws IOException */ - public static File getSubmoduleGitDirectory(final Repository parent, - final String path) { - return new File(getSubmoduleDirectory(parent, path), Constants.DOT_GIT); + public static Repository getSubmoduleRepository(final File parent, + final String path) throws IOException { + File subWorkTree = new File(parent, path); + if (!subWorkTree.isDirectory()) + return null; + File workTree = new File(parent, path); + try { + return new RepositoryBuilder() // + .setMustExist(true) // + .setFS(FS.DETECTED) // + .setWorkTree(workTree) // + .build(); + } catch (RepositoryNotFoundException e) { + return null; + } } /** @@ -348,15 +353,6 @@ public File getDirectory() { return getSubmoduleDirectory(repository, path); } - /** - * Get the .git directory for the current submodule entry - * - * @return .git for submodule repository - */ - public File getGitDirectory() { - return getSubmoduleGitDirectory(repository, path); - } - /** * Advance to next submodule in the index tree. * @@ -466,20 +462,9 @@ public String getModulesUpdate() throws IOException, ConfigInvalidException { ConfigConstants.CONFIG_KEY_UPDATE); } - /** - * Does the current submodule entry have a .git directory in the parent - * repository's working tree? - * - * @return true if .git directory exists, false otherwise - */ - public boolean hasGitDirectory() { - return getGitDirectory().isDirectory(); - } - /** * Get repository for current submodule entry * - * @see #hasGitDirectory() * @return repository or null if non-existent * @throws IOException */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java index c7ae7757a..315d9091c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java @@ -159,7 +159,7 @@ static public class FileEntry extends Entry { file = f; if (f.isDirectory()) { - if (new File(f, Constants.DOT_GIT).isDirectory()) + if (new File(f, Constants.DOT_GIT).exists()) mode = FileMode.GITLINK; else mode = FileMode.TREE; 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 fa57b71ad..89403993f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -76,7 +76,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryBuilder; +import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.io.EolCanonicalizingInputStream; @@ -280,18 +280,16 @@ protected byte[] idSubmodule(Entry e) { * @return non-null submodule id */ protected byte[] idSubmodule(File directory, Entry e) { - final String gitDirPath = e.getName() + "/" + Constants.DOT_GIT; - final File submoduleGitDir = new File(directory, gitDirPath); - if (!submoduleGitDir.isDirectory()) - return zeroid; final Repository submoduleRepo; try { - FS fs = repository != null ? repository.getFS() : FS.DETECTED; - submoduleRepo = new RepositoryBuilder().setGitDir(submoduleGitDir) - .setMustExist(true).setFS(fs).build(); + submoduleRepo = SubmoduleWalk.getSubmoduleRepository(directory, + e.getName()); } catch (IOException exception) { return zeroid; } + if (submoduleRepo == null) + return zeroid; + final ObjectId head; try { head = submoduleRepo.resolve(Constants.HEAD);