From eb63bfc1b82ce21516b211df45069cbea87b7eee Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Wed, 9 Dec 2009 09:54:08 +0100 Subject: [PATCH] Recognize Git repository environment variables This makes the jgit command line behave like the C Git implementation in the respect. These variables are not recognized in the core, though we add support to do the overrides there. Hence other users of the JGit library, like the Eclipse plugin and others, will not be affected. GIT_DIR The location of the ".git" directory. GIT_WORK_TREE The location of the work tree. GIT_INDEX_FILE The location of the index file. GIT_CEILING_DIRECTORIES A colon (semicolon on Windows) separated list of paths that which JGit will not cross when looking for the .git directory. GIT_OBJECT_DIRECTORY The location of the objects directory under which objects are stored. GIT_ALTERNATE_OBJECT_DIRECTORIES A colon (semicolon on Windows) separated list of object directories to search for objects. In addition to these we support the core.worktree config setting when the git directory is set deliberately instead of being found. Change-Id: I2b9bceb13c0f66b25e9e3cefd2e01534a286e04c Signed-off-by: Robin Rosenberg Signed-off-by: Shawn O. Pearce --- .../junit/LocalDiskRepositoryTestCase.java | 22 +++ .../src/org/eclipse/jgit/pgm/Main.java | 55 +++++- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 157 +++++++++++++++++- .../src/org/eclipse/jgit/lib/Constants.java | 34 ++++ .../src/org/eclipse/jgit/lib/GitIndex.java | 2 +- .../org/eclipse/jgit/lib/ObjectDirectory.java | 33 +++- .../src/org/eclipse/jgit/lib/Repository.java | 136 +++++++++++++-- 7 files changed, 415 insertions(+), 24 deletions(-) diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index 5c8935c49..ec9b1d7ac 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -51,6 +51,7 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -58,6 +59,7 @@ import junit.framework.TestCase; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; @@ -124,6 +126,7 @@ public void run() { mockSystemReader = new MockSystemReader(); mockSystemReader.userGitConfig = new FileBasedConfig(new File(trash, "usergitconfig")); + ceilTestDirectories(getCeilings()); SystemReader.setInstance(mockSystemReader); final long now = mockSystemReader.getCurrentTime(); @@ -142,6 +145,25 @@ public void run() { WindowCache.reconfigure(c); } + + protected List getCeilings() { + return Collections.singletonList(trash.getParentFile().getAbsoluteFile()); + } + + private void ceilTestDirectories(List ceilings) { + mockSystemReader.setProperty(Constants.GIT_CEILING_DIRECTORIES_KEY, makePath(ceilings)); + } + + private String makePath(List objects) { + final StringBuilder stringBuilder = new StringBuilder(); + for (Object object : objects) { + if (stringBuilder.length() > 0) + stringBuilder.append(File.pathSeparatorChar); + stringBuilder.append(object.toString()); + } + return stringBuilder.toString(); + } + @Override protected void tearDown() throws Exception { RepositoryCache.clear(); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index 18cf8be46..b4c5660a6 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -49,15 +49,20 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.eclipse.jgit.awtui.AwtAuthenticator; import org.eclipse.jgit.awtui.AwtSshSessionFactory; import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; import org.eclipse.jgit.util.CachedAuthenticator; +import org.eclipse.jgit.util.SystemReader; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.ExampleMode; @@ -158,13 +163,50 @@ private void execute(final String[] argv) throws Exception { final TextBuiltin cmd = subcommand; if (cmd.requiresRepository()) { + if (gitdir == null) { + String gitDirEnv = SystemReader.getInstance().getenv(Constants.GIT_DIR_KEY); + if (gitDirEnv != null) + gitdir = new File(gitDirEnv); + } if (gitdir == null) gitdir = findGitDir(); + + File gitworktree; + String gitWorkTreeEnv = SystemReader.getInstance().getenv(Constants.GIT_WORK_TREE_KEY); + if (gitWorkTreeEnv != null) + gitworktree = new File(gitWorkTreeEnv); + else + gitworktree = null; + + File indexfile; + String indexFileEnv = SystemReader.getInstance().getenv(Constants.GIT_INDEX_KEY); + if (indexFileEnv != null) + indexfile = new File(indexFileEnv); + else + indexfile = null; + + File objectdir; + String objectDirEnv = SystemReader.getInstance().getenv(Constants.GIT_OBJECT_DIRECTORY_KEY); + if (objectDirEnv != null) + objectdir = new File(objectDirEnv); + else + objectdir = null; + + File[] altobjectdirs; + String altObjectDirEnv = SystemReader.getInstance().getenv(Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY); + if (altObjectDirEnv != null) { + String[] parserdAltObjectDirEnv = altObjectDirEnv.split(File.pathSeparator); + altobjectdirs = new File[parserdAltObjectDirEnv.length]; + for (int i = 0; i < parserdAltObjectDirEnv.length; i++) + altobjectdirs[i] = new File(parserdAltObjectDirEnv[i]); + } else + altobjectdirs = null; + if (gitdir == null || !gitdir.isDirectory()) { System.err.println("error: can't find git directory"); System.exit(1); } - cmd.init(new Repository(gitdir), gitdir); + cmd.init(new Repository(gitdir, gitworktree, objectdir, altobjectdirs, indexfile), gitdir); } else { cmd.init(null, gitdir); } @@ -177,12 +219,21 @@ private void execute(final String[] argv) throws Exception { } private static File findGitDir() { - File current = new File(".").getAbsoluteFile(); + Set ceilingDirectories = new HashSet(); + String ceilingDirectoriesVar = SystemReader.getInstance().getenv( + Constants.GIT_CEILING_DIRECTORIES_KEY); + if (ceilingDirectoriesVar != null) { + ceilingDirectories.addAll(Arrays.asList(ceilingDirectoriesVar + .split(File.pathSeparator))); + } + File current = new File("").getAbsoluteFile(); while (current != null) { final File gitDir = new File(current, ".git"); if (gitDir.isDirectory()) return gitDir; current = current.getParentFile(); + if (ceilingDirectories.contains(current.getPath())) + break; } return null; } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index d1dfdf4fa..e29e9e721 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2007, Dave Watson - * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2007-2009, Robin Rosenberg * Copyright (C) 2006-2008, Shawn O. Pearce * and other copyright owners as documented in the project's IP log. * @@ -78,6 +78,161 @@ public void test001_Initalize() { assertEquals(23, HEAD.length()); } + public void test000_openRepoBadArgs() throws IOException { + try { + new Repository(null, null); + fail("Must pass either GIT_DIR or GIT_WORK_TREE"); + } catch (IllegalArgumentException e) { + assertEquals( + "Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor", + e.getMessage()); + } + } + + /** + * Check the default rules for looking up directories and files within a + * repo when the gitDir is given. + * + * @throws IOException + */ + public void test000_openrepo_default_gitDirSet() throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + Repository repo1initial = new Repository(new File(repo1Parent, ".git")); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, ".git"); + Repository r = new Repository(theDir, null); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(repo1Parent, r.getWorkDir()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + } + + /** + * Check that we can pass both a git directory and a work tree + * repo when the gitDir is given. + * + * @throws IOException + */ + public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + Repository repo1initial = new Repository(new File(repo1Parent, ".git")); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, ".git"); + Repository r = new Repository(theDir, repo1Parent.getParentFile()); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(repo1Parent.getParentFile(), r.getWorkDir()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + } + + /** + * Check the default rules for looking up directories and files within a + * repo when the workTree is given. + * + * @throws IOException + */ + public void test000_openrepo_default_workDirSet() throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + Repository repo1initial = new Repository(new File(repo1Parent, ".git")); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, ".git"); + Repository r = new Repository(null, repo1Parent); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(repo1Parent, r.getWorkDir()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + } + + /** + * Check that worktree config has an effect, given absolute path. + * + * @throws IOException + */ + public void test000_openrepo_default_absolute_workdirconfig() + throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + File workdir = new File(trash.getParentFile(), "rw"); + workdir.mkdir(); + Repository repo1initial = new Repository(new File(repo1Parent, ".git")); + repo1initial.create(); + repo1initial.getConfig().setString("core", null, "worktree", + workdir.getAbsolutePath()); + repo1initial.getConfig().save(); + repo1initial.close(); + + File theDir = new File(repo1Parent, ".git"); + Repository r = new Repository(theDir, null); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(workdir, r.getWorkDir()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + } + + /** + * Check that worktree config has an effect, given a relative path. + * + * @throws IOException + */ + public void test000_openrepo_default_relative_workdirconfig() + throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + File workdir = new File(trash.getParentFile(), "rw"); + workdir.mkdir(); + Repository repo1initial = new Repository(new File(repo1Parent, ".git")); + repo1initial.create(); + repo1initial.getConfig() + .setString("core", null, "worktree", "../../rw"); + repo1initial.getConfig().save(); + repo1initial.close(); + + File theDir = new File(repo1Parent, ".git"); + Repository r = new Repository(theDir, null); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(workdir, r.getWorkDir()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + } + + /** + * Check that the given index file is honored and the alternate object + * directories too + * + * @throws IOException + */ + public void test000_openrepo_alternate_index_file_and_objdirs() + throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + File indexFile = new File(trash, "idx"); + File objDir = new File(trash, "../obj"); + File[] altObjDirs = new File[] { db.getObjectsDirectory() }; + Repository repo1initial = new Repository(new File(repo1Parent, ".git")); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, ".git"); + Repository r = new Repository(theDir, null, objDir, altObjDirs, + indexFile); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(theDir.getParentFile(), r.getWorkDir()); + assertEqualsPath(indexFile, r.getIndexFile()); + assertEqualsPath(objDir, r.getObjectsDirectory()); + assertNotNull(r.mapCommit("6db9c2ebf75590eef973081736730a9ea169a0c4")); + // Must close or the default repo pack files created by this test gets + // locked via the alternate object directories on Windows. + r.close(); + } + + protected void assertEqualsPath(File expected, File actual) + throws IOException { + assertEquals(expected.getCanonicalPath(), actual.getCanonicalPath()); + } + public void test002_WriteEmptyTree() throws IOException { // One of our test packs contains the empty tree object. If the pack is // open when we create it we won't write the object file out as a loose 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 c1d78be5a..05de98c1a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -262,6 +262,40 @@ public final class Constants { /** The environment variable that contains the commiter's email */ public static final String GIT_COMMITTER_EMAIL_KEY = "GIT_COMMITTER_EMAIL"; + /** + * The environment variable that limits how close to the root of the file + * systems JGit will traverse when looking for a repository root. + */ + public static final String GIT_CEILING_DIRECTORIES_KEY = "GIT_CEILING_DIRECTORIES"; + + /** + * The environment variable that tells us which directory is the ".git" + * directory + */ + public static final String GIT_DIR_KEY = "GIT_DIR"; + + /** + * The environment variable that tells us which directory is the working + * directory. + */ + public static final String GIT_WORK_TREE_KEY = "GIT_WORK_TREE"; + + /** + * The environment variable that tells us which file holds the Git index. + */ + public static final String GIT_INDEX_KEY = "GIT_INDEX"; + + /** + * The environment variable that tells us where objects are stored + */ + public static final String GIT_OBJECT_DIRECTORY_KEY = "GIT_OBJECT_DIRECTORY"; + + /** + * The environment variable that tells us where to look for objects, besides + * the default objects directory. + */ + public static final String GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY = "GIT_ALTERNATE_OBJECT_DIRECTORIES"; + /** Default value for the user name if no other information is available */ public static final String UNKNOWN_USER_DEFAULT = "unknown-user"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java index 1714d7077..688957b95 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java @@ -137,7 +137,7 @@ else if (o1.length > o2.length) */ public GitIndex(Repository db) { this.db = db; - this.cacheFile = new File(db.getDirectory(), "index"); + this.cacheFile = db.getIndexFile(); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java index 297d85f83..f94d0d5fb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java @@ -84,14 +84,19 @@ public class ObjectDirectory extends ObjectDatabase { private final AtomicReference packList; + private final File[] alternateObjectDir; + /** * Initialize a reference to an on-disk object directory. * * @param dir * the location of the objects directory. + * @param alternateObjectDir + * a list of alternate object directories */ - public ObjectDirectory(final File dir) { + public ObjectDirectory(final File dir, File[] alternateObjectDir) { objects = dir; + this.alternateObjectDir = alternateObjectDir; infoDirectory = new File(objects, "info"); packDirectory = new File(objects, "pack"); alternatesFile = new File(infoDirectory, "alternates"); @@ -422,15 +427,21 @@ private Set listPackDirectory() { @Override protected ObjectDatabase[] loadAlternates() throws IOException { - final BufferedReader br = open(alternatesFile); final List l = new ArrayList(4); - try { - String line; - while ((line = br.readLine()) != null) { - l.add(openAlternate(line)); + if (alternateObjectDir != null) { + for (File d : alternateObjectDir) { + l.add(openAlternate(d)); + } + } else { + final BufferedReader br = open(alternatesFile); + try { + String line; + while ((line = br.readLine()) != null) { + l.add(openAlternate(line)); + } + } finally { + br.close(); } - } finally { - br.close(); } if (l.isEmpty()) { @@ -447,12 +458,16 @@ private static BufferedReader open(final File f) private ObjectDatabase openAlternate(final String location) throws IOException { final File objdir = FS.resolve(objects, location); + return openAlternate(objdir); + } + + private ObjectDatabase openAlternate(File objdir) throws IOException { final File parent = objdir.getParentFile(); if (FileKey.isGitRepository(parent)) { final Repository db = RepositoryCache.open(FileKey.exact(parent)); return new AlternateRepositoryDatabase(db); } - return new ObjectDirectory(objdir); + return new ObjectDirectory(objdir, null); } private static final class PackList { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index f576b057c..a50132bc6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -109,9 +109,17 @@ public class Repository { private final List listeners = new Vector(); // thread safe static private final List allListeners = new Vector(); // thread safe + private File workDir; + + private File indexFile; + /** * Construct a representation of a Git repository. * + * The work tree, object directory, alternate object directories and index + * file locations are deduced from the given git directory and the default + * rules. + * * @param d * GIT_DIR (the location of the repository metadata). * @throws IOException @@ -119,9 +127,71 @@ public class Repository { * accessed. */ public Repository(final File d) throws IOException { - gitDir = d.getAbsoluteFile(); - refs = new RefDatabase(this); - objectDatabase = new ObjectDirectory(FS.resolve(gitDir, "objects")); + this(d, null, null, null, null); // go figure it out + } + + /** + * Construct a representation of a Git repository. + * + * The work tree, object directory, alternate object directories and index + * file locations are deduced from the given git directory and the default + * rules. + * + * @param d + * GIT_DIR (the location of the repository metadata). May be + * null work workTree is set + * @param workTree + * GIT_WORK_TREE (the root of the checkout). May be null for + * default value. + * @throws IOException + * the repository appears to already exist but cannot be + * accessed. + */ + public Repository(final File d, final File workTree) throws IOException { + this(d, workTree, null, null, null); // go figure it out + } + + /** + * Construct a representation of a Git repository using the given parameters + * possibly overriding default conventions. + * + * @param d + * GIT_DIR (the location of the repository metadata). May be null + * for default value in which case it depends on GIT_WORK_TREE. + * @param workTree + * GIT_WORK_TREE (the root of the checkout). May be null for + * default value if GIT_DIR is + * @param objectDir + * GIT_OBJECT_DIRECTORY (where objects and are stored). May be + * null for default value. Relative names ares resolved against + * GIT_WORK_TREE + * @param alternateObjectDir + * GIT_ALTERNATE_OBJECT_DIRECTORIES (where more objects are read + * from). May be null for default value. Relative names ares + * resolved against GIT_WORK_TREE + * @param indexFile + * GIT_INDEX_FILE (the location of the index file). May be null + * for default value. Relative names ares resolved against + * GIT_WORK_TREE. + * @throws IOException + * the repository appears to already exist but cannot be + * accessed. + */ + public Repository(final File d, final File workTree, final File objectDir, + final File[] alternateObjectDir, final File indexFile) throws IOException { + + if (workTree != null) { + workDir = workTree; + if (d == null) + gitDir = new File(workTree, ".git"); + else + gitDir = d; + } else { + if (d != null) + gitDir = d; + else + throw new IllegalArgumentException("Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor"); + } final FileBasedConfig userConfig; userConfig = SystemReader.getInstance().openUserConfig(); @@ -136,14 +206,41 @@ public Repository(final File d) throws IOException { } config = new RepositoryConfig(userConfig, FS.resolve(gitDir, "config")); - if (objectDatabase.exists()) { - try { - getConfig().load(); - } catch (ConfigInvalidException e1) { - IOException e2 = new IOException("Unknown repository format"); - e2.initCause(e1); - throw e2; + try { + getConfig().load(); + } catch (ConfigInvalidException e1) { + IOException e2 = new IOException("Unknown repository format"); + e2.initCause(e1); + throw e2; + } + + if (workDir == null) { + if (d != null) { + // Only read core.worktree if GIT_DIR is set explicitly. See + // git-config(1). + String workTreeConfig = getConfig().getString("core", null, "worktree"); + if (workTreeConfig != null) { + workDir = FS.resolve(d, workTreeConfig); + } else { + workDir = gitDir.getParentFile(); + } } + } + + refs = new RefDatabase(this); + if (objectDir != null) + objectDatabase = new ObjectDirectory(FS.resolve(objectDir, ""), + alternateObjectDir); + else + objectDatabase = new ObjectDirectory(FS.resolve(gitDir, "objects"), + alternateObjectDir); + + if (indexFile != null) + this.indexFile = indexFile; + else + this.indexFile = new File(gitDir, "index"); + + if (objectDatabase.exists()) { final String repositoryFormatVersion = getConfig().getString( "core", null, "repositoryFormatVersion"); if (!"0".equals(repositoryFormatVersion)) { @@ -959,6 +1056,13 @@ public GitIndex getIndex() throws IOException { return index; } + /** + * @return the index file location + */ + public File getIndexFile() { + return indexFile; + } + static byte[] gitInternalSlash(byte[] bytes) { if (File.separatorChar == '/') return bytes; @@ -1084,7 +1188,17 @@ public static String stripWorkDir(File workDir, File file) { * @return the workdir file, i.e. where the files are checked out */ public File getWorkDir() { - return getDirectory().getParentFile(); + return workDir; + } + + /** + * Override default workdir + * + * @param workTree + * the work tree directory + */ + public void setWorkDir(File workTree) { + this.workDir = workTree; } /**