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