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; } /**