1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
#! /usr/bin/python
# vi: expandtab:sts=4
# Copyright (C) 2010 Martin Pool
"""Take approved merge proposals and ask pqm to merge them.
"""
# TODO: comment on an mp
#
# TODO: open in browser
#
# TODO: show all comments
#
# TODO: some way to persistently say "not for me" and not be asked again
import datetime
import optparse
import os
import sys
try:
from bzrlib import initialize as bzrlib_initialize
except ImportError:
import bzrlib.ui
def bzrlib_setup():
"""Setup bzrlib ui for older bzr versions without bzrlib.initialize"""
bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(
sys.stdin, sys.stdout, sys.stderr)
else:
import atexit
def bzrlib_setup():
"""Call bzrlib.initialize and register cleanup to be run on exit"""
context = bzrlib_initialize()
if context is not None:
atexit.register(context.__enter__().__exit__, None, None, None)
from bzrlib.email_message import EmailMessage
from bzrlib.config import GlobalConfig
from bzrlib import gpg
from bzrlib.smtp_connection import SMTPConnection
import hydrazine
PQM_ADDRESS = 'pqm@bazaar-vcs.org'
# PQM_ADDRESS = 'mbp@sourcefrog.net'
def read_choice():
print 'What now?',
return raw_input()
def show_help():
print """\
feed-pqm looks for approved merge proposals and sends them to pqm.
commands:
h help
m set the proposed commit message for this proposal
n skip this, go to the next
p previous
q quit
s submit approved merges to the queue (PQM reads from the launchpad
queue) for already queued merges this toggles in and out of queued,
setting a new queuer and putting it at the back of the queue.
Requires membership in the review team for the target branch.
e send in an approved merge via email. Requires GPG and email setup
appropriately in bazaar.conf, and your key in the PQM keyring.
Please send feedback to mbp@canonical.com
"""
def show_datetime(label, dt):
print " %12s: %s" % (label, dt)
# TODO: show relative time
# (datetime.datetime.utcnow() - dt).days)
def show_mp(mp):
#XXX: this is a little ugly :- its not directly usable in a web browser.
print hydrazine.web_url(mp.self_link)
# nb: .url is empty (except on mirrored branches?)
print ' %12s: %s' % ('message', mp.commit_message)
print ' %12s: %s' % ('source', mp.source_branch.bzr_identity)
print ' %12s: %s' % ('target', mp.target_branch.bzr_identity)
print ' %12s: %s' % ('status', mp.queue_status)
show_datetime('created', mp.date_created)
show_datetime('reviewed', mp.date_reviewed)
print ' %12s: %s' % ('registrant', mp.registrant.name)
for vote in mp.votes_collection:
# XXX: would like to show the date but see bug 530475
print " %12s: %30s %-12s" % ('vote', vote.reviewer.name,
(vote.comment and vote.comment.vote or 'Requested'),
)
# The last comment is usually enough to get a sense for things. Change if
# desired.
print "Recent comments:"
comments = list(mp.all_comments) #XXX: See bug lp:583761
for comment in comments[-1:]:
print "%s: %s" % (comment.author.name, comment.message_body)
def set_message(mp):
print "old commit message: %s" % (mp.commit_message,)
print "new message> ",
mp.commit_message = raw_input()
mp.lp_save()
def queue_mp(launchpad, mp):
if not mp.commit_message:
print "No message set? Use 'm'."
return
if mp.queue_status == 'Queued':
mp.setStatus(status='Approved')
mp.setStatus(status='Queued', revid=mp.reviewed_revid)
def send_mp(launchpad, mp, send_mail=True):
if not mp.commit_message:
print "No message set - %s queued badly? Set a message first." % mp
return False
print "merge command to be signed:"
raw_message = (
"star-merge %s %s\n"
% (mp.source_branch.composePublicURL(scheme='http'),
mp.target_branch.composePublicURL(scheme='http')))
print raw_message
config = GlobalConfig()
signer = gpg.GPGStrategy(config)
signed_message = signer.sign(raw_message.encode('utf8'))
try:
my_email = os.environ['EMAIL']
except KeyError:
my_email = launchpad.me.preferred_email_address.email
print "EMAIL environment variable not set, using %s" % my_email
# TODO: put in bug numbers as well.
commit_message = u"(%s) %s (%s)" % (launchpad.me.name, mp.commit_message,
mp.source_branch.owner.display_name)
message = EmailMessage(my_email, PQM_ADDRESS, commit_message,
signed_message)
if send_mail:
SMTPConnection(config).send_email(message)
print "Sent!"
else:
print "Not sending email - disabled."
# suggested subject should match the last comment on the mp so that gmail
# doesn't see it as a separate thread;
# <https://bugs.edge.launchpad.net/hydrazine/+bug/541586>
subject = "Re: [Merge] %s into %s" % (mp.source_branch.bzr_identity,
mp.target_branch.bzr_identity)
print "Recording that the proposal is submitted to PQM."
fed_pqm = "sent to pqm by email\n"
mp.createComment(content=fed_pqm, subject=subject)
return True
def main(argv):
parser = optparse.OptionParser()
parser.add_option('--nomail', help="Do not send email commands", action="store_true", default=False)
parser.add_option('--queued', help="Examine already queued proposals", action="store_true", default=False)
opts, args = parser.parse_args()
bzrlib_setup()
launchpad = hydrazine.create_session()
project = launchpad.projects[argv[1]]
i = 0
find_status = ['Approved']
if opts.queued:
find_status.append('Queued')
print 'Looking for %s mps in %s' % (find_status, project)
all_mps = list(project.getMergeProposals(status=find_status))
while len(all_mps):
mp = all_mps[i]
show_mp(mp)
while True:
whatnow = read_choice()
if whatnow == 'q':
return 0
elif whatnow in ('h', 'help', '?'):
show_help()
elif whatnow == 'n':
i = (i + 1) % len(all_mps)
break
elif whatnow == 'p':
i -= 1
if i < 0:
i = len(all_mps) - 1
break
elif whatnow == 'm':
set_message(mp)
elif whatnow == 's':
queue_mp(launchpad, mp)
if mp.queue_status not in find_status:
del all_mps[i]
if i == len(all_mps):
i -= 1
break
elif whatnow == 'e':
if send_mp(launchpad, mp, send_mail=not opts.nomail):
del all_mps[i]
if i == len(all_mps):
i -= 1
break
elif whatnow == '':
continue
else:
print "Sorry, don't understand %r" % whatnow
if not len(all_mps):
print "No remaining merge proposals matching status %r?" % find_status
return 0
def usage():
print "feed-pqm <project-name>"
if __name__ == '__main__':
if len(sys.argv) != 2:
usage()
sys.exit(1)
sys.exit(main(sys.argv))
|