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 org.eclipse.jgit.lib.Constants.CHARSET;
50  import static org.eclipse.jgit.lib.Constants.HEAD;
51  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
52  import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
53  import static org.eclipse.jgit.lib.Constants.R_HEADS;
54  import static org.eclipse.jgit.lib.Constants.R_REFS;
55  import static org.eclipse.jgit.lib.Constants.R_TAGS;
56  import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
57  import static org.eclipse.jgit.lib.Ref.Storage.NEW;
58  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
59  
60  import java.io.BufferedReader;
61  import java.io.File;
62  import java.io.FileInputStream;
63  import java.io.FileNotFoundException;
64  import java.io.IOException;
65  import java.io.InputStreamReader;
66  import java.security.DigestInputStream;
67  import java.security.MessageDigest;
68  import java.text.MessageFormat;
69  import java.util.Arrays;
70  import java.util.LinkedList;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.concurrent.atomic.AtomicInteger;
74  import java.util.concurrent.atomic.AtomicReference;
75  
76  import org.eclipse.jgit.annotations.NonNull;
77  import org.eclipse.jgit.errors.InvalidObjectIdException;
78  import org.eclipse.jgit.errors.LockFailedException;
79  import org.eclipse.jgit.errors.MissingObjectException;
80  import org.eclipse.jgit.errors.ObjectWritingException;
81  import org.eclipse.jgit.events.RefsChangedEvent;
82  import org.eclipse.jgit.internal.JGitText;
83  import org.eclipse.jgit.lib.Constants;
84  import org.eclipse.jgit.lib.ObjectId;
85  import org.eclipse.jgit.lib.ObjectIdRef;
86  import org.eclipse.jgit.lib.Ref;
87  import org.eclipse.jgit.lib.RefComparator;
88  import org.eclipse.jgit.lib.RefDatabase;
89  import org.eclipse.jgit.lib.RefUpdate;
90  import org.eclipse.jgit.lib.RefWriter;
91  import org.eclipse.jgit.lib.Repository;
92  import org.eclipse.jgit.lib.SymbolicRef;
93  import org.eclipse.jgit.revwalk.RevObject;
94  import org.eclipse.jgit.revwalk.RevTag;
95  import org.eclipse.jgit.revwalk.RevWalk;
96  import org.eclipse.jgit.util.FS;
97  import org.eclipse.jgit.util.FileUtils;
98  import org.eclipse.jgit.util.IO;
99  import org.eclipse.jgit.util.RawParseUtils;
100 import org.eclipse.jgit.util.RefList;
101 import org.eclipse.jgit.util.RefMap;
102 import org.slf4j.Logger;
103 import org.slf4j.LoggerFactory;
104 
105 /**
106  * Traditional file system based {@link RefDatabase}.
107  * <p>
108  * This is the classical reference database representation for a Git repository.
109  * References are stored in two formats: loose, and packed.
110  * <p>
111  * Loose references are stored as individual files within the {@code refs/}
112  * directory. The file name matches the reference name and the file contents is
113  * the current {@link ObjectId} in string form.
114  * <p>
115  * Packed references are stored in a single text file named {@code packed-refs}.
116  * In the packed format, each reference is stored on its own line. This file
117  * reduces the number of files needed for large reference spaces, reducing the
118  * overall size of a Git repository on disk.
119  */
120 public class RefDirectory extends RefDatabase {
121 	private final static Logger LOG = LoggerFactory
122 			.getLogger(RefDirectory.class);
123 
124 	/** Magic string denoting the start of a symbolic reference file. */
125 	public static final String SYMREF = "ref: "; //$NON-NLS-1$
126 
127 	/** Magic string denoting the header of a packed-refs file. */
128 	public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$
129 
130 	/** If in the header, denotes the file has peeled data. */
131 	public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$
132 
133 	/** The names of the additional refs supported by this class */
134 	private static final String[] additionalRefsNames = new String[] {
135 			Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
136 			Constants.CHERRY_PICK_HEAD };
137 
138 	private final FileRepository parent;
139 
140 	private final File gitDir;
141 
142 	final File refsDir;
143 
144 	private final ReflogWriter logWriter;
145 
146 	private final File packedRefsFile;
147 
148 	/**
149 	 * Immutable sorted list of loose references.
150 	 * <p>
151 	 * Symbolic references in this collection are stored unresolved, that is
152 	 * their target appears to be a new reference with no ObjectId. These are
153 	 * converted into resolved references during a get operation, ensuring the
154 	 * live value is always returned.
155 	 */
156 	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>();
157 
158 	/** Immutable sorted list of packed references. */
159 	final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
160 
161 	/**
162 	 * Number of modifications made to this database.
163 	 * <p>
164 	 * This counter is incremented when a change is made, or detected from the
165 	 * filesystem during a read operation.
166 	 */
167 	private final AtomicInteger modCnt = new AtomicInteger();
168 
169 	/**
170 	 * Last {@link #modCnt} that we sent to listeners.
171 	 * <p>
172 	 * This value is compared to {@link #modCnt}, and a notification is sent to
173 	 * the listeners only when it differs.
174 	 */
175 	private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
176 
177 	RefDirectory(final FileRepository db) {
178 		final FS fs = db.getFS();
179 		parent = db;
180 		gitDir = db.getDirectory();
181 		logWriter = new ReflogWriter(db);
182 		refsDir = fs.resolve(gitDir, R_REFS);
183 		packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
184 
185 		looseRefs.set(RefList.<LooseRef> emptyList());
186 		packedRefs.set(PackedRefList.NO_PACKED_REFS);
187 	}
188 
189 	Repository getRepository() {
190 		return parent;
191 	}
192 
193 	ReflogWriter getLogWriter() {
194 		return logWriter;
195 	}
196 
197 	public void create() throws IOException {
198 		FileUtils.mkdir(refsDir);
199 		FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
200 		FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
201 		logWriter.create();
202 	}
203 
204 	@Override
205 	public void close() {
206 		clearReferences();
207 	}
208 
209 	private void clearReferences() {
210 		looseRefs.set(RefList.<LooseRef> emptyList());
211 		packedRefs.set(PackedRefList.NO_PACKED_REFS);
212 	}
213 
214 	@Override
215 	public void refresh() {
216 		super.refresh();
217 		clearReferences();
218 	}
219 
220 	@Override
221 	public boolean isNameConflicting(String name) throws IOException {
222 		RefList<Ref> packed = getPackedRefs();
223 		RefList<LooseRef> loose = getLooseRefs();
224 
225 		// Cannot be nested within an existing reference.
226 		int lastSlash = name.lastIndexOf('/');
227 		while (0 < lastSlash) {
228 			String needle = name.substring(0, lastSlash);
229 			if (loose.contains(needle) || packed.contains(needle))
230 				return true;
231 			lastSlash = name.lastIndexOf('/', lastSlash - 1);
232 		}
233 
234 		// Cannot be the container of an existing reference.
235 		String prefix = name + '/';
236 		int idx;
237 
238 		idx = -(packed.find(prefix) + 1);
239 		if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
240 			return true;
241 
242 		idx = -(loose.find(prefix) + 1);
243 		if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
244 			return true;
245 
246 		return false;
247 	}
248 
249 	private RefList<LooseRef> getLooseRefs() {
250 		final RefList<LooseRef> oldLoose = looseRefs.get();
251 
252 		LooseScanner scan = new LooseScanner(oldLoose);
253 		scan.scan(ALL);
254 
255 		RefList<LooseRef> loose;
256 		if (scan.newLoose != null) {
257 			loose = scan.newLoose.toRefList();
258 			if (looseRefs.compareAndSet(oldLoose, loose))
259 				modCnt.incrementAndGet();
260 		} else
261 			loose = oldLoose;
262 		return loose;
263 	}
264 
265 	@Override
266 	public Ref exactRef(String name) throws IOException {
267 		RefList<Ref> packed = getPackedRefs();
268 		Ref ref;
269 		try {
270 			ref = readRef(name, packed);
271 			if (ref != null) {
272 				ref = resolve(ref, 0, null, null, packed);
273 			}
274 		} catch (IOException e) {
275 			if (name.contains("/") //$NON-NLS-1$
276 					|| !(e.getCause() instanceof InvalidObjectIdException)) {
277 				throw e;
278 			}
279 
280 			// While looking for a ref outside of refs/ (e.g., 'config'), we
281 			// found a non-ref file (e.g., a config file) instead.  Treat this
282 			// as a ref-not-found condition.
283 			ref = null;
284 		}
285 		fireRefsChanged();
286 		return ref;
287 	}
288 
289 	@Override
290 	public Ref getRef(final String needle) throws IOException {
291 		final RefList<Ref> packed = getPackedRefs();
292 		Ref ref = null;
293 		for (String prefix : SEARCH_PATH) {
294 			try {
295 				ref = readRef(prefix + needle, packed);
296 				if (ref != null) {
297 					ref = resolve(ref, 0, null, null, packed);
298 				}
299 				if (ref != null) {
300 					break;
301 				}
302 			} catch (IOException e) {
303 				if (!(!needle.contains("/") && "".equals(prefix) && e //$NON-NLS-1$ //$NON-NLS-2$
304 						.getCause() instanceof InvalidObjectIdException)) {
305 					throw e;
306 				}
307 			}
308 		}
309 		fireRefsChanged();
310 		return ref;
311 	}
312 
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 
320 		RefList<LooseRef> loose;
321 		if (scan.newLoose != null) {
322 			scan.newLoose.sort();
323 			loose = scan.newLoose.toRefList();
324 			if (looseRefs.compareAndSet(oldLoose, loose))
325 				modCnt.incrementAndGet();
326 		} else
327 			loose = oldLoose;
328 		fireRefsChanged();
329 
330 		RefList.Builder<Ref> symbolic = scan.symbolic;
331 		for (int idx = 0; idx < symbolic.size();) {
332 			final Ref symbolicRef = symbolic.get(idx);
333 			final Ref resolvedRef = resolve(symbolicRef, 0, prefix, loose, packed);
334 			if (resolvedRef != null && resolvedRef.getObjectId() != null) {
335 				symbolic.set(idx, resolvedRef);
336 				idx++;
337 			} else {
338 				// A broken symbolic reference, we have to drop it from the
339 				// collections the client is about to receive. Should be a
340 				// rare occurrence so pay a copy penalty.
341 				symbolic.remove(idx);
342 				final int toRemove = loose.find(symbolicRef.getName());
343 				if (0 <= toRemove)
344 					loose = loose.remove(toRemove);
345 			}
346 		}
347 		symbolic.sort();
348 
349 		return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
350 	}
351 
352 	@Override
353 	public List<Ref> getAdditionalRefs() throws IOException {
354 		List<Ref> ret = new LinkedList<Ref>();
355 		for (String name : additionalRefsNames) {
356 			Ref r = getRef(name);
357 			if (r != null)
358 				ret.add(r);
359 		}
360 		return ret;
361 	}
362 
363 	@SuppressWarnings("unchecked")
364 	private RefList<Ref> upcast(RefList<? extends Ref> loose) {
365 		return (RefList<Ref>) loose;
366 	}
367 
368 	private class LooseScanner {
369 		private final RefList<LooseRef> curLoose;
370 
371 		private int curIdx;
372 
373 		final RefList.Builder<Ref> symbolic = new RefList.Builder<Ref>(4);
374 
375 		RefList.Builder<LooseRef> newLoose;
376 
377 		LooseScanner(final RefList<LooseRef> curLoose) {
378 			this.curLoose = curLoose;
379 		}
380 
381 		void scan(String prefix) {
382 			if (ALL.equals(prefix)) {
383 				scanOne(HEAD);
384 				scanTree(R_REFS, refsDir);
385 
386 				// If any entries remain, they are deleted, drop them.
387 				if (newLoose == null && curIdx < curLoose.size())
388 					newLoose = curLoose.copy(curIdx);
389 
390 			} else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { //$NON-NLS-1$
391 				curIdx = -(curLoose.find(prefix) + 1);
392 				File dir = new File(refsDir, prefix.substring(R_REFS.length()));
393 				scanTree(prefix, dir);
394 
395 				// Skip over entries still within the prefix; these have
396 				// been removed from the directory.
397 				while (curIdx < curLoose.size()) {
398 					if (!curLoose.get(curIdx).getName().startsWith(prefix))
399 						break;
400 					if (newLoose == null)
401 						newLoose = curLoose.copy(curIdx);
402 					curIdx++;
403 				}
404 
405 				// Keep any entries outside of the prefix space, we
406 				// do not know anything about their status.
407 				if (newLoose != null) {
408 					while (curIdx < curLoose.size())
409 						newLoose.add(curLoose.get(curIdx++));
410 				}
411 			}
412 		}
413 
414 		private boolean scanTree(String prefix, File dir) {
415 			final String[] entries = dir.list(LockFile.FILTER);
416 			if (entries == null) // not a directory or an I/O error
417 				return false;
418 			if (0 < entries.length) {
419 				for (int i = 0; i < entries.length; ++i) {
420 					String e = entries[i];
421 					File f = new File(dir, e);
422 					if (f.isDirectory())
423 						entries[i] += '/';
424 				}
425 				Arrays.sort(entries);
426 				for (String name : entries) {
427 					if (name.charAt(name.length() - 1) == '/')
428 						scanTree(prefix + name, new File(dir, name));
429 					else
430 						scanOne(prefix + name);
431 				}
432 			}
433 			return true;
434 		}
435 
436 		private void scanOne(String name) {
437 			LooseRef cur;
438 
439 			if (curIdx < curLoose.size()) {
440 				do {
441 					cur = curLoose.get(curIdx);
442 					int cmp = RefComparator.compareTo(cur, name);
443 					if (cmp < 0) {
444 						// Reference is not loose anymore, its been deleted.
445 						// Skip the name in the new result list.
446 						if (newLoose == null)
447 							newLoose = curLoose.copy(curIdx);
448 						curIdx++;
449 						cur = null;
450 						continue;
451 					}
452 
453 					if (cmp > 0) // Newly discovered loose reference.
454 						cur = null;
455 					break;
456 				} while (curIdx < curLoose.size());
457 			} else
458 				cur = null; // Newly discovered loose reference.
459 
460 			LooseRef n;
461 			try {
462 				n = scanRef(cur, name);
463 			} catch (IOException notValid) {
464 				n = null;
465 			}
466 
467 			if (n != null) {
468 				if (cur != n && newLoose == null)
469 					newLoose = curLoose.copy(curIdx);
470 				if (newLoose != null)
471 					newLoose.add(n);
472 				if (n.isSymbolic())
473 					symbolic.add(n);
474 			} else if (cur != null) {
475 				// Tragically, this file is no longer a loose reference.
476 				// Kill our cached entry of it.
477 				if (newLoose == null)
478 					newLoose = curLoose.copy(curIdx);
479 			}
480 
481 			if (cur != null)
482 				curIdx++;
483 		}
484 	}
485 
486 	@Override
487 	public Ref peel(final Ref ref) throws IOException {
488 		final Ref leaf = ref.getLeaf();
489 		if (leaf.isPeeled() || leaf.getObjectId() == null)
490 			return ref;
491 
492 		ObjectIdRef newLeaf = doPeel(leaf);
493 
494 		// Try to remember this peeling in the cache, so we don't have to do
495 		// it again in the future, but only if the reference is unchanged.
496 		if (leaf.getStorage().isLoose()) {
497 			RefList<LooseRef> curList = looseRefs.get();
498 			int idx = curList.find(leaf.getName());
499 			if (0 <= idx && curList.get(idx) == leaf) {
500 				LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
501 				RefList<LooseRef> newList = curList.set(idx, asPeeled);
502 				looseRefs.compareAndSet(curList, newList);
503 			}
504 		}
505 
506 		return recreate(ref, newLeaf);
507 	}
508 
509 	private ObjectIdRef doPeel(final Ref leaf) throws MissingObjectException,
510 			IOException {
511 		try (RevWalk rw = new RevWalk(getRepository())) {
512 			RevObject obj = rw.parseAny(leaf.getObjectId());
513 			if (obj instanceof RevTag) {
514 				return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
515 						.getName(), leaf.getObjectId(), rw.peel(obj).copy());
516 			} else {
517 				return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
518 						.getName(), leaf.getObjectId());
519 			}
520 		}
521 	}
522 
523 	private static Ref recreate(final Ref old, final ObjectIdRef leaf) {
524 		if (old.isSymbolic()) {
525 			Ref dst = recreate(old.getTarget(), leaf);
526 			return new SymbolicRef(old.getName(), dst);
527 		}
528 		return leaf;
529 	}
530 
531 	void storedSymbolicRef(RefDirectoryUpdate u, FileSnapshot snapshot,
532 			String target) {
533 		putLooseRef(newSymbolicRef(snapshot, u.getRef().getName(), target));
534 		fireRefsChanged();
535 	}
536 
537 	public RefDirectoryUpdate newUpdate(String name, boolean detach)
538 			throws IOException {
539 		boolean detachingSymbolicRef = false;
540 		final RefList<Ref> packed = getPackedRefs();
541 		Ref ref = readRef(name, packed);
542 		if (ref != null)
543 			ref = resolve(ref, 0, null, null, packed);
544 		if (ref == null)
545 			ref = new ObjectIdRef.Unpeeled(NEW, name, null);
546 		else {
547 			detachingSymbolicRef = detach && ref.isSymbolic();
548 			if (detachingSymbolicRef)
549 				ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId());
550 		}
551 		RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
552 		if (detachingSymbolicRef)
553 			refDirUpdate.setDetachingSymbolicRef();
554 		return refDirUpdate;
555 	}
556 
557 	@Override
558 	public RefDirectoryRename newRename(String fromName, String toName)
559 			throws IOException {
560 		RefDirectoryUpdate from = newUpdate(fromName, false);
561 		RefDirectoryUpdate to = newUpdate(toName, false);
562 		return new RefDirectoryRename(from, to);
563 	}
564 
565 	void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
566 		final ObjectId target = update.getNewObjectId().copy();
567 		final Ref leaf = update.getRef().getLeaf();
568 		putLooseRef(new LooseUnpeeled(snapshot, leaf.getName(), target));
569 	}
570 
571 	private void putLooseRef(LooseRef ref) {
572 		RefList<LooseRef> cList, nList;
573 		do {
574 			cList = looseRefs.get();
575 			nList = cList.put(ref);
576 		} while (!looseRefs.compareAndSet(cList, nList));
577 		modCnt.incrementAndGet();
578 		fireRefsChanged();
579 	}
580 
581 	void delete(RefDirectoryUpdate update) throws IOException {
582 		Ref dst = update.getRef().getLeaf();
583 		String name = dst.getName();
584 
585 		// Write the packed-refs file using an atomic update. We might
586 		// wind up reading it twice, before and after the lock, to ensure
587 		// we don't miss an edit made externally.
588 		final PackedRefList packed = getPackedRefs();
589 		if (packed.contains(name)) {
590 			LockFile lck = new LockFile(packedRefsFile);
591 			if (!lck.lock())
592 				throw new LockFailedException(packedRefsFile);
593 			try {
594 				PackedRefList cur = readPackedRefs();
595 				int idx = cur.find(name);
596 				if (0 <= idx)
597 					commitPackedRefs(lck, cur.remove(idx), packed);
598 			} finally {
599 				lck.unlock();
600 			}
601 		}
602 
603 		RefList<LooseRef> curLoose, newLoose;
604 		do {
605 			curLoose = looseRefs.get();
606 			int idx = curLoose.find(name);
607 			if (idx < 0)
608 				break;
609 			newLoose = curLoose.remove(idx);
610 		} while (!looseRefs.compareAndSet(curLoose, newLoose));
611 
612 		int levels = levelsIn(name) - 2;
613 		delete(logWriter.logFor(name), levels);
614 		if (dst.getStorage().isLoose()) {
615 			update.unlock();
616 			delete(fileFor(name), levels);
617 		}
618 
619 		modCnt.incrementAndGet();
620 		fireRefsChanged();
621 	}
622 
623 	/**
624 	 * Adds a set of refs to the set of packed-refs. Only non-symbolic refs are
625 	 * added. If a ref with the given name already existed in packed-refs it is
626 	 * updated with the new value. Each loose ref which was added to the
627 	 * packed-ref file is deleted. If a given ref can't be locked it will not be
628 	 * added to the pack file.
629 	 *
630 	 * @param refs
631 	 *            the refs to be added. Must be fully qualified.
632 	 * @throws IOException
633 	 */
634 	public void pack(List<String> refs) throws IOException {
635 		if (refs.size() == 0)
636 			return;
637 		FS fs = parent.getFS();
638 
639 		// Lock the packed refs file and read the content
640 		LockFile lck = new LockFile(packedRefsFile);
641 		if (!lck.lock())
642 			throw new IOException(MessageFormat.format(
643 					JGitText.get().cannotLock, packedRefsFile));
644 
645 		try {
646 			final PackedRefList packed = getPackedRefs();
647 			RefList<Ref> cur = readPackedRefs();
648 
649 			// Iterate over all refs to be packed
650 			for (String refName : refs) {
651 				Ref ref = readRef(refName, cur);
652 				if (ref.isSymbolic())
653 					continue; // can't pack symbolic refs
654 				// Add/Update it to packed-refs
655 				int idx = cur.find(refName);
656 				if (idx >= 0)
657 					cur = cur.set(idx, peeledPackedRef(ref));
658 				else
659 					cur = cur.add(idx, peeledPackedRef(ref));
660 			}
661 
662 			// The new content for packed-refs is collected. Persist it.
663 			commitPackedRefs(lck, cur, packed);
664 
665 			// Now delete the loose refs which are now packed
666 			for (String refName : refs) {
667 				// Lock the loose ref
668 				File refFile = fileFor(refName);
669 				if (!fs.exists(refFile))
670 					continue;
671 				LockFile rLck = new LockFile(refFile);
672 				if (!rLck.lock())
673 					continue;
674 				try {
675 					LooseRef currentLooseRef = scanRef(null, refName);
676 					if (currentLooseRef == null || currentLooseRef.isSymbolic())
677 						continue;
678 					Ref packedRef = cur.get(refName);
679 					ObjectId clr_oid = currentLooseRef.getObjectId();
680 					if (clr_oid != null
681 							&& clr_oid.equals(packedRef.getObjectId())) {
682 						RefList<LooseRef> curLoose, newLoose;
683 						do {
684 							curLoose = looseRefs.get();
685 							int idx = curLoose.find(refName);
686 							if (idx < 0)
687 								break;
688 							newLoose = curLoose.remove(idx);
689 						} while (!looseRefs.compareAndSet(curLoose, newLoose));
690 						int levels = levelsIn(refName) - 2;
691 						delete(fileFor(refName), levels);
692 					}
693 				} finally {
694 					rLck.unlock();
695 				}
696 			}
697 			// Don't fire refsChanged. The refs have not change, only their
698 			// storage.
699 		} finally {
700 			lck.unlock();
701 		}
702 	}
703 
704 	/**
705 	 * Make sure a ref is peeled and has the Storage PACKED. If the given ref
706 	 * has this attributes simply return it. Otherwise create a new peeled
707 	 * {@link ObjectIdRef} where Storage is set to PACKED.
708 	 *
709 	 * @param f
710 	 * @return a ref for Storage PACKED having the same name, id, peeledId as f
711 	 * @throws MissingObjectException
712 	 * @throws IOException
713 	 */
714 	private Ref peeledPackedRef(Ref f)
715 			throws MissingObjectException, IOException {
716 		if (f.getStorage().isPacked() && f.isPeeled()) {
717 			return f;
718 		}
719 		if (!f.isPeeled()) {
720 			f = peel(f);
721 		}
722 		ObjectId peeledObjectId = f.getPeeledObjectId();
723 		if (peeledObjectId != null) {
724 			return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
725 					f.getObjectId(), peeledObjectId);
726 		} else {
727 			return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
728 					f.getObjectId());
729 		}
730 	}
731 
732 	void log(final RefUpdate update, final String msg, final boolean deref)
733 			throws IOException {
734 		logWriter.log(update, msg, deref);
735 	}
736 
737 	private Ref resolve(final Ref ref, int depth, String prefix,
738 			RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
739 		if (ref.isSymbolic()) {
740 			Ref dst = ref.getTarget();
741 
742 			if (MAX_SYMBOLIC_REF_DEPTH <= depth)
743 				return null; // claim it doesn't exist
744 
745 			// If the cached value can be assumed to be current due to a
746 			// recent scan of the loose directory, use it.
747 			if (loose != null && dst.getName().startsWith(prefix)) {
748 				int idx;
749 				if (0 <= (idx = loose.find(dst.getName())))
750 					dst = loose.get(idx);
751 				else if (0 <= (idx = packed.find(dst.getName())))
752 					dst = packed.get(idx);
753 				else
754 					return ref;
755 			} else {
756 				dst = readRef(dst.getName(), packed);
757 				if (dst == null)
758 					return ref;
759 			}
760 
761 			dst = resolve(dst, depth + 1, prefix, loose, packed);
762 			if (dst == null)
763 				return null;
764 			return new SymbolicRef(ref.getName(), dst);
765 		}
766 		return ref;
767 	}
768 
769 	private PackedRefList getPackedRefs() throws IOException {
770 		final PackedRefList curList = packedRefs.get();
771 		if (!curList.snapshot.isModified(packedRefsFile))
772 			return curList;
773 
774 		final PackedRefList newList = readPackedRefs();
775 		if (packedRefs.compareAndSet(curList, newList)
776 				&& !curList.id.equals(newList.id))
777 			modCnt.incrementAndGet();
778 		return newList;
779 	}
780 
781 	private PackedRefList readPackedRefs() throws IOException {
782 		int maxStaleRetries = 5;
783 		int retries = 0;
784 		while (true) {
785 			final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
786 			final BufferedReader br;
787 			final MessageDigest digest = Constants.newMessageDigest();
788 			try {
789 				br = new BufferedReader(new InputStreamReader(
790 						new DigestInputStream(new FileInputStream(packedRefsFile),
791 								digest), CHARSET));
792 			} catch (FileNotFoundException noPackedRefs) {
793 				if (packedRefsFile.exists()) {
794 					throw noPackedRefs;
795 				}
796 				// Ignore it and leave the new list empty.
797 				return PackedRefList.NO_PACKED_REFS;
798 			}
799 			try {
800 				return new PackedRefList(parsePackedRefs(br), snapshot,
801 						ObjectId.fromRaw(digest.digest()));
802 			} catch (IOException e) {
803 				if (FileUtils.isStaleFileHandle(e) && retries < maxStaleRetries) {
804 					if (LOG.isDebugEnabled()) {
805 						LOG.debug(MessageFormat.format(
806 								JGitText.get().packedRefsHandleIsStale,
807 								Integer.valueOf(retries)), e);
808 					}
809 					retries++;
810 					continue;
811 				}
812 				throw e;
813 			} finally {
814 				br.close();
815 			}
816 		}
817 	}
818 
819 	private RefList<Ref> parsePackedRefs(final BufferedReader br)
820 			throws IOException {
821 		RefList.Builder<Ref> all = new RefList.Builder<Ref>();
822 		Ref last = null;
823 		boolean peeled = false;
824 		boolean needSort = false;
825 
826 		String p;
827 		while ((p = br.readLine()) != null) {
828 			if (p.charAt(0) == '#') {
829 				if (p.startsWith(PACKED_REFS_HEADER)) {
830 					p = p.substring(PACKED_REFS_HEADER.length());
831 					peeled = p.contains(PACKED_REFS_PEELED);
832 				}
833 				continue;
834 			}
835 
836 			if (p.charAt(0) == '^') {
837 				if (last == null)
838 					throw new IOException(JGitText.get().peeledLineBeforeRef);
839 
840 				ObjectId id = ObjectId.fromString(p.substring(1));
841 				last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
842 						.getObjectId(), id);
843 				all.set(all.size() - 1, last);
844 				continue;
845 			}
846 
847 			int sp = p.indexOf(' ');
848 			ObjectId id = ObjectId.fromString(p.substring(0, sp));
849 			String name = copy(p, sp + 1, p.length());
850 			ObjectIdRef cur;
851 			if (peeled)
852 				cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
853 			else
854 				cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
855 			if (last != null && RefComparator.compareTo(last, cur) > 0)
856 				needSort = true;
857 			all.add(cur);
858 			last = cur;
859 		}
860 
861 		if (needSort)
862 			all.sort();
863 		return all.toRefList();
864 	}
865 
866 	private static String copy(final String src, final int off, final int end) {
867 		// Don't use substring since it could leave a reference to the much
868 		// larger existing string. Force construction of a full new object.
869 		return new StringBuilder(end - off).append(src, off, end).toString();
870 	}
871 
872 	private void commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
873 			final PackedRefList oldPackedList) throws IOException {
874 		new RefWriter(refs) {
875 			@Override
876 			protected void writeFile(String name, byte[] content)
877 					throws IOException {
878 				lck.setFSync(true);
879 				lck.setNeedSnapshot(true);
880 				try {
881 					lck.write(content);
882 				} catch (IOException ioe) {
883 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe);
884 				}
885 				try {
886 					lck.waitForStatChange();
887 				} catch (InterruptedException e) {
888 					lck.unlock();
889 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name));
890 				}
891 				if (!lck.commit())
892 					throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
893 
894 				byte[] digest = Constants.newMessageDigest().digest(content);
895 				packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
896 						lck.getCommitSnapshot(), ObjectId.fromRaw(digest)));
897 			}
898 		}.writePackedRefs();
899 	}
900 
901 	private Ref readRef(String name, RefList<Ref> packed) throws IOException {
902 		final RefList<LooseRef> curList = looseRefs.get();
903 		final int idx = curList.find(name);
904 		if (0 <= idx) {
905 			final LooseRef o = curList.get(idx);
906 			final LooseRef n = scanRef(o, name);
907 			if (n == null) {
908 				if (looseRefs.compareAndSet(curList, curList.remove(idx)))
909 					modCnt.incrementAndGet();
910 				return packed.get(name);
911 			}
912 
913 			if (o == n)
914 				return n;
915 			if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
916 				modCnt.incrementAndGet();
917 			return n;
918 		}
919 
920 		final LooseRef n = scanRef(null, name);
921 		if (n == null)
922 			return packed.get(name);
923 
924 		// check whether the found new ref is the an additional ref. These refs
925 		// should not go into looseRefs
926 		for (int i = 0; i < additionalRefsNames.length; i++)
927 			if (name.equals(additionalRefsNames[i]))
928 				return n;
929 
930 		if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
931 			modCnt.incrementAndGet();
932 		return n;
933 	}
934 
935 	LooseRef scanRef(LooseRef ref, String name) throws IOException {
936 		final File path = fileFor(name);
937 		FileSnapshot currentSnapshot = null;
938 
939 		if (ref != null) {
940 			currentSnapshot = ref.getSnapShot();
941 			if (!currentSnapshot.isModified(path))
942 				return ref;
943 			name = ref.getName();
944 		}
945 
946 		final int limit = 4096;
947 		final byte[] buf;
948 		FileSnapshot otherSnapshot = FileSnapshot.save(path);
949 		try {
950 			buf = IO.readSome(path, limit);
951 		} catch (FileNotFoundException noFile) {
952 			if (path.exists() && path.isFile()) {
953 				throw noFile;
954 			}
955 			return null; // doesn't exist or no file; not a reference.
956 		}
957 
958 		int n = buf.length;
959 		if (n == 0)
960 			return null; // empty file; not a reference.
961 
962 		if (isSymRef(buf, n)) {
963 			if (n == limit)
964 				return null; // possibly truncated ref
965 
966 			// trim trailing whitespace
967 			while (0 < n && Character.isWhitespace(buf[n - 1]))
968 				n--;
969 			if (n < 6) {
970 				String content = RawParseUtils.decode(buf, 0, n);
971 				throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content));
972 			}
973 			final String target = RawParseUtils.decode(buf, 5, n);
974 			if (ref != null && ref.isSymbolic()
975 					&& ref.getTarget().getName().equals(target)) {
976 				assert(currentSnapshot != null);
977 				currentSnapshot.setClean(otherSnapshot);
978 				return ref;
979 			}
980 			return newSymbolicRef(otherSnapshot, name, target);
981 		}
982 
983 		if (n < OBJECT_ID_STRING_LENGTH)
984 			return null; // impossibly short object identifier; not a reference.
985 
986 		final ObjectId id;
987 		try {
988 			id = ObjectId.fromString(buf, 0);
989 			if (ref != null && !ref.isSymbolic()
990 					&& id.equals(ref.getTarget().getObjectId())) {
991 				assert(currentSnapshot != null);
992 				currentSnapshot.setClean(otherSnapshot);
993 				return ref;
994 			}
995 
996 		} catch (IllegalArgumentException notRef) {
997 			while (0 < n && Character.isWhitespace(buf[n - 1]))
998 				n--;
999 			String content = RawParseUtils.decode(buf, 0, n);
1000 
1001 			IOException ioException = new IOException(MessageFormat.format(JGitText.get().notARef,
1002 					name, content));
1003 			ioException.initCause(notRef);
1004 			throw ioException;
1005 		}
1006 		return new LooseUnpeeled(otherSnapshot, name, id);
1007 	}
1008 
1009 	private static boolean isSymRef(final byte[] buf, int n) {
1010 		if (n < 6)
1011 			return false;
1012 		return /**/buf[0] == 'r' //
1013 				&& buf[1] == 'e' //
1014 				&& buf[2] == 'f' //
1015 				&& buf[3] == ':' //
1016 				&& buf[4] == ' ';
1017 	}
1018 
1019 	/** If the parent should fire listeners, fires them. */
1020 	private void fireRefsChanged() {
1021 		final int last = lastNotifiedModCnt.get();
1022 		final int curr = modCnt.get();
1023 		if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
1024 			parent.fireEvent(new RefsChangedEvent());
1025 	}
1026 
1027 	/**
1028 	 * Create a reference update to write a temporary reference.
1029 	 *
1030 	 * @return an update for a new temporary reference.
1031 	 * @throws IOException
1032 	 *             a temporary name cannot be allocated.
1033 	 */
1034 	RefDirectoryUpdate newTemporaryUpdate() throws IOException {
1035 		File tmp = File.createTempFile("renamed_", "_ref", refsDir); //$NON-NLS-1$ //$NON-NLS-2$
1036 		String name = Constants.R_REFS + tmp.getName();
1037 		Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
1038 		return new RefDirectoryUpdate(this, ref);
1039 	}
1040 
1041 	/**
1042 	 * Locate the file on disk for a single reference name.
1043 	 *
1044 	 * @param name
1045 	 *            name of the ref, relative to the Git repository top level
1046 	 *            directory (so typically starts with refs/).
1047 	 * @return the loose file location.
1048 	 */
1049 	File fileFor(String name) {
1050 		if (name.startsWith(R_REFS)) {
1051 			name = name.substring(R_REFS.length());
1052 			return new File(refsDir, name);
1053 		}
1054 		return new File(gitDir, name);
1055 	}
1056 
1057 	static int levelsIn(final String name) {
1058 		int count = 0;
1059 		for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
1060 			count++;
1061 		return count;
1062 	}
1063 
1064 	static void delete(final File file, final int depth) throws IOException {
1065 		if (!file.delete() && file.isFile())
1066 			throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file));
1067 
1068 		File dir = file.getParentFile();
1069 		for (int i = 0; i < depth; ++i) {
1070 			if (!dir.delete())
1071 				break; // ignore problem here
1072 			dir = dir.getParentFile();
1073 		}
1074 	}
1075 
1076 	private static class PackedRefList extends RefList<Ref> {
1077 		static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1078 				RefList.emptyList(), FileSnapshot.MISSING_FILE,
1079 				ObjectId.zeroId());
1080 
1081 		final FileSnapshot snapshot;
1082 
1083 		final ObjectId id;
1084 
1085 		PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1086 			super(src);
1087 			snapshot = s;
1088 			id = i;
1089 		}
1090 	}
1091 
1092 	private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
1093 			String name, String target) {
1094 		Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
1095 		return new LooseSymbolicRef(snapshot, name, dst);
1096 	}
1097 
1098 	private static interface LooseRef extends Ref {
1099 		FileSnapshot getSnapShot();
1100 
1101 		LooseRef peel(ObjectIdRef newLeaf);
1102 	}
1103 
1104 	private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
1105 			implements LooseRef {
1106 		private final FileSnapshot snapShot;
1107 
1108 		LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
1109 				@NonNull ObjectId id, @NonNull ObjectId p) {
1110 			super(LOOSE, refName, id, p);
1111 			this.snapShot = snapshot;
1112 		}
1113 
1114 		public FileSnapshot getSnapShot() {
1115 			return snapShot;
1116 		}
1117 
1118 		public LooseRef peel(ObjectIdRef newLeaf) {
1119 			return this;
1120 		}
1121 	}
1122 
1123 	private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
1124 			implements LooseRef {
1125 		private final FileSnapshot snapShot;
1126 
1127 		LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
1128 				@NonNull ObjectId id) {
1129 			super(LOOSE, refName, id);
1130 			this.snapShot = snapshot;
1131 		}
1132 
1133 		public FileSnapshot getSnapShot() {
1134 			return snapShot;
1135 		}
1136 
1137 		public LooseRef peel(ObjectIdRef newLeaf) {
1138 			return this;
1139 		}
1140 	}
1141 
1142 	private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
1143 			implements LooseRef {
1144 		private FileSnapshot snapShot;
1145 
1146 		LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
1147 				@NonNull ObjectId id) {
1148 			super(LOOSE, refName, id);
1149 			this.snapShot = snapShot;
1150 		}
1151 
1152 		public FileSnapshot getSnapShot() {
1153 			return snapShot;
1154 		}
1155 
1156 		@NonNull
1157 		@Override
1158 		public ObjectId getObjectId() {
1159 			ObjectId id = super.getObjectId();
1160 			assert id != null; // checked in constructor
1161 			return id;
1162 		}
1163 
1164 		public LooseRef peel(ObjectIdRef newLeaf) {
1165 			ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
1166 			ObjectId objectId = getObjectId();
1167 			if (peeledObjectId != null) {
1168 				return new LoosePeeledTag(snapShot, getName(),
1169 						objectId, peeledObjectId);
1170 			} else {
1171 				return new LooseNonTag(snapShot, getName(),
1172 						objectId);
1173 			}
1174 		}
1175 	}
1176 
1177 	private final static class LooseSymbolicRef extends SymbolicRef implements
1178 			LooseRef {
1179 		private final FileSnapshot snapShot;
1180 
1181 		LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
1182 				@NonNull Ref target) {
1183 			super(refName, target);
1184 			this.snapShot = snapshot;
1185 		}
1186 
1187 		public FileSnapshot getSnapShot() {
1188 			return snapShot;
1189 		}
1190 
1191 		public LooseRef peel(ObjectIdRef newLeaf) {
1192 			// We should never try to peel the symbolic references.
1193 			throw new UnsupportedOperationException();
1194 		}
1195 	}
1196 }