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.AtomicMoveNotSupportedException;
23  import java.nio.file.Files;
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 		try {
689 			Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
690 					StandardCopyOption.ATOMIC_MOVE);
691 			dst.setReadOnly();
692 			unpackedObjectCache.add(id);
693 			return InsertLooseObjectResult.INSERTED;
694 		} catch (AtomicMoveNotSupportedException e) {
695 			LOG.error(e.getMessage(), e);
696 		} catch (IOException e) {
697 			// ignore
698 		}
699 
700 		// Maybe the directory doesn't exist yet as the object
701 		// directories are always lazily created. Note that we
702 		// try the rename first as the directory likely does exist.
703 		//
704 		FileUtils.mkdir(dst.getParentFile(), true);
705 		try {
706 			Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
707 					StandardCopyOption.ATOMIC_MOVE);
708 			dst.setReadOnly();
709 			unpackedObjectCache.add(id);
710 			return InsertLooseObjectResult.INSERTED;
711 		} catch (AtomicMoveNotSupportedException e) {
712 			LOG.error(e.getMessage(), e);
713 		} catch (IOException e) {
714 			LOG.debug(e.getMessage(), e);
715 		}
716 
717 		if (!createDuplicate && has(id)) {
718 			FileUtils.delete(tmp, FileUtils.RETRY);
719 			return InsertLooseObjectResult.EXISTS_PACKED;
720 		}
721 
722 		// The object failed to be renamed into its proper
723 		// location and it doesn't exist in the repository
724 		// either. We really don't know what went wrong, so
725 		// fail.
726 		//
727 		FileUtils.delete(tmp, FileUtils.RETRY);
728 		return InsertLooseObjectResult.FAILURE;
729 	}
730 
731 	boolean searchPacksAgain(PackList old) {
732 		// Whether to trust the pack folder's modification time. If set
733 		// to false we will always scan the .git/objects/pack folder to
734 		// check for new pack files. If set to true (default) we use the
735 		// lastmodified attribute of the folder and assume that no new
736 		// pack files can be in this folder if his modification time has
737 		// not changed.
738 		boolean trustFolderStat = config.getBoolean(
739 				ConfigConstants.CONFIG_CORE_SECTION,
740 				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
741 
742 		return ((!trustFolderStat) || old.snapshot.isModified(packDirectory))
743 				&& old != scanPacks(old);
744 	}
745 
746 	@Override
747 	Config getConfig() {
748 		return config;
749 	}
750 
751 	@Override
752 	FS getFS() {
753 		return fs;
754 	}
755 
756 	@Override
757 	Set<ObjectId> getShallowCommits() throws IOException {
758 		if (shallowFile == null || !shallowFile.isFile())
759 			return Collections.emptySet();
760 
761 		if (shallowFileSnapshot == null
762 				|| shallowFileSnapshot.isModified(shallowFile)) {
763 			shallowCommitsIds = new HashSet<>();
764 
765 			try (BufferedReader reader = open(shallowFile)) {
766 				String line;
767 				while ((line = reader.readLine()) != null) {
768 					try {
769 						shallowCommitsIds.add(ObjectId.fromString(line));
770 					} catch (IllegalArgumentException ex) {
771 						throw new IOException(MessageFormat
772 								.format(JGitText.get().badShallowLine, line),
773 								ex);
774 					}
775 				}
776 			}
777 
778 			shallowFileSnapshot = FileSnapshot.save(shallowFile);
779 		}
780 
781 		return shallowCommitsIds;
782 	}
783 
784 	private void insertPack(PackFile pf) {
785 		PackList o, n;
786 		do {
787 			o = packList.get();
788 
789 			// If the pack in question is already present in the list
790 			// (picked up by a concurrent thread that did a scan?) we
791 			// do not want to insert it a second time.
792 			//
793 			final PackFile[] oldList = o.packs;
794 			final String name = pf.getPackFile().getName();
795 			for (PackFile p : oldList) {
796 				if (name.equals(p.getPackFile().getName()))
797 					return;
798 			}
799 
800 			final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[1 + oldList.length];
801 			newList[0] = pf;
802 			System.arraycopy(oldList, 0, newList, 1, oldList.length);
803 			n = new PackList(o.snapshot, newList);
804 		} while (!packList.compareAndSet(o, n));
805 	}
806 
807 	private void removePack(PackFile deadPack) {
808 		PackList o, n;
809 		do {
810 			o = packList.get();
811 
812 			final PackFile[] oldList = o.packs;
813 			final int j = indexOf(oldList, deadPack);
814 			if (j < 0)
815 				break;
816 
817 			final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[oldList.length - 1];
818 			System.arraycopy(oldList, 0, newList, 0, j);
819 			System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
820 			n = new PackList(o.snapshot, newList);
821 		} while (!packList.compareAndSet(o, n));
822 		deadPack.close();
823 	}
824 
825 	private static int indexOf(PackFile../../../../../../org/eclipse/jgit/internal/storage/file/PackFile.html#PackFile">PackFile[] list, PackFile pack) {
826 		for (int i = 0; i < list.length; i++) {
827 			if (list[i] == pack)
828 				return i;
829 		}
830 		return -1;
831 	}
832 
833 	private PackList scanPacks(PackList original) {
834 		synchronized (packList) {
835 			PackList o, n;
836 			do {
837 				o = packList.get();
838 				if (o != original) {
839 					// Another thread did the scan for us, while we
840 					// were blocked on the monitor above.
841 					//
842 					return o;
843 				}
844 				n = scanPacksImpl(o);
845 				if (n == o)
846 					return n;
847 			} while (!packList.compareAndSet(o, n));
848 			return n;
849 		}
850 	}
851 
852 	private PackList scanPacksImpl(PackList old) {
853 		final Map<String, PackFile> forReuse = reuseMap(old);
854 		final FileSnapshot snapshot = FileSnapshot.save(packDirectory);
855 		final Set<String> names = listPackDirectory();
856 		final List<PackFile> list = new ArrayList<>(names.size() >> 2);
857 		boolean foundNew = false;
858 		for (String indexName : names) {
859 			// Must match "pack-[0-9a-f]{40}.idx" to be an index.
860 			//
861 			if (indexName.length() != 49 || !indexName.endsWith(".idx")) //$NON-NLS-1$
862 				continue;
863 
864 			final String base = indexName.substring(0, indexName.length() - 3);
865 			int extensions = 0;
866 			for (PackExt ext : PackExt.values()) {
867 				if (names.contains(base + ext.getExtension()))
868 					extensions |= ext.getBit();
869 			}
870 
871 			if ((extensions & PACK.getBit()) == 0) {
872 				// Sometimes C Git's HTTP fetch transport leaves a
873 				// .idx file behind and does not download the .pack.
874 				// We have to skip over such useless indexes.
875 				//
876 				continue;
877 			}
878 
879 			final String packName = base + PACK.getExtension();
880 			final File packFile = new File(packDirectory, packName);
881 			final PackFile oldPack = forReuse.get(packName);
882 			if (oldPack != null
883 					&& !oldPack.getFileSnapshot().isModified(packFile)) {
884 				forReuse.remove(packName);
885 				list.add(oldPack);
886 				continue;
887 			}
888 
889 			list.add(new PackFile(packFile, extensions));
890 			foundNew = true;
891 		}
892 
893 		// If we did not discover any new files, the modification time was not
894 		// changed, and we did not remove any files, then the set of files is
895 		// the same as the set we were given. Instead of building a new object
896 		// return the same collection.
897 		//
898 		if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
899 			old.snapshot.setClean(snapshot);
900 			return old;
901 		}
902 
903 		for (PackFile p : forReuse.values()) {
904 			p.close();
905 		}
906 
907 		if (list.isEmpty())
908 			return new PackList(snapshot, NO_PACKS.packs);
909 
910 		final PackFilefile/PackFile.html#PackFile">PackFile[] r = list.toArray(new PackFile[0]);
911 		Arrays.sort(r, PackFile.SORT);
912 		return new PackList(snapshot, r);
913 	}
914 
915 	private static Map<String, PackFile> reuseMap(PackList old) {
916 		final Map<String, PackFile> forReuse = new HashMap<>();
917 		for (PackFile p : old.packs) {
918 			if (p.invalid()) {
919 				// The pack instance is corrupted, and cannot be safely used
920 				// again. Do not include it in our reuse map.
921 				//
922 				p.close();
923 				continue;
924 			}
925 
926 			final PackFile prior = forReuse.put(p.getPackFile().getName(), p);
927 			if (prior != null) {
928 				// This should never occur. It should be impossible for us
929 				// to have two pack files with the same name, as all of them
930 				// came out of the same directory. If it does, we promised to
931 				// close any PackFiles we did not reuse, so close the second,
932 				// readers are likely to be actively using the first.
933 				//
934 				forReuse.put(prior.getPackFile().getName(), prior);
935 				p.close();
936 			}
937 		}
938 		return forReuse;
939 	}
940 
941 	private Set<String> listPackDirectory() {
942 		final String[] nameList = packDirectory.list();
943 		if (nameList == null)
944 			return Collections.emptySet();
945 		final Set<String> nameSet = new HashSet<>(nameList.length << 1);
946 		for (String name : nameList) {
947 			if (name.startsWith("pack-")) //$NON-NLS-1$
948 				nameSet.add(name);
949 		}
950 		return nameSet;
951 	}
952 
953 	void closeAllPackHandles(File packFile) {
954 		// if the packfile already exists (because we are rewriting a
955 		// packfile for the same set of objects maybe with different
956 		// PackConfig) then make sure we get rid of all handles on the file.
957 		// Windows will not allow for rename otherwise.
958 		if (packFile.exists()) {
959 			for (PackFile p : getPacks()) {
960 				if (packFile.getPath().equals(p.getPackFile().getPath())) {
961 					p.close();
962 					break;
963 				}
964 			}
965 		}
966 	}
967 
968 	AlternateHandle[] myAlternates() {
969 		AlternateHandle[] alt = alternates.get();
970 		if (alt == null) {
971 			synchronized (alternates) {
972 				alt = alternates.get();
973 				if (alt == null) {
974 					try {
975 						alt = loadAlternates();
976 					} catch (IOException e) {
977 						alt = new AlternateHandle[0];
978 					}
979 					alternates.set(alt);
980 				}
981 			}
982 		}
983 		return alt;
984 	}
985 
986 	Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
987 		if (skips == null) {
988 			skips = new HashSet<>();
989 		}
990 		skips.add(handle.getId());
991 		return skips;
992 	}
993 
994 	private AlternateHandle[] loadAlternates() throws IOException {
995 		final List<AlternateHandle> l = new ArrayList<>(4);
996 		try (BufferedReader br = open(alternatesFile)) {
997 			String line;
998 			while ((line = br.readLine()) != null) {
999 				l.add(openAlternate(line));
1000 			}
1001 		}
1002 		return l.toArray(new AlternateHandle[0]);
1003 	}
1004 
1005 	private static BufferedReader open(File f)
1006 			throws IOException, FileNotFoundException {
1007 		return Files.newBufferedReader(f.toPath(), UTF_8);
1008 	}
1009 
1010 	private AlternateHandle openAlternate(String location)
1011 			throws IOException {
1012 		final File objdir = fs.resolve(objects, location);
1013 		return openAlternate(objdir);
1014 	}
1015 
1016 	private AlternateHandle openAlternate(File objdir) throws IOException {
1017 		final File parent = objdir.getParentFile();
1018 		if (FileKey.isGitRepository(parent, fs)) {
1019 			FileKey key = FileKey.exact(parent, fs);
1020 			FileRepository db = (FileRepository) RepositoryCache.open(key);
1021 			return new AlternateRepository(db);
1022 		}
1023 
1024 		ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs, null);
1025 		return new AlternateHandle(db);
1026 	}
1027 
1028 	/**
1029 	 * {@inheritDoc}
1030 	 * <p>
1031 	 * Compute the location of a loose object file.
1032 	 */
1033 	@Override
1034 	public File fileFor(AnyObjectId objectId) {
1035 		String n = objectId.name();
1036 		String d = n.substring(0, 2);
1037 		String f = n.substring(2);
1038 		return new File(new File(getDirectory(), d), f);
1039 	}
1040 
1041 	static final class PackList {
1042 		/** State just before reading the pack directory. */
1043 		final FileSnapshot snapshot;
1044 
1045 		/** All known packs, sorted by {@link PackFile#SORT}. */
1046 		final PackFile[] packs;
1047 
1048 		PackList(FileSnapshot monitor, PackFile[] packs) {
1049 			this.snapshot = monitor;
1050 			this.packs = packs;
1051 		}
1052 	}
1053 
1054 	static class AlternateHandle {
1055 		static class Id {
1056 			String alternateId;
1057 
1058 			public Id(File object) {
1059 				try {
1060 					this.alternateId = object.getCanonicalPath();
1061 				} catch (Exception e) {
1062 					alternateId = null;
1063 				}
1064 			}
1065 
1066 			@Override
1067 			public boolean equals(Object o) {
1068 				if (o == this) {
1069 					return true;
1070 				}
1071 				if (o == null || !(o instanceof Id)) {
1072 					return false;
1073 				}
1074 				Id aId = (Id) o;
1075 				return Objects.equals(alternateId, aId.alternateId);
1076 			}
1077 
1078 			@Override
1079 			public int hashCode() {
1080 				if (alternateId == null) {
1081 					return 1;
1082 				}
1083 				return alternateId.hashCode();
1084 			}
1085 		}
1086 
1087 		final ObjectDirectory db;
1088 
1089 		AlternateHandle(ObjectDirectory db) {
1090 			this.db = db;
1091 		}
1092 
1093 		void close() {
1094 			db.close();
1095 		}
1096 
1097 		public Id getId(){
1098 			return db.getAlternateId();
1099 		}
1100 	}
1101 
1102 	static class AlternateRepository extends AlternateHandle {
1103 		final FileRepository repository;
1104 
1105 		AlternateRepository(FileRepository r) {
1106 			super(r.getObjectDatabase());
1107 			repository = r;
1108 		}
1109 
1110 		@Override
1111 		void close() {
1112 			repository.close();
1113 		}
1114 	}
1115 
1116 	/** {@inheritDoc} */
1117 	@Override
1118 	public ObjectDatabase newCachedDatabase() {
1119 		return newCachedFileObjectDatabase();
1120 	}
1121 
1122 	CachedObjectDirectory newCachedFileObjectDatabase() {
1123 		return new CachedObjectDirectory(this);
1124 	}
1125 
1126 	AlternateHandle.Id getAlternateId() {
1127 		return new AlternateHandle.Id(objects);
1128 	}
1129 }