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