summaryrefslogtreecommitdiff
path: root/repolib.py
diff options
context:
space:
mode:
authormcrute <devnull@localhost>2011-02-24 00:11:42 -0500
committermcrute <devnull@localhost>2011-02-24 00:11:42 -0500
commit82aaff86a5aefbadc0e520949976ec11e312d2b6 (patch)
tree660a6a2ef9dabc1bb25959b048cde435a8f175c8 /repolib.py
downloadhg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.tar.bz2
hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.tar.xz
hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.zip
Initial import
Diffstat (limited to 'repolib.py')
-rw-r--r--repolib.py275
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"""
3Mercurial Repository Management Library
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Hosting
7@date: February 23, 2011
8"""
9
10import os.path
11import logging
12from StringIO import StringIO
13from ConfigParser import SafeConfigParser
14
15
16class 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
64class 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
172class 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
202class 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
263def 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