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
;
22 import java
.io
.FileInputStream
;
23 import java
.io
.FileNotFoundException
;
24 import java
.io
.FileOutputStream
;
25 import java
.io
.IOException
;
26 import java
.io
.InputStreamReader
;
27 import java
.io
.OutputStreamWriter
;
28 import java
.io
.PrintWriter
;
29 import java
.util
.ArrayList
;
30 import java
.util
.HashMap
;
31 import java
.util
.Iterator
;
32 import java
.util
.List
;
35 import org
.spearce
.jgit
.util
.FS
;
38 * An object representing the Git config file.
40 * This can be either the repository specific file or the user global
41 * file depending on how it is instantiated.
43 public class RepositoryConfig
{
45 * Obtain a new configuration instance for ~/.gitconfig.
47 * @return a new configuration instance to read the user's global
48 * configuration file from their home directory.
50 public static RepositoryConfig
openUserConfig() {
51 return new RepositoryConfig(null, new File(System
52 .getProperty("user.home"), ".gitconfig"));
55 private final RepositoryConfig baseConfig
;
57 /** Section name for a remote configuration */
58 public static final String REMOTE_SECTION
= "remote";
60 /** Section name for a branch configuration. */
61 public static final String BRANCH_SECTION
= "branch";
63 private final File configFile
;
65 private boolean readFile
;
67 private CoreConfig core
;
69 private List
<Entry
> entries
;
71 private Map
<String
, Object
> byName
;
73 private Map
<String
, Entry
> lastInEntry
;
75 private Map
<String
, Entry
> lastInGroup
;
77 private static final String MAGIC_EMPTY_VALUE
= "%%magic%%empty%%";
79 RepositoryConfig(final Repository repo
) {
80 this(openUserConfig(), FS
.resolve(repo
.getDirectory(), "config"));
84 * Create a Git configuration file reader/writer/cache for a specific file.
87 * configuration that provides default values if this file does
88 * not set/override a particular key. Often this is the user's
89 * global configuration file, or the system level configuration.
91 * path of the file to load (or save).
93 public RepositoryConfig(final RepositoryConfig base
, final File cfgLocation
) {
95 configFile
= cfgLocation
;
100 * @return Core configuration values
102 public CoreConfig
getCore() {
107 * Obtain an integer value from the configuration.
110 * section the key is grouped within.
112 * name of the key to get.
113 * @param defaultValue
114 * default value to return if no value was present.
115 * @return an integer value from the configuration, or defaultValue.
117 public int getInt(final String section
, final String name
,
118 final int defaultValue
) {
119 return getInt(section
, null, name
, defaultValue
);
123 * Obtain an integer value from the configuration.
126 * section the key is grouped within.
128 * subsection name, such a remote or branch name.
130 * name of the key to get.
131 * @param defaultValue
132 * default value to return if no value was present.
133 * @return an integer value from the configuration, or defaultValue.
135 public int getInt(final String section
, String subsection
,
136 final String name
, final int defaultValue
) {
137 final String str
= getString(section
, subsection
, name
);
141 String n
= str
.trim();
146 switch (Character
.toLowerCase(n
.charAt(n
.length() - 1))) {
148 mul
= 1024 * 1024 * 1024;
158 n
= n
.substring(0, n
.length() - 1).trim();
163 return mul
* Integer
.parseInt(n
);
164 } catch (NumberFormatException nfe
) {
165 throw new IllegalArgumentException("Invalid integer value: "
166 + section
+ "." + name
+ "=" + str
);
171 * Get a boolean value from the git config
174 * section the key is grouped within.
176 * name of the key to get.
177 * @param defaultValue
178 * default value to return if no value was present.
179 * @return true if any value or defaultValue is true, false for missing or
182 protected boolean getBoolean(final String section
, final String name
,
183 final boolean defaultValue
) {
184 return getBoolean(section
, null, name
, defaultValue
);
188 * Get a boolean value from the git config
191 * section the key is grouped within.
193 * subsection name, such a remote or branch name.
195 * name of the key to get.
196 * @param defaultValue
197 * default value to return if no value was present.
198 * @return true if any value or defaultValue is true, false for missing or
201 protected boolean getBoolean(final String section
, String subsection
,
202 final String name
, final boolean defaultValue
) {
203 String n
= getRawString(section
, subsection
, name
);
208 if (MAGIC_EMPTY_VALUE
.equals(n
) || "yes".equals(n
) || "true".equals(n
) || "1".equals(n
)) {
210 } else if ("no".equals(n
) || "false".equals(n
) || "0".equals(n
)) {
213 throw new IllegalArgumentException("Invalid boolean value: "
214 + section
+ "." + name
+ "=" + n
);
222 * @return a String value from git config.
224 public String
getString(final String section
, String subsection
, final String name
) {
225 String val
= getRawString(section
, subsection
, name
);
226 if (MAGIC_EMPTY_VALUE
.equals(val
)) {
232 private String
getRawString(final String section
, final String subsection
,
237 } catch (FileNotFoundException err
) {
238 // Oh well. No sense in complaining about it.
240 } catch (IOException err
) {
241 err
.printStackTrace();
246 if (subsection
!= null)
247 ss
= "."+subsection
.toLowerCase();
251 o
= byName
.get(section
.toLowerCase() + ss
+ "." + name
.toLowerCase());
252 if (o
instanceof List
) {
253 return ((Entry
) ((List
) o
).get(0)).value
;
254 } else if (o
instanceof Entry
) {
255 return ((Entry
) o
).value
;
256 } else if (baseConfig
!= null)
257 return baseConfig
.getRawString(section
, subsection
, name
);
263 * Add or modify a configuration value. The parameters will result in a
264 * configuration entry like this.
267 * [section "subsection"]
272 * section name, e.g "branch"
274 * optional subsection value, e.g. a branch name
276 * parameter name, e.g. "filemode"
278 * parameter value, e.g. "true"
280 public void putString(final String section
, final String subsection
,
281 final String name
, final String value
) {
283 for (int i
= 0; i
< entries
.size(); ++i
) {
285 if (pe
.base
.equals(section
)
286 && (pe
.extendedBase
== null && subsection
== null || pe
.extendedBase
!= null
287 && pe
.extendedBase
.equals(subsection
)))
292 // TODO: This doesn't work in general, so we must revise this code
298 pe
.extendedBase
= subsection
;
302 Entry e
= new Entry();
306 e
.extendedBase
= subsection
;
313 * Create a new default config
315 public void create() {
327 e
.name
= "repositoryformatversion";
337 core
= new CoreConfig(this);
341 * Save config data to the git config file
343 * @throws IOException
345 public void save() throws IOException
{
346 final File tmp
= new File(configFile
.getParentFile(), configFile
349 final PrintWriter r
= new PrintWriter(new BufferedWriter(
350 new OutputStreamWriter(new FileOutputStream(tmp
),
351 Constants
.CHARACTER_ENCODING
)));
354 final Iterator
<Entry
> i
= entries
.iterator();
355 while (i
.hasNext()) {
356 final Entry e
= i
.next();
357 if (e
.prefix
!= null) {
360 if (e
.base
!= null && e
.name
== null) {
363 if (e
.extendedBase
!= null) {
366 r
.print(escapeValue(e
.extendedBase
));
370 } else if (e
.base
!= null && e
.name
!= null) {
371 if (e
.prefix
== null || "".equals(e
.prefix
)) {
375 if (e
.value
!= null) {
376 if (!MAGIC_EMPTY_VALUE
.equals(e
.value
)) {
378 r
.print(escapeValue(e
.value
));
381 if (e
.suffix
!= null) {
385 if (e
.suffix
!= null) {
393 if (!ok
|| !tmp
.renameTo(configFile
)) {
401 * Read the config file
402 * @throws IOException
404 public void load() throws IOException
{
407 final BufferedReader r
= new BufferedReader(new InputStreamReader(
408 new FileInputStream(configFile
), Constants
.CHARACTER_ENCODING
));
411 Entry e
= new Entry();
414 int input
= r
.read();
415 final char in
= (char) input
;
418 } else if ('\n' == in
) {
419 // End of this entry.
421 if (e
.base
!= null) {
425 } else if (e
.suffix
!= null) {
426 // Everything up until the end-of-line is in the suffix.
428 } else if (';' == in
|| '#' == in
) {
429 // The rest of this line is a comment; put into suffix.
430 e
.suffix
= String
.valueOf(in
);
431 } else if (e
.base
== null && Character
.isWhitespace(in
)) {
432 // Save the leading whitespace (if any).
433 if (e
.prefix
== null) {
437 } else if ('[' == in
) {
438 // This is a group header line.
439 e
.base
= readBase(r
);
442 e
.extendedBase
= readValue(r
, true, '"');
446 throw new IOException("Bad group header.");
449 } else if (last
!= null) {
452 e
.extendedBase
= last
.extendedBase
;
454 e
.name
= readName(r
);
455 if (e
.name
.endsWith("\n")) {
456 e
.name
= e
.name
.substring(0, e
.name
.length()-1);
457 e
.value
= MAGIC_EMPTY_VALUE
;
459 e
.value
= readValue(r
, false, -1);
461 throw new IOException("Invalid line in config file.");
468 core
= new CoreConfig(this);
471 private void clear() {
472 entries
= new ArrayList
<Entry
>();
473 byName
= new HashMap
<String
, Object
>();
474 lastInEntry
= new HashMap
<String
, Entry
>();
475 lastInGroup
= new HashMap
<String
, Entry
>();
478 @SuppressWarnings("unchecked")
479 private void add(final Entry e
) {
481 if (e
.base
!= null) {
482 final String b
= e
.base
.toLowerCase();
484 if (e
.extendedBase
!= null) {
485 group
= b
+ "." + e
.extendedBase
;
489 if (e
.name
!= null) {
490 final String n
= e
.name
.toLowerCase();
491 final String key
= group
+ "." + n
;
492 final Object o
= byName
.get(key
);
495 } else if (o
instanceof Entry
) {
496 final ArrayList
<Object
> l
= new ArrayList
<Object
>();
500 } else if (o
instanceof List
) {
501 ((List
<Entry
>) o
).add(e
);
503 lastInEntry
.put(key
, e
);
505 lastInGroup
.put(group
, e
);
509 private static String
escapeValue(final String x
) {
510 boolean inquote
= false;
512 final StringBuffer r
= new StringBuffer(x
.length());
513 for (int k
= 0; k
< x
.length(); k
++) {
514 final char c
= x
.charAt(k
);
522 lineStart
= r
.length();
544 r
.insert(lineStart
, '"');
551 if (!inquote
&& r
.length() > 0
552 && r
.charAt(r
.length() - 1) == ' ') {
553 r
.insert(lineStart
, '"');
570 private static String
readBase(final BufferedReader r
) throws IOException
{
571 final StringBuffer base
= new StringBuffer();
576 throw new IOException("Unexpected end of config file.");
577 } else if (']' == c
) {
580 } else if (' ' == c
|| '\t' == c
) {
585 throw new IOException("Unexpected end of config file.");
586 } else if ('"' == c
) {
589 } else if (' ' == c
|| '\t' == c
) {
592 throw new IOException("Bad base entry. : " + base
+ "," + c
);
596 } else if (Character
.isLetterOrDigit((char) c
) || '.' == c
|| '-' == c
) {
597 base
.append((char) c
);
599 throw new IOException("Bad base entry. : " + base
+ ", " + c
);
602 return base
.toString();
605 private static String
readName(final BufferedReader r
) throws IOException
{
606 final StringBuffer name
= new StringBuffer();
611 throw new IOException("Unexpected end of config file.");
612 } else if ('=' == c
) {
614 } else if (' ' == c
|| '\t' == c
) {
619 throw new IOException("Unexpected end of config file.");
620 } else if ('=' == c
) {
622 } else if (';' == c
|| '#' == c
|| '\n' == c
) {
625 } else if (' ' == c
|| '\t' == c
) {
628 throw new IOException("Bad entry delimiter.");
632 } else if (Character
.isLetterOrDigit((char) c
) || c
== '-') {
633 // From the git-config man page:
634 // The variable names are case-insensitive and only
635 // alphanumeric characters and - are allowed.
636 name
.append((char) c
);
637 } else if ('\n' == c
) {
639 name
.append((char) c
);
642 throw new IOException("Bad config entry name: " + name
+ (char) c
);
645 return name
.toString();
648 private static String
readValue(final BufferedReader r
, boolean quote
,
649 final int eol
) throws IOException
{
650 final StringBuffer value
= new StringBuffer();
651 boolean space
= false;
656 throw new IOException("Unexpected end of config file.");
660 throw new IOException("Newline in quotes not allowed.");
669 if (Character
.isWhitespace((char) c
)) {
673 if (';' == c
|| '#' == c
) {
679 if (value
.length() > 0) {
688 throw new IOException("End of file in escape.");
707 throw new IOException("Bad escape: " + ((char) c
));
714 value
.append((char) c
);
716 return value
.length() > 0 ? value
.toString() : null;
719 public String
toString() {
720 return "RepositoryConfig[" + configFile
.getPath() + "]";