1 from __future__
import division
, absolute_import
, unicode_literals
5 from os
.path
import join
9 from cola
import observable
10 from cola
.decorators
import memoize
11 from cola
.git
import STDOUT
12 from cola
.compat
import ustr
16 """Return a static GitConfig instance."""
19 _USER_CONFIG
= core
.expanduser(join('~', '.gitconfig'))
20 _USER_XDG_CONFIG
= core
.expanduser(
21 join(core
.getenv('XDG_CONFIG_HOME', join('~', '.config')),
25 # Try /etc/gitconfig as a fallback for the system config
26 paths
= (('system', '/etc/gitconfig'),
27 ('user', _USER_XDG_CONFIG
),
28 ('user', _USER_CONFIG
),
29 ('repo', git
.instance().git_path('config')))
31 for category
, path
in paths
:
33 statinfo
.append((category
, path
, core
.stat(path
).st_mtime
))
40 # Try /etc/gitconfig as a fallback for the system config
41 paths
= ('/etc/gitconfig',
44 git
.instance().git_path('config'))
48 mtimes
.append(core
.stat(path
).st_mtime
)
54 class GitConfig(observable
.Observable
):
55 """Encapsulate access to git-config values."""
57 message_user_config_changed
= 'user_config_changed'
58 message_repo_config_changed
= 'repo_config_changed'
61 observable
.Observable
.__init
__(self
)
62 self
.git
= git
.instance()
68 self
._cache
_key
= None
70 self
._config
_files
= {}
71 self
._value
_cache
= {}
73 self
._find
_config
_files
()
81 self
._cache
_key
= None
83 self
._config
_files
.clear()
84 self
._value
_cache
= {}
86 self
._find
_config
_files
()
89 return copy
.deepcopy(self
._user
)
92 return copy
.deepcopy(self
._repo
)
95 return copy
.deepcopy(self
._all
)
97 def _find_config_files(self
):
99 Classify git config files into 'system', 'user', and 'repo'.
101 Populates self._configs with a list of the files in
102 reverse-precedence order. self._config_files is populated with
103 {category: path} where category is one of 'system', 'user', or 'repo'.
106 # Try the git config in git's installation prefix
107 statinfo
= _stat_info()
108 self
._configs
= map(lambda x
: x
[1], statinfo
)
109 self
._config
_files
= {}
110 for (cat
, path
, mtime
) in statinfo
:
111 self
._config
_files
[cat
] = path
114 """Read config values from git."""
121 Return True when the cache matches.
123 Updates the cache and returns False when the cache does not match.
126 cache_key
= _cache_key()
127 if self
._cache
_key
is None or cache_key
!= self
._cache
_key
:
128 self
._cache
_key
= cache_key
132 def _read_configs(self
):
133 """Read git config value into the system, user and repo dicts."""
140 if 'system' in self
._config
_files
:
142 self
.read_config(self
._config
_files
['system']))
144 if 'user' in self
._config
_files
:
146 self
.read_config(self
._config
_files
['user']))
148 if 'repo' in self
._config
_files
:
150 self
.read_config(self
._config
_files
['repo']))
152 for dct
in (self
._system
, self
._user
, self
._repo
):
153 self
._all
.update(dct
)
155 def read_config(self
, path
):
156 """Return git config data from a path as a dictionary."""
158 args
= ('--null', '--file', path
, '--list')
159 config_lines
= self
.git
.config(*args
)[STDOUT
].split('\0')
160 for line
in config_lines
:
162 k
, v
= line
.split('\n', 1)
164 # the user has an invalid entry in their git config
170 if v
in ('true', 'yes'):
172 elif v
in ('false', 'no'):
179 self
._map
[k
.lower()] = k
183 def _get(self
, src
, key
, default
):
189 key
= self
._map
.get(key
.lower(), key
)
193 return src
.get(key
.lower(), default
)
195 def get(self
, key
, default
=None):
196 """Return the string value for a config key."""
197 return self
._get
(self
._all
, key
, default
)
199 def get_user(self
, key
, default
=None):
200 return self
._get
(self
._user
, key
, default
)
202 def get_repo(self
, key
, default
=None):
203 return self
._get
(self
._repo
, key
, default
)
205 def python_to_git(self
, value
):
206 if type(value
) is bool:
211 if type(value
) is int:
215 def set_user(self
, key
, value
):
216 msg
= self
.message_user_config_changed
217 self
.git
.config('--global', key
, self
.python_to_git(value
))
219 self
.notify_observers(msg
, key
, value
)
221 def set_repo(self
, key
, value
):
222 msg
= self
.message_repo_config_changed
223 self
.git
.config(key
, self
.python_to_git(value
))
225 self
.notify_observers(msg
, key
, value
)
229 match
= fnmatch
.fnmatch
232 for key
, val
in self
._all
.items():
237 def get_cached(self
, key
, default
=None):
238 cache
= self
._value
_cache
242 value
= cache
[key
] = self
.get(key
, default
=default
)
245 def gui_encoding(self
):
246 return self
.get_cached('gui.encoding', default
='utf-8')
248 def is_per_file_attrs_enabled(self
):
249 return self
.get_cached('cola.fileattributes', default
=False)
251 def file_encoding(self
, path
):
252 if not self
.is_per_file_attrs_enabled():
254 cache
= self
._attr
_cache
258 value
= cache
[path
] = self
._file
_encoding
(path
)
261 def _file_encoding(self
, path
):
262 """Return the file encoding for a path"""
263 status
, out
, err
= self
.git
.check_attr('encoding', '--', path
)
266 header
= '%s: encoding: ' % path
267 if out
.startswith(header
):
268 encoding
= out
[len(header
):].strip()
269 if (encoding
!= 'unspecified' and
270 encoding
!= 'unset' and
275 guitool_opts
= ('cmd', 'needsfile', 'noconsole', 'norescan', 'confirm',
276 'argprompt', 'revprompt', 'revunmerged', 'title', 'prompt')
278 def get_guitool_opts(self
, name
):
279 """Return the guitool.<name> namespace as a dict"""
280 keyprefix
= 'guitool.' + name
+ '.'
282 for cfg
in self
.guitool_opts
:
283 value
= self
.get(keyprefix
+ cfg
)
289 def get_guitool_names(self
):
290 guitools
= self
.find('guitool.*.cmd')
291 prefix
= len('guitool.')
293 return sorted([name
[prefix
:-suffix
]
294 for (name
, cmd
) in guitools
.items()])