3 from os
.path
import join
7 from cola
import observable
8 from cola
.decorators
import memoize
9 from cola
.git
import STDOUT
14 """Return a static GitConfig instance."""
17 _USER_CONFIG
= core
.expanduser(join('~', '.gitconfig'))
18 _USER_XDG_CONFIG
= core
.expanduser(
19 join(core
.getenv('XDG_CONFIG_HOME', join('~', '.config')),
23 # Try /etc/gitconfig as a fallback for the system config
24 paths
= (('system', '/etc/gitconfig'),
25 ('user', _USER_XDG_CONFIG
),
26 ('user', _USER_CONFIG
),
27 ('repo', git
.instance().git_path('config')))
29 for category
, path
in paths
:
31 statinfo
.append((category
, path
, core
.stat(path
).st_mtime
))
38 # Try /etc/gitconfig as a fallback for the system config
39 paths
= ('/etc/gitconfig',
42 git
.instance().git_path('config'))
46 mtimes
.append(core
.stat(path
).st_mtime
)
52 class GitConfig(observable
.Observable
):
53 """Encapsulate access to git-config values."""
55 message_user_config_changed
= 'user_config_changed'
56 message_repo_config_changed
= 'repo_config_changed'
59 observable
.Observable
.__init
__(self
)
60 self
.git
= git
.instance()
66 self
._cache
_key
= None
68 self
._config
_files
= {}
69 self
._value
_cache
= {}
71 self
._find
_config
_files
()
79 self
._cache
_key
= None
81 self
._config
_files
.clear()
82 self
._value
_cache
= {}
84 self
._find
_config
_files
()
87 return copy
.deepcopy(self
._user
)
90 return copy
.deepcopy(self
._repo
)
93 return copy
.deepcopy(self
._all
)
95 def _find_config_files(self
):
97 Classify git config files into 'system', 'user', and 'repo'.
99 Populates self._configs with a list of the files in
100 reverse-precedence order. self._config_files is populated with
101 {category: path} where category is one of 'system', 'user', or 'repo'.
104 # Try the git config in git's installation prefix
105 statinfo
= _stat_info()
106 self
._configs
= map(lambda x
: x
[1], statinfo
)
107 self
._config
_files
= {}
108 for (cat
, path
, mtime
) in statinfo
:
109 self
._config
_files
[cat
] = path
112 """Read config values from git."""
119 Return True when the cache matches.
121 Updates the cache and returns False when the cache does not match.
124 cache_key
= _cache_key()
125 if self
._cache
_key
is None or cache_key
!= self
._cache
_key
:
126 self
._cache
_key
= cache_key
130 def _read_configs(self
):
131 """Read git config value into the system, user and repo dicts."""
138 if 'system' in self
._config
_files
:
140 self
.read_config(self
._config
_files
['system']))
142 if 'user' in self
._config
_files
:
144 self
.read_config(self
._config
_files
['user']))
146 if 'repo' in self
._config
_files
:
148 self
.read_config(self
._config
_files
['repo']))
150 for dct
in (self
._system
, self
._user
, self
._repo
):
151 self
._all
.update(dct
)
153 def read_config(self
, path
):
154 """Return git config data from a path as a dictionary."""
156 args
= ('--null', '--file', path
, '--list')
157 config_lines
= self
.git
.config(*args
)[STDOUT
].split('\0')
158 for line
in config_lines
:
160 k
, v
= line
.split('\n', 1)
162 # the user has an invalid entry in their git config
168 if v
in ('true', 'yes'):
170 elif v
in ('false', 'no'):
177 self
._map
[k
.lower()] = k
181 def _get(self
, src
, key
, default
):
187 key
= self
._map
.get(key
.lower(), key
)
191 return src
.get(key
.lower(), default
)
193 def get(self
, key
, default
=None):
194 """Return the string value for a config key."""
195 return self
._get
(self
._all
, key
, default
)
197 def get_user(self
, key
, default
=None):
198 return self
._get
(self
._user
, key
, default
)
200 def get_repo(self
, key
, default
=None):
201 return self
._get
(self
._repo
, key
, default
)
203 def python_to_git(self
, value
):
204 if type(value
) is bool:
209 if type(value
) is int:
210 return unicode(value
)
213 def set_user(self
, key
, value
):
214 msg
= self
.message_user_config_changed
215 self
.git
.config('--global', key
, self
.python_to_git(value
))
217 self
.notify_observers(msg
, key
, value
)
219 def set_repo(self
, key
, value
):
220 msg
= self
.message_repo_config_changed
221 self
.git
.config(key
, self
.python_to_git(value
))
223 self
.notify_observers(msg
, key
, value
)
227 match
= fnmatch
.fnmatch
230 for key
, val
in self
._all
.items():
235 def get_cached(self
, key
, default
=None):
236 cache
= self
._value
_cache
240 value
= cache
[key
] = self
.get(key
, default
=default
)
243 def gui_encoding(self
):
244 return self
.get_cached('gui.encoding', default
='utf-8')
246 def is_per_file_attrs_enabled(self
):
247 return self
.get_cached('cola.fileattributes', default
=False)
249 def file_encoding(self
, path
):
250 if not self
.is_per_file_attrs_enabled():
252 cache
= self
._attr
_cache
256 value
= cache
[path
] = self
._file
_encoding
(path
)
259 def _file_encoding(self
, path
):
260 """Return the file encoding for a path"""
261 status
, out
, err
= self
.git
.check_attr('encoding', '--', path
)
264 header
= '%s: encoding: ' % path
265 if out
.startswith(header
):
266 encoding
= out
[len(header
):].strip()
267 if (encoding
!= 'unspecified' and
268 encoding
!= 'unset' and
273 guitool_opts
= ('cmd', 'needsfile', 'noconsole', 'norescan', 'confirm',
274 'argprompt', 'revprompt', 'revunmerged', 'title', 'prompt')
276 def get_guitool_opts(self
, name
):
277 """Return the guitool.<name> namespace as a dict"""
278 keyprefix
= 'guitool.' + name
+ '.'
280 for cfg
in self
.guitool_opts
:
281 value
= self
.get(keyprefix
+ cfg
)
287 def get_guitool_names(self
):
288 guitools
= self
.find('guitool.*.cmd')
289 prefix
= len('guitool.')
291 return sorted([name
[prefix
:-suffix
]
292 for (name
, cmd
) in guitools
.items()])