Merge lp:~broder/ubuntu-archive-tools/backport-helper into lp:ubuntu-archive-tools

Proposed by Evan Broder
Status: Merged
Merged at revision: 183
Proposed branch: lp:~broder/ubuntu-archive-tools/backport-helper
Merge into: lp:ubuntu-archive-tools
Diff against target: 186 lines (+182/-0)
1 file modified
backport-helper.py (+182/-0)
To merge this branch: bzr merge lp:~broder/ubuntu-archive-tools/backport-helper
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+40933@code.launchpad.net

Description of the change

The backports team (plus backporter wannabes like me) are trying to attack any delays we can find in the backport process.

To that end, I've implemented backport-helper.py, intended to be a sync-helper for backports. It finds all bugs in any of the backports projects with a status of IN PROGRESS and ubuntu-archive subscribed, and prompts for any information it can't guess (i.e. which source package to backport). It takes as an argument a filename, to which it writes a list of instructions that can be piped into mass-sync.py.

Right now each bug requires some manual handling. But we're also planning to add scripts for filing backports bugs. Hopefully as the form of backports requests is standardized, we'll be able to add more automatic detection to the script.

I'm registering this merge proposal primarily to get process review - am I understanding how the tools work correctly? Does this workflow line up with how backports are currently processed? What else can backporters do to make the archive admins' lives easier?

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

