7 from cola
import observable
8 from cola
.decorators
import memoize
13 """Return a static GitConfig instance."""
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')),
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'))))
28 for category
, path
in paths
:
30 statinfo
.append((category
, path
, os
.stat(path
).st_mtime
))
37 # Try /etc/gitconfig as a fallback for the system config
38 paths
= ('/etc/gitconfig',
41 git
.instance().git_path('config'))
45 mtimes
.append(os
.stat(path
).st_mtime
)
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'
58 observable
.Observable
.__init
__(self
)
59 self
.git
= git
.instance()
65 self
._cache
_key
= None
67 self
._config
_files
= {}
68 self
._value
_cache
= {}
70 self
._find
_config
_files
()
78 self
._cache
_key
= None
80 self
._config
_files
.clear()
81 self
._value
_cache
= {}
83 self
._find
_config
_files
()
86 return copy
.deepcopy(self
._user
)
89 return copy
.deepcopy(self
._repo
)
92 return copy
.deepcopy(self
._all
)
94 def _find_config_files(self
):
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
111 """Read config values from git."""
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
129 def _read_configs(self
):
130 """Read git config value into the system, user and repo dicts."""
137 if 'system' in self
._config
_files
:
139 self
.read_config(self
._config
_files
['system']))
141 if 'user' in self
._config
_files
:
143 self
.read_config(self
._config
_files
['user']))
145 if 'repo' in self
._config
_files
:
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."""
155 args
= ('--null', '--file', path
, '--list')
156 config_lines
= self
.git
.config(*args
).split('\0')
157 for line
in config_lines
:
159 k
, v
= line
.split('\n', 1)
161 # the user has an invalid entry in their git config
169 if v
in ('true', 'yes'):
171 elif v
in ('false', 'no'):
178 self
._map
[k
.lower()] = k
182 def _get(self
, src
, key
, default
):
188 key
= self
._map
.get(key
.lower(), key
)
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:
210 if type(value
) is int:
211 return unicode(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
))
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
))
224 self
.notify_observers(msg
, key
, value
)
228 match
= fnmatch
.fnmatch
231 for key
, val
in self
._all
.items():
236 def get_cached(self
, key
, default
=None):
237 cache
= self
._value
_cache
241 value
= cache
[key
] = self
.get(key
, default
=default
)
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():
253 cache
= self
._attr
_cache
257 value
= cache
[path
] = self
._file
_encoding
(path
)
260 def _file_encoding(self
, path
):
261 """Return the file encoding for a path"""
262 status
, out
= self
.git
.check_attr('encoding', '--', path
,
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
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
+ '.'
283 for cfg
in self
.guitool_opts
:
284 value
= self
.get(keyprefix
+ cfg
)
290 def get_guitool_names(self
):
291 guitools
= self
.find('guitool.*.cmd')
292 prefix
= len('guitool.')
294 return sorted([name
[prefix
:-suffix
]
295 for (name
, cmd
) in guitools
.items()])