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 <matthias.sohn@sap.com>tags/v1.3.0.201202121842-rc4
@@ -44,10 +44,12 @@ package org.eclipse.jgit.submodule; | |||
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.errors.ConfigInvalidException; | |||
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 class SubmoduleWalkTest extends RepositoryTestCase { | |||
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 class SubmoduleWalkTest extends RepositoryTestCase { | |||
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 class SubmoduleWalkTest extends RepositoryTestCase { | |||
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()); |
@@ -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} |
@@ -294,6 +294,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String invalidCharacterInBase64Data; | |||
/***/ public String invalidCommitParentNumber; | |||
/***/ public String invalidEncryption; | |||
/***/ public String invalidGitdirRef; | |||
/***/ public String invalidGitType; | |||
/***/ public String invalidId; | |||
/***/ public String invalidIdLength; |
@@ -69,6 +69,8 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
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 @@ import org.eclipse.jgit.util.SystemReader; | |||
* @see FileRepositoryBuilder | |||
*/ | |||
public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Repository> { | |||
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 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re | |||
* 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()); | |||
} | |||
} | |||
} | |||
/** |
@@ -155,27 +155,32 @@ public class SubmoduleWalk { | |||
*/ | |||
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 class SubmoduleWalk { | |||
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 class SubmoduleWalk { | |||
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 | |||
*/ |
@@ -159,7 +159,7 @@ public class FileTreeIterator extends WorkingTreeIterator { | |||
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; |
@@ -76,7 +76,7 @@ import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||
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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
* @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); |