~statik/hydrazine/packaging

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))