Lovely, thanks! I haven't been able to try this out for real yet because there aren't any backport requests in the ubuntu-archive queue right now, but I changed it locally to look at Confirmed bugs from the ubuntu-backporters list, it behaves roughly as I'd expect, and the output looks more or less right. I'm going to go ahead and merge this since it's clearly much better than our current available tools.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'backport-helper.py'
2--- backport-helper.py 1970-01-01 00:00:00 +0000
3+++ backport-helper.py 2010-11-16 06:47:57 +0000
4@@ -0,0 +1,182 @@
5+#!/usr/bin/python
6+
7+import optparse
8+import os
9+import re
10+import sys
11+import webbrowser
12+
13+import lputils
14+
15+
16+CONSUMER_KEY = 'backport-helper'
17+
18+valid_series = set()
19+backporters = set()
20+
21+
22+class BPBug(object):
23+ SOURCE_RE = re.compile(r'\bfrom (\w+)\b', re.I)
24+
25+ def __init__(self, bugtask):
26+ self.number = bugtask.bug.id
27+ self.title = bugtask.bug.title
28+ self.reporter = bugtask.bug.owner
29+
30+ # Can't guess the package yet
31+ self.package = None
32+
33+ # Try to guess the backport destination
34+ self.dest = None
35+ self.setDest(bugtask.bug_target_name)
36+
37+ # Try to guess the backport source
38+ self.source = None
39+ match = self.SOURCE_RE.search(bugtask.bug.title)
40+ if match:
41+ self.setSource(match.group(1))
42+
43+ # Collate the bug's history
44+ self.content = bugtask.bug.description
45+ for m in reversed(bugtask.bug.messages):
46+ self.content += '\n=============================================\n'
47+ star = ''
48+ if m.owner in backporters:
49+ star = '(*)'
50+ self.content += '%s%s said %s on %s:\n\n' % (m.owner.name, star,
51+ m.subject, str(m.date_created))
52+ self.content += m.content
53+
54+ # All setters return True if the input was valid
55+ def setSource(self, value):
56+ value = value.lower()
57+ if self.validSuite(value):
58+ self.source = value
59+ return True
60+ return False
61+
62+ def setDest(self, value):
63+ value = value.lower()
64+ value = value.split('-')[0]
65+ if self.validSuite(value):
66+ self.dest = value
67+ return True
68+ return False
69+
70+ def valid(self):
71+ return self.package and self.source and self.dest
72+
73+ def validSuite(self, suite):
74+ return suite.split('-')[0] in valid_series
75+
76+ def writeBackport(self, f):
77+ params = {'number': self.number,
78+ 'fromsuite': self.source,
79+ 'tosuite': self.dest,
80+ 'package': self.package,
81+ 'requestor': self.reporter.name}
82+ f.write('backport %(number)s -b %(requestor)s -S %(fromsuite)s -s %(tosuite)s %(package)s\n' % params)
83+
84+ def __str__(self):
85+ return '#%d (%s: %s -> %s) %s' % (self.number,
86+ self.package if self.package else 'unknown package',
87+ self.source if self.source else 'unknown',
88+ self.dest if self.dest else 'unknown',
89+ self.title)
90+
91+def length(collection):
92+ # XXX: Workaround bug 274074. Thanks wgrant.
93+ return int(collection._wadl_resource.representation['total_size'])
94+
95+
96+def parse_options(args=None):
97+ p = optparse.OptionParser(usage='Usage: %prog [options] filename')
98+
99+ p.add_option('-l', '--launchpad', dest='launchpad_instance',
100+ default='edge')
101+
102+ options, args = p.parse_args(args=args)
103+ if len(args) != 1:
104+ p.error('Need a filename to write instructions to')
105+
106+ return options, args
107+
108+
109+def main(args=None):
110+ global valid_series, backporters
111+
112+ options, args = parse_options(args[1:])
113+
114+ try:
115+ f = open(args[0], 'w')
116+ except:
117+ print 'Error opening instruction file'
118+ return 1
119+
120+ print 'Connceting to LP ...',
121+ lp = lputils.lpfactory(options.launchpad_instance, CONSUMER_KEY)
122+ print 'Done'
123+
124+ valid_series = set(s.name for s in lp.distributions['ubuntu'].series if s.active)
125+ bp_team = lp.people['ubuntu-backporters']
126+ backporters = set(bp_team.getMembersByStatus(status='Approved')).union(
127+ bp_team.getMembersByStatus(status='Administrator'))
128+ pillar = lp.projects['ubp']
129+ subscriber = lp.people['ubuntu-archive']
130+
131+ print 'Loading bugs (this may take some time) ...'
132+ bugs = pillar.searchTasks(bug_subscriber=subscriber,
133+ status='In Progress')
134+ bp_bugs = []
135+ num_bugs = length(bugs)
136+ print 'Found %d bug(s)' % num_bugs
137+
138+ for i, bugtask in enumerate(bugs, 1):
139+ bug = BPBug(bugtask)
140+ print '%d/%d: %s' % (i, num_bugs, bug)
141+ bp_bugs.append(bug)
142+ print 'Done, %d bugs to consider.' % len(bp_bugs)
143+
144+ for bug in bp_bugs:
145+ print
146+ print '=========================='
147+
148+ while True:
149+ print
150+ print bug
151+
152+ msg = '[Show/sKip/'
153+ if bug.valid():
154+ msg += 'process Backport/'
155+ msg += 'set Package/set sOurce series/set Dest series/opEn bug]: '
156+ sys.stdout.write(msg)
157+ sys.stdout.flush()
158+ ret = sys.stdin.readline().rstrip().upper()
159+
160+ if ret == 'S':
161+ print bug.content
162+ elif ret == 'K':
163+ break
164+ elif ret == 'B':
165+ if not bug.valid():
166+ print 'Backport does not yet have all required information'
167+ continue
168+ bug.writeBackport(f)
169+ break
170+ elif ret == 'P':
171+ maybe_package = raw_input('New package? ').lower()
172+ bug.package = maybe_package
173+ elif ret == 'O':
174+ if not bug.setSource(raw_input('New source series? ')):
175+ print 'Invalid series'
176+ elif ret == 'D':
177+ if not bug.setDest(raw_input('New dest series? ')):
178+ print 'Invalid series'
179+ elif ret == 'E':
180+ webbrowser.open('https://bugs.launchpad.net/bugs/%s' % bug.number)
181+ else:
182+ print 'Unknown command: %s' % ret
183+
184+
185+if __name__ == '__main__':
186+ sys.exit(main(sys.argv))

Subscribers

People subscribed via source and target branches