BaseRepositoryBuilder.java

  1. /*
  2.  * Copyright (C) 2010, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.lib;

  11. import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
  12. import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE;
  13. import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE;
  14. import static org.eclipse.jgit.lib.Constants.DOT_GIT;
  15. import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY;
  16. import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY;
  17. import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY;
  18. import static org.eclipse.jgit.lib.Constants.GIT_INDEX_FILE_KEY;
  19. import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY;
  20. import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY;

  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.text.MessageFormat;
  24. import java.util.Collection;
  25. import java.util.LinkedList;
  26. import java.util.List;

  27. import org.eclipse.jgit.annotations.NonNull;
  28. import org.eclipse.jgit.api.errors.InvalidRefNameException;
  29. import org.eclipse.jgit.errors.ConfigInvalidException;
  30. import org.eclipse.jgit.errors.RepositoryNotFoundException;
  31. import org.eclipse.jgit.internal.JGitText;
  32. import org.eclipse.jgit.internal.storage.file.FileRepository;
  33. import org.eclipse.jgit.lib.RepositoryCache.FileKey;
  34. import org.eclipse.jgit.storage.file.FileBasedConfig;
  35. import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
  36. import org.eclipse.jgit.util.FS;
  37. import org.eclipse.jgit.util.IO;
  38. import org.eclipse.jgit.util.RawParseUtils;
  39. import org.eclipse.jgit.util.StringUtils;
  40. import org.eclipse.jgit.util.SystemReader;

  41. /**
  42.  * Base builder to customize repository construction.
  43.  * <p>
  44.  * Repository implementations may subclass this builder in order to add custom
  45.  * repository detection methods.
  46.  *
  47.  * @param <B>
  48.  *            type of the repository builder.
  49.  * @param <R>
  50.  *            type of the repository that is constructed.
  51.  * @see RepositoryBuilder
  52.  * @see FileRepositoryBuilder
  53.  */
  54. public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Repository> {
  55.     private static boolean isSymRef(byte[] ref) {
  56.         if (ref.length < 9)
  57.             return false;
  58.         return /**/ref[0] == 'g' //
  59.                 && ref[1] == 'i' //
  60.                 && ref[2] == 't' //
  61.                 && ref[3] == 'd' //
  62.                 && ref[4] == 'i' //
  63.                 && ref[5] == 'r' //
  64.                 && ref[6] == ':' //
  65.                 && ref[7] == ' ';
  66.     }

  67.     private static File getSymRef(File workTree, File dotGit, FS fs)
  68.             throws IOException {
  69.         byte[] content = IO.readFully(dotGit);
  70.         if (!isSymRef(content)) {
  71.             throw new IOException(MessageFormat.format(
  72.                     JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));
  73.         }

  74.         int pathStart = 8;
  75.         int lineEnd = RawParseUtils.nextLF(content, pathStart);
  76.         while (content[lineEnd - 1] == '\n' ||
  77.                 (content[lineEnd - 1] == '\r'
  78.                         && SystemReader.getInstance().isWindows())) {
  79.             lineEnd--;
  80.         }
  81.         if (lineEnd == pathStart) {
  82.             throw new IOException(MessageFormat.format(
  83.                     JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));
  84.         }

  85.         String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd);
  86.         File gitdirFile = fs.resolve(workTree, gitdirPath);
  87.         if (gitdirFile.isAbsolute()) {
  88.             return gitdirFile;
  89.         }
  90.         return new File(workTree, gitdirPath).getCanonicalFile();
  91.     }

  92.     private FS fs;

  93.     private File gitDir;

  94.     private File objectDirectory;

  95.     private List<File> alternateObjectDirectories;

  96.     private File indexFile;

  97.     private File workTree;

  98.     private String initialBranch = Constants.MASTER;

  99.     /** Directories limiting the search for a Git repository. */
  100.     private List<File> ceilingDirectories;

  101.     /** True only if the caller wants to force bare behavior. */
  102.     private boolean bare;

  103.     /** True if the caller requires the repository to exist. */
  104.     private boolean mustExist;

  105.     /** Configuration file of target repository, lazily loaded if required. */
  106.     private Config config;

  107.     /**
  108.      * Set the file system abstraction needed by this repository.
  109.      *
  110.      * @param fs
  111.      *            the abstraction.
  112.      * @return {@code this} (for chaining calls).
  113.      */
  114.     public B setFS(FS fs) {
  115.         this.fs = fs;
  116.         return self();
  117.     }

  118.     /**
  119.      * Get the file system abstraction, or null if not set.
  120.      *
  121.      * @return the file system abstraction, or null if not set.
  122.      */
  123.     public FS getFS() {
  124.         return fs;
  125.     }

  126.     /**
  127.      * Set the Git directory storing the repository metadata.
  128.      * <p>
  129.      * The meta directory stores the objects, references, and meta files like
  130.      * {@code MERGE_HEAD}, or the index file. If {@code null} the path is
  131.      * assumed to be {@code workTree/.git}.
  132.      *
  133.      * @param gitDir
  134.      *            {@code GIT_DIR}, the repository meta directory.
  135.      * @return {@code this} (for chaining calls).
  136.      */
  137.     public B setGitDir(File gitDir) {
  138.         this.gitDir = gitDir;
  139.         this.config = null;
  140.         return self();
  141.     }

  142.     /**
  143.      * Get the meta data directory; null if not set.
  144.      *
  145.      * @return the meta data directory; null if not set.
  146.      */
  147.     public File getGitDir() {
  148.         return gitDir;
  149.     }

  150.     /**
  151.      * Set the directory storing the repository's objects.
  152.      *
  153.      * @param objectDirectory
  154.      *            {@code GIT_OBJECT_DIRECTORY}, the directory where the
  155.      *            repository's object files are stored.
  156.      * @return {@code this} (for chaining calls).
  157.      */
  158.     public B setObjectDirectory(File objectDirectory) {
  159.         this.objectDirectory = objectDirectory;
  160.         return self();
  161.     }

  162.     /**
  163.      * Get the object directory; null if not set.
  164.      *
  165.      * @return the object directory; null if not set.
  166.      */
  167.     public File getObjectDirectory() {
  168.         return objectDirectory;
  169.     }

  170.     /**
  171.      * Add an alternate object directory to the search list.
  172.      * <p>
  173.      * This setting handles one alternate directory at a time, and is provided
  174.      * to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
  175.      *
  176.      * @param other
  177.      *            another objects directory to search after the standard one.
  178.      * @return {@code this} (for chaining calls).
  179.      */
  180.     public B addAlternateObjectDirectory(File other) {
  181.         if (other != null) {
  182.             if (alternateObjectDirectories == null)
  183.                 alternateObjectDirectories = new LinkedList<>();
  184.             alternateObjectDirectories.add(other);
  185.         }
  186.         return self();
  187.     }

  188.     /**
  189.      * Add alternate object directories to the search list.
  190.      * <p>
  191.      * This setting handles several alternate directories at once, and is
  192.      * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
  193.      *
  194.      * @param inList
  195.      *            other object directories to search after the standard one. The
  196.      *            collection's contents is copied to an internal list.
  197.      * @return {@code this} (for chaining calls).
  198.      */
  199.     public B addAlternateObjectDirectories(Collection<File> inList) {
  200.         if (inList != null) {
  201.             for (File path : inList)
  202.                 addAlternateObjectDirectory(path);
  203.         }
  204.         return self();
  205.     }

  206.     /**
  207.      * Add alternate object directories to the search list.
  208.      * <p>
  209.      * This setting handles several alternate directories at once, and is
  210.      * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}.
  211.      *
  212.      * @param inList
  213.      *            other object directories to search after the standard one. The
  214.      *            array's contents is copied to an internal list.
  215.      * @return {@code this} (for chaining calls).
  216.      */
  217.     public B addAlternateObjectDirectories(File[] inList) {
  218.         if (inList != null) {
  219.             for (File path : inList)
  220.                 addAlternateObjectDirectory(path);
  221.         }
  222.         return self();
  223.     }

  224.     /**
  225.      * Get ordered array of alternate directories; null if non were set.
  226.      *
  227.      * @return ordered array of alternate directories; null if non were set.
  228.      */
  229.     public File[] getAlternateObjectDirectories() {
  230.         final List<File> alts = alternateObjectDirectories;
  231.         if (alts == null)
  232.             return null;
  233.         return alts.toArray(new File[0]);
  234.     }

  235.     /**
  236.      * Force the repository to be treated as bare (have no working directory).
  237.      * <p>
  238.      * If bare the working directory aspects of the repository won't be
  239.      * configured, and will not be accessible.
  240.      *
  241.      * @return {@code this} (for chaining calls).
  242.      */
  243.     public B setBare() {
  244.         setIndexFile(null);
  245.         setWorkTree(null);
  246.         bare = true;
  247.         return self();
  248.     }

  249.     /**
  250.      * Whether this repository was forced bare by {@link #setBare()}.
  251.      *
  252.      * @return true if this repository was forced bare by {@link #setBare()}.
  253.      */
  254.     public boolean isBare() {
  255.         return bare;
  256.     }

  257.     /**
  258.      * Require the repository to exist before it can be opened.
  259.      *
  260.      * @param mustExist
  261.      *            true if it must exist; false if it can be missing and created
  262.      *            after being built.
  263.      * @return {@code this} (for chaining calls).
  264.      */
  265.     public B setMustExist(boolean mustExist) {
  266.         this.mustExist = mustExist;
  267.         return self();
  268.     }

  269.     /**
  270.      * Whether the repository must exist before being opened.
  271.      *
  272.      * @return true if the repository must exist before being opened.
  273.      */
  274.     public boolean isMustExist() {
  275.         return mustExist;
  276.     }

  277.     /**
  278.      * Set the top level directory of the working files.
  279.      *
  280.      * @param workTree
  281.      *            {@code GIT_WORK_TREE}, the working directory of the checkout.
  282.      * @return {@code this} (for chaining calls).
  283.      */
  284.     public B setWorkTree(File workTree) {
  285.         this.workTree = workTree;
  286.         return self();
  287.     }

  288.     /**
  289.      * Get the work tree directory, or null if not set.
  290.      *
  291.      * @return the work tree directory, or null if not set.
  292.      */
  293.     public File getWorkTree() {
  294.         return workTree;
  295.     }

  296.     /**
  297.      * Set the local index file that is caching checked out file status.
  298.      * <p>
  299.      * The location of the index file tracking the status information for each
  300.      * checked out file in {@code workTree}. This may be null to assume the
  301.      * default {@code gitDiir/index}.
  302.      *
  303.      * @param indexFile
  304.      *            {@code GIT_INDEX_FILE}, the index file location.
  305.      * @return {@code this} (for chaining calls).
  306.      */
  307.     public B setIndexFile(File indexFile) {
  308.         this.indexFile = indexFile;
  309.         return self();
  310.     }

  311.     /**
  312.      * Get the index file location, or null if not set.
  313.      *
  314.      * @return the index file location, or null if not set.
  315.      */
  316.     public File getIndexFile() {
  317.         return indexFile;
  318.     }

  319.     /**
  320.      * Set the initial branch of the new repository. If not specified
  321.      * ({@code null} or empty), fall back to the default name (currently
  322.      * master).
  323.      *
  324.      * @param branch
  325.      *            initial branch name of the new repository. If {@code null} or
  326.      *            empty the configured default branch will be used.
  327.      * @return {@code this}
  328.      * @throws InvalidRefNameException
  329.      *             if the branch name is not valid
  330.      *
  331.      * @since 5.11
  332.      */
  333.     public B setInitialBranch(String branch) throws InvalidRefNameException {
  334.         if (StringUtils.isEmptyOrNull(branch)) {
  335.             this.initialBranch = Constants.MASTER;
  336.         } else {
  337.             if (!Repository.isValidRefName(Constants.R_HEADS + branch)) {
  338.                 throw new InvalidRefNameException(MessageFormat
  339.                         .format(JGitText.get().branchNameInvalid, branch));
  340.             }
  341.             this.initialBranch = branch;
  342.         }
  343.         return self();
  344.     }

  345.     /**
  346.      * Get the initial branch of the new repository.
  347.      *
  348.      * @return the initial branch of the new repository.
  349.      * @since 5.11
  350.      */
  351.     public @NonNull String getInitialBranch() {
  352.         return initialBranch;
  353.     }

  354.     /**
  355.      * Read standard Git environment variables and configure from those.
  356.      * <p>
  357.      * This method tries to read the standard Git environment variables, such as
  358.      * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
  359.      * instance. If an environment variable is set, it overrides the value
  360.      * already set in this builder.
  361.      *
  362.      * @return {@code this} (for chaining calls).
  363.      */
  364.     public B readEnvironment() {
  365.         return readEnvironment(SystemReader.getInstance());
  366.     }

  367.     /**
  368.      * Read standard Git environment variables and configure from those.
  369.      * <p>
  370.      * This method tries to read the standard Git environment variables, such as
  371.      * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
  372.      * instance. If a property is already set in the builder, the environment
  373.      * variable is not used.
  374.      *
  375.      * @param sr
  376.      *            the SystemReader abstraction to access the environment.
  377.      * @return {@code this} (for chaining calls).
  378.      */
  379.     public B readEnvironment(SystemReader sr) {
  380.         if (getGitDir() == null) {
  381.             String val = sr.getenv(GIT_DIR_KEY);
  382.             if (val != null)
  383.                 setGitDir(new File(val));
  384.         }

  385.         if (getObjectDirectory() == null) {
  386.             String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY);
  387.             if (val != null)
  388.                 setObjectDirectory(new File(val));
  389.         }

  390.         if (getAlternateObjectDirectories() == null) {
  391.             String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY);
  392.             if (val != null) {
  393.                 for (String path : val.split(File.pathSeparator))
  394.                     addAlternateObjectDirectory(new File(path));
  395.             }
  396.         }

  397.         if (getWorkTree() == null) {
  398.             String val = sr.getenv(GIT_WORK_TREE_KEY);
  399.             if (val != null)
  400.                 setWorkTree(new File(val));
  401.         }

  402.         if (getIndexFile() == null) {
  403.             String val = sr.getenv(GIT_INDEX_FILE_KEY);
  404.             if (val != null)
  405.                 setIndexFile(new File(val));
  406.         }

  407.         if (ceilingDirectories == null) {
  408.             String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY);
  409.             if (val != null) {
  410.                 for (String path : val.split(File.pathSeparator))
  411.                     addCeilingDirectory(new File(path));
  412.             }
  413.         }

  414.         return self();
  415.     }

  416.     /**
  417.      * Add a ceiling directory to the search limit list.
  418.      * <p>
  419.      * This setting handles one ceiling directory at a time, and is provided to
  420.      * support {@code GIT_CEILING_DIRECTORIES}.
  421.      *
  422.      * @param root
  423.      *            a path to stop searching at; its parent will not be searched.
  424.      * @return {@code this} (for chaining calls).
  425.      */
  426.     public B addCeilingDirectory(File root) {
  427.         if (root != null) {
  428.             if (ceilingDirectories == null)
  429.                 ceilingDirectories = new LinkedList<>();
  430.             ceilingDirectories.add(root);
  431.         }
  432.         return self();
  433.     }

  434.     /**
  435.      * Add ceiling directories to the search list.
  436.      * <p>
  437.      * This setting handles several ceiling directories at once, and is provided
  438.      * to support {@code GIT_CEILING_DIRECTORIES}.
  439.      *
  440.      * @param inList
  441.      *            directory paths to stop searching at. The collection's
  442.      *            contents is copied to an internal list.
  443.      * @return {@code this} (for chaining calls).
  444.      */
  445.     public B addCeilingDirectories(Collection<File> inList) {
  446.         if (inList != null) {
  447.             for (File path : inList)
  448.                 addCeilingDirectory(path);
  449.         }
  450.         return self();
  451.     }

  452.     /**
  453.      * Add ceiling directories to the search list.
  454.      * <p>
  455.      * This setting handles several ceiling directories at once, and is provided
  456.      * to support {@code GIT_CEILING_DIRECTORIES}.
  457.      *
  458.      * @param inList
  459.      *            directory paths to stop searching at. The array's contents is
  460.      *            copied to an internal list.
  461.      * @return {@code this} (for chaining calls).
  462.      */
  463.     public B addCeilingDirectories(File[] inList) {
  464.         if (inList != null) {
  465.             for (File path : inList)
  466.                 addCeilingDirectory(path);
  467.         }
  468.         return self();
  469.     }

  470.     /**
  471.      * Configure {@code GIT_DIR} by searching up the file system.
  472.      * <p>
  473.      * Starts from the current working directory of the JVM and scans up through
  474.      * the directory tree until a Git repository is found. Success can be
  475.      * determined by checking for {@code getGitDir() != null}.
  476.      * <p>
  477.      * The search can be limited to specific spaces of the local filesystem by
  478.      * {@link #addCeilingDirectory(File)}, or inheriting the list through a
  479.      * prior call to {@link #readEnvironment()}.
  480.      *
  481.      * @return {@code this} (for chaining calls).
  482.      */
  483.     public B findGitDir() {
  484.         if (getGitDir() == null)
  485.             findGitDir(new File("").getAbsoluteFile()); //$NON-NLS-1$
  486.         return self();
  487.     }

  488.     /**
  489.      * Configure {@code GIT_DIR} by searching up the file system.
  490.      * <p>
  491.      * Starts from the supplied directory path and scans up through the parent
  492.      * directory tree until a Git repository is found. Success can be determined
  493.      * by checking for {@code getGitDir() != null}.
  494.      * <p>
  495.      * The search can be limited to specific spaces of the local filesystem by
  496.      * {@link #addCeilingDirectory(File)}, or inheriting the list through a
  497.      * prior call to {@link #readEnvironment()}.
  498.      *
  499.      * @param current
  500.      *            directory to begin searching in.
  501.      * @return {@code this} (for chaining calls).
  502.      */
  503.     public B findGitDir(File current) {
  504.         if (getGitDir() == null) {
  505.             FS tryFS = safeFS();
  506.             while (current != null) {
  507.                 File dir = new File(current, DOT_GIT);
  508.                 if (FileKey.isGitRepository(dir, tryFS)) {
  509.                     setGitDir(dir);
  510.                     break;
  511.                 } else if (dir.isFile()) {
  512.                     try {
  513.                         setGitDir(getSymRef(current, dir, tryFS));
  514.                         break;
  515.                     } catch (IOException ignored) {
  516.                         // Continue searching if gitdir ref isn't found
  517.                     }
  518.                 } else if (FileKey.isGitRepository(current, tryFS)) {
  519.                     setGitDir(current);
  520.                     break;
  521.                 }

  522.                 current = current.getParentFile();
  523.                 if (current != null && ceilingDirectories != null
  524.                         && ceilingDirectories.contains(current))
  525.                     break;
  526.             }
  527.         }
  528.         return self();
  529.     }

  530.     /**
  531.      * Guess and populate all parameters not already defined.
  532.      * <p>
  533.      * If an option was not set, the setup method will try to default the option
  534.      * based on other options. If insufficient information is available, an
  535.      * exception is thrown to the caller.
  536.      *
  537.      * @return {@code this}
  538.      * @throws java.lang.IllegalArgumentException
  539.      *             insufficient parameters were set, or some parameters are
  540.      *             incompatible with one another.
  541.      * @throws java.io.IOException
  542.      *             the repository could not be accessed to configure the rest of
  543.      *             the builder's parameters.
  544.      */
  545.     public B setup() throws IllegalArgumentException, IOException {
  546.         requireGitDirOrWorkTree();
  547.         setupGitDir();
  548.         setupWorkTree();
  549.         setupInternals();
  550.         return self();
  551.     }

  552.     /**
  553.      * Create a repository matching the configuration in this builder.
  554.      * <p>
  555.      * If an option was not set, the build method will try to default the option
  556.      * based on other options. If insufficient information is available, an
  557.      * exception is thrown to the caller.
  558.      *
  559.      * @return a repository matching this configuration. The caller is
  560.      *         responsible to close the repository instance when it is no longer
  561.      *         needed.
  562.      * @throws java.lang.IllegalArgumentException
  563.      *             insufficient parameters were set.
  564.      * @throws java.io.IOException
  565.      *             the repository could not be accessed to configure the rest of
  566.      *             the builder's parameters.
  567.      */
  568.     @SuppressWarnings({ "unchecked", "resource" })
  569.     public R build() throws IOException {
  570.         R repo = (R) new FileRepository(setup());
  571.         if (isMustExist() && !repo.getObjectDatabase().exists())
  572.             throw new RepositoryNotFoundException(getGitDir());
  573.         return repo;
  574.     }

  575.     /**
  576.      * Require either {@code gitDir} or {@code workTree} to be set.
  577.      */
  578.     protected void requireGitDirOrWorkTree() {
  579.         if (getGitDir() == null && getWorkTree() == null)
  580.             throw new IllegalArgumentException(
  581.                     JGitText.get().eitherGitDirOrWorkTreeRequired);
  582.     }

  583.     /**
  584.      * Perform standard gitDir initialization.
  585.      *
  586.      * @throws java.io.IOException
  587.      *             the repository could not be accessed
  588.      */
  589.     protected void setupGitDir() throws IOException {
  590.         // No gitDir? Try to assume its under the workTree or a ref to another
  591.         // location
  592.         if (getGitDir() == null && getWorkTree() != null) {
  593.             File dotGit = new File(getWorkTree(), DOT_GIT);
  594.             if (!dotGit.isFile())
  595.                 setGitDir(dotGit);
  596.             else
  597.                 setGitDir(getSymRef(getWorkTree(), dotGit, safeFS()));
  598.         }
  599.     }

  600.     /**
  601.      * Perform standard work-tree initialization.
  602.      * <p>
  603.      * This is a method typically invoked inside of {@link #setup()}, near the
  604.      * end after the repository has been identified and its configuration is
  605.      * available for inspection.
  606.      *
  607.      * @throws java.io.IOException
  608.      *             the repository configuration could not be read.
  609.      */
  610.     protected void setupWorkTree() throws IOException {
  611.         if (getFS() == null)
  612.             setFS(FS.DETECTED);

  613.         // If we aren't bare, we should have a work tree.
  614.         //
  615.         if (!isBare() && getWorkTree() == null)
  616.             setWorkTree(guessWorkTreeOrFail());

  617.         if (!isBare()) {
  618.             // If after guessing we're still not bare, we must have
  619.             // a metadata directory to hold the repository. Assume
  620.             // its at the work tree.
  621.             //
  622.             if (getGitDir() == null)
  623.                 setGitDir(getWorkTree().getParentFile());
  624.             if (getIndexFile() == null)
  625.                 setIndexFile(new File(getGitDir(), "index")); //$NON-NLS-1$
  626.         }
  627.     }

  628.     /**
  629.      * Configure the internal implementation details of the repository.
  630.      *
  631.      * @throws java.io.IOException
  632.      *             the repository could not be accessed
  633.      */
  634.     protected void setupInternals() throws IOException {
  635.         if (getObjectDirectory() == null && getGitDir() != null)
  636.             setObjectDirectory(safeFS().resolve(getGitDir(), Constants.OBJECTS));
  637.     }

  638.     /**
  639.      * Get the cached repository configuration, loading if not yet available.
  640.      *
  641.      * @return the configuration of the repository.
  642.      * @throws java.io.IOException
  643.      *             the configuration is not available, or is badly formed.
  644.      */
  645.     protected Config getConfig() throws IOException {
  646.         if (config == null)
  647.             config = loadConfig();
  648.         return config;
  649.     }

  650.     /**
  651.      * Parse and load the repository specific configuration.
  652.      * <p>
  653.      * The default implementation reads {@code gitDir/config}, or returns an
  654.      * empty configuration if gitDir was not set.
  655.      *
  656.      * @return the repository's configuration.
  657.      * @throws java.io.IOException
  658.      *             the configuration is not available.
  659.      */
  660.     protected Config loadConfig() throws IOException {
  661.         if (getGitDir() != null) {
  662.             // We only want the repository's configuration file, and not
  663.             // the user file, as these parameters must be unique to this
  664.             // repository and not inherited from other files.
  665.             //
  666.             File path = safeFS().resolve(getGitDir(), Constants.CONFIG);
  667.             FileBasedConfig cfg = new FileBasedConfig(path, safeFS());
  668.             try {
  669.                 cfg.load();
  670.             } catch (ConfigInvalidException err) {
  671.                 throw new IllegalArgumentException(MessageFormat.format(
  672.                         JGitText.get().repositoryConfigFileInvalid, path
  673.                                 .getAbsolutePath(), err.getMessage()));
  674.             }
  675.             return cfg;
  676.         }
  677.         return new Config();
  678.     }

  679.     private File guessWorkTreeOrFail() throws IOException {
  680.         final Config cfg = getConfig();

  681.         // If set, core.worktree wins.
  682.         //
  683.         String path = cfg.getString(CONFIG_CORE_SECTION, null,
  684.                 CONFIG_KEY_WORKTREE);
  685.         if (path != null)
  686.             return safeFS().resolve(getGitDir(), path).getCanonicalFile();

  687.         // If core.bare is set, honor its value. Assume workTree is
  688.         // the parent directory of the repository.
  689.         //
  690.         if (cfg.getString(CONFIG_CORE_SECTION, null, CONFIG_KEY_BARE) != null) {
  691.             if (cfg.getBoolean(CONFIG_CORE_SECTION, CONFIG_KEY_BARE, true)) {
  692.                 setBare();
  693.                 return null;
  694.             }
  695.             return getGitDir().getParentFile();
  696.         }

  697.         if (getGitDir().getName().equals(DOT_GIT)) {
  698.             // No value for the "bare" flag, but gitDir is named ".git",
  699.             // use the parent of the directory
  700.             //
  701.             return getGitDir().getParentFile();
  702.         }

  703.         // We have to assume we are bare.
  704.         //
  705.         setBare();
  706.         return null;
  707.     }

  708.     /**
  709.      * Get the configured FS, or {@link FS#DETECTED}.
  710.      *
  711.      * @return the configured FS, or {@link FS#DETECTED}.
  712.      */
  713.     protected FS safeFS() {
  714.         return getFS() != null ? getFS() : FS.DETECTED;
  715.     }

  716.     /**
  717.      * Get this object
  718.      *
  719.      * @return {@code this}
  720.      */
  721.     @SuppressWarnings("unchecked")
  722.     protected final B self() {
  723.         return (B) this;
  724.     }
  725. }