BaseRepositoryBuilder.java
- /*
- * Copyright (C) 2010, Google Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- package org.eclipse.jgit.lib;
- import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
- import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE;
- import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE;
- import static org.eclipse.jgit.lib.Constants.DOT_GIT;
- import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY;
- import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY;
- import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY;
- import static org.eclipse.jgit.lib.Constants.GIT_INDEX_FILE_KEY;
- import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY;
- import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY;
- import java.io.File;
- import java.io.IOException;
- import java.text.MessageFormat;
- import java.util.Collection;
- import java.util.LinkedList;
- import java.util.List;
- import org.eclipse.jgit.errors.ConfigInvalidException;
- import org.eclipse.jgit.errors.RepositoryNotFoundException;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.internal.storage.file.FileRepository;
- import org.eclipse.jgit.lib.RepositoryCache.FileKey;
- import org.eclipse.jgit.storage.file.FileBasedConfig;
- 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;
- /**
- * Base builder to customize repository construction.
- * <p>
- * Repository implementations may subclass this builder in order to add custom
- * repository detection methods.
- *
- * @param <B>
- * type of the repository builder.
- * @param <R>
- * type of the repository that is constructed.
- * @see RepositoryBuilder
- * @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 static File getSymRef(File workTree, File dotGit, FS fs)
- throws IOException {
- 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);
- while (content[lineEnd - 1] == '\n' ||
- (content[lineEnd - 1] == '\r' && SystemReader.getInstance().isWindows()))
- lineEnd--;
- if (lineEnd == pathStart)
- throw new IOException(MessageFormat.format(
- JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));
- String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd);
- File gitdirFile = fs.resolve(workTree, gitdirPath);
- if (gitdirFile.isAbsolute())
- return gitdirFile;
- else
- return new File(workTree, gitdirPath).getCanonicalFile();
- }
- private FS fs;
- private File gitDir;
- private File objectDirectory;
- private List<File> alternateObjectDirectories;
- private File indexFile;
- private File workTree;
- /** Directories limiting the search for a Git repository. */
- private List<File> ceilingDirectories;
- /** True only if the caller wants to force bare behavior. */
- private boolean bare;
- /** True if the caller requires the repository to exist. */
- private boolean mustExist;
- /** Configuration file of target repository, lazily loaded if required. */
- private Config config;
- /**
- * Set the file system abstraction needed by this repository.
- *
- * @param fs
- * the abstraction.
- * @return {@code this} (for chaining calls).
- */
- public B setFS(FS fs) {
- this.fs = fs;
- return self();
- }
- /**
- * Get the file system abstraction, or null if not set.
- *
- * @return the file system abstraction, or null if not set.
- */
- public FS getFS() {
- return fs;
- }
- /**
- * Set the Git directory storing the repository metadata.
- * <p>
- * The meta directory stores the objects, references, and meta files like
- * {@code MERGE_HEAD}, or the index file. If {@code null} the path is
- * assumed to be {@code workTree/.git}.
- *
- * @param gitDir
- * {@code GIT_DIR}, the repository meta directory.
- * @return {@code this} (for chaining calls).
- */
- public B setGitDir(File gitDir) {
- this.gitDir = gitDir;
- this.config = null;
- return self();
- }
- /**
- * Get the meta data directory; null if not set.
- *
- * @return the meta data directory; null if not set.
- */
- public File getGitDir() {
- return gitDir;
- }
- /**
- * Set the directory storing the repository's objects.
- *
- * @param objectDirectory
- * {@code GIT_OBJECT_DIRECTORY}, the directory where the
- * repository's object files are stored.
- * @return {@code this} (for chaining calls).
- */
- public B setObjectDirectory(File objectDirectory) {
- this.objectDirectory = objectDirectory;
- return self();
- }
- /**
- * Get the object directory; null if not set.
- *
- * @return the object directory; null if not set.
- */
- public File getObjectDirectory() {
- return objectDirectory;
- }
- /**
- * Add an alternate object directory to the search list.
- * <p>
- * This setting handles one alternate directory at a time, and is provided
- * to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
- *
- * @param other
- * another objects directory to search after the standard one.
- * @return {@code this} (for chaining calls).
- */
- public B addAlternateObjectDirectory(File other) {
- if (other != null) {
- if (alternateObjectDirectories == null)
- alternateObjectDirectories = new LinkedList<>();
- alternateObjectDirectories.add(other);
- }
- return self();
- }
- /**
- * Add alternate object directories to the search list.
- * <p>
- * This setting handles several alternate directories at once, and is
- * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
- *
- * @param inList
- * other object directories to search after the standard one. The
- * collection's contents is copied to an internal list.
- * @return {@code this} (for chaining calls).
- */
- public B addAlternateObjectDirectories(Collection<File> inList) {
- if (inList != null) {
- for (File path : inList)
- addAlternateObjectDirectory(path);
- }
- return self();
- }
- /**
- * Add alternate object directories to the search list.
- * <p>
- * This setting handles several alternate directories at once, and is
- * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
- *
- * @param inList
- * other object directories to search after the standard one. The
- * array's contents is copied to an internal list.
- * @return {@code this} (for chaining calls).
- */
- public B addAlternateObjectDirectories(File[] inList) {
- if (inList != null) {
- for (File path : inList)
- addAlternateObjectDirectory(path);
- }
- return self();
- }
- /**
- * Get ordered array of alternate directories; null if non were set.
- *
- * @return ordered array of alternate directories; null if non were set.
- */
- public File[] getAlternateObjectDirectories() {
- final List<File> alts = alternateObjectDirectories;
- if (alts == null)
- return null;
- return alts.toArray(new File[0]);
- }
- /**
- * Force the repository to be treated as bare (have no working directory).
- * <p>
- * If bare the working directory aspects of the repository won't be
- * configured, and will not be accessible.
- *
- * @return {@code this} (for chaining calls).
- */
- public B setBare() {
- setIndexFile(null);
- setWorkTree(null);
- bare = true;
- return self();
- }
- /**
- * Whether this repository was forced bare by {@link #setBare()}.
- *
- * @return true if this repository was forced bare by {@link #setBare()}.
- */
- public boolean isBare() {
- return bare;
- }
- /**
- * Require the repository to exist before it can be opened.
- *
- * @param mustExist
- * true if it must exist; false if it can be missing and created
- * after being built.
- * @return {@code this} (for chaining calls).
- */
- public B setMustExist(boolean mustExist) {
- this.mustExist = mustExist;
- return self();
- }
- /**
- * Whether the repository must exist before being opened.
- *
- * @return true if the repository must exist before being opened.
- */
- public boolean isMustExist() {
- return mustExist;
- }
- /**
- * Set the top level directory of the working files.
- *
- * @param workTree
- * {@code GIT_WORK_TREE}, the working directory of the checkout.
- * @return {@code this} (for chaining calls).
- */
- public B setWorkTree(File workTree) {
- this.workTree = workTree;
- return self();
- }
- /**
- * Get the work tree directory, or null if not set.
- *
- * @return the work tree directory, or null if not set.
- */
- public File getWorkTree() {
- return workTree;
- }
- /**
- * Set the local index file that is caching checked out file status.
- * <p>
- * The location of the index file tracking the status information for each
- * checked out file in {@code workTree}. This may be null to assume the
- * default {@code gitDiir/index}.
- *
- * @param indexFile
- * {@code GIT_INDEX_FILE}, the index file location.
- * @return {@code this} (for chaining calls).
- */
- public B setIndexFile(File indexFile) {
- this.indexFile = indexFile;
- return self();
- }
- /**
- * Get the index file location, or null if not set.
- *
- * @return the index file location, or null if not set.
- */
- public File getIndexFile() {
- return indexFile;
- }
- /**
- * Read standard Git environment variables and configure from those.
- * <p>
- * This method tries to read the standard Git environment variables, such as
- * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
- * instance. If an environment variable is set, it overrides the value
- * already set in this builder.
- *
- * @return {@code this} (for chaining calls).
- */
- public B readEnvironment() {
- return readEnvironment(SystemReader.getInstance());
- }
- /**
- * Read standard Git environment variables and configure from those.
- * <p>
- * This method tries to read the standard Git environment variables, such as
- * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
- * instance. If a property is already set in the builder, the environment
- * variable is not used.
- *
- * @param sr
- * the SystemReader abstraction to access the environment.
- * @return {@code this} (for chaining calls).
- */
- public B readEnvironment(SystemReader sr) {
- if (getGitDir() == null) {
- String val = sr.getenv(GIT_DIR_KEY);
- if (val != null)
- setGitDir(new File(val));
- }
- if (getObjectDirectory() == null) {
- String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY);
- if (val != null)
- setObjectDirectory(new File(val));
- }
- if (getAlternateObjectDirectories() == null) {
- String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY);
- if (val != null) {
- for (String path : val.split(File.pathSeparator))
- addAlternateObjectDirectory(new File(path));
- }
- }
- if (getWorkTree() == null) {
- String val = sr.getenv(GIT_WORK_TREE_KEY);
- if (val != null)
- setWorkTree(new File(val));
- }
- if (getIndexFile() == null) {
- String val = sr.getenv(GIT_INDEX_FILE_KEY);
- if (val != null)
- setIndexFile(new File(val));
- }
- if (ceilingDirectories == null) {
- String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY);
- if (val != null) {
- for (String path : val.split(File.pathSeparator))
- addCeilingDirectory(new File(path));
- }
- }
- return self();
- }
- /**
- * Add a ceiling directory to the search limit list.
- * <p>
- * This setting handles one ceiling directory at a time, and is provided to
- * support {@code GIT_CEILING_DIRECTORIES}.
- *
- * @param root
- * a path to stop searching at; its parent will not be searched.
- * @return {@code this} (for chaining calls).
- */
- public B addCeilingDirectory(File root) {
- if (root != null) {
- if (ceilingDirectories == null)
- ceilingDirectories = new LinkedList<>();
- ceilingDirectories.add(root);
- }
- return self();
- }
- /**
- * Add ceiling directories to the search list.
- * <p>
- * This setting handles several ceiling directories at once, and is provided
- * to support {@code GIT_CEILING_DIRECTORIES}.
- *
- * @param inList
- * directory paths to stop searching at. The collection's
- * contents is copied to an internal list.
- * @return {@code this} (for chaining calls).
- */
- public B addCeilingDirectories(Collection<File> inList) {
- if (inList != null) {
- for (File path : inList)
- addCeilingDirectory(path);
- }
- return self();
- }
- /**
- * Add ceiling directories to the search list.
- * <p>
- * This setting handles several ceiling directories at once, and is provided
- * to support {@code GIT_CEILING_DIRECTORIES}.
- *
- * @param inList
- * directory paths to stop searching at. The array's contents is
- * copied to an internal list.
- * @return {@code this} (for chaining calls).
- */
- public B addCeilingDirectories(File[] inList) {
- if (inList != null) {
- for (File path : inList)
- addCeilingDirectory(path);
- }
- return self();
- }
- /**
- * Configure {@code GIT_DIR} by searching up the file system.
- * <p>
- * Starts from the current working directory of the JVM and scans up through
- * the directory tree until a Git repository is found. Success can be
- * determined by checking for {@code getGitDir() != null}.
- * <p>
- * The search can be limited to specific spaces of the local filesystem by
- * {@link #addCeilingDirectory(File)}, or inheriting the list through a
- * prior call to {@link #readEnvironment()}.
- *
- * @return {@code this} (for chaining calls).
- */
- public B findGitDir() {
- if (getGitDir() == null)
- findGitDir(new File("").getAbsoluteFile()); //$NON-NLS-1$
- return self();
- }
- /**
- * Configure {@code GIT_DIR} by searching up the file system.
- * <p>
- * Starts from the supplied directory path and scans up through the parent
- * directory tree until a Git repository is found. Success can be determined
- * by checking for {@code getGitDir() != null}.
- * <p>
- * The search can be limited to specific spaces of the local filesystem by
- * {@link #addCeilingDirectory(File)}, or inheriting the list through a
- * prior call to {@link #readEnvironment()}.
- *
- * @param current
- * directory to begin searching in.
- * @return {@code this} (for chaining calls).
- */
- public B findGitDir(File current) {
- if (getGitDir() == null) {
- FS tryFS = safeFS();
- while (current != null) {
- File dir = new File(current, DOT_GIT);
- if (FileKey.isGitRepository(dir, tryFS)) {
- setGitDir(dir);
- break;
- } else if (dir.isFile()) {
- try {
- setGitDir(getSymRef(current, dir, tryFS));
- break;
- } catch (IOException ignored) {
- // Continue searching if gitdir ref isn't found
- }
- } else if (FileKey.isGitRepository(current, tryFS)) {
- setGitDir(current);
- break;
- }
- current = current.getParentFile();
- if (current != null && ceilingDirectories != null
- && ceilingDirectories.contains(current))
- break;
- }
- }
- return self();
- }
- /**
- * Guess and populate all parameters not already defined.
- * <p>
- * If an option was not set, the setup method will try to default the option
- * based on other options. If insufficient information is available, an
- * exception is thrown to the caller.
- *
- * @return {@code this}
- * @throws java.lang.IllegalArgumentException
- * insufficient parameters were set, or some parameters are
- * incompatible with one another.
- * @throws java.io.IOException
- * the repository could not be accessed to configure the rest of
- * the builder's parameters.
- */
- public B setup() throws IllegalArgumentException, IOException {
- requireGitDirOrWorkTree();
- setupGitDir();
- setupWorkTree();
- setupInternals();
- return self();
- }
- /**
- * Create a repository matching the configuration in this builder.
- * <p>
- * If an option was not set, the build method will try to default the option
- * based on other options. If insufficient information is available, an
- * exception is thrown to the caller.
- *
- * @return a repository matching this configuration. The caller is
- * responsible to close the repository instance when it is no longer
- * needed.
- * @throws java.lang.IllegalArgumentException
- * insufficient parameters were set.
- * @throws java.io.IOException
- * the repository could not be accessed to configure the rest of
- * the builder's parameters.
- */
- @SuppressWarnings({ "unchecked", "resource" })
- public R build() throws IOException {
- R repo = (R) new FileRepository(setup());
- if (isMustExist() && !repo.getObjectDatabase().exists())
- throw new RepositoryNotFoundException(getGitDir());
- return repo;
- }
- /**
- * Require either {@code gitDir} or {@code workTree} to be set.
- */
- protected void requireGitDirOrWorkTree() {
- if (getGitDir() == null && getWorkTree() == null)
- throw new IllegalArgumentException(
- JGitText.get().eitherGitDirOrWorkTreeRequired);
- }
- /**
- * Perform standard gitDir initialization.
- *
- * @throws java.io.IOException
- * the repository could not be accessed
- */
- protected void setupGitDir() throws IOException {
- // 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
- setGitDir(getSymRef(getWorkTree(), dotGit, safeFS()));
- }
- }
- /**
- * Perform standard work-tree initialization.
- * <p>
- * This is a method typically invoked inside of {@link #setup()}, near the
- * end after the repository has been identified and its configuration is
- * available for inspection.
- *
- * @throws java.io.IOException
- * the repository configuration could not be read.
- */
- protected void setupWorkTree() throws IOException {
- if (getFS() == null)
- setFS(FS.DETECTED);
- // If we aren't bare, we should have a work tree.
- //
- if (!isBare() && getWorkTree() == null)
- setWorkTree(guessWorkTreeOrFail());
- if (!isBare()) {
- // If after guessing we're still not bare, we must have
- // a metadata directory to hold the repository. Assume
- // its at the work tree.
- //
- if (getGitDir() == null)
- setGitDir(getWorkTree().getParentFile());
- if (getIndexFile() == null)
- setIndexFile(new File(getGitDir(), "index")); //$NON-NLS-1$
- }
- }
- /**
- * Configure the internal implementation details of the repository.
- *
- * @throws java.io.IOException
- * the repository could not be accessed
- */
- protected void setupInternals() throws IOException {
- if (getObjectDirectory() == null && getGitDir() != null)
- setObjectDirectory(safeFS().resolve(getGitDir(), "objects")); //$NON-NLS-1$
- }
- /**
- * Get the cached repository configuration, loading if not yet available.
- *
- * @return the configuration of the repository.
- * @throws java.io.IOException
- * the configuration is not available, or is badly formed.
- */
- protected Config getConfig() throws IOException {
- if (config == null)
- config = loadConfig();
- return config;
- }
- /**
- * Parse and load the repository specific configuration.
- * <p>
- * The default implementation reads {@code gitDir/config}, or returns an
- * empty configuration if gitDir was not set.
- *
- * @return the repository's configuration.
- * @throws java.io.IOException
- * the configuration is not available.
- */
- protected Config loadConfig() throws IOException {
- if (getGitDir() != null) {
- // We only want the repository's configuration file, and not
- // the user file, as these parameters must be unique to this
- // repository and not inherited from other files.
- //
- File path = safeFS().resolve(getGitDir(), Constants.CONFIG);
- FileBasedConfig cfg = new FileBasedConfig(path, safeFS());
- try {
- cfg.load();
- } catch (ConfigInvalidException err) {
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().repositoryConfigFileInvalid, path
- .getAbsolutePath(), err.getMessage()));
- }
- return cfg;
- } else {
- return new Config();
- }
- }
- private File guessWorkTreeOrFail() throws IOException {
- final Config cfg = getConfig();
- // If set, core.worktree wins.
- //
- String path = cfg.getString(CONFIG_CORE_SECTION, null,
- CONFIG_KEY_WORKTREE);
- if (path != null)
- return safeFS().resolve(getGitDir(), path).getCanonicalFile();
- // If core.bare is set, honor its value. Assume workTree is
- // the parent directory of the repository.
- //
- if (cfg.getString(CONFIG_CORE_SECTION, null, CONFIG_KEY_BARE) != null) {
- if (cfg.getBoolean(CONFIG_CORE_SECTION, CONFIG_KEY_BARE, true)) {
- setBare();
- return null;
- }
- return getGitDir().getParentFile();
- }
- if (getGitDir().getName().equals(DOT_GIT)) {
- // No value for the "bare" flag, but gitDir is named ".git",
- // use the parent of the directory
- //
- return getGitDir().getParentFile();
- }
- // We have to assume we are bare.
- //
- setBare();
- return null;
- }
- /**
- * Get the configured FS, or {@link FS#DETECTED}.
- *
- * @return the configured FS, or {@link FS#DETECTED}.
- */
- protected FS safeFS() {
- return getFS() != null ? getFS() : FS.DETECTED;
- }
- /**
- * Get this object
- *
- * @return {@code this}
- */
- @SuppressWarnings("unchecked")
- protected final B self() {
- return (B) this;
- }
- }