~ursinha/lp-qa-tools/bzr-tarmacland

« back to all changes in this revision

Viewing changes to pqm_submit.py

  • Committer: John Arbash Meinel
  • Date: 2007-11-06 02:43:33 UTC
  • mfrom: (37.1.6 bzr-pqm.jamesh)
  • Revision ID: john@arbash-meinel.com-20071106024333-wbjbqrtdgcvtu4bg
(James Henstridge) clean up the SMTP handling, and PQM Submission logic.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
"""Submit an email to a Patch Queue Manager"""
17
17
 
18
 
import smtplib
19
 
 
20
 
import bzrlib
21
18
from bzrlib import (
 
19
    config as _mod_config,
22
20
    errors,
 
21
    gpg,
 
22
    osutils,
 
23
    urlutils,
23
24
    )
24
25
from bzrlib.branch import Branch
25
 
import bzrlib.config
26
 
import bzrlib.gpg
27
 
import bzrlib.osutils
28
 
from bzrlib.trace import note
29
 
import bzrlib.ui
30
 
import bzrlib.urlutils
31
 
 
32
 
 
33
 
_default_smtp_server = 'localhost'
 
26
from bzrlib.email_message import EmailMessage
 
27
from bzrlib.smtp_connection import SMTPConnection
 
28
from bzrlib.trace import note, warning
34
29
 
35
30
 
36
31
class BadCommitMessage(errors.BzrError):
42
37
        self.msg = message
43
38
 
44
39
 
45
 
def _signed_submission(branch, config, target_base):
46
 
    """Get the signed text that we would submit to the pqm"""
47
 
 
48
 
    merge_target = config.get_user_option('pqm_branch')
49
 
    if not merge_target:
50
 
        raise errors.BzrCommandError('No PQM target branch specified '
51
 
                                     'in configuration')
52
 
 
53
 
    unsigned_text = 'star-merge %s %s' % (target_base, merge_target)
54
 
    unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii
55
 
 
56
 
    strategy = bzrlib.gpg.GPGStrategy(config)
57
 
 
58
 
    return strategy.sign(unsigned_text)
59
 
 
60
 
 
61
 
_default_headers = '''From: %s
62
 
To: %s
63
 
Subject: %s
64
 
 
65
 
'''
66
 
 
67
 
 
68
 
def _setup_headers(message, user_from, user_to):
69
 
    """Get all of the important headers filled out."""
70
 
    if '\n' in message:
71
 
        raise BadCommitMessage(message)
72
 
    return _default_headers % (user_from, user_to, message)
73
 
 
74
 
 
75
 
def public_branch(branch, config):
76
 
    target_base = config.get_user_option('public_branch')
77
 
    if target_base is None:
78
 
        repo_loc = branch.repository.bzrdir.root_transport.local_abspath('.')
79
 
        repo_config = bzrlib.config.LocationConfig(repo_loc)
80
 
        public_repo = repo_config.get_user_option("public_repository")
81
 
        if public_repo is not None:
82
 
            branch_relpath = bzrlib.osutils.relpath(repo_loc,
83
 
                branch.bzrdir.root_transport.local_abspath('.'))
84
 
            target_base = bzrlib.urlutils.join(public_repo, branch_relpath)
85
 
    return target_base
 
40
class PQMSubmission(object):
 
41
    """A request to perform a PQM merge into a branch."""
 
42
 
 
43
    def __init__(self, source_branch, public_location=None,
 
44
                 submit_location=None, message=None):
 
45
        """Create a PQMSubmission object.
 
46
 
 
47
        :param source_branch: the source branch for the merge
 
48
        :param public_location: the public location of the source branch
 
49
        :param submit_location: the location of the target branch
 
50
        :param message: The message to use when committing this merge
 
51
 
 
52
        If any of public_location, submit_location or message are
 
53
        omitted, they will be calculated from source_branch.
 
54
        """
 
55
        if source_branch is None:
 
56
            raise errors.NoMergeSource()
 
57
        self.source_branch = source_branch
 
58
 
 
59
        if public_location is None:
 
60
            public_location = self.source_branch.get_public_branch()
 
