diff options
author | mcrute <devnull@localhost> | 2011-02-24 00:11:42 -0500 |
---|---|---|
committer | mcrute <devnull@localhost> | 2011-02-24 00:11:42 -0500 |
commit | 82aaff86a5aefbadc0e520949976ec11e312d2b6 (patch) | |
tree | 660a6a2ef9dabc1bb25959b048cde435a8f175c8 /repolib.py | |
download | hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.tar.bz2 hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.tar.xz hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.zip |
Initial import
Diffstat (limited to 'repolib.py')
-rw-r--r-- | repolib.py | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/repolib.py b/repolib.py new file mode 100644 index 0000000..1ba4814 --- /dev/null +++ b/repolib.py | |||
@@ -0,0 +1,275 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Mercurial Repository Management Library | ||
4 | |||
5 | @author: Mike Crute (mcrute@gmail.com) | ||
6 | @organization: SoftGroup Hosting | ||
7 | @date: February 23, 2011 | ||
8 | """ | ||
9 | |||
10 | import os.path | ||
11 | import logging | ||
12 | from StringIO import StringIO | ||
13 | from ConfigParser import SafeConfigParser | ||
14 | |||
15 | |||
16 | class Adornments(object): | ||
17 | |||
18 | BASE_CSS = ("padding: 0px 4px;" "font-size: 10px;" | ||
19 | "font-weight: normal;" "border: 1px solid;" | ||
20 | "background-color: #fafafa;" | ||
21 | "border-color: #ffffff #f0f0f0 #f0f0f0 #ffffff;") | ||
22 | |||
23 | CSS_YELLOW = ("background-color: #ffe500;" "color: #222222;" | ||
24 | "border-color: #fff066 #a89e43 #a89e43 #fff066;") | ||
25 | |||
26 | CSS_BROWN = ("background-color: #815555;" "color: #ffffff;" | ||
27 | "border-color: #ff3347 #541118 #541118 #ff3347;") | ||
28 | |||
29 | CSS_GREEN = ("background-color: #11a800;" "color: #ffffff;" | ||
30 | "border-color: #5eff4c #3ea832 #3ea832 #5eff4c;") | ||
31 | |||
32 | CSS_BLUE = ("background-color: #0099ff;" "color: #ffffff;" | ||
33 | "border-color: #4cb8ff #3279a8 #3279a8 #4cb8ff;") | ||
34 | |||
35 | CSS_RED = ("background-color: #ff0000;" "color: #ffffff;" | ||
36 | "border-color: #ff6666 #000000 #000000 #ff6666;") | ||
37 | |||
38 | BASE_HTML = '<span style="{0}{{css}}">{{tag}}</span> '.format(BASE_CSS) | ||
39 | LINK_CSS_DARK = 'color: #ffffff; text-decoration: none;' | ||
40 | LINK_CSS_LIGHT = 'color: #000000; text-decoration: none;' | ||
41 | |||
42 | def __init__(self, repo): | ||
43 | self.repo = repo | ||
44 | |||
45 | def __str__(self): | ||
46 | if self.repo.moved_to: | ||
47 | tag = '<a style="{link_css}" href="{url}">MOVED</a>' | ||
48 | tag = tag.format(link_css=self.LINK_CSS_DARK, | ||
49 | url=self.repo.moved_to) | ||
50 | return self.BASE_HTML.format(css=self.CSS_BLUE, tag=tag) | ||
51 | |||
52 | if self.repo.defunct: | ||
53 | return self.BASE_HTML.format(css=self.CSS_BROWN, tag="DEFUNCT") | ||
54 | |||
55 | if self.repo.upstream: | ||
56 | tag = '<a style="{link_css}" href="{url}">FORK</a>' | ||
57 | tag = tag.format(link_css=self.LINK_CSS_LIGHT, | ||
58 | url=self.repo.upstream) | ||
59 | return self.BASE_HTML.format(css=self.CSS_YELLOW, tag=tag) | ||
60 | |||
61 | return "" | ||
62 | |||
63 | |||
64 | class Repository(object): | ||
65 | |||
66 | def __init__(self, path, description=None, contact=None): | ||
67 | self.path = path | ||
68 | self.description = description | ||
69 | self.contact = contact | ||
70 | self.readers = [] | ||
71 | self.writers = [] | ||
72 | self.defunct = False | ||
73 | self.moved_to = None | ||
74 | self.repo_path = None | ||
75 | self.upstream = None | ||
76 | self.lock_script = None | ||
77 | |||
78 | def can_be_read_by(self, user): | ||
79 | return user in self.readers | ||
80 | |||
81 | def can_be_written_by(self, user): | ||
82 | return user in self.writers | ||
83 | |||
84 | @property | ||
85 | def exists(self): | ||
86 | return os.path.exists(self.full_path) | ||
87 | |||
88 | @property | ||
89 | def full_path(self): | ||
90 | return os.path.join(self.repo_path, self.path) | ||
91 | |||
92 | @property | ||
93 | def hgrc_path(self): | ||
94 | return os.path.join(self.full_path, '.hg', 'hgrc') | ||
95 | |||
96 | @classmethod | ||
97 | def from_config(cls, config, repo_path, lock_script=None): | ||
98 | self = cls(config['path'], config.get('description', None), | ||
99 | config.get('contact', None)) | ||
100 | |||
101 | self.readers = ConfigLoader.as_list(config.get('read', '')) | ||
102 | self.writers = ConfigLoader.as_list(config.get('write', '')) | ||
103 | |||
104 | self.defunct = ConfigLoader.as_bool(config.get('defunct', 'no')) | ||
105 | self.moved_to = config.get('moved_to', None) | ||
106 | self.upstream = config.get('upstream', None) | ||
107 | self.upstream = config.get('upstream', None) | ||
108 | |||
109 | self.repo_path = repo_path | ||
110 | self.lock_script = lock_script | ||
111 | |||
112 | return self | ||
113 | |||
114 | def build_hgrc(self, users): | ||
115 | buf = StringIO() | ||
116 | |||
117 | buf.write("# This file is generated by sync-repo-config\n") | ||
118 | buf.write("# Changes are over-written on each run\n\n") | ||
119 | |||
120 | if self.contact or self.description or self.writers: | ||
121 | buf.write("[web]\n") | ||
122 | |||
123 | if self.contact: | ||
124 | buf.write("contact = {0}\n".format(users[self.contact])) | ||
125 | |||
126 | if self.description: | ||
127 | buf.write("description = {adornments}" | ||
128 | " {self.description}\n".format( | ||
129 | adornments=Adornments(self), self=self)) | ||
130 | |||
131 | if self.writers: | ||
132 | buf.write("allow_push = {0}\n".format(",".join(self.writers))) | ||
133 | |||
134 | buf.write("\n") | ||
135 | |||
136 | if self.lock_script: | ||
137 | buf.write("[hooks]\n") | ||
138 | buf.write("pretxnchangegroup.deny.lock = {0}\n".format( | ||
139 | self.lock_script)) | ||
140 | buf.write("\n") | ||
141 | |||
142 | buf.write("[ssh]\n") | ||
143 | buf.write("readers = {0}\n".format(",".join(self.readers))) | ||
144 | buf.write("writers = {0}\n".format(",".join(self.writers))) | ||
145 | |||
146 | return buf.getvalue() | ||
147 | |||
148 | def write_hgrc(self, users): | ||
149 | if not self.exists: | ||
150 | raise IOError("Repository {0!r} does not exist".format( | ||
151 | self.full_path)) | ||
152 | |||
153 | with open(self.hgrc_path, 'w') as hgrc: | ||
154 | hgrc.write(self.build_hgrc(users)) | ||
155 | |||
156 | def load_hgrc(self): | ||
157 | hgrc = SafeConfigParser() | ||
158 | with open(self.hgrc_path, 'r') as fp: | ||
159 | hgrc.readfp(fp) | ||
160 | |||
161 | return hgrc | ||
162 | |||
163 | def load_from_hgrc(self): | ||
164 | hgrc = self.load_hgrc() | ||
165 | |||
166 | self.description = hgrc.get("web", "description") | ||
167 | self.contact = hgrc.get("web", "contact") | ||
168 | self.readers = ConfigLoader.as_list(hgrc.get("ssh", "readers")) | ||
169 | self.writers = ConfigLoader.as_list(hgrc.get("ssh", "writers")) | ||
170 | |||
171 | |||
172 | class User(object): | ||
173 | |||
174 | def __init__(self, username, name, email): | ||
175 | self.username = username | ||
176 | self.name = name | ||
177 | self.email = email | ||
178 | self.can_create = False | ||
179 | self.ssh_key = None | ||
180 | self.login_script = None | ||
181 | |||
182 | def __str__(self): | ||
183 | return "{self.name} <{self.email}>".format(self=self) | ||
184 | |||
185 | @classmethod | ||
186 | def from_config(cls, config, login_script): | ||
187 | self = cls(config['username'], config['name'], config['email']) | ||
188 | |||
189 | self.can_create = ConfigLoader.as_bool(config['can_create']) | ||
190 | self.ssh_key = config['ssh_key'] | ||
191 | self.login_script = login_script | ||
192 | |||
193 | return self | ||
194 | |||
195 | @property | ||
196 | def ssh_line(self): | ||
197 | return ('command="{self.login_script} {self.username}",' | ||
198 | 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding' | ||
199 | ' {self.ssh_key} {self.email}\n').format(self=self) | ||
200 | |||
201 | |||
202 | class ConfigLoader(object): | ||
203 | |||
204 | def __init__(self, cfg_file): | ||
205 | self.config = SafeConfigParser() | ||
206 | with open(cfg_file, 'r') as config_file: | ||
207 | self.config.readfp(config_file) | ||
208 | |||
209 | self.login_script = self.config.get('system', 'login_script') | ||
210 | self.repo_path = self.config.get('system', 'repo_path') | ||
211 | self.repo_user = self.config.get('system', 'repo_user') | ||
212 | self.lock_script = self.config.get('system', 'lock_script') | ||
213 | |||
214 | def _filtered_sections(self, section_name): | ||
215 | for section in self.config.sections(): | ||
216 | if section.startswith(section_name + ":"): | ||
217 | yield section | ||
218 | |||
219 | @staticmethod | ||
220 | def as_bool(value): | ||
221 | return value.lower() in ('yes', 'true', 'on') | ||
222 | |||
223 | @staticmethod | ||
224 | def as_list(value): | ||
225 | return [item.strip() for item in value.split(",") | ||
226 | if value] | ||
227 | |||
228 | @staticmethod | ||
229 | def clean_section_name(name): | ||
230 | return name.split(':')[1] | ||
231 | |||
232 | @property | ||
233 | def repos(self): | ||
234 | for section in self._filtered_sections('repo'): | ||
235 | values = dict(self.config.items(section)) | ||
236 | values['path'] = self.clean_section_name(section) | ||
237 | |||
238 | yield Repository.from_config(values, self.repo_path, | ||
239 | self.lock_script) | ||
240 | |||
241 | @property | ||
242 | def users(self): | ||
243 | for section in self._filtered_sections('user'): | ||
244 | values = dict(self.config.items(section)) | ||
245 | values['username'] = self.clean_section_name(section) | ||
246 | |||
247 | yield User.from_config(values, self.login_script) | ||
248 | |||
249 | @property | ||
250 | def user_dict(self): | ||
251 | return dict((user.username, user) for user in self.users) | ||
252 | |||
253 | @property | ||
254 | def repo_user_authorized_keys(self): | ||
255 | home_dir = os.path.expanduser('~' + self.repo_user) | ||
256 | |||
257 | if '~' in home_dir: | ||
258 | raise ValueError("User {0!r} doesn't exist".format(self.repo_user)) | ||
259 | |||
260 | return os.path.join(home_dir, '.ssh', 'authorized_keys') | ||
261 | |||
262 | |||
263 | def get_logger(name): | ||
264 | format = "%(levelname)s: %(message)s" | ||
265 | logging.basicConfig(level=logging.DEBUG, format=format, | ||
266 | filename="scripts.log") | ||
267 | |||
268 | stream = logging.StreamHandler() | ||
269 | stream.setLevel(logging.WARNING) | ||
270 | stream.setFormatter(logging.Formatter(format)) | ||
271 | |||
272 | logger = logging.getLogger(name) | ||
273 | logger.addHandler(stream) | ||
274 | |||
275 | return logger | ||