gitcfg: Use os.path.join() to construct the path to ~/.gitconfig
[git-cola.git] / cola / gitcfg.py
blob9a75527a192b3d3225b235337dc0452e4f646b24
1 import os
2 import sys
3 import copy
4 import fnmatch
6 from cola import core
7 from cola import git
8 from cola.decorators import memoize
11 @memoize
12 def instance():
13 """Return a static GitConfig instance."""
14 return GitConfig()
17 def _appendifexists(category, path, result):
18 try:
19 mtime = os.stat(path).st_mtime
20 result.append((category, path, mtime))
21 except OSError:
22 pass
25 def _stat_info():
26 data = []
27 # Try /etc/gitconfig as a fallback for the system config
28 userconfig = os.path.expanduser(os.path.join('~', '.gitconfig'))
29 _appendifexists('system', '/etc/gitconfig', data)
30 _appendifexists('user', userconfig, data)
31 _appendifexists('repo', git.instance().git_path('config'), data)
32 return data
35 class GitConfig(object):
36 """Encapsulate access to git-config values."""
38 def __init__(self):
39 self.git = git.instance()
40 self._system = {}
41 self._user = {}
42 self._repo = {}
43 self._all = {}
44 self._cache_key = None
45 self._configs = []
46 self._config_files = {}
47 self._find_config_files()
49 def reset(self):
50 self._system = {}
51 self._user = {}
52 self._repo = {}
53 self._all = {}
54 self._configs = []
55 self._config_files = {}
56 self._find_config_files()
58 def user(self):
59 return copy.deepcopy(self._user)
61 def repo(self):
62 return copy.deepcopy(self._repo)
64 def all(self):
65 return copy.deepcopy(self._all)
67 def _find_config_files(self):
68 """
69 Classify git config files into 'system', 'user', and 'repo'.
71 Populates self._configs with a list of the files in
72 reverse-precedence order. self._config_files is populated with
73 {category: path} where category is one of 'system', 'user', or 'repo'.
75 """
76 # Try the git config in git's installation prefix
77 statinfo = _stat_info()
78 self._configs = map(lambda x: x[1], statinfo)
79 self._config_files = {}
80 for (cat, path, mtime) in statinfo:
81 self._config_files[cat] = path
83 def update(self):
84 """Read config values from git."""
85 if self._cached():
86 return
87 self._read_configs()
89 def _cached(self):
90 """
91 Return True when the cache matches.
93 Updates the cache and returns False when the cache does not match.
95 """
96 cache_key = _stat_info()
97 if not self._cache_key or cache_key != self._cache_key:
98 self._cache_key = cache_key
99 return False
100 return True
102 def _read_configs(self):
103 """Read git config value into the system, user and repo dicts."""
104 self.reset()
106 if 'system' in self._config_files:
107 self._system = self.read_config(self._config_files['system'])
109 if 'user' in self._config_files:
110 self._user = self.read_config(self._config_files['user'])
112 if 'repo' in self._config_files:
113 self._repo = self.read_config(self._config_files['repo'])
115 self._all = {}
116 for dct in (self._system, self._user, self._repo):
117 self._all.update(dct)
119 def read_config(self, path):
120 """Return git config data from a path as a dictionary."""
121 dest = {}
122 args = ('--null', '--file', path, '--list')
123 config_lines = self.git.config(*args).split('\0')
124 for line in config_lines:
125 try:
126 k, v = line.split('\n')
127 except:
128 # the user has an invalid entry in their git config
129 continue
130 v = core.decode(v)
131 if v == 'yes':
132 v = 'true'
133 elif v == 'no':
134 v = 'false'
135 if v == 'true' or v == 'false':
136 v = bool(eval(v.title()))
137 try:
138 v = int(eval(v))
139 except:
140 pass
141 dest[k] = v
142 return dest
144 def get(self, key, default=None):
145 """Return the string value for a config key."""
146 self.update()
147 return self._all.get(key, default)
149 def find(self, pat):
150 result = {}
151 for key, val in self._all.items():
152 if fnmatch.fnmatch(key, pat):
153 result[key] = val
154 return result
156 def get_encoding(self, default='utf-8'):
157 return self.get('gui.encoding', default=default)
159 guitool_opts = ('cmd', 'needsfile', 'noconsole', 'norescan', 'confirm',
160 'argprompt', 'revprompt', 'revunmerged', 'title', 'prompt')
162 def get_guitool_opts(self, name):
163 """Return the guitool.<name> namespace as a dict"""
164 keyprefix = 'guitool.' + name + '.'
165 opts = {}
166 for cfg in self.guitool_opts:
167 value = self.get(keyprefix + cfg)
168 if value is None:
169 continue
170 opts[cfg] = value
171 return opts
173 def get_guitool_names(self):
174 cmds = []
175 guitools = self.find('guitool.*.cmd')
176 for name, cmd in guitools.items():
177 name = name[len('guitool.'):-len('.cmd')]
178 cmds.append(name)
179 return cmds