RefDirectory.java

  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> and others
  6.  *
  7.  * This program and the accompanying materials are made available under the
  8.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  9.  * https://www.eclipse.org/org/documents/edl-v10.php.
  10.  *
  11.  * SPDX-License-Identifier: BSD-3-Clause
  12.  */

  13. package org.eclipse.jgit.internal.storage.file;

  14. import static java.nio.charset.StandardCharsets.UTF_8;
  15. import static org.eclipse.jgit.lib.Constants.HEAD;
  16. import static org.eclipse.jgit.lib.Constants.LOGS;
  17. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
  18. import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
  19. import static org.eclipse.jgit.lib.Constants.R_HEADS;
  20. import static org.eclipse.jgit.lib.Constants.R_REFS;
  21. import static org.eclipse.jgit.lib.Constants.R_TAGS;
  22. import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
  23. import static org.eclipse.jgit.lib.Ref.Storage.NEW;
  24. import static org.eclipse.jgit.lib.Ref.Storage.PACKED;

  25. import java.io.BufferedReader;
  26. import java.io.File;
  27. import java.io.FileInputStream;
  28. import java.io.FileNotFoundException;
  29. import java.io.IOException;
  30. import java.io.InputStreamReader;
  31. import java.io.InterruptedIOException;
  32. import java.nio.file.DirectoryNotEmptyException;
  33. import java.nio.file.Files;
  34. import java.nio.file.Path;
  35. import java.security.DigestInputStream;
  36. import java.security.MessageDigest;
  37. import java.text.MessageFormat;
  38. import java.util.Arrays;
  39. import java.util.Collection;
  40. import java.util.Collections;
  41. import java.util.HashMap;
  42. import java.util.LinkedList;
  43. import java.util.List;
  44. import java.util.Map;
  45. import java.util.concurrent.atomic.AtomicInteger;
  46. import java.util.concurrent.atomic.AtomicReference;
  47. import java.util.concurrent.locks.ReentrantLock;
  48. import java.util.stream.Stream;

  49. import org.eclipse.jgit.annotations.NonNull;
  50. import org.eclipse.jgit.annotations.Nullable;
  51. import org.eclipse.jgit.errors.InvalidObjectIdException;
  52. import org.eclipse.jgit.errors.LockFailedException;
  53. import org.eclipse.jgit.errors.MissingObjectException;
  54. import org.eclipse.jgit.errors.ObjectWritingException;
  55. import org.eclipse.jgit.events.RefsChangedEvent;
  56. import org.eclipse.jgit.internal.JGitText;
  57. import org.eclipse.jgit.lib.ConfigConstants;
  58. import org.eclipse.jgit.lib.Constants;
  59. import org.eclipse.jgit.lib.ObjectId;
  60. import org.eclipse.jgit.lib.ObjectIdRef;
  61. import org.eclipse.jgit.lib.Ref;
  62. import org.eclipse.jgit.lib.RefComparator;
  63. import org.eclipse.jgit.lib.RefDatabase;
  64. import org.eclipse.jgit.lib.RefUpdate;
  65. import org.eclipse.jgit.lib.RefWriter;
  66. import org.eclipse.jgit.lib.Repository;
  67. import org.eclipse.jgit.lib.SymbolicRef;
  68. import org.eclipse.jgit.revwalk.RevObject;
  69. import org.eclipse.jgit.revwalk.RevTag;
  70. import org.eclipse.jgit.revwalk.RevWalk;
  71. import org.eclipse.jgit.util.FS;
  72. import org.eclipse.jgit.util.FileUtils;
  73. import org.eclipse.jgit.util.IO;
  74. import org.eclipse.jgit.util.RawParseUtils;
  75. import org.eclipse.jgit.util.RefList;
  76. import org.eclipse.jgit.util.RefMap;
  77. import org.slf4j.Logger;
  78. import org.slf4j.LoggerFactory;

  79. /**
  80.  * Traditional file system based {@link org.eclipse.jgit.lib.RefDatabase}.
  81.  * <p>
  82.  * This is the classical reference database representation for a Git repository.
  83.  * References are stored in two formats: loose, and packed.
  84.  * <p>
  85.  * Loose references are stored as individual files within the {@code refs/}
  86.  * directory. The file name matches the reference name and the file contents is
  87.  * the current {@link org.eclipse.jgit.lib.ObjectId} in string form.
  88.  * <p>
  89.  * Packed references are stored in a single text file named {@code packed-refs}.
  90.  * In the packed format, each reference is stored on its own line. This file
  91.  * reduces the number of files needed for large reference spaces, reducing the
  92.  * overall size of a Git repository on disk.
  93.  */
  94. public class RefDirectory extends RefDatabase {
  95.     private static final Logger LOG = LoggerFactory
  96.             .getLogger(RefDirectory.class);

  97.     /** Magic string denoting the start of a symbolic reference file. */
  98.     public static final String SYMREF = "ref: "; //$NON-NLS-1$

  99.     /** Magic string denoting the header of a packed-refs file. */
  100.     public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$

  101.     /** If in the header, denotes the file has peeled data. */
  102.     public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$

  103.     /** The names of the additional refs supported by this class */
  104.     private static final String[] additionalRefsNames = new String[] {
  105.             Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
  106.             Constants.CHERRY_PICK_HEAD };

  107.     @SuppressWarnings("boxing")
  108.     private static final List<Integer> RETRY_SLEEP_MS =
  109.             Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));

  110.     private final FileRepository parent;

  111.     private final File gitDir;

  112.     final File refsDir;

  113.     final File packedRefsFile;

  114.     final File logsDir;

  115.     final File logsRefsDir;

  116.     /**
  117.      * Immutable sorted list of loose references.
  118.      * <p>
  119.      * Symbolic references in this collection are stored unresolved, that is
  120.      * their target appears to be a new reference with no ObjectId. These are
  121.      * converted into resolved references during a get operation, ensuring the
  122.      * live value is always returned.
  123.      */
  124.     private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<>();

  125.     /** Immutable sorted list of packed references. */
  126.     final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();

  127.     /**
  128.      * Lock for coordinating operations within a single process that may contend
  129.      * on the {@code packed-refs} file.
  130.      * <p>
  131.      * All operations that write {@code packed-refs} must still acquire a
  132.      * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
  133.      * this lock, since there may be multiple {@link RefDirectory} instances or
  134.      * other processes operating on the same repo on disk.
  135.      * <p>
  136.      * This lock exists so multiple threads in the same process can wait in a fair
  137.      * queue without trying, failing, and retrying to acquire the on-disk lock. If
  138.      * {@code RepositoryCache} is used, this lock instance will be used by all
  139.      * threads.
  140.      */
  141.     final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);

  142.     /**
  143.      * Number of modifications made to this database.
  144.      * <p>
  145.      * This counter is incremented when a change is made, or detected from the
  146.      * filesystem during a read operation.
  147.      */
  148.     private final AtomicInteger modCnt = new AtomicInteger();

  149.     /**
  150.      * Last {@link #modCnt} that we sent to listeners.
  151.      * <p>
  152.      * This value is compared to {@link #modCnt}, and a notification is sent to
  153.      * the listeners only when it differs.
  154.      */
  155.     private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();

  156.     private List<Integer> retrySleepMs = RETRY_SLEEP_MS;

  157.     RefDirectory(FileRepository db) {
  158.         final FS fs = db.getFS();
  159.         parent = db;
  160.         gitDir = db.getDirectory();
  161.         refsDir = fs.resolve(gitDir, R_REFS);
  162.         logsDir = fs.resolve(gitDir, LOGS);
  163.         logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
  164.         packedRefsFile = fs.resolve(gitDir, PACKED_REFS);

  165.         looseRefs.set(RefList.<LooseRef> emptyList());
  166.         packedRefs.set(NO_PACKED_REFS);
  167.     }

  168.     Repository getRepository() {
  169.         return parent;
  170.     }

  171.     ReflogWriter newLogWriter(boolean force) {
  172.         return new ReflogWriter(this, force);
  173.     }

  174.     /**
  175.      * Locate the log file on disk for a single reference name.
  176.      *
  177.      * @param name
  178.      *            name of the ref, relative to the Git repository top level
  179.      *            directory (so typically starts with refs/).
  180.      * @return the log file location.
  181.      */
  182.     public File logFor(String name) {
  183.         if (name.startsWith(R_REFS)) {
  184.             name = name.substring(R_REFS.length());
  185.             return new File(logsRefsDir, name);
  186.         }
  187.         return new File(logsDir, name);
  188.     }

  189.     /** {@inheritDoc} */
  190.     @Override
  191.     public void create() throws IOException {
  192.         FileUtils.mkdir(refsDir);
  193.         FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
  194.         FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
  195.         newLogWriter(false).create();
  196.     }

  197.     /** {@inheritDoc} */
  198.     @Override
  199.     public void close() {
  200.         clearReferences();
  201.     }

  202.     private void clearReferences() {
  203.         looseRefs.set(RefList.<LooseRef> emptyList());
  204.         packedRefs.set(NO_PACKED_REFS);
  205.     }

  206.     /** {@inheritDoc} */
  207.     @Override
  208.     public void refresh() {
  209.         super.refresh();
  210.         clearReferences();
  211.     }

  212.     /** {@inheritDoc} */
  213.     @Override
  214.     public boolean isNameConflicting(String name) throws IOException {
  215.         RefList<Ref> packed = getPackedRefs();
  216.         RefList<LooseRef> loose = getLooseRefs();

  217.         // Cannot be nested within an existing reference.
  218.         int lastSlash = name.lastIndexOf('/');
  219.         while (0 < lastSlash) {
  220.             String needle = name.substring(0, lastSlash);
  221.             if (loose.contains(needle) || packed.contains(needle))
  222.                 return true;
  223.             lastSlash = name.lastIndexOf('/', lastSlash - 1);
  224.         }

  225.         // Cannot be the container of an existing reference.
  226.         String prefix = name + '/';
  227.         int idx;

  228.         idx = -(packed.find(prefix) + 1);
  229.         if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
  230.             return true;

  231.         idx = -(loose.find(prefix) + 1);
  232.         if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
  233.             return true;

  234.         return false;
  235.     }

  236.     private RefList<LooseRef> getLooseRefs() {
  237.         final RefList<LooseRef> oldLoose = looseRefs.get();

  238.         LooseScanner scan = new LooseScanner(oldLoose);
  239.         scan.scan(ALL);

  240.         RefList<LooseRef> loose;
  241.         if (scan.newLoose != null) {
  242.             loose = scan.newLoose.toRefList();
  243.             if (looseRefs.compareAndSet(oldLoose, loose))
  244.                 modCnt.incrementAndGet();
  245.         } else
  246.             loose = oldLoose;
  247.         return loose;
  248.     }

  249.     @Nullable
  250.     private Ref readAndResolve(String name, RefList<Ref> packed) throws IOException {
  251.         try {
  252.             Ref ref = readRef(name, packed);
  253.             if (ref != null) {
  254.                 ref = resolve(ref, 0, null, null, packed);
  255.             }
  256.             return ref;
  257.         } catch (IOException e) {
  258.             if (name.contains("/") //$NON-NLS-1$
  259.                     || !(e.getCause() instanceof InvalidObjectIdException)) {
  260.                 throw e;
  261.             }

  262.             // While looking for a ref outside of refs/ (e.g., 'config'), we
  263.             // found a non-ref file (e.g., a config file) instead.  Treat this
  264.             // as a ref-not-found condition.
  265.             return null;
  266.         }
  267.     }

  268.     /** {@inheritDoc} */
  269.     @Override
  270.     public Ref exactRef(String name) throws IOException {
  271.         try {
  272.             return readAndResolve(name, getPackedRefs());
  273.         } finally {
  274.             fireRefsChanged();
  275.         }
  276.     }

  277.     /** {@inheritDoc} */
  278.     @Override
  279.     @NonNull
  280.     public Map<String, Ref> exactRef(String... refs) throws IOException {
  281.         try {
  282.             RefList<Ref> packed = getPackedRefs();
  283.             Map<String, Ref> result = new HashMap<>(refs.length);
  284.             for (String name : refs) {
  285.                 Ref ref = readAndResolve(name, packed);
  286.                 if (ref != null) {
  287.                     result.put(name, ref);
  288.                 }
  289.             }
  290.             return result;
  291.         } finally {
  292.             fireRefsChanged();
  293.         }
  294.     }

  295.     /** {@inheritDoc} */
  296.     @Override
  297.     @Nullable
  298.     public Ref firstExactRef(String... refs) throws IOException {
  299.         try {
  300.             RefList<Ref> packed = getPackedRefs();
  301.             for (String name : refs) {
  302.                 Ref ref = readAndResolve(name, packed);
  303.                 if (ref != null) {
  304.                     return ref;
  305.                 }
  306.             }
  307.             return null;
  308.         } finally {
  309.             fireRefsChanged();
  310.         }
  311.     }

  312.     /** {@inheritDoc} */
  313.     @Override
  314.     public Map<String, Ref> getRefs(String prefix) throws IOException {
  315.         final RefList<LooseRef> oldLoose = looseRefs.get();
  316.         LooseScanner scan = new LooseScanner(oldLoose);
  317.         scan.scan(prefix);
  318.         final RefList<Ref> packed = getPackedRefs();

  319.         RefList<LooseRef> loose;
  320.         if (scan.newLoose != null) {
  321.             scan.newLoose.sort();
  322.             loose = scan.newLoose.toRefList();
  323.             if (looseRefs.compareAndSet(oldLoose, loose))
  324.                 modCnt.incrementAndGet();
  325.         } else
  326.             loose = oldLoose;
  327.         fireRefsChanged();

  328.         RefList.Builder<Ref> symbolic = scan.symbolic;
  329.         for (int idx = 0; idx < symbolic.size();) {
  330.             final Ref symbolicRef = symbolic.get(idx);
  331.             final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
  332.             if (resolvedRef != null && resolvedRef.getObjectId() != null) {
  333.                 symbolic.set(idx, resolvedRef);
  334.                 idx++;
  335.             } else {
  336.                 // A broken symbolic reference, we have to drop it from the
  337.                 // collections the client is about to receive. Should be a
  338.                 // rare occurrence so pay a copy penalty.
  339.                 symbolic.remove(idx);
  340.                 final int toRemove = loose.find(symbolicRef.getName());
  341.                 if (0 <= toRemove)
  342.                     loose = loose.remove(toRemove);
  343.             }
  344.         }
  345.         symbolic.sort();

  346.         return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
  347.     }

  348.     /** {@inheritDoc} */
  349.     @Override
  350.     public List<Ref> getAdditionalRefs() throws IOException {
  351.         List<Ref> ret = new LinkedList<>();
  352.         for (String name : additionalRefsNames) {
  353.             Ref r = exactRef(name);
  354.             if (r != null)
  355.                 ret.add(r);
  356.         }
  357.         return ret;
  358.     }

  359.     @SuppressWarnings("unchecked")
  360.     private RefList<Ref> upcast(RefList<? extends Ref> loose) {
  361.         return (RefList<Ref>) loose;
  362.     }

  363.     private class LooseScanner {
  364.         private final RefList<LooseRef> curLoose;

  365.         private int curIdx;

  366.         final RefList.Builder<Ref> symbolic = new RefList.Builder<>(4);

  367.         RefList.Builder<LooseRef> newLoose;

  368.         LooseScanner(RefList<LooseRef> curLoose) {
  369.             this.curLoose = curLoose;
  370.         }

  371.         void scan(String prefix) {
  372.             if (ALL.equals(prefix)) {
  373.                 scanOne(HEAD);
  374.                 scanTree(R_REFS, refsDir);

  375.                 // If any entries remain, they are deleted, drop them.
  376.                 if (newLoose == null && curIdx < curLoose.size())
  377.                     newLoose = curLoose.copy(curIdx);

  378.             } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { //$NON-NLS-1$
  379.                 curIdx = -(curLoose.find(prefix) + 1);
  380.                 File dir = new File(refsDir, prefix.substring(R_REFS.length()));
  381.                 scanTree(prefix, dir);

  382.                 // Skip over entries still within the prefix; these have
  383.                 // been removed from the directory.
  384.                 while (curIdx < curLoose.size()) {
  385.                     if (!curLoose.get(curIdx).getName().startsWith(prefix))
  386.                         break;
  387.                     if (newLoose == null)
  388.                         newLoose = curLoose.copy(curIdx);
  389.                     curIdx++;
  390.                 }

  391.                 // Keep any entries outside of the prefix space, we
  392.                 // do not know anything about their status.
  393.                 if (newLoose != null) {
  394.                     while (curIdx < curLoose.size())
  395.                         newLoose.add(curLoose.get(curIdx++));
  396.                 }
  397.             }
  398.         }

  399.         private boolean scanTree(String prefix, File dir) {
  400.             final String[] entries = dir.list(LockFile.FILTER);
  401.             if (entries == null) // not a directory or an I/O error
  402.                 return false;
  403.             if (0 < entries.length) {
  404.                 for (int i = 0; i < entries.length; ++i) {
  405.                     String e = entries[i];
  406.                     File f = new File(dir, e);
  407.                     if (f.isDirectory())
  408.                         entries[i] += '/';
  409.                 }
  410.                 Arrays.sort(entries);
  411.                 for (String name : entries) {
  412.                     if (name.charAt(name.length() - 1) == '/')
  413.                         scanTree(prefix + name, new File(dir, name));
  414.                     else
  415.                         scanOne(prefix + name);
  416.                 }
  417.             }
  418.             return true;
  419.         }

  420.         private void scanOne(String name) {
  421.             LooseRef cur;

  422.             if (curIdx < curLoose.size()) {
  423.                 do {
  424.                     cur = curLoose.get(curIdx);
  425.                     int cmp = RefComparator.compareTo(cur, name);
  426.                     if (cmp < 0) {
  427.                         // Reference is not loose anymore, its been deleted.
  428.                         // Skip the name in the new result list.
  429.                         if (newLoose == null)
  430.                             newLoose = curLoose.copy(curIdx);
  431.                         curIdx++;
  432.                         cur = null;
  433.                         continue;
  434.                     }

  435.                     if (cmp > 0) // Newly discovered loose reference.
  436.                         cur = null;
  437.                     break;
  438.                 } while (curIdx < curLoose.size());
  439.             } else
  440.                 cur = null; // Newly discovered loose reference.

  441.             LooseRef n;
  442.             try {
  443.                 n = scanRef(cur, name);
  444.             } catch (IOException notValid) {
  445.                 n = null;
  446.             }

  447.             if (n != null) {
  448.                 if (cur != n && newLoose == null)
  449.                     newLoose = curLoose.copy(curIdx);
  450.                 if (newLoose != null)
  451.                     newLoose.add(n);
  452.                 if (n.isSymbolic())
  453.                     symbolic.add(n);
  454.             } else if (cur != null) {
  455.                 // Tragically, this file is no longer a loose reference.
  456.                 // Kill our cached entry of it.
  457.                 if (newLoose == null)
  458.                     newLoose = curLoose.copy(curIdx);
  459.             }

  460.             if (cur != null)
  461.                 curIdx++;
  462.         }
  463.     }

  464.     /** {@inheritDoc} */
  465.     @Override
  466.     public Ref peel(Ref ref) throws IOException {
  467.         final Ref leaf = ref.getLeaf();
  468.         if (leaf.isPeeled() || leaf.getObjectId() == null)
  469.             return ref;

  470.         ObjectIdRef newLeaf = doPeel(leaf);

  471.         // Try to remember this peeling in the cache, so we don't have to do
  472.         // it again in the future, but only if the reference is unchanged.
  473.         if (leaf.getStorage().isLoose()) {
  474.             RefList<LooseRef> curList = looseRefs.get();
  475.             int idx = curList.find(leaf.getName());
  476.             if (0 <= idx && curList.get(idx) == leaf) {
  477.                 LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
  478.                 RefList<LooseRef> newList = curList.set(idx, asPeeled);
  479.                 looseRefs.compareAndSet(curList, newList);
  480.             }
  481.         }

  482.         return recreate(ref, newLeaf);
  483.     }

  484.     private ObjectIdRef doPeel(Ref leaf) throws MissingObjectException,
  485.             IOException {
  486.         try (RevWalk rw = new RevWalk(getRepository())) {
  487.             RevObject obj = rw.parseAny(leaf.getObjectId());
  488.             if (obj instanceof RevTag) {
  489.                 return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
  490.                         .getName(), leaf.getObjectId(), rw.peel(obj).copy());
  491.             }
  492.             return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
  493.                     leaf.getName(), leaf.getObjectId());
  494.         }
  495.     }

  496.     private static Ref recreate(Ref old, ObjectIdRef leaf) {
  497.         if (old.isSymbolic()) {
  498.             Ref dst = recreate(old.getTarget(), leaf);
  499.             return new SymbolicRef(old.getName(), dst);
  500.         }
  501.         return leaf;
  502.     }

  503.     void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
  504.             String target) {
  505.         putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
  506.         fireRefsChanged();
  507.     }

  508.     /** {@inheritDoc} */
  509.     @Override
  510.     public RefDirectoryUpdate newUpdate(String name, boolean detach)
  511.             throws IOException {
  512.         boolean detachingSymbolicRef = false;
  513.         final RefList<Ref> packed = getPackedRefs();
  514.         Ref ref = readRef(name, packed);
  515.         if (ref != null)
  516.             ref = resolve(ref, 0, null, null, packed);
  517.         if (ref == null)
  518.             ref = new ObjectIdRef.Unpeeled(NEW, name, null);
  519.         else {
  520.             detachingSymbolicRef = detach && ref.isSymbolic();
  521.         }
  522.         RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
  523.         if (detachingSymbolicRef)
  524.             refDirUpdate.setDetachingSymbolicRef();
  525.         return refDirUpdate;
  526.     }

  527.     /** {@inheritDoc} */
  528.     @Override
  529.     public RefDirectoryRename newRename(String fromName, String toName)
  530.             throws IOException {
  531.         RefDirectoryUpdate from = newUpdate(fromName, false);
  532.         RefDirectoryUpdate to = newUpdate(toName, false);
  533.         return new RefDirectoryRename(from, to);
  534.     }

  535.     /** {@inheritDoc} */
  536.     @Override
  537.     public PackedBatchRefUpdate newBatchUpdate() {
  538.         return new PackedBatchRefUpdate(this);
  539.     }

  540.     /** {@inheritDoc} */
  541.     @Override
  542.     public boolean performsAtomicTransactions() {
  543.         return true;
  544.     }

  545.     void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
  546.         final ObjectId target = update.getNewObjectId().copy();
  547.         final Ref leaf = update.getRef().getLeaf();
  548.         putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
  549.     }

  550.     private void putLooseRef(LooseRef ref) {
  551.         RefList<LooseRef> cList, nList;
  552.         do {
  553.             cList = looseRefs.get();
  554.             nList = cList.put(ref);
  555.         } while (!looseRefs.compareAndSet(cList, nList));
  556.         modCnt.incrementAndGet();
  557.         fireRefsChanged();
  558.     }

  559.     void delete(RefDirectoryUpdate update) throws IOException {
  560.         Ref dst = update.getRef();
  561.         if (!update.isDetachingSymbolicRef()) {
  562.             dst = dst.getLeaf();
  563.         }
  564.         String name = dst.getName();

  565.         // Write the packed-refs file using an atomic update. We might
  566.         // wind up reading it twice, before and after the lock, to ensure
  567.         // we don't miss an edit made externally.
  568.         final PackedRefList packed = getPackedRefs();
  569.         if (packed.contains(name)) {
  570.             inProcessPackedRefsLock.lock();
  571.             try {
  572.                 LockFile lck = lockPackedRefsOrThrow();
  573.                 try {
  574.                     PackedRefList cur = readPackedRefs();
  575.                     int idx = cur.find(name);
  576.                     if (0 <= idx) {
  577.                         commitPackedRefs(lck, cur.remove(idx), packed, true);
  578.                     }
  579.                 } finally {
  580.                     lck.unlock();
  581.                 }
  582.             } finally {
  583.                 inProcessPackedRefsLock.unlock();
  584.             }
  585.         }

  586.         RefList<LooseRef> curLoose, newLoose;
  587.         do {
  588.             curLoose = looseRefs.get();
  589.             int idx = curLoose.find(name);
  590.             if (idx < 0)
  591.                 break;
  592.             newLoose = curLoose.remove(idx);
  593.         } while (!looseRefs.compareAndSet(curLoose, newLoose));

  594.         int levels = levelsIn(name) - 2;
  595.         delete(logFor(name), levels);
  596.         if (dst.getStorage().isLoose()) {
  597.             update.unlock();
  598.             delete(fileFor(name), levels);
  599.         }

  600.         modCnt.incrementAndGet();
  601.         fireRefsChanged();
  602.     }

  603.     /**
  604.      * Adds a set of refs to the set of packed-refs. Only non-symbolic refs are
  605.      * added. If a ref with the given name already existed in packed-refs it is
  606.      * updated with the new value. Each loose ref which was added to the
  607.      * packed-ref file is deleted. If a given ref can't be locked it will not be
  608.      * added to the pack file.
  609.      *
  610.      * @param refs
  611.      *            the refs to be added. Must be fully qualified.
  612.      * @throws java.io.IOException
  613.      */
  614.     public void pack(List<String> refs) throws IOException {
  615.         pack(refs, Collections.emptyMap());
  616.     }

  617.     PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
  618.         return pack(heldLocks.keySet(), heldLocks);
  619.     }

  620.     private PackedRefList pack(Collection<String> refs,
  621.             Map<String, LockFile> heldLocks) throws IOException {
  622.         for (LockFile ol : heldLocks.values()) {
  623.             ol.requireLock();
  624.         }
  625.         if (refs.isEmpty()) {
  626.             return null;
  627.         }
  628.         FS fs = parent.getFS();

  629.         // Lock the packed refs file and read the content
  630.         inProcessPackedRefsLock.lock();
  631.         try {
  632.             LockFile lck = lockPackedRefsOrThrow();
  633.             try {
  634.                 final PackedRefList packed = getPackedRefs();
  635.                 RefList<Ref> cur = readPackedRefs();

  636.                 // Iterate over all refs to be packed
  637.                 boolean dirty = false;
  638.                 for (String refName : refs) {
  639.                     Ref oldRef = readRef(refName, cur);
  640.                     if (oldRef == null) {
  641.                         continue; // A non-existent ref is already correctly packed.
  642.                     }
  643.                     if (oldRef.isSymbolic()) {
  644.                         continue; // can't pack symbolic refs
  645.                     }
  646.                     // Add/Update it to packed-refs
  647.                     Ref newRef = peeledPackedRef(oldRef);
  648.                     if (newRef == oldRef) {
  649.                         // No-op; peeledPackedRef returns the input ref only if it's already
  650.                         // packed, and readRef returns a packed ref only if there is no
  651.                         // loose ref.
  652.                         continue;
  653.                     }

  654.                     dirty = true;
  655.                     int idx = cur.find(refName);
  656.                     if (idx >= 0) {
  657.                         cur = cur.set(idx, newRef);
  658.                     } else {
  659.                         cur = cur.add(idx, newRef);
  660.                     }
  661.                 }
  662.                 if (!dirty) {
  663.                     // All requested refs were already packed accurately
  664.                     return packed;
  665.                 }

  666.                 // The new content for packed-refs is collected. Persist it.
  667.                 PackedRefList result = commitPackedRefs(lck, cur, packed,
  668.                         false);

  669.                 // Now delete the loose refs which are now packed
  670.                 for (String refName : refs) {
  671.                     // Lock the loose ref
  672.                     File refFile = fileFor(refName);
  673.                     if (!fs.exists(refFile)) {
  674.                         continue;
  675.                     }

  676.                     LockFile rLck = heldLocks.get(refName);
  677.                     boolean shouldUnlock;
  678.                     if (rLck == null) {
  679.                         rLck = new LockFile(refFile);
  680.                         if (!rLck.lock()) {
  681.                             continue;
  682.                         }
  683.                         shouldUnlock = true;
  684.                     } else {
  685.                         shouldUnlock = false;
  686.                     }

  687.                     try {
  688.                         LooseRef currentLooseRef = scanRef(null, refName);
  689.                         if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
  690.                             continue;
  691.                         }
  692.                         Ref packedRef = cur.get(refName);
  693.                         ObjectId clr_oid = currentLooseRef.getObjectId();
  694.                         if (clr_oid != null
  695.                                 && clr_oid.equals(packedRef.getObjectId())) {
  696.                             RefList<LooseRef> curLoose, newLoose;
  697.                             do {
  698.                                 curLoose = looseRefs.get();
  699.                                 int idx = curLoose.find(refName);
  700.                                 if (idx < 0) {
  701.                                     break;
  702.                                 }
  703.                                 newLoose = curLoose.remove(idx);
  704.                             } while (!looseRefs.compareAndSet(curLoose, newLoose));
  705.                             int levels = levelsIn(refName) - 2;
  706.                             delete(refFile, levels, rLck);
  707.                         }
  708.                     } finally {
  709.                         if (shouldUnlock) {
  710.                             rLck.unlock();
  711.                         }
  712.                     }
  713.                 }
  714.                 // Don't fire refsChanged. The refs have not change, only their
  715.                 // storage.
  716.                 return result;
  717.             } finally {
  718.                 lck.unlock();
  719.             }
  720.         } finally {
  721.             inProcessPackedRefsLock.unlock();
  722.         }
  723.     }

  724.     @Nullable
  725.     LockFile lockPackedRefs() throws IOException {
  726.         LockFile lck = new LockFile(packedRefsFile);
  727.         for (int ms : getRetrySleepMs()) {
  728.             sleep(ms);
  729.             if (lck.lock()) {
  730.                 return lck;
  731.             }
  732.         }
  733.         return null;
  734.     }

  735.     private LockFile lockPackedRefsOrThrow() throws IOException {
  736.         LockFile lck = lockPackedRefs();
  737.         if (lck == null) {
  738.             throw new LockFailedException(packedRefsFile);
  739.         }
  740.         return lck;
  741.     }

  742.     /**
  743.      * Make sure a ref is peeled and has the Storage PACKED. If the given ref
  744.      * has this attributes simply return it. Otherwise create a new peeled
  745.      * {@link ObjectIdRef} where Storage is set to PACKED.
  746.      *
  747.      * @param f
  748.      * @return a ref for Storage PACKED having the same name, id, peeledId as f
  749.      * @throws MissingObjectException
  750.      * @throws IOException
  751.      */
  752.     private Ref peeledPackedRef(Ref f)
  753.             throws MissingObjectException, IOException {
  754.         if (f.getStorage().isPacked() && f.isPeeled()) {
  755.             return f;
  756.         }
  757.         if (!f.isPeeled()) {
  758.             f = peel(f);
  759.         }
  760.         ObjectId peeledObjectId = f.getPeeledObjectId();
  761.         if (peeledObjectId != null) {
  762.             return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
  763.                     f.getObjectId(), peeledObjectId);
  764.         }
  765.         return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
  766.                 f.getObjectId());
  767.     }

  768.     void log(boolean force, RefUpdate update, String msg, boolean deref)
  769.             throws IOException {
  770.         newLogWriter(force).log(update, msg, deref);
  771.     }

  772.     private Ref resolve(final Ref ref, int depth, String prefix,
  773.             RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
  774.         if (ref.isSymbolic()) {
  775.             Ref dst = ref.getTarget();

  776.             if (MAX_SYMBOLIC_REF_DEPTH <= depth)
  777.                 return null; // claim it doesn't exist

  778.             // If the cached value can be assumed to be current due to a
  779.             // recent scan of the loose directory, use it.
  780.             if (loose != null && dst.getName().startsWith(prefix)) {
  781.                 int idx;
  782.                 if (0 <= (idx = loose.find(dst.getName())))
  783.                     dst = loose.get(idx);
  784.                 else if (0 <= (idx = packed.find(dst.getName())))
  785.                     dst = packed.get(idx);
  786.                 else
  787.                     return ref;
  788.             } else {
  789.                 dst = readRef(dst.getName(), packed);
  790.                 if (dst == null)
  791.                     return ref;
  792.             }

  793.             dst = resolve(dst, depth + 1, prefix, loose, packed);
  794.             if (dst == null)
  795.                 return null;
  796.             return new SymbolicRef(ref.getName(), dst);
  797.         }
  798.         return ref;
  799.     }

  800.     PackedRefList getPackedRefs() throws IOException {
  801.         boolean trustFolderStat = getRepository().getConfig().getBoolean(
  802.                 ConfigConstants.CONFIG_CORE_SECTION,
  803.                 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);

  804.         final PackedRefList curList = packedRefs.get();
  805.         if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
  806.             return curList;
  807.         }

  808.         final PackedRefList newList = readPackedRefs();
  809.         if (packedRefs.compareAndSet(curList, newList)
  810.                 && !curList.id.equals(newList.id)) {
  811.             modCnt.incrementAndGet();
  812.         }
  813.         return newList;
  814.     }

  815.     private PackedRefList readPackedRefs() throws IOException {
  816.         int maxStaleRetries = 5;
  817.         int retries = 0;
  818.         while (true) {
  819.             final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
  820.             final MessageDigest digest = Constants.newMessageDigest();
  821.             try (BufferedReader br = new BufferedReader(new InputStreamReader(
  822.                     new DigestInputStream(new FileInputStream(packedRefsFile),
  823.                             digest),
  824.                     UTF_8))) {
  825.                 try {
  826.                     return new PackedRefList(parsePackedRefs(br), snapshot,
  827.                             ObjectId.fromRaw(digest.digest()));
  828.                 } catch (IOException e) {
  829.                     if (FileUtils.isStaleFileHandleInCausalChain(e)
  830.                             && retries < maxStaleRetries) {
  831.                         if (LOG.isDebugEnabled()) {
  832.                             LOG.debug(MessageFormat.format(
  833.                                     JGitText.get().packedRefsHandleIsStale,
  834.                                     Integer.valueOf(retries)), e);
  835.                         }
  836.                         retries++;
  837.                         continue;
  838.                     }
  839.                     throw e;
  840.                 }
  841.             } catch (FileNotFoundException noPackedRefs) {
  842.                 if (packedRefsFile.exists()) {
  843.                     throw noPackedRefs;
  844.                 }
  845.                 // Ignore it and leave the new list empty.
  846.                 return NO_PACKED_REFS;
  847.             }
  848.         }
  849.     }

  850.     private RefList<Ref> parsePackedRefs(BufferedReader br)
  851.             throws IOException {
  852.         RefList.Builder<Ref> all = new RefList.Builder<>();
  853.         Ref last = null;
  854.         boolean peeled = false;
  855.         boolean needSort = false;

  856.         String p;
  857.         while ((p = br.readLine()) != null) {
  858.             if (p.charAt(0) == '#') {
  859.                 if (p.startsWith(PACKED_REFS_HEADER)) {
  860.                     p = p.substring(PACKED_REFS_HEADER.length());
  861.                     peeled = p.contains(PACKED_REFS_PEELED);
  862.                 }
  863.                 continue;
  864.             }

  865.             if (p.charAt(0) == '^') {
  866.                 if (last == null)
  867.                     throw new IOException(JGitText.get().peeledLineBeforeRef);

  868.                 ObjectId id = ObjectId.fromString(p.substring(1));
  869.                 last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
  870.                         .getObjectId(), id);
  871.                 all.set(all.size() - 1, last);
  872.                 continue;
  873.             }

  874.             int sp = p.indexOf(' ');
  875.             if (sp < 0) {
  876.                 throw new IOException(MessageFormat.format(
  877.                         JGitText.get().packedRefsCorruptionDetected,
  878.                         packedRefsFile.getAbsolutePath()));
  879.             }
  880.             ObjectId id = ObjectId.fromString(p.substring(0, sp));
  881.             String name = copy(p, sp + 1, p.length());
  882.             ObjectIdRef cur;
  883.             if (peeled)
  884.                 cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
  885.             else
  886.                 cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
  887.             if (last != null && RefComparator.compareTo(last, cur) > 0)
  888.                 needSort = true;
  889.             all.add(cur);
  890.             last = cur;
  891.         }

  892.         if (needSort)
  893.             all.sort();
  894.         return all.toRefList();
  895.     }

  896.     private static String copy(String src, int off, int end) {
  897.         // Don't use substring since it could leave a reference to the much
  898.         // larger existing string. Force construction of a full new object.
  899.         return new StringBuilder(end - off).append(src, off, end).toString();
  900.     }

  901.     PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
  902.             final PackedRefList oldPackedList, boolean changed)
  903.             throws IOException {
  904.         // Can't just return packedRefs.get() from this method; it might have been
  905.         // updated again after writePackedRefs() returns.
  906.         AtomicReference<PackedRefList> result = new AtomicReference<>();
  907.         new RefWriter(refs) {
  908.             @Override
  909.             protected void writeFile(String name, byte[] content)
  910.                     throws IOException {
  911.                 lck.setFSync(true);
  912.                 lck.setNeedSnapshot(true);
  913.                 try {
  914.                     lck.write(content);
  915.                 } catch (IOException ioe) {
  916.                     throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
  917.                 }
  918.                 try {
  919.                     lck.waitForStatChange();
  920.                 } catch (InterruptedException e) {
  921.                     lck.unlock();
  922.                     throw new ObjectWritingException(
  923.                             MessageFormat.format(
  924.                                     JGitText.get().interruptedWriting, name),
  925.                             e);
  926.                 }
  927.                 if (!lck.commit())
  928.                     throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));

  929.                 byte[] digest = Constants.newMessageDigest().digest(content);
  930.                 PackedRefList newPackedList = new PackedRefList(
  931.                         refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));

  932.                 // This thread holds the file lock, so no other thread or process should
  933.                 // be able to modify the packed-refs file on disk. If the list changed,
  934.                 // it means something is very wrong, so throw an exception.
  935.                 //
  936.                 // However, we can't use a naive compareAndSet to check whether the
  937.                 // update was successful, because another thread might _read_ the
  938.                 // packed refs file that was written out by this thread while holding
  939.                 // the lock, and update the packedRefs reference to point to that. So
  940.                 // compare the actual contents instead.
  941.                 PackedRefList afterUpdate = packedRefs.updateAndGet(
  942.                         p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
  943.                 if (!afterUpdate.id.equals(newPackedList.id)) {
  944.                     throw new ObjectWritingException(
  945.                             MessageFormat.format(JGitText.get().unableToWrite, name));
  946.                 }
  947.                 if (changed) {
  948.                     modCnt.incrementAndGet();
  949.                 }
  950.                 result.set(newPackedList);
  951.             }
  952.         }.writePackedRefs();
  953.         return result.get();
  954.     }

  955.     private Ref readRef(String name, RefList<Ref> packed) throws IOException {
  956.         final RefList<LooseRef> curList = looseRefs.get();
  957.         final int idx = curList.find(name);
  958.         if (0 <= idx) {
  959.             final LooseRef o = curList.get(idx);
  960.             final LooseRef n = scanRef(o, name);
  961.             if (n == null) {
  962.                 if (looseRefs.compareAndSet(curList, curList.remove(idx)))
  963.                     modCnt.incrementAndGet();
  964.                 return packed.get(name);
  965.             }

  966.             if (o == n)
  967.                 return n;
  968.             if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
  969.                 modCnt.incrementAndGet();
  970.             return n;
  971.         }

  972.         final LooseRef n = scanRef(null, name);
  973.         if (n == null)
  974.             return packed.get(name);

  975.         // check whether the found new ref is the an additional ref. These refs
  976.         // should not go into looseRefs
  977.         for (String additionalRefsName : additionalRefsNames) {
  978.             if (name.equals(additionalRefsName)) {
  979.                 return n;
  980.             }
  981.         }

  982.         if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
  983.             modCnt.incrementAndGet();
  984.         return n;
  985.     }

  986.     LooseRef scanRef(LooseRef ref, String name) throws IOException {
  987.         final File path = fileFor(name);
  988.         FileSnapshot currentSnapshot = null;

  989.         if (ref != null) {
  990.             currentSnapshot = ref.getSnapShot();
  991.             if (!currentSnapshot.isModified(path))
  992.                 return ref;
  993.             name = ref.getName();
  994.         }

  995.         final int limit = 4096;
  996.         final byte[] buf;
  997.         FileSnapshot otherSnapshot = FileSnapshot.save(path);
  998.         try {
  999.             buf = IO.readSome(path, limit);
  1000.         } catch (FileNotFoundException noFile) {
  1001.             if (path.exists() && path.isFile()) {
  1002.                 throw noFile;
  1003.             }
  1004.             return null; // doesn't exist or no file; not a reference.
  1005.         }

  1006.         int n = buf.length;
  1007.         if (n == 0)
  1008.             return null; // empty file; not a reference.

  1009.         if (isSymRef(buf, n)) {
  1010.             if (n == limit)
  1011.                 return null; // possibly truncated ref

  1012.             // trim trailing whitespace
  1013.             while (0 < n && Character.isWhitespace(buf[n - 1]))
  1014.                 n--;
  1015.             if (n < 6) {
  1016.                 String content = RawParseUtils.decode(buf, 0, n);
  1017.                 throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
  1018.             }
  1019.             final String target = RawParseUtils.decode(buf, 5, n);
  1020.             if (ref != null && ref.isSymbolic()
  1021.                     && ref.getTarget().getName().equals(target)) {
  1022.                 assert(currentSnapshot != null);
  1023.                 currentSnapshot.setClean(otherSnapshot);
  1024.                 return ref;
  1025.             }
  1026.             return newSymbolicRef(otherSnapshot, name, target);
  1027.         }

  1028.         if (n < OBJECT_ID_STRING_LENGTH)
  1029.             return null; // impossibly short object identifier; not a reference.

  1030.         final ObjectId id;
  1031.         try {
  1032.             id = ObjectId.fromString(buf, 0);
  1033.             if (ref != null && !ref.isSymbolic()
  1034.                     && id.equals(ref.getTarget().getObjectId())) {
  1035.                 assert(currentSnapshot != null);
  1036.                 currentSnapshot.setClean(otherSnapshot);
  1037.                 return ref;
  1038.             }

  1039.         } catch (IllegalArgumentException notRef) {
  1040.             while (0 < n && Character.isWhitespace(buf[n - 1]))
  1041.                 n--;
  1042.             String content = RawParseUtils.decode(buf, 0, n);

  1043.             throw new IOException(MessageFormat.format(JGitText.get().notARef,
  1044.                     name, content), notRef);
  1045.         }
  1046.         return new LooseUnpeeled(otherSnapshot, name, id);
  1047.     }

  1048.     private static boolean isSymRef(byte[] buf, int n) {
  1049.         if (n < 6)
  1050.             return false;
  1051.         return /**/buf[0] == 'r' //
  1052.                 && buf[1] == 'e' //
  1053.                 && buf[2] == 'f' //
  1054.                 && buf[3] == ':' //
  1055.                 && buf[4] == ' ';
  1056.     }

  1057.     /**
  1058.      * Detect if we are in a clone command execution
  1059.      *
  1060.      * @return {@code true} if we are currently cloning a repository
  1061.      * @throws IOException
  1062.      */
  1063.     boolean isInClone() throws IOException {
  1064.         return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
  1065.     }

  1066.     private boolean hasDanglingHead() throws IOException {
  1067.         Ref head = exactRef(Constants.HEAD);
  1068.         if (head != null) {
  1069.             ObjectId id = head.getObjectId();
  1070.             return id == null || id.equals(ObjectId.zeroId());
  1071.         }
  1072.         return false;
  1073.     }

  1074.     private boolean hasLooseRef() throws IOException {
  1075.         try (Stream<Path> stream = Files.walk(refsDir.toPath())) {
  1076.             return stream.anyMatch(Files::isRegularFile);
  1077.         }
  1078.     }

  1079.     /** If the parent should fire listeners, fires them. */
  1080.     void fireRefsChanged() {
  1081.         final int last = lastNotifiedModCnt.get();
  1082.         final int curr = modCnt.get();
  1083.         if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
  1084.             parent.fireEvent(new RefsChangedEvent());
  1085.     }

  1086.     /**
  1087.      * Create a reference update to write a temporary reference.
  1088.      *
  1089.      * @return an update for a new temporary reference.
  1090.      * @throws IOException
  1091.      *             a temporary name cannot be allocated.
  1092.      */
  1093.     RefDirectoryUpdate newTemporaryUpdate() throws IOException {
  1094.         File tmp = File.createTempFile("renamed_", "_ref", refsDir); //$NON-NLS-1$ //$NON-NLS-2$
  1095.         String name = Constants.R_REFS + tmp.getName();
  1096.         Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
  1097.         return new RefDirectoryUpdate(this, ref);
  1098.     }

  1099.     /**
  1100.      * Locate the file on disk for a single reference name.
  1101.      *
  1102.      * @param name
  1103.      *            name of the ref, relative to the Git repository top level
  1104.      *            directory (so typically starts with refs/).
  1105.      * @return the loose file location.
  1106.      */
  1107.     File fileFor(String name) {
  1108.         if (name.startsWith(R_REFS)) {
  1109.             name = name.substring(R_REFS.length());
  1110.             return new File(refsDir, name);
  1111.         }
  1112.         return new File(gitDir, name);
  1113.     }

  1114.     static int levelsIn(String name) {
  1115.         int count = 0;
  1116.         for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
  1117.             count++;
  1118.         return count;
  1119.     }

  1120.     static void delete(File file, int depth) throws IOException {
  1121.         delete(file, depth, null);
  1122.     }

  1123.     private static void delete(File file, int depth, LockFile rLck)
  1124.             throws IOException {
  1125.         if (!file.delete() && file.isFile()) {
  1126.             throw new IOException(MessageFormat.format(
  1127.                     JGitText.get().fileCannotBeDeleted, file));
  1128.         }

  1129.         if (rLck != null) {
  1130.             rLck.unlock(); // otherwise cannot delete dir below
  1131.         }
  1132.         File dir = file.getParentFile();
  1133.         for (int i = 0; i < depth; ++i) {
  1134.             try {
  1135.                 Files.deleteIfExists(dir.toPath());
  1136.             } catch (DirectoryNotEmptyException e) {
  1137.                 // Don't log; normal case when there are other refs with the
  1138.                 // same prefix
  1139.                 break;
  1140.             } catch (IOException e) {
  1141.                 LOG.warn(MessageFormat.format(JGitText.get().unableToRemovePath,
  1142.                         dir), e);
  1143.                 break;
  1144.             }
  1145.             dir = dir.getParentFile();
  1146.         }
  1147.     }

  1148.     /**
  1149.      * Get times to sleep while retrying a possibly contentious operation.
  1150.      * <p>
  1151.      * For retrying an operation that might have high contention, such as locking
  1152.      * the {@code packed-refs} file, the caller may implement a retry loop using
  1153.      * the returned values:
  1154.      *
  1155.      * <pre>
  1156.      * for (int toSleepMs : getRetrySleepMs()) {
  1157.      *   sleep(toSleepMs);
  1158.      *   if (isSuccessful(doSomething())) {
  1159.      *     return success;
  1160.      *   }
  1161.      * }
  1162.      * return failure;
  1163.      * </pre>
  1164.      *
  1165.      * The first value in the returned iterable is 0, and the caller should treat
  1166.      * a fully-consumed iterator as a timeout.
  1167.      *
  1168.      * @return iterable of times, in milliseconds, that the caller should sleep
  1169.      *         before attempting an operation.
  1170.      */
  1171.     Iterable<Integer> getRetrySleepMs() {
  1172.         return retrySleepMs;
  1173.     }

  1174.     void setRetrySleepMs(List<Integer> retrySleepMs) {
  1175.         if (retrySleepMs == null || retrySleepMs.isEmpty()
  1176.                 || retrySleepMs.get(0).intValue() != 0) {
  1177.             throw new IllegalArgumentException();
  1178.         }
  1179.         this.retrySleepMs = retrySleepMs;
  1180.     }

  1181.     /**
  1182.      * Sleep with {@link Thread#sleep(long)}, converting {@link
  1183.      * InterruptedException} to {@link InterruptedIOException}.
  1184.      *
  1185.      * @param ms
  1186.      *            time to sleep, in milliseconds; zero or negative is a no-op.
  1187.      * @throws InterruptedIOException
  1188.      *             if sleeping was interrupted.
  1189.      */
  1190.     static void sleep(long ms) throws InterruptedIOException {
  1191.         if (ms <= 0) {
  1192.             return;
  1193.         }
  1194.         try {
  1195.             Thread.sleep(ms);
  1196.         } catch (InterruptedException e) {
  1197.             InterruptedIOException ie = new InterruptedIOException();
  1198.             ie.initCause(e);
  1199.             throw ie;
  1200.         }
  1201.     }

  1202.     static class PackedRefList extends RefList<Ref> {

  1203.         private final FileSnapshot snapshot;

  1204.         private final ObjectId id;

  1205.         private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
  1206.             super(src);
  1207.             snapshot = s;
  1208.             id = i;
  1209.         }
  1210.     }

  1211.     private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
  1212.             RefList.emptyList(), FileSnapshot.MISSING_FILE,
  1213.             ObjectId.zeroId());

  1214.     private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
  1215.             String name, String target) {
  1216.         Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
  1217.         return new LooseSymbolicRef(snapshot, name, dst);
  1218.     }

  1219.     private static interface LooseRef extends Ref {
  1220.         FileSnapshot getSnapShot();

  1221.         LooseRef peel(ObjectIdRef newLeaf);
  1222.     }

  1223.     private static final class LoosePeeledTag extends ObjectIdRef.PeeledTag
  1224.             implements LooseRef {
  1225.         private final FileSnapshot snapShot;

  1226.         LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
  1227.                 @NonNull ObjectId id, @NonNull ObjectId p) {
  1228.             super(LOOSE, refName, id, p);
  1229.             this.snapShot = snapshot;
  1230.         }

  1231.         @Override
  1232.         public FileSnapshot getSnapShot() {
  1233.             return snapShot;
  1234.         }

  1235.         @Override
  1236.         public LooseRef peel(ObjectIdRef newLeaf) {
  1237.             return this;
  1238.         }
  1239.     }

  1240.     private static final class LooseNonTag extends ObjectIdRef.PeeledNonTag
  1241.             implements LooseRef {
  1242.         private final FileSnapshot snapShot;

  1243.         LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
  1244.                 @NonNull ObjectId id) {
  1245.             super(LOOSE, refName, id);
  1246.             this.snapShot = snapshot;
  1247.         }

  1248.         @Override
  1249.         public FileSnapshot getSnapShot() {
  1250.             return snapShot;
  1251.         }

  1252.         @Override
  1253.         public LooseRef peel(ObjectIdRef newLeaf) {
  1254.             return this;
  1255.         }
  1256.     }

  1257.     private static final class LooseUnpeeled extends ObjectIdRef.Unpeeled
  1258.             implements LooseRef {
  1259.         private FileSnapshot snapShot;

  1260.         LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
  1261.                 @NonNull ObjectId id) {
  1262.             super(LOOSE, refName, id);
  1263.             this.snapShot = snapShot;
  1264.         }

  1265.         @Override
  1266.         public FileSnapshot getSnapShot() {
  1267.             return snapShot;
  1268.         }

  1269.         @NonNull
  1270.         @Override
  1271.         public ObjectId getObjectId() {
  1272.             ObjectId id = super.getObjectId();
  1273.             assert id != null; // checked in constructor
  1274.             return id;
  1275.         }

  1276.         @Override
  1277.         public LooseRef peel(ObjectIdRef newLeaf) {
  1278.             ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
  1279.             ObjectId objectId = getObjectId();
  1280.             if (peeledObjectId != null) {
  1281.                 return new LoosePeeledTag(snapShot, getName(),
  1282.                         objectId, peeledObjectId);
  1283.             }
  1284.             return new LooseNonTag(snapShot, getName(), objectId);
  1285.         }
  1286.     }

  1287.     private static final class LooseSymbolicRef extends SymbolicRef implements
  1288.             LooseRef {
  1289.         private final FileSnapshot snapShot;

  1290.         LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
  1291.                 @NonNull Ref target) {
  1292.             super(refName, target);
  1293.             this.snapShot = snapshot;
  1294.         }

  1295.         @Override
  1296.         public FileSnapshot getSnapShot() {
  1297.             return snapShot;
  1298.         }

  1299.         @Override
  1300.         public LooseRef peel(ObjectIdRef newLeaf) {
  1301.             // We should never try to peel the symbolic references.
  1302.             throw new UnsupportedOperationException();
  1303.         }
  1304.     }
  1305. }