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;
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 if (configFile.exists()) {
184 throw noFile;
185 }
186 clear();
187 snapshot = newSnapshot;
188 return;
189 } catch (IOException e) {
190 if (FileUtils.isStaleFileHandle(e)
191 && retries < maxStaleRetries) {
192 if (LOG.isDebugEnabled()) {
193 LOG.debug(MessageFormat.format(
194 JGitText.get().configHandleIsStale,
195 Integer.valueOf(retries)), e);
196 }
197 retries++;
198 continue;
199 }
200 throw new IOException(MessageFormat
201 .format(JGitText.get().cannotReadFile, getFile()), e);
202 } catch (ConfigInvalidException e) {
203 throw new ConfigInvalidException(MessageFormat
204 .format(JGitText.get().cannotReadFile, getFile()), e);
205 }
206 }
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220 @Override
221 public void save() throws IOException {
222 final byte[] out;
223 final String text = toText();
224 if (utf8Bom) {
225 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
226 bos.write(0xEF);
227 bos.write(0xBB);
228 bos.write(0xBF);
229 bos.write(text.getBytes(UTF_8));
230 out = bos.toByteArray();
231 } else {
232 out = Constants.encode(text);
233 }
234
235 final LockFile lf = new LockFile(getFile());
236 if (!lf.lock())
237 throw new LockFailedException(getFile());
238 try {
239 lf.setNeedSnapshot(true);
240 lf.write(out);
241 if (!lf.commit())
242 throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile()));
243 } finally {
244 lf.unlock();
245 }
246 snapshot = lf.getCommitSnapshot();
247 hash = hash(out);
248
249 fireConfigChangedEvent();
250 }
251
252
253 @Override
254 public void clear() {
255 hash = hash(new byte[0]);
256 super.clear();
257 }
258
259 private static ObjectId hash(byte[] rawText) {
260 return ObjectId.fromRaw(Constants.newMessageDigest().digest(rawText));
261 }
262
263
264 @SuppressWarnings("nls")
265 @Override
266 public String toString() {
267 return getClass().getSimpleName() + "[" + getFile().getPath() + "]";
268 }
269
270
271
272
273
274
275
276 public boolean isOutdated() {
277 return snapshot.isModified(getFile());
278 }
279
280
281
282
283
284
285 @Override
286 @Nullable
287 protected byte[] readIncludedConfig(String relPath)
288 throws ConfigInvalidException {
289 final File file;
290 if (relPath.startsWith("~/")) {
291 file = fs.resolve(fs.userHome(), relPath.substring(2));
292 } else {
293 file = fs.resolve(configFile.getParentFile(), relPath);
294 }
295
296 if (!file.exists()) {
297 return null;
298 }
299
300 try {
301 return IO.readFully(file);
302 } catch (IOException ioe) {
303 throw new ConfigInvalidException(MessageFormat
304 .format(JGitText.get().cannotReadFile, relPath), ioe);
305 }
306 }
307 }