gitcfg: Use st_mtime as the cache key
[git-cola.git] / cola / gitcfg.py
blobcfc7ed904123e640339ec8838f6f67d78bcc1073
1 import os
2 import sys
4 from cola import core
5 from cola import gitcmd
8 _config = None
9 def instance():
10 """Return a static GitConfig instance."""
11 global _config
12 if not _config:
13 _config = GitConfig()
14 return _config
17 def _find_in_path(app):
18 """Find a program in $PATH."""
19 is_win32 = sys.platform == 'win32'
20 for path in os.environ.get('PATH', '').split(os.pathsep):
21 candidate = os.path.join(path, app)
22 if os.path.exists(candidate):
23 return candidate
24 if is_win32:
25 candidate = os.path.join(path, app + '.exe')
26 if os.path.exists(candidate):
27 return candidate
28 return None
31 class GitConfig(object):
32 """Encapsulate access to git-config values."""
34 def __init__(self):
35 self.git = gitcmd.instance()
36 self._system = {}
37 self._user = {}
38 self._repo = {}
39 self._cache_key = None
41 self._configs = []
42 self._config_files = {}
43 self._find_config_files()
45 def reset(self):
46 self._system = {}
47 self._user = {}
48 self._repo = {}
49 self._configs = []
50 self._config_files = {}
51 self._find_config_files()
53 def _find_config_files(self):
54 """
55 Classify git config files into 'system', 'user', and 'repo'.
57 Populates self._configs with a list of the files in
58 reverse-precedence order. self._config_files is populated with
59 {category: path} where category is one of 'system', 'user', or 'repo'.
61 """
62 # Try the git config in git's installation prefix
63 git_path = _find_in_path('git')
64 if git_path:
65 bin_dir = os.path.dirname(git_path)
66 prefix = os.path.dirname(bin_dir)
67 system_config = os.path.join(prefix, 'etc')
68 if os.path.exists(system_config):
69 self._config_files['system'] = system_config
70 self._configs.append(system_config)
72 # Try /etc/gitconfig as a fallback for the system config
73 if 'system' not in self._config_files:
74 system_config = '/etc/gitconfig'
75 if os.path.exists(system_config):
76 self._config_files['system'] = system_config
77 self._configs.append(system_config)
79 # Check for the user config
80 user_config = os.path.expanduser('~/.gitconfig')
81 if os.path.exists(user_config):
82 self._config_files['user'] = user_config
83 self._configs.append(user_config)
85 # Check for the repo config
86 repo_config = self.git.git_path('config')
87 if os.path.exists(repo_config):
88 self._config_files['repo'] = repo_config
89 self._configs.append(repo_config)
91 def update(self):
92 """Read config values from git."""
93 cache_key = self._get_cache_key()
94 if cache_key == self._cache_key:
95 return
96 self._read_configs()
97 self._cache_key = cache_key
99 def _get_cache_key(self):
100 """Return a cache key used to avoiding re-reading git-config."""
101 return map(lambda x: os.stat(x).st_mtime, self._configs)
103 def _read_configs(self):
104 self._system = {}
105 self._user = {}
106 self._repo = {}
108 if 'system' in self._config_files:
109 self._system = self.read_config(self._config_files['system'])
111 if 'user' in self._config_files:
112 self._user = self.read_config(self._config_files['user'])
114 if 'repo' in self._config_files:
115 self._repo = self.read_config(self._config_files['repo'])
117 def read_config(self, path):
118 """Return git config data from a path as a dictionary."""
119 dest = {}
120 opts = {'f': path, 'z': True, 'list': True}
121 config_lines = self.git.config(**opts).split('\0')
122 for line in config_lines:
123 try:
124 k, v = line.split('\n')
125 except:
126 # the user has an invalid entry in their git config
127 continue
128 v = core.decode(v)
129 if v == 'true' or v == 'false':
130 v = bool(eval(v.title()))
131 try:
132 v = int(eval(v))
133 except:
134 pass
135 dest[k] = v
136 return dest
138 def get(self, key, default=None):
139 """Return the string value for a config key."""
140 self.update()
141 for dct in (self._repo, self._user, self._system):
142 if key in dct:
143 return dct[key]
144 return default