From 03e860a7b7245e2b128d4614f7b5bd04b556a0c4 Mon Sep 17 00:00:00 2001 From: Christian Halstrick Date: Wed, 10 Dec 2014 17:42:42 +0100 Subject: [PATCH] Allow explicit configuration of git directory in InitCommand Native git's "init" command allows to specify the location of the .git folder with the option "--separate-git-dir". This allows for example to setup repositories with a non-standard layout. E.g. .git folder under /repos/a.git and the worktree under /home/git/a. Both directories contain pointers to the other side: /repos/a.git/config contains core.worktree=/home/git/a . And /home/git/a/.git is a file containing "gitdir: /repos/a.git". This commit adds that option to InitCommand. This feature is needed to support the new submodule layout where the .git folder of the submodules is under .git/modules/. Change-Id: I0208f643808bf8f28e2c979d6e33662607775f1f --- .../eclipse/jgit/junit/MockSystemReader.java | 1 + .../org/eclipse/jgit/api/InitCommandTest.java | 110 ++++++++++++++++++ .../eclipse/jgit/internal/JGitText.properties | 2 + .../src/org/eclipse/jgit/api/InitCommand.java | 94 +++++++++++++-- .../org/eclipse/jgit/internal/JGitText.java | 2 + .../internal/storage/file/FileRepository.java | 22 +++- .../src/org/eclipse/jgit/lib/Constants.java | 16 ++- 7 files changed, 233 insertions(+), 14 deletions(-) diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index deca34106..3d21f9f8a 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -90,6 +90,7 @@ public MockSystemReader() { init(Constants.GIT_AUTHOR_EMAIL_KEY); init(Constants.GIT_COMMITTER_NAME_KEY); init(Constants.GIT_COMMITTER_EMAIL_KEY); + setProperty(Constants.OS_USER_DIR, "."); userGitConfig = new MockConfig(null, null); systemGitConfig = new MockConfig(null, null); setCurrentPlatform(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java index 3296717c0..e85022376 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; @@ -50,8 +51,12 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; @@ -101,4 +106,109 @@ public void testInitBareRepository() throws IOException, assertNotNull(repository); assertTrue(repository.isBare()); } + + // non-bare repos where gitDir and directory is set. Same as + // "git init --separate-git-dir /tmp/a /tmp/b" + @Test + public void testInitWithExplicitGitDir() throws IOException, + JGitInternalException, GitAPIException { + File wt = createTempDirectory("testInitRepositoryWT"); + File gitDir = createTempDirectory("testInitRepositoryGIT"); + InitCommand command = new InitCommand(); + command.setDirectory(wt); + command.setGitDir(gitDir); + Repository repository = command.call().getRepository(); + addRepoToClose(repository); + assertNotNull(repository); + assertEqualsFile(wt, repository.getWorkTree()); + assertEqualsFile(gitDir, repository.getDirectory()); + } + + // non-bare repos where only gitDir is set. Same as + // "git init --separate-git-dir /tmp/a" + @Test + public void testInitWithOnlyExplicitGitDir() throws IOException, + JGitInternalException, GitAPIException { + MockSystemReader reader = (MockSystemReader) SystemReader.getInstance(); + reader.setProperty(Constants.OS_USER_DIR, getTemporaryDirectory() + .getAbsolutePath()); + File gitDir = createTempDirectory("testInitRepository/.git"); + InitCommand command = new InitCommand(); + command.setGitDir(gitDir); + Repository repository = command.call().getRepository(); + addRepoToClose(repository); + assertNotNull(repository); + assertEqualsFile(gitDir, repository.getDirectory()); + assertEqualsFile(new File(reader.getProperty("user.dir")), + repository.getWorkTree()); + } + + // Bare repos where gitDir and directory is set will only work if gitDir and + // directory is pointing to same dir. Same as + // "git init --bare --separate-git-dir /tmp/a /tmp/b" + // (works in native git but I guess that's more a bug) + @Test(expected = IllegalStateException.class) + public void testInitBare_DirAndGitDirMustBeEqual() throws IOException, + JGitInternalException, GitAPIException { + File gitDir = createTempDirectory("testInitRepository.git"); + InitCommand command = new InitCommand(); + command.setBare(true); + command.setDirectory(gitDir); + command.setGitDir(new File(gitDir, "..")); + command.call(); + } + + // If neither directory nor gitDir is set in a non-bare repo make sure + // worktree and gitDir are set correctly. Standard case. Same as + // "git init" + @Test + public void testInitWithDefaultsNonBare() throws JGitInternalException, + GitAPIException, IOException { + MockSystemReader reader = (MockSystemReader) SystemReader.getInstance(); + reader.setProperty(Constants.OS_USER_DIR, getTemporaryDirectory() + .getAbsolutePath()); + InitCommand command = new InitCommand(); + command.setBare(false); + Repository repository = command.call().getRepository(); + addRepoToClose(repository); + assertNotNull(repository); + assertEqualsFile(new File(reader.getProperty("user.dir"), ".git"), + repository.getDirectory()); + assertEqualsFile(new File(reader.getProperty("user.dir")), + repository.getWorkTree()); + } + + // If neither directory nor gitDir is set in a bare repo make sure + // worktree and gitDir are set correctly. Standard case. Same as + // "git init --bare" + @Test(expected = NoWorkTreeException.class) + public void testInitWithDefaultsBare() throws JGitInternalException, + GitAPIException, IOException { + MockSystemReader reader = (MockSystemReader) SystemReader.getInstance(); + reader.setProperty(Constants.OS_USER_DIR, getTemporaryDirectory() + .getAbsolutePath()); + InitCommand command = new InitCommand(); + command.setBare(true); + Repository repository = command.call().getRepository(); + addRepoToClose(repository); + assertNotNull(repository); + assertEqualsFile(new File(reader.getProperty("user.dir")), + repository.getDirectory()); + assertNull(repository.getWorkTree()); + } + + // In a non-bare repo when directory and gitDir is set then they shouldn't + // point to the same dir. Same as + // "git init --separate-git-dir /tmp/a /tmp/a" + // (works in native git but I guess that's more a bug) + @Test(expected = IllegalStateException.class) + public void testInitNonBare_GitdirAndDirShouldntBeSame() + throws JGitInternalException, GitAPIException, IOException { + File gitDir = createTempDirectory("testInitRepository.git"); + InitCommand command = new InitCommand(); + command.setBare(false); + command.setGitDir(gitDir); + command.setDirectory(gitDir); + command.call().getRepository(); + } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 76c709422..a753188e8 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -249,6 +249,8 @@ indexFileIsInUse=Index file is in use indexFileIsTooLargeForJgit=Index file is too large for jgit indexSignatureIsInvalid=Index signature is invalid: {0} indexWriteException=Modified index could not be written +initFailedBareRepoDifferentDirs=When initializing a bare repo with directory {0} and separate git-dir {1} specified both folders must point to the same location +initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location inMemoryBufferLimitExceeded=In-memory buffer limit exceeded inputStreamMustSupportMark=InputStream must support mark() integerValueOutOfRange=Integer value {0}.{1} out of range diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java index bf43e90d4..37a788e85 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java @@ -44,13 +44,16 @@ import java.io.File; import java.io.IOException; +import java.text.MessageFormat; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; +import org.eclipse.jgit.util.SystemReader; /** * Create an empty git repository or reinitalize an existing one @@ -61,6 +64,8 @@ public class InitCommand implements Callable { private File directory; + private File gitDir; + private boolean bare; /** @@ -74,18 +79,36 @@ public Git call() throws GitAPIException { if (bare) builder.setBare(); builder.readEnvironment(); + if (gitDir != null) + builder.setGitDir(gitDir); + else + gitDir = builder.getGitDir(); if (directory != null) { - File d = directory; - if (!bare) - d = new File(d, Constants.DOT_GIT); - builder.setGitDir(d); + if (bare) + builder.setGitDir(directory); + else { + builder.setWorkTree(directory); + if (gitDir == null) + builder.setGitDir(new File(directory, Constants.DOT_GIT)); + } } else if (builder.getGitDir() == null) { - File d = new File("."); //$NON-NLS-1$ - if (d.getParentFile() != null) - d = d.getParentFile(); + String dStr = SystemReader.getInstance() + .getProperty("user.dir"); //$NON-NLS-1$ + if (dStr == null) + dStr = "."; //$NON-NLS-1$ + File d = new File(dStr); if (!bare) d = new File(d, Constants.DOT_GIT); builder.setGitDir(d); + } else { + // directory was not set but gitDir was set + if (!bare) { + String dStr = SystemReader.getInstance().getProperty( + "user.dir"); //$NON-NLS-1$ + if (dStr == null) + dStr = "."; //$NON-NLS-1$ + builder.setWorkTree(new File(dStr)); + } } Repository repository = builder.build(); if (!repository.getObjectDatabase().exists()) @@ -103,20 +126,67 @@ public Git call() throws GitAPIException { * @param directory * the directory to init to * @return this instance + * @throws IllegalStateException + * if the combination of directory, gitDir and bare is illegal. + * E.g. if for a non-bare repository directory and gitDir point + * to the same directory of if for a bare repository both + * directory and gitDir are specified */ - public InitCommand setDirectory(File directory) { + public InitCommand setDirectory(File directory) + throws IllegalStateException { + validateDirs(directory, gitDir, bare); this.directory = directory; return this; } /** - * @param bare - * whether the repository is bare or not + * @param gitDir + * the repository meta directory * @return this instance + * @throws IllegalStateException + * if the combination of directory, gitDir and bare is illegal. + * E.g. if for a non-bare repository directory and gitDir point + * to the same directory of if for a bare repository both + * directory and gitDir are specified + * @since 3.6 */ - public InitCommand setBare(boolean bare) { - this.bare = bare; + public InitCommand setGitDir(File gitDir) + throws IllegalStateException { + validateDirs(directory, gitDir, bare); + this.gitDir = gitDir; return this; } + private static void validateDirs(File directory, File gitDir, boolean bare) + throws IllegalStateException { + if (directory != null) { + if (bare) { + if (gitDir != null && !gitDir.equals(directory)) + throw new IllegalStateException(MessageFormat.format( + JGitText.get().initFailedBareRepoDifferentDirs, + gitDir, directory)); + } else { + if (gitDir != null && gitDir.equals(directory)) + throw new IllegalStateException(MessageFormat.format( + JGitText.get().initFailedNonBareRepoSameDirs, + gitDir, directory)); + } + } + } + + /** + * @param bare + * whether the repository is bare or not + * @throws IllegalStateException + * if the combination of directory, gitDir and bare is illegal. + * E.g. if for a non-bare repository directory and gitDir point + * to the same directory of if for a bare repository both + * directory and gitDir are specified + * @return this instance + */ + public InitCommand setBare(boolean bare) { + validateDirs(directory, gitDir, bare); + this.bare = bare; + return this; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index d1f0deec9..65272fb0b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -308,6 +308,8 @@ public static JGitText get() { /***/ public String indexFileIsTooLargeForJgit; /***/ public String indexSignatureIsInvalid; /***/ public String indexWriteException; + /***/ public String initFailedBareRepoDifferentDirs; + /***/ public String initFailedNonBareRepoSameDirs; /***/ public String inMemoryBufferLimitExceeded; /***/ public String inputStreamMustSupportMark; /***/ public String integerValueOutOfRange; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java index 9670bf10a..995621ee3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java @@ -273,7 +273,8 @@ public void create(boolean bare) throws IOException { ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_HIDEDOTFILES, HideDotFiles.DOTGITONLY); - if (hideDotFiles != HideDotFiles.FALSE && !isBare()) + if (hideDotFiles != HideDotFiles.FALSE && !isBare() + && getDirectory().getName().startsWith(".")) //$NON-NLS-1$ getFS().setHidden(getDirectory(), true); refs.create(); objectDatabase.create(); @@ -329,6 +330,25 @@ public void create(boolean bare) throws IOException { // Java has no other way cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true); + if (!bare) { + File workTree = getWorkTree(); + if (!getDirectory().getParentFile().equals(workTree)) { + cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree() + .getAbsolutePath()); + LockFile dotGitLockFile = new LockFile(new File(workTree, + Constants.DOT_GIT), getFS()); + try { + if (dotGitLockFile.lock()) { + dotGitLockFile.write(Constants.encode(Constants.GITDIR + + getDirectory().getAbsolutePath())); + dotGitLockFile.commit(); + } + } finally { + dotGitLockFile.unlock(); + } + } + } cfg.save(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index d14614dc3..32ccc7c33 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -272,7 +272,14 @@ public final class Constants { */ public static final String INFO_EXCLUDE = "info/exclude"; - /** The environment variable that contains the system user name */ + /** + * The system property that contains the system user name + * + * @since 3.6 + */ + public static final String OS_USER_DIR = "user.dir"; + + /** The system property that contains the system user name */ public static final String OS_USER_NAME_KEY = "user.name"; /** The environment variable that contains the author's name */ @@ -358,6 +365,13 @@ public final class Constants { /** Name of the .git/shallow file */ public static final String SHALLOW = "shallow"; + /** + * Prefix of the first line in a ".git" file + * + * @since 3.6 + */ + public static final String GITDIR = "gitdir: "; + /** * Create a new digest function for objects. *