Skip to content

Commit 2f79cf9

Browse files
kevinsawickimsohn
authored andcommitted
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 <[email protected]>
1 parent 7bde08e commit 2f79cf9

File tree

7 files changed

+173
-51
lines changed

7 files changed

+173
-51
lines changed

org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@
4444

4545
import static org.junit.Assert.assertEquals;
4646
import static org.junit.Assert.assertFalse;
47+
import static org.junit.Assert.assertNotNull;
4748
import static org.junit.Assert.assertNull;
4849
import static org.junit.Assert.assertTrue;
4950

5051
import java.io.File;
52+
import java.io.FileWriter;
5153
import java.io.IOException;
5254

5355
import org.eclipse.jgit.dircache.DirCache;
@@ -58,7 +60,9 @@
5860
import org.eclipse.jgit.lib.Constants;
5961
import org.eclipse.jgit.lib.FileMode;
6062
import org.eclipse.jgit.lib.ObjectId;
63+
import org.eclipse.jgit.lib.Repository;
6164
import org.eclipse.jgit.lib.RepositoryTestCase;
65+
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
6266
import org.eclipse.jgit.treewalk.filter.PathFilter;
6367
import org.junit.Test;
6468

@@ -97,8 +101,6 @@ public void apply(DirCacheEntry ent) {
97101
assertEquals(path, gen.getPath());
98102
assertEquals(id, gen.getObjectId());
99103
assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
100-
assertEquals(new File(db.getWorkTree(), path + File.separatorChar
101-
+ Constants.DOT_GIT), gen.getGitDirectory());
102104
assertNull(gen.getConfigUpdate());
103105
assertNull(gen.getConfigUrl());
104106
assertNull(gen.getModulesPath());
@@ -108,6 +110,101 @@ public void apply(DirCacheEntry ent) {
108110
assertFalse(gen.next());
109111
}
110112

113+
@Test
114+
public void repositoryWithRootLevelSubmoduleAbsoluteRef()
115+
throws IOException, ConfigInvalidException {
116+
final ObjectId id = ObjectId
117+
.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
118+
final String path = "sub";
119+
File dotGit = new File(db.getWorkTree(), path + File.separatorChar
120+
+ Constants.DOT_GIT);
121+
if (!dotGit.getParentFile().exists())
122+
dotGit.getParentFile().mkdirs();
123+
124+
File modulesGitDir = new File(db.getDirectory(), "modules"
125+
+ File.separatorChar + path);
126+
new FileWriter(dotGit).append(
127+
"gitdir: " + modulesGitDir.getAbsolutePath()).close();
128+
FileRepositoryBuilder builder = new FileRepositoryBuilder();
129+
builder.setWorkTree(new File(db.getWorkTree(), path));
130+
builder.build().create();
131+
132+
DirCache cache = db.lockDirCache();
133+
DirCacheEditor editor = cache.editor();
134+
editor.add(new PathEdit(path) {
135+
136+
public void apply(DirCacheEntry ent) {
137+
ent.setFileMode(FileMode.GITLINK);
138+
ent.setObjectId(id);
139+
}
140+
});
141+
editor.commit();
142+
143+
SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
144+
assertTrue(gen.next());
145+
assertEquals(path, gen.getPath());
146+
assertEquals(id, gen.getObjectId());
147+
assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
148+
assertNull(gen.getConfigUpdate());
149+
assertNull(gen.getConfigUrl());
150+
assertNull(gen.getModulesPath());
151+
assertNull(gen.getModulesUpdate());
152+
assertNull(gen.getModulesUrl());
153+
Repository subRepo = gen.getRepository();
154+
assertNotNull(subRepo);
155+
assertEquals(modulesGitDir, subRepo.getDirectory());
156+
assertEquals(new File(db.getWorkTree(), path), subRepo.getWorkTree());
157+
assertFalse(gen.next());
158+
}
159+
160+
@Test
161+
public void repositoryWithRootLevelSubmoduleRelativeRef()
162+
throws IOException, ConfigInvalidException {
163+
final ObjectId id = ObjectId
164+
.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
165+
final String path = "sub";
166+
File dotGit = new File(db.getWorkTree(), path + File.separatorChar
167+
+ Constants.DOT_GIT);
168+
if (!dotGit.getParentFile().exists())
169+
dotGit.getParentFile().mkdirs();
170+
171+
File modulesGitDir = new File(db.getDirectory(), "modules"
172+
+ File.separatorChar + path);
173+
new FileWriter(dotGit).append(
174+
"gitdir: " + "../" + Constants.DOT_GIT + "/modules/" + path)
175+
.close();
176+
FileRepositoryBuilder builder = new FileRepositoryBuilder();
177+
builder.setWorkTree(new File(db.getWorkTree(), path));
178+
builder.build().create();
179+
180+
DirCache cache = db.lockDirCache();
181+
DirCacheEditor editor = cache.editor();
182+
editor.add(new PathEdit(path) {
183+
184+
public void apply(DirCacheEntry ent) {
185+
ent.setFileMode(FileMode.GITLINK);
186+
ent.setObjectId(id);
187+
}
188+
});
189+
editor.commit();
190+
191+
SubmoduleWalk gen = SubmoduleWalk.forIndex(db);
192+
assertTrue(gen.next());
193+
assertEquals(path, gen.getPath());
194+
assertEquals(id, gen.getObjectId());
195+
assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
196+
assertNull(gen.getConfigUpdate());
197+
assertNull(gen.getConfigUrl());
198+
assertNull(gen.getModulesPath());
199+
assertNull(gen.getModulesUpdate());
200+
assertNull(gen.getModulesUrl());
201+
Repository subRepo = gen.getRepository();
202+
assertNotNull(subRepo);
203+
assertEquals(modulesGitDir, subRepo.getDirectory());
204+
assertEquals(new File(db.getWorkTree(), path), subRepo.getWorkTree());
205+
assertFalse(gen.next());
206+
}
207+
111208
@Test
112209
public void repositoryWithNestedSubmodule() throws IOException,
113210
ConfigInvalidException {
@@ -130,8 +227,6 @@ public void apply(DirCacheEntry ent) {
130227
assertEquals(path, gen.getPath());
131228
assertEquals(id, gen.getObjectId());
132229
assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
133-
assertEquals(new File(db.getWorkTree(), path + File.separatorChar
134-
+ Constants.DOT_GIT), gen.getGitDirectory());
135230
assertNull(gen.getConfigUpdate());
136231
assertNull(gen.getConfigUrl());
137232
assertNull(gen.getModulesPath());

org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ invalidChannel=Invalid channel {0}
234234
invalidCharacterInBase64Data=Invalid character in Base64 data.
235235
invalidCommitParentNumber=Invalid commit parent number
236236
invalidEncryption=Invalid encryption
237+
invalidGitdirRef = Invalid .git reference in file ''{0}''
237238
invalidGitType=invalid git type: {0}
238239
invalidId=Invalid id {0}
239240
invalidIdLength=Invalid id length {0}; should be {1}

org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ public static JGitText get() {
294294
/***/ public String invalidCharacterInBase64Data;
295295
/***/ public String invalidCommitParentNumber;
296296
/***/ public String invalidEncryption;
297+
/***/ public String invalidGitdirRef;
297298
/***/ public String invalidGitType;
298299
/***/ public String invalidId;
299300
/***/ public String invalidIdLength;

org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
import org.eclipse.jgit.storage.file.FileRepository;
7070
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
7171
import org.eclipse.jgit.util.FS;
72+
import org.eclipse.jgit.util.IO;
73+
import org.eclipse.jgit.util.RawParseUtils;
7274
import org.eclipse.jgit.util.SystemReader;
7375

7476
/**
@@ -85,6 +87,19 @@
8587
* @see FileRepositoryBuilder
8688
*/
8789
public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Repository> {
90+
private static boolean isSymRef(byte[] ref) {
91+
if (ref.length < 9)
92+
return false;
93+
return /**/ref[0] == 'g' //
94+
&& ref[1] == 'i' //
95+
&& ref[2] == 't' //
96+
&& ref[3] == 'd' //
97+
&& ref[4] == 'i' //
98+
&& ref[5] == 'r' //
99+
&& ref[6] == ':' //
100+
&& ref[7] == ' ';
101+
}
102+
88103
private FS fs;
89104

90105
private File gitDir;
@@ -546,10 +561,37 @@ protected void requireGitDirOrWorkTree() {
546561
* the repository could not be accessed
547562
*/
548563
protected void setupGitDir() throws IOException {
549-
// No gitDir? Try to assume its under the workTree.
550-
//
551-
if (getGitDir() == null && getWorkTree() != null)
552-
setGitDir(new File(getWorkTree(), DOT_GIT));
564+
// No gitDir? Try to assume its under the workTree or a ref to another
565+
// location
566+
if (getGitDir() == null && getWorkTree() != null) {
567+
File dotGit = new File(getWorkTree(), DOT_GIT);
568+
if (!dotGit.isFile())
569+
setGitDir(dotGit);
570+
else {
571+
byte[] content = IO.readFully(dotGit);
572+
if (!isSymRef(content))
573+
throw new IOException(MessageFormat.format(
574+
JGitText.get().invalidGitdirRef,
575+
dotGit.getAbsolutePath()));
576+
int pathStart = 8;
577+
int lineEnd = RawParseUtils.nextLF(content, pathStart);
578+
if (content[lineEnd - 1] == '\n')
579+
lineEnd--;
580+
if (lineEnd == pathStart)
581+
throw new IOException(MessageFormat.format(
582+
JGitText.get().invalidGitdirRef,
583+
dotGit.getAbsolutePath()));
584+
585+
String gitdirPath = RawParseUtils.decode(content, pathStart,
586+
lineEnd);
587+
File gitdirFile = new File(gitdirPath);
588+
if (gitdirFile.isAbsolute())
589+
setGitDir(gitdirFile);
590+
else
591+
setGitDir(new File(getWorkTree(), gitdirPath)
592+
.getCanonicalFile());
593+
}
594+
}
553595
}
554596

555597
/**

org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -155,27 +155,32 @@ public static File getSubmoduleDirectory(final Repository parent,
155155
*/
156156
public static Repository getSubmoduleRepository(final Repository parent,
157157
final String path) throws IOException {
158-
File directory = getSubmoduleGitDirectory(parent, path);
159-
if (!directory.isDirectory())
160-
return null;
161-
try {
162-
return new RepositoryBuilder().setMustExist(true)
163-
.setFS(FS.DETECTED).setGitDir(directory).build();
164-
} catch (RepositoryNotFoundException e) {
165-
return null;
166-
}
158+
return getSubmoduleRepository(parent.getWorkTree(), path);
167159
}
168160

169161
/**
170-
* Get the .git directory for a repository submodule path
162+
* Get submodule repository at path
171163
*
172164
* @param parent
173165
* @param path
174-
* @return .git for submodule repository
166+
* @return repository or null if repository doesn't exist
167+
* @throws IOException
175168
*/
176-
public static File getSubmoduleGitDirectory(final Repository parent,
177-
final String path) {
178-
return new File(getSubmoduleDirectory(parent, path), Constants.DOT_GIT);
169+
public static Repository getSubmoduleRepository(final File parent,
170+
final String path) throws IOException {
171+
File subWorkTree = new File(parent, path);
172+
if (!subWorkTree.isDirectory())
173+
return null;
174+
File workTree = new File(parent, path);
175+
try {
176+
return new RepositoryBuilder() //
177+
.setMustExist(true) //
178+
.setFS(FS.DETECTED) //
179+
.setWorkTree(workTree) //
180+
.build();
181+
} catch (RepositoryNotFoundException e) {
182+
return null;
183+
}
179184
}
180185

181186
/**
@@ -348,15 +353,6 @@ public File getDirectory() {
348353
return getSubmoduleDirectory(repository, path);
349354
}
350355

351-
/**
352-
* Get the .git directory for the current submodule entry
353-
*
354-
* @return .git for submodule repository
355-
*/
356-
public File getGitDirectory() {
357-
return getSubmoduleGitDirectory(repository, path);
358-
}
359-
360356
/**
361357
* Advance to next submodule in the index tree.
362358
*
@@ -466,20 +462,9 @@ public String getModulesUpdate() throws IOException, ConfigInvalidException {
466462
ConfigConstants.CONFIG_KEY_UPDATE);
467463
}
468464

469-
/**
470-
* Does the current submodule entry have a .git directory in the parent
471-
* repository's working tree?
472-
*
473-
* @return true if .git directory exists, false otherwise
474-
*/
475-
public boolean hasGitDirectory() {
476-
return getGitDirectory().isDirectory();
477-
}
478-
479465
/**
480466
* Get repository for current submodule entry
481467
*
482-
* @see #hasGitDirectory()
483468
* @return repository or null if non-existent
484469
* @throws IOException
485470
*/

org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ static public class FileEntry extends Entry {
159159
file = f;
160160

161161
if (f.isDirectory()) {
162-
if (new File(f, Constants.DOT_GIT).isDirectory())
162+
if (new File(f, Constants.DOT_GIT).exists())
163163
mode = FileMode.GITLINK;
164164
else
165165
mode = FileMode.TREE;

org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
import org.eclipse.jgit.lib.FileMode;
7777
import org.eclipse.jgit.lib.ObjectId;
7878
import org.eclipse.jgit.lib.Repository;
79-
import org.eclipse.jgit.lib.RepositoryBuilder;
79+
import org.eclipse.jgit.submodule.SubmoduleWalk;
8080
import org.eclipse.jgit.util.FS;
8181
import org.eclipse.jgit.util.IO;
8282
import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
@@ -280,18 +280,16 @@ protected byte[] idSubmodule(Entry e) {
280280
* @return non-null submodule id
281281
*/
282282
protected byte[] idSubmodule(File directory, Entry e) {
283-
final String gitDirPath = e.getName() + "/" + Constants.DOT_GIT;
284-
final File submoduleGitDir = new File(directory, gitDirPath);
285-
if (!submoduleGitDir.isDirectory())
286-
return zeroid;
287283
final Repository submoduleRepo;
288284
try {
289-
FS fs = repository != null ? repository.getFS() : FS.DETECTED;
290-
submoduleRepo = new RepositoryBuilder().setGitDir(submoduleGitDir)
291-
.setMustExist(true).setFS(fs).build();
285+
submoduleRepo = SubmoduleWalk.getSubmoduleRepository(directory,
286+
e.getName());
292287
} catch (IOException exception) {
293288
return zeroid;
294289
}
290+
if (submoduleRepo == null)
291+
return zeroid;
292+
295293
final ObjectId head;
296294
try {
297295
head = submoduleRepo.resolve(Constants.HEAD);

0 commit comments

Comments
 (0)