View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2009-2010, Google Inc.
4    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
6    * and other copyright owners as documented in the project's IP log.
7    *
8    * This program and the accompanying materials are made available
9    * under the terms of the Eclipse Distribution License v1.0 which
10   * accompanies this distribution, is reproduced below, and is
11   * available at http://www.eclipse.org/org/documents/edl-v10.php
12   *
13   * All rights reserved.
14   *
15   * Redistribution and use in source and binary forms, with or
16   * without modification, are permitted provided that the following
17   * conditions are met:
18   *
19   * - Redistributions of source code must retain the above copyright
20   *   notice, this list of conditions and the following disclaimer.
21   *
22   * - Redistributions in binary form must reproduce the above
23   *   copyright notice, this list of conditions and the following
24   *   disclaimer in the documentation and/or other materials provided
25   *   with the distribution.
26   *
27   * - Neither the name of the Eclipse Foundation, Inc. nor the
28   *   names of its contributors may be used to endorse or promote
29   *   products derived from this software without specific prior
30   *   written permission.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
33   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
34   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
39   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
43   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45   */
46  
47  package org.eclipse.jgit.internal.storage.file;
48  
49  import static java.nio.charset.StandardCharsets.UTF_8;
50  import static org.eclipse.jgit.lib.Constants.HEAD;
51  import static org.eclipse.jgit.lib.Constants.LOGS;
52  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
53  import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
54  import static org.eclipse.jgit.lib.Constants.R_HEADS;
55  import static org.eclipse.jgit.lib.Constants.R_REFS;
56  import static org.eclipse.jgit.lib.Constants.R_TAGS;
57  import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
58  import static org.eclipse.jgit.lib.Ref.Storage.NEW;
59  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
60  
61  import java.io.BufferedReader;
62  import java.io.File;
63  import java.io.FileInputStream;
64  import java.io.FileNotFoundException;
65  import java.io.IOException;
66  import java.io.InputStreamReader;
67  import java.io.InterruptedIOException;
68  import java.nio.file.DirectoryNotEmptyException;
69  import java.nio.file.Files;
70  import java.nio.file.Path;
71  import java.security.DigestInputStream;
72  import java.security.MessageDigest;
73  import java.text.MessageFormat;
74  import java.util.Arrays;
75  import java.util.Collection;
76  import java.util.Collections;
77  import java.util.LinkedList;
78  import java.util.List;
79  import java.util.Map;
80  import java.util.concurrent.atomic.AtomicInteger;
81  import java.util.concurrent.atomic.AtomicReference;
82  import java.util.concurrent.locks.ReentrantLock;
83  import java.util.stream.Stream;
84  
85  import org.eclipse.jgit.annotations.NonNull;
86  import org.eclipse.jgit.annotations.Nullable;
87  import org.eclipse.jgit.errors.InvalidObjectIdException;
88  import org.eclipse.jgit.errors.LockFailedException;
89  import org.eclipse.jgit.errors.MissingObjectException;
90  import org.eclipse.jgit.errors.ObjectWritingException;
91  import org.eclipse.jgit.events.RefsChangedEvent;
92  import org.eclipse.jgit.internal.JGitText;
93  import org.eclipse.jgit.lib.ConfigConstants;
94  import org.eclipse.jgit.lib.Constants;
95  import org.eclipse.jgit.lib.ObjectId;
96  import org.eclipse.jgit.lib.ObjectIdRef;
97  import org.eclipse.jgit.lib.Ref;
98  import org.eclipse.jgit.lib.RefComparator;
99  import org.eclipse.jgit.lib.RefDatabase;
100 import org.eclipse.jgit.lib.RefUpdate;
101 import org.eclipse.jgit.lib.RefWriter;
102 import org.eclipse.jgit.lib.Repository;
103 import org.eclipse.jgit.lib.SymbolicRef;
104 import org.eclipse.jgit.revwalk.RevObject;
105 import org.eclipse.jgit.revwalk.RevTag;
106 import org.eclipse.jgit.revwalk.RevWalk;
107 import org.eclipse.jgit.util.FS;
108 import org.eclipse.jgit.util.FileUtils;
109 import org.eclipse.jgit.util.IO;
110 import org.eclipse.jgit.util.RawParseUtils;
111 import org.eclipse.jgit.util.RefList;
112 import org.eclipse.jgit.util.RefMap;
113 import org.slf4j.Logger;
114 import org.slf4j.LoggerFactory;
115 
116 /**
117  * Traditional file system based {@link org.eclipse.jgit.lib.RefDatabase}.
118  * <p>
119  * This is the classical reference database representation for a Git repository.
120  * References are stored in two formats: loose, and packed.
121  * <p>
122  * Loose references are stored as individual files within the {@code refs/}
123  * directory. The file name matches the reference name and the file contents is
124  * the current {@link org.eclipse.jgit.lib.ObjectId} in string form.
125  * <p>
126  * Packed references are stored in a single text file named {@code packed-refs}.
127  * In the packed format, each reference is stored on its own line. This file
128  * reduces the number of files needed for large reference spaces, reducing the
129  * overall size of a Git repository on disk.
130  */
131 public class RefDirectory extends RefDatabase {
132 	private final static Logger LOG = LoggerFactory
133 			.getLogger(RefDirectory.class);
134 
135 	/** Magic string denoting the start of a symbolic reference file. */
136 	public static final String SYMREF = "ref: "; //$NON-NLS-1$
137 
138 	/** Magic string denoting the header of a packed-refs file. */
139 	public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$
140 
141 	/** If in the header, denotes the file has peeled data. */
142 	public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$
143 
144 	/** The names of the additional refs supported by this class */
145 	private static final String[] additionalRefsNames = new String[] {
146 			Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
147 			Constants.CHERRY_PICK_HEAD };
148 
149 	@SuppressWarnings("boxing")
150 	private static final List<Integer> RETRY_SLEEP_MS =
151 			Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));
152 
153 	private final FileRepository parent;
154 
155 	private final File gitDir;
156 
157 	final File refsDir;
158 
159 	final File packedRefsFile;
160 
161 	final File logsDir;
162 
163 	final File logsRefsDir;
164 
165 	/**
166 	 * Immutable sorted list of loose references.
167 	 * <p>
168 	 * Symbolic references in this collection are stored unresolved, that is
169 	 * their target appears to be a new reference with no ObjectId. These are
170 	 * converted into resolved references during a get operation, ensuring the
171 	 * live value is always returned.
172 	 */
173 	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<>();
174 
175 	/** Immutable sorted list of packed references. */
176 	final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
177 
178 	/**
179 	 * Lock for coordinating operations within a single process that may contend
180 	 * on the {@code packed-refs} file.
181 	 * <p>
182 	 * All operations that write {@code packed-refs} must still acquire a
183 	 * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
184 	 * this lock, since there may be multiple {@link RefDirectory} instances or
185 	 * other processes operating on the same repo on disk.
186 	 * <p>
187 	 * This lock exists so multiple threads in the same process can wait in a fair
188 	 * queue without trying, failing, and retrying to acquire the on-disk lock. If
189 	 * {@code RepositoryCache} is used, this lock instance will be used by all
190 	 * threads.
191 	 */
192 	final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
193 
194 	/**
195 	 * Number of modifications made to this database.
196 	 * <p>
197 	 * This counter is incremented when a change is made, or detected from the
198 	 * filesystem during a read operation.
199 	 */
200 	private final AtomicInteger modCnt = new AtomicInteger();
201 
202 	/**
203 	 * Last {@link #modCnt} that we sent to listeners.
204 	 * <p>
205 	 * This value is compared to {@link #modCnt}, and a notification is sent to
206 	 * the listeners only when it differs.
207 	 */
208 	private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
209 
210 	private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
211 
212 	RefDirectory(FileRepository db) {
213 		final FS fs = db.getFS();
214 		parent = db;
215 		gitDir = db.getDirectory();
216 		refsDir = fs.resolve(gitDir, R_REFS);
217 		logsDir = fs.resolve(gitDir, LOGS);
218 		logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
219 		packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
220 
221 		looseRefs.set(RefList.<LooseRef> emptyList());
222 		packedRefs.set(NO_PACKED_REFS);
223 	}
224 
225 	Repository getRepository() {
226 		return parent;
227 	}
228 
229 	ReflogWriter newLogWriter(boolean force) {
230 		return new ReflogWriter(this, force);
231 	}
232 
233 	/**
234 	 * Locate the log file on disk for a single reference name.
235 	 *
236 	 * @param name
237 	 *            name of the ref, relative to the Git repository top level
238 	 *            directory (so typically starts with refs/).
239 	 * @return the log file location.
240 	 */
241 	public File logFor(String name) {
242 		if (name.startsWith(R_REFS)) {
243 			name = name.substring(R_REFS.length());
244 			return new File(logsRefsDir, name);
245 		}
246 		return new File(logsDir, name);
247 	}
248 
249 	/** {@inheritDoc} */
250 	@Override
251 	public void create() throws IOException {
252 		FileUtils.mkdir(refsDir);
253 		FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
254 		FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
255 		newLogWriter(false).create();
256 	}
257 
258 	/** {@inheritDoc} */
259 	@Override
260 	public void close() {
261 		clearReferences();
262 	}
263 
264 	private void clearReferences() {
265 		looseRefs.set(RefList.<LooseRef> emptyList());
266 		packedRefs.set(NO_PACKED_REFS);
267 	}
268 
269 	/** {@inheritDoc} */
270 	@Override
271 	public void refresh() {
272 		super.refresh();
273 		clearReferences();
274 	}
275 
276 	/** {@inheritDoc} */
277 	@Override
278 	public boolean isNameConflicting(String name) throws IOException {
279 		RefList<Ref> packed = getPackedRefs();
280 		RefList<LooseRef> loose = getLooseRefs();
281 
282 		// Cannot be nested within an existing reference.
283 		int lastSlash = name.lastIndexOf('/');
284 		while (0 < lastSlash) {
285 			String needle = name.substring(0, lastSlash);
286 			if (loose.contains(needle) || packed.contains(needle))
287 				return true;
288 			lastSlash = name.lastIndexOf('/', lastSlash - 1);
289 		}
290 
291 		// Cannot be the container of an existing reference.
292 		String prefix = name + '/';
293 		int idx;
294 
295 		idx = -(packed.find(prefix) + 1);
296 		if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
297 			return true;
298 
299 		idx = -(loose.find(prefix) + 1);
300 		if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
301 			return true;
302 
303 		return false;
304 	}
305 
306 	private RefList<LooseRef> getLooseRefs() {
307 		final RefList<LooseRef> oldLoose = looseRefs.get();
308 
309 		LooseScanner scan = new LooseScanner(oldLoose);
310 		scan.scan(ALL);
311 
312 		RefList<LooseRef> loose;
313 		if (scan.newLoose != null) {
314 			loose = scan.newLoose.toRefList();
315 			if (looseRefs.compareAndSet(oldLoose, loose))
316 				modCnt.incrementAndGet();
317 		} else
318 			loose = oldLoose;
319 		return loose;
320 	}
321 
322 	/** {@inheritDoc} */
323 	@Override
324 	public Ref exactRef(String name) throws IOException {
325 		RefList<Ref> packed = getPackedRefs();
326 		Ref ref;
327 		try {
328 			ref = readRef(name, packed);
329 			if (ref != null) {
330 				ref = resolve(ref, 0, null, null, packed);
331 			}
332 		} catch (IOException e) {
333 			if (name.contains("/") //$NON-NLS-1$
334 					|| !(e.getCause() instanceof InvalidObjectIdException)) {
335 				throw e;
336 			}
337 
338 			// While looking for a ref outside of refs/ (e.g., 'config'), we
339 			// found a non-ref file (e.g., a config file) instead.  Treat this
340 			// as a ref-not-found condition.
341 			ref = null;
342 		}
343 		fireRefsChanged();
344 		return ref;
345 	}
346 
347 	/** {@inheritDoc} */
348 	@Override
349 	public Ref getRef(String needle) throws IOException {
350 		final RefList<Ref> packed = getPackedRefs();
351 		Ref ref = null;
352 		for (String prefix : SEARCH_PATH) {
353 			try {
354 				ref = readRef(prefix + needle, packed);
355 				if (ref != null) {
356 					ref = resolve(ref, 0, null, null, packed);
357 				}
358 				if (ref != null) {
359 					break;
360 				}
361 			} catch (IOException e) {
362 				if (!(!needle.contains("/") && "".equals(prefix) && e //$NON-NLS-1$ //$NON-NLS-2$
363 						.getCause() instanceof InvalidObjectIdException)) {
364 					throw e;
365 				}
366 			}
367 		}
368 		fireRefsChanged();
369 		return ref;
370 	}
371 
372 	/** {@inheritDoc} */
373 	@Override
374 	public Map<String, Ref> getRefs(String prefix) throws IOException {
375 		final RefList<LooseRef> oldLoose = looseRefs.get();
376 		LooseScanner scan = new LooseScanner(oldLoose);
377 		scan.scan(prefix);
378 		final RefList<Ref> packed = getPackedRefs();
379 
380 		RefList<LooseRef> loose;
381 		if (scan.newLoose != null) {
382 			scan.newLoose.sort();
383 			loose = scan.newLoose.toRefList();
384 			if (looseRefs.compareAndSet(oldLoose, loose))
385 				modCnt.incrementAndGet();
386 		} else
387 			loose = oldLoose;
388 		fireRefsChanged();
389 
390 		RefList.Builder<Ref> symbolic = scan.symbolic;
391 		for (int idx = 0; idx < symbolic.size();) {
392 			final Ref symbolicRef = symbolic.get(idx);
393 			final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
394 			if (resolvedRef != null && resolvedRef.getObjectId() != null) {
395 				symbolic.set(idx, resolvedRef);
396 				idx++;
397 			} else {
398 				// A broken symbolic reference, we have to drop it from the
399 				// collections the client is about to receive. Should be a
400 				// rare occurrence so pay a copy penalty.
401 				symbolic.remove(idx);
402 				final int toRemove = loose.find(symbolicRef.getName());
403 				if (0 <= toRemove)
404 					loose = loose.remove(toRemove);
405 			}
406 		}
407 		symbolic.sort();
408 
409 		return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
410 	}
411 
412 	/** {@inheritDoc} */
413 	@Override
414 	public List<Ref> getAdditionalRefs() throws IOException {
415 		List<Ref> ret = new LinkedList<>();
416 		for (String name : additionalRefsNames) {
417 			Ref r = getRef(name);
418 			if (r != null)
419 				ret.add(r);
420 		}
421 		return ret;
422 	}
423 
424 	@SuppressWarnings("unchecked")
425 	private RefList<Ref> upcast(RefList<? extends Ref> loose) {
426 		return (RefList<Ref>) loose;
427 	}
428 
429 	private class LooseScanner {
430 		private final RefList<LooseRef> curLoose;
431 
432 		private int curIdx;
433 
434 		final RefList.Builder<Ref> symbolic = new RefList.Builder<>(4);
435 
436 		RefList.Builder<LooseRef> newLoose;
437 
438 		LooseScanner(RefList<LooseRef> curLoose) {
439 			this.curLoose = curLoose;
440 		}
441 
442 		void scan(String prefix) {
443 			if (ALL.equals(prefix)) {
444 				scanOne(HEAD);
445 				scanTree(R_REFS, refsDir);
446 
447 				// If any entries remain, they are deleted, drop them.
448 				if (newLoose == null && curIdx < curLoose.size())
449 					newLoose = curLoose.copy(curIdx);
450 
451 			} else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { //$NON-NLS-1$
452 				curIdx = -(curLoose.find(prefix) + 1);
453 				File dir = new File(refsDir, prefix.substring(R_REFS.length()));
454 				scanTree(prefix, dir);
455 
456 				// Skip over entries still within the prefix; these have
457 				// been removed from the directory.
458 				while (curIdx < curLoose.size()) {
459 					if (!curLoose.get(curIdx).getName().startsWith(prefix))
460 						break;
461 					if (newLoose == null)
462 						newLoose = curLoose.copy(curIdx);
463 					curIdx++;
464 				}
465 
466 				// Keep any entries outside of the prefix space, we
467 				// do not know anything about their status.
468 				if (newLoose != null) {
469 					while (curIdx < curLoose.size())
470 						newLoose.add(curLoose.get(curIdx++));
471 				}
472 			}
473 		}
474 
475 		private boolean scanTree(String prefix, File dir) {
476 			final String[] entries = dir.list(LockFile.FILTER);
477 			if (entries == null) // not a directory or an I/O error
478 				return false;
479 			if (0 < entries.length) {
480 				for (int i = 0; i < entries.length; ++i) {
481 					String e = entries[i];
482 					File f = new File(dir, e);
483 					if (f.isDirectory())
484 						entries[i] += '/';
485 				}
486 				Arrays.sort(entries);
487 				for (String name : entries) {
488 					if (name.charAt(name.length() - 1) == '/')
489 						scanTree(prefix + name, new File(dir, name));
490 					else
491 						scanOne(prefix + name);
492 				}
493 			}
494 			return true;
495 		}
496 
497 		private void scanOne(String name) {
498 			LooseRef cur;
499 
500 			if (curIdx < curLoose.size()) {
501 				do {
502 					cur = curLoose.get(curIdx);
503 					int cmp = RefComparator.compareTo(cur, name);
504 					if (cmp < 0) {
505 						// Reference is not loose anymore, its been deleted.
506 						// Skip the name in the new result list.
507 						if (newLoose == null)
508 							newLoose = curLoose.copy(curIdx);
509 						curIdx++;
510 						cur = null;
511 						continue;
512 					}
513 
514 					if (cmp > 0) // Newly discovered loose reference.
515 						cur = null;
516 					break;
517 				} while (curIdx < curLoose.size());
518 			} else
519 				cur = null; // Newly discovered loose reference.
520 
521 			LooseRef n;
522 			try {
523 				n = scanRef(cur, name);
524 			} catch (IOException notValid) {
525 				n = null;
526 			}
527 
528 			if (n != null) {
529 				if (cur != n && newLoose == null)
530 					newLoose = curLoose.copy(curIdx);
531 				if (newLoose != null)
532 					newLoose.add(n);
533 				if (n.isSymbolic())
534 					symbolic.add(n);
535 			} else if (cur != null) {
536 				// Tragically, this file is no longer a loose reference.
537 				// Kill our cached entry of it.
538 				if (newLoose == null)
539 					newLoose = curLoose.copy(curIdx);
540 			}
541 
542 			if (cur != null)
543 				curIdx++;
544 		}
545 	}
546 
547 	/** {@inheritDoc} */
548 	@Override
549 	public Ref peel(Ref ref) throws IOException {
550 		final Ref leaf = ref.getLeaf();
551 		if (leaf.isPeeled() || leaf.getObjectId() == null)
552 			return ref;
553 
554 		ObjectIdRef newLeaf = doPeel(leaf);
555 
556 		// Try to remember this peeling in the cache, so we don't have to do
557 		// it again in the future, but only if the reference is unchanged.
558 		if (leaf.getStorage().isLoose()) {
559 			RefList<LooseRef> curList = looseRefs.get();
560 			int idx = curList.find(leaf.getName());
561 			if (0 <= idx && curList.get(idx) == leaf) {
562 				LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
563 				RefList<LooseRef> newList = curList.set(idx, asPeeled);
564 				looseRefs.compareAndSet(curList, newList);
565 			}
566 		}
567 
568 		return recreate(ref, newLeaf);
569 	}
570 
571 	private ObjectIdRef doPeel(Ref leaf) throws MissingObjectException,
572 			IOException {
573 		try (RevWalk rw = new RevWalk(getRepository())) {
574 			RevObject obj = rw.parseAny(leaf.getObjectId());
575 			if (obj instanceof RevTag) {
576 				return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
577 						.getName(), leaf.getObjectId(), rw.peel(obj).copy());
578 			} else {
579 				return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
580 						.getName(), leaf.getObjectId());
581 			}
582 		}
583 	}
584 
585 	private static Ref recreate(Ref old, ObjectIdRef leaf) {
586 		if (old.isSymbolic()) {
587 			Ref dst = recreate(old.getTarget(), leaf);
588 			return new SymbolicRef(old.getName(), dst);
589 		}
590 		return leaf;
591 	}
592 
593 	void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
594 			String target) {
595 		putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
596 		fireRefsChanged();
597 	}
598 
599 	/** {@inheritDoc} */
600 	@Override
601 	public RefDirectoryUpdate newUpdate(String name, boolean detach)
602 			throws IOException {
603 		boolean detachingSymbolicRef = false;
604 		final RefList<Ref> packed = getPackedRefs();
605 		Ref ref = readRef(name, packed);
606 		if (ref != null)
607 			ref = resolve(ref, 0, null, null, packed);
608 		if (ref == null)
609 			ref = new ObjectIdRef.Unpeeled(NEW, name, null);
610 		else {
611 			detachingSymbolicRef = detach && ref.isSymbolic();
612 		}
613 		RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
614 		if (detachingSymbolicRef)
615 			refDirUpdate.setDetachingSymbolicRef();
616 		return refDirUpdate;
617 	}
618 
619 	/** {@inheritDoc} */
620 	@Override
621 	public RefDirectoryRename newRename(String fromName, String toName)
622 			throws IOException {
623 		RefDirectoryUpdate from = newUpdate(fromName, false);
624 		RefDirectoryUpdate to = newUpdate(toName, false);
625 		return new RefDirectoryRename(from, to);
626 	}
627 
628 	/** {@inheritDoc} */
629 	@Override
630 	public PackedBatchRefUpdate newBatchUpdate() {
631 		return new PackedBatchRefUpdate(this);
632 	}
633 
634 	/** {@inheritDoc} */
635 	@Override
636 	public boolean performsAtomicTransactions() {
637 		return true;
638 	}
639 
640 	void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
641 		final ObjectId target = update.getNewObjectId().copy();
642 		final Ref leaf = update.getRef().getLeaf();
643 		putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
644 	}
645 
646 	private void putLooseRef(LooseRef ref) {
647 		RefList<LooseRef> cList, nList;
648 		do {
649 			cList = looseRefs.get();
650 			nList = cList.put(ref);
651 		} while (!looseRefs.compareAndSet(cList, nList));
652 		modCnt.incrementAndGet();
653 		fireRefsChanged();
654 	}
655 
656 	void delete(RefDirectoryUpdate update) throws IOException {
657 		Ref dst = update.getRef();
658 		if (!update.isDetachingSymbolicRef()) {
659 			dst = dst.getLeaf();
660 		}
661 		String name = dst.getName();
662 
663 		// Write the packed-refs file using an atomic update. We might
664 		// wind up reading it twice, before and after the lock, to ensure
665 		// we don't miss an edit made externally.
666 		final PackedRefList packed = getPackedRefs();
667 		if (packed.contains(name)) {
668 			inProcessPackedRefsLock.lock();
669 			try {
670 				LockFile lck = lockPackedRefsOrThrow();
671 				try {
672 					PackedRefList cur = readPackedRefs();
673 					int idx = cur.find(name);
674 					if (0 <= idx) {
675 						commitPackedRefs(lck, cur.remove(idx), packed, true);
676 					}
677 				} finally {
678 					lck.unlock();
679 				}
680 			} finally {
681 				inProcessPackedRefsLock.unlock();
682 			}
683 		}
684 
685 		RefList<LooseRef> curLoose, newLoose;
686 		do {
687 			curLoose = looseRefs.get();
688 			int idx = curLoose.find(name);
689 			if (idx < 0)
690 				break;
691 			newLoose = curLoose.remove(idx);
692 		} while (!looseRefs.compareAndSet(curLoose, newLoose));
693 
694 		int levels = levelsIn(name) - 2;
695 		delete(logFor(name), levels);
696 		if (dst.getStorage().isLoose()) {
697 			update.unlock();
698 			delete(fileFor(name), levels);
699 		}
700 
701 		modCnt.incrementAndGet();
702 		fireRefsChanged();
703 	}
704 
705 	/**
706 	 * Adds a set of refs to the set of packed-refs. Only non-symbolic refs are
707 	 * added. If a ref with the given name already existed in packed-refs it is
708 	 * updated with the new value. Each loose ref which was added to the
709 	 * packed-ref file is deleted. If a given ref can't be locked it will not be
710 	 * added to the pack file.
711 	 *
712 	 * @param refs
713 	 *            the refs to be added. Must be fully qualified.
714 	 * @throws java.io.IOException
715 	 */
716 	public void pack(List<String> refs) throws IOException {
717 		pack(refs, Collections.emptyMap());
718 	}
719 
720 	PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
721 		return pack(heldLocks.keySet(), heldLocks);
722 	}
723 
724 	private PackedRefList pack(Collection<String> refs,
725 			Map<String, LockFile> heldLocks) throws IOException {
726 		for (LockFile ol : heldLocks.values()) {
727 			ol.requireLock();
728 		}
729 		if (refs.size() == 0) {
730 			return null;
731 		}
732 		FS fs = parent.getFS();
733 
734 		// Lock the packed refs file and read the content
735 		inProcessPackedRefsLock.lock();
736 		try {
737 			LockFile lck = lockPackedRefsOrThrow();
738 			try {
739 				final PackedRefList packed = getPackedRefs();
740 				RefList<Ref> cur = readPackedRefs();
741 
742 				// Iterate over all refs to be packed
743 				boolean dirty = false;
744 				for (String refName : refs) {
745 					Ref oldRef = readRef(refName, cur);
746 					if (oldRef == null) {
747 						continue; // A non-existent ref is already correctly packed.
748 					}
749 					if (oldRef.isSymbolic()) {
750 						continue; // can't pack symbolic refs
751 					}
752 					// Add/Update it to packed-refs
753 					Ref newRef = peeledPackedRef(oldRef);
754 					if (newRef == oldRef) {
755 						// No-op; peeledPackedRef returns the input ref only if it's already
756 						// packed, and readRef returns a packed ref only if there is no
757 						// loose ref.
758 						continue;
759 					}
760 
761 					dirty = true;
762 					int idx = cur.find(refName);
763 					if (idx >= 0) {
764 						cur = cur.set(idx, newRef);
765 					} else {
766 						cur = cur.add(idx, newRef);
767 					}
768 				}
769 				if (!dirty) {
770 					// All requested refs were already packed accurately
771 					return packed;
772 				}
773 
774 				// The new content for packed-refs is collected. Persist it.
775 				PackedRefList result = commitPackedRefs(lck, cur, packed,
776 						false);
777 
778 				// Now delete the loose refs which are now packed
779 				for (String refName : refs) {
780 					// Lock the loose ref
781 					File refFile = fileFor(refName);
782 					if (!fs.exists(refFile)) {
783 						continue;
784 					}
785 
786 					LockFile rLck = heldLocks.get(refName);
787 					boolean shouldUnlock;
788 					if (rLck == null) {
789 						rLck = new LockFile(refFile);
790 						if (!rLck.lock()) {
791 							continue;
792 						}
793 						shouldUnlock = true;
794 					} else {
795 						shouldUnlock = false;
796 					}
797 
798 					try {
799 						LooseRef currentLooseRef = scanRef(null, refName);
800 						if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
801 							continue;
802 						}
803 						Ref packedRef = cur.get(refName);
804 						ObjectId clr_oid = currentLooseRef.getObjectId();
805 						if (clr_oid != null
806 								&& clr_oid.equals(packedRef.getObjectId())) {
807 							RefList<LooseRef> curLoose, newLoose;
808 							do {
809 								curLoose = looseRefs.get();
810 								int idx = curLoose.find(refName);
811 								if (idx < 0) {
812 									break;
813 								}
814 								newLoose = curLoose.remove(idx);
815 							} while (!looseRefs.compareAndSet(curLoose, newLoose));
816 							int levels = levelsIn(refName) - 2;
817 							delete(refFile, levels, rLck);
818 						}
819 					} finally {
820 						if (shouldUnlock) {
821 							rLck.unlock();
822 						}
823 					}
824 				}
825 				// Don't fire refsChanged. The refs have not change, only their
826 				// storage.
827 				return result;
828 			} finally {
829 				lck.unlock();
830 			}
831 		} finally {
832 			inProcessPackedRefsLock.unlock();
833 		}
834 	}
835 
836 	@Nullable
837 	LockFile lockPackedRefs() throws IOException {
838 		LockFile lck = new LockFile(packedRefsFile);
839 		for (int ms : getRetrySleepMs()) {
840 			sleep(ms);
841 			if (lck.lock()) {
842 				return lck;
843 			}
844 		}
845 		return null;
846 	}
847 
848 	private LockFile lockPackedRefsOrThrow() throws IOException {
849 		LockFile lck = lockPackedRefs();
850 		if (lck == null) {
851 			throw new LockFailedException(packedRefsFile);
852 		}
853 		return lck;
854 	}
855 
856 	/**
857 	 * Make sure a ref is peeled and has the Storage PACKED. If the given ref
858 	 * has this attributes simply return it. Otherwise create a new peeled
859 	 * {@link ObjectIdRef} where Storage is set to PACKED.
860 	 *
861 	 * @param f
862 	 * @return a ref for Storage PACKED having the same name, id, peeledId as f
863 	 * @throws MissingObjectException
864 	 * @throws IOException
865 	 */
866 	private Ref peeledPackedRef(Ref f)
867 			throws MissingObjectException, IOException {
868 		if (f.getStorage().isPacked() && f.isPeeled()) {
869 			return f;
870 		}
871 		if (!f.isPeeled()) {
872 			f = peel(f);
873 		}
874 		ObjectId peeledObjectId = f.getPeeledObjectId();
875 		if (peeledObjectId != null) {
876 			return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
877 					f.getObjectId(), peeledObjectId);
878 		} else {
879 			return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
880 					f.getObjectId());
881 		}
882 	}
883 
884 	void log(boolean force, RefUpdate update, String msg, boolean deref)
885 			throws IOException {
886 		newLogWriter(force).log(update, msg, deref);
887 	}
888 
889 	private Ref resolve(final Ref ref, int depth, String prefix,
890 			RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
891 		if (ref.isSymbolic()) {
892 			Ref dst = ref.getTarget();
893 
894 			if (MAX_SYMBOLIC_REF_DEPTH <= depth)
895 				return null; // claim it doesn't exist
896 
897 			// If the cached value can be assumed to be current due to a
898 			// recent scan of the loose directory, use it.
899 			if (loose != null && dst.getName().startsWith(prefix)) {
900 				int idx;
901 				if (0 <= (idx = loose.find(dst.getName())))
902 					dst = loose.get(idx);
903 				else if (0 <= (idx = packed.find(dst.getName())))
904 					dst = packed.get(idx);
905 				else
906 					return ref;
907 			} else {
908 				dst = readRef(dst.getName(), packed);
909 				if (dst == null)
910 					return ref;
911 			}
912 
913 			dst = resolve(dst, depth + 1, prefix, loose, packed);
914 			if (dst == null)
915 				return null;
916 			return new SymbolicRef(ref.getName(), dst);
917 		}
918 		return ref;
919 	}
920 
921 	PackedRefList getPackedRefs() throws IOException {
922 		boolean trustFolderStat = getRepository().getConfig().getBoolean(
923 				ConfigConstants.CONFIG_CORE_SECTION,
924 				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
925 
926 		final PackedRefList curList = packedRefs.get();
927 		if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
928 			return curList;
929 		}
930 
931 		final PackedRefList newList = readPackedRefs();
932 		if (packedRefs.compareAndSet(curList, newList)
933 				&& !curList.id.equals(newList.id)) {
934 			modCnt.incrementAndGet();
935 		}
936 		return newList;
937 	}
938 
939 	private PackedRefList readPackedRefs() throws IOException {
940 		int maxStaleRetries = 5;
941 		int retries = 0;
942 		while (true) {
943 			final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
944 			final MessageDigest digest = Constants.newMessageDigest();
945 			try (BufferedReader br = new BufferedReader(new InputStreamReader(
946 					new DigestInputStream(new FileInputStream(packedRefsFile),
947 							digest),
948 					UTF_8))) {
949 				try {
950 					return new PackedRefList(parsePackedRefs(br), snapshot,
951 							ObjectId.fromRaw(digest.digest()));
952 				} catch (IOException e) {
953 					if (FileUtils.isStaleFileHandleInCausalChain(e)
954 							&& retries < maxStaleRetries) {
955 						if (LOG.isDebugEnabled()) {
956 							LOG.debug(MessageFormat.format(
957 									JGitText.get().packedRefsHandleIsStale,
958 									Integer.valueOf(retries)), e);
959 						}
960 						retries++;
961 						continue;
962 					}
963 					throw e;
964 				}
965 			} catch (FileNotFoundException noPackedRefs) {
966 				if (packedRefsFile.exists()) {
967 					throw noPackedRefs;
968 				}
969 				// Ignore it and leave the new list empty.
970 				return NO_PACKED_REFS;
971 			}
972 		}
973 	}
974 
975 	private RefList<Ref> parsePackedRefs(BufferedReader br)
976 			throws IOException {
977 		RefList.Builder<Ref> all = new RefList.Builder<>();
978 		Ref last = null;
979 		boolean peeled = false;
980 		boolean needSort = false;
981 
982 		String p;
983 		while ((p = br.readLine()) != null) {
984 			if (p.charAt(0) == '#') {
985 				if (p.startsWith(PACKED_REFS_HEADER)) {
986 					p = p.substring(PACKED_REFS_HEADER.length());
987 					peeled = p.contains(PACKED_REFS_PEELED);
988 				}
989 				continue;
990 			}
991 
992 			if (p.charAt(0) == '^') {
993 				if (last == null)
994 					throw new IOException(JGitText.get().peeledLineBeforeRef);
995 
996 				ObjectId id = ObjectId.fromString(p.substring(1));
997 				last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
998 						.getObjectId(), id);
999 				all.set(all.size() - 1, last);
1000 				continue;
1001 			}
1002 
1003 			int sp = p.indexOf(' ');
1004 			if (sp < 0) {
1005 				throw new IOException(MessageFormat.format(
1006 						JGitText.get().packedRefsCorruptionDetected,
1007 						packedRefsFile.getAbsolutePath()));
1008 			}
1009 			ObjectId id = ObjectId.fromString(p.substring(0, sp));
1010 			String name = copy(p, sp + 1, p.length());
1011 			ObjectIdRef cur;
1012 			if (peeled)
1013 				cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
1014 			else
1015 				cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
1016 			if (last != null && RefComparator.compareTo(last, cur) > 0)
1017 				needSort = true;
1018 			all.add(cur);
1019 			last = cur;
1020 		}
1021 
1022 		if (needSort)
1023 			all.sort();
1024 		return all.toRefList();
1025 	}
1026 
1027 	private static String copy(String src, int off, int end) {
1028 		// Don't use substring since it could leave a reference to the much
1029 		// larger existing string. Force construction of a full new object.
1030 		return new StringBuilder(end - off).append(src, off, end).toString();
1031 	}
1032 
1033 	PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
1034 			final PackedRefList oldPackedList, boolean changed)
1035 			throws IOException {
1036 		// Can't just return packedRefs.get() from this method; it might have been
1037 		// updated again after writePackedRefs() returns.
1038 		AtomicReference<PackedRefList> result = new AtomicReference<>();
1039 		new RefWriter(refs) {
1040 			@Override
1041 			protected void writeFile(String name, byte[] content)
1042 					throws IOException {
1043 				lck.setFSync(true);
1044 				lck.setNeedSnapshot(true);
1045 				try {
1046 					lck.write(content);
1047 				} catch (IOException ioe) {
1048 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
1049 				}
1050 				try {
1051 					lck.waitForStatChange();
1052 				} catch (InterruptedException e) {
1053 					lck.unlock();
1054 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name));
1055 				}
1056 				if (!lck.commit())
1057 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
1058 
1059 				byte[] digest = Constants.newMessageDigest().digest(content);
1060 				PackedRefList newPackedList = new PackedRefList(
1061 						refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
1062 
1063 				// This thread holds the file lock, so no other thread or process should
1064 				// be able to modify the packed-refs file on disk. If the list changed,
1065 				// it means something is very wrong, so throw an exception.
1066 				//
1067 				// However, we can't use a naive compareAndSet to check whether the
1068 				// update was successful, because another thread might _read_ the
1069 				// packed refs file that was written out by this thread while holding
1070 				// the lock, and update the packedRefs reference to point to that. So
1071 				// compare the actual contents instead.
1072 				PackedRefList afterUpdate = packedRefs.updateAndGet(
1073 						p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
1074 				if (!afterUpdate.id.equals(newPackedList.id)) {
1075 					throw new ObjectWritingException(
1076 							MessageFormat.format(JGitText.get().unableToWrite, name));
1077 				}
1078 				if (changed) {
1079 					modCnt.incrementAndGet();
1080 				}
1081 				result.set(newPackedList);
1082 			}
1083 		}.writePackedRefs();
1084 		return result.get();
1085 	}
1086 
1087 	private Ref readRef(String name, RefList<Ref> packed) throws IOException {
1088 		final RefList<LooseRef> curList = looseRefs.get();
1089 		final int idx = curList.find(name);
1090 		if (0 <= idx) {
1091 			final LooseRef o = curList.get(idx);
1092 			final LooseRef n = scanRef(o, name);
1093 			if (n == null) {
1094 				if (looseRefs.compareAndSet(curList, curList.remove(idx)))
1095 					modCnt.incrementAndGet();
1096 				return packed.get(name);
1097 			}
1098 
1099 			if (o == n)
1100 				return n;
1101 			if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
1102 				modCnt.incrementAndGet();
1103 			return n;
1104 		}
1105 
1106 		final LooseRef n = scanRef(null, name);
1107 		if (n == null)
1108 			return packed.get(name);
1109 
1110 		// check whether the found new ref is the an additional ref. These refs
1111 		// should not go into looseRefs
1112 		for (int i = 0; i < additionalRefsNames.length; i++)
1113 			if (name.equals(additionalRefsNames[i]))
1114 				return n;
1115 
1116 		if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
1117 			modCnt.incrementAndGet();
1118 		return n;
1119 	}
1120 
1121 	LooseRef scanRef(LooseRef ref, String name) throws IOException {
1122 		final File path = fileFor(name);
1123 		FileSnapshot currentSnapshot = null;
1124 
1125 		if (ref != null) {
1126 			currentSnapshot = ref.getSnapShot();
1127 			if (!currentSnapshot.isModified(path))
1128 				return ref;
1129 			name = ref.getName();
1130 		}
1131 
1132 		final int limit = 4096;
1133 		final byte[] buf;
1134 		FileSnapshot otherSnapshot = FileSnapshot.save(path);
1135 		try {
1136 			buf = IO.readSome(path, limit);
1137 		} catch (FileNotFoundException noFile) {
1138 			if (path.exists() && path.isFile()) {
1139 				throw noFile;
1140 			}
1141 			return null; // doesn't exist or no file; not a reference.
1142 		}
1143 
1144 		int n = buf.length;
1145 		if (n == 0)
1146 			return null; // empty file; not a reference.
1147 
1148 		if (isSymRef(buf, n)) {
1149 			if (n == limit)
1150 				return null; // possibly truncated ref
1151 
1152 			// trim trailing whitespace
1153 			while (0 < n && Character.isWhitespace(buf[n - 1]))
1154 				n--;
1155 			if (n < 6) {
1156 				String content = RawParseUtils.decode(buf, 0, n);
1157 				throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
1158 			}
1159 			final String target = RawParseUtils.decode(buf, 5, n);
1160 			if (ref != null && ref.isSymbolic()
1161 					&& ref.getTarget().getName().equals(target)) {
1162 				assert(currentSnapshot != null);
1163 				currentSnapshot.setClean(otherSnapshot);
1164 				return ref;
1165 			}
1166 			return newSymbolicRef(otherSnapshot, name, target);
1167 		}
1168 
1169 		if (n < OBJECT_ID_STRING_LENGTH)
1170 			return null; // impossibly short object identifier; not a reference.
1171 
1172 		final ObjectId id;
1173 		try {
1174 			id = ObjectId.fromString(buf, 0);
1175 			if (ref != null && !ref.isSymbolic()
1176 					&& id.equals(ref.getTarget().getObjectId())) {
1177 				assert(currentSnapshot != null);
1178 				currentSnapshot.setClean(otherSnapshot);
1179 				return ref;
1180 			}
1181 
1182 		} catch (IllegalArgumentException notRef) {
1183 			while (0 < n && Character.isWhitespace(buf[n - 1]))
1184 				n--;
1185 			String content = RawParseUtils.decode(buf, 0, n);
1186 
1187 			throw new IOException(MessageFormat.format(JGitText.get().notARef,
1188 					name, content), notRef);
1189 		}
1190 		return new LooseUnpeeled(otherSnapshot, name, id);
1191 	}
1192 
1193 	private static boolean isSymRef(byte[] buf, int n) {
1194 		if (n < 6)
1195 			return false;
1196 		return /**/buf[0] == 'r' //
1197 				&& buf[1] == 'e' //
1198 				&& buf[2] == 'f' //
1199 				&& buf[3] == ':' //
1200 				&& buf[4] == ' ';
1201 	}
1202 
1203 	/**
1204 	 * Detect if we are in a clone command execution
1205 	 *
1206 	 * @return {@code true} if we are currently cloning a repository
1207 	 * @throws IOException
1208 	 */
1209 	boolean isInClone() throws IOException {
1210 		return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
1211 	}
1212 
1213 	private boolean hasDanglingHead() throws IOException {
1214 		Ref head = exactRef(Constants.HEAD);
1215 		if (head != null) {
1216 			ObjectId id = head.getObjectId();
1217 			return id == null || id.equals(ObjectId.zeroId());
1218 		}
1219 		return false;
1220 	}
1221 
1222 	private boolean hasLooseRef() throws IOException {
1223 		try (Stream<Path> stream = Files.walk(refsDir.toPath())) {
1224 			return stream.anyMatch(Files::isRegularFile);
1225 		}
1226 	}
1227 
1228 	/** If the parent should fire listeners, fires them. */
1229 	void fireRefsChanged() {
1230 		final int last = lastNotifiedModCnt.get();
1231 		final int curr = modCnt.get();
1232 		if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
1233 			parent.fireEvent(new RefsChangedEvent());
1234 	}
1235 
1236 	/**
1237 	 * Create a reference update to write a temporary reference.
1238 	 *
1239 	 * @return an update for a new temporary reference.
1240 	 * @throws IOException
1241 	 *             a temporary name cannot be allocated.
1242 	 */
1243 	RefDirectoryUpdate newTemporaryUpdate() throws IOException {
1244 		File tmp = File.createTempFile("renamed_", "_ref", refsDir); //$NON-NLS-1$ //$NON-NLS-2$
1245 		String name = Constants.R_REFS + tmp.getName();
1246 		Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
1247 		return new RefDirectoryUpdate(this, ref);
1248 	}
1249 
1250 	/**
1251 	 * Locate the file on disk for a single reference name.
1252 	 *
1253 	 * @param name
1254 	 *            name of the ref, relative to the Git repository top level
1255 	 *            directory (so typically starts with refs/).
1256 	 * @return the loose file location.
1257 	 */
1258 	File fileFor(String name) {
1259 		if (name.startsWith(R_REFS)) {
1260 			name = name.substring(R_REFS.length());
1261 			return new File(refsDir, name);
1262 		}
1263 		return new File(gitDir, name);
1264 	}
1265 
1266 	static int levelsIn(String name) {
1267 		int count = 0;
1268 		for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
1269 			count++;
1270 		return count;
1271 	}
1272 
1273 	static void delete(File file, int depth) throws IOException {
1274 		delete(file, depth, null);
1275 	}
1276 
1277 	private static void delete(File file, int depth, LockFile rLck)
1278 			throws IOException {
1279 		if (!file.delete() && file.isFile()) {
1280 			throw new IOException(MessageFormat.format(
1281 					JGitText.get().fileCannotBeDeleted, file));
1282 		}
1283 
1284 		if (rLck != null) {
1285 			rLck.unlock(); // otherwise cannot delete dir below
1286 		}
1287 		File dir = file.getParentFile();
1288 		for (int i = 0; i < depth; ++i) {
1289 			try {
1290 				Files.deleteIfExists(dir.toPath());
1291 			} catch (DirectoryNotEmptyException e) {
1292 				// Don't log; normal case when there are other refs with the
1293 				// same prefix
1294 				break;
1295 			} catch (IOException e) {
1296 				LOG.warn(MessageFormat.format(JGitText.get().unableToRemovePath,
1297 						dir), e);
1298 				break;
1299 			}
1300 			dir = dir.getParentFile();
1301 		}
1302 	}
1303 
1304 	/**
1305 	 * Get times to sleep while retrying a possibly contentious operation.
1306 	 * <p>
1307 	 * For retrying an operation that might have high contention, such as locking
1308 	 * the {@code packed-refs} file, the caller may implement a retry loop using
1309 	 * the returned values:
1310 	 *
1311 	 * <pre>
1312 	 * for (int toSleepMs : getRetrySleepMs()) {
1313 	 *   sleep(toSleepMs);
1314 	 *   if (isSuccessful(doSomething())) {
1315 	 *     return success;
1316 	 *   }
1317 	 * }
1318 	 * return failure;
1319 	 * </pre>
1320 	 *
1321 	 * The first value in the returned iterable is 0, and the caller should treat
1322 	 * a fully-consumed iterator as a timeout.
1323 	 *
1324 	 * @return iterable of times, in milliseconds, that the caller should sleep
1325 	 *         before attempting an operation.
1326 	 */
1327 	Iterable<Integer> getRetrySleepMs() {
1328 		return retrySleepMs;
1329 	}
1330 
1331 	void setRetrySleepMs(List<Integer> retrySleepMs) {
1332 		if (retrySleepMs == null || retrySleepMs.isEmpty()
1333 				|| retrySleepMs.get(0).intValue() != 0) {
1334 			throw new IllegalArgumentException();
1335 		}
1336 		this.retrySleepMs = retrySleepMs;
1337 	}
1338 
1339 	/**
1340 	 * Sleep with {@link Thread#sleep(long)}, converting {@link
1341 	 * InterruptedException} to {@link InterruptedIOException}.
1342 	 *
1343 	 * @param ms
1344 	 *            time to sleep, in milliseconds; zero or negative is a no-op.
1345 	 * @throws InterruptedIOException
1346 	 *             if sleeping was interrupted.
1347 	 */
1348 	static void sleep(long ms) throws InterruptedIOException {
1349 		if (ms <= 0) {
1350 			return;
1351 		}
1352 		try {
1353 			Thread.sleep(ms);
1354 		} catch (InterruptedException e) {
1355 			InterruptedIOException ie = new InterruptedIOException();
1356 			ie.initCause(e);
1357 			throw ie;
1358 		}
1359 	}
1360 
1361 	static class PackedRefList extends RefList<Ref> {
1362 
1363 		private final FileSnapshot snapshot;
1364 
1365 		private final ObjectId id;
1366 
1367 		private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1368 			super(src);
1369 			snapshot = s;
1370 			id = i;
1371 		}
1372 	}
1373 
1374 	private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1375 			RefList.emptyList(), FileSnapshot.MISSING_FILE,
1376 			ObjectId.zeroId());
1377 
1378 	private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
1379 			String name, String target) {
1380 		Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
1381 		return new LooseSymbolicRef(snapshot, name, dst);
1382 	}
1383 
1384 	private static interface LooseRef extends Ref {
1385 		FileSnapshot getSnapShot();
1386 
1387 		LooseRef peel(ObjectIdRef newLeaf);
1388 	}
1389 
1390 	private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
1391 			implements LooseRef {
1392 		private final FileSnapshot snapShot;
1393 
1394 		LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
1395 				@NonNull ObjectId id, @NonNull ObjectId p) {
1396 			super(LOOSE, refName, id, p);
1397 			this.snapShot = snapshot;
1398 		}
1399 
1400 		@Override
1401 		public FileSnapshot getSnapShot() {
1402 			return snapShot;
1403 		}
1404 
1405 		@Override
1406 		public LooseRef peel(ObjectIdRef newLeaf) {
1407 			return this;
1408 		}
1409 	}
1410 
1411 	private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
1412 			implements LooseRef {
1413 		private final FileSnapshot snapShot;
1414 
1415 		LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
1416 				@NonNull ObjectId id) {
1417 			super(LOOSE, refName, id);
1418 			this.snapShot = snapshot;
1419 		}
1420 
1421 		@Override
1422 		public FileSnapshot getSnapShot() {
1423 			return snapShot;
1424 		}
1425 
1426 		@Override
1427 		public LooseRef peel(ObjectIdRef newLeaf) {
1428 			return this;
1429 		}
1430 	}
1431 
1432 	private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
1433 			implements LooseRef {
1434 		private FileSnapshot snapShot;
1435 
1436 		LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
1437 				@NonNull ObjectId id) {
1438 			super(LOOSE, refName, id);
1439 			this.snapShot = snapShot;
1440 		}
1441 
1442 		@Override
1443 		public FileSnapshot getSnapShot() {
1444 			return snapShot;
1445 		}
1446 
1447 		@NonNull
1448 		@Override
1449 		public ObjectId getObjectId() {
1450 			ObjectId id = super.getObjectId();
1451 			assert id != null; // checked in constructor
1452 			return id;
1453 		}
1454 
1455 		@Override
1456 		public LooseRef peel(ObjectIdRef newLeaf) {
1457 			ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
1458 			ObjectId objectId = getObjectId();
1459 			if (peeledObjectId != null) {
1460 				return new LoosePeeledTag(snapShot, getName(),
1461 						objectId, peeledObjectId);
1462 			} else {
1463 				return new LooseNonTag(snapShot, getName(),
1464 						objectId);
1465 			}
1466 		}
1467 	}
1468 
1469 	private final static class LooseSymbolicRef extends SymbolicRef implements
1470 			LooseRef {
1471 		private final FileSnapshot snapShot;
1472 
1473 		LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
1474 				@NonNull Ref target) {
1475 			super(refName, target);
1476 			this.snapShot = snapshot;
1477 		}
1478 
1479 		@Override
1480 		public FileSnapshot getSnapShot() {
1481 			return snapShot;
1482 		}
1483 
1484 		@Override
1485 		public LooseRef peel(ObjectIdRef newLeaf) {
1486 			// We should never try to peel the symbolic references.
1487 			throw new UnsupportedOperationException();
1488 		}
1489 	}
1490 }