Merge ~cjwatson/launchpadlib:tidy-contrib into launchpadlib:main
- Git
- lp:~cjwatson/launchpadlib
- tidy-contrib
- Merge into main
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | f7fc211d5fa3ff39ba11f3dcdb9346c12c4ca971 |
Proposed branch: | ~cjwatson/launchpadlib:tidy-contrib |
Merge into: | launchpadlib:main |
Diff against target: |
1124 lines (+1/-8) 2 files modified
NEWS.rst (+1/-0) dev/null (+0/-8) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jürgen Gmach | Approve | ||
Review via email: mp+410882@code.launchpad.net |
Commit message
Remove some obsolete scripts from contrib/
Description of the change
`editmoin` is a separate project that has nothing to do with launchpadlib.
`close_
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/NEWS.rst b/NEWS.rst |
2 | index f9be974..4f3f24f 100644 |
3 | --- a/NEWS.rst |
4 | +++ b/NEWS.rst |
5 | @@ -5,6 +5,7 @@ NEWS for launchpadlib |
6 | 1.10.16 |
7 | ======= |
8 | - Add ``pre-commit`` configuration. |
9 | +- Remove some obsolete scripts from ``contrib/``. |
10 | |
11 | 1.10.15.1 (2021-10-27) |
12 | ====================== |
13 | diff --git a/contrib/close_bugs_from_commits.conf.sample b/contrib/close_bugs_from_commits.conf.sample |
14 | deleted file mode 100644 |
15 | index 2fca4ae..0000000 |
16 | --- a/contrib/close_bugs_from_commits.conf.sample |
17 | +++ /dev/null |
18 | @@ -1,18 +0,0 @@ |
19 | -[Project] |
20 | -# A comma separated list of Launchpad project names to close bugs for. |
21 | -# Only bugs targeted to these projects are closed. |
22 | -name = malone,launchpad |
23 | - |
24 | -[Branch States] |
25 | -# Which revnos were last looked through for each branch? The next time |
26 | -# the script run, it will start looking at revno+1. |
27 | -db = 7753 |
28 | -devel = 7845 |
29 | - |
30 | -[Branch Locations] |
31 | -# Which branches should be looked through for bug fixes? The left-hand |
32 | -# column is the branch name, and will be used in the comment saying that a |
33 | -# bug was fixed (e.g. "Fixed in db r7000."). |
34 | -db = bzr+ssh://bazaar.launchpad.net/~launchpad-pqm/launchpad/db-devel |
35 | -devel = bzr+ssh://bazaar.launchpad.net/~launchpad-pqm/launchpad/devel |
36 | - |
37 | diff --git a/contrib/close_bugs_from_commits.py b/contrib/close_bugs_from_commits.py |
38 | deleted file mode 100755 |
39 | index cd92d95..0000000 |
40 | --- a/contrib/close_bugs_from_commits.py |
41 | +++ /dev/null |
42 | @@ -1,119 +0,0 @@ |
43 | -#!/usr/bin/env python |
44 | - |
45 | -# Copyright (C) 2009 Canonical Ltd. |
46 | -# |
47 | -# This file is part of launchpadlib. |
48 | -# |
49 | -# launchpadlib is free software: you can redistribute it and/or modify it |
50 | -# under the terms of the GNU Lesser General Public License as published by the |
51 | -# Free Software Foundation, version 3 of the License. |
52 | -# |
53 | -# launchpadlib is distributed in the hope that it will be useful, but WITHOUT |
54 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
55 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
56 | -# for more details. |
57 | -# |
58 | -# You should have received a copy of the GNU Lesser General Public License |
59 | -# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>. |
60 | - |
61 | -from __future__ import with_statement |
62 | - |
63 | -import re |
64 | -import os |
65 | -import sys |
66 | -from ConfigParser import RawConfigParser |
67 | - |
68 | -from bzrlib.branch import Branch |
69 | - |
70 | -from launchpadlib.launchpad import Launchpad |
71 | - |
72 | -fixed_bug_freeform_re = re.compile( |
73 | - r'\(fixes bugs{0,1} (\d+(?:,\s*\d+)*)\)', re.IGNORECASE) |
74 | -fixed_bug_structured_re = re.compile( |
75 | - r'\[bugs{0,1}[= ](\d+(?:,\s*\d+)*)\]', re.IGNORECASE) |
76 | - |
77 | - |
78 | -def get_fixed_bug_ids(revision): |
79 | - """Try to extract which bugs were fixed by this revision.""" |
80 | - fixed_bugs = set() |
81 | - commit_message = ' '.join(revision.message.split()) |
82 | - match = fixed_bug_freeform_re.search(commit_message) |
83 | - if match is None: |
84 | - match = fixed_bug_structured_re.search(commit_message) |
85 | - if match is not None: |
86 | - fixed_bugs.update(int(bug_id) for bug_id in match.group(1).split(',')) |
87 | - # TODO: Search revision properties. |
88 | - return fixed_bugs |
89 | - |
90 | - |
91 | -def get_config(config_filepath): |
92 | - config_parser = RawConfigParser() |
93 | - config_parser.read([config_filepath]) |
94 | - branches = dict( |
95 | - (name, dict(location=location)) |
96 | - for name, location in config_parser.items('Branch Locations')) |
97 | - for name in branches: |
98 | - branches[name]['last_revno'] = config_parser.getint( |
99 | - 'Branch States', name) |
100 | - projects = config_parser.get('Project', 'name').split(',') |
101 | - return branches, projects |
102 | - |
103 | - |
104 | -def set_last_revno(config_filepath, branch_name, revno): |
105 | - config_parser = RawConfigParser() |
106 | - config_parser.read([config_filepath]) |
107 | - config_parser.set('Branch States', branch_name, revno) |
108 | - with open(config_filepath, 'w') as config_file: |
109 | - config_parser.write(config_file) |
110 | - |
111 | - |
112 | -def main(): |
113 | - launchpad = Launchpad.login_with(os.path.basename(sys.argv[0]), |
114 | - 'production') |
115 | - |
116 | - branches, project_names = get_config('close_bugs_from_commits.conf') |
117 | - |
118 | - projects_links = [] |
119 | - for project_name in project_names: |
120 | - projects_links.append(launchpad.projects[project_name].self_link) |
121 | - for branch_name, branch_info in branches.items(): |
122 | - branch = Branch.open(branch_info['location']) |
123 | - repository = branch.repository |
124 | - start_revno = branch_info['last_revno'] + 1 |
125 | - for revno in range(start_revno, branch.revno()+1): |
126 | - rev_id = branch.get_rev_id(revno) |
127 | - revision = repository.get_revision(rev_id) |
128 | - fixed_bugs = get_fixed_bug_ids(revision) |
129 | - for fixed_bug in sorted(fixed_bugs): |
130 | - try: |
131 | - lp_bug = launchpad.bugs[int(fixed_bug)] |
132 | - except KeyError: |
133 | - # Invalid bug id specified, skip it. |
134 | - continue |
135 | - for bug_task in lp_bug.bug_tasks: |
136 | - if bug_task.target.self_link in projects_links: |
137 | - break |
138 | - else: |
139 | - # The bug wasn't targeted to our project. |
140 | - continue |
141 | - fixed_statuses = [u'Fix Committed', u'Fix Released'] |
142 | - if bug_task.status not in fixed_statuses: |
143 | - print "Marking bug %s as fixed in r%s." % ( |
144 | - lp_bug.id, revno) |
145 | - branch_location = branch_info['location'].replace( |
146 | - 'bzr+ssh', 'http') |
147 | - codebrowse_url = ( |
148 | - branch_location + '/revision/' + str(revno)) |
149 | - bug_task.transitionToStatus(status=u'Fix Committed') |
150 | - bug_task.bug.newMessage( |
151 | - subject=u'Bug fixed by a commit', |
152 | - content=u'Fixed in %s r%s <%s>' % ( |
153 | - branch_name, revno, codebrowse_url)) |
154 | - set_last_revno( |
155 | - 'close_bugs_from_commits.conf', branch_name, revno) |
156 | - return 0 |
157 | - |
158 | - |
159 | - |
160 | -if __name__ == '__main__': |
161 | - sys.exit(main()) |
162 | diff --git a/contrib/editmoin.py b/contrib/editmoin.py |
163 | deleted file mode 100755 |
164 | index 01b48e4..0000000 |
165 | --- a/contrib/editmoin.py |
166 | +++ /dev/null |
167 | @@ -1,423 +0,0 @@ |
168 | -#!/usr/bin/env python |
169 | - |
170 | -""" |
171 | -Copyright (c) 2002-2006 Gustavo Niemeyer <gustavo@niemeyer.net> |
172 | - |
173 | -This program allows you to edit moin (see http://moin.sourceforge.net) |
174 | -pages with your preferred editor. The default editor is vi. If you want |
175 | -to use any other, just set the EDITOR environment variable. |
176 | - |
177 | -To define your moin id used when logging in in a specifc moin, edit a |
178 | -file named ~/.moin_ids and include lines like "http://moin.url/etc myid". |
179 | - |
180 | -WARNING: This program expects information to be in a very specific |
181 | - format. It will break if this format changes, so there are |
182 | - no warranties of working at all. All I can say is that it |
183 | - worked for me, at least once. ;-) |
184 | - |
185 | -Tested moin versions: 0.9, 0.11, 1.0, 1.1, 1.3.5, 1.5, 1.5.1, 1.5.4, 1.5.5, |
186 | - 1.6, 1.6.1 |
187 | -""" |
188 | - |
189 | -__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>" |
190 | -__version__ = "1.10.1" |
191 | -__license__ = "GPL" |
192 | - |
193 | -import tempfile |
194 | -import textwrap |
195 | -import sys, os |
196 | -import urllib |
197 | -import shutil |
198 | -import md5 |
199 | -import re |
200 | -import subprocess |
201 | - |
202 | - |
203 | -USAGE = "Usage: editmoin [-t <template page>] <moin page URL>\n" |
204 | - |
205 | -IDFILENAME = os.path.expanduser("~/.moin_ids") |
206 | -ALIASFILENAME = os.path.expanduser("~/.moin_aliases") |
207 | - |
208 | - |
209 | -BODYRE = re.compile('<textarea.*?name="savetext".*?>(.*)</textarea>', |
210 | - re.M|re.DOTALL) |
211 | -DATESTAMPRE = re.compile('<input.*?name="datestamp".*?value="(.*?)".*?>') |
212 | -NOTIFYRE = re.compile('<input.*?name="notify".*?value="(.*?)".*?>') |
213 | -COMMENTRE = re.compile('<input.*?name="comment".*>') |
214 | -TRIVIALRE = re.compile('<input.*?name="trivial".*?value="(.*?)".*?>') |
215 | -MESSAGERE1 = re.compile('^</table>(.*?)<a.*?>Clear message</a>', |
216 | - re.M|re.DOTALL) |
217 | -MESSAGERE2 = re.compile('<div class="message">(.*?)</div>', re.M|re.DOTALL) |
218 | -MESSAGERE3 = re.compile('<div id="message">\s*<p>(.*?)</p>', re.M|re.DOTALL) |
219 | -STATUSRE = re.compile('<p class="status">(.*?)</p>', re.M|re.DOTALL) |
220 | -CANCELRE = re.compile('<input.*?type="submit" name="button_cancel" value="(.*?)">') |
221 | -EDITORRE = re.compile('<input.*?type="hidden" name="editor" value="text">') |
222 | -TICKETRE = re.compile('<input.*?type="hidden" name="ticket" value="(.*?)">') |
223 | -REVRE = re.compile('<input.*?type="hidden" name="rev" value="(.*?)">') |
224 | -CATEGORYRE = re.compile('<option value="(Category\w+?)">') |
225 | -SELECTIONRE = re.compile("\(([^)]*)\)\s*([^(]*)") |
226 | -EXTENDMSG = "Use the Preview button to extend the locking period." |
227 | - |
228 | - |
229 | -marker = object() |
230 | - |
231 | - |
232 | -class Error(Exception): pass |
233 | - |
234 | - |
235 | -class MoinFile: |
236 | - |
237 | - multi_selection = ["notify", "trivial", "add_category"] |
238 | - |
239 | - def __init__(self, filename, id, has_moin_session): |
240 | - self.filename = filename |
241 | - self.id = id |
242 | - self.has_moin_session = has_moin_session |
243 | - self.data = open(filename).read() |
244 | - self.body = self._get_data(BODYRE, "body") |
245 | - |
246 | - try: |
247 | - self.datestamp = self._get_data(DATESTAMPRE, "datestamp") |
248 | - except Error: |
249 | - self.datestamp = None |
250 | - |
251 | - try: |
252 | - self.notify = self._get_data(NOTIFYRE, "notify") |
253 | - self.comment = "None" |
254 | - except Error: |
255 | - self.notify = None |
256 | - if COMMENTRE.search(self.data): |
257 | - self.comment = "None" |
258 | - else: |
259 | - self.comment = None |
260 | - |
261 | - try: |
262 | - self.trivial = self._get_data(TRIVIALRE, "trivial") |
263 | - except Error: |
264 | - self.trivial = None |
265 | - |
266 | - self.categories = self._get_data_findall(CATEGORYRE, "category", []) |
267 | - self.add_category = None |
268 | - |
269 | - match = STATUSRE.search(self.data) |
270 | - if match: |
271 | - self.status = strip_html(match.group(1)) |
272 | - else: |
273 | - self.status = None |
274 | - |
275 | - self.rev = self._get_data(REVRE, "rev", None) |
276 | - self.ticket = self._get_data(TICKETRE, "ticket", None) |
277 | - |
278 | - def _get_data(self, pattern, info, default=marker): |
279 | - match = pattern.search(self.data) |
280 | - if not match: |
281 | - if default is not marker: |
282 | - return default |
283 | - message = get_message(self.data) |
284 | - if message: |
285 | - print message |
286 | - raise Error, info+" information not found" |
287 | - else: |
288 | - return match.group(1) |
289 | - |
290 | - def _get_data_findall(self, pattern, info, default=marker): |
291 | - groups = pattern.findall(self.data) |
292 | - if not groups: |
293 | - if default is not marker: |
294 | - return default |
295 | - raise Error, info+" information not found" |
296 | - return groups |
297 | - |
298 | - def _get_selection(self, str): |
299 | - for selected, option in SELECTIONRE.findall(str): |
300 | - if selected.strip(): |
301 | - return option.strip() |
302 | - return None |
303 | - |
304 | - def _unescape(self, data): |
305 | - data = data.replace("<", "<") |
306 | - data = data.replace(">", ">") |
307 | - data = data.replace("&", "&") |
308 | - return data |
309 | - |
310 | - def has_cancel(self): |
311 | - return (CANCELRE.search(self.data) is not None) |
312 | - |
313 | - def has_editor(self): |
314 | - return (EDITORRE.search(self.data) is not None) |
315 | - |
316 | - def write_raw(self): |
317 | - filename = tempfile.mktemp(".moin") |
318 | - file = open(filename, "w") |
319 | - if self.has_moin_session: |
320 | - syntax_version = "1.6" |
321 | - else: |
322 | - syntax_version = "1.5" |
323 | - file.write("@@ Syntax: %s\n" % syntax_version) |
324 | - if not self.id: |
325 | - file.write("@@ WARNING! You're NOT logged in!\n") |
326 | - if self.status is not None: |
327 | - text = self.status.replace(EXTENDMSG, "").strip() |
328 | - lines = textwrap.wrap(text, 70, |
329 | - initial_indent="@@ Message: ", |
330 | - subsequent_indent="@ ") |
331 | - for line in lines: |
332 | - file.write(line+"\n") |
333 | - if self.comment is not None: |
334 | - file.write("@@ Comment: %s\n" % self.comment) |
335 | - if self.trivial is not None: |
336 | - file.write("@@ Trivial: ( ) Yes (x) No\n") |
337 | - if self.notify is not None: |
338 | - yes, no = (self.notify and ("x", " ") or (" ", "x")) |
339 | - file.write("@@ Notify: (%s) Yes (%s) No\n" % (yes, no)) |
340 | - if self.categories: |
341 | - file.write("@@ Add category: (x) None\n") |
342 | - for category in self.categories: |
343 | - file.write("@ ( ) %s\n" % category) |
344 | - file.write(self._unescape(self.body)) |
345 | - file.close() |
346 | - return filename |
347 | - |
348 | - def read_raw(self, filename): |
349 | - file = open(filename) |
350 | - lines = [] |
351 | - data = file.readline() |
352 | - while data != "\n": |
353 | - if data[0] != "@": |
354 | - break |
355 | - if len(data) < 2: |
356 | - pass |
357 | - elif data[1] == "@": |
358 | - lines.append(data[2:].strip()) |
359 | - else: |
360 | - if not lines: |
361 | - lines.append("") |
362 | - lines[-1] += " " |
363 | - lines[-1] += data[2:].strip() |
364 | - data = file.readline() |
365 | - self.body = data+file.read() |
366 | - file.close() |
367 | - for line in lines: |
368 | - sep = line.find(":") |
369 | - if sep != -1: |
370 | - attr = line[:sep].lower().replace(' ', '_') |
371 | - value = line[sep+1:].strip() |
372 | - if attr in self.multi_selection: |
373 | - setattr(self, attr, self._get_selection(value)) |
374 | - else: |
375 | - setattr(self, attr, value) |
376 | - |
377 | -def get_message(data): |
378 | - match = MESSAGERE3.search(data) |
379 | - if not match: |
380 | - # Check for moin < 1.3.5 (not sure the precise version it changed). |
381 | - match = MESSAGERE2.search(data) |
382 | - if not match: |
383 | - # Check for moin <= 0.9. |
384 | - match = MESSAGERE1.search(data) |
385 | - if match: |
386 | - return strip_html(match.group(1)) |
387 | - return None |
388 | - |
389 | -def strip_html(data): |
390 | - data = re.subn("\n", " ", data)[0] |
391 | - data = re.subn("<p>|<br>", "\n", data)[0] |
392 | - data = re.subn("<.*?>", "", data)[0] |
393 | - data = re.subn("Clear data", "", data)[0] |
394 | - data = re.subn("[ \t]+", " ", data)[0] |
395 | - data = data.strip() |
396 | - return data |
397 | - |
398 | -def get_id(moinurl): |
399 | - if os.path.isfile(IDFILENAME): |
400 | - file = open(IDFILENAME) |
401 | - for line in file.readlines(): |
402 | - line = line.strip() |
403 | - if line and line[0] != "#": |
404 | - tokens = line.split() |
405 | - if len(tokens) > 1: |
406 | - url, id = tokens[:2] |
407 | - else: |
408 | - url, id = tokens[0], None |
409 | - if moinurl.startswith(url): |
410 | - return id |
411 | - return None |
412 | - |
413 | -def translate_shortcut(moinurl): |
414 | - if "://" in moinurl: |
415 | - return moinurl |
416 | - if "/" in moinurl: |
417 | - shortcut, pathinfo = moinurl.split("/", 1) |
418 | - else: |
419 | - shortcut, pathinfo = moinurl, "" |
420 | - if os.path.isfile(ALIASFILENAME): |
421 | - file = open(ALIASFILENAME) |
422 | - try: |
423 | - for line in file.readlines(): |
424 | - line = line.strip() |
425 | - if line and line[0] != "#": |
426 | - alias, value = line.split(None, 1) |
427 | - if pathinfo: |
428 | - value = "%s/%s" % (value, pathinfo) |
429 | - if shortcut == alias: |
430 | - if "://" in value: |
431 | - return value |
432 | - if "/" in value: |
433 | - shortcut, pathinfo = value.split("/", 1) |
434 | - else: |
435 | - shortcut, pathinfo = value, "" |
436 | - finally: |
437 | - file.close() |
438 | - if os.path.isfile(IDFILENAME): |
439 | - file = open(IDFILENAME) |
440 | - try: |
441 | - for line in file.readlines(): |
442 | - line = line.strip() |
443 | - if line and line[0] != "#": |
444 | - url = line.split()[0] |
445 | - if shortcut in url: |
446 | - if pathinfo: |
447 | - return "%s/%s" % (url, pathinfo) |
448 | - else: |
449 | - return url |
450 | - finally: |
451 | - file.close() |
452 | - raise Error, "no suitable url found for shortcut '%s'" % shortcut |
453 | - |
454 | - |
455 | -def get_urlopener(moinurl, id=None): |
456 | - urlopener = urllib.FancyURLopener() |
457 | - proxy = os.environ.get("http_proxy") |
458 | - if proxy: |
459 | - urlopener.proxies.update({"http": proxy}) |
460 | - if id: |
461 | - # moinmoin < 1.6 |
462 | - urlopener.addheader("Cookie", "MOIN_ID=\"%s\"" % id) |
463 | - # moinmoin >= 1.6 |
464 | - urlopener.addheader("Cookie", "MOIN_SESSION=\"%s\"" % id) |
465 | - return urlopener |
466 | - |
467 | -def fetchfile(urlopener, url, id, template): |
468 | - geturl = url+"?action=edit" |
469 | - if template: |
470 | - geturl += "&template=" + urllib.quote(template) |
471 | - filename, headers = urlopener.retrieve(geturl) |
472 | - has_moin_session = "MOIN_SESSION" in headers.get("set-cookie", "") |
473 | - return MoinFile(filename, id, has_moin_session) |
474 | - |
475 | -def editfile(moinfile): |
476 | - edited = 0 |
477 | - filename = moinfile.write_raw() |
478 | - editor = os.environ.get("EDITOR", "vi") |
479 | - digest = md5.md5(open(filename).read()).digest() |
480 | - subprocess.call([editor, filename]) |
481 | - if digest != md5.md5(open(filename).read()).digest(): |
482 | - shutil.copyfile(filename, os.path.expanduser("~/.moin_lastedit")) |
483 | - edited = 1 |
484 | - moinfile.read_raw(filename) |
485 | - os.unlink(filename) |
486 | - return edited |
487 | - |
488 | -def sendfile(urlopener, url, moinfile): |
489 | - if moinfile.comment is not None: |
490 | - comment = "&comment=" |
491 | - if moinfile.comment.lower() != "none": |
492 | - comment += urllib.quote(moinfile.comment) |
493 | - else: |
494 | - comment = "" |
495 | - data = "button_save=1&savetext=%s%s" \ |
496 | - % (urllib.quote(moinfile.body), comment) |
497 | - if moinfile.has_editor(): |
498 | - data += "&action=edit" # Moin >= 1.5 |
499 | - else: |
500 | - data += "&action=savepage" # Moin < 1.5 |
501 | - if moinfile.datestamp: |
502 | - data += "&datestamp=" + moinfile.datestamp |
503 | - if moinfile.rev: |
504 | - data += "&rev=" + moinfile.rev |
505 | - if moinfile.ticket: |
506 | - data += "&ticket=" + moinfile.ticket |
507 | - if moinfile.notify == "Yes": |
508 | - data += "¬ify=1" |
509 | - if moinfile.trivial == "Yes": |
510 | - data += "&trivial=1" |
511 | - if moinfile.add_category and moinfile.add_category != "None": |
512 | - data += "&category=" + urllib.quote(moinfile.add_category) |
513 | - url = urlopener.open(url, data) |
514 | - answer = url.read() |
515 | - url.close() |
516 | - message = get_message(answer) |
517 | - if message is None: |
518 | - print answer |
519 | - raise Error, "data submitted, but message information not found" |
520 | - else: |
521 | - print message |
522 | - |
523 | -def sendcancel(urlopener, url, moinfile): |
524 | - if not moinfile.has_cancel(): |
525 | - return |
526 | - data = "button_cancel=Cancel" |
527 | - if moinfile.has_editor(): |
528 | - data += "&action=edit&savetext=dummy" # Moin >= 1.5 |
529 | - else: |
530 | - data += "&action=savepage" # Moin < 1.5 |
531 | - if moinfile.datestamp: |
532 | - data += "&datestamp=" + moinfile.datestamp |
533 | - if moinfile.rev: |
534 | - data += "&rev=" + moinfile.rev |
535 | - if moinfile.ticket: |
536 | - data += "&ticket=" + moinfile.ticket |
537 | - url = urlopener.open(url, data) |
538 | - answer = url.read() |
539 | - url.close() |
540 | - message = get_message(answer) |
541 | - if not message: |
542 | - print answer |
543 | - raise Error, "cancel submitted, but message information not found" |
544 | - else: |
545 | - print message |
546 | - |
547 | - |
548 | -def editshortcut(shortcut, template=None, editfile_func=editfile): |
549 | - """Edit a Moin page at a shortcut path. |
550 | - |
551 | - By default the editfile() function is used to actually edit the |
552 | - page, but a custom one can be passed in as the editfile_func |
553 | - parameter. |
554 | - |
555 | - Return True if the page as edited, otherwise False. |
556 | - """ |
557 | - url = translate_shortcut(shortcut) |
558 | - id = get_id(url) |
559 | - urlopener = get_urlopener(url, id) |
560 | - moinfile = fetchfile(urlopener, url, id, template) |
561 | - try: |
562 | - page_edited = editfile_func(moinfile) |
563 | - if page_edited: |
564 | - sendfile(urlopener, url, moinfile) |
565 | - else: |
566 | - sendcancel(urlopener, url, moinfile) |
567 | - finally: |
568 | - os.unlink(moinfile.filename) |
569 | - return page_edited |
570 | - |
571 | - |
572 | -def main(): |
573 | - argv = sys.argv[1:] |
574 | - template = None |
575 | - if len(argv) > 2 and argv[0] == "-t": |
576 | - template = argv[1] |
577 | - argv = argv[2:] |
578 | - if len(argv) != 1 or argv[0] in ("-h", "--help"): |
579 | - sys.stderr.write(USAGE) |
580 | - sys.exit(1) |
581 | - try: |
582 | - editshortcut(argv[0], template) |
583 | - except (IOError, OSError, Error), e: |
584 | - sys.stderr.write("error: %s\n" % str(e)) |
585 | - sys.exit(1) |
586 | - |
587 | -if __name__ == "__main__": |
588 | - main() |
589 | - |
590 | -# vim:et:ts=4:sw=4 |
591 | diff --git a/contrib/test_close_bugs_from_commits.py b/contrib/test_close_bugs_from_commits.py |
592 | deleted file mode 100644 |
593 | index 11daf2e..0000000 |
594 | --- a/contrib/test_close_bugs_from_commits.py |
595 | +++ /dev/null |
596 | @@ -1,100 +0,0 @@ |
597 | -# Copyright (C) 2009 Canonical Ltd. |
598 | -# |
599 | -# This file is part of launchpadlib. |
600 | -# |
601 | -# launchpadlib is free software: you can redistribute it and/or modify it |
602 | -# under the terms of the GNU Lesser General Public License as published by the |
603 | -# Free Software Foundation, version 3 of the License. |
604 | -# |
605 | -# launchpadlib is distributed in the hope that it will be useful, but WITHOUT |
606 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
607 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
608 | -# for more details. |
609 | -# |
610 | -# You should have received a copy of the GNU Lesser General Public License |
611 | -# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>. |
612 | - |
613 | -"""Test for close_bugs_from_commits.get_fixed_bug_ids().""" |
614 | - |
615 | -import unittest |
616 | - |
617 | -from close_bugs_from_commits import get_fixed_bug_ids |
618 | - |
619 | - |
620 | -class FakeRevision: |
621 | - |
622 | - def __init__(self, message): |
623 | - self.message = message |
624 | - |
625 | - |
626 | -class TestGetFixedBugIds(unittest.TestCase): |
627 | - |
628 | - def test_one_freeform(self): |
629 | - # One bug specified in the format: (fixes bug 42) |
630 | - self.assertEqual( |
631 | - get_fixed_bug_ids(FakeRevision("before (fixes bug 42) after")), |
632 | - set([42])) |
633 | - |
634 | - def test_multiple_freeform(self): |
635 | - # Multiple bugs specified in the format: (fixes bugs 42, 84, 168) |
636 | - self.assertEqual( |
637 | - get_fixed_bug_ids( |
638 | - FakeRevision("before (fixes bug 42, 84, 168) after")), |
639 | - set([42, 84, 168])) |
640 | - self.assertEqual( |
641 | - get_fixed_bug_ids( |
642 | - FakeRevision("before (fixes bug 42,84,168) after")), |
643 | - set([42, 84, 168])) |
644 | - |
645 | - def test_freeform_case_insensitve(self): |
646 | - # The freeform format isn't case sensitive. |
647 | - self.assertEqual( |
648 | - get_fixed_bug_ids(FakeRevision("before (FIXES BUG 42) after")), |
649 | - set([42])) |
650 | - |
651 | - def test_one_structured(self): |
652 | - # One bug specified in the format: [bug=42] |
653 | - self.assertEqual( |
654 | - get_fixed_bug_ids(FakeRevision("before [bug=42]) after")), |
655 | - set([42])) |
656 | - |
657 | - def test_one_structured_no_equal_sign(self): |
658 | - # One bug specified in the format: [bug 42] |
659 | - self.assertEqual( |
660 | - get_fixed_bug_ids(FakeRevision("before [bug 42]) after")), |
661 | - set([42])) |
662 | - |
663 | - def test_multiple_structured(self): |
664 | - # Multiple bugs specified in the format: [bug=42, 84, 168] |
665 | - self.assertEqual( |
666 | - get_fixed_bug_ids( |
667 | - FakeRevision("before [bug=42, 84, 168]) after")), |
668 | - set([42, 84, 168])) |
669 | - |
670 | - def test_multiple_structured_bugs(self): |
671 | - # Multiple bugs specified in the format: [bugs=42, 84, 168] |
672 | - self.assertEqual( |
673 | - get_fixed_bug_ids( |
674 | - FakeRevision("before [bugs=42, 84, 168]) after")), |
675 | - set([42, 84, 168])) |
676 | - self.assertEqual( |
677 | - get_fixed_bug_ids( |
678 | - FakeRevision("before [bugs=42,84,168]) after")), |
679 | - set([42, 84, 168])) |
680 | - |
681 | - def test_mention_bug(self): |
682 | - # Bugs simply mentioned among the commit message shouldn't be |
683 | - # considered being fixed. |
684 | - self.assertEqual( |
685 | - get_fixed_bug_ids( |
686 | - FakeRevision("find bug 4231 after")), |
687 | - set([])) |
688 | - |
689 | - def test_mention_multiple_bugs(self): |
690 | - # Bugs simply mentioned among the commit message shouldn't be |
691 | - # considered being fixed. |
692 | - self.assertEqual( |
693 | - get_fixed_bug_ids( |
694 | - FakeRevision("find bug 123456 after 2345 and also 1234, end.")), |
695 | - set([])) |
696 | - |
697 | diff --git a/contrib/update-milestone-progress.conf.sample b/contrib/update-milestone-progress.conf.sample |
698 | deleted file mode 100644 |
699 | index feaff5b..0000000 |
700 | --- a/contrib/update-milestone-progress.conf.sample |
701 | +++ /dev/null |
702 | @@ -1,43 +0,0 @@ |
703 | -[Branch] |
704 | -# The location of the branch that should be scanned for commits. It's |
705 | -# best to keep a mirror locally, and point to the local mirror. Going |
706 | -# through each commit on a remote branch might take quite a while. |
707 | -location=bzr+ssh://bazaar.launchpad.net/~launchpad-pqm/launchpad/devel |
708 | -# The revision number where we should start scanning for commits. This |
709 | -# should be the revision where release-critical is turned off. |
710 | -start_revno=7677 |
711 | - |
712 | -[Milestone] |
713 | -# The name of the current milestone. |
714 | -name=2.2.2 |
715 | -# The project where bugs should be search in. |
716 | -project=launchpad-project |
717 | -# The day the milestone starts. |
718 | -start=2009-02-02 |
719 | -# The day the milestone ends. |
720 | -end=2009-02-25 |
721 | -# The day PQM gets set to release-critical mode. |
722 | -release-critical=2009-02-20 |
723 | -# The name of the template for the progress wiki page. |
724 | -template=update-milestone.template.sample |
725 | -# The base URL where the wiki page live. The page URL will be this URL |
726 | -# plus the milestone name appended to it. This should be an editmoin |
727 | -# shortcut URL. |
728 | -base_wiki_location=dev.launchpad.net/VersionThreeDotO/Project/Progress |
729 | -# The page where the story definitions are. The story titles on the |
730 | -# progress page will link to this URL, together with an anchor, which is |
731 | -# the story tag name with the 'story-' part removed. |
732 | -stories_page=https://dev.launchpad.net/VersionThreeDotO/Project/StoryCards |
733 | - |
734 | -[Stories] |
735 | -# The list of stories that are scheduled for this milestone. |
736 | -# story_tag_name=story_name_for_display |
737 | -story-hwdb-affected-users=HWDB Affected Users |
738 | -story-hwdb-dmi-lspci=Include DMI and lspci information in HWDB submissions |
739 | -story-hwdb-filter-by-device-and-system=Find out which computers have a certain device |
740 | -story-link-to-upstream-filebug=Link to file bugs upstream |
741 | -story-link-to-upstream-searchbugs=Link to search bugs upstream |
742 | -story-set-remote-product-manually=Enable search and filebug links for a project manually |
743 | -story-set-remote-product-automatically=Enable search and filebug links for a project automatically |
744 | -story-inline-edit-bug-summary-description=Inline editing of bug description and summary |
745 | -story-hwdb-users-using-driver=Get number of users using a driver |
746 | diff --git a/contrib/update-milestone-progress.py b/contrib/update-milestone-progress.py |
747 | deleted file mode 100755 |
748 | index 1c207fc..0000000 |
749 | --- a/contrib/update-milestone-progress.py |
750 | +++ /dev/null |
751 | @@ -1,359 +0,0 @@ |
752 | -#!/usr/bin/env python |
753 | - |
754 | -# Copyright (C) 2009 Canonical Ltd. |
755 | -# |
756 | -# This file is part of launchpadlib. |
757 | -# |
758 | -# launchpadlib is free software: you can redistribute it and/or modify it |
759 | -# under the terms of the GNU Lesser General Public License as published by the |
760 | -# Free Software Foundation, version 3 of the License. |
761 | -# |
762 | -# launchpadlib is distributed in the hope that it will be useful, but WITHOUT |
763 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
764 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
765 | -# for more details. |
766 | -# |
767 | -# You should have received a copy of the GNU Lesser General Public License |
768 | -# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>. |
769 | - |
770 | -from __future__ import with_statement |
771 | -from ConfigParser import RawConfigParser |
772 | -from datetime import date, timedelta |
773 | -import os |
774 | -import sys |
775 | - |
776 | -from bzrlib.branch import Branch |
777 | - |
778 | -from close_bugs_from_commits import get_fixed_bug_ids |
779 | -from editmoin import editshortcut |
780 | -from launchpadlib.launchpad import Launchpad |
781 | - |
782 | - |
783 | -class Config: |
784 | - def __init__(self): |
785 | - self.tag_to_story_name = {} |
786 | - self.milestone_name = None |
787 | - self.start = None |
788 | - self.end = None |
789 | - self.project_name = None |
790 | - self.template_filename = None |
791 | - self.branch_location = None |
792 | - self.branch_start_revno = None |
793 | - self.base_wiki_location = None |
794 | - self.stories_page = None |
795 | - |
796 | - @property |
797 | - def story_tags(self): |
798 | - return self.tag_to_story_name.keys() |
799 | - |
800 | - |
801 | -def get_assignee_name(bugtask): |
802 | - """Get the name of the person assigned to the bug task. |
803 | - |
804 | - Instead of accessing bugtask.assignee.name, the name is extracted |
805 | - from the assignee_link, which avoids fetching the assignee object |
806 | - from the web service. |
807 | - """ |
808 | - if bugtask.assignee_link is None: |
809 | - return None |
810 | - url_parts = bugtask.assignee_link.split('/') |
811 | - return url_parts[-1].lstrip('~') |
812 | - |
813 | - |
814 | -def string_isodate_to_date(string_isodate): |
815 | - if 'T' in string_isodate: |
816 | - string_isodate, rest = string_isodate.split('T', 1) |
817 | - year, month, day = string_isodate.split('-') |
818 | - return date(int(year), int(month), int(day)) |
819 | - |
820 | - |
821 | -class MilestoneState: |
822 | - """The current state of the milestone.""" |
823 | - |
824 | - def __init__(self, start, end, today): |
825 | - self.days = {} |
826 | - self.today = today |
827 | - self.start = start |
828 | - self.end = end |
829 | - one_day = timedelta(days=1) |
830 | - current_day = start |
831 | - while current_day <= end: |
832 | - self.days[current_day] = {} |
833 | - current_day += one_day |
834 | - |
835 | - def mark_done(self, bug_id, day): |
836 | - if day < self.start: |
837 | - day = self.start |
838 | - if day > self.end: |
839 | - # It was fixed after the milestone ended. |
840 | - return |
841 | - self.days[day][bug_id] = 'D' |
842 | - |
843 | - def mark_started(self, bug_id, day): |
844 | - if day not in self.days: |
845 | - day = self.start |
846 | - self.days[day][bug_id] = 'P' |
847 | - |
848 | - def mark_added(self, bug_id, day): |
849 | - self.days[day][bug_id] = 'N' |
850 | - |
851 | - |
852 | -def get_config(config_filepath): |
853 | - """Create a map between committers and their test pages. |
854 | - |
855 | - Read the config file, and return a map, mapping a committer to the |
856 | - name of the wiki page where test plans are held. |
857 | - """ |
858 | - config_parser = RawConfigParser() |
859 | - config_parser.read([config_filepath]) |
860 | - config = Config() |
861 | - |
862 | - config.branch_location = config_parser.get('Branch', 'location') |
863 | - config.branch_start_revno = config_parser.getint('Branch', 'start_revno') |
864 | - |
865 | - config.milestone_name = config_parser.get('Milestone', 'name') |
866 | - config.project_name = config_parser.get('Milestone', 'project') |
867 | - config.template_filename = config_parser.get('Milestone', 'template') |
868 | - config.base_wiki_location = config_parser.get( |
869 | - 'Milestone', 'base_wiki_location') |
870 | - config.stories_page = config_parser.get( |
871 | - 'Milestone', 'stories_page') |
872 | - config.start = string_isodate_to_date( |
873 | - config_parser.get('Milestone', 'start')) |
874 | - config.end = string_isodate_to_date( |
875 | - config_parser.get('Milestone', 'end')) |
876 | - config.release_critical = string_isodate_to_date( |
877 | - config_parser.get('Milestone', 'release_critical')) |
878 | - config.story_tags = [] |
879 | - for tag_name, story_name in config_parser.items('Stories'): |
880 | - config.story_tags.append(tag_name) |
881 | - config.tag_to_story_name[unicode(tag_name)] = story_name |
882 | - return config |
883 | - |
884 | - |
885 | -class Story: |
886 | - |
887 | - def __init__(self, name, tag_name): |
888 | - self.name = name |
889 | - self.tag_name = tag_name |
890 | - self.tasks = [] |
891 | - |
892 | - |
893 | -ALL_BUG_STATUSES = [ |
894 | - "New", |
895 | - "Incomplete", |
896 | - "Invalid", |
897 | - "Won't Fix", |
898 | - "Confirmed", |
899 | - "Triaged", |
900 | - "In Progress", |
901 | - "Fix Committed", |
902 | - "Fix Released", |
903 | - ] |
904 | - |
905 | -content_color = { |
906 | - 'P': '#FF8080', |
907 | - 'N': '#FFFFE0', |
908 | - 'D': '#80FF80', |
909 | - } |
910 | - |
911 | -def get_cell_style(content, fallback_color=None): |
912 | - color = content_color.get(content, fallback_color) |
913 | - if color is not None: |
914 | - return '<style="background-color: %s;">' % color |
915 | - else: |
916 | - return '' |
917 | - |
918 | -def generate_milestone_table(milestone_state, stories, config): |
919 | - table_rows = [] |
920 | - row_items = [ |
921 | - "''Story/Task''", |
922 | - "''Assignee''", |
923 | - ] |
924 | - for day in sorted(milestone_state.days.keys()): |
925 | - row_items.append("''%s''" % str(day.day)) |
926 | - table_rows.append('|| %s ||' % ' || '.join(row_items)) |
927 | - for story in stories: |
928 | - row_items = [] |
929 | - unimportant, story_anchor = story.tag_name.split('-', 1) |
930 | - if story.tag_name == 'unrelated-bugs': |
931 | - row_items.append( |
932 | - '<rowstyle="background-color: #CC6633;">' |
933 | - ' %s' % story.name) |
934 | - else: |
935 | - row_items.append( |
936 | - '<rowstyle="background-color: #E0E0FF;">' |
937 | - " '''[[%s#%s|%s]]'''" % ( |
938 | - config.stories_page, story_anchor, story.name)) |
939 | - |
940 | - row_items.append('') # Assignee |
941 | - task_state = '' |
942 | - for day in sorted(milestone_state.days.keys()): |
943 | - row_items.append(task_state) |
944 | - table_rows.append('||%s ||' % ' ||'.join(row_items)) |
945 | - for bugtask in story.tasks: |
946 | - row_items = [] |
947 | - row_items.append( |
948 | - '[[https://launchpad.net/bugs/%(bug_id)s|#%(bug_id)s]]:' |
949 | - ' %(title)s' % dict( |
950 | - bug_id=bugtask.bug.id, title=bugtask.bug.title)) |
951 | - assignee_name = get_assignee_name(bugtask) |
952 | - if assignee_name is not None: |
953 | - row_items.append(" [[/%s|%s]] "% ( |
954 | - assignee_name, assignee_name)) |
955 | - else: |
956 | - row_items.append('') # Assignee |
957 | - task_state = '' |
958 | - future_color = '' |
959 | - for day, changes in sorted(milestone_state.days.items()): |
960 | - task_state = changes.get(bugtask.bug.id, task_state) |
961 | - if day > milestone_state.today: |
962 | - task_state = '' |
963 | - if day > config.release_critical: |
964 | - future_color = '#C8BBBE' |
965 | - row_items.append("%s %s" % ( |
966 | - get_cell_style(task_state, future_color), task_state)) |
967 | - table_rows.append('|| %s ||' % ' ||'.join(row_items)) |
968 | - |
969 | - return '\n'.join(table_rows) |
970 | - |
971 | - |
972 | - |
973 | -def get_day_added(task, fallback=None): |
974 | - return fallback |
975 | - |
976 | - |
977 | -def get_day_started(task): |
978 | - if task.date_in_progress is not None: |
979 | - return string_isodate_to_date(task.date_in_progress) |
980 | - return None |
981 | - |
982 | - |
983 | -def get_day_done(task): |
984 | - # We should be able to use date_closed, but it seems like it's not |
985 | - # always set. |
986 | - closed_date_attributes = ['date_fix_committed', 'date_fix_released'] |
987 | - for closed_date_attribute in closed_date_attributes: |
988 | - date_closed = getattr(task, closed_date_attribute) |
989 | - if date_closed is not None: |
990 | - return string_isodate_to_date(date_closed) |
991 | - return None |
992 | - |
993 | - |
994 | -def get_associated_story_tags(bugtask): |
995 | - return [tag for tag in bugtask.bug.tags if tag.startswith('story-')] |
996 | - |
997 | - |
998 | -def main(): |
999 | - config = get_config('update-milestone-progress.conf') |
1000 | - launchpad = Launchpad.login_with(os.path.basename(sys.argv[0]), |
1001 | - 'production') |
1002 | - stories = dict( |
1003 | - (story_tag, Story(story_name, story_tag)) |
1004 | - for story_tag, story_name in sorted(config.tag_to_story_name.items())) |
1005 | - # 'unrelated' isn't quite right, but I want to sort on the tag name |
1006 | - # and have the bugs come last. |
1007 | - stories['unrelated-bugs'] = Story( |
1008 | - 'Bugs not related to a story', 'unrelated-bugs') |
1009 | - project = launchpad.projects[config.project_name] |
1010 | - for milestone in project.all_milestones: |
1011 | - if milestone.name == config.milestone_name: |
1012 | - break |
1013 | - else: |
1014 | - raise AssertionError("No milestone: %s" % config.milestone_name) |
1015 | - |
1016 | - today = date.today() |
1017 | - milestone_state = MilestoneState(config.start, config.end, today) |
1018 | - |
1019 | - milestone_bugtasks = dict( |
1020 | - (bugtask.bug.id, bugtask) |
1021 | - for bugtask in project.searchTasks(status=ALL_BUG_STATUSES, |
1022 | - milestone=milestone)) |
1023 | - assignees = set() |
1024 | - for task in milestone_bugtasks.values(): |
1025 | - assignee_name = get_assignee_name(task) |
1026 | - if assignee_name is not None: |
1027 | - assignees.add(assignee_name) |
1028 | - associated_story_tags = get_associated_story_tags(task) |
1029 | - if len(associated_story_tags) == 0: |
1030 | - # This bug isn't part of a story. Put it into the pseudo |
1031 | - # story which is used for all such bug fixes. |
1032 | - associated_story_tags = ['unrelated-bugs'] |
1033 | - for story_tag in associated_story_tags: |
1034 | - if story_tag not in stories: |
1035 | - stories[story_tag] = Story( |
1036 | - story_tag[len('story-'):], story_tag) |
1037 | - stories[story_tag].tasks.append(task) |
1038 | - milestone_state.mark_added( |
1039 | - task.bug.id, get_day_added(task, fallback=config.start)) |
1040 | - day_started = get_day_started(task) |
1041 | - if day_started is not None: |
1042 | - milestone_state.mark_started(task.bug.id, day_started) |
1043 | - day_done = get_day_done(task) |
1044 | - if day_done is not None: |
1045 | - milestone_state.mark_done(task.bug.id, day_done) |
1046 | - |
1047 | - # Look through commits and marks tasks as done, if any bug fixes are |
1048 | - # found. |
1049 | - branch = Branch.open(config.branch_location) |
1050 | - repository = branch.repository |
1051 | - start_revno = config.branch_start_revno |
1052 | - for revno in range(start_revno, branch.revno()+1): |
1053 | - rev_id = branch.get_rev_id(revno) |
1054 | - revision = repository.get_revision(rev_id) |
1055 | - fixed_bugs = get_fixed_bug_ids(revision) |
1056 | - for fixed_bug in sorted(fixed_bugs): |
1057 | - try: |
1058 | - lp_bug = launchpad.bugs[int(fixed_bug)] |
1059 | - except KeyError: |
1060 | - # Invalid bug id specified, skip it. |
1061 | - continue |
1062 | - if milestone_bugtasks.get(lp_bug.id) is not None: |
1063 | - milestone_state.mark_done( |
1064 | - lp_bug.id, date.fromtimestamp(revision.timestamp)) |
1065 | - |
1066 | - # Order by the order in the config file first. |
1067 | - sorted_stories = [stories[story_tag] for story_tag in config.story_tags] |
1068 | - sorted_stories.extend( |
1069 | - story for story_tag, story in sorted(stories.items()) |
1070 | - if story_tag not in config.story_tags) |
1071 | - table = generate_milestone_table(milestone_state, sorted_stories, config) |
1072 | - with open(config.template_filename, 'r') as template: |
1073 | - page = template.read() % dict( |
1074 | - milestone=config.milestone_name, |
1075 | - progress_table=table, |
1076 | - ) |
1077 | - def update_if_modified(moinfile): |
1078 | - if moinfile._unescape(moinfile.body) == page: |
1079 | - # Nothing has changed, cancel the edit. |
1080 | - return 0 |
1081 | - else: |
1082 | - moinfile.body = page |
1083 | - return 1 |
1084 | - |
1085 | - page_shortcut = config.base_wiki_location + config.milestone_name |
1086 | - editshortcut(page_shortcut, editfile_func=update_if_modified) |
1087 | - |
1088 | - # Generate assignee pages. |
1089 | - for assignee_name in assignees: |
1090 | - print "Assignee: %s" % assignee_name |
1091 | - assignee_stories = dict() |
1092 | - for story in stories.values(): |
1093 | - assigned_bugs = [ |
1094 | - bug_task for bug_task in story.tasks |
1095 | - if (bug_task.assignee is not None and |
1096 | - bug_task.assignee.name == assignee_name)] |
1097 | - if len(assigned_bugs) > 0: |
1098 | - assignee_stories[story.tag_name] = Story( |
1099 | - story.name, story.tag_name) |
1100 | - assignee_stories[story.tag_name].tasks = assigned_bugs |
1101 | - page = generate_milestone_table( |
1102 | - milestone_state, assignee_stories.values(), config) |
1103 | - assignee_page_shortcut = page_shortcut + '/' + assignee_name |
1104 | - editshortcut( |
1105 | - assignee_page_shortcut, editfile_func=update_if_modified) |
1106 | - |
1107 | - print "done." |
1108 | - |
1109 | -if __name__ == '__main__': |
1110 | - main() |
1111 | diff --git a/contrib/update-milestone-progress.template.sample b/contrib/update-milestone-progress.template.sample |
1112 | deleted file mode 100644 |
1113 | index 0db2ae0..0000000 |
1114 | --- a/contrib/update-milestone-progress.template.sample |
1115 | +++ /dev/null |
1116 | @@ -1,8 +0,0 @@ |
1117 | -= %(milestone)s Feature Progress = |
1118 | - |
1119 | -All the features that are currently being worked on are listed here. Don't edit this page. This information is being generated by a script, and any manual edits will be overwritten the next time the script runs. |
1120 | - |
1121 | -'''This page is currently in a beta stage, and the information here might not be |
1122 | -correct.''' |
1123 | - |
1124 | -%(progress_table)s |
LGTM