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.annotations.Nullable;
61 import org.eclipse.jgit.errors.ConfigInvalidException;
62 import org.eclipse.jgit.errors.LockFailedException;
63 import org.eclipse.jgit.internal.JGitText;
64 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
65 import org.eclipse.jgit.internal.storage.file.LockFile;
66 import org.eclipse.jgit.lib.Config;
67 import org.eclipse.jgit.lib.Constants;
68 import org.eclipse.jgit.lib.ObjectId;
69 import org.eclipse.jgit.lib.StoredConfig;
70 import org.eclipse.jgit.util.FS;
71 import org.eclipse.jgit.util.FileUtils;
72 import org.eclipse.jgit.util.IO;
73 import org.eclipse.jgit.util.RawParseUtils;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77
78
79
80 public class FileBasedConfig extends StoredConfig {
81 private final static Logger LOG = LoggerFactory
82 .getLogger(FileBasedConfig.class);
83
84 private final File configFile;
85
86 private final FS fs;
87
88 private boolean utf8Bom;
89
90 private volatile FileSnapshot snapshot;
91
92 private volatile ObjectId hash;
93
94
95
96
97
98
99
100
101
102
103 public FileBasedConfig(File cfgLocation, FS fs) {
104 this(null, cfgLocation, fs);
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118 public FileBasedConfig(Config base, File cfgLocation, FS fs) {
119 super(base);
120 configFile = cfgLocation;
121 this.fs = fs;
122 this.snapshot = FileSnapshot.DIRTY;
123 this.hash = ObjectId.zeroId();
124 }
125
126
127 @Override
128 protected boolean notifyUponTransientChanges() {
129
130 return false;
131 }
132
133
134
135
136
137
138 public final File getFile() {
139 return configFile;
140 }
141
142
143
144
145
146
147
148
149
150 @Override
151 public void load() throws IOException, ConfigInvalidException {
152 final int maxStaleRetries = 5;
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 if (configFile.exists()) {
182 throw noFile;
183 }
184 clear();
185 snapshot = newSnapshot;
186 return;
187 } catch (IOException e) {
188 if (FileUtils.isStaleFileHandle(e)
189 && retries < maxStaleRetries) {
190 if (LOG.isDebugEnabled()) {
191 LOG.debug(MessageFormat.format(
192 JGitText.get().configHandleIsStale,
193 Integer.valueOf(retries)), e);
194 }
195 retries++;
196 continue;
197 }
198 throw new IOException(MessageFormat
199 .format(JGitText.get().cannotReadFile, getFile()), e);
200 } catch (ConfigInvalidException e) {
201 throw new ConfigInvalidException(MessageFormat
202 .format(JGitText.get().cannotReadFile, getFile()), e);
203 }
204 }
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218 @Override
219 public void save() throws IOException {
220 final byte[] out;
221 final String text = toText();
222 if (utf8Bom) {
223 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
224 bos.write(0xEF);
225 bos.write(0xBB);
226 bos.write(0xBF);
227 bos.write(text.getBytes(UTF_8));
228 out = bos.toByteArray();
229 } else {
230 out = Constants.encode(text);
231 }
232
233 final LockFile lf = new LockFile(getFile());
234 if (!lf.lock())
235 throw new LockFailedException(getFile());
236 try {
237 lf.setNeedSnapshot(true);
238 lf.write(out);
239 if (!lf.commit())
240 throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile()));
241 } finally {
242 lf.unlock();
243 }
244 snapshot = lf.getCommitSnapshot();
245 hash = hash(out);
246
247 fireConfigChangedEvent();
248 }
249
250
251 @Override
252 public void clear() {
253 hash = hash(new byte[0]);
254 super.clear();
255 }
256
257 private static ObjectId hash(byte[] rawText) {
258 return ObjectId.fromRaw(Constants.newMessageDigest().digest(rawText));
259 }
260
261
262 @SuppressWarnings("nls")
263 @Override
264 public String toString() {
265 return getClass().getSimpleName() + "[" + getFile().getPath() + "]";
266 }
267
268
269
270
271
272
273
274 public boolean isOutdated() {
275 return snapshot.isModified(getFile());
276 }
277
278
279
280
281
282
283 @Override
284 @Nullable
285 protected byte[] readIncludedConfig(String relPath)
286 throws ConfigInvalidException {
287 final File file;
288 if (relPath.startsWith("~/")) {
289 file = fs.resolve(fs.userHome(), relPath.substring(2));
290 } else {
291 file = fs.resolve(configFile.getParentFile(), relPath);
292 }
293
294 if (!file.exists()) {
295 return null;
296 }
297
298 try {
299 return IO.readFully(file);
300 } catch (IOException ioe) {
301 throw new ConfigInvalidException(MessageFormat
302 .format(JGitText.get().cannotReadFile, relPath), ioe);
303 }
304 }
305 }