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