Merge lp:~rainct/merge-o-matic/redesign into lp:merge-o-matic

Proposed by Siegfried Gevatter
Status: Needs review
Proposed branch: lp:~rainct/merge-o-matic/redesign
Merge into: lp:merge-o-matic
Diff against target: None lines
To merge this branch: bzr merge lp:~rainct/merge-o-matic/redesign
Reviewer Review Type Date Requested Status
Scott James Remnant (Canonical) Pending
Review via email: mp+9075@code.launchpad.net

Commit message

- Move the HTML out of the Python files, make use of a very simple templating system (tinpy) instead.
- Give the page a real design which uses less space, looks more pleasant and where the comments input box is actually visible.
- Move some of the code out from merge-status.py and manual-status.py to a new file shared between both.
- Update the code to use the hashlib module for md5, suppressing a deprecation warning.
- Remove some unneeded imports, and shebangs where they are unnecessary.

To post a comment you must log in.

Unmerged revisions

169. By Siegfried Gevatter

Couple of bug fixes, supress the warning about the md5 module being deprecated by switching to hashlib, move stuff from merge-status.py and manual-status.py into a new status_common.py and other changes..

168. By Siegfried Gevatter

Remove unneeded imports.

167. By Siegfried Gevatter

Display the uploader, if the upload was sponsored.

166. By Siegfried Gevatter

Replace < and > with &lt; and &gt; in the Last Uploader name, before passing it to status.py, so that the e-mail address is displayed.

165. By Siegfried Gevatter

Use user-readable varialbe names in the for loop in status.tpl instead of accessing the package details with their numeric index.

164. By Siegfried Gevatter

More progress...

163. By Siegfried Gevatter

Start putting in proper variables.

162. By Siegfried Gevatter

Revert momlib changes.

161. By Siegfried Gevatter

merge

160. By Siegfried Gevatter

merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'images'
=== added file 'images/header.png'
0Binary files images/header.png 1970-01-01 00:00:00 +0000 and images/header.png 2008-03-28 22:21:54 +0000 differ0Binary files images/header.png 1970-01-01 00:00:00 +0000 and images/header.png 2008-03-28 22:21:54 +0000 differ
=== added file 'images/header_bg.png'
1Binary files images/header_bg.png 1970-01-01 00:00:00 +0000 and images/header_bg.png 2008-03-28 22:21:54 +0000 differ1Binary files images/header_bg.png 1970-01-01 00:00:00 +0000 and images/header_bg.png 2008-03-28 22:21:54 +0000 differ
=== modified file 'manual-status.py'
--- manual-status.py 2009-03-02 15:33:48 +0000
+++ manual-status.py 2009-07-20 18:07:18 +0000
@@ -5,6 +5,8 @@
5# Copyright © 2008 Canonical Ltd.5# Copyright © 2008 Canonical Ltd.
6# Author: Scott James Remnant <scott@ubuntu.com>.6# Author: Scott James Remnant <scott@ubuntu.com>.
7#7#
8# Copyright © 2008-2009 Siegfried-A. Gevatter Pujals <rainct@ubuntu.com>
9#
8# This program is free software: you can redistribute it and/or modify10# This program is free software: you can redistribute it and/or modify
9# it under the terms of version 3 of the GNU General Public License as11# it under the terms of version 3 of the GNU General Public License as
10# published by the Free Software Foundation.12# published by the Free Software Foundation.
@@ -19,39 +21,11 @@
1921
20import os22import os
21import bz223import bz2
22import fcntl
23
24from rfc822 import parseaddr24from rfc822 import parseaddr
25
25from momlib import *26from momlib import *
2627from util.status_common import *
2728
28# Order of priorities
29PRIORITY = [ "unknown", "required", "important", "standard", "optional",
30 "extra" ]
31COLOURS = [ "#ff8080", "#ffb580", "#ffea80", "#dfff80", "#abff80", "#80ff8b" ]
32
33# Sections
34SECTIONS = [ "new", "updated" ]
35
36
37def options(parser):
38 parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO",
39 default=SRC_DISTRO,
40 help="Source distribution")
41 parser.add_option("-S", "--source-suite", type="string", metavar="SUITE",
42 default=SRC_DIST,
43 help="Source suite (aka distrorelease)")
44
45 parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO",
46 default=OUR_DISTRO,
47 help="Destination distribution")
48 parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE",
49 default=OUR_DIST,
50 help="Destination suite (aka distrorelease)")
51
52 parser.add_option("-c", "--component", type="string", metavar="COMPONENT",
53 action="append",
54 help="Process only these destination components")
5529
56def main(options, args):30def main(options, args):
57 src_distro = options.source_distro31 src_distro = options.source_distro
@@ -60,6 +34,8 @@
60 our_distro = options.dest_distro34 our_distro = options.dest_distro
61 our_dist = options.dest_suite35 our_dist = options.dest_suite
6236
37 SECTIONS.remove("outstanding")
38
63 # For each package in the destination distribution, find out whether39 # For each package in the destination distribution, find out whether
64 # there's an open merge, and if so add an entry to the table for it.40 # there's an open merge, and if so add an entry to the table for it.
65 for our_component in DISTROS[our_distro]["components"]:41 for our_component in DISTROS[our_distro]["components"]:
@@ -69,10 +45,10 @@
6945
70 merges = []46 merges = []
7147
72 for our_source in get_sources(our_distro, our_dist, our_component):48 for source in get_sources(our_distro, our_dist, our_component):
73 try:49 try:
74 package = our_source["Package"]50 package = source["Package"]
75 our_version = Version(our_source["Version"])51 our_version = Version(source["Version"])
76 our_pool_source = get_pool_source(our_distro, package,52 our_pool_source = get_pool_source(our_distro, package,
77 our_version)53 our_version)
78 logging.debug("%s: %s is %s", package, our_distro, our_version)54 logging.debug("%s: %s is %s", package, our_distro, our_version)
@@ -97,11 +73,11 @@
97 pass73 pass
9874
99 try:75 try:
100 priority_idx = PRIORITY.index(our_source["Priority"])76 priority_idx = PRIORITY.index(source["Priority"])
101 except KeyError:77 except KeyError:
102 priority_idx = 078 priority_idx = 0
10379
104 filename = changes_file(our_distro, our_source)80 filename = changes_file(our_distro, source)
105 if os.path.isfile(filename):81 if os.path.isfile(filename):
106 changes = open(filename)82 changes = open(filename)
107 elif os.path.isfile(filename + ".bz2"):83 elif os.path.isfile(filename + ".bz2"):
@@ -119,7 +95,7 @@
119 user = None95 user = None
120 uploaded = False96 uploaded = False
12197
122 uploader = get_uploader(our_distro, our_source)98 uploader = get_uploader(our_distro, source)
12399
124 if uploaded:100 if uploaded:
125 section = "updated"101 section = "updated"
@@ -127,177 +103,15 @@
127 section = "new"103 section = "new"
128104
129 merges.append((section, priority_idx, package, user, uploader,105 merges.append((section, priority_idx, package, user, uploader,
130 our_source, our_version, src_version))106 source, our_version, src_version))
131107
132 write_status_page(our_component, merges, our_distro, src_distro)108 write_status_page(our_component, merges, our_distro,
109 src_distro, True)
133110
134 status_file = "%s/merges/tomerge-%s-manual" % (ROOT, our_component)111 status_file = "%s/merges/tomerge-%s-manual" % (ROOT, our_component)
135 remove_old_comments(status_file, merges)112 remove_old_comments(status_file, merges)
136 write_status_file(status_file, merges)113 write_status_file(status_file, merges)
137114
138
139def write_status_page(component, merges, left_distro, right_distro):
140 """Write out the manual merge status page."""
141 merges.sort()
142
143 status_file = "%s/merges/%s-manual.html" % (ROOT, component)
144 status = open(status_file + ".new", "w")
145 try:
146 print >>status, "<html>"
147 print >>status
148 print >>status, "<head>"
149 print >>status, "<title>Ubuntu Merge-o-Matic: %s manual</title>" \
150 % component
151 print >>status, "<style>"
152 print >>status, "img#ubuntu {"
153 print >>status, " border: 0;"
154 print >>status, "}"
155 print >>status, "h1 {"
156 print >>status, " padding-top: 0.5em;"
157 print >>status, " font-family: sans-serif;"
158 print >>status, " font-size: 2.0em;"
159 print >>status, " font-weight: bold;"
160 print >>status, "}"
161 print >>status, "h2 {"
162 print >>status, " padding-top: 0.5em;"
163 print >>status, " font-family: sans-serif;"
164 print >>status, " font-size: 1.5em;"
165 print >>status, " font-weight: bold;"
166 print >>status, "}"
167 print >>status, "p, td {"
168 print >>status, " font-family: sans-serif;"
169 print >>status, " margin-bottom: 0;"
170 print >>status, "}"
171 print >>status, "li {"
172 print >>status, " font-family: sans-serif;"
173 print >>status, " margin-bottom: 1em;"
174 print >>status, "}"
175 print >>status, "tr.first td {"
176 print >>status, " border-top: 2px solid white;"
177 print >>status, "}"
178 print >>status, "</style>"
179 print >>status, "<% from momlib import * %>"
180 print >>status, "</head>"
181 print >>status, "<body>"
182 print >>status, "<img src=\".img/ubuntulogo-100.png\" id=\"ubuntu\">"
183 print >>status, "<h1>Ubuntu Merge-o-Matic: %s manual</h1>" % component
184
185 for section in SECTIONS:
186 section_merges = [ m for m in merges if m[0] == section ]
187 print >>status, ("<p><a href=\"#%s\">%s %s merges</a></p>"
188 % (section, len(section_merges), section))
189
190 print >>status, "<% comment = get_comments() %>"
191
192 for section in SECTIONS:
193 section_merges = [ m for m in merges if m[0] == section ]
194
195 print >>status, ("<h2 id=\"%s\">%s Merges</h2>"
196 % (section, section.title()))
197
198 do_table(status, section_merges, left_distro, right_distro, component)
199
200 print >>status, "</body>"
201 print >>status, "</html>"
202 finally:
203 status.close()
204
205 os.rename(status_file + ".new", status_file)
206
207def get_uploader(distro, source):
208 """Obtain the uploader from the dsc file signature."""
209 for md5sum, size, name in files(source):
210 if name.endswith(".dsc"):
211 dsc_file = name
212 break
213 else:
214 return None
215
216 filename = "%s/pool/%s/%s/%s/%s" \
217 % (ROOT, distro, pathhash(source["Package"]), source["Package"],
218 dsc_file)
219
220 (a, b, c) = os.popen3("gpg --verify %s" % filename)
221 stdout = c.readlines()
222 try:
223 return stdout[1].split("Good signature from")[1].strip().strip("\"")
224 except IndexError:
225 return None
226
227def do_table(status, merges, left_distro, right_distro, component):
228 """Output a table."""
229 print >>status, "<table cellspacing=0>"
230 print >>status, "<tr bgcolor=#d0d0d0>"
231 print >>status, "<td rowspan=2><b>Package</b></td>"
232 print >>status, "<td colspan=2><b>Last Uploader</b></td>"
233 print >>status, "<td rowspan=2><b>Comment</b></td>"
234 print >>status, "<td rowspan=2><b>Bug</b></td>"
235 print >>status, "</tr>"
236 print >>status, "<tr bgcolor=#d0d0d0>"
237 print >>status, "<td><b>%s Version</b></td>" % left_distro.title()
238 print >>status, "<td><b>%s Version</b></td>" % right_distro.title()
239 print >>status, "</tr>"
240
241 for uploaded, priority, package, user, uploader, source, \
242 left_version, right_version in merges:
243 if user is not None:
244 who = user
245 who = who.replace("&", "&amp;")
246 who = who.replace("<", "&lt;")
247 who = who.replace(">", "&gt;")
248
249 if uploader is not None:
250 (usr_name, usr_mail) = parseaddr(user)
251 (upl_name, upl_mail) = parseaddr(uploader)
252
253 if len(usr_name) and usr_name != upl_name:
254 u_who = uploader
255 u_who = u_who.replace("&", "&amp;")
256 u_who = u_who.replace("<", "&lt;")
257 u_who = u_who.replace(">", "&gt;")
258
259 who = "%s<br><small><em>Uploader:</em> %s</small>" \
260 % (who, u_who)
261 else:
262 who = "&nbsp;"
263
264 print >>status, "<tr bgcolor=%s class=first>" % COLOURS[priority]
265 print >>status, "<td><tt><a href=\"https://patches.ubuntu.com/" \
266 "%s/%s/%s_%s.patch\">%s</a></tt>" \
267 % (pathhash(package), package, package, left_version, package)
268 print >>status, " <sup><a href=\"https://launchpad.net/ubuntu/" \
269 "+source/%s\">LP</a></sup>" % package
270 print >>status, " <sup><a href=\"http://packages.qa.debian.org/" \
271 "%s\">PTS</a></sup></td>" % package
272 print >>status, "<td colspan=2>%s</td>" % who
273 print >>status, "<td rowspan=2><form method=\"get\" action=\"addcomment.py\"><br />"
274 print >>status, "<input type=\"hidden\" name=\"component\" value=\"%s\" />" % component
275 print >>status, "<input type=\"hidden\" name=\"package\" value=\"%s\" />" % package
276 print >>status, "<%%\n\
277the_comment = \"\"\n\
278if(comment.has_key(\"%s\")):\n\
279 the_comment = comment[\"%s\"]\n\
280req.write(\"<input type=\\\"text\\\" style=\\\"border-style: none; background-color: %s\\\" name=\\\"comment\\\" value=\\\"%%s\\\" title=\\\"%%s\\\" />\" %% (the_comment, the_comment))\n\
281%%>" % (package, package, COLOURS[priority])
282 print >>status, "</form></td>"
283 print >>status, "<td rowspan=2>"
284 print >>status, "<%%\n\
285if(comment.has_key(\"%s\")):\n\
286 req.write(\"%%s\" %% gen_buglink_from_comment(comment[\"%s\"]))\n\
287else:\n\
288 req.write(\"&nbsp;\")\n\
289\n\
290%%>" % (package, package)
291 print >>status, "</td>"
292 print >>status, "</tr>"
293 print >>status, "<tr bgcolor=%s>" % COLOURS[priority]
294 print >>status, "<td><small>%s</small></td>" % source["Binary"]
295 print >>status, "<td>%s</td>" % left_version
296 print >>status, "<td>%s</td>" % right_version
297 print >>status, "</tr>"
298
299 print >>status, "</table>"
300
301def write_status_file(status_file, merges):115def write_status_file(status_file, merges):
302 """Write out the merge status file."""116 """Write out the merge status file."""
303 status = open(status_file + ".new", "w")117 status = open(status_file + ".new", "w")
@@ -316,4 +130,3 @@
316if __name__ == "__main__":130if __name__ == "__main__":
317 run(main, options, usage="%prog",131 run(main, options, usage="%prog",
318 description="output status of manual merges")132 description="output status of manual merges")
319
320133
=== modified file 'merge-status.py'
--- merge-status.py 2009-03-02 15:33:48 +0000
+++ merge-status.py 2009-07-20 18:07:18 +0000
@@ -5,6 +5,8 @@
5# Copyright © 2008 Canonical Ltd.5# Copyright © 2008 Canonical Ltd.
6# Author: Scott James Remnant <scott@ubuntu.com>.6# Author: Scott James Remnant <scott@ubuntu.com>.
7#7#
8# Copyright © 2008-2009 Siegfried-A. Gevatter Pujals <rainct@ubuntu.com>
9#
8# This program is free software: you can redistribute it and/or modify10# This program is free software: you can redistribute it and/or modify
9# it under the terms of version 3 of the GNU General Public License as11# it under the terms of version 3 of the GNU General Public License as
10# published by the Free Software Foundation.12# published by the Free Software Foundation.
@@ -19,41 +21,11 @@
1921
20import os22import os
21import bz223import bz2
22import sys
23import glob
24import fcntl
25
26from rfc822 import parseaddr24from rfc822 import parseaddr
25
27from momlib import *26from momlib import *
2827from util.status_common import *
2928
30# Order of priorities
31PRIORITY = [ "unknown", "required", "important", "standard", "optional",
32 "extra" ]
33COLOURS = [ "#ff8080", "#ffb580", "#ffea80", "#dfff80", "#abff80", "#80ff8b" ]
34
35# Sections
36SECTIONS = [ "outstanding", "new", "updated" ]
37
38
39def options(parser):
40 parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO",
41 default=SRC_DISTRO,
42 help="Source distribution")
43 parser.add_option("-S", "--source-suite", type="string", metavar="SUITE",
44 default=SRC_DIST,
45 help="Source suite (aka distrorelease)")
46
47 parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO",
48 default=OUR_DISTRO,
49 help="Destination distribution")
50 parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE",
51 default=OUR_DIST,
52 help="Destination suite (aka distrorelease)")
53
54 parser.add_option("-c", "--component", type="string", metavar="COMPONENT",
55 action="append",
56 help="Process only these destination components")
5729
58def main(options, args):30def main(options, args):
59 src_distro = options.source_distro31 src_distro = options.source_distro
@@ -140,192 +112,12 @@
140112
141 merges.sort()113 merges.sort()
142114
143 write_status_page(our_component, merges, our_distro, src_distro)115 write_status_page(our_component, merges, our_distro, src_distro, False)
144116
145 status_file = "%s/merges/tomerge-%s" % (ROOT, our_component)117 status_file = "%s/merges/tomerge-%s" % (ROOT, our_component)
146 remove_old_comments(status_file, merges)118 remove_old_comments(status_file, merges)
147 write_status_file(status_file, merges)119 write_status_file(status_file, merges)
148120
149
150def get_uploader(distro, source):
151 """Obtain the uploader from the dsc file signature."""
152 for md5sum, size, name in files(source):
153 if name.endswith(".dsc"):
154 dsc_file = name
155 break
156 else:
157 return None
158
159 filename = "%s/pool/%s/%s/%s/%s" \
160 % (ROOT, distro, pathhash(source["Package"]), source["Package"],
161 dsc_file)
162
163 (a, b, c) = os.popen3("gpg --verify %s" % filename)
164 stdout = c.readlines()
165 try:
166 return stdout[1].split("Good signature from")[1].strip().strip("\"")
167 except IndexError:
168 return None
169
170
171def write_status_page(component, merges, left_distro, right_distro):
172 """Write out the merge status page."""
173 status_file = "%s/merges/%s.html" % (ROOT, component)
174 status = open(status_file + ".new", "w")
175 try:
176 print >>status, "<html>"
177 print >>status
178 print >>status, "<head>"
179 print >>status, "<title>Ubuntu Merge-o-Matic: %s</title>" % component
180 print >>status, "<style>"
181 print >>status, "img#ubuntu {"
182 print >>status, " border: 0;"
183 print >>status, "}"
184 print >>status, "h1 {"
185 print >>status, " padding-top: 0.5em;"
186 print >>status, " font-family: sans-serif;"
187 print >>status, " font-size: 2.0em;"
188 print >>status, " font-weight: bold;"
189 print >>status, "}"
190 print >>status, "h2 {"
191 print >>status, " padding-top: 0.5em;"
192 print >>status, " font-family: sans-serif;"
193 print >>status, " font-size: 1.5em;"
194 print >>status, " font-weight: bold;"
195 print >>status, "}"
196 print >>status, "p, td {"
197 print >>status, " font-family: sans-serif;"
198 print >>status, " margin-bottom: 0;"
199 print >>status, "}"
200 print >>status, "li {"
201 print >>status, " font-family: sans-serif;"
202 print >>status, " margin-bottom: 1em;"
203 print >>status, "}"
204 print >>status, "tr.first td {"
205 print >>status, " border-top: 2px solid white;"
206 print >>status, "}"
207 print >>status, "</style>"
208 print >>status, "<% from momlib import * %>"
209 print >>status, "</head>"
210 print >>status, "<body>"
211 print >>status, "<img src=\".img/ubuntulogo-100.png\" id=\"ubuntu\">"
212 print >>status, "<h1>Ubuntu Merge-o-Matic: %s</h1>" % component
213
214 for section in SECTIONS:
215 section_merges = [ m for m in merges if m[0] == section ]
216 print >>status, ("<p><a href=\"#%s\">%s %s merges</a></p>"
217 % (section, len(section_merges), section))
218
219 print >>status, "<ul>"
220 print >>status, ("<li>If you are not the previous uploader, ask the "
221 "previous uploader before doing the merge. This "
222 "prevents two people from doing the same work.</li>")
223 print >>status, ("<li>Before uploading, update the changelog to "
224 "have your name and a list of the outstanding "
225 "Ubuntu changes.</li>")
226 print >>status, ("<li>Try and keep the diff small, this may involve "
227 "manually tweaking <tt>po</tt> files and the"
228 "like.</li>")
229 print >>status, "</ul>"
230
231 print >>status, "<% comment = get_comments() %>"
232
233 for section in SECTIONS:
234 section_merges = [ m for m in merges if m[0] == section ]
235
236 print >>status, ("<h2 id=\"%s\">%s Merges</h2>"
237 % (section, section.title()))
238
239 do_table(status, section_merges, left_distro, right_distro, component)
240
241 print >>status, "<h2 id=stats>Statistics</h2>"
242 print >>status, ("<img src=\"%s-now.png\" title=\"Current stats\">"
243 % component)
244 print >>status, ("<img src=\"%s-trend.png\" title=\"Six month trend\">"
245 % component)
246 print >>status, "</body>"
247 print >>status, "</html>"
248 finally:
249 status.close()
250
251 os.rename(status_file + ".new", status_file)
252
253def do_table(status, merges, left_distro, right_distro, component):
254 """Output a table."""
255 print >>status, "<table cellspacing=0>"
256 print >>status, "<tr bgcolor=#d0d0d0>"
257 print >>status, "<td rowspan=2><b>Package</b></td>"
258 print >>status, "<td colspan=3><b>Last Uploader</b></td>"
259 print >>status, "<td rowspan=2><b>Comment</b></td>"
260 print >>status, "<td rowspan=2><b>Bug</b></td>"
261 print >>status, "</tr>"
262 print >>status, "<tr bgcolor=#d0d0d0>"
263 print >>status, "<td><b>%s Version</b></td>" % left_distro.title()
264 print >>status, "<td><b>%s Version</b></td>" % right_distro.title()
265 print >>status, "<td><b>Base Version</b></td>"
266 print >>status, "</tr>"
267
268 for uploaded, priority, package, user, uploader, source, \
269 base_version, left_version, right_version in merges:
270 if user is not None:
271 who = user
272 who = who.replace("&", "&amp;")
273 who = who.replace("<", "&lt;")
274 who = who.replace(">", "&gt;")
275
276 if uploader is not None:
277 (usr_name, usr_mail) = parseaddr(user)
278 (upl_name, upl_mail) = parseaddr(uploader)
279
280 if len(usr_name) and usr_name != upl_name:
281 u_who = uploader
282 u_who = u_who.replace("&", "&amp;")
283 u_who = u_who.replace("<", "&lt;")
284 u_who = u_who.replace(">", "&gt;")
285
286 who = "%s<br><small><em>Uploader:</em> %s</small>" \
287 % (who, u_who)
288 else:
289 who = "&nbsp;"
290
291 print >>status, "<tr bgcolor=%s class=first>" % COLOURS[priority]
292 print >>status, "<td><tt><a href=\"%s/%s/REPORT\">" \
293 "%s</a></tt>" % (pathhash(package), package, package)
294 print >>status, " <sup><a href=\"https://launchpad.net/ubuntu/" \
295 "+source/%s\">LP</a></sup>" % package
296 print >>status, " <sup><a href=\"http://packages.qa.debian.org/" \
297 "%s\">PTS</a></sup></td>" % package
298 print >>status, "<td colspan=3>%s</td>" % who
299 print >>status, "<td rowspan=2><form method=\"get\" action=\"addcomment.py\"><br />"
300 print >>status, "<input type=\"hidden\" name=\"component\" value=\"%s\" />" % component
301 print >>status, "<input type=\"hidden\" name=\"package\" value=\"%s\" />" % package
302 print >>status, "<%%\n\
303the_comment = \"\"\n\
304if(comment.has_key(\"%s\")):\n\
305 the_comment = comment[\"%s\"]\n\
306req.write(\"<input type=\\\"text\\\" style=\\\"border-style: none; background-color: %s\\\" name=\\\"comment\\\" value=\\\"%%s\\\" title=\\\"%%s\\\" />\" %% (the_comment, the_comment))\n\
307%%>" % (package, package, COLOURS[priority])
308 print >>status, "</form></td>"
309 print >>status, "<td rowspan=2>"
310 print >>status, "<%%\n\
311if(comment.has_key(\"%s\")):\n\
312 req.write(\"%%s\" %% gen_buglink_from_comment(comment[\"%s\"]))\n\
313else:\n\
314 req.write(\"&nbsp;\")\n\
315\n\
316%%>" % (package, package)
317 print >>status, "</td>"
318 print >>status, "</tr>"
319 print >>status, "<tr bgcolor=%s>" % COLOURS[priority]
320 print >>status, "<td><small>%s</small></td>" % source["Binary"]
321 print >>status, "<td>%s</td>" % left_version
322 print >>status, "<td>%s</td>" % right_version
323 print >>status, "<td>%s</td>" % base_version
324 print >>status, "</tr>"
325
326 print >>status, "</table>"
327
328
329def write_status_file(status_file, merges):121def write_status_file(status_file, merges):
330 """Write out the merge status file."""122 """Write out the merge status file."""
331 status = open(status_file + ".new", "w")123 status = open(status_file + ".new", "w")
@@ -341,6 +133,4 @@
341 os.rename(status_file + ".new", status_file)133 os.rename(status_file + ".new", status_file)
342134
343if __name__ == "__main__":135if __name__ == "__main__":
344 run(main, options, usage="%prog",136 run(main, options, usage="%prog", description="output merge status")
345 description="output merge status")
346
347137
=== modified file 'momlib.py'
--- momlib.py 2009-05-29 07:33:55 +0000
+++ momlib.py 2009-07-20 18:07:18 +0000
@@ -20,7 +20,7 @@
20import os20import os
21import re21import re
22import sys22import sys
23import md523import hashlib
24import time24import time
25import fcntl25import fcntl
26import errno26import errno
@@ -156,7 +156,7 @@
156156
157def md5sum(filename):157def md5sum(filename):
158 """Return an md5sum."""158 """Return an md5sum."""
159 return md5.new(open(filename).read()).hexdigest()159 return hashlib.md5(open(filename).read()).hexdigest()
160160
161161
162# --------------------------------------------------------------------------- #162# --------------------------------------------------------------------------- #
163163
=== added directory 'templates'
=== added file 'templates/status.tpl'
--- templates/status.tpl 1970-01-01 00:00:00 +0000
+++ templates/status.tpl 2009-07-20 18:07:18 +0000
@@ -0,0 +1,100 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
3<html xmlns="http://www.w3.org/1999/xhtml"><head>
4 <title>Ubuntu Merge-o-Matic: [% var component %][% if manual %] manual[% endif %]</title>
5 <link rel="stylesheet" href="./style.css" type="text/css" />
6 <!--- <script language="javascript" src="./javascript.js" type="text/javascript"></script> --->
7 <link rel="shortcut icon" href="http://ubuntu.com/favicon.ico" type="image/x-icon" >
8 <% from momlib import * %>
9 <% comments = get_comments() %>
10</head><body>
11
12<div id="header">
13 <h1>Ubuntu Merge-o-Matic: [% var component %][% if manual %] manual[% endif %]</h1>
14</div>
15
16<div id="page"><div id="summary">
17 <ul>
18 [% for section in sections %]<li><a href="#[% var section['name'] %]">[% var section['len'] %] [% var section['name'] %] merges</a></li>[% done %]
19 </ul><ul>
20 <li>If you are not the previous uploader, ask the previous uploader before doing the merge. This prevents two people from doing the same work.</li>
21 <li>Before uploading, update the changelog to have your name and a list of the outstanding Ubuntu changes, and don't forget the <em>-v</em> option with <em>debuild</em>.</li>
22 <li>Try to keep the diff small; this may involve manually tweaking po files and the like.</li>
23 </ul>
24
25</div><div id="merges">
26[% for section in sections %]
27 <h2 id="[% var section['name'] %]">[% var section['title'] %] Merges</h2>
28 <table cellspacing="0"><thead>
29 <tr>
30 <th></th>
31 <th>Package</th>
32 <th>Ubuntu</th>
33 <th>Debian</th>
34 [% if not_manual %]<th>Base</th>[% endif %]
35 <th>Last Uploader</th>
36 <th>Comment</th>
37 <th>Bug</th>
38 </tr>
39 </thead><tbody>
40 [% for uploaded, priority, package, user, uploader, source, base_version,
41 left_version, right_version, pathhash in section['packages'] %]
42 <tr>
43 <td class="priority[% var priority %]" class="indicator"></td>
44 <td>
45 <a href="[% var pathhash %]/[% var package %]/REPORT">[% var package %]</a>
46 <a href="https://launchpad.net/distros/ubuntu/+source/[% var package %]">(LP)</a>
47 <a href="http://packages.qa.debian.org/[% var package %]">(PTS)</a>
48 <span>[% var source['Binary'] %]</span>
49 </td>
50 <td class="version">[% var left_version %]</td>
51 <td class="version">[% var right_version %]</td>
52 [% if not_manual %]<td class="version">[% var base_version %]</td>[% endif %]
53 <td>[% var user %]
54 [% if uploader %]
55 <br /><small><em>Uploader:</em> [% var uploader %]</small>
56 [% endif %]
57 </td>
58 <td class="form_row">
59<%
60if comments.has_key("[% var package %]"):
61 comment = comments["[% var package %]"]
62 buglink = gen_buglink_from_comment(comments["[% var package %]"])
63else:
64 comment = buglink = ""
65# Keep this line here to end the indentation
66%>
67 <form method="GET" action="addcomment.py">
68 <input type="hidden" name="component" value="[% var component %]" />
69 <input type="hidden" name="package" value="[% var package %]" />
70 <input type="text" name="comment"
71 value="<% req.write(comment) %>"
72 title="<% req.write(comment) %>" />
73 </form>
74 </td>
75 <td class="bug"><% req.write("%s" % buglink) %></td>
76 </tr>
77 [% done %]
78 </tbody></table>
79[% done %]
80</div>
81
82<br />
83<strong>Colour keys:</strong>
84<table id="colours"><tr>
85 <td bgcolor="#ff8080">unknown</td>
86 <td bgcolor="#ffb580">required</td>
87 <td bgcolor="#ffea80">important</td>
88 <td bgcolor="#dfff80">standard</td>
89 <td bgcolor="#abff80">optional</td>
90 <td bgcolor="#80ff8b">extra</td>
91</tr></table>
92
93<div id="statistics">
94 <h2 >Statistics</h2>
95 <img src="[% var component %]-now.png" title="Current stats" />
96 <img src="[% var component %]-trend.png" title="Six month trend" />
97</div></div>
98
99</body>
100</html>
0101
=== added file 'templates/style.css'
--- templates/style.css 1970-01-01 00:00:00 +0000
+++ templates/style.css 2009-07-20 18:07:18 +0000
@@ -0,0 +1,97 @@
1body {
2 font-family: sans-serif;
3}
4
5body, div#header img, div#header h1 {
6 margin: 0;
7 padding: 0;
8}
9
10div#header {
11 background: url('images/header_bg.png') repeat-x;
12 height: 75px;
13}
14
15div#header h1 {
16 background: url('images/header.png') no-repeat;
17 padding: 20px 20px 0 320px;
18 height: 90px;
19 font-size: 32px;
20}
21
22div#page {
23 margin: 15px;
24}
25
26div#page h2 {
27 width: 100%;
28 color: #6D4C07;
29 padding-bottom: .0em;
30 padding-top: 0.4em;
31 font-family: Verdana, arial, helvetica, sans-serif;
32 font-size: 160%;
33 border-bottom: 2px solid #6D4C07;
34}
35
36div#page li {
37 margin-bottom: 1em;
38}
39
40div#summary ul {
41 font-size: 13px;
42 margin-bottom: -8px;
43}
44
45div#summary li {
46 margin: 4px 0 0 4px;
47}
48
49div#merges table, div#merges tr {
50 margin: 0;
51 width: 98%;
52 border: 1px solid #C1B496;
53 border-right: 0;
54 font-size: 90%;
55}
56
57div#merges thead tr {
58 background: #D9BB7A;
59 font-weight: bold;
60}
61
62div#merges td {
63 border-top: 1px solid #C1B496;
64 border-right: 1px solid #C1B496;
65 padding: 4px;
66}
67
68div#merges td span {
69 display: block;
70 margin: 3px 0 0 0;
71 font-size: 70%;
72}
73
74div#merges td.version, div#merges td.bug {
75 text-align: center;
76}
77
78table#colours td {
79 width: 100px;
80 text-align: center;
81}
82
83input {
84 width: 155px;
85}
86
87td.form_row {
88 width: 160px;
89}
90
91.indicator { width: 35px; }
92.priority0 { background-color: #ff8080; }
93.priority1 { background-color: #ffb580; }
94.priority2 { background-color: #ffea80; }
95.priority3 { background-color: #dfff80; }
96.priority4 { background-color: #abff80; }
97.priority5 { background-color: #80ff8b; }
098
=== modified file 'util/__init__.py'
--- util/__init__.py 2008-01-10 13:19:44 +0000
+++ util/__init__.py 2008-03-25 19:58:32 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
3#2#
4# Copyright © 2008 Canonical Ltd.3# Copyright © 2008 Canonical Ltd.
54
=== modified file 'util/shell.py'
--- util/shell.py 2008-01-10 13:19:44 +0000
+++ util/shell.py 2008-03-25 19:58:32 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
3# util/shell.py - child process forking (popen-alike)2# util/shell.py - child process forking (popen-alike)
4#3#
54
=== added file 'util/status_common.py'
--- util/status_common.py 1970-01-01 00:00:00 +0000
+++ util/status_common.py 2009-07-20 18:07:18 +0000
@@ -0,0 +1,127 @@
1# -*- coding: utf-8 -*-
2# manual-status.py - output status of manual merges
3#
4# Copyright © 2008 Canonical Ltd.
5# Author: Scott James Remnant <scott@ubuntu.com>.
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of version 3 of the GNU General Public License as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19import sys
20import os
21
22from momlib import *
23from util import tinpy
24
25PRIORITY = [ "unknown", "required", "important", "standard", "optional",
26 "extra" ]
27SECTIONS = [ "outstanding", "new", "updated" ]
28
29
30def options(parser):
31 parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO",
32 default=SRC_DISTRO,
33 help="Source distribution")
34 parser.add_option("-S", "--source-suite", type="string", metavar="SUITE",
35 default=SRC_DIST,
36 help="Source suite (aka distrorelease)")
37
38 parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO",
39 default=OUR_DISTRO,
40 help="Destination distribution")
41 parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE",
42 default=OUR_DIST,
43 help="Destination suite (aka distrorelease)")
44
45 parser.add_option("-c", "--component", type="string", metavar="COMPONENT",
46 action="append",
47 help="Process only these destination components")
48
49def get_uploader(distro, source):
50 """Obtain the uploader from the dsc file signature."""
51 for md5sum, size, name in files(source):
52 if name.endswith(".dsc"):
53 dsc_file = name
54 break
55 else:
56 return None
57
58 filename = "%s/pool/%s/%s/%s/%s" \
59 % (ROOT, distro, pathhash(source["Package"]), source["Package"],
60 dsc_file)
61
62 (a, b, c) = os.popen3("gpg --verify %s" % filename)
63 stdout = c.readlines()
64 try:
65 return stdout[1].split("Good signature from")[1].strip().strip("\"")
66 except IndexError:
67 return None
68
69def escape_email(text):
70 """Takes a string and replaces <, > and & with their HTML representation."""
71 return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
72
73def package_to_html(package):
74 """Gives the user and uploader field in a package tuple the appropriate
75 values expected by status.tpl (both fields escaped, and the uploader field
76 empty if it's the same as the user)."""
77
78 package = list(package)
79
80 if package[4]:
81 if package[3]:
82 user_name = parseaddr(package[3])[0]
83 uploader_name = parseaddr(package[4])[0]
84
85 if user_name == uploader_name:
86 package[4] = ""
87 else:
88 package[3] = package[4]
89 package[4] = ""
90 elif not package[3]:
91 package[3] = "&nbsp;"
92
93 package[3] = escape_email(package[3])
94 package[4] = escape_email(package[4])
95 package.append(pathhash(package[2]))
96
97 return package
98
99def write_status_page(component, merges, left_distro, right_distro, manual):
100 """Write out the merge status page."""
101
102 status_file = "%s/merges/%s.html" % (ROOT, component)
103 status = open(status_file + ".new", "w")
104
105 try:
106 sections = []
107
108 for section in SECTIONS:
109 section_packages = [ package_to_html(m) for m in merges
110 if m[0] == section ]
111 sections.append({
112 "name": section,
113 "title": section.title(),
114 "len": len(section_packages),
115 "packages": section_packages,
116 })
117
118 html = tinpy.build(open("templates/status.tpl").read(),
119 root = ROOT, component = component, sections = sections,
120 manual = manual, not_manual = not manual)
121
122 status.write(html)
123
124 finally:
125 status.close()
126
127 os.rename(status_file + ".new", status_file)
0128
=== added file 'util/tinpy.py'
--- util/tinpy.py 1970-01-01 00:00:00 +0000
+++ util/tinpy.py 2009-07-17 17:24:13 +0000
@@ -0,0 +1,326 @@
1# -*- coding: utf-8 -*-
2#
3# Tiny Python Template (tinpy). Version 2.4.
4# http://sourceforge.net/projects/tinpy/
5#
6# Copyright © 2001-2006 Keisuke URAGO <bravo@resourcez.org>
7# Released under the MIT License.
8
9import string
10import re
11from StringIO import StringIO
12
13class TemplateParser:
14 "Template parser."
15
16 def __init__(self):
17 # Template syntax pattern. e.g. "[%var foobar%]"
18 self.tppattern = re.compile('(\[%.*?%\])', re.DOTALL)
19 self.lineno = 1
20 self.chunk = ''
21
22 def set_processhandler(self, handler):
23 self.handler = handler
24
25 def parse(self, source):
26 "Parsing source. source is string"
27 for chunk in self.tppattern.split(source):
28 self.lineno = self.lineno + chunk.count('\n')
29 self.chunk = chunk
30 if self.tppattern.match(chunk):
31 apply(self.handler.handle, chunk[2:-2].split())
32 continue
33 try: self.handler.handle_data(chunk)
34 except: pass
35 if hasattr(self.handler, 'end_document'):
36 self.handler.end_document()
37
38class ProcessingHandler:
39 "Parser processing handler."
40
41 def __init__(self):
42 self.template = TemplateManager(Template())
43 self.in_comment = 0
44
45 def op_comment(self, arg):
46 if arg == 'begin':
47 self.in_comment = 1
48 elif arg == 'end':
49 self.in_comment = 0
50 else:
51 raise SyntaxError, "invalid syntax: comment %s" % str(arg)
52
53 def op_stag(self, *args):
54 if len(args) != 0:
55 raise SyntaxError, \
56 "invalid syntax: %s" % string.join(list(args))
57 self.template.write('[%%')
58
59 def op_etag(self, *args):
60 if len(args) != 0:
61 raise SyntaxError, \
62 "invalid syntax: %s" % string.join(list(args))
63 self.template.write('%%]')
64
65 def op_var(self, *args):
66 if len(args) != 1:
67 raise SyntaxError, \
68 "invalid syntax: args: %s" % string.join(list(args))
69 self.template.write('%%(%s)s' % args[0])
70
71 def op_for(self, *args):
72 args = list(args)
73 try:
74 in_index = args.index('in')
75 vars = [token.strip() for token in ''.join(args[:in_index]).split(',')]
76 if len(vars) == 1:
77 vars = vars[0]
78 srclist = ''.join(args[in_index+1:])
79 except Exception, e:
80 raise SyntaxError, "invalid syntax: '%s' %s" % (string.join(list(args)), e)
81 self.template.forblock_begin(vars, srclist)
82
83 def op_done(self):
84 self.template.forblock_end()
85
86 def op_if(self, *args):
87 if len(args) != 1:
88 raise SyntaxError, \
89 "invalid syntax: args: %s" % string.join(list(args))
90 self.template.ifblock_begin(args[0])
91
92 def op_endif(self):
93 self.template.ifblock_end()
94
95 def handle_data(self, chunk):
96 if self.in_comment == 1:
97 return
98 self.template.write(chunk.replace('%', '%%'))
99
100 def end_document(self):
101 self.template.end_document()
102
103 def handle(self, op, *tokens):
104 # Comment out process.
105 if op == 'comment':
106 self.op_comment(string.join(tokens))
107 return
108 if self.in_comment:
109 return
110
111 # Handle operation.
112 apply(getattr(self, 'op_' + op), tokens)
113
114class Template:
115 "Template object."
116
117 def __init__(self):
118 self.nodelist = []
119 self.initdescriptor()
120
121 def initdescriptor(self):
122 self.__descriptor = StringIO()
123
124 def write(self, s):
125 self.__descriptor.write(s)
126
127 def getvalue(self):
128 return self.__descriptor.getvalue()
129
130class ForBlock(Template):
131 "For block object."
132
133 def __init__(self, parent, seqvarnames, seqname):
134 Template.__init__(self)
135 self.parent = parent
136
137 self.seqvarnames = seqvarnames
138 self.seqname = seqname
139
140class IfBlock(Template):
141 "For block object."
142
143 def __init__(self, parent, varname):
144 Template.__init__(self)
145 self.parent = parent
146
147 self.varname = varname
148
149class DictEnhanceAccessor:
150 "Variable dictionary enhance interface."
151
152 p = re.compile('^(?P<var>[a-zA-Z_][a-zA-Z0-9_]*)(?P<modifier>(\[.+?\])+)$')
153 d = re.compile('(\[[a-zA-Z_][a-zA-Z0-9_]*\])')
154
155 def __init__(self, strict, dic={}):
156 self.strict = strict
157 self.dic = dic
158
159 def __getitem__(self, key):
160 m = self.p.match(key)
161 if not m: return self.dic.get(key, '') # Normal key.
162 val = self.dic.get(m.group('var'), '')
163 buf = StringIO()
164 for mo in self.d.split(m.group('modifier')):
165 if self.d.match(mo):
166 buf.write("['%s']" % self.dic[mo[1:-1]])
167 else:
168 buf.write(mo)
169 try:
170 return eval('val'+ buf.getvalue(), # code
171 {'__builtins__':{}}, # globals is restricted.
172 {'val':val}) # locals is only 'val'
173 except Exception, e:
174 if self.strict: raise Exception, e
175 return ''
176
177 def __setitem__(self, key, val):
178 self.dic[key] = val
179
180 def __repr__(self):
181 return str(self.dic)
182
183class VariableStack:
184 "Variable data stack."
185
186 def __init__(self, variables=None):
187 self.stack = []
188 if variables:
189 self.push(variables)
190
191 def __getitem__(self, num):
192 return self.stack[num]
193
194 def __repr__(self):
195 return str(self.stack)
196
197 def pop(self):
198 return self.stack.pop(0)
199
200 def push(self, data):
201 self.stack.insert(0, data)
202
203 def find(self, varname):
204 value = None
205 for variables in self.stack:
206 if variables.has_key(varname):
207 value = variables[varname]
208 break
209 return value
210
211 def normalize(self):
212 mapitem = {}
213 for variables in self.stack:
214 for key in variables.keys():
215 if not mapitem.has_key(key):
216 mapitem[key] = variables[key]
217 return mapitem
218
219class TemplateManager:
220 "Template management object."
221
222 def __init__(self, template):
223 self.template = template
224 self.currentnode = self.template
225
226 def __pprint(self, nodelist):
227 from types import StringType
228 for node in nodelist:
229 if type(node) == StringType:
230 print node
231 else:
232 print node, 'for %s in %s' % (node.seqvarnames, node.seqname)
233 self.__pprint(node.nodelist)
234 print '<end of block>'
235
236 def pprint(self):
237 self.__pprint(self.template.nodelist)
238
239 def write(self, s):
240 self.currentnode.write(s)
241
242 def __crop_chunk(self, node):
243 node.nodelist.append(node.getvalue())
244 node.initdescriptor()
245
246 def __append_child(self, node, child):
247 node.nodelist.append(child)
248
249 def ifblock_begin(self, varname):
250 self.__crop_chunk(self.currentnode)
251 ifblock = IfBlock(self.currentnode, varname)
252 self.__append_child(self.currentnode, ifblock)
253 self.currentnode = ifblock
254
255 def ifblock_end(self):
256 self.__crop_chunk(self.currentnode)
257 self.currentnode = self.currentnode.parent
258
259 def forblock_begin(self, seqvarnames, seqname):
260 self.__crop_chunk(self.currentnode)
261 forblock = ForBlock(self.currentnode, seqvarnames, seqname)
262 self.__append_child(self.currentnode, forblock)
263 self.currentnode = forblock
264
265 def forblock_end(self):
266 self.__crop_chunk(self.currentnode)
267 self.currentnode = self.currentnode.parent
268
269 def end_document(self):
270 self.__crop_chunk(self.currentnode)
271
272class TemplateRenderer:
273 "Template renderer."
274
275 def __init__(self, template, variable, strict):
276 self.varstack = VariableStack(variable)
277 self.template = template
278 self.strict = strict
279 self.__buffer = StringIO()
280
281 def render(self):
282 self.__build(self.template.nodelist)
283 return self.__buffer.getvalue()
284
285 def __build(self, nodelist):
286 for node in nodelist:
287 varmap = DictEnhanceAccessor(self.strict, self.varstack.normalize())
288 if isinstance(node, IfBlock):
289 if varmap[node.varname]:
290 self.__build(node.nodelist)
291 elif isinstance(node, ForBlock):
292 for var in varmap[node.seqname]:
293 if type(node.seqvarnames) == str:
294 self.varstack.push({node.seqvarnames: var})
295 else:
296 self.varstack.push(dict(zip(node.seqvarnames, var)))
297 self.__build(node.nodelist)
298 self.varstack.pop()
299 else:
300 self.__buffer.write(node % varmap)
301
302class ParseError(Exception):
303 'Parse error'
304
305def compile(template):
306 "Compile from template characters."
307
308 tpl = TemplateParser()
309 hn = ProcessingHandler()
310 tpl.set_processhandler(hn)
311 try:
312 tpl.parse(template)
313 except SyntaxError, e:
314 raise SyntaxError, "line %d, in '%s' %s" % (
315 tpl.lineno, tpl.chunk, e)
316 return hn.template.template
317
318def build(template, vardict=None, strict=False, **kw):
319 "Building document from template and variables."
320
321 if vardict == None: vardict = {}
322 for key in kw.keys():
323 vardict[key] = kw[key]
324 if not isinstance(template, Template):
325 template = compile(template)
326 return TemplateRenderer(template, vardict, strict).render()
0327
=== modified file 'util/tree.py'
--- util/tree.py 2008-01-10 13:19:44 +0000
+++ util/tree.py 2008-03-25 19:58:32 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
3# util/tree.py - useful functions for dealing with trees of files2# util/tree.py - useful functions for dealing with trees of files
4#3#

Subscribers

People subscribed via source and target branches

to status/vote changes: