View Javadoc
1   /*
2    * Copyright (C) 2008-2010, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.lib;
13  
14  import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES;
15  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
16  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
17  import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
18  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
19  import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
20  import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
21  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
22  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE;
23  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL;
24  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1;
25  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1;
26  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE;
27  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1;
28  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8;
29  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
30  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
31  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
32  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
33  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
34  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
35  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR;
36  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER;
37  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL;
38  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT;
39  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE;
40  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY;
41  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE;
42  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY;
43  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
44  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
45  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE;
46  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME;
47  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
48  import static org.eclipse.jgit.util.Paths.compare;
49  import static org.eclipse.jgit.util.Paths.compareSameName;
50  import static org.eclipse.jgit.util.RawParseUtils.nextLF;
51  import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
52  
53  import java.text.MessageFormat;
54  import java.text.Normalizer;
55  import java.util.ArrayList;
56  import java.util.EnumSet;
57  import java.util.HashSet;
58  import java.util.List;
59  import java.util.Locale;
60  import java.util.Set;
61  
62  import org.eclipse.jgit.annotations.NonNull;
63  import org.eclipse.jgit.annotations.Nullable;
64  import org.eclipse.jgit.errors.CorruptObjectException;
65  import org.eclipse.jgit.internal.JGitText;
66  import org.eclipse.jgit.util.MutableInteger;
67  import org.eclipse.jgit.util.RawParseUtils;
68  import org.eclipse.jgit.util.StringUtils;
69  
70  /**
71   * Verifies that an object is formatted correctly.
72   * <p>
73   * Verifications made by this class only check that the fields of an object are
74   * formatted correctly. The ObjectId checksum of the object is not verified, and
75   * connectivity links between objects are also not verified. Its assumed that
76   * the caller can provide both of these validations on its own.
77   * <p>
78   * Instances of this class are not thread safe, but they may be reused to
79   * perform multiple object validations, calling {@link #reset()} between them to
80   * clear the internal state (e.g. {@link #getGitsubmodules()})
81   */
82  public class ObjectChecker {
83  	/** Header "tree " */
84  	public static final byte[] tree = Constants.encodeASCII("tree "); //$NON-NLS-1$
85  
86  	/** Header "parent " */
87  	public static final byte[] parent = Constants.encodeASCII("parent "); //$NON-NLS-1$
88  
89  	/** Header "author " */
90  	public static final byte[] author = Constants.encodeASCII("author "); //$NON-NLS-1$
91  
92  	/** Header "committer " */
93  	public static final byte[] committer = Constants.encodeASCII("committer "); //$NON-NLS-1$
94  
95  	/** Header "encoding " */
96  	public static final byte[] encoding = Constants.encodeASCII("encoding "); //$NON-NLS-1$
97  
98  	/** Header "object " */
99  	public static final byte[] object = Constants.encodeASCII("object "); //$NON-NLS-1$
100 
101 	/** Header "type " */
102 	public static final byte[] type = Constants.encodeASCII("type "); //$NON-NLS-1$
103 
104 	/** Header "tag " */
105 	public static final byte[] tag = Constants.encodeASCII("tag "); //$NON-NLS-1$
106 
107 	/** Header "tagger " */
108 	public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
109 
110 	/** Path ".gitmodules" */
111 	private static final byte[] dotGitmodules = Constants.encodeASCII(DOT_GIT_MODULES);
112 
113 	/**
114 	 * Potential issues identified by the checker.
115 	 *
116 	 * @since 4.2
117 	 */
118 	public enum ErrorType {
119 		// @formatter:off
120 		// These names match git-core so that fsck section keys also match.
121 		/***/ NULL_SHA1,
122 		/***/ DUPLICATE_ENTRIES,
123 		/***/ TREE_NOT_SORTED,
124 		/***/ ZERO_PADDED_FILEMODE,
125 		/***/ EMPTY_NAME,
126 		/***/ FULL_PATHNAME,
127 		/***/ HAS_DOT,
128 		/***/ HAS_DOTDOT,
129 		/***/ HAS_DOTGIT,
130 		/***/ BAD_OBJECT_SHA1,
131 		/***/ BAD_PARENT_SHA1,
132 		/***/ BAD_TREE_SHA1,
133 		/***/ MISSING_AUTHOR,
134 		/***/ MISSING_COMMITTER,
135 		/***/ MISSING_OBJECT,
136 		/***/ MISSING_TREE,
137 		/***/ MISSING_TYPE_ENTRY,
138 		/***/ MISSING_TAG_ENTRY,
139 		/***/ BAD_DATE,
140 		/***/ BAD_EMAIL,
141 		/***/ BAD_TIMEZONE,
142 		/***/ MISSING_EMAIL,
143 		/***/ MISSING_SPACE_BEFORE_DATE,
144 		/** @since 5.2 */ GITMODULES_BLOB,
145 		/** @since 5.2 */ GITMODULES_LARGE,
146 		/** @since 5.2 */ GITMODULES_NAME,
147 		/** @since 5.2 */ GITMODULES_PARSE,
148 		/** @since 5.2 */ GITMODULES_PATH,
149 		/** @since 5.2 */ GITMODULES_SYMLINK,
150 		/** @since 5.2 */ GITMODULES_URL,
151 		/***/ UNKNOWN_TYPE,
152 
153 		// These are unique to JGit.
154 		/***/ WIN32_BAD_NAME,
155 		/***/ BAD_UTF8;
156 		// @formatter:on
157 
158 		/** @return camelCaseVersion of the name. */
159 		public String getMessageId() {
160 			String n = name();
161 			StringBuilder r = new StringBuilder(n.length());
162 			for (int i = 0; i < n.length(); i++) {
163 				char c = n.charAt(i);
164 				if (c != '_') {
165 					r.append(StringUtils.toLowerCase(c));
166 				} else {
167 					r.append(n.charAt(++i));
168 				}
169 			}
170 			return r.toString();
171 		}
172 	}
173 
174 	private final MutableObjectIdhtml#MutableObjectId">MutableObjectId tempId = new MutableObjectId();
175 	private final MutableInteger.html#MutableInteger">MutableInteger bufPtr = new MutableInteger();
176 
177 	private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class);
178 	private ObjectIdSet skipList;
179 	private boolean allowInvalidPersonIdent;
180 	private boolean windows;
181 	private boolean macosx;
182 
183 	private final List<GitmoduleEntry> gitsubmodules = new ArrayList<>();
184 
185 	/**
186 	 * Enable accepting specific malformed (but not horribly broken) objects.
187 	 *
188 	 * @param objects
189 	 *            collection of object names known to be broken in a non-fatal
190 	 *            way that should be ignored by the checker.
191 	 * @return {@code this}
192 	 * @since 4.2
193 	 */
194 	public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) {
195 		skipList = objects;
196 		return this;
197 	}
198 
199 	/**
200 	 * Configure error types to be ignored across all objects.
201 	 *
202 	 * @param ids
203 	 *            error types to ignore. The caller's set is copied.
204 	 * @return {@code this}
205 	 * @since 4.2
206 	 */
207 	public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) {
208 		errors = EnumSet.allOf(ErrorType.class);
209 		if (ids != null) {
210 			errors.removeAll(ids);
211 		}
212 		return this;
213 	}
214 
215 	/**
216 	 * Add message type to be ignored across all objects.
217 	 *
218 	 * @param id
219 	 *            error type to ignore.
220 	 * @param ignore
221 	 *            true to ignore this error; false to treat the error as an
222 	 *            error and throw.
223 	 * @return {@code this}
224 	 * @since 4.2
225 	 */
226 	public ObjectChecker setIgnore(ErrorType id, boolean ignore) {
227 		if (ignore) {
228 			errors.remove(id);
229 		} else {
230 			errors.add(id);
231 		}
232 		return this;
233 	}
234 
235 	/**
236 	 * Enable accepting leading zero mode in tree entries.
237 	 * <p>
238 	 * Some broken Git libraries generated leading zeros in the mode part of
239 	 * tree entries. This is technically incorrect but gracefully allowed by
240 	 * git-core. JGit rejects such trees by default, but may need to accept
241 	 * them on broken histories.
242 	 * <p>
243 	 * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}.
244 	 *
245 	 * @param allow allow leading zero mode.
246 	 * @return {@code this}.
247 	 * @since 3.4
248 	 */
249 	public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
250 		return setIgnore(ZERO_PADDED_FILEMODE, allow);
251 	}
252 
253 	/**
254 	 * Enable accepting invalid author, committer and tagger identities.
255 	 * <p>
256 	 * Some broken Git versions/libraries allowed users to create commits and
257 	 * tags with invalid formatting between the name, email and timestamp.
258 	 *
259 	 * @param allow
260 	 *            if true accept invalid person identity strings.
261 	 * @return {@code this}.
262 	 * @since 4.0
263 	 */
264 	public ObjectChecker setAllowInvalidPersonIdent(boolean allow) {
265 		allowInvalidPersonIdent = allow;
266 		return this;
267 	}
268 
269 	/**
270 	 * Restrict trees to only names legal on Windows platforms.
271 	 * <p>
272 	 * Also rejects any mixed case forms of reserved names ({@code .git}).
273 	 *
274 	 * @param win true if Windows name checking should be performed.
275 	 * @return {@code this}.
276 	 * @since 3.4
277 	 */
278 	public ObjectChecker setSafeForWindows(boolean win) {
279 		windows = win;
280 		return this;
281 	}
282 
283 	/**
284 	 * Restrict trees to only names legal on Mac OS X platforms.
285 	 * <p>
286 	 * Rejects any mixed case forms of reserved names ({@code .git})
287 	 * for users working on HFS+ in case-insensitive (default) mode.
288 	 *
289 	 * @param mac true if Mac OS X name checking should be performed.
290 	 * @return {@code this}.
291 	 * @since 3.4
292 	 */
293 	public ObjectChecker setSafeForMacOS(boolean mac) {
294 		macosx = mac;
295 		return this;
296 	}
297 
298 	/**
299 	 * Check an object for parsing errors.
300 	 *
301 	 * @param objType
302 	 *            type of the object. Must be a valid object type code in
303 	 *            {@link org.eclipse.jgit.lib.Constants}.
304 	 * @param raw
305 	 *            the raw data which comprises the object. This should be in the
306 	 *            canonical format (that is the format used to generate the
307 	 *            ObjectId of the object). The array is never modified.
308 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
309 	 *             if an error is identified.
310 	 */
311 	public void check(int objType, byte[] raw)
312 			throws CorruptObjectException {
313 		check(idFor(objType, raw), objType, raw);
314 	}
315 
316 	/**
317 	 * Check an object for parsing errors.
318 	 *
319 	 * @param id
320 	 *            identify of the object being checked.
321 	 * @param objType
322 	 *            type of the object. Must be a valid object type code in
323 	 *            {@link org.eclipse.jgit.lib.Constants}.
324 	 * @param raw
325 	 *            the raw data which comprises the object. This should be in the
326 	 *            canonical format (that is the format used to generate the
327 	 *            ObjectId of the object). The array is never modified.
328 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
329 	 *             if an error is identified.
330 	 * @since 4.2
331 	 */
332 	public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
333 			throws CorruptObjectException {
334 		switch (objType) {
335 		case OBJ_COMMIT:
336 			checkCommit(id, raw);
337 			break;
338 		case OBJ_TAG:
339 			checkTag(id, raw);
340 			break;
341 		case OBJ_TREE:
342 			checkTree(id, raw);
343 			break;
344 		case OBJ_BLOB:
345 			BlobObjectChecker checker = newBlobObjectChecker();
346 			if (checker == null) {
347 				checkBlob(raw);
348 			} else {
349 				checker.update(raw, 0, raw.length);
350 				checker.endBlob(id);
351 			}
352 			break;
353 		default:
354 			report(UNKNOWN_TYPE, id, MessageFormat.format(
355 					JGitText.get().corruptObjectInvalidType2,
356 					Integer.valueOf(objType)));
357 		}
358 	}
359 
360 	private boolean checkId(byte[] raw) {
361 		int p = bufPtr.value;
362 		try {
363 			tempId.fromString(raw, p);
364 		} catch (IllegalArgumentException e) {
365 			bufPtr.value = nextLF(raw, p);
366 			return false;
367 		}
368 
369 		p += OBJECT_ID_STRING_LENGTH;
370 		if (raw[p] == '\n') {
371 			bufPtr.value = p + 1;
372 			return true;
373 		}
374 		bufPtr.value = nextLF(raw, p);
375 		return false;
376 	}
377 
378 	private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
379 			throws CorruptObjectException {
380 		if (allowInvalidPersonIdent) {
381 			bufPtr.value = nextLF(raw, bufPtr.value);
382 			return;
383 		}
384 
385 		final int emailB = nextLF(raw, bufPtr.value, '<');
386 		if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
387 			report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
388 			bufPtr.value = nextLF(raw, bufPtr.value);
389 			return;
390 		}
391 
392 		final int emailE = nextLF(raw, emailB, '>');
393 		if (emailE == emailB || raw[emailE - 1] != '>') {
394 			report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
395 			bufPtr.value = nextLF(raw, bufPtr.value);
396 			return;
397 		}
398 		if (emailE == raw.length || raw[emailE] != ' ') {
399 			report(MISSING_SPACE_BEFORE_DATE, id,
400 					JGitText.get().corruptObjectBadDate);
401 			bufPtr.value = nextLF(raw, bufPtr.value);
402 			return;
403 		}
404 
405 		parseBase10(raw, emailE + 1, bufPtr); // when
406 		if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
407 				|| raw[bufPtr.value] != ' ') {
408 			report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
409 			bufPtr.value = nextLF(raw, bufPtr.value);
410 			return;
411 		}
412 
413 		int p = bufPtr.value + 1;
414 		parseBase10(raw, p, bufPtr); // tz offset
415 		if (p == bufPtr.value) {
416 			report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
417 			bufPtr.value = nextLF(raw, bufPtr.value);
418 			return;
419 		}
420 
421 		p = bufPtr.value;
422 		if (raw[p] == '\n') {
423 			bufPtr.value = p + 1;
424 		} else {
425 			report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
426 			bufPtr.value = nextLF(raw, p);
427 		}
428 	}
429 
430 	/**
431 	 * Check a commit for errors.
432 	 *
433 	 * @param raw
434 	 *            the commit data. The array is never modified.
435 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
436 	 *             if any error was detected.
437 	 */
438 	public void checkCommit(byte[] raw) throws CorruptObjectException {
439 		checkCommit(idFor(OBJ_COMMIT, raw), raw);
440 	}
441 
442 	/**
443 	 * Check a commit for errors.
444 	 *
445 	 * @param id
446 	 *            identity of the object being checked.
447 	 * @param raw
448 	 *            the commit data. The array is never modified.
449 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
450 	 *             if any error was detected.
451 	 * @since 4.2
452 	 */
453 	public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
454 			throws CorruptObjectException {
455 		bufPtr.value = 0;
456 
457 		if (!match(raw, tree)) {
458 			report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
459 		} else if (!checkId(raw)) {
460 			report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
461 		}
462 
463 		while (match(raw, parent)) {
464 			if (!checkId(raw)) {
465 				report(BAD_PARENT_SHA1, id,
466 						JGitText.get().corruptObjectInvalidParent);
467 			}
468 		}
469 
470 		if (match(raw, author)) {
471 			checkPersonIdent(raw, id);
472 		} else {
473 			report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
474 		}
475 
476 		if (match(raw, committer)) {
477 			checkPersonIdent(raw, id);
478 		} else {
479 			report(MISSING_COMMITTER, id,
480 					JGitText.get().corruptObjectNoCommitter);
481 		}
482 	}
483 
484 	/**
485 	 * Check an annotated tag for errors.
486 	 *
487 	 * @param raw
488 	 *            the tag data. The array is never modified.
489 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
490 	 *             if any error was detected.
491 	 */
492 	public void checkTag(byte[] raw) throws CorruptObjectException {
493 		checkTag(idFor(OBJ_TAG, raw), raw);
494 	}
495 
496 	/**
497 	 * Check an annotated tag for errors.
498 	 *
499 	 * @param id
500 	 *            identity of the object being checked.
501 	 * @param raw
502 	 *            the tag data. The array is never modified.
503 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
504 	 *             if any error was detected.
505 	 * @since 4.2
506 	 */
507 	public void checkTag(@Nullable AnyObjectId id, byte[] raw)
508 			throws CorruptObjectException {
509 		bufPtr.value = 0;
510 		if (!match(raw, object)) {
511 			report(MISSING_OBJECT, id,
512 					JGitText.get().corruptObjectNoObjectHeader);
513 		} else if (!checkId(raw)) {
514 			report(BAD_OBJECT_SHA1, id,
515 					JGitText.get().corruptObjectInvalidObject);
516 		}
517 
518 		if (!match(raw, type)) {
519 			report(MISSING_TYPE_ENTRY, id,
520 					JGitText.get().corruptObjectNoTypeHeader);
521 		}
522 		bufPtr.value = nextLF(raw, bufPtr.value);
523 
524 		if (!match(raw, tag)) {
525 			report(MISSING_TAG_ENTRY, id,
526 					JGitText.get().corruptObjectNoTagHeader);
527 		}
528 		bufPtr.value = nextLF(raw, bufPtr.value);
529 
530 		if (match(raw, tagger)) {
531 			checkPersonIdent(raw, id);
532 		}
533 	}
534 
535 	private static boolean duplicateName(final byte[] raw,
536 			final int thisNamePos, final int thisNameEnd) {
537 		final int sz = raw.length;
538 		int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
539 		for (;;) {
540 			int nextMode = 0;
541 			for (;;) {
542 				if (nextPtr >= sz)
543 					return false;
544 				final byte c = raw[nextPtr++];
545 				if (' ' == c)
546 					break;
547 				nextMode <<= 3;
548 				nextMode += c - '0';
549 			}
550 
551 			final int nextNamePos = nextPtr;
552 			for (;;) {
553 				if (nextPtr == sz)
554 					return false;
555 				final byte c = raw[nextPtr++];
556 				if (c == 0)
557 					break;
558 			}
559 			if (nextNamePos + 1 == nextPtr)
560 				return false;
561 
562 			int cmp = compareSameName(
563 					raw, thisNamePos, thisNameEnd,
564 					raw, nextNamePos, nextPtr - 1, nextMode);
565 			if (cmp < 0)
566 				return false;
567 			else if (cmp == 0)
568 				return true;
569 
570 			nextPtr += Constants.OBJECT_ID_LENGTH;
571 		}
572 	}
573 
574 	/**
575 	 * Check a canonical formatted tree for errors.
576 	 *
577 	 * @param raw
578 	 *            the raw tree data. The array is never modified.
579 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
580 	 *             if any error was detected.
581 	 */
582 	public void checkTree(byte[] raw) throws CorruptObjectException {
583 		checkTree(idFor(OBJ_TREE, raw), raw);
584 	}
585 
586 	/**
587 	 * Check a canonical formatted tree for errors.
588 	 *
589 	 * @param id
590 	 *            identity of the object being checked.
591 	 * @param raw
592 	 *            the raw tree data. The array is never modified.
593 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
594 	 *             if any error was detected.
595 	 * @since 4.2
596 	 */
597 	public void checkTree(@Nullable AnyObjectId id, byte[] raw)
598 			throws CorruptObjectException {
599 		final int sz = raw.length;
600 		int ptr = 0;
601 		int lastNameB = 0, lastNameE = 0, lastMode = 0;
602 		Set<String> normalized = windows || macosx
603 				? new HashSet<>()
604 				: null;
605 
606 		while (ptr < sz) {
607 			int thisMode = 0;
608 			for (;;) {
609 				if (ptr == sz) {
610 					throw new CorruptObjectException(
611 							JGitText.get().corruptObjectTruncatedInMode);
612 				}
613 				final byte c = raw[ptr++];
614 				if (' ' == c)
615 					break;
616 				if (c < '0' || c > '7') {
617 					throw new CorruptObjectException(
618 							JGitText.get().corruptObjectInvalidModeChar);
619 				}
620 				if (thisMode == 0 && c == '0') {
621 					report(ZERO_PADDED_FILEMODE, id,
622 							JGitText.get().corruptObjectInvalidModeStartsZero);
623 				}
624 				thisMode <<= 3;
625 				thisMode += c - '0';
626 			}
627 
628 			if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
629 				throw new CorruptObjectException(MessageFormat.format(
630 						JGitText.get().corruptObjectInvalidMode2,
631 						Integer.valueOf(thisMode)));
632 			}
633 
634 			final int thisNameB = ptr;
635 			ptr = scanPathSegment(raw, ptr, sz, id);
636 			if (ptr == sz || raw[ptr] != 0) {
637 				throw new CorruptObjectException(
638 						JGitText.get().corruptObjectTruncatedInName);
639 			}
640 			checkPathSegment2(raw, thisNameB, ptr, id);
641 			if (normalized != null) {
642 				if (!normalized.add(normalize(raw, thisNameB, ptr))) {
643 					report(DUPLICATE_ENTRIES, id,
644 							JGitText.get().corruptObjectDuplicateEntryNames);
645 				}
646 			} else if (duplicateName(raw, thisNameB, ptr)) {
647 				report(DUPLICATE_ENTRIES, id,
648 						JGitText.get().corruptObjectDuplicateEntryNames);
649 			}
650 
651 			if (lastNameB != 0) {
652 				int cmp = compare(
653 						raw, lastNameB, lastNameE, lastMode,
654 						raw, thisNameB, ptr, thisMode);
655 				if (cmp > 0) {
656 					report(TREE_NOT_SORTED, id,
657 							JGitText.get().corruptObjectIncorrectSorting);
658 				}
659 			}
660 
661 			lastNameB = thisNameB;
662 			lastNameE = ptr;
663 			lastMode = thisMode;
664 
665 			ptr += 1 + OBJECT_ID_LENGTH;
666 			if (ptr > sz) {
667 				throw new CorruptObjectException(
668 						JGitText.get().corruptObjectTruncatedInObjectId);
669 			}
670 
671 			if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
672 				report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
673 			}
674 
675 			if (id != null && isGitmodules(raw, lastNameB, lastNameE, id)) {
676 				ObjectId blob = ObjectId.fromRaw(raw, ptr - OBJECT_ID_LENGTH);
677 				gitsubmodules.add(new GitmoduleEntry(id, blob));
678 			}
679 		}
680 	}
681 
682 	private int scanPathSegment(byte[] raw, int ptr, int end,
683 			@Nullable AnyObjectId id) throws CorruptObjectException {
684 		for (; ptr < end; ptr++) {
685 			byte c = raw[ptr];
686 			if (c == 0) {
687 				return ptr;
688 			}
689 			if (c == '/') {
690 				report(FULL_PATHNAME, id,
691 						JGitText.get().corruptObjectNameContainsSlash);
692 			}
693 			if (windows && isInvalidOnWindows(c)) {
694 				if (c > 31) {
695 					throw new CorruptObjectException(String.format(
696 							JGitText.get().corruptObjectNameContainsChar,
697 							Byte.valueOf(c)));
698 				}
699 				throw new CorruptObjectException(String.format(
700 						JGitText.get().corruptObjectNameContainsByte,
701 						Integer.valueOf(c & 0xff)));
702 			}
703 		}
704 		return ptr;
705 	}
706 
707 	@Nullable
708 	private ObjectId idFor(int objType, byte[] raw) {
709 		if (skipList != null) {
710 			try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
711 				return fmt.idFor(objType, raw);
712 			}
713 		}
714 		return null;
715 	}
716 
717 	private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
718 			String why) throws CorruptObjectException {
719 		if (errors.contains(err)
720 				&& (id == null || skipList == null || !skipList.contains(id))) {
721 			if (id != null) {
722 				throw new CorruptObjectException(err, id, why);
723 			}
724 			throw new CorruptObjectException(why);
725 		}
726 	}
727 
728 	/**
729 	 * Check tree path entry for validity.
730 	 * <p>
731 	 * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a
732 	 * multi-directory path string such as {@code "src/main.c"}.
733 	 *
734 	 * @param path
735 	 *            path string to scan.
736 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
737 	 *             path is invalid.
738 	 * @since 3.6
739 	 */
740 	public void checkPath(String path) throws CorruptObjectException {
741 		byte[] buf = Constants.encode(path);
742 		checkPath(buf, 0, buf.length);
743 	}
744 
745 	/**
746 	 * Check tree path entry for validity.
747 	 * <p>
748 	 * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a
749 	 * multi-directory path string such as {@code "src/main.c"}.
750 	 *
751 	 * @param raw
752 	 *            buffer to scan.
753 	 * @param ptr
754 	 *            offset to first byte of the name.
755 	 * @param end
756 	 *            offset to one past last byte of name.
757 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
758 	 *             path is invalid.
759 	 * @since 3.6
760 	 */
761 	public void checkPath(byte[] raw, int ptr, int end)
762 			throws CorruptObjectException {
763 		int start = ptr;
764 		for (; ptr < end; ptr++) {
765 			if (raw[ptr] == '/') {
766 				checkPathSegment(raw, start, ptr);
767 				start = ptr + 1;
768 			}
769 		}
770 		checkPathSegment(raw, start, end);
771 	}
772 
773 	/**
774 	 * Check tree path entry for validity.
775 	 *
776 	 * @param raw
777 	 *            buffer to scan.
778 	 * @param ptr
779 	 *            offset to first byte of the name.
780 	 * @param end
781 	 *            offset to one past last byte of name.
782 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
783 	 *             name is invalid.
784 	 * @since 3.4
785 	 */
786 	public void checkPathSegment(byte[] raw, int ptr, int end)
787 			throws CorruptObjectException {
788 		int e = scanPathSegment(raw, ptr, end, null);
789 		if (e < end && raw[e] == 0)
790 			throw new CorruptObjectException(
791 					JGitText.get().corruptObjectNameContainsNullByte);
792 		checkPathSegment2(raw, ptr, end, null);
793 	}
794 
795 	private void checkPathSegment2(byte[] raw, int ptr, int end,
796 			@Nullable AnyObjectId id) throws CorruptObjectException {
797 		if (ptr == end) {
798 			report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
799 			return;
800 		}
801 
802 		if (raw[ptr] == '.') {
803 			switch (end - ptr) {
804 			case 1:
805 				report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
806 				break;
807 			case 2:
808 				if (raw[ptr + 1] == '.') {
809 					report(HAS_DOTDOT, id,
810 							JGitText.get().corruptObjectNameDotDot);
811 				}
812 				break;
813 			case 4:
814 				if (isGit(raw, ptr + 1)) {
815 					report(HAS_DOTGIT, id, String.format(
816 							JGitText.get().corruptObjectInvalidName,
817 							RawParseUtils.decode(raw, ptr, end)));
818 				}
819 				break;
820 			default:
821 				if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
822 					report(HAS_DOTGIT, id, String.format(
823 							JGitText.get().corruptObjectInvalidName,
824 							RawParseUtils.decode(raw, ptr, end)));
825 				}
826 			}
827 		} else if (isGitTilde1(raw, ptr, end)) {
828 			report(HAS_DOTGIT, id, String.format(
829 					JGitText.get().corruptObjectInvalidName,
830 					RawParseUtils.decode(raw, ptr, end)));
831 		}
832 		if (macosx && isMacHFSGit(raw, ptr, end, id)) {
833 			report(HAS_DOTGIT, id, String.format(
834 					JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
835 					RawParseUtils.decode(raw, ptr, end)));
836 		}
837 
838 		if (windows) {
839 			// Windows ignores space and dot at end of file name.
840 			if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
841 				report(WIN32_BAD_NAME, id, String.format(
842 						JGitText.get().corruptObjectInvalidNameEnd,
843 						Character.valueOf(((char) raw[end - 1]))));
844 			}
845 			if (end - ptr >= 3) {
846 				checkNotWindowsDevice(raw, ptr, end, id);
847 			}
848 		}
849 	}
850 
851 	// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
852 	// to ".git" therefore we should prevent such names
853 	private boolean isMacHFSPath(byte[] raw, int ptr, int end, byte[] path,
854 			@Nullable AnyObjectId id) throws CorruptObjectException {
855 		boolean ignorable = false;
856 		int g = 0;
857 		while (ptr < end) {
858 			switch (raw[ptr]) {
859 			case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
860 				if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
861 					return false;
862 				}
863 				switch (raw[ptr + 1]) {
864 				case (byte) 0x80:
865 					switch (raw[ptr + 2]) {
866 					case (byte) 0x8c:	// U+200C 0xe2808c ZERO WIDTH NON-JOINER
867 					case (byte) 0x8d:	// U+200D 0xe2808d ZERO WIDTH JOINER
868 					case (byte) 0x8e:	// U+200E 0xe2808e LEFT-TO-RIGHT MARK
869 					case (byte) 0x8f:	// U+200F 0xe2808f RIGHT-TO-LEFT MARK
870 					case (byte) 0xaa:	// U+202A 0xe280aa LEFT-TO-RIGHT EMBEDDING
871 					case (byte) 0xab:	// U+202B 0xe280ab RIGHT-TO-LEFT EMBEDDING
872 					case (byte) 0xac:	// U+202C 0xe280ac POP DIRECTIONAL FORMATTING
873 					case (byte) 0xad:	// U+202D 0xe280ad LEFT-TO-RIGHT OVERRIDE
874 					case (byte) 0xae:	// U+202E 0xe280ae RIGHT-TO-LEFT OVERRIDE
875 						ignorable = true;
876 						ptr += 3;
877 						continue;
878 					default:
879 						return false;
880 					}
881 				case (byte) 0x81:
882 					switch (raw[ptr + 2]) {
883 					case (byte) 0xaa:	// U+206A 0xe281aa INHIBIT SYMMETRIC SWAPPING
884 					case (byte) 0xab:	// U+206B 0xe281ab ACTIVATE SYMMETRIC SWAPPING
885 					case (byte) 0xac:	// U+206C 0xe281ac INHIBIT ARABIC FORM SHAPING
886 					case (byte) 0xad:	// U+206D 0xe281ad ACTIVATE ARABIC FORM SHAPING
887 					case (byte) 0xae:	// U+206E 0xe281ae NATIONAL DIGIT SHAPES
888 					case (byte) 0xaf:	// U+206F 0xe281af NOMINAL DIGIT SHAPES
889 						ignorable = true;
890 						ptr += 3;
891 						continue;
892 					default:
893 						return false;
894 					}
895 				default:
896 					return false;
897 				}
898 			case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
899 				if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
900 					return false;
901 				}
902 				// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
903 				if ((raw[ptr + 1] == (byte) 0xbb)
904 						&& (raw[ptr + 2] == (byte) 0xbf)) {
905 					ignorable = true;
906 					ptr += 3;
907 					continue;
908 				}
909 				return false;
910 			default:
911 				if (g == path.length) {
912 					return false;
913 				}
914 				if (toLower(raw[ptr++]) != path[g++]) {
915 					return false;
916 				}
917 			}
918 		}
919 		if (g == path.length && ignorable) {
920 			return true;
921 		}
922 		return false;
923 	}
924 
925 	private boolean isMacHFSGit(byte[] raw, int ptr, int end,
926 			@Nullable AnyObjectId id) throws CorruptObjectException {
927 		byte[] git = new byte[] { '.', 'g', 'i', 't' };
928 		return isMacHFSPath(raw, ptr, end, git, id);
929 	}
930 
931 	private boolean isMacHFSGitmodules(byte[] raw, int ptr, int end,
932 			@Nullable AnyObjectId id) throws CorruptObjectException {
933 		return isMacHFSPath(raw, ptr, end, dotGitmodules, id);
934 	}
935 
936 	private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
937 			@Nullable AnyObjectId id) throws CorruptObjectException {
938 		if ((ptr + 2) >= end) {
939 			report(BAD_UTF8, id, MessageFormat.format(
940 					JGitText.get().corruptObjectInvalidNameInvalidUtf8,
941 					toHexString(raw, ptr, end)));
942 			return false;
943 		}
944 		return true;
945 	}
946 
947 	private static String toHexString(byte[] raw, int ptr, int end) {
948 		StringBuilder b = new StringBuilder("0x"); //$NON-NLS-1$
949 		for (int i = ptr; i < end; i++)
950 			b.append(String.format("%02x", Byte.valueOf(raw[i]))); //$NON-NLS-1$
951 		return b.toString();
952 	}
953 
954 	private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
955 			@Nullable AnyObjectId id) throws CorruptObjectException {
956 		switch (toLower(raw[ptr])) {
957 		case 'a': // AUX
958 			if (end - ptr >= 3
959 					&& toLower(raw[ptr + 1]) == 'u'
960 					&& toLower(raw[ptr + 2]) == 'x'
961 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
962 				report(WIN32_BAD_NAME, id,
963 						JGitText.get().corruptObjectInvalidNameAux);
964 			}
965 			break;
966 
967 		case 'c': // CON, COM[1-9]
968 			if (end - ptr >= 3
969 					&& toLower(raw[ptr + 2]) == 'n'
970 					&& toLower(raw[ptr + 1]) == 'o'
971 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
972 				report(WIN32_BAD_NAME, id,
973 						JGitText.get().corruptObjectInvalidNameCon);
974 			}
975 			if (end - ptr >= 4
976 					&& toLower(raw[ptr + 2]) == 'm'
977 					&& toLower(raw[ptr + 1]) == 'o'
978 					&& isPositiveDigit(raw[ptr + 3])
979 					&& (end - ptr == 4 || raw[ptr + 4] == '.')) {
980 				report(WIN32_BAD_NAME, id, String.format(
981 						JGitText.get().corruptObjectInvalidNameCom,
982 						Character.valueOf(((char) raw[ptr + 3]))));
983 			}
984 			break;
985 
986 		case 'l': // LPT[1-9]
987 			if (end - ptr >= 4
988 					&& toLower(raw[ptr + 1]) == 'p'
989 					&& toLower(raw[ptr + 2]) == 't'
990 					&& isPositiveDigit(raw[ptr + 3])
991 					&& (end - ptr == 4 || raw[ptr + 4] == '.')) {
992 				report(WIN32_BAD_NAME, id, String.format(
993 						JGitText.get().corruptObjectInvalidNameLpt,
994 						Character.valueOf(((char) raw[ptr + 3]))));
995 			}
996 			break;
997 
998 		case 'n': // NUL
999 			if (end - ptr >= 3
1000 					&& toLower(raw[ptr + 1]) == 'u'
1001 					&& toLower(raw[ptr + 2]) == 'l'
1002 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
1003 				report(WIN32_BAD_NAME, id,
1004 						JGitText.get().corruptObjectInvalidNameNul);
1005 			}
1006 			break;
1007 
1008 		case 'p': // PRN
1009 			if (end - ptr >= 3
1010 					&& toLower(raw[ptr + 1]) == 'r'
1011 					&& toLower(raw[ptr + 2]) == 'n'
1012 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
1013 				report(WIN32_BAD_NAME, id,
1014 						JGitText.get().corruptObjectInvalidNamePrn);
1015 			}
1016 			break;
1017 		}
1018 	}
1019 
1020 	private static boolean isInvalidOnWindows(byte c) {
1021 		// Windows disallows "special" characters in a path component.
1022 		switch (c) {
1023 		case '"':
1024 		case '*':
1025 		case ':':
1026 		case '<':
1027 		case '>':
1028 		case '?':
1029 		case '\\':
1030 		case '|':
1031 			return true;
1032 		}
1033 		return 1 <= c && c <= 31;
1034 	}
1035 
1036 	private static boolean isGit(byte[] buf, int p) {
1037 		return toLower(buf[p]) == 'g'
1038 				&& toLower(buf[p + 1]) == 'i'
1039 				&& toLower(buf[p + 2]) == 't';
1040 	}
1041 
1042 	/**
1043 	 * Check if the filename contained in buf[start:end] could be read as a
1044 	 * .gitmodules file when checked out to the working directory.
1045 	 *
1046 	 * This ought to be a simple comparison, but some filesystems have peculiar
1047 	 * rules for normalizing filenames:
1048 	 *
1049 	 * NTFS has backward-compatibility support for 8.3 synonyms of long file
1050 	 * names (see
1051 	 * https://web.archive.org/web/20160318181041/https://usn.pw/blog/gen/2015/06/09/filenames/
1052 	 * for details). NTFS is also case-insensitive.
1053 	 *
1054 	 * MacOS's HFS+ folds away ignorable Unicode characters in addition to case
1055 	 * folding.
1056 	 *
1057 	 * @param buf
1058 	 *            byte array to decode
1059 	 * @param start
1060 	 *            position where a supposed filename is starting
1061 	 * @param end
1062 	 *            position where a supposed filename is ending
1063 	 * @param id
1064 	 *            object id for error reporting
1065 	 *
1066 	 * @return true if the filename in buf could be a ".gitmodules" file
1067 	 * @throws CorruptObjectException
1068 	 */
1069 	private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id)
1070 			throws CorruptObjectException {
1071 		// Simple cases first.
1072 		if (end - start < 8) {
1073 			return false;
1074 		}
1075 		return (end - start == dotGitmodules.length
1076 				&& RawParseUtils.match(buf, start, dotGitmodules) != -1)
1077 			|| (macosx && isMacHFSGitmodules(buf, start, end, id))
1078 			|| (windows && isNTFSGitmodules(buf, start, end));
1079 	}
1080 
1081 	private boolean matchLowerCase(byte[] b, int ptr, byte[] src) {
1082 		if (ptr + src.length > b.length) {
1083 			return false;
1084 		}
1085 		for (int i = 0; i < src.length; i++, ptr++) {
1086 			if (toLower(b[ptr]) != src[i]) {
1087 				return false;
1088 			}
1089 		}
1090 		return true;
1091 	}
1092 
1093 	// .gitmodules, case-insensitive, or an 8.3 abbreviation of the same.
1094 	private boolean isNTFSGitmodules(byte[] buf, int start, int end) {
1095 		if (end - start == 11) {
1096 			return matchLowerCase(buf, start, dotGitmodules);
1097 		}
1098 
1099 		if (end - start != 8) {
1100 			return false;
1101 		}
1102 
1103 		// "gitmod" or a prefix of "gi7eba", followed by...
1104 		byte[] gitmod = new byte[]{'g', 'i', 't', 'm', 'o', 'd', '~'};
1105 		if (matchLowerCase(buf, start, gitmod)) {
1106 			start += 6;
1107 		} else {
1108 			byte[] gi7eba = new byte[]{'g', 'i', '7', 'e', 'b', 'a'};
1109 			for (int i = 0; i < gi7eba.length; i++, start++) {
1110 				byte c = (byte) toLower(buf[start]);
1111 				if (c == '~') {
1112 					break;
1113 				}
1114 				if (c != gi7eba[i]) {
1115 					return false;
1116 				}
1117 			}
1118 		}
1119 
1120 		// ... ~ and a number
1121 		if (end - start < 2) {
1122 			return false;
1123 		}
1124 		if (buf[start] != '~') {
1125 			return false;
1126 		}
1127 		start++;
1128 		if (buf[start] < '1' || buf[start] > '9') {
1129 			return false;
1130 		}
1131 		start++;
1132 		for (; start != end; start++) {
1133 			if (buf[start] < '0' || buf[start] > '9') {
1134 				return false;
1135 			}
1136 		}
1137 		return true;
1138 	}
1139 
1140 	private static boolean isGitTilde1(byte[] buf, int p, int end) {
1141 		if (end - p != 5)
1142 			return false;
1143 		return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
1144 				&& toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
1145 				&& buf[p + 4] == '1';
1146 	}
1147 
1148 	private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
1149 		if (isGit(raw, ptr)) {
1150 			int dots = 0;
1151 			boolean space = false;
1152 			int p = end - 1;
1153 			for (; (ptr + 2) < p; p--) {
1154 				if (raw[p] == '.')
1155 					dots++;
1156 				else if (raw[p] == ' ')
1157 					space = true;
1158 				else
1159 					break;
1160 			}
1161 			return p == ptr + 2 && (dots == 1 || space);
1162 		}
1163 		return false;
1164 	}
1165 
1166 	private boolean match(byte[] b, byte[] src) {
1167 		int r = RawParseUtils.match(b, bufPtr.value, src);
1168 		if (r < 0) {
1169 			return false;
1170 		}
1171 		bufPtr.value = r;
1172 		return true;
1173 	}
1174 
1175 	private static char toLower(byte b) {
1176 		if ('A' <= b && b <= 'Z')
1177 			return (char) (b + ('a' - 'A'));
1178 		return (char) b;
1179 	}
1180 
1181 	private static boolean isPositiveDigit(byte b) {
1182 		return '1' <= b && b <= '9';
1183 	}
1184 
1185 	/**
1186 	 * Create a new {@link org.eclipse.jgit.lib.BlobObjectChecker}.
1187 	 *
1188 	 * @return new BlobObjectChecker or null if it's not provided.
1189 	 * @since 4.9
1190 	 */
1191 	@Nullable
1192 	public BlobObjectChecker newBlobObjectChecker() {
1193 		return null;
1194 	}
1195 
1196 	/**
1197 	 * Check a blob for errors.
1198 	 *
1199 	 * <p>
1200 	 * This may not be called from PackParser in some cases. Use
1201 	 * {@link #newBlobObjectChecker} instead.
1202 	 *
1203 	 * @param raw
1204 	 *            the blob data. The array is never modified.
1205 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
1206 	 *             if any error was detected.
1207 	 */
1208 	public void checkBlob(byte[] raw) throws CorruptObjectException {
1209 		// We can always assume the blob is valid.
1210 	}
1211 
1212 	private String normalize(byte[] raw, int ptr, int end) {
1213 		String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
1214 		return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
1215 	}
1216 
1217 	/**
1218 	 * Get the list of ".gitmodules" files found in the pack. For each, report
1219 	 * its blob id (e.g. to validate its contents) and the tree where it was
1220 	 * found (e.g. to check if it is in the root)
1221 	 *
1222 	 * @return List of pairs of ids {@literal <tree, blob>}.
1223 	 *
1224 	 * @since 4.7.5
1225 	 */
1226 	public List<GitmoduleEntry> getGitsubmodules() {
1227 		return gitsubmodules;
1228 	}
1229 
1230 	/**
1231 	 * Reset the invocation-specific state from this instance. Specifically this
1232 	 * clears the list of .gitmodules files encountered (see
1233 	 * {@link #getGitsubmodules()})
1234 	 *
1235 	 * Configurations like errors to filter, skip lists or the specified O.S.
1236 	 * (set via {@link #setSafeForMacOS(boolean)} or
1237 	 * {@link #setSafeForWindows(boolean)}) are NOT cleared.
1238 	 *
1239 	 * @since 5.2
1240 	 */
1241 	public void reset() {
1242 		gitsubmodules.clear();
1243 	}
1244 }