1
# config.py - Reading and writing Git config files
2
# Copyright (C) 2011 Jelmer Vernooij <jelmer@samba.org>
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; version 2
7
# of the License or (at your option) a later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
"""Reading and writing Git configuration files.
22
* preserve formatting when updating configuration files
23
* treat subsection names as case-insensitive for [branch.foo] style
31
from dulwich.file import GitFile
35
"""A Git configuration."""
37
def get(self, section, name):
38
"""Retrieve the contents of a configuration setting.
40
:param section: Tuple with section name and optional subsection namee
41
:param subsection: Subsection name
42
:return: Contents of the setting
43
:raise KeyError: if the value is not set
45
raise NotImplementedError(self.get)
47
def get_boolean(self, section, name, default=None):
48
"""Retrieve a configuration setting as boolean.
50
:param section: Tuple with section name and optional subsection namee
51
:param name: Name of the setting, including section and possible
53
:return: Contents of the setting
54
:raise KeyError: if the value is not set
57
value = self.get(section, name)
60
if value.lower() == "true":
62
elif value.lower() == "false":
64
raise ValueError("not a valid boolean string: %r" % value)
66
def set(self, section, name, value):
67
"""Set a configuration value.
69
:param name: Name of the configuration value, including section
70
and optional subsection
71
:param: Value of the setting
73
raise NotImplementedError(self.set)
76
class ConfigDict(Config):
77
"""Git configuration stored in a dictionary."""
79
def __init__(self, values=None):
80
"""Create a new ConfigDict."""
86
return "%s(%r)" % (self.__class__.__name__, self._values)
88
def __eq__(self, other):
90
isinstance(other, self.__class__) and
91
other._values == self._values)
94
def _parse_setting(cls, name):
95
parts = name.split(".")
97
return (parts[0], parts[1], parts[2])
99
return (parts[0], None, parts[1])
101
def get(self, section, name):
102
if isinstance(section, basestring):
103
section = (section, )
106
return self._values[section][name]
109
return self._values[(section[0],)][name]
111
def set(self, section, name, value):
112
if isinstance(section, basestring):
113
section = (section, )
114
self._values.setdefault(section, {})[name] = value
117
def _format_string(value):
118
if (value.startswith(" ") or
119
value.startswith("\t") or
120
value.endswith(" ") or
121
value.endswith("\t")):
122
return '"%s"' % _escape_value(value)
123
return _escape_value(value)
126
def _parse_string(value):
127
value = value.strip()
133
in_quotes = (not in_quotes)
134
ret.append(_unescape_value("".join(block)))
136
elif c in ("#", ";") and not in_quotes:
137
# the rest of the line is a comment
143
raise ValueError("value starts with quote but lacks end quote")
145
ret.append(_unescape_value("".join(block)).rstrip())
150
def _unescape_value(value):
151
"""Unescape a value."""
160
return re.sub(r"(\\.)", unescape, value)
163
def _escape_value(value):
164
"""Escape a value."""
165
return value.replace("\\", "\\\\").replace("\n", "\\n").replace("\t", "\\t").replace("\"", "\\\"")
168
def _check_variable_name(name):
170
if not c.isalnum() and c != '-':
175
def _check_section_name(name):
177
if not c.isalnum() and c not in ('-', '.'):
182
def _strip_comments(line):
183
line = line.split("#")[0]
184
line = line.split(";")[0]
188
class ConfigFile(ConfigDict):
189
"""A Git configuration file, like .git/config or ~/.gitconfig.
193
def from_file(cls, f):
194
"""Read configuration from a file-like object."""
198
for lineno, line in enumerate(f.readlines()):
201
if len(line) > 0 and line[0] == "[":
202
line = _strip_comments(line).rstrip()
203
last = line.index("]")
205
raise ValueError("expected trailing ]")
206
pts = line[1:last].split(" ", 1)
208
pts[0] = pts[0].lower()
210
if pts[1][0] != "\"" or pts[1][-1] != "\"":
212
"Invalid subsection " + pts[1])
214
pts[1] = pts[1][1:-1]
215
if not _check_section_name(pts[0]):
216
raise ValueError("invalid section name %s" %
218
section = (pts[0], pts[1])
220
if not _check_section_name(pts[0]):
221
raise ValueError("invalid section name %s" %
223
pts = pts[0].split(".", 1)
225
section = (pts[0], pts[1])
228
ret._values[section] = {}
229
if _strip_comments(line).strip() == "":
232
raise ValueError("setting %r without section" % line)
234
setting, value = line.split("=", 1)
238
setting = setting.strip().lower()
239
if not _check_variable_name(setting):
240
raise ValueError("invalid variable name %s" % setting)
241
if value.endswith("\\\n"):
246
value = _parse_string(value)
247
ret._values[section][setting] = value
250
else: # continuation line
251
if line.endswith("\\\n"):
256
value = _parse_string(line)
257
ret._values[section][setting] += value
263
def from_path(cls, path):
264
"""Read configuration from a file on disk."""
265
f = GitFile(path, 'rb')
267
ret = cls.from_file(f)
273
def write_to_path(self, path=None):
274
"""Write configuration to a file on disk."""
277
f = GitFile(path, 'wb')
279
self.write_to_file(f)
283
def write_to_file(self, f):
284
"""Write configuration to a file-like object."""
285
for section, values in self._values.iteritems():
287
section_name, subsection_name = section
289
(section_name, ) = section
290
subsection_name = None
291
if subsection_name is None:
292
f.write("[%s]\n" % section_name)
294
f.write("[%s \"%s\"]\n" % (section_name, subsection_name))
295
for key, value in values.iteritems():
296
f.write("%s = %s\n" % (key, _escape_value(value)))
299
class StackedConfig(Config):
300
"""Configuration which reads from multiple config files.."""
302
def __init__(self, backends, writable=None):
303
self.backends = backends
304
self.writable = writable
307
return "<%s for %r>" % (self.__class__.__name__, self.backends)
310
def default_backends(cls):
311
"""Retrieve the default configuration.
313
This will look in the repository configuration (if for_path is
314
specified), the users' home directory and the system
318
paths.append(os.path.expanduser("~/.gitconfig"))
319
paths.append("/etc/gitconfig")
323
cf = ConfigFile.from_path(path)
324
except (IOError, OSError), e:
325
if e.errno != errno.ENOENT:
332
def get(self, section, name):
333
for backend in self.backends:
335
return backend.get(section, name)
340
def set(self, section, name, value):
341
if self.writable is None:
342
raise NotImplementedError(self.set)
343
return self.writable.set(section, name, value)