View Javadoc
1   /*
2    * Copyright (C) 2017 Two Sigma Open Source and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  
15  import java.io.File;
16  import java.io.IOException;
17  import java.nio.file.Files;
18  import java.nio.file.NoSuchFileException;
19  import java.nio.file.attribute.FileTime;
20  import java.text.ParseException;
21  import java.time.Instant;
22  
23  import org.eclipse.jgit.api.errors.JGitInternalException;
24  import org.eclipse.jgit.lib.ConfigConstants;
25  import org.eclipse.jgit.util.FileUtils;
26  import org.eclipse.jgit.util.GitDateParser;
27  import org.eclipse.jgit.util.SystemReader;
28  
29  /**
30   * This class manages the gc.log file for a {@link FileRepository}.
31   */
32  class GcLog {
33  	private final FileRepository repo;
34  
35  	private final File logFile;
36  
37  	private final LockFile lock;
38  
39  	private Instant gcLogExpire;
40  
41  	private static final String LOG_EXPIRY_DEFAULT = "1.day.ago"; //$NON-NLS-1$
42  
43  	private boolean nonEmpty = false;
44  
45  	/**
46  	 * Construct a GcLog object for a {@link FileRepository}
47  	 *
48  	 * @param repo
49  	 *            the repository
50  	 */
51  	GcLog(FileRepository repo) {
52  		this.repo = repo;
53  		logFile = new File(repo.getDirectory(), "gc.log"); //$NON-NLS-1$
54  		lock = new LockFile(logFile);
55  	}
56  
57  	private Instant getLogExpiry() throws ParseException {
58  		if (gcLogExpire == null) {
59  			String logExpiryStr = repo.getConfig().getString(
60  					ConfigConstants.CONFIG_GC_SECTION, null,
61  					ConfigConstants.CONFIG_KEY_LOGEXPIRY);
62  			if (logExpiryStr == null) {
63  				logExpiryStr = LOG_EXPIRY_DEFAULT;
64  			}
65  			gcLogExpire = GitDateParser.parse(logExpiryStr, null,
66  					SystemReader.getInstance().getLocale()).toInstant();
67  		}
68  		return gcLogExpire;
69  	}
70  
71  	private boolean autoGcBlockedByOldLockFile() {
72  		try {
73  			FileTime lastModified = Files.getLastModifiedTime(FileUtils.toPath(logFile));
74  			if (lastModified.toInstant().compareTo(getLogExpiry()) > 0) {
75  				// There is an existing log file, which is too recent to ignore
76  				return true;
77  			}
78  		} catch (NoSuchFileException e) {
79  			// No existing log file, OK.
80  		} catch (IOException | ParseException e) {
81  			throw new JGitInternalException(e.getMessage(), e);
82  		}
83  		return false;
84  	}
85  
86  	/**
87  	 * Lock the GC log file for updates
88  	 *
89  	 * @return {@code true} if we hold the lock
90  	 */
91  	boolean lock() {
92  		try {
93  			if (!lock.lock()) {
94  				return false;
95  			}
96  		} catch (IOException e) {
97  			throw new JGitInternalException(e.getMessage(), e);
98  		}
99  		if (autoGcBlockedByOldLockFile()) {
100 			lock.unlock();
101 			return false;
102 		}
103 		return true;
104 	}
105 
106 	/**
107 	 * Unlock (roll back) the GC log lock
108 	 */
109 	void unlock() {
110 		lock.unlock();
111 	}
112 
113 	/**
114 	 * Commit changes to the gc log, if there have been any writes. Otherwise,
115 	 * just unlock and delete the existing file (if any)
116 	 *
117 	 * @return true if committing (or unlocking/deleting) succeeds.
118 	 */
119 	boolean commit() {
120 		if (nonEmpty) {
121 			return lock.commit();
122 		}
123 		logFile.delete();
124 		lock.unlock();
125 		return true;
126 	}
127 
128 	/**
129 	 * Write to the pending gc log. Content will be committed upon a call to
130 	 * commit()
131 	 *
132 	 * @param content
133 	 *            The content to write
134 	 * @throws IOException
135 	 */
136 	void write(String content) throws IOException {
137 		if (content.length() > 0) {
138 			nonEmpty = true;
139 		}
140 		lock.write(content.getBytes(UTF_8));
141 	}
142 }