View Javadoc
1   /*
2    * Copyright (C) 2009, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
15  import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
16  
17  import java.io.BufferedReader;
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.nio.file.Files;
23  import java.nio.file.NoSuchFileException;
24  import java.nio.file.StandardCopyOption;
25  import java.text.MessageFormat;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Objects;
35  import java.util.Set;
36  import java.util.concurrent.atomic.AtomicReference;
37  
38  import org.eclipse.jgit.errors.CorruptObjectException;
39  import org.eclipse.jgit.errors.PackInvalidException;
40  import org.eclipse.jgit.errors.PackMismatchException;
41  import org.eclipse.jgit.internal.JGitText;
42  import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
43  import org.eclipse.jgit.internal.storage.pack.PackExt;
44  import org.eclipse.jgit.internal.storage.pack.PackWriter;
45  import org.eclipse.jgit.lib.AbbreviatedObjectId;
46  import org.eclipse.jgit.lib.AnyObjectId;
47  import org.eclipse.jgit.lib.Config;
48  import org.eclipse.jgit.lib.ConfigConstants;
49  import org.eclipse.jgit.lib.Constants;
50  import org.eclipse.jgit.lib.ObjectDatabase;
51  import org.eclipse.jgit.lib.ObjectId;
52  import org.eclipse.jgit.lib.ObjectLoader;
53  import org.eclipse.jgit.lib.RepositoryCache;
54  import org.eclipse.jgit.lib.RepositoryCache.FileKey;
55  import org.eclipse.jgit.util.FS;
56  import org.eclipse.jgit.util.FileUtils;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  /**
61   * Traditional file system based {@link org.eclipse.jgit.lib.ObjectDatabase}.
62   * <p>
63   * This is the classical object database representation for a Git repository,
64   * where objects are stored loose by hashing them into directories by their
65   * {@link org.eclipse.jgit.lib.ObjectId}, or are stored in compressed containers
66   * known as {@link org.eclipse.jgit.internal.storage.file.PackFile}s.
67   * <p>
68   * Optionally an object database can reference one or more alternates; other
69   * ObjectDatabase instances that are searched in addition to the current
70   * database.
71   * <p>
72   * Databases are divided into two halves: a half that is considered to be fast
73   * to search (the {@code PackFile}s), and a half that is considered to be slow
74   * to search (loose objects). When alternates are present the fast half is fully
75   * searched (recursively through all alternates) before the slow half is
76   * considered.
77   */
78  public class ObjectDirectory extends FileObjectDatabase {
79  	private static final Logger LOG = LoggerFactory
80  			.getLogger(ObjectDirectory.class);
81  
82  	private static final PackList NO_PACKS = new PackList(
83  			FileSnapshot.DIRTY, new PackFile[0]);
84  
85  	/** Maximum number of candidates offered as resolutions of abbreviation. */
86  	private static final int RESOLVE_ABBREV_LIMIT = 256;
87  
88  	private final AlternateHandle handle = new AlternateHandle(this);
89  
90  	private final Config config;
91  
92  	private final File objects;
93  
94  	private final File infoDirectory;
95  
96  	private final File packDirectory;
97  
98  	private final File preservedDirectory;
99  
100 	private final File alternatesFile;
101 
102 	private final FS fs;
103 
104 	private final AtomicReference<AlternateHandle[]> alternates;
105 
106 	private final UnpackedObjectCache unpackedObjectCache;
107 
108 	private final File shallowFile;
109 
110 	private FileSnapshot shallowFileSnapshot = FileSnapshot.DIRTY;
111 
112 	private Set<ObjectId> shallowCommitsIds;
113 
114 	final AtomicReference<PackList> packList;
115 
116 	/**
117 	 * Initialize a reference to an on-disk object directory.
118 	 *
119 	 * @param cfg
120 	 *            configuration this directory consults for write settings.
121 	 * @param dir
122 	 *            the location of the <code>objects</code> directory.
123 	 * @param alternatePaths
124 	 *            a list of alternate object directories
125 	 * @param fs
126 	 *            the file system abstraction which will be necessary to perform
127 	 *            certain file system operations.
128 	 * @param shallowFile
129 	 *            file which contains IDs of shallow commits, null if shallow
130 	 *            commits handling should be turned off
131 	 * @throws java.io.IOException
132 	 *             an alternate object cannot be opened.
133 	 */
134 	public ObjectDirectory(final Config cfg, final File dir,
135 			File[] alternatePaths, FS fs, File shallowFile) throws IOException {
136 		config = cfg;
137 		objects = dir;
138 		infoDirectory = new File(objects, "info"); //$NON-NLS-1$
139 		packDirectory = new File(objects, "pack"); //$NON-NLS-1$
140 		preservedDirectory = new File(packDirectory, "preserved"); //$NON-NLS-1$
141 		alternatesFile = new File(objects, Constants.INFO_ALTERNATES);
142 		packList = new AtomicReference<>(NO_PACKS);
143 		unpackedObjectCache = new UnpackedObjectCache();
144 		this.fs = fs;
145 		this.shallowFile = shallowFile;
146 
147 		alternates = new AtomicReference<>();
148 		if (alternatePaths != null) {
149 			AlternateHandle[] alt;
150 
151 			alt = new AlternateHandle[alternatePaths.length];
152 			for (int i = 0; i < alternatePaths.length; i++)
153 				alt[i] = openAlternate(alternatePaths[i]);
154 			alternates.set(alt);
155 		}
156 	}
157 
158 	/** {@inheritDoc} */
159 	@Override
160 	public final File getDirectory() {
161 		return objects;
162 	}
163 
164 	/**
165 	 * <p>Getter for the field <code>packDirectory</code>.</p>
166 	 *
167 	 * @return the location of the <code>pack</code> directory.
168 	 */
169 	public final File getPackDirectory() {
170 		return packDirectory;
171 	}
172 
173 	/**
174 	 * <p>Getter for the field <code>preservedDirectory</code>.</p>
175 	 *
176 	 * @return the location of the <code>preserved</code> directory.
177 	 */
178 	public final File getPreservedDirectory() {
179 		return preservedDirectory;
180 	}
181 
182 	/** {@inheritDoc} */
183 	@Override
184 	public boolean exists() {
185 		return fs.exists(objects);
186 	}
187 
188 	/** {@inheritDoc} */
189 	@Override
190 	public void create() throws IOException {
191 		FileUtils.mkdirs(objects);
192 		FileUtils.mkdir(infoDirectory);
193 		FileUtils.mkdir(packDirectory);
194 	}
195 
196 	/** {@inheritDoc} */
197 	@Override
198 	public ObjectDirectoryInserter newInserter() {
199 		return new ObjectDirectoryInserter(this, config);
200 	}
201 
202 	/**
203 	 * Create a new inserter that inserts all objects as pack files, not loose
204 	 * objects.
205 	 *
206 	 * @return new inserter.
207 	 */
208 	public PackInserter newPackInserter() {
209 		return new PackInserter(this);
210 	}
211 
212 	/** {@inheritDoc} */
213 	@Override
214 	public void close() {
215 		unpackedObjectCache.clear();
216 
217 		final PackList packs = packList.get();
218 		if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
219 			for (PackFile p : packs.packs)
220 				p.close();
221 		}
222 
223 		// Fully close all loaded alternates and clear the alternate list.
224 		AlternateHandle[] alt = alternates.get();
225 		if (alt != null && alternates.compareAndSet(alt, null)) {
226 			for(AlternateHandle od : alt)
227 				od.close();
228 		}
229 	}
230 
231 	/** {@inheritDoc} */
232 	@Override
233 	public Collection<PackFile> getPacks() {
234 		PackList list = packList.get();
235 		if (list == NO_PACKS)
236 			list = scanPacks(list);
237 		PackFile[] packs = list.packs;
238 		return Collections.unmodifiableCollection(Arrays.asList(packs));
239 	}
240 
241 	/**
242 	 * {@inheritDoc}
243 	 * <p>
244 	 * Add a single existing pack to the list of available pack files.
245 	 */
246 	@Override
247 	public PackFile openPack(File pack)
248 			throws IOException {
249 		final String p = pack.getName();
250 		if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
251 			throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, pack));
252 
253 		// The pack and index are assumed to exist. The existence of other
254 		// extensions needs to be explicitly checked.
255 		//
256 		int extensions = PACK.getBit() | INDEX.getBit();
257 		final String base = p.substring(0, p.length() - 4);
258 		for (PackExt ext : PackExt.values()) {
259 			if ((extensions & ext.getBit()) == 0) {
260 				final String name = base + ext.getExtension();
261 				if (new File(pack.getParentFile(), name).exists())
262 					extensions |= ext.getBit();
263 			}
264 		}
265 
266 		PackFile res = new PackFile(pack, extensions);
267 		insertPack(res);
268 		return res;
269 	}
270 
271 	/** {@inheritDoc} */
272 	@Override
273 	public String toString() {
274 		return "ObjectDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
275 	}
276 
277 	/** {@inheritDoc} */
278 	@Override
279 	public boolean has(AnyObjectId objectId) {
280 		return unpackedObjectCache.isUnpacked(objectId)
281 				|| hasPackedInSelfOrAlternate(objectId, null)
282 				|| hasLooseInSelfOrAlternate(objectId, null);
283 	}
284 
285 	private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId,
286 			Set<AlternateHandle.Id> skips) {
287 		if (hasPackedObject(objectId)) {
288 			return true;
289 		}
290 		skips = addMe(skips);
291 		for (AlternateHandle alt : myAlternates()) {
292 			if (!skips.contains(alt.getId())) {
293 				if (alt.db.hasPackedInSelfOrAlternate(objectId, skips)) {
294 					return true;
295 				}
296 			}
297 		}
298 		return false;
299 	}
300 
301 	private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId,
302 			Set<AlternateHandle.Id> skips) {
303 		if (fileFor(objectId).exists()) {
304 			return true;
305 		}
306 		skips = addMe(skips);
307 		for (AlternateHandle alt : myAlternates()) {
308 			if (!skips.contains(alt.getId())) {
309 				if (alt.db.hasLooseInSelfOrAlternate(objectId, skips)) {
310 					return true;
311 				}
312 			}
313 		}
314 		return false;
315 	}
316 
317 	boolean hasPackedObject(AnyObjectId objectId) {
318 		PackList pList;
319 		do {
320 			pList = packList.get();
321 			for (PackFile p : pList.packs) {
322 				try {
323 					if (p.hasObject(objectId))
324 						return true;
325 				} catch (IOException e) {
326 					// The hasObject call should have only touched the index,
327 					// so any failure here indicates the index is unreadable
328 					// by this process, and the pack is likewise not readable.
329 					LOG.warn(MessageFormat.format(
330 							JGitText.get().unableToReadPackfile,
331 							p.getPackFile().getAbsolutePath()), e);
332 					removePack(p);
333 				}
334 			}
335 		} while (searchPacksAgain(pList));
336 		return false;
337 	}
338 
339 	@Override
340 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
341 			throws IOException {
342 		resolve(matches, id, null);
343 	}
344 
345 	private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
346 			Set<AlternateHandle.Id> skips)
347 			throws IOException {
348 		// Go through the packs once. If we didn't find any resolutions
349 		// scan for new packs and check once more.
350 		int oldSize = matches.size();
351 		PackList pList;
352 		do {
353 			pList = packList.get();
354 			for (PackFile p : pList.packs) {
355 				try {
356 					p.resolve(matches, id, RESOLVE_ABBREV_LIMIT);
357 					p.resetTransientErrorCount();
358 				} catch (IOException e) {
359 					handlePackError(e, p);
360 				}
361 				if (matches.size() > RESOLVE_ABBREV_LIMIT)
362 					return;
363 			}
364 		} while (matches.size() == oldSize && searchPacksAgain(pList));
365 
366 		String fanOut = id.name().substring(0, 2);
367 		String[] entries = new File(getDirectory(), fanOut).list();
368 		if (entries != null) {
369 			for (String e : entries) {
370 				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
371 					continue;
372 				try {
373 					ObjectId entId = ObjectId.fromString(fanOut + e);
374 					if (id.prefixCompare(entId) == 0)
375 						matches.add(entId);
376 				} catch (IllegalArgumentException notId) {
377 					continue;
378 				}
379 				if (matches.size() > RESOLVE_ABBREV_LIMIT)
380 					return;
381 			}
382 		}
383 
384 		skips = addMe(skips);
385 		for (AlternateHandle alt : myAlternates()) {
386 			if (!skips.contains(alt.getId())) {
387 				alt.db.resolve(matches, id, skips);
388 				if (matches.size() > RESOLVE_ABBREV_LIMIT) {
389 					return;
390 				}
391 			}
392 		}
393 	}
394 
395 	@Override
396 	ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
397 			throws IOException {
398 		if (unpackedObjectCache.isUnpacked(objectId)) {
399 			ObjectLoader ldr = openLooseObject(curs, objectId);
400 			if (ldr != null) {
401 				return ldr;
402 			}
403 		}
404 		ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId, null);
405 		if (ldr != null) {
406 			return ldr;
407 		}
408 		return openLooseFromSelfOrAlternate(curs, objectId, null);
409 	}
410 
411 	private ObjectLoader openPackedFromSelfOrAlternate(WindowCursor curs,
412 			AnyObjectId objectId, Set<AlternateHandle.Id> skips) {
413 		ObjectLoader ldr = openPackedObject(curs, objectId);
414 		if (ldr != null) {
415 			return ldr;
416 		}
417 		skips = addMe(skips);
418 		for (AlternateHandle alt : myAlternates()) {
419 			if (!skips.contains(alt.getId())) {
420 				ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId, skips);
421 				if (ldr != null) {
422 					return ldr;
423 				}
424 			}
425 		}
426 		return null;
427 	}
428 
429 	private ObjectLoader openLooseFromSelfOrAlternate(WindowCursor curs,
430 			AnyObjectId objectId, Set<AlternateHandle.Id> skips)
431 					throws IOException {
432 		ObjectLoader ldr = openLooseObject(curs, objectId);
433 		if (ldr != null) {
434 			return ldr;
435 		}
436 		skips = addMe(skips);
437 		for (AlternateHandle alt : myAlternates()) {
438 			if (!skips.contains(alt.getId())) {
439 				ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId, skips);
440 				if (ldr != null) {
441 					return ldr;
442 				}
443 			}
444 		}
445 		return null;
446 	}
447 
448 	ObjectLoader openPackedObject(WindowCursor curs, AnyObjectId objectId) {
449 		PackList pList;
450 		do {
451 			SEARCH: for (;;) {
452 				pList = packList.get();
453 				for (PackFile p : pList.packs) {
454 					try {
455 						ObjectLoader ldr = p.get(curs, objectId);
456 						p.resetTransientErrorCount();
457 						if (ldr != null)
458 							return ldr;
459 					} catch (PackMismatchException e) {
460 						// Pack was modified; refresh the entire pack list.
461 						if (searchPacksAgain(pList))
462 							continue SEARCH;
463 					} catch (IOException e) {
464 						handlePackError(e, p);
465 					}
466 				}
467 				break SEARCH;
468 			}
469 		} while (searchPacksAgain(pList));
470 		return null;
471 	}
472 
473 	@Override
474 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
475 			throws IOException {
476 		File path = fileFor(id);
477 		try (FileInputStream in = new FileInputStream(path)) {
478 			unpackedObjectCache.add(id);
479 			return UnpackedObject.open(in, path, id, curs);
480 		} catch (FileNotFoundException noFile) {
481 			if (path.exists()) {
482 				throw noFile;
483 			}
484 			unpackedObjectCache.remove(id);
485 			return null;
486 		}
487 	}
488 
489 	@Override
490 	long getObjectSize(WindowCursor curs, AnyObjectId id)
491 			throws IOException {
492 		if (unpackedObjectCache.isUnpacked(id)) {
493 			long len = getLooseObjectSize(curs, id);
494 			if (0 <= len) {
495 				return len;
496 			}
497 		}
498 		long len = getPackedSizeFromSelfOrAlternate(curs, id, null);
499 		if (0 <= len) {
500 			return len;
501 		}
502 		return getLooseSizeFromSelfOrAlternate(curs, id, null);
503 	}
504 
505 	private long getPackedSizeFromSelfOrAlternate(WindowCursor curs,
506 			AnyObjectId id, Set<AlternateHandle.Id> skips) {
507 		long len = getPackedObjectSize(curs, id);
508 		if (0 <= len) {
509 			return len;
510 		}
511 		skips = addMe(skips);
512 		for (AlternateHandle alt : myAlternates()) {
513 			if (!skips.contains(alt.getId())) {
514 				len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips);
515 				if (0 <= len) {
516 					return len;
517 				}
518 			}
519 		}
520 		return -1;
521 	}
522 
523 	private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
524 			AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException {
525 		long len = getLooseObjectSize(curs, id);
526 		if (0 <= len) {
527 			return len;
528 		}
529 		skips = addMe(skips);
530 		for (AlternateHandle alt : myAlternates()) {
531 			if (!skips.contains(alt.getId())) {
532 				len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips);
533 				if (0 <= len) {
534 					return len;
535 				}
536 			}
537 		}
538 		return -1;
539 	}
540 
541 	private long getPackedObjectSize(WindowCursor curs, AnyObjectId id) {
542 		PackList pList;
543 		do {
544 			SEARCH: for (;;) {
545 				pList = packList.get();
546 				for (PackFile p : pList.packs) {
547 					try {
548 						long len = p.getObjectSize(curs, id);
549 						p.resetTransientErrorCount();
550 						if (0 <= len)
551 							return len;
552 					} catch (PackMismatchException e) {
553 						// Pack was modified; refresh the entire pack list.
554 						if (searchPacksAgain(pList))
555 							continue SEARCH;
556 					} catch (IOException e) {
557 						handlePackError(e, p);
558 					}
559 				}
560 				break SEARCH;
561 			}
562 		} while (searchPacksAgain(pList));
563 		return -1;
564 	}
565 
566 	private long getLooseObjectSize(WindowCursor curs, AnyObjectId id)
567 			throws IOException {
568 		File f = fileFor(id);
569 		try (FileInputStream in = new FileInputStream(f)) {
570 			unpackedObjectCache.add(id);
571 			return UnpackedObject.getSize(in, id, curs);
572 		} catch (FileNotFoundException noFile) {
573 			if (f.exists()) {
574 				throw noFile;
575 			}
576 			unpackedObjectCache.remove(id);
577 			return -1;
578 		}
579 	}
580 
581 	@Override
582 	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
583 			WindowCursor curs) throws IOException {
584 		selectObjectRepresentation(packer, otp, curs, null);
585 	}
586 
587 	private void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
588 			WindowCursor curs, Set<AlternateHandle.Id> skips) throws IOException {
589 		PackList pList = packList.get();
590 		SEARCH: for (;;) {
591 			for (PackFile p : pList.packs) {
592 				try {
593 					LocalObjectRepresentation rep = p.representation(curs, otp);
594 					p.resetTransientErrorCount();
595 					if (rep != null)
596 						packer.select(otp, rep);
597 				} catch (PackMismatchException e) {
598 					// Pack was modified; refresh the entire pack list.
599 					//
600 					pList = scanPacks(pList);
601 					continue SEARCH;
602 				} catch (IOException e) {
603 					handlePackError(e, p);
604 				}
605 			}
606 			break SEARCH;
607 		}
608 
609 		skips = addMe(skips);
610 		for (AlternateHandle h : myAlternates()) {
611 			if (!skips.contains(h.getId())) {
612 				h.db.selectObjectRepresentation(packer, otp, curs, skips);
613 			}
614 		}
615 	}
616 
617 	private void handlePackError(IOException e, PackFile p) {
618 		String warnTmpl = null;
619 		int transientErrorCount = 0;
620 		String errTmpl = JGitText.get().exceptionWhileReadingPack;
621 		if ((e instanceof CorruptObjectException)
622 				|| (e instanceof PackInvalidException)) {
623 			warnTmpl = JGitText.get().corruptPack;
624 			LOG.warn(MessageFormat.format(warnTmpl,
625 					p.getPackFile().getAbsolutePath()), e);
626 			// Assume the pack is corrupted, and remove it from the list.
627 			removePack(p);
628 		} else if (e instanceof FileNotFoundException) {
629 			if (p.getPackFile().exists()) {
630 				errTmpl = JGitText.get().packInaccessible;
631 				transientErrorCount = p.incrementTransientErrorCount();
632 			} else {
633 				warnTmpl = JGitText.get().packWasDeleted;
634 				removePack(p);
635 			}
636 		} else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
637 			warnTmpl = JGitText.get().packHandleIsStale;
638 			removePack(p);
639 		} else {
640 			transientErrorCount = p.incrementTransientErrorCount();
641 		}
642 		if (warnTmpl != null) {
643 			LOG.warn(MessageFormat.format(warnTmpl,
644 					p.getPackFile().getAbsolutePath()), e);
645 		} else {
646 			if (doLogExponentialBackoff(transientErrorCount)) {
647 				// Don't remove the pack from the list, as the error may be
648 				// transient.
649 				LOG.error(MessageFormat.format(errTmpl,
650 						p.getPackFile().getAbsolutePath(),
651 						Integer.valueOf(transientErrorCount)), e);
652 			}
653 		}
654 	}
655 
656 	/**
657 	 * @param n
658 	 *            count of consecutive failures
659 	 * @return @{code true} if i is a power of 2
660 	 */
661 	private boolean doLogExponentialBackoff(int n) {
662 		return (n & (n - 1)) == 0;
663 	}
664 
665 	@Override
666 	InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
667 			boolean createDuplicate) throws IOException {
668 		// If the object is already in the repository, remove temporary file.
669 		//
670 		if (unpackedObjectCache.isUnpacked(id)) {
671 			FileUtils.delete(tmp, FileUtils.RETRY);
672 			return InsertLooseObjectResult.EXISTS_LOOSE;
673 		}
674 		if (!createDuplicate && has(id)) {
675 			FileUtils.delete(tmp, FileUtils.RETRY);
676 			return InsertLooseObjectResult.EXISTS_PACKED;
677 		}
678 
679 		final File dst = fileFor(id);
680 		if (dst.exists()) {
681 			// We want to be extra careful and avoid replacing an object
682 			// that already exists. We can't be sure renameTo() would
683 			// fail on all platforms if dst exists, so we check first.
684 			//
685 			FileUtils.delete(tmp, FileUtils.RETRY);
686 			return InsertLooseObjectResult.EXISTS_LOOSE;
687 		}
688 
689 		try {
690 			return tryMove(tmp, dst, id);
691 		} catch (NoSuchFileException e) {
692 			// It's possible the directory doesn't exist yet as the object
693 			// directories are always lazily created. Note that we try the
694 			// rename/move first as the directory likely does exist.
695 			//
696 			// Create the directory.
697 			//
698 			FileUtils.mkdir(dst.getParentFile(), true);
699 		} catch (IOException e) {
700 			// Any other IO error is considered a failure.
701 			//
702 			LOG.error(e.getMessage(), e);
703 			FileUtils.delete(tmp, FileUtils.RETRY);
704 			return InsertLooseObjectResult.FAILURE;
705 		}
706 
707 		try {
708 			return tryMove(tmp, dst, id);
709 		} catch (IOException e) {
710 			// The object failed to be renamed into its proper location and
711 			// it doesn't exist in the repository either. We really don't
712 			// know what went wrong, so fail.
713 			//
714 			LOG.error(e.getMessage(), e);
715 			FileUtils.delete(tmp, FileUtils.RETRY);
716 			return InsertLooseObjectResult.FAILURE;
717 		}
718 	}
719 
720 	private InsertLooseObjectResult tryMove(File tmp, File dst,
721 			ObjectId id)
722 			throws IOException {
723 		Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
724 				StandardCopyOption.ATOMIC_MOVE);
725 		dst.setReadOnly();
726 		unpackedObjectCache.add(id);
727 		return InsertLooseObjectResult.INSERTED;
728 	}
729 
730 	boolean searchPacksAgain(PackList old) {
731 		// Whether to trust the pack folder's modification time. If set
732 		// to false we will always scan the .git/objects/pack folder to
733 		// check for new pack files. If set to true (default) we use the
734 		// lastmodified attribute of the folder and assume that no new
735 		// pack files can be in this folder if his modification time has
736 		// not changed.
737 		boolean trustFolderStat = config.getBoolean(
738 				ConfigConstants.CONFIG_CORE_SECTION,
739 				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
740 
741 		return ((!trustFolderStat) || old.snapshot.isModified(packDirectory))
742 				&& old != scanPacks(old);
743 	}
744 
745 	@Override
746 	Config getConfig() {
747 		return config;
748 	}
749 
750 	@Override
751 	FS getFS() {
752 		return fs;
753 	}
754 
755 	@Override
756 	Set<ObjectId> getShallowCommits() throws IOException {
757 		if (shallowFile == null || !shallowFile.isFile())
758 			return Collections.emptySet();
759 
760 		if (shallowFileSnapshot == null
761 				|| shallowFileSnapshot.isModified(shallowFile)) {
762 			shallowCommitsIds = new HashSet<>();
763 
764 			try (BufferedReader reader = open(shallowFile)) {
765 				String line;
766 				while ((line = reader.readLine()) != null) {
767 					try {
768 						shallowCommitsIds.add(ObjectId.fromString(line));
769 					} catch (IllegalArgumentException ex) {
770 						throw new IOException(MessageFormat
771 								.format(JGitText.get().badShallowLine, line),
772 								ex);
773 					}
774 				}
775 			}
776 
777 			shallowFileSnapshot = FileSnapshot.save(shallowFile);
778 		}
779 
780 		return shallowCommitsIds;
781 	}
782 
783 	private void insertPack(PackFile pf) {
784 		PackList o, n;
785 		do {
786 			o = packList.get();
787 
788 			// If the pack in question is already present in the list
789 			// (picked up by a concurrent thread that did a scan?) we
790 			// do not want to insert it a second time.
791 			//
792 			final PackFile[] oldList = o.packs;
793 			final String name = pf.getPackFile().getName();
794 			for (PackFile p : oldList) {
795 				if (name.equals(p.getPackFile().getName()))
796 					return;
797 			}
798 
799 			final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[1 + oldList.length];
800 			newList[0] = pf;
801 			System.arraycopy(oldList, 0, newList, 1, oldList.length);
802 			n = new PackList(o.snapshot, newList);
803 		} while (!packList.compareAndSet(o, n));
804 	}
805 
806 	private void removePack(PackFile deadPack) {
807 		PackList o, n;
808 		do {
809 			o = packList.get();
810 
811 			final PackFile[] oldList = o.packs;
812 			final int j = indexOf(oldList, deadPack);
813 			if (j < 0)
814 				break;
815 
816 			final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[oldList.length - 1];
817 			System.arraycopy(oldList, 0, newList, 0, j);
818 			System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
819 			n = new PackList(o.snapshot, newList);
820 		} while (!packList.compareAndSet(o, n));
821 		deadPack.close();
822 	}
823 
824 	private static int indexOf(PackFile../../../../../../org/eclipse/jgit/internal/storage/file/PackFile.html#PackFile">PackFile[] list, PackFile pack) {
825 		for (int i = 0; i < list.length; i++) {
826 			if (list[i] == pack)
827 				return i;
828 		}
829 		return -1;
830 	}
831 
832 	private PackList scanPacks(PackList original) {
833 		synchronized (packList) {
834 			PackList o, n;
835 			do {
836 				o = packList.get();
837 				if (o != original) {
838 					// Another thread did the scan for us, while we
839 					// were blocked on the monitor above.
840 					//
841 					return o;
842 				}
843 				n = scanPacksImpl(o);
844 				if (n == o)
845 					return n;
846 			} while (!packList.compareAndSet(o, n));
847 			return n;
848 		}
849 	}
850 
851 	private PackList scanPacksImpl(PackList old) {
852 		final Map<String, PackFile> forReuse = reuseMap(old);
853 		final FileSnapshot snapshot = FileSnapshot.save(packDirectory);
854 		final Set<String> names = listPackDirectory();
855 		final List<PackFile> list = new ArrayList<>(names.size() >> 2);
856 		boolean foundNew = false;
857 		for (String indexName : names) {
858 			// Must match "pack-[0-9a-f]{40}.idx" to be an index.
859 			//
860 			if (indexName.length() != 49 || !indexName.endsWith(".idx")) //$NON-NLS-1$
861 				continue;
862 
863 			final String base = indexName.substring(0, indexName.length() - 3);
864 			int extensions = 0;
865 			for (PackExt ext : PackExt.values()) {
866 				if (names.contains(base + ext.getExtension()))
867 					extensions |= ext.getBit();
868 			}
869 
870 			if ((extensions & PACK.getBit()) == 0) {
871 				// Sometimes C Git's HTTP fetch transport leaves a
872 				// .idx file behind and does not download the .pack.
873 				// We have to skip over such useless indexes.
874 				//
875 				continue;
876 			}
877 
878 			final String packName = base + PACK.getExtension();
879 			final File packFile = new File(packDirectory, packName);
880 			final PackFile oldPack = forReuse.get(packName);
881 			if (oldPack != null
882 					&& !oldPack.getFileSnapshot().isModified(packFile)) {
883 				forReuse.remove(packName);
884 				list.add(oldPack);
885 				continue;
886 			}
887 
888 			list.add(new PackFile(packFile, extensions));
889 			foundNew = true;
890 		}
891 
892 		// If we did not discover any new files, the modification time was not
893 		// changed, and we did not remove any files, then the set of files is
894 		// the same as the set we were given. Instead of building a new object
895 		// return the same collection.
896 		//
897 		if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
898 			old.snapshot.setClean(snapshot);
899 			return old;
900 		}
901 
902 		for (PackFile p : forReuse.values()) {
903 			p.close();
904 		}
905 
906 		if (list.isEmpty())
907 			return new PackList(snapshot, NO_PACKS.packs);
908 
909 		final PackFilefile/PackFile.html#PackFile">PackFile[] r = list.toArray(new PackFile[0]);
910 		Arrays.sort(r, PackFile.SORT);
911 		return new PackList(snapshot, r);
912 	}
913 
914 	private static Map<String, PackFile> reuseMap(PackList old) {
915 		final Map<String, PackFile> forReuse = new HashMap<>();
916 		for (PackFile p : old.packs) {
917 			if (p.invalid()) {
918 				// The pack instance is corrupted, and cannot be safely used
919 				// again. Do not include it in our reuse map.
920 				//
921 				p.close();
922 				continue;
923 			}
924 
925 			final PackFile prior = forReuse.put(p.getPackFile().getName(), p);
926 			if (prior != null) {
927 				// This should never occur. It should be impossible for us
928 				// to have two pack files with the same name, as all of them
929 				// came out of the same directory. If it does, we promised to
930 				// close any PackFiles we did not reuse, so close the second,
931 				// readers are likely to be actively using the first.
932 				//
933 				forReuse.put(prior.getPackFile().getName(), prior);
934 				p.close();
935 			}
936 		}
937 		return forReuse;
938 	}
939 
940 	private Set<String> listPackDirectory() {
941 		final String[] nameList = packDirectory.list();
942 		if (nameList == null)
943 			return Collections.emptySet();
944 		final Set<String> nameSet = new HashSet<>(nameList.length << 1);
945 		for (String name : nameList) {
946 			if (name.startsWith("pack-")) //$NON-NLS-1$
947 				nameSet.add(name);
948 		}
949 		return nameSet;
950 	}
951 
952 	void closeAllPackHandles(File packFile) {
953 		// if the packfile already exists (because we are rewriting a
954 		// packfile for the same set of objects maybe with different
955 		// PackConfig) then make sure we get rid of all handles on the file.
956 		// Windows will not allow for rename otherwise.
957 		if (packFile.exists()) {
958 			for (PackFile p : getPacks()) {
959 				if (packFile.getPath().equals(p.getPackFile().getPath())) {
960 					p.close();
961 					break;
962 				}
963 			}
964 		}
965 	}
966 
967 	AlternateHandle[] myAlternates() {
968 		AlternateHandle[] alt = alternates.get();
969 		if (alt == null) {
970 			synchronized (alternates) {
971 				alt = alternates.get();
972 				if (alt == null) {
973 					try {
974 						alt = loadAlternates();
975 					} catch (IOException e) {
976 						alt = new AlternateHandle[0];
977 					}
978 					alternates.set(alt);
979 				}
980 			}
981 		}
982 		return alt;
983 	}
984 
985 	Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
986 		if (skips == null) {
987 			skips = new HashSet<>();
988 		}
989 		skips.add(handle.getId());
990 		return skips;
991 	}
992 
993 	private AlternateHandle[] loadAlternates() throws IOException {
994 		final List<AlternateHandle> l = new ArrayList<>(4);
995 		try (BufferedReader br = open(alternatesFile)) {
996 			String line;
997 			while ((line = br.readLine()) != null) {
998 				l.add(openAlternate(line));
999 			}
1000 		}
1001 		return l.toArray(new AlternateHandle[0]);
1002 	}
1003 
1004 	private static BufferedReader open(File f)
1005 			throws IOException, FileNotFoundException {
1006 		return Files.newBufferedReader(f.toPath(), UTF_8);
1007 	}
1008 
1009 	private AlternateHandle openAlternate(String location)
1010 			throws IOException {
1011 		final File objdir = fs.resolve(objects, location);
1012 		return openAlternate(objdir);
1013 	}
1014 
1015 	private AlternateHandle openAlternate(File objdir) throws IOException {
1016 		final File parent = objdir.getParentFile();
1017 		if (FileKey.isGitRepository(parent, fs)) {
1018 			FileKey key = FileKey.exact(parent, fs);
1019 			FileRepository db = (FileRepository) RepositoryCache.open(key);
1020 			return new AlternateRepository(db);
1021 		}
1022 
1023 		ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs, null);
1024 		return new AlternateHandle(db);
1025 	}
1026 
1027 	/**
1028 	 * {@inheritDoc}
1029 	 * <p>
1030 	 * Compute the location of a loose object file.
1031 	 */
1032 	@Override
1033 	public File fileFor(AnyObjectId objectId) {
1034 		String n = objectId.name();
1035 		String d = n.substring(0, 2);
1036 		String f = n.substring(2);
1037 		return new File(new File(getDirectory(), d), f);
1038 	}
1039 
1040 	static final class PackList {
1041 		/** State just before reading the pack directory. */
1042 		final FileSnapshot snapshot;
1043 
1044 		/** All known packs, sorted by {@link PackFile#SORT}. */
1045 		final PackFile[] packs;
1046 
1047 		PackList(FileSnapshot monitor, PackFile[] packs) {
1048 			this.snapshot = monitor;
1049 			this.packs = packs;
1050 		}
1051 	}
1052 
1053 	static class AlternateHandle {
1054 		static class Id {
1055 			String alternateId;
1056 
1057 			public Id(File object) {
1058 				try {
1059 					this.alternateId = object.getCanonicalPath();
1060 				} catch (Exception e) {
1061 					alternateId = null;
1062 				}
1063 			}
1064 
1065 			@Override
1066 			public boolean equals(Object o) {
1067 				if (o == this) {
1068 					return true;
1069 				}
1070 				if (o == null || !(o instanceof Id)) {
1071 					return false;
1072 				}
1073 				Id aId = (Id) o;
1074 				return Objects.equals(alternateId, aId.alternateId);
1075 			}
1076 
1077 			@Override
1078 			public int hashCode() {
1079 				if (alternateId == null) {
1080 					return 1;
1081 				}
1082 				return alternateId.hashCode();
1083 			}
1084 		}
1085 
1086 		final ObjectDirectory db;
1087 
1088 		AlternateHandle(ObjectDirectory db) {
1089 			this.db = db;
1090 		}
1091 
1092 		void close() {
1093 			db.close();
1094 		}
1095 
1096 		public Id getId(){
1097 			return db.getAlternateId();
1098 		}
1099 	}
1100 
1101 	static class AlternateRepository extends AlternateHandle {
1102 		final FileRepository repository;
1103 
1104 		AlternateRepository(FileRepository r) {
1105 			super(r.getObjectDatabase());
1106 			repository = r;
1107 		}
1108 
1109 		@Override
1110 		void close() {
1111 			repository.close();
1112 		}
1113 	}
1114 
1115 	/** {@inheritDoc} */
1116 	@Override
1117 	public ObjectDatabase newCachedDatabase() {
1118 		return newCachedFileObjectDatabase();
1119 	}
1120 
1121 	CachedObjectDirectory newCachedFileObjectDatabase() {
1122 		return new CachedObjectDirectory(this);
1123 	}
1124 
1125 	AlternateHandle.Id getAlternateId() {
1126 		return new AlternateHandle.Id(objects);
1127 	}
1128 }