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-2008, 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 java.io.File;
50  import java.io.FileInputStream;
51  import java.io.FileNotFoundException;
52  import java.io.IOException;
53  import java.text.MessageFormat;
54  import java.text.ParseException;
55  import java.util.HashSet;
56  import java.util.Locale;
57  import java.util.Objects;
58  import java.util.Set;
59  
60  import org.eclipse.jgit.annotations.Nullable;
61  import org.eclipse.jgit.api.errors.JGitInternalException;
62  import org.eclipse.jgit.attributes.AttributesNode;
63  import org.eclipse.jgit.attributes.AttributesNodeProvider;
64  import org.eclipse.jgit.errors.ConfigInvalidException;
65  import org.eclipse.jgit.events.ConfigChangedEvent;
66  import org.eclipse.jgit.events.ConfigChangedListener;
67  import org.eclipse.jgit.events.IndexChangedEvent;
68  import org.eclipse.jgit.internal.JGitText;
69  import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
70  import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
71  import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
72  import org.eclipse.jgit.lib.BaseRepositoryBuilder;
73  import org.eclipse.jgit.lib.ConfigConstants;
74  import org.eclipse.jgit.lib.Constants;
75  import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
76  import org.eclipse.jgit.lib.CoreConfig.SymLinks;
77  import org.eclipse.jgit.lib.ObjectId;
78  import org.eclipse.jgit.lib.ProgressMonitor;
79  import org.eclipse.jgit.lib.Ref;
80  import org.eclipse.jgit.lib.RefDatabase;
81  import org.eclipse.jgit.lib.RefUpdate;
82  import org.eclipse.jgit.lib.ReflogReader;
83  import org.eclipse.jgit.lib.Repository;
84  import org.eclipse.jgit.storage.file.FileBasedConfig;
85  import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
86  import org.eclipse.jgit.storage.pack.PackConfig;
87  import org.eclipse.jgit.util.FS;
88  import org.eclipse.jgit.util.FileUtils;
89  import org.eclipse.jgit.util.IO;
90  import org.eclipse.jgit.util.RawParseUtils;
91  import org.eclipse.jgit.util.StringUtils;
92  import org.eclipse.jgit.util.SystemReader;
93  
94  /**
95   * Represents a Git repository. A repository holds all objects and refs used for
96   * managing source code (could by any type of file, but source code is what
97   * SCM's are typically used for).
98   *
99   * In Git terms all data is stored in GIT_DIR, typically a directory called
100  * .git. A work tree is maintained unless the repository is a bare repository.
101  * Typically the .git directory is located at the root of the work dir.
102  *
103  * <ul>
104  * <li>GIT_DIR
105  * 	<ul>
106  * 		<li>objects/ - objects</li>
107  * 		<li>refs/ - tags and heads</li>
108  * 		<li>config - configuration</li>
109  * 		<li>info/ - more configurations</li>
110  * 	</ul>
111  * </li>
112  * </ul>
113  * <p>
114  * This class is thread-safe.
115  * <p>
116  * This implementation only handles a subtly undocumented subset of git features.
117  */
118 public class FileRepository extends Repository {
119 	private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$
120 
121 	private final FileBasedConfig systemConfig;
122 	private final FileBasedConfig userConfig;
123 	private final FileBasedConfig repoConfig;
124 	private final RefDatabase refs;
125 	private final ObjectDirectory objectDatabase;
126 
127 	private final Object snapshotLock = new Object();
128 
129 	// protected by snapshotLock
130 	private FileSnapshot snapshot;
131 
132 	/**
133 	 * Construct a representation of a Git repository.
134 	 * <p>
135 	 * The work tree, object directory, alternate object directories and index
136 	 * file locations are deduced from the given git directory and the default
137 	 * rules by running
138 	 * {@link org.eclipse.jgit.storage.file.FileRepositoryBuilder}. This
139 	 * constructor is the same as saying:
140 	 *
141 	 * <pre>
142 	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
143 	 * </pre>
144 	 *
145 	 * @param gitDir
146 	 *            GIT_DIR (the location of the repository metadata).
147 	 * @throws java.io.IOException
148 	 *             the repository appears to already exist but cannot be
149 	 *             accessed.
150 	 * @see FileRepositoryBuilder
151 	 */
152 	public FileRepository(File gitDir) throws IOException {
153 		this(new FileRepositoryBuilder().setGitDir(gitDir).setup());
154 	}
155 
156 	/**
157 	 * A convenience API for {@link #FileRepository(File)}.
158 	 *
159 	 * @param gitDir
160 	 *            GIT_DIR (the location of the repository metadata).
161 	 * @throws java.io.IOException
162 	 *             the repository appears to already exist but cannot be
163 	 *             accessed.
164 	 * @see FileRepositoryBuilder
165 	 */
166 	public FileRepository(String gitDir) throws IOException {
167 		this(new File(gitDir));
168 	}
169 
170 	/**
171 	 * Create a repository using the local file system.
172 	 *
173 	 * @param options
174 	 *            description of the repository's important paths.
175 	 * @throws java.io.IOException
176 	 *             the user configuration file or repository configuration file
177 	 *             cannot be accessed.
178 	 */
179 	public FileRepository(BaseRepositoryBuilder options) throws IOException {
180 		super(options);
181 
182 		if (StringUtils.isEmptyOrNull(SystemReader.getInstance().getenv(
183 				Constants.GIT_CONFIG_NOSYSTEM_KEY)))
184 			systemConfig = SystemReader.getInstance().openSystemConfig(null,
185 					getFS());
186 		else
187 			systemConfig = new FileBasedConfig(null, FS.DETECTED) {
188 				@Override
189 				public void load() {
190 					// empty, do not load
191 				}
192 
193 				@Override
194 				public boolean isOutdated() {
195 					// regular class would bomb here
196 					return false;
197 				}
198 			};
199 		userConfig = SystemReader.getInstance().openUserConfig(systemConfig,
200 				getFS());
201 		repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
202 				getDirectory(), Constants.CONFIG),
203 				getFS());
204 
205 		loadSystemConfig();
206 		loadUserConfig();
207 		loadRepoConfig();
208 
209 		repoConfig.addChangeListener(new ConfigChangedListener() {
210 			@Override
211 			public void onConfigChanged(ConfigChangedEvent event) {
212 				fireEvent(event);
213 			}
214 		});
215 
216 		final long repositoryFormatVersion = getConfig().getLong(
217 				ConfigConstants.CONFIG_CORE_SECTION, null,
218 				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
219 
220 		String reftype = repoConfig.getString(
221 				"extensions", null, "refStorage"); //$NON-NLS-1$ //$NON-NLS-2$
222 		if (repositoryFormatVersion >= 1 && reftype != null) {
223 			if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
224 				refs = new RefTreeDatabase(this, new RefDirectory(this));
225 			} else {
226 				throw new IOException(JGitText.get().unknownRepositoryFormat);
227 			}
228 		} else {
229 			refs = new RefDirectory(this);
230 		}
231 
232 		objectDatabase = new ObjectDirectory(repoConfig, //
233 				options.getObjectDirectory(), //
234 				options.getAlternateObjectDirectories(), //
235 				getFS(), //
236 				new File(getDirectory(), Constants.SHALLOW));
237 
238 		if (objectDatabase.exists()) {
239 			if (repositoryFormatVersion > 1)
240 				throw new IOException(MessageFormat.format(
241 						JGitText.get().unknownRepositoryFormat2,
242 						Long.valueOf(repositoryFormatVersion)));
243 		}
244 
245 		if (!isBare()) {
246 			snapshot = FileSnapshot.save(getIndexFile());
247 		}
248 	}
249 
250 	private void loadSystemConfig() throws IOException {
251 		try {
252 			systemConfig.load();
253 		} catch (ConfigInvalidException e) {
254 			throw new IOException(MessageFormat.format(JGitText
255 					.get().systemConfigFileInvalid, systemConfig.getFile()
256 							.getAbsolutePath(),
257 					e), e);
258 		}
259 	}
260 
261 	private void loadUserConfig() throws IOException {
262 		try {
263 			userConfig.load();
264 		} catch (ConfigInvalidException e) {
265 			throw new IOException(MessageFormat.format(JGitText
266 					.get().userConfigFileInvalid, userConfig.getFile()
267 							.getAbsolutePath(),
268 					e), e);
269 		}
270 	}
271 
272 	private void loadRepoConfig() throws IOException {
273 		try {
274 			repoConfig.load();
275 		} catch (ConfigInvalidException e) {
276 			throw new IOException(JGitText.get().unknownRepositoryFormat, e);
277 		}
278 	}
279 
280 	/**
281 	 * {@inheritDoc}
282 	 * <p>
283 	 * Create a new Git repository initializing the necessary files and
284 	 * directories.
285 	 */
286 	@Override
287 	public void create(boolean bare) throws IOException {
288 		final FileBasedConfig cfg = getConfig();
289 		if (cfg.getFile().exists()) {
290 			throw new IllegalStateException(MessageFormat.format(
291 					JGitText.get().repositoryAlreadyExists, getDirectory()));
292 		}
293 		FileUtils.mkdirs(getDirectory(), true);
294 		HideDotFiles hideDotFiles = getConfig().getEnum(
295 				ConfigConstants.CONFIG_CORE_SECTION, null,
296 				ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
297 				HideDotFiles.DOTGITONLY);
298 		if (hideDotFiles != HideDotFiles.FALSE && !isBare()
299 				&& getDirectory().getName().startsWith(".")) //$NON-NLS-1$
300 			getFS().setHidden(getDirectory(), true);
301 		refs.create();
302 		objectDatabase.create();
303 
304 		FileUtils.mkdir(new File(getDirectory(), "branches")); //$NON-NLS-1$
305 		FileUtils.mkdir(new File(getDirectory(), "hooks")); //$NON-NLS-1$
306 
307 		RefUpdate head = updateRef(Constants.HEAD);
308 		head.disableRefLog();
309 		head.link(Constants.R_HEADS + Constants.MASTER);
310 
311 		final boolean fileMode;
312 		if (getFS().supportsExecute()) {
313 			File tmp = File.createTempFile("try", "execute", getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$
314 
315 			getFS().setExecute(tmp, true);
316 			final boolean on = getFS().canExecute(tmp);
317 
318 			getFS().setExecute(tmp, false);
319 			final boolean off = getFS().canExecute(tmp);
320 			FileUtils.delete(tmp);
321 
322 			fileMode = on && !off;
323 		} else {
324 			fileMode = false;
325 		}
326 
327 		SymLinks symLinks = SymLinks.FALSE;
328 		if (getFS().supportsSymlinks()) {
329 			File tmp = new File(getDirectory(), "tmplink"); //$NON-NLS-1$
330 			try {
331 				getFS().createSymLink(tmp, "target"); //$NON-NLS-1$
332 				symLinks = null;
333 				FileUtils.delete(tmp);
334 			} catch (IOException e) {
335 				// Normally a java.nio.file.FileSystemException
336 			}
337 		}
338 		if (symLinks != null)
339 			cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
340 					ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name()
341 							.toLowerCase(Locale.ROOT));
342 		cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
343 				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
344 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
345 				ConfigConstants.CONFIG_KEY_FILEMODE, fileMode);
346 		if (bare)
347 			cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
348 					ConfigConstants.CONFIG_KEY_BARE, true);
349 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
350 				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare);
351 		if (SystemReader.getInstance().isMacOS())
352 			// Java has no other way
353 			cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
354 					ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true);
355 		if (!bare) {
356 			File workTree = getWorkTree();
357 			if (!getDirectory().getParentFile().equals(workTree)) {
358 				cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
359 						ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
360 								.getAbsolutePath());
361 				LockFile dotGitLockFile = new LockFile(new File(workTree,
362 						Constants.DOT_GIT));
363 				try {
364 					if (dotGitLockFile.lock()) {
365 						dotGitLockFile.write(Constants.encode(Constants.GITDIR
366 								+ getDirectory().getAbsolutePath()));
367 						dotGitLockFile.commit();
368 					}
369 				} finally {
370 					dotGitLockFile.unlock();
371 				}
372 			}
373 		}
374 		cfg.save();
375 	}
376 
377 	/**
378 	 * Get the directory containing the objects owned by this repository
379 	 *
380 	 * @return the directory containing the objects owned by this repository.
381 	 */
382 	public File getObjectsDirectory() {
383 		return objectDatabase.getDirectory();
384 	}
385 
386 	/** {@inheritDoc} */
387 	@Override
388 	public ObjectDirectory getObjectDatabase() {
389 		return objectDatabase;
390 	}
391 
392 	/** {@inheritDoc} */
393 	@Override
394 	public RefDatabase getRefDatabase() {
395 		return refs;
396 	}
397 
398 	/** {@inheritDoc} */
399 	@Override
400 	public FileBasedConfig getConfig() {
401 		if (systemConfig.isOutdated()) {
402 			try {
403 				loadSystemConfig();
404 			} catch (IOException e) {
405 				throw new RuntimeException(e);
406 			}
407 		}
408 		if (userConfig.isOutdated()) {
409 			try {
410 				loadUserConfig();
411 			} catch (IOException e) {
412 				throw new RuntimeException(e);
413 			}
414 		}
415 		if (repoConfig.isOutdated()) {
416 				try {
417 					loadRepoConfig();
418 				} catch (IOException e) {
419 					throw new RuntimeException(e);
420 				}
421 		}
422 		return repoConfig;
423 	}
424 
425 	/** {@inheritDoc} */
426 	@Override
427 	@Nullable
428 	public String getGitwebDescription() throws IOException {
429 		String d;
430 		try {
431 			d = RawParseUtils.decode(IO.readFully(descriptionFile()));
432 		} catch (FileNotFoundException err) {
433 			return null;
434 		}
435 		if (d != null) {
436 			d = d.trim();
437 			if (d.isEmpty() || UNNAMED.equals(d)) {
438 				return null;
439 			}
440 		}
441 		return d;
442 	}
443 
444 	/** {@inheritDoc} */
445 	@Override
446 	public void setGitwebDescription(@Nullable String description)
447 			throws IOException {
448 		String old = getGitwebDescription();
449 		if (Objects.equals(old, description)) {
450 			return;
451 		}
452 
453 		File path = descriptionFile();
454 		LockFile lock = new LockFile(path);
455 		if (!lock.lock()) {
456 			throw new IOException(MessageFormat.format(JGitText.get().lockError,
457 					path.getAbsolutePath()));
458 		}
459 		try {
460 			String d = description;
461 			if (d != null) {
462 				d = d.trim();
463 				if (!d.isEmpty()) {
464 					d += '\n';
465 				}
466 			} else {
467 				d = ""; //$NON-NLS-1$
468 			}
469 			lock.write(Constants.encode(d));
470 			lock.commit();
471 		} finally {
472 			lock.unlock();
473 		}
474 	}
475 
476 	private File descriptionFile() {
477 		return new File(getDirectory(), "description"); //$NON-NLS-1$
478 	}
479 
480 	/**
481 	 * {@inheritDoc}
482 	 * <p>
483 	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
484 	 * <p>
485 	 * When a repository borrows objects from another repository, it can
486 	 * advertise that it safely has that other repository's references, without
487 	 * exposing any other details about the other repository. This may help a
488 	 * client trying to push changes avoid pushing more than it needs to.
489 	 */
490 	@Override
491 	public Set<ObjectId> getAdditionalHaves() {
492 		return getAdditionalHaves(null);
493 	}
494 
495 	/**
496 	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
497 	 * <p>
498 	 * When a repository borrows objects from another repository, it can
499 	 * advertise that it safely has that other repository's references, without
500 	 * exposing any other details about the other repository.  This may help
501 	 * a client trying to push changes avoid pushing more than it needs to.
502 	 *
503 	 * @param skips
504 	 *            Set of AlternateHandle Ids already seen
505 	 *
506 	 * @return unmodifiable collection of other known objects.
507 	 */
508 	private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) {
509 		HashSet<ObjectId> r = new HashSet<>();
510 		skips = objectDatabase.addMe(skips);
511 		for (AlternateHandle d : objectDatabase.myAlternates()) {
512 			if (d instanceof AlternateRepository && !skips.contains(d.getId())) {
513 				FileRepository repo;
514 
515 				repo = ((AlternateRepository) d).repository;
516 				for (Ref ref : repo.getAllRefs().values()) {
517 					if (ref.getObjectId() != null)
518 						r.add(ref.getObjectId());
519 					if (ref.getPeeledObjectId() != null)
520 						r.add(ref.getPeeledObjectId());
521 				}
522 				r.addAll(repo.getAdditionalHaves(skips));
523 			}
524 		}
525 		return r;
526 	}
527 
528 	/**
529 	 * Add a single existing pack to the list of available pack files.
530 	 *
531 	 * @param pack
532 	 *            path of the pack file to open.
533 	 * @throws java.io.IOException
534 	 *             index file could not be opened, read, or is not recognized as
535 	 *             a Git pack file index.
536 	 */
537 	public void openPack(File pack) throws IOException {
538 		objectDatabase.openPack(pack);
539 	}
540 
541 	/** {@inheritDoc} */
542 	@Override
543 	public void scanForRepoChanges() throws IOException {
544 		getRefDatabase().getRefs(); // This will look for changes to refs
545 		detectIndexChanges();
546 	}
547 
548 	/** Detect index changes. */
549 	private void detectIndexChanges() {
550 		if (isBare()) {
551 			return;
552 		}
553 
554 		File indexFile = getIndexFile();
555 		synchronized (snapshotLock) {
556 			if (snapshot == null) {
557 				snapshot = FileSnapshot.save(indexFile);
558 				return;
559 			}
560 			if (!snapshot.isModified(indexFile)) {
561 				return;
562 			}
563 		}
564 		notifyIndexChanged(false);
565 	}
566 
567 	/** {@inheritDoc} */
568 	@Override
569 	public void notifyIndexChanged(boolean internal) {
570 		synchronized (snapshotLock) {
571 			snapshot = FileSnapshot.save(getIndexFile());
572 		}
573 		fireEvent(new IndexChangedEvent(internal));
574 	}
575 
576 	/** {@inheritDoc} */
577 	@Override
578 	public ReflogReader getReflogReader(String refName) throws IOException {
579 		Ref ref = findRef(refName);
580 		if (ref != null)
581 			return new ReflogReaderImpl(this, ref.getName());
582 		return null;
583 	}
584 
585 	/** {@inheritDoc} */
586 	@Override
587 	public AttributesNodeProvider createAttributesNodeProvider() {
588 		return new AttributesNodeProviderImpl(this);
589 	}
590 
591 	/**
592 	 * Implementation a {@link AttributesNodeProvider} for a
593 	 * {@link FileRepository}.
594 	 *
595 	 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
596 	 *
597 	 */
598 	static class AttributesNodeProviderImpl implements
599 			AttributesNodeProvider {
600 
601 		private AttributesNode infoAttributesNode;
602 
603 		private AttributesNode globalAttributesNode;
604 
605 		/**
606 		 * Constructor.
607 		 *
608 		 * @param repo
609 		 *            {@link Repository} that will provide the attribute nodes.
610 		 */
611 		protected AttributesNodeProviderImpl(Repository repo) {
612 			infoAttributesNode = new InfoAttributesNode(repo);
613 			globalAttributesNode = new GlobalAttributesNode(repo);
614 		}
615 
616 		@Override
617 		public AttributesNode getInfoAttributesNode() throws IOException {
618 			if (infoAttributesNode instanceof InfoAttributesNode)
619 				infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
620 						.load();
621 			return infoAttributesNode;
622 		}
623 
624 		@Override
625 		public AttributesNode getGlobalAttributesNode() throws IOException {
626 			if (globalAttributesNode instanceof GlobalAttributesNode)
627 				globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
628 						.load();
629 			return globalAttributesNode;
630 		}
631 
632 		static void loadRulesFromFile(AttributesNode r, File attrs)
633 				throws FileNotFoundException, IOException {
634 			if (attrs.exists()) {
635 				try (FileInputStream in = new FileInputStream(attrs)) {
636 					r.parse(in);
637 				}
638 			}
639 		}
640 
641 	}
642 
643 	private boolean shouldAutoDetach() {
644 		return getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
645 				ConfigConstants.CONFIG_KEY_AUTODETACH, true);
646 	}
647 
648 	/** {@inheritDoc} */
649 	@Override
650 	public void autoGC(ProgressMonitor monitor) {
651 		GC gc = new GC(this);
652 		gc.setPackConfig(new PackConfig(this));
653 		gc.setProgressMonitor(monitor);
654 		gc.setAuto(true);
655 		gc.setBackground(shouldAutoDetach());
656 		try {
657 			gc.gc();
658 		} catch (ParseException | IOException e) {
659 			throw new JGitInternalException(JGitText.get().gcFailed, e);
660 		}
661 	}
662 }