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;
157
158 newSnapshot = FileSnapshot.saveNoConfig(getFile());
159 try {
160 final byte[] in = IO.readFully(getFile());
161 final ObjectId newHash = hash(in);
162 if (hash.equals(newHash)) {
163 if (oldSnapshot.equals(newSnapshot)) {
164 oldSnapshot.setClean(newSnapshot);
165 } else {
166 snapshot = newSnapshot;
167 }
168 } else {
169 final String decoded;
170 if (isUtf8(in)) {
171 decoded = RawParseUtils.decode(UTF_8,
172 in, 3, in.length);
173 utf8Bom = true;
174 } else {
175 decoded = RawParseUtils.decode(in);
176 }
177 fromText(decoded);
178 snapshot = newSnapshot;
179 hash = newHash;
180 }
181 return;
182 } catch (FileNotFoundException noFile) {
183
184 if (retries < maxRetries && configFile.exists()) {
185 if (LOG.isDebugEnabled()) {
186 LOG.debug(MessageFormat.format(
187 JGitText.get().configHandleMayBeLocked,
188 Integer.valueOf(retries)), noFile);
189 }
190 try {
191 Thread.sleep(retryDelayMillis);
192 } catch (InterruptedException e) {
193 Thread.currentThread().interrupt();
194 }
195 retries++;
196 retryDelayMillis *= 2;
197 continue;
198 }
199 if (configFile.exists()) {
200 throw noFile;
201 }
202 clear();
203 snapshot = newSnapshot;
204 return;
205 } catch (IOException e) {
206 if (FileUtils.isStaleFileHandle(e)
207 && retries < maxRetries) {
208 if (LOG.isDebugEnabled()) {
209 LOG.debug(MessageFormat.format(
210 JGitText.get().configHandleIsStale,
211 Integer.valueOf(retries)), e);
212 }
213 retries++;
214 continue;
215 }
216 throw new IOException(MessageFormat
217 .format(JGitText.get().cannotReadFile, getFile()), e);
218 } catch (ConfigInvalidException e) {
219 throw new ConfigInvalidException(MessageFormat
220 .format(JGitText.get().cannotReadFile, getFile()), e);
221 }
222 }
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236 @Override
237 public void save() throws IOException {
238 final byte[] out;
239 final String text = toText();
240 if (utf8Bom) {
241 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
242 bos.write(0xEF);
243 bos.write(0xBB);
244 bos.write(0xBF);
245 bos.write(text.getBytes(UTF_8));
246 out = bos.toByteArray();
247 } else {
248 out = Constants.encode(text);
249 }
250
251 final LockFileal/storage/file/LockFile.html#LockFile">LockFile lf = new LockFile(getFile());
252 if (!lf.lock())
253 throw new LockFailedException(getFile());
254 try {
255 lf.setNeedSnapshot(true);
256 lf.write(out);
257 if (!lf.commit())
258 throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile()));
259 } finally {
260 lf.unlock();
261 }
262 snapshot = lf.getCommitSnapshot();
263 hash = hash(out);
264
265 fireConfigChangedEvent();
266 }
267
268
269 @Override
270 public void clear() {
271 hash = hash(new byte[0]);
272 super.clear();
273 }
274
275 private static ObjectId hash(byte[] rawText) {
276 return ObjectId.fromRaw(Constants.newMessageDigest().digest(rawText));
277 }
278
279
280 @SuppressWarnings("nls")
281 @Override
282 public String toString() {
283 return getClass().getSimpleName() + "[" + getFile().getPath() + "]";
284 }
285
286
287
288
289
290
291
292 public boolean isOutdated() {
293 return snapshot.isModified(getFile());
294 }
295
296
297
298
299
300
301 @Override
302 protected byte[] readIncludedConfig(String relPath)
303 throws ConfigInvalidException {
304 final File file;
305 if (relPath.startsWith("~/")) {
306 file = fs.resolve(fs.userHome(), relPath.substring(2));
307 } else {
308 file = fs.resolve(configFile.getParentFile(), relPath);
309 }
310
311 if (!file.exists()) {
312 return null;
313 }
314
315 try {
316 return IO.readFully(file);
317 } catch (IOException ioe) {
318 throw new ConfigInvalidException(MessageFormat
319 .format(JGitText.get().cannotReadFile, relPath), ioe);
320 }
321 }
322 }