View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2008-2010, Google Inc.
4    * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2006-2012, Shawn O. Pearce <spearce@spearce.org>
6    * Copyright (C) 2012, Daniel Megert <daniel_megert@ch.ibm.com>
7    * Copyright (C) 2017, Wim Jongman <wim.jongman@remainsoftware.com>
8    * and other copyright owners as documented in the project's IP log.
9    *
10   * This program and the accompanying materials are made available
11   * under the terms of the Eclipse Distribution License v1.0 which
12   * accompanies this distribution, is reproduced below, and is
13   * available at http://www.eclipse.org/org/documents/edl-v10.php
14   *
15   * All rights reserved.
16   *
17   * Redistribution and use in source and binary forms, with or
18   * without modification, are permitted provided that the following
19   * conditions are met:
20   *
21   * - Redistributions of source code must retain the above copyright
22   *   notice, this list of conditions and the following disclaimer.
23   *
24   * - Redistributions in binary form must reproduce the above
25   *   copyright notice, this list of conditions and the following
26   *   disclaimer in the documentation and/or other materials provided
27   *   with the distribution.
28   *
29   * - Neither the name of the Eclipse Foundation, Inc. nor the
30   *   names of its contributors may be used to endorse or promote
31   *   products derived from this software without specific prior
32   *   written permission.
33   *
34   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
35   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
36   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
38   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
39   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
43   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
46   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47   */
48  
49  package org.eclipse.jgit.lib;
50  
51  import java.io.BufferedOutputStream;
52  import java.io.File;
53  import java.io.FileNotFoundException;
54  import java.io.FileOutputStream;
55  import java.io.IOException;
56  import java.io.OutputStream;
57  import java.net.URISyntaxException;
58  import java.text.MessageFormat;
59  import java.util.Collection;
60  import java.util.Collections;
61  import java.util.HashMap;
62  import java.util.HashSet;
63  import java.util.LinkedList;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Set;
67  import java.util.concurrent.atomic.AtomicInteger;
68  import java.util.concurrent.atomic.AtomicLong;
69  import java.util.regex.Pattern;
70  
71  import org.eclipse.jgit.annotations.NonNull;
72  import org.eclipse.jgit.annotations.Nullable;
73  import org.eclipse.jgit.attributes.AttributesNodeProvider;
74  import org.eclipse.jgit.dircache.DirCache;
75  import org.eclipse.jgit.errors.AmbiguousObjectException;
76  import org.eclipse.jgit.errors.CorruptObjectException;
77  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
78  import org.eclipse.jgit.errors.MissingObjectException;
79  import org.eclipse.jgit.errors.NoWorkTreeException;
80  import org.eclipse.jgit.errors.RevisionSyntaxException;
81  import org.eclipse.jgit.events.IndexChangedEvent;
82  import org.eclipse.jgit.events.IndexChangedListener;
83  import org.eclipse.jgit.events.ListenerList;
84  import org.eclipse.jgit.events.RepositoryEvent;
85  import org.eclipse.jgit.internal.JGitText;
86  import org.eclipse.jgit.internal.storage.file.GC;
87  import org.eclipse.jgit.revwalk.RevBlob;
88  import org.eclipse.jgit.revwalk.RevCommit;
89  import org.eclipse.jgit.revwalk.RevObject;
90  import org.eclipse.jgit.revwalk.RevTree;
91  import org.eclipse.jgit.revwalk.RevWalk;
92  import org.eclipse.jgit.transport.RefSpec;
93  import org.eclipse.jgit.transport.RemoteConfig;
94  import org.eclipse.jgit.treewalk.TreeWalk;
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.SystemReader;
100 import org.slf4j.Logger;
101 import org.slf4j.LoggerFactory;
102 
103 /**
104  * Represents a Git repository.
105  * <p>
106  * A repository holds all objects and refs used for managing source code (could
107  * be any type of file, but source code is what SCM's are typically used for).
108  * <p>
109  * The thread-safety of a {@link Repository} very much depends on the concrete
110  * implementation. Applications working with a generic {@code Repository} type
111  * must not assume the instance is thread-safe.
112  * <ul>
113  * <li>{@code FileRepository} is thread-safe.
114  * <li>{@code DfsRepository} thread-safety is determined by its subclass.
115  * </ul>
116  */
117 public abstract class Repository implements AutoCloseable {
118 	private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
119 	private static final ListenerList globalListeners = new ListenerList();
120 
121 	/**
122 	 * Branch names containing slashes should not have a name component that is
123 	 * one of the reserved device names on Windows.
124 	 *
125 	 * @see #normalizeBranchName(String)
126 	 */
127 	private static final Pattern FORBIDDEN_BRANCH_NAME_COMPONENTS = Pattern
128 			.compile(
129 					"(^|/)(aux|com[1-9]|con|lpt[1-9]|nul|prn)(\\.[^/]*)?", //$NON-NLS-1$
130 					Pattern.CASE_INSENSITIVE);
131 
132 	/** @return the global listener list observing all events in this JVM. */
133 	public static ListenerList getGlobalListenerList() {
134 		return globalListeners;
135 	}
136 
137 	/** Use counter */
138 	final AtomicInteger useCnt = new AtomicInteger(1);
139 
140 	final AtomicLong closedAt = new AtomicLong();
141 
142 	/** Metadata directory holding the repository's critical files. */
143 	private final File gitDir;
144 
145 	/** File abstraction used to resolve paths. */
146 	private final FS fs;
147 
148 	private final ListenerList myListeners = new ListenerList();
149 
150 	/** If not bare, the top level directory of the working files. */
151 	private final File workTree;
152 
153 	/** If not bare, the index file caching the working file states. */
154 	private final File indexFile;
155 
156 	/**
157 	 * Initialize a new repository instance.
158 	 *
159 	 * @param options
160 	 *            options to configure the repository.
161 	 */
162 	protected Repository(final BaseRepositoryBuilder options) {
163 		gitDir = options.getGitDir();
164 		fs = options.getFS();
165 		workTree = options.getWorkTree();
166 		indexFile = options.getIndexFile();
167 	}
168 
169 	/** @return listeners observing only events on this repository. */
170 	@NonNull
171 	public ListenerList getListenerList() {
172 		return myListeners;
173 	}
174 
175 	/**
176 	 * Fire an event to all registered listeners.
177 	 * <p>
178 	 * The source repository of the event is automatically set to this
179 	 * repository, before the event is delivered to any listeners.
180 	 *
181 	 * @param event
182 	 *            the event to deliver.
183 	 */
184 	public void fireEvent(RepositoryEvent<?> event) {
185 		event.setRepository(this);
186 		myListeners.dispatch(event);
187 		globalListeners.dispatch(event);
188 	}
189 
190 	/**
191 	 * Create a new Git repository.
192 	 * <p>
193 	 * Repository with working tree is created using this method. This method is
194 	 * the same as {@code create(false)}.
195 	 *
196 	 * @throws IOException
197 	 * @see #create(boolean)
198 	 */
199 	public void create() throws IOException {
200 		create(false);
201 	}
202 
203 	/**
204 	 * Create a new Git repository initializing the necessary files and
205 	 * directories.
206 	 *
207 	 * @param bare
208 	 *            if true, a bare repository (a repository without a working
209 	 *            directory) is created.
210 	 * @throws IOException
211 	 *             in case of IO problem
212 	 */
213 	public abstract void create(boolean bare) throws IOException;
214 
215 	/**
216 	 * @return local metadata directory; {@code null} if repository isn't local.
217 	 */
218 	/*
219 	 * TODO This method should be annotated as Nullable, because in some
220 	 * specific configurations metadata is not located in the local file system
221 	 * (for example in memory databases). In "usual" repositories this
222 	 * annotation would only cause compiler errors at places where the actual
223 	 * directory can never be null.
224 	 */
225 	public File getDirectory() {
226 		return gitDir;
227 	}
228 
229 	/**
230 	 * @return the object database which stores this repository's data.
231 	 */
232 	@NonNull
233 	public abstract ObjectDatabase getObjectDatabase();
234 
235 	/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
236 	@NonNull
237 	public ObjectInserter newObjectInserter() {
238 		return getObjectDatabase().newInserter();
239 	}
240 
241 	/** @return a new reader to read objects from {@link #getObjectDatabase()} */
242 	@NonNull
243 	public ObjectReader newObjectReader() {
244 		return getObjectDatabase().newReader();
245 	}
246 
247 	/** @return the reference database which stores the reference namespace. */
248 	@NonNull
249 	public abstract RefDatabase getRefDatabase();
250 
251 	/**
252 	 * @return the configuration of this repository
253 	 */
254 	@NonNull
255 	public abstract StoredConfig getConfig();
256 
257 	/**
258 	 * @return a new {@link AttributesNodeProvider}. This
259 	 *         {@link AttributesNodeProvider} is lazy loaded only once. It means
260 	 *         that it will not be updated after loading. Prefer creating new
261 	 *         instance for each use.
262 	 * @since 4.2
263 	 */
264 	@NonNull
265 	public abstract AttributesNodeProvider createAttributesNodeProvider();
266 
267 	/**
268 	 * @return the used file system abstraction, or or {@code null} if
269 	 *         repository isn't local.
270 	 */
271 	/*
272 	 * TODO This method should be annotated as Nullable, because in some
273 	 * specific configurations metadata is not located in the local file system
274 	 * (for example in memory databases). In "usual" repositories this
275 	 * annotation would only cause compiler errors at places where the actual
276 	 * directory can never be null.
277 	 */
278 	public FS getFS() {
279 		return fs;
280 	}
281 
282 	/**
283 	 * @param objectId
284 	 * @return true if the specified object is stored in this repo or any of the
285 	 *         known shared repositories.
286 	 */
287 	public boolean hasObject(AnyObjectId objectId) {
288 		try {
289 			return getObjectDatabase().has(objectId);
290 		} catch (IOException e) {
291 			// Legacy API, assume error means "no"
292 			return false;
293 		}
294 	}
295 
296 	/**
297 	 * Open an object from this repository.
298 	 * <p>
299 	 * This is a one-shot call interface which may be faster than allocating a
300 	 * {@link #newObjectReader()} to perform the lookup.
301 	 *
302 	 * @param objectId
303 	 *            identity of the object to open.
304 	 * @return a {@link ObjectLoader} for accessing the object.
305 	 * @throws MissingObjectException
306 	 *             the object does not exist.
307 	 * @throws IOException
308 	 *             the object store cannot be accessed.
309 	 */
310 	@NonNull
311 	public ObjectLoader open(final AnyObjectId objectId)
312 			throws MissingObjectException, IOException {
313 		return getObjectDatabase().open(objectId);
314 	}
315 
316 	/**
317 	 * Open an object from this repository.
318 	 * <p>
319 	 * This is a one-shot call interface which may be faster than allocating a
320 	 * {@link #newObjectReader()} to perform the lookup.
321 	 *
322 	 * @param objectId
323 	 *            identity of the object to open.
324 	 * @param typeHint
325 	 *            hint about the type of object being requested, e.g.
326 	 *            {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if
327 	 *            the object type is not known, or does not matter to the
328 	 *            caller.
329 	 * @return a {@link ObjectLoader} for accessing the object.
330 	 * @throws MissingObjectException
331 	 *             the object does not exist.
332 	 * @throws IncorrectObjectTypeException
333 	 *             typeHint was not OBJ_ANY, and the object's actual type does
334 	 *             not match typeHint.
335 	 * @throws IOException
336 	 *             the object store cannot be accessed.
337 	 */
338 	@NonNull
339 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
340 			throws MissingObjectException, IncorrectObjectTypeException,
341 			IOException {
342 		return getObjectDatabase().open(objectId, typeHint);
343 	}
344 
345 	/**
346 	 * Create a command to update, create or delete a ref in this repository.
347 	 *
348 	 * @param ref
349 	 *            name of the ref the caller wants to modify.
350 	 * @return an update command. The caller must finish populating this command
351 	 *         and then invoke one of the update methods to actually make a
352 	 *         change.
353 	 * @throws IOException
354 	 *             a symbolic ref was passed in and could not be resolved back
355 	 *             to the base ref, as the symbolic ref could not be read.
356 	 */
357 	@NonNull
358 	public RefUpdate updateRef(final String ref) throws IOException {
359 		return updateRef(ref, false);
360 	}
361 
362 	/**
363 	 * Create a command to update, create or delete a ref in this repository.
364 	 *
365 	 * @param ref
366 	 *            name of the ref the caller wants to modify.
367 	 * @param detach
368 	 *            true to create a detached head
369 	 * @return an update command. The caller must finish populating this command
370 	 *         and then invoke one of the update methods to actually make a
371 	 *         change.
372 	 * @throws IOException
373 	 *             a symbolic ref was passed in and could not be resolved back
374 	 *             to the base ref, as the symbolic ref could not be read.
375 	 */
376 	@NonNull
377 	public RefUpdate updateRef(final String ref, final boolean detach) throws IOException {
378 		return getRefDatabase().newUpdate(ref, detach);
379 	}
380 
381 	/**
382 	 * Create a command to rename a ref in this repository
383 	 *
384 	 * @param fromRef
385 	 *            name of ref to rename from
386 	 * @param toRef
387 	 *            name of ref to rename to
388 	 * @return an update command that knows how to rename a branch to another.
389 	 * @throws IOException
390 	 *             the rename could not be performed.
391 	 *
392 	 */
393 	@NonNull
394 	public RefRename renameRef(final String fromRef, final String toRef) throws IOException {
395 		return getRefDatabase().newRename(fromRef, toRef);
396 	}
397 
398 	/**
399 	 * Parse a git revision string and return an object id.
400 	 *
401 	 * Combinations of these operators are supported:
402 	 * <ul>
403 	 * <li><b>HEAD</b>, <b>MERGE_HEAD</b>, <b>FETCH_HEAD</b></li>
404 	 * <li><b>SHA-1</b>: a complete or abbreviated SHA-1</li>
405 	 * <li><b>refs/...</b>: a complete reference name</li>
406 	 * <li><b>short-name</b>: a short reference name under {@code refs/heads},
407 	 * {@code refs/tags}, or {@code refs/remotes} namespace</li>
408 	 * <li><b>tag-NN-gABBREV</b>: output from describe, parsed by treating
409 	 * {@code ABBREV} as an abbreviated SHA-1.</li>
410 	 * <li><i>id</i><b>^</b>: first parent of commit <i>id</i>, this is the same
411 	 * as {@code id^1}</li>
412 	 * <li><i>id</i><b>^0</b>: ensure <i>id</i> is a commit</li>
413 	 * <li><i>id</i><b>^n</b>: n-th parent of commit <i>id</i></li>
414 	 * <li><i>id</i><b>~n</b>: n-th historical ancestor of <i>id</i>, by first
415 	 * parent. {@code id~3} is equivalent to {@code id^1^1^1} or {@code id^^^}.</li>
416 	 * <li><i>id</i><b>:path</b>: Lookup path under tree named by <i>id</i></li>
417 	 * <li><i>id</i><b>^{commit}</b>: ensure <i>id</i> is a commit</li>
418 	 * <li><i>id</i><b>^{tree}</b>: ensure <i>id</i> is a tree</li>
419 	 * <li><i>id</i><b>^{tag}</b>: ensure <i>id</i> is a tag</li>
420 	 * <li><i>id</i><b>^{blob}</b>: ensure <i>id</i> is a blob</li>
421 	 * </ul>
422 	 *
423 	 * <p>
424 	 * The following operators are specified by Git conventions, but are not
425 	 * supported by this method:
426 	 * <ul>
427 	 * <li><b>ref@{n}</b>: n-th version of ref as given by its reflog</li>
428 	 * <li><b>ref@{time}</b>: value of ref at the designated time</li>
429 	 * </ul>
430 	 *
431 	 * @param revstr
432 	 *            A git object references expression
433 	 * @return an ObjectId or {@code null} if revstr can't be resolved to any
434 	 *         ObjectId
435 	 * @throws AmbiguousObjectException
436 	 *             {@code revstr} contains an abbreviated ObjectId and this
437 	 *             repository contains more than one object which match to the
438 	 *             input abbreviation.
439 	 * @throws IncorrectObjectTypeException
440 	 *             the id parsed does not meet the type required to finish
441 	 *             applying the operators in the expression.
442 	 * @throws RevisionSyntaxException
443 	 *             the expression is not supported by this implementation, or
444 	 *             does not meet the standard syntax.
445 	 * @throws IOException
446 	 *             on serious errors
447 	 */
448 	@Nullable
449 	public ObjectId resolve(final String revstr)
450 			throws AmbiguousObjectException, IncorrectObjectTypeException,
451 			RevisionSyntaxException, IOException {
452 		try (RevWalk rw = new RevWalk(this)) {
453 			Object resolved = resolve(rw, revstr);
454 			if (resolved instanceof String) {
455 				final Ref ref = getRef((String)resolved);
456 				return ref != null ? ref.getLeaf().getObjectId() : null;
457 			} else {
458 				return (ObjectId) resolved;
459 			}
460 		}
461 	}
462 
463 	/**
464 	 * Simplify an expression, but unlike {@link #resolve(String)} it will not
465 	 * resolve a branch passed or resulting from the expression, such as @{-}.
466 	 * Thus this method can be used to process an expression to a method that
467 	 * expects a branch or revision id.
468 	 *
469 	 * @param revstr
470 	 * @return object id or ref name from resolved expression or {@code null} if
471 	 *         given expression cannot be resolved
472 	 * @throws AmbiguousObjectException
473 	 * @throws IOException
474 	 */
475 	@Nullable
476 	public String simplify(final String revstr)
477 			throws AmbiguousObjectException, IOException {
478 		try (RevWalk rw = new RevWalk(this)) {
479 			Object resolved = resolve(rw, revstr);
480 			if (resolved != null)
481 				if (resolved instanceof String)
482 					return (String) resolved;
483 				else
484 					return ((AnyObjectId) resolved).getName();
485 			return null;
486 		}
487 	}
488 
489 	@Nullable
490 	private Object resolve(final RevWalk rw, final String revstr)
491 			throws IOException {
492 		char[] revChars = revstr.toCharArray();
493 		RevObject rev = null;
494 		String name = null;
495 		int done = 0;
496 		for (int i = 0; i < revChars.length; ++i) {
497 			switch (revChars[i]) {
498 			case '^':
499 				if (rev == null) {
500 					if (name == null)
501 						if (done == 0)
502 							name = new String(revChars, done, i);
503 						else {
504 							done = i + 1;
505 							break;
506 						}
507 					rev = parseSimple(rw, name);
508 					name = null;
509 					if (rev == null)
510 						return null;
511 				}
512 				if (i + 1 < revChars.length) {
513 					switch (revChars[i + 1]) {
514 					case '0':
515 					case '1':
516 					case '2':
517 					case '3':
518 					case '4':
519 					case '5':
520 					case '6':
521 					case '7':
522 					case '8':
523 					case '9':
524 						int j;
525 						rev = rw.parseCommit(rev);
526 						for (j = i + 1; j < revChars.length; ++j) {
527 							if (!Character.isDigit(revChars[j]))
528 								break;
529 						}
530 						String parentnum = new String(revChars, i + 1, j - i
531 								- 1);
532 						int pnum;
533 						try {
534 							pnum = Integer.parseInt(parentnum);
535 						} catch (NumberFormatException e) {
536 							throw new RevisionSyntaxException(
537 									JGitText.get().invalidCommitParentNumber,
538 									revstr);
539 						}
540 						if (pnum != 0) {
541 							RevCommit commit = (RevCommit) rev;
542 							if (pnum > commit.getParentCount())
543 								rev = null;
544 							else
545 								rev = commit.getParent(pnum - 1);
546 						}
547 						i = j - 1;
548 						done = j;
549 						break;
550 					case '{':
551 						int k;
552 						String item = null;
553 						for (k = i + 2; k < revChars.length; ++k) {
554 							if (revChars[k] == '}') {
555 								item = new String(revChars, i + 2, k - i - 2);
556 								break;
557 							}
558 						}
559 						i = k;
560 						if (item != null)
561 							if (item.equals("tree")) { //$NON-NLS-1$
562 								rev = rw.parseTree(rev);
563 							} else if (item.equals("commit")) { //$NON-NLS-1$
564 								rev = rw.parseCommit(rev);
565 							} else if (item.equals("blob")) { //$NON-NLS-1$
566 								rev = rw.peel(rev);
567 								if (!(rev instanceof RevBlob))
568 									throw new IncorrectObjectTypeException(rev,
569 											Constants.TYPE_BLOB);
570 							} else if (item.equals("")) { //$NON-NLS-1$
571 								rev = rw.peel(rev);
572 							} else
573 								throw new RevisionSyntaxException(revstr);
574 						else
575 							throw new RevisionSyntaxException(revstr);
576 						done = k;
577 						break;
578 					default:
579 						rev = rw.peel(rev);
580 						if (rev instanceof RevCommit) {
581 							RevCommit commit = ((RevCommit) rev);
582 							if (commit.getParentCount() == 0)
583 								rev = null;
584 							else
585 								rev = commit.getParent(0);
586 						} else
587 							throw new IncorrectObjectTypeException(rev,
588 									Constants.TYPE_COMMIT);
589 					}
590 				} else {
591 					rev = rw.peel(rev);
592 					if (rev instanceof RevCommit) {
593 						RevCommit commit = ((RevCommit) rev);
594 						if (commit.getParentCount() == 0)
595 							rev = null;
596 						else
597 							rev = commit.getParent(0);
598 					} else
599 						throw new IncorrectObjectTypeException(rev,
600 								Constants.TYPE_COMMIT);
601 				}
602 				done = i + 1;
603 				break;
604 			case '~':
605 				if (rev == null) {
606 					if (name == null)
607 						if (done == 0)
608 							name = new String(revChars, done, i);
609 						else {
610 							done = i + 1;
611 							break;
612 						}
613 					rev = parseSimple(rw, name);
614 					name = null;
615 					if (rev == null)
616 						return null;
617 				}
618 				rev = rw.peel(rev);
619 				if (!(rev instanceof RevCommit))
620 					throw new IncorrectObjectTypeException(rev,
621 							Constants.TYPE_COMMIT);
622 				int l;
623 				for (l = i + 1; l < revChars.length; ++l) {
624 					if (!Character.isDigit(revChars[l]))
625 						break;
626 				}
627 				int dist;
628 				if (l - i > 1) {
629 					String distnum = new String(revChars, i + 1, l - i - 1);
630 					try {
631 						dist = Integer.parseInt(distnum);
632 					} catch (NumberFormatException e) {
633 						throw new RevisionSyntaxException(
634 								JGitText.get().invalidAncestryLength, revstr);
635 					}
636 				} else
637 					dist = 1;
638 				while (dist > 0) {
639 					RevCommit commit = (RevCommit) rev;
640 					if (commit.getParentCount() == 0) {
641 						rev = null;
642 						break;
643 					}
644 					commit = commit.getParent(0);
645 					rw.parseHeaders(commit);
646 					rev = commit;
647 					--dist;
648 				}
649 				i = l - 1;
650 				done = l;
651 				break;
652 			case '@':
653 				if (rev != null)
654 					throw new RevisionSyntaxException(revstr);
655 				if (i + 1 < revChars.length && revChars[i + 1] != '{')
656 					continue;
657 				int m;
658 				String time = null;
659 				for (m = i + 2; m < revChars.length; ++m) {
660 					if (revChars[m] == '}') {
661 						time = new String(revChars, i + 2, m - i - 2);
662 						break;
663 					}
664 				}
665 				if (time != null) {
666 					if (time.equals("upstream")) { //$NON-NLS-1$
667 						if (name == null)
668 							name = new String(revChars, done, i);
669 						if (name.equals("")) //$NON-NLS-1$
670 							// Currently checked out branch, HEAD if
671 							// detached
672 							name = Constants.HEAD;
673 						if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
674 							throw new RevisionSyntaxException(MessageFormat
675 									.format(JGitText.get().invalidRefName,
676 											name),
677 									revstr);
678 						Ref ref = getRef(name);
679 						name = null;
680 						if (ref == null)
681 							return null;
682 						if (ref.isSymbolic())
683 							ref = ref.getLeaf();
684 						name = ref.getName();
685 
686 						RemoteConfig remoteConfig;
687 						try {
688 							remoteConfig = new RemoteConfig(getConfig(),
689 									"origin"); //$NON-NLS-1$
690 						} catch (URISyntaxException e) {
691 							throw new RevisionSyntaxException(revstr);
692 						}
693 						String remoteBranchName = getConfig()
694 								.getString(
695 										ConfigConstants.CONFIG_BRANCH_SECTION,
696 								Repository.shortenRefName(ref.getName()),
697 										ConfigConstants.CONFIG_KEY_MERGE);
698 						List<RefSpec> fetchRefSpecs = remoteConfig
699 								.getFetchRefSpecs();
700 						for (RefSpec refSpec : fetchRefSpecs) {
701 							if (refSpec.matchSource(remoteBranchName)) {
702 								RefSpec expandFromSource = refSpec
703 										.expandFromSource(remoteBranchName);
704 								name = expandFromSource.getDestination();
705 								break;
706 							}
707 						}
708 						if (name == null)
709 							throw new RevisionSyntaxException(revstr);
710 					} else if (time.matches("^-\\d+$")) { //$NON-NLS-1$
711 						if (name != null)
712 							throw new RevisionSyntaxException(revstr);
713 						else {
714 							String previousCheckout = resolveReflogCheckout(-Integer
715 									.parseInt(time));
716 							if (ObjectId.isId(previousCheckout))
717 								rev = parseSimple(rw, previousCheckout);
718 							else
719 								name = previousCheckout;
720 						}
721 					} else {
722 						if (name == null)
723 							name = new String(revChars, done, i);
724 						if (name.equals("")) //$NON-NLS-1$
725 							name = Constants.HEAD;
726 						if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
727 							throw new RevisionSyntaxException(MessageFormat
728 									.format(JGitText.get().invalidRefName,
729 											name),
730 									revstr);
731 						Ref ref = getRef(name);
732 						name = null;
733 						if (ref == null)
734 							return null;
735 						// @{n} means current branch, not HEAD@{1} unless
736 						// detached
737 						if (ref.isSymbolic())
738 							ref = ref.getLeaf();
739 						rev = resolveReflog(rw, ref, time);
740 					}
741 					i = m;
742 				} else
743 					throw new RevisionSyntaxException(revstr);
744 				break;
745 			case ':': {
746 				RevTree tree;
747 				if (rev == null) {
748 					if (name == null)
749 						name = new String(revChars, done, i);
750 					if (name.equals("")) //$NON-NLS-1$
751 						name = Constants.HEAD;
752 					rev = parseSimple(rw, name);
753 					name = null;
754 				}
755 				if (rev == null)
756 					return null;
757 				tree = rw.parseTree(rev);
758 				if (i == revChars.length - 1)
759 					return tree.copy();
760 
761 				TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(),
762 						new String(revChars, i + 1, revChars.length - i - 1),
763 						tree);
764 				return tw != null ? tw.getObjectId(0) : null;
765 			}
766 			default:
767 				if (rev != null)
768 					throw new RevisionSyntaxException(revstr);
769 			}
770 		}
771 		if (rev != null)
772 			return rev.copy();
773 		if (name != null)
774 			return name;
775 		if (done == revstr.length())
776 			return null;
777 		name = revstr.substring(done);
778 		if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
779 			throw new RevisionSyntaxException(
780 					MessageFormat.format(JGitText.get().invalidRefName, name),
781 					revstr);
782 		if (getRef(name) != null)
783 			return name;
784 		return resolveSimple(name);
785 	}
786 
787 	private static boolean isHex(char c) {
788 		return ('0' <= c && c <= '9') //
789 				|| ('a' <= c && c <= 'f') //
790 				|| ('A' <= c && c <= 'F');
791 	}
792 
793 	private static boolean isAllHex(String str, int ptr) {
794 		while (ptr < str.length()) {
795 			if (!isHex(str.charAt(ptr++)))
796 				return false;
797 		}
798 		return true;
799 	}
800 
801 	@Nullable
802 	private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
803 		ObjectId id = resolveSimple(revstr);
804 		return id != null ? rw.parseAny(id) : null;
805 	}
806 
807 	@Nullable
808 	private ObjectId resolveSimple(final String revstr) throws IOException {
809 		if (ObjectId.isId(revstr))
810 			return ObjectId.fromString(revstr);
811 
812 		if (Repository.isValidRefName("x/" + revstr)) { //$NON-NLS-1$
813 			Ref r = getRefDatabase().getRef(revstr);
814 			if (r != null)
815 				return r.getObjectId();
816 		}
817 
818 		if (AbbreviatedObjectId.isId(revstr))
819 			return resolveAbbreviation(revstr);
820 
821 		int dashg = revstr.indexOf("-g"); //$NON-NLS-1$
822 		if ((dashg + 5) < revstr.length() && 0 <= dashg
823 				&& isHex(revstr.charAt(dashg + 2))
824 				&& isHex(revstr.charAt(dashg + 3))
825 				&& isAllHex(revstr, dashg + 4)) {
826 			// Possibly output from git describe?
827 			String s = revstr.substring(dashg + 2);
828 			if (AbbreviatedObjectId.isId(s))
829 				return resolveAbbreviation(s);
830 		}
831 
832 		return null;
833 	}
834 
835 	@Nullable
836 	private String resolveReflogCheckout(int checkoutNo)
837 			throws IOException {
838 		ReflogReader reader = getReflogReader(Constants.HEAD);
839 		if (reader == null) {
840 			return null;
841 		}
842 		List<ReflogEntry> reflogEntries = reader.getReverseEntries();
843 		for (ReflogEntry entry : reflogEntries) {
844 			CheckoutEntry checkout = entry.parseCheckout();
845 			if (checkout != null)
846 				if (checkoutNo-- == 1)
847 					return checkout.getFromBranch();
848 		}
849 		return null;
850 	}
851 
852 	private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
853 			throws IOException {
854 		int number;
855 		try {
856 			number = Integer.parseInt(time);
857 		} catch (NumberFormatException nfe) {
858 			throw new RevisionSyntaxException(MessageFormat.format(
859 					JGitText.get().invalidReflogRevision, time));
860 		}
861 		assert number >= 0;
862 		ReflogReader reader = getReflogReader(ref.getName());
863 		if (reader == null) {
864 			throw new RevisionSyntaxException(
865 					MessageFormat.format(JGitText.get().reflogEntryNotFound,
866 							Integer.valueOf(number), ref.getName()));
867 		}
868 		ReflogEntry entry = reader.getReverseEntry(number);
869 		if (entry == null)
870 			throw new RevisionSyntaxException(MessageFormat.format(
871 					JGitText.get().reflogEntryNotFound,
872 					Integer.valueOf(number), ref.getName()));
873 
874 		return rw.parseCommit(entry.getNewId());
875 	}
876 
877 	@Nullable
878 	private ObjectId resolveAbbreviation(final String revstr) throws IOException,
879 			AmbiguousObjectException {
880 		AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
881 		try (ObjectReader reader = newObjectReader()) {
882 			Collection<ObjectId> matches = reader.resolve(id);
883 			if (matches.size() == 0)
884 				return null;
885 			else if (matches.size() == 1)
886 				return matches.iterator().next();
887 			else
888 				throw new AmbiguousObjectException(id, matches);
889 		}
890 	}
891 
892 	/** Increment the use counter by one, requiring a matched {@link #close()}. */
893 	public void incrementOpen() {
894 		useCnt.incrementAndGet();
895 	}
896 
897 	/** Decrement the use count, and maybe close resources. */
898 	@Override
899 	public void close() {
900 		int newCount = useCnt.decrementAndGet();
901 		if (newCount == 0) {
902 			if (RepositoryCache.isCached(this)) {
903 				closedAt.set(System.currentTimeMillis());
904 			} else {
905 				doClose();
906 			}
907 		} else if (newCount == -1) {
908 			// should not happen, only log when useCnt became negative to
909 			// minimize number of log entries
910 			String message = MessageFormat.format(JGitText.get().corruptUseCnt,
911 					toString());
912 			if (LOG.isDebugEnabled()) {
913 				LOG.debug(message, new IllegalStateException());
914 			} else {
915 				LOG.warn(message);
916 			}
917 			if (RepositoryCache.isCached(this)) {
918 				closedAt.set(System.currentTimeMillis());
919 			}
920 		}
921 	}
922 
923 	/**
924 	 * Invoked when the use count drops to zero during {@link #close()}.
925 	 * <p>
926 	 * The default implementation closes the object and ref databases.
927 	 */
928 	protected void doClose() {
929 		getObjectDatabase().close();
930 		getRefDatabase().close();
931 	}
932 
933 	@Override
934 	@NonNull
935 	public String toString() {
936 		String desc;
937 		File directory = getDirectory();
938 		if (directory != null)
939 			desc = directory.getPath();
940 		else
941 			desc = getClass().getSimpleName() + "-" //$NON-NLS-1$
942 					+ System.identityHashCode(this);
943 		return "Repository[" + desc + "]"; //$NON-NLS-1$ //$NON-NLS-2$
944 	}
945 
946 	/**
947 	 * Get the name of the reference that {@code HEAD} points to.
948 	 * <p>
949 	 * This is essentially the same as doing:
950 	 *
951 	 * <pre>
952 	 * return exactRef(Constants.HEAD).getTarget().getName()
953 	 * </pre>
954 	 *
955 	 * Except when HEAD is detached, in which case this method returns the
956 	 * current ObjectId in hexadecimal string format.
957 	 *
958 	 * @return name of current branch (for example {@code refs/heads/master}),
959 	 *         an ObjectId in hex format if the current branch is detached, or
960 	 *         {@code null} if the repository is corrupt and has no HEAD
961 	 *         reference.
962 	 * @throws IOException
963 	 */
964 	@Nullable
965 	public String getFullBranch() throws IOException {
966 		Ref head = exactRef(Constants.HEAD);
967 		if (head == null) {
968 			return null;
969 		}
970 		if (head.isSymbolic()) {
971 			return head.getTarget().getName();
972 		}
973 		ObjectId objectId = head.getObjectId();
974 		if (objectId != null) {
975 			return objectId.name();
976 		}
977 		return null;
978 	}
979 
980 	/**
981 	 * Get the short name of the current branch that {@code HEAD} points to.
982 	 * <p>
983 	 * This is essentially the same as {@link #getFullBranch()}, except the
984 	 * leading prefix {@code refs/heads/} is removed from the reference before
985 	 * it is returned to the caller.
986 	 *
987 	 * @return name of current branch (for example {@code master}), an ObjectId
988 	 *         in hex format if the current branch is detached, or {@code null}
989 	 *         if the repository is corrupt and has no HEAD reference.
990 	 * @throws IOException
991 	 */
992 	@Nullable
993 	public String getBranch() throws IOException {
994 		String name = getFullBranch();
995 		if (name != null)
996 			return shortenRefName(name);
997 		return null;
998 	}
999 
1000 	/**
1001 	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
1002 	 * <p>
1003 	 * When a repository borrows objects from another repository, it can
1004 	 * advertise that it safely has that other repository's references, without
1005 	 * exposing any other details about the other repository.  This may help
1006 	 * a client trying to push changes avoid pushing more than it needs to.
1007 	 *
1008 	 * @return unmodifiable collection of other known objects.
1009 	 */
1010 	@NonNull
1011 	public Set<ObjectId> getAdditionalHaves() {
1012 		return Collections.emptySet();
1013 	}
1014 
1015 	/**
1016 	 * Get a ref by name.
1017 	 *
1018 	 * @param name
1019 	 *            the name of the ref to lookup. May be a short-hand form, e.g.
1020 	 *            "master" which is is automatically expanded to
1021 	 *            "refs/heads/master" if "refs/heads/master" already exists.
1022 	 * @return the Ref with the given name, or {@code null} if it does not exist
1023 	 * @throws IOException
1024 	 * @deprecated Use {@link #exactRef(String)} or {@link #findRef(String)}
1025 	 * instead.
1026 	 */
1027 	@Deprecated
1028 	@Nullable
1029 	public Ref getRef(final String name) throws IOException {
1030 		return findRef(name);
1031 	}
1032 
1033 	/**
1034 	 * Get a ref by name.
1035 	 *
1036 	 * @param name
1037 	 *            the name of the ref to lookup. Must not be a short-hand
1038 	 *            form; e.g., "master" is not automatically expanded to
1039 	 *            "refs/heads/master".
1040 	 * @return the Ref with the given name, or {@code null} if it does not exist
1041 	 * @throws IOException
1042 	 * @since 4.2
1043 	 */
1044 	@Nullable
1045 	public Ref exactRef(String name) throws IOException {
1046 		return getRefDatabase().exactRef(name);
1047 	}
1048 
1049 	/**
1050 	 * Search for a ref by (possibly abbreviated) name.
1051 	 *
1052 	 * @param name
1053 	 *            the name of the ref to lookup. May be a short-hand form, e.g.
1054 	 *            "master" which is is automatically expanded to
1055 	 *            "refs/heads/master" if "refs/heads/master" already exists.
1056 	 * @return the Ref with the given name, or {@code null} if it does not exist
1057 	 * @throws IOException
1058 	 * @since 4.2
1059 	 */
1060 	@Nullable
1061 	public Ref findRef(String name) throws IOException {
1062 		return getRefDatabase().getRef(name);
1063 	}
1064 
1065 	/**
1066 	 * @return mutable map of all known refs (heads, tags, remotes).
1067 	 */
1068 	@NonNull
1069 	public Map<String, Ref> getAllRefs() {
1070 		try {
1071 			return getRefDatabase().getRefs(RefDatabase.ALL);
1072 		} catch (IOException e) {
1073 			return new HashMap<>();
1074 		}
1075 	}
1076 
1077 	/**
1078 	 * @return mutable map of all tags; key is short tag name ("v1.0") and value
1079 	 *         of the entry contains the ref with the full tag name
1080 	 *         ("refs/tags/v1.0").
1081 	 */
1082 	@NonNull
1083 	public Map<String, Ref> getTags() {
1084 		try {
1085 			return getRefDatabase().getRefs(Constants.R_TAGS);
1086 		} catch (IOException e) {
1087 			return new HashMap<>();
1088 		}
1089 	}
1090 
1091 	/**
1092 	 * Peel a possibly unpeeled reference to an annotated tag.
1093 	 * <p>
1094 	 * If the ref cannot be peeled (as it does not refer to an annotated tag)
1095 	 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
1096 	 *
1097 	 * @param ref
1098 	 *            The ref to peel
1099 	 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
1100 	 *         new Ref object representing the same data as Ref, but isPeeled()
1101 	 *         will be true and getPeeledObjectId will contain the peeled object
1102 	 *         (or null).
1103 	 */
1104 	@NonNull
1105 	public Ref peel(final Ref ref) {
1106 		try {
1107 			return getRefDatabase().peel(ref);
1108 		} catch (IOException e) {
1109 			// Historical accident; if the reference cannot be peeled due
1110 			// to some sort of repository access problem we claim that the
1111 			// same as if the reference was not an annotated tag.
1112 			return ref;
1113 		}
1114 	}
1115 
1116 	/**
1117 	 * @return a map with all objects referenced by a peeled ref.
1118 	 */
1119 	@NonNull
1120 	public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
1121 		Map<String, Ref> allRefs = getAllRefs();
1122 		Map<AnyObjectId, Set<Ref>> ret = new HashMap<>(allRefs.size());
1123 		for (Ref ref : allRefs.values()) {
1124 			ref = peel(ref);
1125 			AnyObjectId target = ref.getPeeledObjectId();
1126 			if (target == null)
1127 				target = ref.getObjectId();
1128 			// We assume most Sets here are singletons
1129 			Set<Ref> oset = ret.put(target, Collections.singleton(ref));
1130 			if (oset != null) {
1131 				// that was not the case (rare)
1132 				if (oset.size() == 1) {
1133 					// Was a read-only singleton, we must copy to a new Set
1134 					oset = new HashSet<>(oset);
1135 				}
1136 				ret.put(target, oset);
1137 				oset.add(ref);
1138 			}
1139 		}
1140 		return ret;
1141 	}
1142 
1143 	/**
1144 	 * @return the index file location or {@code null} if repository isn't
1145 	 *         local.
1146 	 * @throws NoWorkTreeException
1147 	 *             if this is bare, which implies it has no working directory.
1148 	 *             See {@link #isBare()}.
1149 	 */
1150 	@NonNull
1151 	public File getIndexFile() throws NoWorkTreeException {
1152 		if (isBare())
1153 			throw new NoWorkTreeException();
1154 		return indexFile;
1155 	}
1156 
1157 	/**
1158 	 * Locate a reference to a commit and immediately parse its content.
1159 	 * <p>
1160 	 * This method only returns successfully if the commit object exists,
1161 	 * is verified to be a commit, and was parsed without error.
1162 	 *
1163 	 * @param id
1164 	 *            name of the commit object.
1165 	 * @return reference to the commit object. Never null.
1166 	 * @throws MissingObjectException
1167 	 *             the supplied commit does not exist.
1168 	 * @throws IncorrectObjectTypeException
1169 	 *             the supplied id is not a commit or an annotated tag.
1170 	 * @throws IOException
1171 	 *             a pack file or loose object could not be read.
1172 	 * @since 4.8
1173 	 */
1174 	public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException,
1175 			IOException, MissingObjectException {
1176 		if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) {
1177 			return (RevCommit) id;
1178 		}
1179 		try (RevWalk walk = new RevWalk(this)) {
1180 			return walk.parseCommit(id);
1181 		}
1182 	}
1183 
1184 	/**
1185 	 * Create a new in-core index representation and read an index from disk.
1186 	 * <p>
1187 	 * The new index will be read before it is returned to the caller. Read
1188 	 * failures are reported as exceptions and therefore prevent the method from
1189 	 * returning a partially populated index.
1190 	 *
1191 	 * @return a cache representing the contents of the specified index file (if
1192 	 *         it exists) or an empty cache if the file does not exist.
1193 	 * @throws NoWorkTreeException
1194 	 *             if this is bare, which implies it has no working directory.
1195 	 *             See {@link #isBare()}.
1196 	 * @throws IOException
1197 	 *             the index file is present but could not be read.
1198 	 * @throws CorruptObjectException
1199 	 *             the index file is using a format or extension that this
1200 	 *             library does not support.
1201 	 */
1202 	@NonNull
1203 	public DirCache readDirCache() throws NoWorkTreeException,
1204 			CorruptObjectException, IOException {
1205 		return DirCache.read(this);
1206 	}
1207 
1208 	/**
1209 	 * Create a new in-core index representation, lock it, and read from disk.
1210 	 * <p>
1211 	 * The new index will be locked and then read before it is returned to the
1212 	 * caller. Read failures are reported as exceptions and therefore prevent
1213 	 * the method from returning a partially populated index.
1214 	 *
1215 	 * @return a cache representing the contents of the specified index file (if
1216 	 *         it exists) or an empty cache if the file does not exist.
1217 	 * @throws NoWorkTreeException
1218 	 *             if this is bare, which implies it has no working directory.
1219 	 *             See {@link #isBare()}.
1220 	 * @throws IOException
1221 	 *             the index file is present but could not be read, or the lock
1222 	 *             could not be obtained.
1223 	 * @throws CorruptObjectException
1224 	 *             the index file is using a format or extension that this
1225 	 *             library does not support.
1226 	 */
1227 	@NonNull
1228 	public DirCache lockDirCache() throws NoWorkTreeException,
1229 			CorruptObjectException, IOException {
1230 		// we want DirCache to inform us so that we can inform registered
1231 		// listeners about index changes
1232 		IndexChangedListener l = new IndexChangedListener() {
1233 			@Override
1234 			public void onIndexChanged(IndexChangedEvent event) {
1235 				notifyIndexChanged();
1236 			}
1237 		};
1238 		return DirCache.lock(this, l);
1239 	}
1240 
1241 	/**
1242 	 * @return an important state
1243 	 */
1244 	@NonNull
1245 	public RepositoryState getRepositoryState() {
1246 		if (isBare() || getDirectory() == null)
1247 			return RepositoryState.BARE;
1248 
1249 		// Pre Git-1.6 logic
1250 		if (new File(getWorkTree(), ".dotest").exists()) //$NON-NLS-1$
1251 			return RepositoryState.REBASING;
1252 		if (new File(getDirectory(), ".dotest-merge").exists()) //$NON-NLS-1$
1253 			return RepositoryState.REBASING_INTERACTIVE;
1254 
1255 		// From 1.6 onwards
1256 		if (new File(getDirectory(),"rebase-apply/rebasing").exists()) //$NON-NLS-1$
1257 			return RepositoryState.REBASING_REBASING;
1258 		if (new File(getDirectory(),"rebase-apply/applying").exists()) //$NON-NLS-1$
1259 			return RepositoryState.APPLY;
1260 		if (new File(getDirectory(),"rebase-apply").exists()) //$NON-NLS-1$
1261 			return RepositoryState.REBASING;
1262 
1263 		if (new File(getDirectory(),"rebase-merge/interactive").exists()) //$NON-NLS-1$
1264 			return RepositoryState.REBASING_INTERACTIVE;
1265 		if (new File(getDirectory(),"rebase-merge").exists()) //$NON-NLS-1$
1266 			return RepositoryState.REBASING_MERGE;
1267 
1268 		// Both versions
1269 		if (new File(getDirectory(), Constants.MERGE_HEAD).exists()) {
1270 			// we are merging - now check whether we have unmerged paths
1271 			try {
1272 				if (!readDirCache().hasUnmergedPaths()) {
1273 					// no unmerged paths -> return the MERGING_RESOLVED state
1274 					return RepositoryState.MERGING_RESOLVED;
1275 				}
1276 			} catch (IOException e) {
1277 				// Can't decide whether unmerged paths exists. Return
1278 				// MERGING state to be on the safe side (in state MERGING
1279 				// you are not allow to do anything)
1280 			}
1281 			return RepositoryState.MERGING;
1282 		}
1283 
1284 		if (new File(getDirectory(), "BISECT_LOG").exists()) //$NON-NLS-1$
1285 			return RepositoryState.BISECTING;
1286 
1287 		if (new File(getDirectory(), Constants.CHERRY_PICK_HEAD).exists()) {
1288 			try {
1289 				if (!readDirCache().hasUnmergedPaths()) {
1290 					// no unmerged paths
1291 					return RepositoryState.CHERRY_PICKING_RESOLVED;
1292 				}
1293 			} catch (IOException e) {
1294 				// fall through to CHERRY_PICKING
1295 			}
1296 
1297 			return RepositoryState.CHERRY_PICKING;
1298 		}
1299 
1300 		if (new File(getDirectory(), Constants.REVERT_HEAD).exists()) {
1301 			try {
1302 				if (!readDirCache().hasUnmergedPaths()) {
1303 					// no unmerged paths
1304 					return RepositoryState.REVERTING_RESOLVED;
1305 				}
1306 			} catch (IOException e) {
1307 				// fall through to REVERTING
1308 			}
1309 
1310 			return RepositoryState.REVERTING;
1311 		}
1312 
1313 		return RepositoryState.SAFE;
1314 	}
1315 
1316 	/**
1317 	 * Check validity of a ref name. It must not contain character that has
1318 	 * a special meaning in a Git object reference expression. Some other
1319 	 * dangerous characters are also excluded.
1320 	 *
1321 	 * For portability reasons '\' is excluded
1322 	 *
1323 	 * @param refName
1324 	 *
1325 	 * @return true if refName is a valid ref name
1326 	 */
1327 	public static boolean isValidRefName(final String refName) {
1328 		final int len = refName.length();
1329 		if (len == 0)
1330 			return false;
1331 		if (refName.endsWith(".lock")) //$NON-NLS-1$
1332 			return false;
1333 
1334 		// Refs may be stored as loose files so invalid paths
1335 		// on the local system must also be invalid refs.
1336 		try {
1337 			SystemReader.getInstance().checkPath(refName);
1338 		} catch (CorruptObjectException e) {
1339 			return false;
1340 		}
1341 
1342 		int components = 1;
1343 		char p = '\0';
1344 		for (int i = 0; i < len; i++) {
1345 			final char c = refName.charAt(i);
1346 			if (c <= ' ')
1347 				return false;
1348 			switch (c) {
1349 			case '.':
1350 				switch (p) {
1351 				case '\0': case '/': case '.':
1352 					return false;
1353 				}
1354 				if (i == len -1)
1355 					return false;
1356 				break;
1357 			case '/':
1358 				if (i == 0 || i == len - 1)
1359 					return false;
1360 				if (p == '/')
1361 					return false;
1362 				components++;
1363 				break;
1364 			case '{':
1365 				if (p == '@')
1366 					return false;
1367 				break;
1368 			case '~': case '^': case ':':
1369 			case '?': case '[': case '*':
1370 			case '\\':
1371 			case '\u007F':
1372 				return false;
1373 			}
1374 			p = c;
1375 		}
1376 		return components > 1;
1377 	}
1378 
1379 	/**
1380 	 * Normalizes the passed branch name into a possible valid branch name. The
1381 	 * validity of the returned name should be checked by a subsequent call to
1382 	 * {@link #isValidRefName(String)}.
1383 	 * <p/>
1384 	 * Future implementations of this method could be more restrictive or more
1385 	 * lenient about the validity of specific characters in the returned name.
1386 	 * <p/>
1387 	 * The current implementation returns the trimmed input string if this is
1388 	 * already a valid branch name. Otherwise it returns a trimmed string with
1389 	 * special characters not allowed by {@link #isValidRefName(String)}
1390 	 * replaced by hyphens ('-') and blanks replaced by underscores ('_').
1391 	 * Leading and trailing slashes, dots, hyphens, and underscores are removed.
1392 	 *
1393 	 * @param name
1394 	 *            to normalize
1395 	 *
1396 	 * @return The normalized name or an empty String if it is {@code null} or
1397 	 *         empty.
1398 	 * @since 4.7
1399 	 * @see #isValidRefName(String)
1400 	 */
1401 	public static String normalizeBranchName(String name) {
1402 		if (name == null || name.isEmpty()) {
1403 			return ""; //$NON-NLS-1$
1404 		}
1405 		String result = name.trim();
1406 		String fullName = result.startsWith(Constants.R_HEADS) ? result
1407 				: Constants.R_HEADS + result;
1408 		if (isValidRefName(fullName)) {
1409 			return result;
1410 		}
1411 
1412 		// All Unicode blanks to underscore
1413 		result = result.replaceAll("(?:\\h|\\v)+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
1414 		StringBuilder b = new StringBuilder();
1415 		char p = '/';
1416 		for (int i = 0, len = result.length(); i < len; i++) {
1417 			char c = result.charAt(i);
1418 			if (c < ' ' || c == 127) {
1419 				continue;
1420 			}
1421 			// Substitute a dash for problematic characters
1422 			switch (c) {
1423 			case '\\':
1424 			case '^':
1425 			case '~':
1426 			case ':':
1427 			case '?':
1428 			case '*':
1429 			case '[':
1430 			case '@':
1431 			case '<':
1432 			case '>':
1433 			case '|':
1434 			case '"':
1435 				c = '-';
1436 				break;
1437 			default:
1438 				break;
1439 			}
1440 			// Collapse multiple slashes, dashes, dots, underscores, and omit
1441 			// dashes, dots, and underscores following a slash.
1442 			switch (c) {
1443 			case '/':
1444 				if (p == '/') {
1445 					continue;
1446 				}
1447 				p = '/';
1448 				break;
1449 			case '.':
1450 			case '_':
1451 			case '-':
1452 				if (p == '/' || p == '-') {
1453 					continue;
1454 				}
1455 				p = '-';
1456 				break;
1457 			default:
1458 				p = c;
1459 				break;
1460 			}
1461 			b.append(c);
1462 		}
1463 		// Strip trailing special characters, and avoid the .lock extension
1464 		result = b.toString().replaceFirst("[/_.-]+$", "") //$NON-NLS-1$ //$NON-NLS-2$
1465 				.replaceAll("\\.lock($|/)", "_lock$1"); //$NON-NLS-1$ //$NON-NLS-2$
1466 		return FORBIDDEN_BRANCH_NAME_COMPONENTS.matcher(result)
1467 				.replaceAll("$1+$2$3"); //$NON-NLS-1$
1468 	}
1469 
1470 	/**
1471 	 * Strip work dir and return normalized repository path.
1472 	 *
1473 	 * @param workDir
1474 	 *            Work dir
1475 	 * @param file
1476 	 *            File whose path shall be stripped of its workdir
1477 	 * @return normalized repository relative path or the empty string if the
1478 	 *         file is not relative to the work directory.
1479 	 */
1480 	@NonNull
1481 	public static String stripWorkDir(File workDir, File file) {
1482 		final String filePath = file.getPath();
1483 		final String workDirPath = workDir.getPath();
1484 
1485 		if (filePath.length() <= workDirPath.length() ||
1486 		    filePath.charAt(workDirPath.length()) != File.separatorChar ||
1487 		    !filePath.startsWith(workDirPath)) {
1488 			File absWd = workDir.isAbsolute() ? workDir : workDir.getAbsoluteFile();
1489 			File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
1490 			if (absWd == workDir && absFile == file)
1491 				return ""; //$NON-NLS-1$
1492 			return stripWorkDir(absWd, absFile);
1493 		}
1494 
1495 		String relName = filePath.substring(workDirPath.length() + 1);
1496 		if (File.separatorChar != '/')
1497 			relName = relName.replace(File.separatorChar, '/');
1498 		return relName;
1499 	}
1500 
1501 	/**
1502 	 * @return true if this is bare, which implies it has no working directory.
1503 	 */
1504 	public boolean isBare() {
1505 		return workTree == null;
1506 	}
1507 
1508 	/**
1509 	 * @return the root directory of the working tree, where files are checked
1510 	 *         out for viewing and editing.
1511 	 * @throws NoWorkTreeException
1512 	 *             if this is bare, which implies it has no working directory.
1513 	 *             See {@link #isBare()}.
1514 	 */
1515 	@NonNull
1516 	public File getWorkTree() throws NoWorkTreeException {
1517 		if (isBare())
1518 			throw new NoWorkTreeException();
1519 		return workTree;
1520 	}
1521 
1522 	/**
1523 	 * Force a scan for changed refs.
1524 	 *
1525 	 * @throws IOException
1526 	 */
1527 	public abstract void scanForRepoChanges() throws IOException;
1528 
1529 	/**
1530 	 * Notify that the index changed
1531 	 */
1532 	public abstract void notifyIndexChanged();
1533 
1534 	/**
1535 	 * @param refName
1536 	 *
1537 	 * @return a more user friendly ref name
1538 	 */
1539 	@NonNull
1540 	public static String shortenRefName(String refName) {
1541 		if (refName.startsWith(Constants.R_HEADS))
1542 			return refName.substring(Constants.R_HEADS.length());
1543 		if (refName.startsWith(Constants.R_TAGS))
1544 			return refName.substring(Constants.R_TAGS.length());
1545 		if (refName.startsWith(Constants.R_REMOTES))
1546 			return refName.substring(Constants.R_REMOTES.length());
1547 		return refName;
1548 	}
1549 
1550 	/**
1551 	 * @param refName
1552 	 * @return the remote branch name part of <code>refName</code>, i.e. without
1553 	 *         the <code>refs/remotes/&lt;remote&gt;</code> prefix, if
1554 	 *         <code>refName</code> represents a remote tracking branch;
1555 	 *         otherwise {@code null}.
1556 	 * @since 3.4
1557 	 */
1558 	@Nullable
1559 	public String shortenRemoteBranchName(String refName) {
1560 		for (String remote : getRemoteNames()) {
1561 			String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
1562 			if (refName.startsWith(remotePrefix))
1563 				return refName.substring(remotePrefix.length());
1564 		}
1565 		return null;
1566 	}
1567 
1568 	/**
1569 	 * @param refName
1570 	 * @return the remote name part of <code>refName</code>, i.e. without the
1571 	 *         <code>refs/remotes/&lt;remote&gt;</code> prefix, if
1572 	 *         <code>refName</code> represents a remote tracking branch;
1573 	 *         otherwise {@code null}.
1574 	 * @since 3.4
1575 	 */
1576 	@Nullable
1577 	public String getRemoteName(String refName) {
1578 		for (String remote : getRemoteNames()) {
1579 			String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
1580 			if (refName.startsWith(remotePrefix))
1581 				return remote;
1582 		}
1583 		return null;
1584 	}
1585 
1586 	/**
1587 	 * Read the {@code GIT_DIR/description} file for gitweb.
1588 	 *
1589 	 * @return description text; null if no description has been configured.
1590 	 * @throws IOException
1591 	 *             description cannot be accessed.
1592 	 * @since 4.6
1593 	 */
1594 	@Nullable
1595 	public String getGitwebDescription() throws IOException {
1596 		return null;
1597 	}
1598 
1599 	/**
1600 	 * Set the {@code GIT_DIR/description} file for gitweb.
1601 	 *
1602 	 * @param description
1603 	 *            new description; null to clear the description.
1604 	 * @throws IOException
1605 	 *             description cannot be persisted.
1606 	 * @since 4.6
1607 	 */
1608 	public void setGitwebDescription(@Nullable String description)
1609 			throws IOException {
1610 		throw new IOException(JGitText.get().unsupportedRepositoryDescription);
1611 	}
1612 
1613 	/**
1614 	 * @param refName
1615 	 * @return a {@link ReflogReader} for the supplied refname, or {@code null}
1616 	 *         if the named ref does not exist.
1617 	 * @throws IOException
1618 	 *             the ref could not be accessed.
1619 	 * @since 3.0
1620 	 */
1621 	@Nullable
1622 	public abstract ReflogReader getReflogReader(String refName)
1623 			throws IOException;
1624 
1625 	/**
1626 	 * Return the information stored in the file $GIT_DIR/MERGE_MSG. In this
1627 	 * file operations triggering a merge will store a template for the commit
1628 	 * message of the merge commit.
1629 	 *
1630 	 * @return a String containing the content of the MERGE_MSG file or
1631 	 *         {@code null} if this file doesn't exist
1632 	 * @throws IOException
1633 	 * @throws NoWorkTreeException
1634 	 *             if this is bare, which implies it has no working directory.
1635 	 *             See {@link #isBare()}.
1636 	 */
1637 	@Nullable
1638 	public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
1639 		return readCommitMsgFile(Constants.MERGE_MSG);
1640 	}
1641 
1642 	/**
1643 	 * Write new content to the file $GIT_DIR/MERGE_MSG. In this file operations
1644 	 * triggering a merge will store a template for the commit message of the
1645 	 * merge commit. If <code>null</code> is specified as message the file will
1646 	 * be deleted.
1647 	 *
1648 	 * @param msg
1649 	 *            the message which should be written or <code>null</code> to
1650 	 *            delete the file
1651 	 *
1652 	 * @throws IOException
1653 	 */
1654 	public void writeMergeCommitMsg(String msg) throws IOException {
1655 		File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
1656 		writeCommitMsg(mergeMsgFile, msg);
1657 	}
1658 
1659 	/**
1660 	 * Return the information stored in the file $GIT_DIR/COMMIT_EDITMSG. In
1661 	 * this file hooks triggered by an operation may read or modify the current
1662 	 * commit message.
1663 	 *
1664 	 * @return a String containing the content of the COMMIT_EDITMSG file or
1665 	 *         {@code null} if this file doesn't exist
1666 	 * @throws IOException
1667 	 * @throws NoWorkTreeException
1668 	 *             if this is bare, which implies it has no working directory.
1669 	 *             See {@link #isBare()}.
1670 	 * @since 4.0
1671 	 */
1672 	@Nullable
1673 	public String readCommitEditMsg() throws IOException, NoWorkTreeException {
1674 		return readCommitMsgFile(Constants.COMMIT_EDITMSG);
1675 	}
1676 
1677 	/**
1678 	 * Write new content to the file $GIT_DIR/COMMIT_EDITMSG. In this file hooks
1679 	 * triggered by an operation may read or modify the current commit message.
1680 	 * If {@code null} is specified as message the file will be deleted.
1681 	 *
1682 	 * @param msg
1683 	 *            the message which should be written or {@code null} to delete
1684 	 *            the file
1685 	 *
1686 	 * @throws IOException
1687 	 * @since 4.0
1688 	 */
1689 	public void writeCommitEditMsg(String msg) throws IOException {
1690 		File commiEditMsgFile = new File(gitDir, Constants.COMMIT_EDITMSG);
1691 		writeCommitMsg(commiEditMsgFile, msg);
1692 	}
1693 
1694 	/**
1695 	 * Return the information stored in the file $GIT_DIR/MERGE_HEAD. In this
1696 	 * file operations triggering a merge will store the IDs of all heads which
1697 	 * should be merged together with HEAD.
1698 	 *
1699 	 * @return a list of commits which IDs are listed in the MERGE_HEAD file or
1700 	 *         {@code null} if this file doesn't exist. Also if the file exists
1701 	 *         but is empty {@code null} will be returned
1702 	 * @throws IOException
1703 	 * @throws NoWorkTreeException
1704 	 *             if this is bare, which implies it has no working directory.
1705 	 *             See {@link #isBare()}.
1706 	 */
1707 	@Nullable
1708 	public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
1709 		if (isBare() || getDirectory() == null)
1710 			throw new NoWorkTreeException();
1711 
1712 		byte[] raw = readGitDirectoryFile(Constants.MERGE_HEAD);
1713 		if (raw == null)
1714 			return null;
1715 
1716 		LinkedList<ObjectId> heads = new LinkedList<>();
1717 		for (int p = 0; p < raw.length;) {
1718 			heads.add(ObjectId.fromString(raw, p));
1719 			p = RawParseUtils
1720 					.nextLF(raw, p + Constants.OBJECT_ID_STRING_LENGTH);
1721 		}
1722 		return heads;
1723 	}
1724 
1725 	/**
1726 	 * Write new merge-heads into $GIT_DIR/MERGE_HEAD. In this file operations
1727 	 * triggering a merge will store the IDs of all heads which should be merged
1728 	 * together with HEAD. If <code>null</code> is specified as list of commits
1729 	 * the file will be deleted
1730 	 *
1731 	 * @param heads
1732 	 *            a list of commits which IDs should be written to
1733 	 *            $GIT_DIR/MERGE_HEAD or <code>null</code> to delete the file
1734 	 * @throws IOException
1735 	 */
1736 	public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
1737 		writeHeadsFile(heads, Constants.MERGE_HEAD);
1738 	}
1739 
1740 	/**
1741 	 * Return the information stored in the file $GIT_DIR/CHERRY_PICK_HEAD.
1742 	 *
1743 	 * @return object id from CHERRY_PICK_HEAD file or {@code null} if this file
1744 	 *         doesn't exist. Also if the file exists but is empty {@code null}
1745 	 *         will be returned
1746 	 * @throws IOException
1747 	 * @throws NoWorkTreeException
1748 	 *             if this is bare, which implies it has no working directory.
1749 	 *             See {@link #isBare()}.
1750 	 */
1751 	@Nullable
1752 	public ObjectId readCherryPickHead() throws IOException,
1753 			NoWorkTreeException {
1754 		if (isBare() || getDirectory() == null)
1755 			throw new NoWorkTreeException();
1756 
1757 		byte[] raw = readGitDirectoryFile(Constants.CHERRY_PICK_HEAD);
1758 		if (raw == null)
1759 			return null;
1760 
1761 		return ObjectId.fromString(raw, 0);
1762 	}
1763 
1764 	/**
1765 	 * Return the information stored in the file $GIT_DIR/REVERT_HEAD.
1766 	 *
1767 	 * @return object id from REVERT_HEAD file or {@code null} if this file
1768 	 *         doesn't exist. Also if the file exists but is empty {@code null}
1769 	 *         will be returned
1770 	 * @throws IOException
1771 	 * @throws NoWorkTreeException
1772 	 *             if this is bare, which implies it has no working directory.
1773 	 *             See {@link #isBare()}.
1774 	 */
1775 	@Nullable
1776 	public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
1777 		if (isBare() || getDirectory() == null)
1778 			throw new NoWorkTreeException();
1779 
1780 		byte[] raw = readGitDirectoryFile(Constants.REVERT_HEAD);
1781 		if (raw == null)
1782 			return null;
1783 		return ObjectId.fromString(raw, 0);
1784 	}
1785 
1786 	/**
1787 	 * Write cherry pick commit into $GIT_DIR/CHERRY_PICK_HEAD. This is used in
1788 	 * case of conflicts to store the cherry which was tried to be picked.
1789 	 *
1790 	 * @param head
1791 	 *            an object id of the cherry commit or <code>null</code> to
1792 	 *            delete the file
1793 	 * @throws IOException
1794 	 */
1795 	public void writeCherryPickHead(ObjectId head) throws IOException {
1796 		List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1797 				: null;
1798 		writeHeadsFile(heads, Constants.CHERRY_PICK_HEAD);
1799 	}
1800 
1801 	/**
1802 	 * Write revert commit into $GIT_DIR/REVERT_HEAD. This is used in case of
1803 	 * conflicts to store the revert which was tried to be picked.
1804 	 *
1805 	 * @param head
1806 	 *            an object id of the revert commit or <code>null</code> to
1807 	 *            delete the file
1808 	 * @throws IOException
1809 	 */
1810 	public void writeRevertHead(ObjectId head) throws IOException {
1811 		List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1812 				: null;
1813 		writeHeadsFile(heads, Constants.REVERT_HEAD);
1814 	}
1815 
1816 	/**
1817 	 * Write original HEAD commit into $GIT_DIR/ORIG_HEAD.
1818 	 *
1819 	 * @param head
1820 	 *            an object id of the original HEAD commit or <code>null</code>
1821 	 *            to delete the file
1822 	 * @throws IOException
1823 	 */
1824 	public void writeOrigHead(ObjectId head) throws IOException {
1825 		List<ObjectId> heads = head != null ? Collections.singletonList(head)
1826 				: null;
1827 		writeHeadsFile(heads, Constants.ORIG_HEAD);
1828 	}
1829 
1830 	/**
1831 	 * Return the information stored in the file $GIT_DIR/ORIG_HEAD.
1832 	 *
1833 	 * @return object id from ORIG_HEAD file or {@code null} if this file
1834 	 *         doesn't exist. Also if the file exists but is empty {@code null}
1835 	 *         will be returned
1836 	 * @throws IOException
1837 	 * @throws NoWorkTreeException
1838 	 *             if this is bare, which implies it has no working directory.
1839 	 *             See {@link #isBare()}.
1840 	 */
1841 	@Nullable
1842 	public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
1843 		if (isBare() || getDirectory() == null)
1844 			throw new NoWorkTreeException();
1845 
1846 		byte[] raw = readGitDirectoryFile(Constants.ORIG_HEAD);
1847 		return raw != null ? ObjectId.fromString(raw, 0) : null;
1848 	}
1849 
1850 	/**
1851 	 * Return the information stored in the file $GIT_DIR/SQUASH_MSG. In this
1852 	 * file operations triggering a squashed merge will store a template for the
1853 	 * commit message of the squash commit.
1854 	 *
1855 	 * @return a String containing the content of the SQUASH_MSG file or
1856 	 *         {@code null} if this file doesn't exist
1857 	 * @throws IOException
1858 	 * @throws NoWorkTreeException
1859 	 *             if this is bare, which implies it has no working directory.
1860 	 *             See {@link #isBare()}.
1861 	 */
1862 	@Nullable
1863 	public String readSquashCommitMsg() throws IOException {
1864 		return readCommitMsgFile(Constants.SQUASH_MSG);
1865 	}
1866 
1867 	/**
1868 	 * Write new content to the file $GIT_DIR/SQUASH_MSG. In this file
1869 	 * operations triggering a squashed merge will store a template for the
1870 	 * commit message of the squash commit. If <code>null</code> is specified as
1871 	 * message the file will be deleted.
1872 	 *
1873 	 * @param msg
1874 	 *            the message which should be written or <code>null</code> to
1875 	 *            delete the file
1876 	 *
1877 	 * @throws IOException
1878 	 */
1879 	public void writeSquashCommitMsg(String msg) throws IOException {
1880 		File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG);
1881 		writeCommitMsg(squashMsgFile, msg);
1882 	}
1883 
1884 	@Nullable
1885 	private String readCommitMsgFile(String msgFilename) throws IOException {
1886 		if (isBare() || getDirectory() == null)
1887 			throw new NoWorkTreeException();
1888 
1889 		File mergeMsgFile = new File(getDirectory(), msgFilename);
1890 		try {
1891 			return RawParseUtils.decode(IO.readFully(mergeMsgFile));
1892 		} catch (FileNotFoundException e) {
1893 			if (mergeMsgFile.exists()) {
1894 				throw e;
1895 			}
1896 			// the file has disappeared in the meantime ignore it
1897 			return null;
1898 		}
1899 	}
1900 
1901 	private void writeCommitMsg(File msgFile, String msg) throws IOException {
1902 		if (msg != null) {
1903 			FileOutputStream fos = new FileOutputStream(msgFile);
1904 			try {
1905 				fos.write(msg.getBytes(Constants.CHARACTER_ENCODING));
1906 			} finally {
1907 				fos.close();
1908 			}
1909 		} else {
1910 			FileUtils.delete(msgFile, FileUtils.SKIP_MISSING);
1911 		}
1912 	}
1913 
1914 	/**
1915 	 * Read a file from the git directory.
1916 	 *
1917 	 * @param filename
1918 	 * @return the raw contents or {@code null} if the file doesn't exist or is
1919 	 *         empty
1920 	 * @throws IOException
1921 	 */
1922 	@Nullable
1923 	private byte[] readGitDirectoryFile(String filename) throws IOException {
1924 		File file = new File(getDirectory(), filename);
1925 		try {
1926 			byte[] raw = IO.readFully(file);
1927 			return raw.length > 0 ? raw : null;
1928 		} catch (FileNotFoundException notFound) {
1929 			if (file.exists()) {
1930 				throw notFound;
1931 			}
1932 			return null;
1933 		}
1934 	}
1935 
1936 	/**
1937 	 * Write the given heads to a file in the git directory.
1938 	 *
1939 	 * @param heads
1940 	 *            a list of object ids to write or null if the file should be
1941 	 *            deleted.
1942 	 * @param filename
1943 	 * @throws FileNotFoundException
1944 	 * @throws IOException
1945 	 */
1946 	private void writeHeadsFile(List<? extends ObjectId> heads, String filename)
1947 			throws FileNotFoundException, IOException {
1948 		File headsFile = new File(getDirectory(), filename);
1949 		if (heads != null) {
1950 			try (OutputStream bos = new BufferedOutputStream(
1951 					new FileOutputStream(headsFile))) {
1952 				for (ObjectId id : heads) {
1953 					id.copyTo(bos);
1954 					bos.write('\n');
1955 				}
1956 			}
1957 		} else {
1958 			FileUtils.delete(headsFile, FileUtils.SKIP_MISSING);
1959 		}
1960 	}
1961 
1962 	/**
1963 	 * Read a file formatted like the git-rebase-todo file. The "done" file is
1964 	 * also formatted like the git-rebase-todo file. These files can be found in
1965 	 * .git/rebase-merge/ or .git/rebase-append/ folders.
1966 	 *
1967 	 * @param path
1968 	 *            path to the file relative to the repository's git-dir. E.g.
1969 	 *            "rebase-merge/git-rebase-todo" or "rebase-append/done"
1970 	 * @param includeComments
1971 	 *            <code>true</code> if also comments should be reported
1972 	 * @return the list of steps
1973 	 * @throws IOException
1974 	 * @since 3.2
1975 	 */
1976 	@NonNull
1977 	public List<RebaseTodoLine> readRebaseTodo(String path,
1978 			boolean includeComments)
1979 			throws IOException {
1980 		return new RebaseTodoFile(this).readRebaseTodo(path, includeComments);
1981 	}
1982 
1983 	/**
1984 	 * Write a file formatted like a git-rebase-todo file.
1985 	 *
1986 	 * @param path
1987 	 *            path to the file relative to the repository's git-dir. E.g.
1988 	 *            "rebase-merge/git-rebase-todo" or "rebase-append/done"
1989 	 * @param steps
1990 	 *            the steps to be written
1991 	 * @param append
1992 	 *            whether to append to an existing file or to write a new file
1993 	 * @throws IOException
1994 	 * @since 3.2
1995 	 */
1996 	public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
1997 			boolean append)
1998 			throws IOException {
1999 		new RebaseTodoFile(this).writeRebaseTodoFile(path, steps, append);
2000 	}
2001 
2002 	/**
2003 	 * @return the names of all known remotes
2004 	 * @since 3.4
2005 	 */
2006 	@NonNull
2007 	public Set<String> getRemoteNames() {
2008 		return getConfig()
2009 				.getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
2010 	}
2011 
2012 	/**
2013 	 * Check whether any housekeeping is required; if yes, run garbage
2014 	 * collection; if not, exit without performing any work. Some JGit commands
2015 	 * run autoGC after performing operations that could create many loose
2016 	 * objects.
2017 	 * <p/>
2018 	 * Currently this option is supported for repositories of type
2019 	 * {@code FileRepository} only. See {@link GC#setAuto(boolean)} for
2020 	 * configuration details.
2021 	 *
2022 	 * @param monitor
2023 	 *            to report progress
2024 	 * @since 4.6
2025 	 */
2026 	public void autoGC(ProgressMonitor monitor) {
2027 		// default does nothing
2028 	}
2029 }