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