SubmoduleWalk.java

  1. /*
  2.  * Copyright (C) 2011, GitHub 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.submodule;

  11. import java.io.File;
  12. import java.io.IOException;
  13. import java.text.MessageFormat;
  14. import java.util.HashMap;
  15. import java.util.Map;

  16. import org.eclipse.jgit.dircache.DirCache;
  17. import org.eclipse.jgit.dircache.DirCacheIterator;
  18. import org.eclipse.jgit.errors.ConfigInvalidException;
  19. import org.eclipse.jgit.errors.CorruptObjectException;
  20. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  21. import org.eclipse.jgit.errors.MissingObjectException;
  22. import org.eclipse.jgit.errors.RepositoryNotFoundException;
  23. import org.eclipse.jgit.internal.JGitText;
  24. import org.eclipse.jgit.lib.AnyObjectId;
  25. import org.eclipse.jgit.lib.BaseRepositoryBuilder;
  26. import org.eclipse.jgit.lib.BlobBasedConfig;
  27. import org.eclipse.jgit.lib.Config;
  28. import org.eclipse.jgit.lib.ConfigConstants;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.FileMode;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.Ref;
  33. import org.eclipse.jgit.lib.Repository;
  34. import org.eclipse.jgit.lib.RepositoryBuilder;
  35. import org.eclipse.jgit.lib.RepositoryBuilderFactory;
  36. import org.eclipse.jgit.lib.StoredConfig;
  37. import org.eclipse.jgit.storage.file.FileBasedConfig;
  38. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  39. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  40. import org.eclipse.jgit.treewalk.TreeWalk;
  41. import org.eclipse.jgit.treewalk.filter.PathFilter;
  42. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  43. import org.eclipse.jgit.util.FS;

  44. /**
  45.  * Walker that visits all submodule entries found in a tree
  46.  */
  47. public class SubmoduleWalk implements AutoCloseable {

  48.     /**
  49.      * The values for the config parameter submodule.<name>.ignore
  50.      *
  51.      * @since 3.6
  52.      */
  53.     public enum IgnoreSubmoduleMode {
  54.         /**
  55.          * Ignore all modifications to submodules
  56.          */
  57.         ALL,

  58.         /**
  59.          * Ignore changes to the working tree of a submodule
  60.          */
  61.         DIRTY,

  62.         /**
  63.          * Ignore changes to untracked files in the working tree of a submodule
  64.          */
  65.         UNTRACKED,

  66.         /**
  67.          * Ignore nothing. That's the default
  68.          */
  69.         NONE;
  70.     }

  71.     /**
  72.      * Create a generator to walk over the submodule entries currently in the
  73.      * index
  74.      *
  75.      * The {@code .gitmodules} file is read from the index.
  76.      *
  77.      * @param repository
  78.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  79.      * @return generator over submodule index entries. The caller is responsible
  80.      *         for calling {@link #close()}.
  81.      * @throws java.io.IOException
  82.      */
  83.     public static SubmoduleWalk forIndex(Repository repository)
  84.             throws IOException {
  85.         @SuppressWarnings("resource") // The caller closes it
  86.         SubmoduleWalk generator = new SubmoduleWalk(repository);
  87.         try {
  88.             DirCache index = repository.readDirCache();
  89.             generator.setTree(new DirCacheIterator(index));
  90.         } catch (IOException e) {
  91.             generator.close();
  92.             throw e;
  93.         }
  94.         return generator;
  95.     }

  96.     /**
  97.      * Create a generator and advance it to the submodule entry at the given
  98.      * path
  99.      *
  100.      * @param repository
  101.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  102.      * @param treeId
  103.      *            the root of a tree containing both a submodule at the given
  104.      *            path and .gitmodules at the root.
  105.      * @param path
  106.      *            a {@link java.lang.String} object.
  107.      * @return generator at given path. The caller is responsible for calling
  108.      *         {@link #close()}. Null if no submodule at given path.
  109.      * @throws java.io.IOException
  110.      */
  111.     public static SubmoduleWalk forPath(Repository repository,
  112.             AnyObjectId treeId, String path) throws IOException {
  113.         SubmoduleWalk generator = new SubmoduleWalk(repository);
  114.         try {
  115.             generator.setTree(treeId);
  116.             PathFilter filter = PathFilter.create(path);
  117.             generator.setFilter(filter);
  118.             generator.setRootTree(treeId);
  119.             while (generator.next())
  120.                 if (filter.isDone(generator.walk))
  121.                     return generator;
  122.         } catch (IOException e) {
  123.             generator.close();
  124.             throw e;
  125.         }
  126.         generator.close();
  127.         return null;
  128.     }

  129.     /**
  130.      * Create a generator and advance it to the submodule entry at the given
  131.      * path
  132.      *
  133.      * @param repository
  134.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  135.      * @param iterator
  136.      *            the root of a tree containing both a submodule at the given
  137.      *            path and .gitmodules at the root.
  138.      * @param path
  139.      *            a {@link java.lang.String} object.
  140.      * @return generator at given path. The caller is responsible for calling
  141.      *         {@link #close()}. Null if no submodule at given path.
  142.      * @throws java.io.IOException
  143.      */
  144.     public static SubmoduleWalk forPath(Repository repository,
  145.             AbstractTreeIterator iterator, String path) throws IOException {
  146.         SubmoduleWalk generator = new SubmoduleWalk(repository);
  147.         try {
  148.             generator.setTree(iterator);
  149.             PathFilter filter = PathFilter.create(path);
  150.             generator.setFilter(filter);
  151.             generator.setRootTree(iterator);
  152.             while (generator.next())
  153.                 if (filter.isDone(generator.walk))
  154.                     return generator;
  155.         } catch (IOException e) {
  156.             generator.close();
  157.             throw e;
  158.         }
  159.         generator.close();
  160.         return null;
  161.     }

  162.     /**
  163.      * Get submodule directory
  164.      *
  165.      * @param parent
  166.      *            the {@link org.eclipse.jgit.lib.Repository}.
  167.      * @param path
  168.      *            submodule path
  169.      * @return directory
  170.      */
  171.     public static File getSubmoduleDirectory(final Repository parent,
  172.             final String path) {
  173.         return new File(parent.getWorkTree(), path);
  174.     }

  175.     /**
  176.      * Get submodule repository
  177.      *
  178.      * @param parent
  179.      *            the {@link org.eclipse.jgit.lib.Repository}.
  180.      * @param path
  181.      *            submodule path
  182.      * @return repository or null if repository doesn't exist
  183.      * @throws java.io.IOException
  184.      */
  185.     public static Repository getSubmoduleRepository(final Repository parent,
  186.             final String path) throws IOException {
  187.         return getSubmoduleRepository(parent.getWorkTree(), path,
  188.                 parent.getFS());
  189.     }

  190.     /**
  191.      * Get submodule repository at path
  192.      *
  193.      * @param parent
  194.      *            the parent
  195.      * @param path
  196.      *            submodule path
  197.      * @return repository or null if repository doesn't exist
  198.      * @throws java.io.IOException
  199.      */
  200.     public static Repository getSubmoduleRepository(final File parent,
  201.             final String path) throws IOException {
  202.         return getSubmoduleRepository(parent, path, FS.DETECTED);
  203.     }

  204.     /**
  205.      * Get submodule repository at path, using the specified file system
  206.      * abstraction
  207.      *
  208.      * @param parent
  209.      * @param path
  210.      * @param fs
  211.      *            the file system abstraction to be used
  212.      * @return repository or null if repository doesn't exist
  213.      * @throws IOException
  214.      * @since 4.10
  215.      */
  216.     public static Repository getSubmoduleRepository(final File parent,
  217.             final String path, FS fs) throws IOException {
  218.         return getSubmoduleRepository(parent, path, fs,
  219.                 new RepositoryBuilder());
  220.     }

  221.     /**
  222.      * Get submodule repository at path, using the specified file system
  223.      * abstraction and the specified builder
  224.      *
  225.      * @param parent
  226.      *            {@link Repository} that contains the submodule
  227.      * @param path
  228.      *            of the working tree of the submodule
  229.      * @param fs
  230.      *            {@link FS} to use
  231.      * @param builder
  232.      *            {@link BaseRepositoryBuilder} to use to build the submodule
  233.      *            repository
  234.      * @return the {@link Repository} of the submodule, or {@code null} if it
  235.      *         doesn't exist
  236.      * @throws IOException
  237.      *             on errors
  238.      * @since 5.6
  239.      */
  240.     public static Repository getSubmoduleRepository(File parent, String path,
  241.             FS fs, BaseRepositoryBuilder<?, ? extends Repository> builder)
  242.             throws IOException {
  243.         File subWorkTree = new File(parent, path);
  244.         if (!subWorkTree.isDirectory()) {
  245.             return null;
  246.         }
  247.         try {
  248.             return builder //
  249.                     .setMustExist(true) //
  250.                     .setFS(fs) //
  251.                     .setWorkTree(subWorkTree) //
  252.                     .build();
  253.         } catch (RepositoryNotFoundException e) {
  254.             return null;
  255.         }
  256.     }

  257.     /**
  258.      * Resolve submodule repository URL.
  259.      * <p>
  260.      * This handles relative URLs that are typically specified in the
  261.      * '.gitmodules' file by resolving them against the remote URL of the parent
  262.      * repository.
  263.      * <p>
  264.      * Relative URLs will be resolved against the parent repository's working
  265.      * directory if the parent repository has no configured remote URL.
  266.      *
  267.      * @param parent
  268.      *            parent repository
  269.      * @param url
  270.      *            absolute or relative URL of the submodule repository
  271.      * @return resolved URL
  272.      * @throws java.io.IOException
  273.      */
  274.     public static String getSubmoduleRemoteUrl(final Repository parent,
  275.             final String url) throws IOException {
  276.         if (!url.startsWith("./") && !url.startsWith("../")) //$NON-NLS-1$ //$NON-NLS-2$
  277.             return url;

  278.         String remoteName = null;
  279.         // Look up remote URL associated wit HEAD ref
  280.         Ref ref = parent.exactRef(Constants.HEAD);
  281.         if (ref != null) {
  282.             if (ref.isSymbolic())
  283.                 ref = ref.getLeaf();
  284.             remoteName = parent.getConfig().getString(
  285.                     ConfigConstants.CONFIG_BRANCH_SECTION,
  286.                     Repository.shortenRefName(ref.getName()),
  287.                     ConfigConstants.CONFIG_KEY_REMOTE);
  288.         }

  289.         // Fall back to 'origin' if current HEAD ref has no remote URL
  290.         if (remoteName == null)
  291.             remoteName = Constants.DEFAULT_REMOTE_NAME;

  292.         String remoteUrl = parent.getConfig().getString(
  293.                 ConfigConstants.CONFIG_REMOTE_SECTION, remoteName,
  294.                 ConfigConstants.CONFIG_KEY_URL);

  295.         // Fall back to parent repository's working directory if no remote URL
  296.         if (remoteUrl == null) {
  297.             remoteUrl = parent.getWorkTree().getAbsolutePath();
  298.             // Normalize slashes to '/'
  299.             if ('\\' == File.separatorChar)
  300.                 remoteUrl = remoteUrl.replace('\\', '/');
  301.         }

  302.         // Remove trailing '/'
  303.         if (remoteUrl.charAt(remoteUrl.length() - 1) == '/')
  304.             remoteUrl = remoteUrl.substring(0, remoteUrl.length() - 1);

  305.         char separator = '/';
  306.         String submoduleUrl = url;
  307.         while (submoduleUrl.length() > 0) {
  308.             if (submoduleUrl.startsWith("./")) //$NON-NLS-1$
  309.                 submoduleUrl = submoduleUrl.substring(2);
  310.             else if (submoduleUrl.startsWith("../")) { //$NON-NLS-1$
  311.                 int lastSeparator = remoteUrl.lastIndexOf('/');
  312.                 if (lastSeparator < 1) {
  313.                     lastSeparator = remoteUrl.lastIndexOf(':');
  314.                     separator = ':';
  315.                 }
  316.                 if (lastSeparator < 1)
  317.                     throw new IOException(MessageFormat.format(
  318.                             JGitText.get().submoduleParentRemoteUrlInvalid,
  319.                             remoteUrl));
  320.                 remoteUrl = remoteUrl.substring(0, lastSeparator);
  321.                 submoduleUrl = submoduleUrl.substring(3);
  322.             } else
  323.                 break;
  324.         }
  325.         return remoteUrl + separator + submoduleUrl;
  326.     }

  327.     private final Repository repository;

  328.     private final TreeWalk walk;

  329.     private StoredConfig repoConfig;

  330.     private AbstractTreeIterator rootTree;

  331.     private Config modulesConfig;

  332.     private String path;

  333.     private Map<String, String> pathToName;

  334.     private RepositoryBuilderFactory factory;

  335.     /**
  336.      * Create submodule generator
  337.      *
  338.      * @param repository
  339.      *            the {@link org.eclipse.jgit.lib.Repository}.
  340.      * @throws java.io.IOException
  341.      */
  342.     public SubmoduleWalk(Repository repository) throws IOException {
  343.         this.repository = repository;
  344.         repoConfig = repository.getConfig();
  345.         walk = new TreeWalk(repository);
  346.         walk.setRecursive(true);
  347.     }

  348.     /**
  349.      * Set the config used by this walk.
  350.      *
  351.      * This method need only be called if constructing a walk manually instead of
  352.      * with one of the static factory methods above.
  353.      *
  354.      * @param config
  355.      *            .gitmodules config object
  356.      * @return this generator
  357.      */
  358.     public SubmoduleWalk setModulesConfig(Config config) {
  359.         modulesConfig = config;
  360.         loadPathNames();
  361.         return this;
  362.     }

  363.     /**
  364.      * Set the tree used by this walk for finding {@code .gitmodules}.
  365.      * <p>
  366.      * The root tree is not read until the first submodule is encountered by the
  367.      * walk.
  368.      * <p>
  369.      * This method need only be called if constructing a walk manually instead of
  370.      * with one of the static factory methods above.
  371.      *
  372.      * @param tree
  373.      *            tree containing .gitmodules
  374.      * @return this generator
  375.      */
  376.     public SubmoduleWalk setRootTree(AbstractTreeIterator tree) {
  377.         rootTree = tree;
  378.         modulesConfig = null;
  379.         pathToName = null;
  380.         return this;
  381.     }

  382.     /**
  383.      * Set the tree used by this walk for finding {@code .gitmodules}.
  384.      * <p>
  385.      * The root tree is not read until the first submodule is encountered by the
  386.      * walk.
  387.      * <p>
  388.      * This method need only be called if constructing a walk manually instead of
  389.      * with one of the static factory methods above.
  390.      *
  391.      * @param id
  392.      *            ID of a tree containing .gitmodules
  393.      * @return this generator
  394.      * @throws java.io.IOException
  395.      */
  396.     public SubmoduleWalk setRootTree(AnyObjectId id) throws IOException {
  397.         final CanonicalTreeParser p = new CanonicalTreeParser();
  398.         p.reset(walk.getObjectReader(), id);
  399.         rootTree = p;
  400.         modulesConfig = null;
  401.         pathToName = null;
  402.         return this;
  403.     }

  404.     /**
  405.      * Load the config for this walk from {@code .gitmodules}.
  406.      * <p>
  407.      * Uses the root tree if {@link #setRootTree(AbstractTreeIterator)} was
  408.      * previously called, otherwise uses the working tree.
  409.      * <p>
  410.      * If no submodule config is found, loads an empty config.
  411.      *
  412.      * @return this generator
  413.      * @throws java.io.IOException
  414.      *             if an error occurred, or if the repository is bare
  415.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  416.      */
  417.     public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidException {
  418.         if (rootTree == null) {
  419.             File modulesFile = new File(repository.getWorkTree(),
  420.                     Constants.DOT_GIT_MODULES);
  421.             FileBasedConfig config = new FileBasedConfig(modulesFile,
  422.                     repository.getFS());
  423.             config.load();
  424.             modulesConfig = config;
  425.             loadPathNames();
  426.         } else {
  427.             try (TreeWalk configWalk = new TreeWalk(repository)) {
  428.                 configWalk.addTree(rootTree);

  429.                 // The root tree may be part of the submodule walk, so we need to revert
  430.                 // it after this walk.
  431.                 int idx;
  432.                 for (idx = 0; !rootTree.first(); idx++) {
  433.                     rootTree.back(1);
  434.                 }

  435.                 try {
  436.                     configWalk.setRecursive(false);
  437.                     PathFilter filter = PathFilter.create(Constants.DOT_GIT_MODULES);
  438.                     configWalk.setFilter(filter);
  439.                     while (configWalk.next()) {
  440.                         if (filter.isDone(configWalk)) {
  441.                             modulesConfig = new BlobBasedConfig(null, repository,
  442.                                     configWalk.getObjectId(0));
  443.                             loadPathNames();
  444.                             return this;
  445.                         }
  446.                     }
  447.                     modulesConfig = new Config();
  448.                     pathToName = null;
  449.                 } finally {
  450.                     if (idx > 0)
  451.                         rootTree.next(idx);
  452.                 }
  453.             }
  454.         }
  455.         return this;
  456.     }

  457.     private void loadPathNames() {
  458.         pathToName = null;
  459.         if (modulesConfig != null) {
  460.             HashMap<String, String> pathNames = new HashMap<>();
  461.             for (String name : modulesConfig
  462.                     .getSubsections(ConfigConstants.CONFIG_SUBMODULE_SECTION)) {
  463.                 pathNames.put(modulesConfig.getString(
  464.                         ConfigConstants.CONFIG_SUBMODULE_SECTION, name,
  465.                         ConfigConstants.CONFIG_KEY_PATH), name);
  466.             }
  467.             pathToName = pathNames;
  468.         }
  469.     }

  470.     /**
  471.      * Checks whether the working tree contains a .gitmodules file. That's a
  472.      * hint that the repo contains submodules.
  473.      *
  474.      * @param repository
  475.      *            the repository to check
  476.      * @return <code>true</code> if the working tree contains a .gitmodules file,
  477.      *         <code>false</code> otherwise. Always returns <code>false</code>
  478.      *         for bare repositories.
  479.      * @throws java.io.IOException
  480.      * @throws CorruptObjectException if any.
  481.      * @since 3.6
  482.      */
  483.     public static boolean containsGitModulesFile(Repository repository)
  484.             throws IOException {
  485.         if (repository.isBare()) {
  486.             return false;
  487.         }
  488.         File modulesFile = new File(repository.getWorkTree(),
  489.                 Constants.DOT_GIT_MODULES);
  490.         return (modulesFile.exists());
  491.     }

  492.     private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException {
  493.         if (modulesConfig == null) {
  494.             loadModulesConfig();
  495.         }
  496.     }

  497.     private String getModuleName(String modulePath) {
  498.         String name = pathToName != null ? pathToName.get(modulePath) : null;
  499.         return name != null ? name : modulePath;
  500.     }

  501.     /**
  502.      * Set tree filter
  503.      *
  504.      * @param filter
  505.      *            a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object.
  506.      * @return this generator
  507.      */
  508.     public SubmoduleWalk setFilter(TreeFilter filter) {
  509.         walk.setFilter(filter);
  510.         return this;
  511.     }

  512.     /**
  513.      * Set the tree iterator used for finding submodule entries
  514.      *
  515.      * @param iterator
  516.      *            an {@link org.eclipse.jgit.treewalk.AbstractTreeIterator}
  517.      *            object.
  518.      * @return this generator
  519.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  520.      */
  521.     public SubmoduleWalk setTree(AbstractTreeIterator iterator)
  522.             throws CorruptObjectException {
  523.         walk.addTree(iterator);
  524.         return this;
  525.     }

  526.     /**
  527.      * Set the tree used for finding submodule entries
  528.      *
  529.      * @param treeId
  530.      *            an {@link org.eclipse.jgit.lib.AnyObjectId} object.
  531.      * @return this generator
  532.      * @throws java.io.IOException
  533.      * @throws IncorrectObjectTypeException
  534.      *             if any.
  535.      * @throws MissingObjectException
  536.      *             if any.
  537.      */
  538.     public SubmoduleWalk setTree(AnyObjectId treeId) throws IOException {
  539.         walk.addTree(treeId);
  540.         return this;
  541.     }

  542.     /**
  543.      * Reset generator and start new submodule walk
  544.      *
  545.      * @return this generator
  546.      */
  547.     public SubmoduleWalk reset() {
  548.         repoConfig = repository.getConfig();
  549.         modulesConfig = null;
  550.         pathToName = null;
  551.         walk.reset();
  552.         return this;
  553.     }

  554.     /**
  555.      * Get directory that will be the root of the submodule's local repository
  556.      *
  557.      * @return submodule repository directory
  558.      */
  559.     public File getDirectory() {
  560.         return getSubmoduleDirectory(repository, path);
  561.     }

  562.     /**
  563.      * Advance to next submodule in the index tree.
  564.      *
  565.      * The object id and path of the next entry can be obtained by calling
  566.      * {@link #getObjectId()} and {@link #getPath()}.
  567.      *
  568.      * @return true if entry found, false otherwise
  569.      * @throws java.io.IOException
  570.      */
  571.     public boolean next() throws IOException {
  572.         while (walk.next()) {
  573.             if (FileMode.GITLINK != walk.getFileMode(0))
  574.                 continue;
  575.             path = walk.getPathString();
  576.             return true;
  577.         }
  578.         path = null;
  579.         return false;
  580.     }

  581.     /**
  582.      * Get path of current submodule entry
  583.      *
  584.      * @return path
  585.      */
  586.     public String getPath() {
  587.         return path;
  588.     }

  589.     /**
  590.      * Sets the {@link RepositoryBuilderFactory} to use for creating submodule
  591.      * repositories. If none is set, a plain {@link RepositoryBuilder} is used.
  592.      *
  593.      * @param factory
  594.      *            to set
  595.      * @since 5.6
  596.      */
  597.     public void setBuilderFactory(RepositoryBuilderFactory factory) {
  598.         this.factory = factory;
  599.     }

  600.     private BaseRepositoryBuilder<?, ? extends Repository> getBuilder() {
  601.         return factory != null ? factory.get() : new RepositoryBuilder();
  602.     }

  603.     /**
  604.      * The module name for the current submodule entry (used for the section
  605.      * name of .git/config)
  606.      *
  607.      * @since 4.10
  608.      * @return name
  609.      * @throws ConfigInvalidException
  610.      * @throws IOException
  611.      */
  612.     public String getModuleName() throws IOException, ConfigInvalidException {
  613.         lazyLoadModulesConfig();
  614.         return getModuleName(path);
  615.     }

  616.     /**
  617.      * Get object id of current submodule entry
  618.      *
  619.      * @return object id
  620.      */
  621.     public ObjectId getObjectId() {
  622.         return walk.getObjectId(0);
  623.     }

  624.     /**
  625.      * Get the configured path for current entry. This will be the value from
  626.      * the .gitmodules file in the current repository's working tree.
  627.      *
  628.      * @return configured path
  629.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  630.      * @throws java.io.IOException
  631.      */
  632.     public String getModulesPath() throws IOException, ConfigInvalidException {
  633.         lazyLoadModulesConfig();
  634.         return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
  635.                 getModuleName(), ConfigConstants.CONFIG_KEY_PATH);
  636.     }

  637.     /**
  638.      * Get the configured remote URL for current entry. This will be the value
  639.      * from the repository's config.
  640.      *
  641.      * @return configured URL
  642.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  643.      * @throws java.io.IOException
  644.      */
  645.     public String getConfigUrl() throws IOException, ConfigInvalidException {
  646.         return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
  647.                 getModuleName(), ConfigConstants.CONFIG_KEY_URL);
  648.     }

  649.     /**
  650.      * Get the configured remote URL for current entry. This will be the value
  651.      * from the .gitmodules file in the current repository's working tree.
  652.      *
  653.      * @return configured URL
  654.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  655.      * @throws java.io.IOException
  656.      */
  657.     public String getModulesUrl() throws IOException, ConfigInvalidException {
  658.         lazyLoadModulesConfig();
  659.         return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
  660.                 getModuleName(), ConfigConstants.CONFIG_KEY_URL);
  661.     }

  662.     /**
  663.      * Get the configured update field for current entry. This will be the value
  664.      * from the repository's config.
  665.      *
  666.      * @return update value
  667.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  668.      * @throws java.io.IOException
  669.      */
  670.     public String getConfigUpdate() throws IOException, ConfigInvalidException {
  671.         return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
  672.                 getModuleName(), ConfigConstants.CONFIG_KEY_UPDATE);
  673.     }

  674.     /**
  675.      * Get the configured update field for current entry. This will be the value
  676.      * from the .gitmodules file in the current repository's working tree.
  677.      *
  678.      * @return update value
  679.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  680.      * @throws java.io.IOException
  681.      */
  682.     public String getModulesUpdate() throws IOException, ConfigInvalidException {
  683.         lazyLoadModulesConfig();
  684.         return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
  685.                 getModuleName(), ConfigConstants.CONFIG_KEY_UPDATE);
  686.     }

  687.     /**
  688.      * Get the configured ignore field for the current entry. This will be the
  689.      * value from the .gitmodules file in the current repository's working tree.
  690.      *
  691.      * @return ignore value
  692.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  693.      * @throws java.io.IOException
  694.      * @since 3.6
  695.      */
  696.     public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
  697.             ConfigInvalidException {
  698.         IgnoreSubmoduleMode mode = repoConfig.getEnum(
  699.                 IgnoreSubmoduleMode.values(),
  700.                 ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
  701.                 ConfigConstants.CONFIG_KEY_IGNORE, null);
  702.         if (mode != null) {
  703.             return mode;
  704.         }
  705.         lazyLoadModulesConfig();
  706.         return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
  707.                 ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
  708.                 ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE);
  709.     }

  710.     /**
  711.      * Get repository for current submodule entry
  712.      *
  713.      * @return repository or null if non-existent
  714.      * @throws java.io.IOException
  715.      */
  716.     public Repository getRepository() throws IOException {
  717.         return getSubmoduleRepository(repository.getWorkTree(), path,
  718.                 repository.getFS(), getBuilder());
  719.     }

  720.     /**
  721.      * Get commit id that HEAD points to in the current submodule's repository
  722.      *
  723.      * @return object id of HEAD reference
  724.      * @throws java.io.IOException
  725.      */
  726.     public ObjectId getHead() throws IOException {
  727.         try (Repository subRepo = getRepository()) {
  728.             if (subRepo == null) {
  729.                 return null;
  730.             }
  731.             return subRepo.resolve(Constants.HEAD);
  732.         }
  733.     }

  734.     /**
  735.      * Get ref that HEAD points to in the current submodule's repository
  736.      *
  737.      * @return ref name, null on failures
  738.      * @throws java.io.IOException
  739.      */
  740.     public String getHeadRef() throws IOException {
  741.         try (Repository subRepo = getRepository()) {
  742.             if (subRepo == null) {
  743.                 return null;
  744.             }
  745.             Ref head = subRepo.exactRef(Constants.HEAD);
  746.             return head != null ? head.getLeaf().getName() : null;
  747.         }
  748.     }

  749.     /**
  750.      * Get the resolved remote URL for the current submodule.
  751.      * <p>
  752.      * This method resolves the value of {@link #getModulesUrl()} to an absolute
  753.      * URL
  754.      *
  755.      * @return resolved remote URL
  756.      * @throws java.io.IOException
  757.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  758.      */
  759.     public String getRemoteUrl() throws IOException, ConfigInvalidException {
  760.         String url = getModulesUrl();
  761.         return url != null ? getSubmoduleRemoteUrl(repository, url) : null;
  762.     }

  763.     /**
  764.      * {@inheritDoc}
  765.      * <p>
  766.      * Release any resources used by this walker's reader.
  767.      *
  768.      * @since 4.0
  769.      */
  770.     @Override
  771.     public void close() {
  772.         walk.close();
  773.     }
  774. }