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