61
            # Fall back to the old public_repository hack.
 
62
            if public_location is None:
 
63
                src_loc = source_branch.bzrdir.root_transport.local_abspath('.')
 
64
                repository = source_branch.repository
 
65
                repo_loc = repository.bzrdir.root_transport.local_abspath('.')
 
66
                repo_config = _mod_config.LocationConfig(repo_loc)
 
67
                public_repo = repo_config.get_user_option("public_repository")
 
68
                if public_repo is not None:
 
69
                    warning("Please use public_branch, not public_repository, "
 
70
                            "to set the public location of branches.")
 
71
                    branch_relpath = osutils.relpath(repo_loc, src_loc)
 
72
                    public_location = urlutils.join(public_repo, branch_relpath)
 
73
 
 
74
            if public_location is None:
 
75
                raise errors.BzrCommandError(
 
76
                    'No public branch location given.  Please specify with '
 
77
                    '--public-location or see "bzr help pqm-submit" to see how '
 
78
                    'to set it in ~/.bazaar/locations.conf')
 
79
        self.public_location = public_location
 
80
 
 
81
        if submit_location is None:
 
82
            config = self.source_branch.get_config()
 
83
            # First check the deprecated pqm_branch config key:
 
84
            submit_location = config.get_user_option('pqm_branch')
 
85
            if submit_location is not None:
 
86
                warning("Please use submit_branch, not pqm_branch to set "
 
87
                        "the PQM merge target branch.")
 
88
            else:
 
89
                # Otherwise, use the standard config key:
 
90
                submit_location = self.source_branch.get_submit_branch()
 
91
 
 
92
            if submit_location is None:
 
93
                raise errors.NoSubmitBranch(self.source_branch)
 
94
        self.submit_location = submit_location
 
95
 
 
96
        # Check that the message is okay to pass to PQM
 
97
        if message is None:
 
98
            repository = self.source_branch.repository
 
99
            rev = repository.get_revision(self.source_branch.last_revision())
 
100
            message = rev.message
 
101
        self.message = message.encode('utf8')
 
102
        if '\n' in self.message:
 
103
            raise BadCommitMessage(self.message)
 
104
 
 
105
    def check_public_branch(self):
 
106
        """Check that the public branch is up to date with the local copy."""
 
107
        note('Checking that the public branch is up to date ...')
 
108
        local_revision = self.source_branch.last_revision()
 
109
        public_revision = Branch.open(self.public_location).last_revision()
 
110
        if local_revision != public_revision:
 
111
            raise errors.PublicBranchOutOfDate(
 
112
                self.public_location, local_revision)
 
113
 
 
114
    def to_lines(self):
 
115
        """Serialise as a list of lines."""
 
116
        return ['star-merge %s %s\n' % (self.public_location, self.submit_location)]
 
117
 
 
118
    def to_signed(self):
 
119
        """Serialize as a signed string."""
 
120
        unsigned_text = ''.join(self.to_lines())
 
121
        unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii
 
122
 
 
123
        strategy = gpg.GPGStrategy(self.source_branch.get_config())
 
124
        return strategy.sign(unsigned_text)
 
125
 
 
126
    def to_email(self, mail_from, mail_to, sign=True):
 
127
        """Serialize as an email message.
 
128
 
 
129
        :param mail_from: The from address for the message
 
130
        :param mail_to: The address to send the message to
 
131
        :param sign: If True, gpg-sign the email
 
132
        :return: an email message
 
133
        """
 
134
        if sign:
 
135
            body = self.to_signed()
 
136
        else:
 
137
            body = ''.join(self.to_lines())
 
138
        message = EmailMessage(mail_from, mail_to, self.message, body)
 
139
        return message
86
140
 
87
141
 
88
142
def submit(branch, message, dry_run=False, public_location=None):
89
143
    """Submit the given branch to the pqm."""
90
144
    config = branch.get_config()
91
145
 
92
 
    if message is None:
93
 
        rev = branch.repository.get_revision(branch.last_revision())
94
 
        message = rev.message
95
 
    message = message.encode('utf8')
 
146
    submission = PQMSubmission(
 
147
        source_branch=branch, public_location=public_location, message=message)
