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