View Javadoc
1   /*
2    * Copyright (C) 2011, GitHub Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.submodule;
44  
45  import java.io.File;
46  import java.io.IOException;
47  import java.text.MessageFormat;
48  import java.util.HashMap;
49  import java.util.Map;
50  
51  import org.eclipse.jgit.dircache.DirCache;
52  import org.eclipse.jgit.dircache.DirCacheIterator;
53  import org.eclipse.jgit.errors.ConfigInvalidException;
54  import org.eclipse.jgit.errors.CorruptObjectException;
55  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
56  import org.eclipse.jgit.errors.MissingObjectException;
57  import org.eclipse.jgit.errors.RepositoryNotFoundException;
58  import org.eclipse.jgit.internal.JGitText;
59  import org.eclipse.jgit.lib.AnyObjectId;
60  import org.eclipse.jgit.lib.BlobBasedConfig;
61  import org.eclipse.jgit.lib.Config;
62  import org.eclipse.jgit.lib.ConfigConstants;
63  import org.eclipse.jgit.lib.Constants;
64  import org.eclipse.jgit.lib.FileMode;
65  import org.eclipse.jgit.lib.ObjectId;
66  import org.eclipse.jgit.lib.Ref;
67  import org.eclipse.jgit.lib.Repository;
68  import org.eclipse.jgit.lib.RepositoryBuilder;
69  import org.eclipse.jgit.lib.StoredConfig;
70  import org.eclipse.jgit.storage.file.FileBasedConfig;
71  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
72  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
73  import org.eclipse.jgit.treewalk.TreeWalk;
74  import org.eclipse.jgit.treewalk.filter.PathFilter;
75  import org.eclipse.jgit.treewalk.filter.TreeFilter;
76  import org.eclipse.jgit.util.FS;
77  
78  /**
79   * Walker that visits all submodule entries found in a tree
80   */
81  public class SubmoduleWalk implements AutoCloseable {
82  
83  	/**
84  	 * The values for the config parameter submodule.<name>.ignore
85  	 *
86  	 * @since 3.6
87  	 */
88  	public enum IgnoreSubmoduleMode {
89  		/**
90  		 * Ignore all modifications to submodules
91  		 */
92  		ALL,
93  
94  		/**
95  		 * Ignore changes to the working tree of a submodule
96  		 */
97  		DIRTY,
98  
99  		/**
100 		 * Ignore changes to untracked files in the working tree of a submodule
101 		 */
102 		UNTRACKED,
103 
104 		/**
105 		 * Ignore nothing. That's the default
106 		 */
107 		NONE;
108 	}
109 
110 	/**
111 	 * Create a generator to walk over the submodule entries currently in the
112 	 * index
113 	 *
114 	 * The {@code .gitmodules} file is read from the index.
115 	 *
116 	 * @param repository
117 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
118 	 * @return generator over submodule index entries. The caller is responsible
119 	 *         for calling {@link #close()}.
120 	 * @throws java.io.IOException
121 	 */
122 	public static SubmoduleWalk forIndex(Repository repository)
123 			throws IOException {
124 		@SuppressWarnings("resource") // The caller closes it
125 		SubmoduleWalk generator = new SubmoduleWalk(repository);
126 		try {
127 			DirCache index = repository.readDirCache();
128 			generator.setTree(new DirCacheIterator(index));
129 		} catch (IOException e) {
130 			generator.close();
131 			throw e;
132 		}
133 		return generator;
134 	}
135 
136 	/**
137 	 * Create a generator and advance it to the submodule entry at the given
138 	 * path
139 	 *
140 	 * @param repository
141 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
142 	 * @param treeId
143 	 *            the root of a tree containing both a submodule at the given
144 	 *            path and .gitmodules at the root.
145 	 * @param path
146 	 *            a {@link java.lang.String} object.
147 	 * @return generator at given path. The caller is responsible for calling
148 	 *         {@link #close()}. Null if no submodule at given path.
149 	 * @throws java.io.IOException
150 	 */
151 	public static SubmoduleWalk forPath(Repository repository,
152 			AnyObjectId treeId, String path) throws IOException {
153 		SubmoduleWalk generator = new SubmoduleWalk(repository);
154 		try {
155 			generator.setTree(treeId);
156 			PathFilter filter = PathFilter.create(path);
157 			generator.setFilter(filter);
158 			generator.setRootTree(treeId);
159 			while (generator.next())
160 				if (filter.isDone(generator.walk))
161 					return generator;
162 		} catch (IOException e) {
163 			generator.close();
164 			throw e;
165 		}
166 		generator.close();
167 		return null;
168 	}
169 
170 	/**
171 	 * Create a generator and advance it to the submodule entry at the given
172 	 * path
173 	 *
174 	 * @param repository
175 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
176 	 * @param iterator
177 	 *            the root of a tree containing both a submodule at the given
178 	 *            path and .gitmodules at the root.
179 	 * @param path
180 	 *            a {@link java.lang.String} object.
181 	 * @return generator at given path. The caller is responsible for calling
182 	 *         {@link #close()}. Null if no submodule at given path.
183 	 * @throws java.io.IOException
184 	 */
185 	public static SubmoduleWalk forPath(Repository repository,
186 			AbstractTreeIterator iterator, String path) throws IOException {
187 		SubmoduleWalk generator = new SubmoduleWalk(repository);
188 		try {
189 			generator.setTree(iterator);
190 			PathFilter filter = PathFilter.create(path);
191 			generator.setFilter(filter);
192 			generator.setRootTree(iterator);
193 			while (generator.next())
194 				if (filter.isDone(generator.walk))
195 					return generator;
196 		} catch (IOException e) {
197 			generator.close();
198 			throw e;
199 		}
200 		generator.close();
201 		return null;
202 	}
203 
204 	/**
205 	 * Get submodule directory
206 	 *
207 	 * @param parent
208 	 *            the {@link org.eclipse.jgit.lib.Repository}.
209 	 * @param path
210 	 *            submodule path
211 	 * @return directory
212 	 */
213 	public static File getSubmoduleDirectory(final Repository parent,
214 			final String path) {
215 		return new File(parent.getWorkTree(), path);
216 	}
217 
218 	/**
219 	 * Get submodule repository
220 	 *
221 	 * @param parent
222 	 *            the {@link org.eclipse.jgit.lib.Repository}.
223 	 * @param path
224 	 *            submodule path
225 	 * @return repository or null if repository doesn't exist
226 	 * @throws java.io.IOException
227 	 */
228 	public static Repository>Repository getSubmoduleRepository(final Repository parent,
229 			final String path) throws IOException {
230 		return getSubmoduleRepository(parent.getWorkTree(), path,
231 				parent.getFS());
232 	}
233 
234 	/**
235 	 * Get submodule repository at path
236 	 *
237 	 * @param parent
238 	 *            the parent
239 	 * @param path
240 	 *            submodule path
241 	 * @return repository or null if repository doesn't exist
242 	 * @throws java.io.IOException
243 	 */
244 	public static Repository getSubmoduleRepository(final File parent,
245 			final String path) throws IOException {
246 		return getSubmoduleRepository(parent, path, FS.DETECTED);
247 	}
248 
249 	/**
250 	 * Get submodule repository at path, using the specified file system
251 	 * abstraction
252 	 *
253 	 * @param parent
254 	 * @param path
255 	 * @param fs
256 	 *            the file system abstraction to be used
257 	 * @return repository or null if repository doesn't exist
258 	 * @throws IOException
259 	 * @since 4.10
260 	 */
261 	public static Repository getSubmoduleRepository(final File parent,
262 			final String path, FS fs) throws IOException {
263 		File subWorkTree = new File(parent, path);
264 		if (!subWorkTree.isDirectory())
265 			return null;
266 		File workTree = new File(parent, path);
267 		try {
268 			return new RepositoryBuilder() //
269 					.setMustExist(true) //
270 					.setFS(fs) //
271 					.setWorkTree(workTree) //
272 					.build();
273 		} catch (RepositoryNotFoundException e) {
274 			return null;
275 		}
276 	}
277 
278 	/**
279 	 * Resolve submodule repository URL.
280 	 * <p>
281 	 * This handles relative URLs that are typically specified in the
282 	 * '.gitmodules' file by resolving them against the remote URL of the parent
283 	 * repository.
284 	 * <p>
285 	 * Relative URLs will be resolved against the parent repository's working
286 	 * directory if the parent repository has no configured remote URL.
287 	 *
288 	 * @param parent
289 	 *            parent repository
290 	 * @param url
291 	 *            absolute or relative URL of the submodule repository
292 	 * @return resolved URL
293 	 * @throws java.io.IOException
294 	 */
295 	public static String getSubmoduleRemoteUrl(final Repository parent,
296 			final String url) throws IOException {
297 		if (!url.startsWith("./") && !url.startsWith("../")) //$NON-NLS-1$ //$NON-NLS-2$
298 			return url;
299 
300 		String remoteName = null;
301 		// Look up remote URL associated wit HEAD ref
302 		Ref ref = parent.exactRef(Constants.HEAD);
303 		if (ref != null) {
304 			if (ref.isSymbolic())
305 				ref = ref.getLeaf();
306 			remoteName = parent.getConfig().getString(
307 					ConfigConstants.CONFIG_BRANCH_SECTION,
308 					Repository.shortenRefName(ref.getName()),
309 					ConfigConstants.CONFIG_KEY_REMOTE);
310 		}
311 
312 		// Fall back to 'origin' if current HEAD ref has no remote URL
313 		if (remoteName == null)
314 			remoteName = Constants.DEFAULT_REMOTE_NAME;
315 
316 		String remoteUrl = parent.getConfig().getString(
317 				ConfigConstants.CONFIG_REMOTE_SECTION, remoteName,
318 				ConfigConstants.CONFIG_KEY_URL);
319 
320 		// Fall back to parent repository's working directory if no remote URL
321 		if (remoteUrl == null) {
322 			remoteUrl = parent.getWorkTree().getAbsolutePath();
323 			// Normalize slashes to '/'
324 			if ('\\' == File.separatorChar)
325 				remoteUrl = remoteUrl.replace('\\', '/');
326 		}
327 
328 		// Remove trailing '/'
329 		if (remoteUrl.charAt(remoteUrl.length() - 1) == '/')
330 			remoteUrl = remoteUrl.substring(0, remoteUrl.length() - 1);
331 
332 		char separator = '/';
333 		String submoduleUrl = url;
334 		while (submoduleUrl.length() > 0) {
335 			if (submoduleUrl.startsWith("./")) //$NON-NLS-1$
336 				submoduleUrl = submoduleUrl.substring(2);
337 			else if (submoduleUrl.startsWith("../")) { //$NON-NLS-1$
338 				int lastSeparator = remoteUrl.lastIndexOf('/');
339 				if (lastSeparator < 1) {
340 					lastSeparator = remoteUrl.lastIndexOf(':');
341 					separator = ':';
342 				}
343 				if (lastSeparator < 1)
344 					throw new IOException(MessageFormat.format(
345 							JGitText.get().submoduleParentRemoteUrlInvalid,
346 							remoteUrl));
347 				remoteUrl = remoteUrl.substring(0, lastSeparator);
348 				submoduleUrl = submoduleUrl.substring(3);
349 			} else
350 				break;
351 		}
352 		return remoteUrl + separator + submoduleUrl;
353 	}
354 
355 	private final Repository repository;
356 
357 	private final TreeWalk walk;
358 
359 	private StoredConfig repoConfig;
360 
361 	private AbstractTreeIterator rootTree;
362 
363 	private Config modulesConfig;
364 
365 	private String path;
366 
367 	private Map<String, String> pathToName;
368 
369 	/**
370 	 * Create submodule generator
371 	 *
372 	 * @param repository
373 	 *            the {@link org.eclipse.jgit.lib.Repository}.
374 	 * @throws java.io.IOException
375 	 */
376 	public SubmoduleWalk(Repository repository) throws IOException {
377 		this.repository = repository;
378 		repoConfig = repository.getConfig();
379 		walk = new TreeWalk(repository);
380 		walk.setRecursive(true);
381 	}
382 
383 	/**
384 	 * Set the config used by this walk.
385 	 *
386 	 * This method need only be called if constructing a walk manually instead of
387 	 * with one of the static factory methods above.
388 	 *
389 	 * @param config
390 	 *            .gitmodules config object
391 	 * @return this generator
392 	 */
393 	public SubmoduleWalk setModulesConfig(Config config) {
394 		modulesConfig = config;
395 		loadPathNames();
396 		return this;
397 	}
398 
399 	/**
400 	 * Set the tree used by this walk for finding {@code .gitmodules}.
401 	 * <p>
402 	 * The root tree is not read until the first submodule is encountered by the
403 	 * walk.
404 	 * <p>
405 	 * This method need only be called if constructing a walk manually instead of
406 	 * with one of the static factory methods above.
407 	 *
408 	 * @param tree
409 	 *            tree containing .gitmodules
410 	 * @return this generator
411 	 */
412 	public SubmoduleWalk setRootTree(AbstractTreeIterator tree) {
413 		rootTree = tree;
414 		modulesConfig = null;
415 		pathToName = null;
416 		return this;
417 	}
418 
419 	/**
420 	 * Set the tree used by this walk for finding {@code .gitmodules}.
421 	 * <p>
422 	 * The root tree is not read until the first submodule is encountered by the
423 	 * walk.
424 	 * <p>
425 	 * This method need only be called if constructing a walk manually instead of
426 	 * with one of the static factory methods above.
427 	 *
428 	 * @param id
429 	 *            ID of a tree containing .gitmodules
430 	 * @return this generator
431 	 * @throws java.io.IOException
432 	 */
433 	public SubmoduleWalk setRootTree(AnyObjectId id) throws IOException {
434 		final CanonicalTreeParserreeParser.html#CanonicalTreeParser">CanonicalTreeParser p = new CanonicalTreeParser();
435 		p.reset(walk.getObjectReader(), id);
436 		rootTree = p;
437 		modulesConfig = null;
438 		pathToName = null;
439 		return this;
440 	}
441 
442 	/**
443 	 * Load the config for this walk from {@code .gitmodules}.
444 	 * <p>
445 	 * Uses the root tree if {@link #setRootTree(AbstractTreeIterator)} was
446 	 * previously called, otherwise uses the working tree.
447 	 * <p>
448 	 * If no submodule config is found, loads an empty config.
449 	 *
450 	 * @return this generator
451 	 * @throws java.io.IOException
452 	 *             if an error occurred, or if the repository is bare
453 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
454 	 */
455 	public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidException {
456 		if (rootTree == null) {
457 			File modulesFile = new File(repository.getWorkTree(),
458 					Constants.DOT_GIT_MODULES);
459 			FileBasedConfig config = new FileBasedConfig(modulesFile,
460 					repository.getFS());
461 			config.load();
462 			modulesConfig = config;
463 			loadPathNames();
464 		} else {
465 			try (TreeWalk.html#TreeWalk">TreeWalk configWalk = new TreeWalk(repository)) {
466 				configWalk.addTree(rootTree);
467 
468 				// The root tree may be part of the submodule walk, so we need to revert
469 				// it after this walk.
470 				int idx;
471 				for (idx = 0; !rootTree.first(); idx++) {
472 					rootTree.back(1);
473 				}
474 
475 				try {
476 					configWalk.setRecursive(false);
477 					PathFilter filter = PathFilter.create(Constants.DOT_GIT_MODULES);
478 					configWalk.setFilter(filter);
479 					while (configWalk.next()) {
480 						if (filter.isDone(configWalk)) {
481 							modulesConfig = new BlobBasedConfig(null, repository,
482 									configWalk.getObjectId(0));
483 							loadPathNames();
484 							return this;
485 						}
486 					}
487 					modulesConfig = new Config();
488 					pathToName = null;
489 				} finally {
490 					if (idx > 0)
491 						rootTree.next(idx);
492 				}
493 			}
494 		}
495 		return this;
496 	}
497 
498 	private void loadPathNames() {
499 		pathToName = null;
500 		if (modulesConfig != null) {
501 			HashMap<String, String> pathNames = new HashMap<>();
502 			for (String name : modulesConfig
503 					.getSubsections(ConfigConstants.CONFIG_SUBMODULE_SECTION)) {
504 				pathNames.put(modulesConfig.getString(
505 						ConfigConstants.CONFIG_SUBMODULE_SECTION, name,
506 						ConfigConstants.CONFIG_KEY_PATH), name);
507 			}
508 			pathToName = pathNames;
509 		}
510 	}
511 
512 	/**
513 	 * Checks whether the working tree contains a .gitmodules file. That's a
514 	 * hint that the repo contains submodules.
515 	 *
516 	 * @param repository
517 	 *            the repository to check
518 	 * @return <code>true</code> if the working tree contains a .gitmodules file,
519 	 *         <code>false</code> otherwise. Always returns <code>false</code>
520 	 *         for bare repositories.
521 	 * @throws java.io.IOException
522 	 * @throws CorruptObjectException if any.
523 	 * @since 3.6
524 	 */
525 	public static boolean containsGitModulesFile(Repository repository)
526 			throws IOException {
527 		if (repository.isBare()) {
528 			return false;
529 		}
530 		File modulesFile = new File(repository.getWorkTree(),
531 				Constants.DOT_GIT_MODULES);
532 		return (modulesFile.exists());
533 	}
534 
535 	private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException {
536 		if (modulesConfig == null) {
537 			loadModulesConfig();
538 		}
539 	}
540 
541 	private String getModuleName(String modulePath) {
542 		String name = pathToName != null ? pathToName.get(modulePath) : null;
543 		return name != null ? name : modulePath;
544 	}
545 
546 	/**
547 	 * Set tree filter
548 	 *
549 	 * @param filter
550 	 *            a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object.
551 	 * @return this generator
552 	 */
553 	public SubmoduleWalk setFilter(TreeFilter filter) {
554 		walk.setFilter(filter);
555 		return this;
556 	}
557 
558 	/**
559 	 * Set the tree iterator used for finding submodule entries
560 	 *
561 	 * @param iterator
562 	 *            an {@link org.eclipse.jgit.treewalk.AbstractTreeIterator}
563 	 *            object.
564 	 * @return this generator
565 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
566 	 */
567 	public SubmoduleWalk setTree(AbstractTreeIterator iterator)
568 			throws CorruptObjectException {
569 		walk.addTree(iterator);
570 		return this;
571 	}
572 
573 	/**
574 	 * Set the tree used for finding submodule entries
575 	 *
576 	 * @param treeId
577 	 *            an {@link org.eclipse.jgit.lib.AnyObjectId} object.
578 	 * @return this generator
579 	 * @throws java.io.IOException
580 	 * @throws IncorrectObjectTypeException
581 	 *             if any.
582 	 * @throws MissingObjectException
583 	 *             if any.
584 	 */
585 	public SubmoduleWalk setTree(AnyObjectId treeId) throws IOException {
586 		walk.addTree(treeId);
587 		return this;
588 	}
589 
590 	/**
591 	 * Reset generator and start new submodule walk
592 	 *
593 	 * @return this generator
594 	 */
595 	public SubmoduleWalk reset() {
596 		repoConfig = repository.getConfig();
597 		modulesConfig = null;
598 		pathToName = null;
599 		walk.reset();
600 		return this;
601 	}
602 
603 	/**
604 	 * Get directory that will be the root of the submodule's local repository
605 	 *
606 	 * @return submodule repository directory
607 	 */
608 	public File getDirectory() {
609 		return getSubmoduleDirectory(repository, path);
610 	}
611 
612 	/**
613 	 * Advance to next submodule in the index tree.
614 	 *
615 	 * The object id and path of the next entry can be obtained by calling
616 	 * {@link #getObjectId()} and {@link #getPath()}.
617 	 *
618 	 * @return true if entry found, false otherwise
619 	 * @throws java.io.IOException
620 	 */
621 	public boolean next() throws IOException {
622 		while (walk.next()) {
623 			if (FileMode.GITLINK != walk.getFileMode(0))
624 				continue;
625 			path = walk.getPathString();
626 			return true;
627 		}
628 		path = null;
629 		return false;
630 	}
631 
632 	/**
633 	 * Get path of current submodule entry
634 	 *
635 	 * @return path
636 	 */
637 	public String getPath() {
638 		return path;
639 	}
640 
641 	/**
642 	 * The module name for the current submodule entry (used for the section name of .git/config)
643 	 * @since 4.10
644 	 * @return name
645 	 */
646 	public String getModuleName() {
647 		return getModuleName(path);
648 	}
649 
650 	/**
651 	 * Get object id of current submodule entry
652 	 *
653 	 * @return object id
654 	 */
655 	public ObjectId getObjectId() {
656 		return walk.getObjectId(0);
657 	}
658 
659 	/**
660 	 * Get the configured path for current entry. This will be the value from
661 	 * the .gitmodules file in the current repository's working tree.
662 	 *
663 	 * @return configured path
664 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
665 	 * @throws java.io.IOException
666 	 */
667 	public String getModulesPath() throws IOException, ConfigInvalidException {
668 		lazyLoadModulesConfig();
669 		return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
670 				getModuleName(), ConfigConstants.CONFIG_KEY_PATH);
671 	}
672 
673 	/**
674 	 * Get the configured remote URL for current entry. This will be the value
675 	 * from the repository's config.
676 	 *
677 	 * @return configured URL
678 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
679 	 * @throws java.io.IOException
680 	 */
681 	public String getConfigUrl() throws IOException, ConfigInvalidException {
682 		return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
683 				getModuleName(), ConfigConstants.CONFIG_KEY_URL);
684 	}
685 
686 	/**
687 	 * Get the configured remote URL for current entry. This will be the value
688 	 * from the .gitmodules file in the current repository's working tree.
689 	 *
690 	 * @return configured URL
691 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
692 	 * @throws java.io.IOException
693 	 */
694 	public String getModulesUrl() throws IOException, ConfigInvalidException {
695 		lazyLoadModulesConfig();
696 		return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
697 				getModuleName(), ConfigConstants.CONFIG_KEY_URL);
698 	}
699 
700 	/**
701 	 * Get the configured update field for current entry. This will be the value
702 	 * from the repository's config.
703 	 *
704 	 * @return update value
705 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
706 	 * @throws java.io.IOException
707 	 */
708 	public String getConfigUpdate() throws IOException, ConfigInvalidException {
709 		return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
710 				getModuleName(), ConfigConstants.CONFIG_KEY_UPDATE);
711 	}
712 
713 	/**
714 	 * Get the configured update field for current entry. This will be the value
715 	 * from the .gitmodules file in the current repository's working tree.
716 	 *
717 	 * @return update value
718 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
719 	 * @throws java.io.IOException
720 	 */
721 	public String getModulesUpdate() throws IOException, ConfigInvalidException {
722 		lazyLoadModulesConfig();
723 		return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
724 				getModuleName(), ConfigConstants.CONFIG_KEY_UPDATE);
725 	}
726 
727 	/**
728 	 * Get the configured ignore field for the current entry. This will be the
729 	 * value from the .gitmodules file in the current repository's working tree.
730 	 *
731 	 * @return ignore value
732 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
733 	 * @throws java.io.IOException
734 	 * @since 3.6
735 	 */
736 	public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
737 			ConfigInvalidException {
738 		lazyLoadModulesConfig();
739 		return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
740 				ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
741 				ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE);
742 	}
743 
744 	/**
745 	 * Get repository for current submodule entry
746 	 *
747 	 * @return repository or null if non-existent
748 	 * @throws java.io.IOException
749 	 */
750 	public Repository getRepository() throws IOException {
751 		return getSubmoduleRepository(repository, path);
752 	}
753 
754 	/**
755 	 * Get commit id that HEAD points to in the current submodule's repository
756 	 *
757 	 * @return object id of HEAD reference
758 	 * @throws java.io.IOException
759 	 */
760 	public ObjectId getHead() throws IOException {
761 		try (Repository subRepo = getRepository()) {
762 			if (subRepo == null) {
763 				return null;
764 			}
765 			return subRepo.resolve(Constants.HEAD);
766 		}
767 	}
768 
769 	/**
770 	 * Get ref that HEAD points to in the current submodule's repository
771 	 *
772 	 * @return ref name, null on failures
773 	 * @throws java.io.IOException
774 	 */
775 	public String getHeadRef() throws IOException {
776 		try (Repository subRepo = getRepository()) {
777 			if (subRepo == null) {
778 				return null;
779 			}
780 			Ref head = subRepo.exactRef(Constants.HEAD);
781 			return head != null ? head.getLeaf().getName() : null;
782 		}
783 	}
784 
785 	/**
786 	 * Get the resolved remote URL for the current submodule.
787 	 * <p>
788 	 * This method resolves the value of {@link #getModulesUrl()} to an absolute
789 	 * URL
790 	 *
791 	 * @return resolved remote URL
792 	 * @throws java.io.IOException
793 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
794 	 */
795 	public String getRemoteUrl() throws IOException, ConfigInvalidException {
796 		String url = getModulesUrl();
797 		return url != null ? getSubmoduleRemoteUrl(repository, url) : null;
798 	}
799 
800 	/**
801 	 * {@inheritDoc}
802 	 * <p>
803 	 * Release any resources used by this walker's reader.
804 	 *
805 	 * @since 4.0
806 	 */
807 	@Override
808 	public void close() {
809 		walk.close();
810 	}
811 }