1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 package org.eclipse.jgit.storage.file;
51
52 import static java.nio.charset.StandardCharsets.UTF_8;
53
54 import java.io.ByteArrayOutputStream;
55 import java.io.File;
56 import java.io.FileNotFoundException;
57 import java.io.IOException;
58 import java.text.MessageFormat;
59
60 import org.eclipse.jgit.errors.ConfigInvalidException;
61 import org.eclipse.jgit.errors.LockFailedException;
62 import org.eclipse.jgit.internal.JGitText;
63 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
64 import org.eclipse.jgit.internal.storage.file.LockFile;
65 import org.eclipse.jgit.lib.Config;
66 import org.eclipse.jgit.lib.Constants;
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.lib.StoredConfig;
69 import org.eclipse.jgit.util.FS;
70 import org.eclipse.jgit.util.FileUtils;
71 import org.eclipse.jgit.util.IO;
72 import org.eclipse.jgit.util.RawParseUtils;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76
77
78
79 public class FileBasedConfig extends StoredConfig {
80 private final static Logger LOG = LoggerFactory
81 .getLogger(FileBasedConfig.class);
82
83 private final File configFile;
84
85 private final FS fs;
86
87 private boolean utf8Bom;
88
89 private volatile FileSnapshot snapshot;
90
91 private volatile ObjectId hash;
92
93
94
95
96
97
98
99
100
101
102 public FileBasedConfig(File cfgLocation, FS fs) {
103 this(null, cfgLocation, fs);
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117 public FileBasedConfig(Config base, File cfgLocation, FS fs) {
118 super(base);
119 configFile = cfgLocation;
120 this.fs = fs;
121 this.snapshot = FileSnapshot.DIRTY;
122 this.hash = ObjectId.zeroId();
123 }
124
125
126 @Override
127 protected boolean notifyUponTransientChanges() {
128
129 return false;
130 }
131
132
133
134
135
136
137 public final File getFile() {
138 return configFile;
139 }
140
141
142
143
144
145
146
147
148
149 @Override
150 public void load() throws IOException, ConfigInvalidException {
151 final int maxRetries = 5;
152 int retryDelayMillis = 20;
153 int retries = 0;
154 while (true) {
155 final FileSnapshot oldSnapshot = snapshot;
156 final FileSnapshot newSnapshot = FileSnapshot.save(getFile());
157 try {
158 final byte[] in = IO.readFully(getFile());
159 final ObjectId newHash = hash(in);
160 if (hash.equals(newHash)) {
161 if (oldSnapshot.equals(newSnapshot)) {
162 oldSnapshot.setClean(newSnapshot);
163 } else {
164 snapshot = newSnapshot;
165 }
166 } else {
167 final String decoded;
168 if (isUtf8(in)) {
169 decoded = RawParseUtils.decode(UTF_8,
170 in, 3, in.length);
171 utf8Bom = true;
172 } else {
173 decoded = RawParseUtils.decode(in);
174 }
175 fromText(decoded);
176 snapshot = newSnapshot;
177 hash = newHash;
178 }
179 return;
180 } catch (FileNotFoundException noFile) {
181
182 if (retries < maxRetries && configFile.exists()) {
183 if (LOG.isDebugEnabled()) {
184 LOG.debug(MessageFormat.format(
185 JGitText.get().configHandleMayBeLocked,
186 Integer.valueOf(retries)), noFile);
187 }
188 try {
189 Thread.sleep(retryDelayMillis);
190 } catch (InterruptedException e) {
191 Thread.currentThread().interrupt();
192 }
193 retries++;
194 retryDelayMillis *= 2;
195 continue;
196 }
197 if (configFile.exists()) {
198 throw noFile;
199 }
200 clear();
201 snapshot = newSnapshot;
202 return;
203 } catch (IOException e) {
204 if (FileUtils.isStaleFileHandle(e)
205 && retries < maxRetries) {
206 if (LOG.isDebugEnabled()) {
207 LOG.debug(MessageFormat.format(
208 JGitText.get().configHandleIsStale,
209 Integer.valueOf(retries)), e);
210 }
211 retries++;
212 continue;
213 }
214 throw new IOException(MessageFormat
215 .format(JGitText.get().cannotReadFile, getFile()), e);
216 } catch (ConfigInvalidException e) {
217 throw new ConfigInvalidException(MessageFormat
218 .format(JGitText.get().cannotReadFile, getFile()), e);
219 }
220 }
221 }
222
223
224
225
226
227
228
229
230
231
232
233
234 @Override
235 public void save() throws IOException {
236 final byte[] out;
237 final String text = toText();
238 if (utf8Bom) {
239 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
240 bos.write(0xEF);
241 bos.write(0xBB);
242 bos.write(0xBF);
243 bos.write(text.getBytes(UTF_8));
244 out = bos.toByteArray();
245 } else {
246 out = Constants.encode(text);
247 }
248
249 final LockFileal/storage/file/LockFile.html#LockFile">LockFile lf = new LockFile(getFile());
250 if (!lf.lock())
251 throw new LockFailedException(getFile());
252 try {
253 lf.setNeedSnapshot(true);
254 lf.write(out);
255 if (!lf.commit())
256 throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile()));
257 } finally {
258 lf.unlock();
259 }
260 snapshot = lf.getCommitSnapshot();
261 hash = hash(out);
262
263 fireConfigChangedEvent();
264 }
265
266
267 @Override
268 public void clear() {
269 hash = hash(new byte[0]);
270 super.clear();
271 }
272
273 private static ObjectId hash(byte[] rawText) {
274 return ObjectId.fromRaw(Constants.newMessageDigest().digest(rawText));
275 }
276
277
278 @SuppressWarnings("nls")
279 @Override
280 public String toString() {
281 return getClass().getSimpleName() + "[" + getFile().getPath() + "]";
282 }
283
284
285
286
287
288
289
290 public boolean isOutdated() {
291 return snapshot.isModified(getFile());
292 }
293
294
295
296
297
298
299 @Override
300 protected byte[] readIncludedConfig(String relPath)
301 throws ConfigInvalidException {
302 final File file;
303 if (relPath.startsWith("~/")) {
304 file = fs.resolve(fs.userHome(), relPath.substring(2));
305 } else {
306 file = fs.resolve(configFile.getParentFile(), relPath);
307 }
308
309 if (!file.exists()) {
310 return null;
311 }
312
313 try {
314 return IO.readFully(file);
315 } catch (IOException ioe) {
316 throw new ConfigInvalidException(MessageFormat
317 .format(JGitText.get().cannotReadFile, relPath), ioe);
318 }
319 }
320 }