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