i18n: update git-cola.pot for v1.8.5
[git-cola.git] / cola / gitcfg.py
blobab38900257d290c89daec0c26d63faed90beb402
1 import os
2 import copy
3 import fnmatch
5 from cola import core
6 from cola import git
7 from cola import observable
8 from cola.decorators import memoize
11 @memoize
12 def instance():
13 """Return a static GitConfig instance."""
14 return GitConfig()
16 _USER_CONFIG = os.path.expanduser(os.path.join('~', '.gitconfig'))
17 _USER_XDG_CONFIG = os.path.expanduser(os.path.join(
18 os.environ.get('XDG_CONFIG_HOME', os.path.join('~', '.config')),
19 'git', 'config'))
21 def _stat_info():
22 # Try /etc/gitconfig as a fallback for the system config
23 paths = (('system', '/etc/gitconfig'),
24 ('user', core.decode(_USER_XDG_CONFIG)),
25 ('user', core.decode(_USER_CONFIG)),
26 ('repo', core.decode(git.instance().git_path('config'))))
27 statinfo = []
28 for category, path in paths:
29 try:
30 statinfo.append((category, path, os.stat(path).st_mtime))
31 except OSError:
32 continue
33 return statinfo
36 def _cache_key():
37 # Try /etc/gitconfig as a fallback for the system config
38 paths = ('/etc/gitconfig',
39 _USER_XDG_CONFIG,
40 _USER_CONFIG,
41 git.instance().git_path('config'))
42 mtimes = []
43 for path in paths:
44 try:
45 mtimes.append(os.stat(path).st_mtime)
46 except OSError:
47 continue
48 return mtimes
51 class GitConfig(observable.Observable):
52 """Encapsulate access to git-config values."""
54 message_user_config_changed = 'user_config_changed'
55 message_repo_config_changed = 'repo_config_changed'
57 def __init__(self):
58 observable.Observable.__init__(self)
59 self.git = git.instance()
60 self._map = {}
61 self._system = {}
62 self._user = {}
63 self._repo = {}
64 self._all = {}
65 self._cache_key = None
66 self._configs = []
67 self._config_files = {}
68 self._value_cache = {}
69 self._attr_cache = {}
70 self._find_config_files()
72 def reset(self):
73 self._map.clear()
74 self._system.clear()
75 self._user.clear()
76 self._repo.clear()
77 self._all.clear()
78 self._cache_key = None
79 self._configs = []
80 self._config_files.clear()
81 self._value_cache = {}
82 self._attr_cache = {}
83 self._find_config_files()
85 def user(self):
86 return copy.deepcopy(self._user)
88 def repo(self):
89 return copy.deepcopy(self._repo)
91 def all(self):
92 return copy.deepcopy(self._all)
94 def _find_config_files(self):
95 """
96 Classify git config files into 'system', 'user', and 'repo'.
98 Populates self._configs with a list of the files in
99 reverse-precedence order. self._config_files is populated with
100 {category: path} where category is one of 'system', 'user', or 'repo'.
103 # Try the git config in git's installation prefix
104 statinfo = _stat_info()
105 self._configs = map(lambda x: x[1], statinfo)
106 self._config_files = {}
107 for (cat, path, mtime) in statinfo:
108 self._config_files[cat] = path
110 def update(self):
111 """Read config values from git."""
112 if self._cached():
113 return
114 self._read_configs()
116 def _cached(self):
118 Return True when the cache matches.
120 Updates the cache and returns False when the cache does not match.
123 cache_key = _cache_key()
124 if self._cache_key is None or cache_key != self._cache_key:
125 self._cache_key = cache_key
126 return False
127 return True
129 def _read_configs(self):
130 """Read git config value into the system, user and repo dicts."""
131 self._map.clear()
132 self._system.clear()
133 self._user.clear()
134 self._repo.clear()
135 self._all.clear()
137 if 'system' in self._config_files:
138 self._system.update(
139 self.read_config(self._config_files['system']))
141 if 'user' in self._config_files:
142 self._user.update(
143 self.read_config(self._config_files['user']))
145 if 'repo' in self._config_files:
146 self._repo.update(
147 self.read_config(self._config_files['repo']))
149 for dct in (self._system, self._user, self._repo):
150 self._all.update(dct)
152 def read_config(self, path):
153 """Return git config data from a path as a dictionary."""
154 dest = {}
155 args = ('--null', '--file', path, '--list')
156 config_lines = self.git.config(*args).split('\0')
157 for line in config_lines:
158 try:
159 k, v = line.split('\n', 1)
160 except ValueError:
161 # the user has an invalid entry in their git config
162 if not line:
163 continue
164 k = line
165 v = 'true'
166 k = core.decode(k)
167 v = core.decode(v)
169 if v in ('true', 'yes'):
170 v = True
171 elif v in ('false', 'no'):
172 v = False
173 else:
174 try:
175 v = int(v)
176 except ValueError:
177 pass
178 self._map[k.lower()] = k
179 dest[k] = v
180 return dest
182 def _get(self, src, key, default):
183 self.update()
184 try:
185 return src[key]
186 except KeyError:
187 pass
188 key = self._map.get(key.lower(), key)
189 try:
190 return src[key]
191 except KeyError:
192 return src.get(key.lower(), default)
194 def get(self, key, default=None):
195 """Return the string value for a config key."""
196 return self._get(self._all, key, default)
198 def get_user(self, key, default=None):
199 return self._get(self._user, key, default)
201 def get_repo(self, key, default=None):
202 return self._get(self._repo, key, default)
204 def python_to_git(self, value):
205 if type(value) is bool:
206 if value:
207 return 'true'
208 else:
209 return 'false'
210 if type(value) is int:
211 return unicode(value)
212 return value
214 def set_user(self, key, value):
215 msg = self.message_user_config_changed
216 self.git.config('--global', key, self.python_to_git(value))
217 self.update()
218 self.notify_observers(msg, key, value)
220 def set_repo(self, key, value):
221 msg = self.message_repo_config_changed
222 self.git.config(key, self.python_to_git(value))
223 self.update()
224 self.notify_observers(msg, key, value)
226 def find(self, pat):
227 pat = pat.lower()
228 match = fnmatch.fnmatch
229 result = {}
230 self.update()
231 for key, val in self._all.items():
232 if match(key, pat):
233 result[key] = val
234 return result
236 def get_cached(self, key, default=None):
237 cache = self._value_cache
238 try:
239 value = cache[key]
240 except KeyError:
241 value = cache[key] = self.get(key, default=default)
242 return value
244 def gui_encoding(self):
245 return self.get_cached('gui.encoding', default='utf-8')
247 def is_per_file_attrs_enabled(self):
248 return self.get_cached('cola.fileattributes', default=False)
250 def file_encoding(self, path):
251 if not self.is_per_file_attrs_enabled():
252 return None
253 cache = self._attr_cache
254 try:
255 value = cache[path]
256 except KeyError:
257 value = cache[path] = self._file_encoding(path)
258 return value
260 def _file_encoding(self, path):
261 """Return the file encoding for a path"""
262 status, out = self.git.check_attr('encoding', '--', path,
263 with_status=True)
264 if status != 0:
265 return None
266 out = core.decode(out)
267 header = '%s: encoding: ' % path
268 if out.startswith(header):
269 encoding = out[len(header):].strip()
270 if (encoding != 'unspecified' and
271 encoding != 'unset' and
272 encoding != 'set'):
273 return encoding
274 return None
276 guitool_opts = ('cmd', 'needsfile', 'noconsole', 'norescan', 'confirm',
277 'argprompt', 'revprompt', 'revunmerged', 'title', 'prompt')
279 def get_guitool_opts(self, name):
280 """Return the guitool.<name> namespace as a dict"""
281 keyprefix = 'guitool.' + name + '.'
282 opts = {}
283 for cfg in self.guitool_opts:
284 value = self.get(keyprefix + cfg)
285 if value is None:
286 continue
287 opts[cfg] = value
288 return opts
290 def get_guitool_names(self):
291 guitools = self.find('guitool.*.cmd')
292 prefix = len('guitool.')
293 suffix = len('.cmd')
294 return sorted([name[prefix:-suffix]
295 for (name, cmd) in guitools.items()])