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
1=== added directory 'images'
2=== added file 'images/header.png'
3Binary files images/header.png 1970-01-01 00:00:00 +0000 and images/header.png 2008-03-28 22:21:54 +0000 differ
4=== added file 'images/header_bg.png'
5Binary 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
6=== modified file 'manual-status.py'
7--- manual-status.py 2009-03-02 15:33:48 +0000
8+++ manual-status.py 2009-07-20 18:07:18 +0000
9@@ -5,6 +5,8 @@
10 # Copyright © 2008 Canonical Ltd.
11 # Author: Scott James Remnant <scott@ubuntu.com>.
12 #
13+# Copyright © 2008-2009 Siegfried-A. Gevatter Pujals <rainct@ubuntu.com>
14+#
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of version 3 of the GNU General Public License as
17 # published by the Free Software Foundation.
18@@ -19,39 +21,11 @@
19
20 import os
21 import bz2
22-import fcntl
23-
24 from rfc822 import parseaddr
25+
26 from momlib import *
27-
28-
29-# Order of priorities
30-PRIORITY = [ "unknown", "required", "important", "standard", "optional",
31- "extra" ]
32-COLOURS = [ "#ff8080", "#ffb580", "#ffea80", "#dfff80", "#abff80", "#80ff8b" ]
33-
34-# Sections
35-SECTIONS = [ "new", "updated" ]
36-
37-
38-def options(parser):
39- parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO",
40- default=SRC_DISTRO,
41- help="Source distribution")
42- parser.add_option("-S", "--source-suite", type="string", metavar="SUITE",
43- default=SRC_DIST,
44- help="Source suite (aka distrorelease)")
45-
46- parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO",
47- default=OUR_DISTRO,
48- help="Destination distribution")
49- parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE",
50- default=OUR_DIST,
51- help="Destination suite (aka distrorelease)")
52-
53- parser.add_option("-c", "--component", type="string", metavar="COMPONENT",
54- action="append",
55- help="Process only these destination components")
56+from util.status_common import *
57+
58
59 def main(options, args):
60 src_distro = options.source_distro
61@@ -60,6 +34,8 @@
62 our_distro = options.dest_distro
63 our_dist = options.dest_suite
64
65+ SECTIONS.remove("outstanding")
66+
67 # For each package in the destination distribution, find out whether
68 # there's an open merge, and if so add an entry to the table for it.
69 for our_component in DISTROS[our_distro]["components"]:
70@@ -69,10 +45,10 @@
71
72 merges = []
73
74- for our_source in get_sources(our_distro, our_dist, our_component):
75+ for source in get_sources(our_distro, our_dist, our_component):
76 try:
77- package = our_source["Package"]
78- our_version = Version(our_source["Version"])
79+ package = source["Package"]
80+ our_version = Version(source["Version"])
81 our_pool_source = get_pool_source(our_distro, package,
82 our_version)
83 logging.debug("%s: %s is %s", package, our_distro, our_version)
84@@ -97,11 +73,11 @@
85 pass
86
87 try:
88- priority_idx = PRIORITY.index(our_source["Priority"])
89+ priority_idx = PRIORITY.index(source["Priority"])
90 except KeyError:
91 priority_idx = 0
92
93- filename = changes_file(our_distro, our_source)
94+ filename = changes_file(our_distro, source)
95 if os.path.isfile(filename):
96 changes = open(filename)
97 elif os.path.isfile(filename + ".bz2"):
98@@ -119,7 +95,7 @@
99 user = None
100 uploaded = False
101
102- uploader = get_uploader(our_distro, our_source)
103+ uploader = get_uploader(our_distro, source)
104
105 if uploaded:
106 section = "updated"
107@@ -127,177 +103,15 @@
108 section = "new"
109
110 merges.append((section, priority_idx, package, user, uploader,
111- our_source, our_version, src_version))
112+ source, our_version, src_version))
113
114- write_status_page(our_component, merges, our_distro, src_distro)
115+ write_status_page(our_component, merges, our_distro,
116+ src_distro, True)
117
118 status_file = "%s/merges/tomerge-%s-manual" % (ROOT, our_component)
119 remove_old_comments(status_file, merges)
120 write_status_file(status_file, merges)
121
122-
123-def write_status_page(component, merges, left_distro, right_distro):
124- """Write out the manual merge status page."""
125- merges.sort()
126-
127- status_file = "%s/merges/%s-manual.html" % (ROOT, component)
128- status = open(status_file + ".new", "w")
129- try:
130- print >>status, "<html>"
131- print >>status
132- print >>status, "<head>"
133- print >>status, "<title>Ubuntu Merge-o-Matic: %s manual</title>" \
134- % component
135- print >>status, "<style>"
136- print >>status, "img#ubuntu {"
137- print >>status, " border: 0;"
138- print >>status, "}"
139- print >>status, "h1 {"
140- print >>status, " padding-top: 0.5em;"
141- print >>status, " font-family: sans-serif;"
142- print >>status, " font-size: 2.0em;"
143- print >>status, " font-weight: bold;"
144- print >>status, "}"
145- print >>status, "h2 {"
146- print >>status, " padding-top: 0.5em;"
147- print >>status, " font-family: sans-serif;"
148- print >>status, " font-size: 1.5em;"
149- print >>status, " font-weight: bold;"
150- print >>status, "}"
151- print >>status, "p, td {"
152- print >>status, " font-family: sans-serif;"
153- print >>status, " margin-bottom: 0;"
154- print >>status, "}"
155- print >>status, "li {"
156- print >>status, " font-family: sans-serif;"
157- print >>status, " margin-bottom: 1em;"
158- print >>status, "}"
159- print >>status, "tr.first td {"
160- print >>status, " border-top: 2px solid white;"
161- print >>status, "}"
162- print >>status, "</style>"
163- print >>status, "<% from momlib import * %>"
164- print >>status, "</head>"
165- print >>status, "<body>"
166- print >>status, "<img src=\".img/ubuntulogo-100.png\" id=\"ubuntu\">"
167- print >>status, "<h1>Ubuntu Merge-o-Matic: %s manual</h1>" % component
168-
169- for section in SECTIONS:
170- section_merges = [ m for m in merges if m[0] == section ]
171- print >>status, ("<p><a href=\"#%s\">%s %s merges</a></p>"
172- % (section, len(section_merges), section))
173-
174- print >>status, "<% comment = get_comments() %>"
175-
176- for section in SECTIONS:
177- section_merges = [ m for m in merges if m[0] == section ]
178-
179- print >>status, ("<h2 id=\"%s\">%s Merges</h2>"
180- % (section, section.title()))
181-
182- do_table(status, section_merges, left_distro, right_distro, component)
183-
184- print >>status, "</body>"
185- print >>status, "</html>"
186- finally:
187- status.close()
188-
189- os.rename(status_file + ".new", status_file)
190-
191-def get_uploader(distro, source):
192- """Obtain the uploader from the dsc file signature."""
193- for md5sum, size, name in files(source):
194- if name.endswith(".dsc"):
195- dsc_file = name
196- break
197- else:
198- return None
199-
200- filename = "%s/pool/%s/%s/%s/%s" \
201- % (ROOT, distro, pathhash(source["Package"]), source["Package"],
202- dsc_file)
203-
204- (a, b, c) = os.popen3("gpg --verify %s" % filename)
205- stdout = c.readlines()
206- try:
207- return stdout[1].split("Good signature from")[1].strip().strip("\"")
208- except IndexError:
209- return None
210-
211-def do_table(status, merges, left_distro, right_distro, component):
212- """Output a table."""
213- print >>status, "<table cellspacing=0>"
214- print >>status, "<tr bgcolor=#d0d0d0>"
215- print >>status, "<td rowspan=2><b>Package</b></td>"
216- print >>status, "<td colspan=2><b>Last Uploader</b></td>"
217- print >>status, "<td rowspan=2><b>Comment</b></td>"
218- print >>status, "<td rowspan=2><b>Bug</b></td>"
219- print >>status, "</tr>"
220- print >>status, "<tr bgcolor=#d0d0d0>"
221- print >>status, "<td><b>%s Version</b></td>" % left_distro.title()
222- print >>status, "<td><b>%s Version</b></td>" % right_distro.title()
223- print >>status, "</tr>"
224-
225- for uploaded, priority, package, user, uploader, source, \
226- left_version, right_version in merges:
227- if user is not None:
228- who = user
229- who = who.replace("&", "&amp;")
230- who = who.replace("<", "&lt;")
231- who = who.replace(">", "&gt;")
232-
233- if uploader is not None:
234- (usr_name, usr_mail) = parseaddr(user)
235- (upl_name, upl_mail) = parseaddr(uploader)
236-
237- if len(usr_name) and usr_name != upl_name:
238- u_who = uploader
239- u_who = u_who.replace("&", "&amp;")
240- u_who = u_who.replace("<", "&lt;")
241- u_who = u_who.replace(">", "&gt;")
242-
243- who = "%s<br><small><em>Uploader:</em> %s</small>" \
244- % (who, u_who)
245- else:
246- who = "&nbsp;"
247-
248- print >>status, "<tr bgcolor=%s class=first>" % COLOURS[priority]
249- print >>status, "<td><tt><a href=\"https://patches.ubuntu.com/" \
250- "%s/%s/%s_%s.patch\">%s</a></tt>" \
251- % (pathhash(package), package, package, left_version, package)
252- print >>status, " <sup><a href=\"https://launchpad.net/ubuntu/" \
253- "+source/%s\">LP</a></sup>" % package
254- print >>status, " <sup><a href=\"http://packages.qa.debian.org/" \
255- "%s\">PTS</a></sup></td>" % package
256- print >>status, "<td colspan=2>%s</td>" % who
257- print >>status, "<td rowspan=2><form method=\"get\" action=\"addcomment.py\"><br />"
258- print >>status, "<input type=\"hidden\" name=\"component\" value=\"%s\" />" % component
259- print >>status, "<input type=\"hidden\" name=\"package\" value=\"%s\" />" % package
260- print >>status, "<%%\n\
261-the_comment = \"\"\n\
262-if(comment.has_key(\"%s\")):\n\
263- the_comment = comment[\"%s\"]\n\
264-req.write(\"<input type=\\\"text\\\" style=\\\"border-style: none; background-color: %s\\\" name=\\\"comment\\\" value=\\\"%%s\\\" title=\\\"%%s\\\" />\" %% (the_comment, the_comment))\n\
265-%%>" % (package, package, COLOURS[priority])
266- print >>status, "</form></td>"
267- print >>status, "<td rowspan=2>"
268- print >>status, "<%%\n\
269-if(comment.has_key(\"%s\")):\n\
270- req.write(\"%%s\" %% gen_buglink_from_comment(comment[\"%s\"]))\n\
271-else:\n\
272- req.write(\"&nbsp;\")\n\
273-\n\
274-%%>" % (package, package)
275- print >>status, "</td>"
276- print >>status, "</tr>"
277- print >>status, "<tr bgcolor=%s>" % COLOURS[priority]
278- print >>status, "<td><small>%s</small></td>" % source["Binary"]
279- print >>status, "<td>%s</td>" % left_version
280- print >>status, "<td>%s</td>" % right_version
281- print >>status, "</tr>"
282-
283- print >>status, "</table>"
284-
285 def write_status_file(status_file, merges):
286 """Write out the merge status file."""
287 status = open(status_file + ".new", "w")
288@@ -316,4 +130,3 @@
289 if __name__ == "__main__":
290 run(main, options, usage="%prog",
291 description="output status of manual merges")
292-
293
294=== modified file 'merge-status.py'
295--- merge-status.py 2009-03-02 15:33:48 +0000
296+++ merge-status.py 2009-07-20 18:07:18 +0000
297@@ -5,6 +5,8 @@
298 # Copyright © 2008 Canonical Ltd.
299 # Author: Scott James Remnant <scott@ubuntu.com>.
300 #
301+# Copyright © 2008-2009 Siegfried-A. Gevatter Pujals <rainct@ubuntu.com>
302+#
303 # This program is free software: you can redistribute it and/or modify
304 # it under the terms of version 3 of the GNU General Public License as
305 # published by the Free Software Foundation.
306@@ -19,41 +21,11 @@
307
308 import os
309 import bz2
310-import sys
311-import glob
312-import fcntl
313-
314 from rfc822 import parseaddr
315+
316 from momlib import *
317-
318-
319-# Order of priorities
320-PRIORITY = [ "unknown", "required", "important", "standard", "optional",
321- "extra" ]
322-COLOURS = [ "#ff8080", "#ffb580", "#ffea80", "#dfff80", "#abff80", "#80ff8b" ]
323-
324-# Sections
325-SECTIONS = [ "outstanding", "new", "updated" ]
326-
327-
328-def options(parser):
329- parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO",
330- default=SRC_DISTRO,
331- help="Source distribution")
332- parser.add_option("-S", "--source-suite", type="string", metavar="SUITE",
333- default=SRC_DIST,
334- help="Source suite (aka distrorelease)")
335-
336- parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO",
337- default=OUR_DISTRO,
338- help="Destination distribution")
339- parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE",
340- default=OUR_DIST,
341- help="Destination suite (aka distrorelease)")
342-
343- parser.add_option("-c", "--component", type="string", metavar="COMPONENT",
344- action="append",
345- help="Process only these destination components")
346+from util.status_common import *
347+
348
349 def main(options, args):
350 src_distro = options.source_distro
351@@ -140,192 +112,12 @@
352
353 merges.sort()
354
355- write_status_page(our_component, merges, our_distro, src_distro)
356+ write_status_page(our_component, merges, our_distro, src_distro, False)
357
358 status_file = "%s/merges/tomerge-%s" % (ROOT, our_component)
359 remove_old_comments(status_file, merges)
360 write_status_file(status_file, merges)
361
362-
363-def get_uploader(distro, source):
364- """Obtain the uploader from the dsc file signature."""
365- for md5sum, size, name in files(source):
366- if name.endswith(".dsc"):
367- dsc_file = name
368- break
369- else:
370- return None
371-
372- filename = "%s/pool/%s/%s/%s/%s" \
373- % (ROOT, distro, pathhash(source["Package"]), source["Package"],
374- dsc_file)
375-
376- (a, b, c) = os.popen3("gpg --verify %s" % filename)
377- stdout = c.readlines()
378- try:
379- return stdout[1].split("Good signature from")[1].strip().strip("\"")
380- except IndexError:
381- return None
382-
383-
384-def write_status_page(component, merges, left_distro, right_distro):
385- """Write out the merge status page."""
386- status_file = "%s/merges/%s.html" % (ROOT, component)
387- status = open(status_file + ".new", "w")
388- try:
389- print >>status, "<html>"
390- print >>status
391- print >>status, "<head>"
392- print >>status, "<title>Ubuntu Merge-o-Matic: %s</title>" % component
393- print >>status, "<style>"
394- print >>status, "img#ubuntu {"
395- print >>status, " border: 0;"
396- print >>status, "}"
397- print >>status, "h1 {"
398- print >>status, " padding-top: 0.5em;"
399- print >>status, " font-family: sans-serif;"
400- print >>status, " font-size: 2.0em;"
401- print >>status, " font-weight: bold;"
402- print >>status, "}"
403- print >>status, "h2 {"
404- print >>status, " padding-top: 0.5em;"
405- print >>status, " font-family: sans-serif;"
406- print >>status, " font-size: 1.5em;"
407- print >>status, " font-weight: bold;"
408- print >>status, "}"
409- print >>status, "p, td {"
410- print >>status, " font-family: sans-serif;"
411- print >>status, " margin-bottom: 0;"
412- print >>status, "}"
413- print >>status, "li {"
414- print >>status, " font-family: sans-serif;"
415- print >>status, " margin-bottom: 1em;"
416- print >>status, "}"
417- print >>status, "tr.first td {"
418- print >>status, " border-top: 2px solid white;"
419- print >>status, "}"
420- print >>status, "</style>"
421- print >>status, "<% from momlib import * %>"
422- print >>status, "</head>"
423- print >>status, "<body>"
424- print >>status, "<img src=\".img/ubuntulogo-100.png\" id=\"ubuntu\">"
425- print >>status, "<h1>Ubuntu Merge-o-Matic: %s</h1>" % component
426-
427- for section in SECTIONS:
428- section_merges = [ m for m in merges if m[0] == section ]
429- print >>status, ("<p><a href=\"#%s\">%s %s merges</a></p>"
430- % (section, len(section_merges), section))
431-
432- print >>status, "<ul>"
433- print >>status, ("<li>If you are not the previous uploader, ask the "
434- "previous uploader before doing the merge. This "
435- "prevents two people from doing the same work.</li>")
436- print >>status, ("<li>Before uploading, update the changelog to "
437- "have your name and a list of the outstanding "
438- "Ubuntu changes.</li>")
439- print >>status, ("<li>Try and keep the diff small, this may involve "
440- "manually tweaking <tt>po</tt> files and the"
441- "like.</li>")
442- print >>status, "</ul>"
443-
444- print >>status, "<% comment = get_comments() %>"
445-
446- for section in SECTIONS:
447- section_merges = [ m for m in merges if m[0] == section ]
448-
449- print >>status, ("<h2 id=\"%s\">%s Merges</h2>"
450- % (section, section.title()))
451-
452- do_table(status, section_merges, left_distro, right_distro, component)
453-
454- print >>status, "<h2 id=stats>Statistics</h2>"
455- print >>status, ("<img src=\"%s-now.png\" title=\"Current stats\">"
456- % component)
457- print >>status, ("<img src=\"%s-trend.png\" title=\"Six month trend\">"
458- % component)
459- print >>status, "</body>"
460- print >>status, "</html>"
461- finally:
462- status.close()
463-
464- os.rename(status_file + ".new", status_file)
465-
466-def do_table(status, merges, left_distro, right_distro, component):
467- """Output a table."""
468- print >>status, "<table cellspacing=0>"
469- print >>status, "<tr bgcolor=#d0d0d0>"
470- print >>status, "<td rowspan=2><b>Package</b></td>"
471- print >>status, "<td colspan=3><b>Last Uploader</b></td>"
472- print >>status, "<td rowspan=2><b>Comment</b></td>"
473- print >>status, "<td rowspan=2><b>Bug</b></td>"
474- print >>status, "</tr>"
475- print >>status, "<tr bgcolor=#d0d0d0>"
476- print >>status, "<td><b>%s Version</b></td>" % left_distro.title()
477- print >>status, "<td><b>%s Version</b></td>" % right_distro.title()
478- print >>status, "<td><b>Base Version</b></td>"
479- print >>status, "</tr>"
480-
481- for uploaded, priority, package, user, uploader, source, \
482- base_version, left_version, right_version in merges:
483- if user is not None:
484- who = user
485- who = who.replace("&", "&amp;")
486- who = who.replace("<", "&lt;")
487- who = who.replace(">", "&gt;")
488-
489- if uploader is not None:
490- (usr_name, usr_mail) = parseaddr(user)
491- (upl_name, upl_mail) = parseaddr(uploader)
492-
493- if len(usr_name) and usr_name != upl_name:
494- u_who = uploader
495- u_who = u_who.replace("&", "&amp;")
496- u_who = u_who.replace("<", "&lt;")
497- u_who = u_who.replace(">", "&gt;")
498-
499- who = "%s<br><small><em>Uploader:</em> %s</small>" \
500- % (who, u_who)
501- else:
502- who = "&nbsp;"
503-
504- print >>status, "<tr bgcolor=%s class=first>" % COLOURS[priority]
505- print >>status, "<td><tt><a href=\"%s/%s/REPORT\">" \
506- "%s</a></tt>" % (pathhash(package), package, package)
507- print >>status, " <sup><a href=\"https://launchpad.net/ubuntu/" \
508- "+source/%s\">LP</a></sup>" % package
509- print >>status, " <sup><a href=\"http://packages.qa.debian.org/" \
510- "%s\">PTS</a></sup></td>" % package
511- print >>status, "<td colspan=3>%s</td>" % who
512- print >>status, "<td rowspan=2><form method=\"get\" action=\"addcomment.py\"><br />"
513- print >>status, "<input type=\"hidden\" name=\"component\" value=\"%s\" />" % component
514- print >>status, "<input type=\"hidden\" name=\"package\" value=\"%s\" />" % package
515- print >>status, "<%%\n\
516-the_comment = \"\"\n\
517-if(comment.has_key(\"%s\")):\n\
518- the_comment = comment[\"%s\"]\n\
519-req.write(\"<input type=\\\"text\\\" style=\\\"border-style: none; background-color: %s\\\" name=\\\"comment\\\" value=\\\"%%s\\\" title=\\\"%%s\\\" />\" %% (the_comment, the_comment))\n\
520-%%>" % (package, package, COLOURS[priority])
521- print >>status, "</form></td>"
522- print >>status, "<td rowspan=2>"
523- print >>status, "<%%\n\
524-if(comment.has_key(\"%s\")):\n\
525- req.write(\"%%s\" %% gen_buglink_from_comment(comment[\"%s\"]))\n\
526-else:\n\
527- req.write(\"&nbsp;\")\n\
528-\n\
529-%%>" % (package, package)
530- print >>status, "</td>"
531- print >>status, "</tr>"
532- print >>status, "<tr bgcolor=%s>" % COLOURS[priority]
533- print >>status, "<td><small>%s</small></td>" % source["Binary"]
534- print >>status, "<td>%s</td>" % left_version
535- print >>status, "<td>%s</td>" % right_version
536- print >>status, "<td>%s</td>" % base_version
537- print >>status, "</tr>"
538-
539- print >>status, "</table>"
540-
541-
542 def write_status_file(status_file, merges):
543 """Write out the merge status file."""
544 status = open(status_file + ".new", "w")
545@@ -341,6 +133,4 @@
546 os.rename(status_file + ".new", status_file)
547
548 if __name__ == "__main__":
549- run(main, options, usage="%prog",
550- description="output merge status")
551-
552+ run(main, options, usage="%prog", description="output merge status")
553
554=== modified file 'momlib.py'
555--- momlib.py 2009-05-29 07:33:55 +0000
556+++ momlib.py 2009-07-20 18:07:18 +0000
557@@ -20,7 +20,7 @@
558 import os
559 import re
560 import sys
561-import md5
562+import hashlib
563 import time
564 import fcntl
565 import errno
566@@ -156,7 +156,7 @@
567
568 def md5sum(filename):
569 """Return an md5sum."""
570- return md5.new(open(filename).read()).hexdigest()
571+ return hashlib.md5(open(filename).read()).hexdigest()
572
573
574 # --------------------------------------------------------------------------- #
575
576=== added directory 'templates'
577=== added file 'templates/status.tpl'
578--- templates/status.tpl 1970-01-01 00:00:00 +0000
579+++ templates/status.tpl 2009-07-20 18:07:18 +0000
580@@ -0,0 +1,100 @@
581+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
582+
583+<html xmlns="http://www.w3.org/1999/xhtml"><head>
584+ <title>Ubuntu Merge-o-Matic: [% var component %][% if manual %] manual[% endif %]</title>
585+ <link rel="stylesheet" href="./style.css" type="text/css" />
586+ <!--- <script language="javascript" src="./javascript.js" type="text/javascript"></script> --->
587+ <link rel="shortcut icon" href="http://ubuntu.com/favicon.ico" type="image/x-icon" >
588+ <% from momlib import * %>
589+ <% comments = get_comments() %>
590+</head><body>
591+
592+<div id="header">
593+ <h1>Ubuntu Merge-o-Matic: [% var component %][% if manual %] manual[% endif %]</h1>
594+</div>
595+
596+<div id="page"><div id="summary">
597+ <ul>
598+ [% for section in sections %]<li><a href="#[% var section['name'] %]">[% var section['len'] %] [% var section['name'] %] merges</a></li>[% done %]
599+ </ul><ul>
600+ <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>
601+ <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>
602+ <li>Try to keep the diff small; this may involve manually tweaking po files and the like.</li>
603+ </ul>
604+
605+</div><div id="merges">
606+[% for section in sections %]
607+ <h2 id="[% var section['name'] %]">[% var section['title'] %] Merges</h2>
608+ <table cellspacing="0"><thead>
609+ <tr>
610+ <th></th>
611+ <th>Package</th>
612+ <th>Ubuntu</th>
613+ <th>Debian</th>
614+ [% if not_manual %]<th>Base</th>[% endif %]
615+ <th>Last Uploader</th>
616+ <th>Comment</th>
617+ <th>Bug</th>
618+ </tr>
619+ </thead><tbody>
620+ [% for uploaded, priority, package, user, uploader, source, base_version,
621+ left_version, right_version, pathhash in section['packages'] %]
622+ <tr>
623+ <td class="priority[% var priority %]" class="indicator"></td>
624+ <td>
625+ <a href="[% var pathhash %]/[% var package %]/REPORT">[% var package %]</a>
626+ <a href="https://launchpad.net/distros/ubuntu/+source/[% var package %]">(LP)</a>
627+ <a href="http://packages.qa.debian.org/[% var package %]">(PTS)</a>
628+ <span>[% var source['Binary'] %]</span>
629+ </td>
630+ <td class="version">[% var left_version %]</td>
631+ <td class="version">[% var right_version %]</td>
632+ [% if not_manual %]<td class="version">[% var base_version %]</td>[% endif %]
633+ <td>[% var user %]
634+ [% if uploader %]
635+ <br /><small><em>Uploader:</em> [% var uploader %]</small>
636+ [% endif %]
637+ </td>
638+ <td class="form_row">
639+<%
640+if comments.has_key("[% var package %]"):
641+ comment = comments["[% var package %]"]
642+ buglink = gen_buglink_from_comment(comments["[% var package %]"])
643+else:
644+ comment = buglink = ""
645+# Keep this line here to end the indentation
646+%>
647+ <form method="GET" action="addcomment.py">
648+ <input type="hidden" name="component" value="[% var component %]" />
649+ <input type="hidden" name="package" value="[% var package %]" />
650+ <input type="text" name="comment"
651+ value="<% req.write(comment) %>"
652+ title="<% req.write(comment) %>" />
653+ </form>
654+ </td>
655+ <td class="bug"><% req.write("%s" % buglink) %></td>
656+ </tr>
657+ [% done %]
658+ </tbody></table>
659+[% done %]
660+</div>
661+
662+<br />
663+<strong>Colour keys:</strong>
664+<table id="colours"><tr>
665+ <td bgcolor="#ff8080">unknown</td>
666+ <td bgcolor="#ffb580">required</td>
667+ <td bgcolor="#ffea80">important</td>
668+ <td bgcolor="#dfff80">standard</td>
669+ <td bgcolor="#abff80">optional</td>
670+ <td bgcolor="#80ff8b">extra</td>
671+</tr></table>
672+
673+<div id="statistics">
674+ <h2 >Statistics</h2>
675+ <img src="[% var component %]-now.png" title="Current stats" />
676+ <img src="[% var component %]-trend.png" title="Six month trend" />
677+</div></div>
678+
679+</body>
680+</html>
681
682=== added file 'templates/style.css'
683--- templates/style.css 1970-01-01 00:00:00 +0000
684+++ templates/style.css 2009-07-20 18:07:18 +0000
685@@ -0,0 +1,97 @@
686+body {
687+ font-family: sans-serif;
688+}
689+
690+body, div#header img, div#header h1 {
691+ margin: 0;
692+ padding: 0;
693+}
694+
695+div#header {
696+ background: url('images/header_bg.png') repeat-x;
697+ height: 75px;
698+}
699+
700+div#header h1 {
701+ background: url('images/header.png') no-repeat;
702+ padding: 20px 20px 0 320px;
703+ height: 90px;
704+ font-size: 32px;
705+}
706+
707+div#page {
708+ margin: 15px;
709+}
710+
711+div#page h2 {
712+ width: 100%;
713+ color: #6D4C07;
714+ padding-bottom: .0em;
715+ padding-top: 0.4em;
716+ font-family: Verdana, arial, helvetica, sans-serif;
717+ font-size: 160%;
718+ border-bottom: 2px solid #6D4C07;
719+}
720+
721+div#page li {
722+ margin-bottom: 1em;
723+}
724+
725+div#summary ul {
726+ font-size: 13px;
727+ margin-bottom: -8px;
728+}
729+
730+div#summary li {
731+ margin: 4px 0 0 4px;
732+}
733+
734+div#merges table, div#merges tr {
735+ margin: 0;
736+ width: 98%;
737+ border: 1px solid #C1B496;
738+ border-right: 0;
739+ font-size: 90%;
740+}
741+
742+div#merges thead tr {
743+ background: #D9BB7A;
744+ font-weight: bold;
745+}
746+
747+div#merges td {
748+ border-top: 1px solid #C1B496;
749+ border-right: 1px solid #C1B496;
750+ padding: 4px;
751+}
752+
753+div#merges td span {
754+ display: block;
755+ margin: 3px 0 0 0;
756+ font-size: 70%;
757+}
758+
759+div#merges td.version, div#merges td.bug {
760+ text-align: center;
761+}
762+
763+table#colours td {
764+ width: 100px;
765+ text-align: center;
766+}
767+
768+input {
769+ width: 155px;
770+}
771+
772+td.form_row {
773+ width: 160px;
774+}
775+
776+.indicator { width: 35px; }
777+.priority0 { background-color: #ff8080; }
778+.priority1 { background-color: #ffb580; }
779+.priority2 { background-color: #ffea80; }
780+.priority3 { background-color: #dfff80; }
781+.priority4 { background-color: #abff80; }
782+.priority5 { background-color: #80ff8b; }
783
784=== modified file 'util/__init__.py'
785--- util/__init__.py 2008-01-10 13:19:44 +0000
786+++ util/__init__.py 2008-03-25 19:58:32 +0000
787@@ -1,4 +1,3 @@
788-#!/usr/bin/env python
789 # -*- coding: utf-8 -*-
790 #
791 # Copyright © 2008 Canonical Ltd.
792
793=== modified file 'util/shell.py'
794--- util/shell.py 2008-01-10 13:19:44 +0000
795+++ util/shell.py 2008-03-25 19:58:32 +0000
796@@ -1,4 +1,3 @@
797-#!/usr/bin/env python
798 # -*- coding: utf-8 -*-
799 # util/shell.py - child process forking (popen-alike)
800 #
801
802=== added file 'util/status_common.py'
803--- util/status_common.py 1970-01-01 00:00:00 +0000
804+++ util/status_common.py 2009-07-20 18:07:18 +0000
805@@ -0,0 +1,127 @@
806+# -*- coding: utf-8 -*-
807+# manual-status.py - output status of manual merges
808+#
809+# Copyright © 2008 Canonical Ltd.
810+# Author: Scott James Remnant <scott@ubuntu.com>.
811+#
812+# This program is free software: you can redistribute it and/or modify
813+# it under the terms of version 3 of the GNU General Public License as
814+# published by the Free Software Foundation.
815+#
816+# This program is distributed in the hope that it will be useful,
817+# but WITHOUT ANY WARRANTY; without even the implied warranty of
818+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
819+# GNU General Public License for more details.
820+#
821+# You should have received a copy of the GNU General Public License
822+# along with this program. If not, see <http://www.gnu.org/licenses/>.
823+
824+import sys
825+import os
826+
827+from momlib import *
828+from util import tinpy
829+
830+PRIORITY = [ "unknown", "required", "important", "standard", "optional",
831+ "extra" ]
832+SECTIONS = [ "outstanding", "new", "updated" ]
833+
834+
835+def options(parser):
836+ parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO",
837+ default=SRC_DISTRO,
838+ help="Source distribution")
839+ parser.add_option("-S", "--source-suite", type="string", metavar="SUITE",
840+ default=SRC_DIST,
841+ help="Source suite (aka distrorelease)")
842+
843+ parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO",
844+ default=OUR_DISTRO,
845+ help="Destination distribution")
846+ parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE",
847+ default=OUR_DIST,
848+ help="Destination suite (aka distrorelease)")
849+
850+ parser.add_option("-c", "--component", type="string", metavar="COMPONENT",
851+ action="append",
852+ help="Process only these destination components")
853+
854+def get_uploader(distro, source):
855+ """Obtain the uploader from the dsc file signature."""
856+ for md5sum, size, name in files(source):
857+ if name.endswith(".dsc"):
858+ dsc_file = name
859+ break
860+ else:
861+ return None
862+
863+ filename = "%s/pool/%s/%s/%s/%s" \
864+ % (ROOT, distro, pathhash(source["Package"]), source["Package"],
865+ dsc_file)
866+
867+ (a, b, c) = os.popen3("gpg --verify %s" % filename)
868+ stdout = c.readlines()
869+ try:
870+ return stdout[1].split("Good signature from")[1].strip().strip("\"")
871+ except IndexError:
872+ return None
873+
874+def escape_email(text):
875+ """Takes a string and replaces <, > and & with their HTML representation."""
876+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
877+
878+def package_to_html(package):
879+ """Gives the user and uploader field in a package tuple the appropriate
880+ values expected by status.tpl (both fields escaped, and the uploader field
881+ empty if it's the same as the user)."""
882+
883+ package = list(package)
884+
885+ if package[4]:
886+ if package[3]:
887+ user_name = parseaddr(package[3])[0]
888+ uploader_name = parseaddr(package[4])[0]
889+
890+ if user_name == uploader_name:
891+ package[4] = ""
892+ else:
893+ package[3] = package[4]
894+ package[4] = ""
895+ elif not package[3]:
896+ package[3] = "&nbsp;"
897+
898+ package[3] = escape_email(package[3])
899+ package[4] = escape_email(package[4])
900+ package.append(pathhash(package[2]))
901+
902+ return package
903+
904+def write_status_page(component, merges, left_distro, right_distro, manual):
905+ """Write out the merge status page."""
906+
907+ status_file = "%s/merges/%s.html" % (ROOT, component)
908+ status = open(status_file + ".new", "w")
909+
910+ try:
911+ sections = []
912+
913+ for section in SECTIONS:
914+ section_packages = [ package_to_html(m) for m in merges
915+ if m[0] == section ]
916+ sections.append({
917+ "name": section,
918+ "title": section.title(),
919+ "len": len(section_packages),
920+ "packages": section_packages,
921+ })
922+
923+ html = tinpy.build(open("templates/status.tpl").read(),
924+ root = ROOT, component = component, sections = sections,
925+ manual = manual, not_manual = not manual)
926+
927+ status.write(html)
928+
929+ finally:
930+ status.close()
931+
932+ os.rename(status_file + ".new", status_file)
933
934=== added file 'util/tinpy.py'
935--- util/tinpy.py 1970-01-01 00:00:00 +0000
936+++ util/tinpy.py 2009-07-17 17:24:13 +0000
937@@ -0,0 +1,326 @@
938+# -*- coding: utf-8 -*-
939+#
940+# Tiny Python Template (tinpy). Version 2.4.
941+# http://sourceforge.net/projects/tinpy/
942+#
943+# Copyright © 2001-2006 Keisuke URAGO <bravo@resourcez.org>
944+# Released under the MIT License.
945+
946+import string
947+import re
948+from StringIO import StringIO
949+
950+class TemplateParser:
951+ "Template parser."
952+
953+ def __init__(self):
954+ # Template syntax pattern. e.g. "[%var foobar%]"
955+ self.tppattern = re.compile('(\[%.*?%\])', re.DOTALL)
956+ self.lineno = 1
957+ self.chunk = ''
958+
959+ def set_processhandler(self, handler):
960+ self.handler = handler
961+
962+ def parse(self, source):
963+ "Parsing source. source is string"
964+ for chunk in self.tppattern.split(source):
965+ self.lineno = self.lineno + chunk.count('\n')
966+ self.chunk = chunk
967+ if self.tppattern.match(chunk):
968+ apply(self.handler.handle, chunk[2:-2].split())
969+ continue
970+ try: self.handler.handle_data(chunk)
971+ except: pass
972+ if hasattr(self.handler, 'end_document'):
973+ self.handler.end_document()
974+
975+class ProcessingHandler:
976+ "Parser processing handler."
977+
978+ def __init__(self):
979+ self.template = TemplateManager(Template())
980+ self.in_comment = 0
981+
982+ def op_comment(self, arg):
983+ if arg == 'begin':
984+ self.in_comment = 1
985+ elif arg == 'end':
986+ self.in_comment = 0
987+ else:
988+ raise SyntaxError, "invalid syntax: comment %s" % str(arg)
989+
990+ def op_stag(self, *args):
991+ if len(args) != 0:
992+ raise SyntaxError, \
993+ "invalid syntax: %s" % string.join(list(args))
994+ self.template.write('[%%')
995+
996+ def op_etag(self, *args):
997+ if len(args) != 0:
998+ raise SyntaxError, \
999+ "invalid syntax: %s" % string.join(list(args))
1000+ self.template.write('%%]')
1001+
1002+ def op_var(self, *args):
1003+ if len(args) != 1:
1004+ raise SyntaxError, \
1005+ "invalid syntax: args: %s" % string.join(list(args))
1006+ self.template.write('%%(%s)s' % args[0])
1007+
1008+ def op_for(self, *args):
1009+ args = list(args)
1010+ try:
1011+ in_index = args.index('in')
1012+ vars = [token.strip() for token in ''.join(args[:in_index]).split(',')]
1013+ if len(vars) == 1:
1014+ vars = vars[0]
1015+ srclist = ''.join(args[in_index+1:])
1016+ except Exception, e:
1017+ raise SyntaxError, "invalid syntax: '%s' %s" % (string.join(list(args)), e)
1018+ self.template.forblock_begin(vars, srclist)
1019+
1020+ def op_done(self):
1021+ self.template.forblock_end()
1022+
1023+ def op_if(self, *args):
1024+ if len(args) != 1:
1025+ raise SyntaxError, \
1026+ "invalid syntax: args: %s" % string.join(list(args))
1027+ self.template.ifblock_begin(args[0])
1028+
1029+ def op_endif(self):
1030+ self.template.ifblock_end()
1031+
1032+ def handle_data(self, chunk):
1033+ if self.in_comment == 1:
1034+ return
1035+ self.template.write(chunk.replace('%', '%%'))
1036+
1037+ def end_document(self):
1038+ self.template.end_document()
1039+
1040+ def handle(self, op, *tokens):
1041+ # Comment out process.
1042+ if op == 'comment':
1043+ self.op_comment(string.join(tokens))
1044+ return
1045+ if self.in_comment:
1046+ return
1047+
1048+ # Handle operation.
1049+ apply(getattr(self, 'op_' + op), tokens)
1050+
1051+class Template:
1052+ "Template object."
1053+
1054+ def __init__(self):
1055+ self.nodelist = []
1056+ self.initdescriptor()
1057+
1058+ def initdescriptor(self):
1059+ self.__descriptor = StringIO()
1060+
1061+ def write(self, s):
1062+ self.__descriptor.write(s)
1063+
1064+ def getvalue(self):
1065+ return self.__descriptor.getvalue()
1066+
1067+class ForBlock(Template):
1068+ "For block object."
1069+
1070+ def __init__(self, parent, seqvarnames, seqname):
1071+ Template.__init__(self)
1072+ self.parent = parent
1073+
1074+ self.seqvarnames = seqvarnames
1075+ self.seqname = seqname
1076+
1077+class IfBlock(Template):
1078+ "For block object."
1079+
1080+ def __init__(self, parent, varname):
1081+ Template.__init__(self)
1082+ self.parent = parent
1083+
1084+ self.varname = varname
1085+
1086+class DictEnhanceAccessor:
1087+ "Variable dictionary enhance interface."
1088+
1089+ p = re.compile('^(?P<var>[a-zA-Z_][a-zA-Z0-9_]*)(?P<modifier>(\[.+?\])+)$')
1090+ d = re.compile('(\[[a-zA-Z_][a-zA-Z0-9_]*\])')
1091+
1092+ def __init__(self, strict, dic={}):
1093+ self.strict = strict
1094+ self.dic = dic
1095+
1096+ def __getitem__(self, key):
1097+ m = self.p.match(key)
1098+ if not m: return self.dic.get(key, '') # Normal key.
1099+ val = self.dic.get(m.group('var'), '')
1100+ buf = StringIO()
1101+ for mo in self.d.split(m.group('modifier')):
1102+ if self.d.match(mo):
1103+ buf.write("['%s']" % self.dic[mo[1:-1]])
1104+ else:
1105+ buf.write(mo)
1106+ try:
1107+ return eval('val'+ buf.getvalue(), # code
1108+ {'__builtins__':{}}, # globals is restricted.
1109+ {'val':val}) # locals is only 'val'
1110+ except Exception, e:
1111+ if self.strict: raise Exception, e
1112+ return ''
1113+
1114+ def __setitem__(self, key, val):
1115+ self.dic[key] = val
1116+
1117+ def __repr__(self):
1118+ return str(self.dic)
1119+
1120+class VariableStack:
1121+ "Variable data stack."
1122+
1123+ def __init__(self, variables=None):
1124+ self.stack = []
1125+ if variables:
1126+ self.push(variables)
1127+
1128+ def __getitem__(self, num):
1129+ return self.stack[num]
1130+
1131+ def __repr__(self):
1132+ return str(self.stack)
1133+
1134+ def pop(self):
1135+ return self.stack.pop(0)
1136+
1137+ def push(self, data):
1138+ self.stack.insert(0, data)
1139+
1140+ def find(self, varname):
1141+ value = None
1142+ for variables in self.stack:
1143+ if variables.has_key(varname):
1144+ value = variables[varname]
1145+ break
1146+ return value
1147+
1148+ def normalize(self):
1149+ mapitem = {}
1150+ for variables in self.stack:
1151+ for key in variables.keys():
1152+ if not mapitem.has_key(key):
1153+ mapitem[key] = variables[key]
1154+ return mapitem
1155+
1156+class TemplateManager:
1157+ "Template management object."
1158+
1159+ def __init__(self, template):
1160+ self.template = template
1161+ self.currentnode = self.template
1162+
1163+ def __pprint(self, nodelist):
1164+ from types import StringType
1165+ for node in nodelist:
1166+ if type(node) == StringType:
1167+ print node
1168+ else:
1169+ print node, 'for %s in %s' % (node.seqvarnames, node.seqname)
1170+ self.__pprint(node.nodelist)
1171+ print '<end of block>'
1172+
1173+ def pprint(self):
1174+ self.__pprint(self.template.nodelist)
1175+
1176+ def write(self, s):
1177+ self.currentnode.write(s)
1178+
1179+ def __crop_chunk(self, node):
1180+ node.nodelist.append(node.getvalue())
1181+ node.initdescriptor()
1182+
1183+ def __append_child(self, node, child):
1184+ node.nodelist.append(child)
1185+
1186+ def ifblock_begin(self, varname):
1187+ self.__crop_chunk(self.currentnode)
1188+ ifblock = IfBlock(self.currentnode, varname)
1189+ self.__append_child(self.currentnode, ifblock)
1190+ self.currentnode = ifblock
1191+
1192+ def ifblock_end(self):
1193+ self.__crop_chunk(self.currentnode)
1194+ self.currentnode = self.currentnode.parent
1195+
1196+ def forblock_begin(self, seqvarnames, seqname):
1197+ self.__crop_chunk(self.currentnode)
1198+ forblock = ForBlock(self.currentnode, seqvarnames, seqname)
1199+ self.__append_child(self.currentnode, forblock)
1200+ self.currentnode = forblock
1201+
1202+ def forblock_end(self):
1203+ self.__crop_chunk(self.currentnode)
1204+ self.currentnode = self.currentnode.parent
1205+
1206+ def end_document(self):
1207+ self.__crop_chunk(self.currentnode)
1208+
1209+class TemplateRenderer:
1210+ "Template renderer."
1211+
1212+ def __init__(self, template, variable, strict):
1213+ self.varstack = VariableStack(variable)
1214+ self.template = template
1215+ self.strict = strict
1216+ self.__buffer = StringIO()
1217+
1218+ def render(self):
1219+ self.__build(self.template.nodelist)
1220+ return self.__buffer.getvalue()
1221+
1222+ def __build(self, nodelist):
1223+ for node in nodelist:
1224+ varmap = DictEnhanceAccessor(self.strict, self.varstack.normalize())
1225+ if isinstance(node, IfBlock):
1226+ if varmap[node.varname]:
1227+ self.__build(node.nodelist)
1228+ elif isinstance(node, ForBlock):
1229+ for var in varmap[node.seqname]:
1230+ if type(node.seqvarnames) == str:
1231+ self.varstack.push({node.seqvarnames: var})
1232+ else:
1233+ self.varstack.push(dict(zip(node.seqvarnames, var)))
1234+ self.__build(node.nodelist)
1235+ self.varstack.pop()
1236+ else:
1237+ self.__buffer.write(node % varmap)
1238+
1239+class ParseError(Exception):
1240+ 'Parse error'
1241+
1242+def compile(template):
1243+ "Compile from template characters."
1244+
1245+ tpl = TemplateParser()
1246+ hn = ProcessingHandler()
1247+ tpl.set_processhandler(hn)
1248+ try:
1249+ tpl.parse(template)
1250+ except SyntaxError, e:
1251+ raise SyntaxError, "line %d, in '%s' %s" % (
1252+ tpl.lineno, tpl.chunk, e)
1253+ return hn.template.template
1254+
1255+def build(template, vardict=None, strict=False, **kw):
1256+ "Building document from template and variables."
1257+
1258+ if vardict == None: vardict = {}
1259+ for key in kw.keys():
1260+ vardict[key] = kw[key]
1261+ if not isinstance(template, Template):
1262+ template = compile(template)
1263+ return TemplateRenderer(template, vardict, strict).render()
1264
1265=== modified file 'util/tree.py'
1266--- util/tree.py 2008-01-10 13:19:44 +0000
1267+++ util/tree.py 2008-03-25 19:58:32 +0000
1268@@ -1,4 +1,3 @@
1269-#!/usr/bin/env python
1270 # -*- coding: utf-8 -*-
1271 # util/tree.py - useful functions for dealing with trees of files
1272 #

Subscribers

People subscribed via source and target branches

to status/vote changes: