Fix parsing of subsections in git config
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / RepositoryConfig.java
blobdf3fdfc62198ff7cb092e5e30b8ca1a884d4b424
1 /*
2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org.spearce.jgit.lib;
19 import java.io.BufferedReader;
20 import java.io.BufferedWriter;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.OutputStreamWriter;
27 import java.io.PrintWriter;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
34 public class RepositoryConfig {
35 private final Repository repo;
37 private final File configFile;
39 private CoreConfig core;
41 private List entries;
43 private Map byName;
45 private Map lastInEntry;
47 private Map lastInGroup;
49 private static final String MAGIC_EMPTY_VALUE = "%%magic%%empty%%";
51 // used for global configs
52 private RepositoryConfig() {
53 repo = null;
54 configFile = new File(System.getProperty("user.home"), ".gitconfig");
55 clear();
56 if (configFile.exists()) {
57 try {
58 load();
59 } catch (IOException e) {
60 e.printStackTrace();
65 private static RepositoryConfig globalConfig = null;
67 public static RepositoryConfig getGlobalConfig() {
68 if (globalConfig == null)
69 globalConfig = new RepositoryConfig();
70 return globalConfig;
73 protected RepositoryConfig(final Repository r) {
74 repo = r;
75 configFile = new File(repo.getDirectory(), "config");
76 clear();
79 public CoreConfig getCore() {
80 return core;
83 public int getInt(final String group, final String name,
84 final int defaultValue) {
85 final String n = getString(group, name);
86 if (n == null) {
87 if (repo == null)
88 return defaultValue;
89 return getGlobalConfig().getInt(group, name, defaultValue);
92 try {
93 return Integer.parseInt(n);
94 } catch (NumberFormatException nfe) {
95 throw new IllegalArgumentException("Invalid integer value: "
96 + group + "." + name + "=" + n);
100 public boolean getBoolean(final String group, final String name,
101 final boolean defaultValue) {
102 String n = getRawString(group, name);
103 if (n == null) {
104 if (repo == null)
105 return defaultValue;
106 return getGlobalConfig().getBoolean(group, name, defaultValue);
109 n = n.toLowerCase();
110 if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) {
111 return true;
112 } else if ("no".equals(n) || "false".equals(n) || "0".equals(n)) {
113 return false;
114 } else {
115 throw new IllegalArgumentException("Invalid boolean value: "
116 + group + "." + name + "=" + n);
120 public String getString(final String group, final String name) {
121 String val = getRawString(group, name);
122 if (MAGIC_EMPTY_VALUE.equals(val)) {
123 return "";
125 return val;
128 private String getRawString(final String group, final String name) {
129 final Object o;
130 o = byName.get(group.toLowerCase() + "." + name.toLowerCase());
131 if (o instanceof List) {
132 return ((Entry) ((List) o).get(0)).value;
133 } else if (o instanceof Entry) {
134 return ((Entry) o).value;
135 } else {
136 if (repo == null)
137 return null;
138 return getGlobalConfig().getString(group, name);
142 public void create() {
143 Entry e;
145 clear();
147 e = new Entry();
148 e.base = "core";
149 add(e);
151 e = new Entry();
152 e.base = "core";
153 e.name = "repositoryformatversion";
154 e.value = "0";
155 add(e);
157 e = new Entry();
158 e.base = "core";
159 e.name = "filemode";
160 e.value = "true";
161 add(e);
163 core = new CoreConfig(this);
166 public void save() throws IOException {
167 final File tmp = new File(configFile.getParentFile(), configFile
168 .getName()
169 + ".lock");
170 final PrintWriter r = new PrintWriter(new BufferedWriter(
171 new OutputStreamWriter(new FileOutputStream(tmp),
172 Constants.CHARACTER_ENCODING)));
173 boolean ok = false;
174 try {
175 final Iterator i = entries.iterator();
176 while (i.hasNext()) {
177 final Entry e = (Entry) i.next();
178 if (e.prefix != null) {
179 r.print(e.prefix);
181 if (e.base != null && e.name == null) {
182 r.print('[');
183 r.print(e.base);
184 if (e.extendedBase != null) {
185 r.print(' ');
186 r.print('"');
187 r.print(escapeValue(e.extendedBase));
188 r.print('"');
190 r.print(']');
191 } else if (e.base != null && e.name != null) {
192 if (e.prefix == null || "".equals(e.prefix)) {
193 r.print('\t');
195 r.print(e.name);
196 if (e.value != null) {
197 if (!MAGIC_EMPTY_VALUE.equals(e.value)) {
198 r.print(" = ");
199 r.print(escapeValue(e.value));
202 if (e.suffix != null) {
203 r.print(' ');
206 if (e.suffix != null) {
207 r.print(e.suffix);
209 r.println();
211 ok = true;
212 } finally {
213 r.close();
214 if (!ok || !tmp.renameTo(configFile)) {
215 tmp.delete();
220 public void load() throws IOException {
221 clear();
222 final BufferedReader r = new BufferedReader(new InputStreamReader(
223 new FileInputStream(configFile), Constants.CHARACTER_ENCODING));
224 try {
225 Entry last = null;
226 Entry e = new Entry();
227 for (;;) {
228 r.mark(1);
229 int input = r.read();
230 final char in = (char) input;
231 if (-1 == input) {
232 break;
233 } else if ('\n' == in) {
234 // End of this entry.
235 add(e);
236 if (e.base != null) {
237 last = e;
239 e = new Entry();
240 } else if (e.suffix != null) {
241 // Everything up until the end-of-line is in the suffix.
242 e.suffix += in;
243 } else if (';' == in || '#' == in) {
244 // The rest of this line is a comment; put into suffix.
245 e.suffix = String.valueOf(in);
246 } else if (e.base == null && Character.isWhitespace(in)) {
247 // Save the leading whitespace (if any).
248 if (e.prefix == null) {
249 e.prefix = "";
251 e.prefix += in;
252 } else if ('[' == in) {
253 // This is a group header line.
254 e.base = readBase(r);
255 input = r.read();
256 if ('"' == input) {
257 e.extendedBase = readValue(r, true, '"');
258 input = r.read();
260 if (']' != input) {
261 throw new IOException("Bad group header.");
263 e.suffix = "";
264 } else if (last != null) {
265 // Read a value.
266 e.base = last.base;
267 e.extendedBase = last.extendedBase;
268 r.reset();
269 e.name = readName(r);
270 if (e.name.endsWith("\n")) {
271 e.name = e.name.substring(0, e.name.length()-1);
272 e.value = MAGIC_EMPTY_VALUE;
273 } else
274 e.value = readValue(r, false, -1);
275 } else {
276 throw new IOException("Invalid line in config file.");
279 } finally {
280 r.close();
283 core = new CoreConfig(this);
286 private void clear() {
287 entries = new ArrayList();
288 byName = new HashMap();
289 lastInEntry = new HashMap();
290 lastInGroup = new HashMap();
293 private void add(final Entry e) {
294 entries.add(e);
295 if (e.base != null) {
296 final String b = e.base.toLowerCase();
297 final String group;
298 if (e.extendedBase != null) {
299 group = b + "." + e.extendedBase;
300 } else {
301 group = b;
303 if (e.name != null) {
304 final String n = e.name.toLowerCase();
305 final String key = group + "." + n;
306 final Object o = byName.get(key);
307 if (o == null) {
308 byName.put(key, e);
309 } else if (o instanceof Entry) {
310 final ArrayList l = new ArrayList();
311 l.add(o);
312 l.add(e);
313 byName.put(key, l);
314 } else if (o instanceof List) {
315 ((List) o).add(e);
317 lastInEntry.put(key, e);
319 lastInGroup.put(group, e);
323 private static String escapeValue(final String x) {
324 boolean inquote = false;
325 int lineStart = 0;
326 final StringBuffer r = new StringBuffer(x.length());
327 for (int k = 0; k < x.length(); k++) {
328 final char c = x.charAt(k);
329 switch (c) {
330 case '\n':
331 if (inquote) {
332 r.append('"');
333 inquote = false;
335 r.append("\\n\\\n");
336 lineStart = r.length();
337 break;
339 case '\t':
340 r.append("\\t");
341 break;
343 case '\b':
344 r.append("\\b");
345 break;
347 case '\\':
348 r.append("\\\\");
349 break;
351 case '"':
352 r.append("\\\"");
353 break;
355 case ';':
356 case '#':
357 if (!inquote) {
358 r.insert(lineStart, '"');
359 inquote = true;
361 r.append(c);
362 break;
364 case ' ':
365 if (!inquote && r.length() > 0
366 && r.charAt(r.length() - 1) == ' ') {
367 r.insert(lineStart, '"');
368 inquote = true;
370 r.append(' ');
371 break;
373 default:
374 r.append(c);
375 break;
378 if (inquote) {
379 r.append('"');
381 return r.toString();
384 private static String readBase(final BufferedReader r) throws IOException {
385 final StringBuffer base = new StringBuffer();
386 for (;;) {
387 r.mark(1);
388 int c = r.read();
389 if (c < 0) {
390 throw new IOException("Unexpected end of config file.");
391 } else if (']' == c) {
392 r.reset();
393 break;
394 } else if (' ' == c || '\t' == c) {
395 for (;;) {
396 r.mark(1);
397 c = r.read();
398 if (c < 0) {
399 throw new IOException("Unexpected end of config file.");
400 } else if ('"' == c) {
401 r.reset();
402 break;
403 } else if (' ' == c || '\t' == c) {
404 // Skipped...
405 } else {
406 throw new IOException("Bad base entry. : " + base + "," + c);
409 break;
410 } else if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c) {
411 base.append((char) c);
412 } else {
413 throw new IOException("Bad base entry. : " + base + ", " + c);
416 return base.toString();
419 private static String readName(final BufferedReader r) throws IOException {
420 final StringBuffer name = new StringBuffer();
421 for (;;) {
422 r.mark(1);
423 int c = r.read();
424 if (c < 0) {
425 throw new IOException("Unexpected end of config file.");
426 } else if ('=' == c) {
427 break;
428 } else if (' ' == c || '\t' == c) {
429 for (;;) {
430 r.mark(1);
431 c = r.read();
432 if (c < 0) {
433 throw new IOException("Unexpected end of config file.");
434 } else if ('=' == c) {
435 break;
436 } else if (';' == c || '#' == c || '\n' == c) {
437 r.reset();
438 break;
439 } else if (' ' == c || '\t' == c) {
440 // Skipped...
441 } else {
442 throw new IOException("Bad entry delimiter.");
445 break;
446 } else if (Character.isLetterOrDigit((char) c)) {
447 name.append((char) c);
448 } else if ('\n' == c) {
449 r.reset();
450 name.append((char) c);
451 break;
452 } else {
453 throw new IOException("Bad config entry name: " + name + (char) c);
456 return name.toString();
459 private static String readValue(final BufferedReader r, boolean quote,
460 final int eol) throws IOException {
461 final StringBuffer value = new StringBuffer();
462 boolean space = false;
463 for (;;) {
464 r.mark(1);
465 int c = r.read();
466 if (c < 0) {
467 throw new IOException("Unexpected end of config file.");
469 if ('\n' == c) {
470 if (quote) {
471 throw new IOException("Newline in quotes not allowed.");
473 r.reset();
474 break;
476 if (eol == c) {
477 break;
479 if (!quote) {
480 if (Character.isWhitespace((char) c)) {
481 space = true;
482 continue;
484 if (';' == c || '#' == c) {
485 r.reset();
486 break;
489 if (space) {
490 if (value.length() > 0) {
491 value.append(' ');
493 space = false;
495 if ('\\' == c) {
496 c = r.read();
497 switch (c) {
498 case -1:
499 throw new IOException("End of file in escape.");
500 case '\n':
501 continue;
502 case 't':
503 value.append('\t');
504 continue;
505 case 'b':
506 value.append('\b');
507 continue;
508 case 'n':
509 value.append('\n');
510 continue;
511 case '\\':
512 value.append('\\');
513 continue;
514 case '"':
515 value.append('"');
516 continue;
517 default:
518 throw new IOException("Bad escape: " + ((char) c));
521 if ('"' == c) {
522 quote = !quote;
523 continue;
525 value.append((char) c);
527 return value.length() > 0 ? value.toString() : null;
530 public String toString() {
531 return "RepositoryConfig[" + configFile.getPath() + "]";
534 static class Entry {
535 String prefix;
537 String base;
539 String extendedBase;
541 String name;
543 String value;
545 String suffix;
548 RepositoryConfig(String file) throws IOException {
549 repo = null;
550 configFile = new File(file);
551 clear();
552 load();