96
148
 
97
 
    user_from = config.get_user_option('pqm_user_email')
98
 
    if not user_from:
99
 
        user_from = config.username()
100
 
    user_from = user_from.encode('utf8') # Make sure this isn't unicode
101
 
    user_to = config.get_user_option('pqm_email')
102
 
    if not user_to:
 
149
    mail_from = config.get_user_option('pqm_user_email')
 
150
    if not mail_from:
 
151
        mail_from = config.username()
 
152
    mail_from = mail_from.encode('utf8') # Make sure this isn't unicode
 
153
    mail_to = config.get_user_option('pqm_email')
 
154
    if not mail_to:
103
155
        raise errors.BzrCommandError('No PQM submission address specified '
104
156
                                     'in configuration')
105
 
    user_to = user_to.encode('utf8') # same here
106
 
 
107
 
    # All the entries must be utf-8 so the message creation doesn't fail
108
 
    msg = _setup_headers(message, user_from, user_to)
109
 
 
110
 
    server = config.get_user_option('smtp_server')
111
 
    if not server:
112
 
        server = _default_smtp_server
113
 
    smtp_username = config.get_user_option('smtp_username')
114
 
    smtp_password = config.get_user_option('smtp_password')
115
 
 
116
 
    if public_location is None:
117
 
        target_base = public_branch(branch, config)
118
 
    else:
119
 
        target_base = public_location
120
 
 
121
 
    if target_base is None:
122
 
        raise errors.BzrCommandError('Could not find public location, either'
123
 
                                     'specify with --public-location, or see'
124
 
                                     ' "bzr help pqm-submit" to see how to set'
125
 
                                     'it in ~/.bazaar/locations.conf')
126
 
 
127
 
    note('Checking that the public branch is up to date ...')
128
 
    try:
129
 
        public_revision = Branch.open(target_base).last_revision()
130
 
    except errors.NotBranchError:
131
 
        raise errors.BzrCommandError(
132
 
            'Public location has no branch: %s' % target_base)
133
 
    if public_revision != branch.last_revision():
134
 
        raise errors.BzrCommandError(
135
 
            'Local last revision does not match public last revision: %s' %
136
 
            (target_base,))
137
 
    # Get the signed text, this should be done just before
138
 
    # emailing, so that we don't run gpg --cl for nothing
139
 
    msg += _signed_submission(branch, config, target_base)
 
157
    mail_to = mail_to.encode('utf8') # same here
 
158
 
 
159
    submission.check_public_branch()
 
160
 
 
161
    message = submission.to_email(mail_from, mail_to)
140
162
 
141
163
    if dry_run:
142
 
        return print_info(msg, server, user_from, user_to)
143
 
 
144
 
    smtp = smtplib.SMTP()
145
 
    smtp.connect(server)
146
 
 
147
 
    # The world can always use a little bit more encryption :)
148
 
    smtp.starttls()
149
 
 
150
 
    if smtp_username is not None:
151
 
        if smtp_password is None:
152
 
            smtp_password = bzrlib.ui.ui_factory.get_password(
153
 
                'Please enter SMTP password for %(host)s', host=server)
154
 
        try:
155
 
            smtp.login(smtp_username, smtp_password)
156
 
        except smtplib.SMTPHeloError, e:
157
 
            raise errors.BzrCommandError('SMTP server refused HELO: %d %s'
158
 
                                         % (e.smtp_code, e.smtp_error))
159
 
        except smtplib.SMTPAuthenticationError, e:
160
 
            raise errors.BzrCommandError('SMTP server refused authentication: %d %s'
161
 
                                         % (e.smtp_code, e.smtp_error))
162
 
        except smtplib.SMTPException, e:
163
 
            raise errors.BzrCommandError(str(e))
164
 
 
165
 
    smtp.sendmail(user_from, [user_to], msg)
166
 
 
167
 
 
168
 
def print_info(msg, server, user_from, user_to):
169
 
    print "Server: %s" % server
170
 
    print "%s" % msg
 
164
        print message.as_string()
 
165
        return
 
166
 
 
167
    SMTPConnection(config).send_email(message)