~bzr-pqm/bzr/bzr.dev

5971.1.36 by Jonathan Riddell
update copyright
1
# Copyright (C) 2005, 2011 Canonical Ltd
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
17
18
"""GPG signing and checking logic."""
19
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
20
import os
21
import sys
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
22
from StringIO import StringIO
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
23
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
1442.1.58 by Robert Collins
gpg signing of content
26
import errno
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
27
import subprocess
28
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
29
from bzrlib import (
30
    errors,
31
    trace,
1551.8.11 by Aaron Bentley
Clear terminal before signing
32
    ui,
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
33
    )
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
34
""")
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
35
5971.1.79 by Jonathan Riddell
gpg.py uses i18n but bzrlib.i18n is not ready for use, so add a stub class for now
36
class i18n:
37
    """this class is ready to use bzrlib.i18n but bzrlib.i18n is not ready to
38
    use so here is a stub until it is"""
39
    @staticmethod
40
    def gettext(string):
41
        return string
42
        
43
    @staticmethod
44
    def ngettext(single, plural, number):
45
        if number == 1:
46
            return single
47
        else:
48
            return plural
49
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
50
#verification results
5971.1.1 by Jonathan Riddell
add a verify command
51
SIGNATURE_VALID = 0
52
SIGNATURE_KEY_MISSING = 1
53
SIGNATURE_NOT_VALID = 2
54
SIGNATURE_NOT_SIGNED = 3
55
56
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
57
class DisabledGPGStrategy(object):
58
    """A GPG Strategy that makes everything fail."""
59
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
60
    @staticmethod
61
    def verify_signatures_available():
62
        return True
63
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
64
    def __init__(self, ignored):
65
        """Real strategies take a configuration."""
66
67
    def sign(self, content):
68
        raise errors.SigningFailed('Signing is disabled.')
69
5971.1.31 by Jonathan Riddell
and update tests
70
    def verify(self, content, testament):
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
71
        raise errors.SignatureVerificationFailed('Signature verification is \
72
disabled.')
5971.1.6 by Jonathan Riddell
fix methods for dummy gpg strategies
73
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
74
    def set_acceptable_keys(self, command_line_input):
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
75
        pass
76
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
77
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
78
class LoopbackGPGStrategy(object):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
79
    """A GPG Strategy that acts like 'cat' - data is just passed through.
5971.1.86 by Jonathan Riddell
doc string formatting
80
    Used in tests.
81
    """
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
82
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
83
    @staticmethod
84
    def verify_signatures_available():
85
        return True
86
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
87
    def __init__(self, ignored):
88
        """Real strategies take a configuration."""
89
90
    def sign(self, content):
1551.12.15 by Aaron Bentley
add header/trailer to fake clearsigned texts
91
        return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
1551.12.52 by Aaron Bentley
speling fix
92
                "-----END PSEUDO-SIGNED CONTENT-----\n")
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
93
5971.1.31 by Jonathan Riddell
and update tests
94
    def verify(self, content, testament):
5971.1.22 by Jonathan Riddell
fix tests
95
        return SIGNATURE_VALID, None
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
96
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
97
    def set_acceptable_keys(self, command_line_input):
98
        if command_line_input is not None:
99
            patterns = command_line_input.split(",")
100
            self.acceptable_keys = []
101
            for pattern in patterns:
102
                if pattern == "unknown":
103
                    pass
104
                else:
105
                    self.acceptable_keys.append(pattern)
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
106
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
107
    def do_verifications(self, revisions, repository):
108
        count = {SIGNATURE_VALID: 0,
109
                 SIGNATURE_KEY_MISSING: 0,
110
                 SIGNATURE_NOT_VALID: 0,
111
                 SIGNATURE_NOT_SIGNED: 0}
112
        result = []
113
        all_verifiable = True
114
        for rev_id in revisions:
115
            verification_result, uid =\
116
                                repository.verify_revision(rev_id,self)
117
            result.append([rev_id, verification_result, uid])
118
            count[verification_result] += 1
119
            if verification_result != SIGNATURE_VALID:
120
                all_verifiable = False
121
        return (count, result, all_verifiable)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
122
123
    def valid_commits_message(self, count):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
124
        return i18n.gettext(u"{0} commits with valid signatures").format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
125
                                        count[SIGNATURE_VALID])            
126
127
    def unknown_key_message(self, count):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
128
        return i18n.ngettext(u"{0} commit with unknown key",
129
                             u"{0} commits with unknown keys",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
130
                             count[SIGNATURE_KEY_MISSING]).format(
131
                                        count[SIGNATURE_KEY_MISSING])
132
133
    def commit_not_valid_message(self, count):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
134
        return i18n.ngettext(u"{0} commit not valid",
135
                             u"{0} commits not valid",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
136
                             count[SIGNATURE_NOT_VALID]).format(
137
                                            count[SIGNATURE_NOT_VALID])
138
139
    def commit_not_signed_message(self, count):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
140
        return i18n.ngettext(u"{0} commit not signed",
141
                             u"{0} commits not signed",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
142
                             count[SIGNATURE_NOT_SIGNED]).format(
143
                                        count[SIGNATURE_NOT_SIGNED])
144
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
145
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
146
def _set_gpg_tty():
147
    tty = os.environ.get('TTY')
148
    if tty is not None:
149
        os.environ['GPG_TTY'] = tty
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
150
        trace.mutter('setting GPG_TTY=%s', tty)
151
    else:
152
        # This is not quite worthy of a warning, because some people
153
        # don't need GPG_TTY to be set. But it is worthy of a big mark
154
        # in ~/.bzr.log, so that people can debug it if it happens to them
155
        trace.mutter('** Env var TTY empty, cannot set GPG_TTY.'
156
                     '  Is TTY exported?')
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
157
158
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
159
class GPGStrategy(object):
160
    """GPG Signing and checking facilities."""
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
161
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
162
    acceptable_keys = None
163
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
164
    @staticmethod
165
    def verify_signatures_available():
5971.1.82 by Jonathan Riddell
method doc
166
        """
5971.1.86 by Jonathan Riddell
doc string formatting
167
        check if this strategy can verify signatures
168
5971.1.82 by Jonathan Riddell
method doc
169
        :return: boolean if this strategy can verify signatures
170
        """
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
171
        try:
172
            import gpgme
173
            return True
174
        except ImportError, error:
175
            return False
176
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
177
    def _command_line(self):
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
178
        return [self._config.gpg_signing_command(), '--clearsign']
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
179
180
    def __init__(self, config):
181
        self._config = config
5971.1.61 by Jonathan Riddell
make gpgme context global to class
182
        try:
183
            import gpgme
184
            self.context = gpgme.Context()
185
        except ImportError, error:
186
            pass # can't use verify()
1442.1.58 by Robert Collins
gpg signing of content
187
188
    def sign(self, content):
2273.1.1 by John Arbash Meinel
``GPGStrategy.sign()`` will now raise ``BzrBadParameterUnicode`` if
189
        if isinstance(content, unicode):
190
            raise errors.BzrBadParameterUnicode('content')
1551.8.11 by Aaron Bentley
Clear terminal before signing
191
        ui.ui_factory.clear_term()
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
192
193
        preexec_fn = _set_gpg_tty
194
        if sys.platform == 'win32':
195
            # Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
196
            preexec_fn = None
1442.1.58 by Robert Collins
gpg signing of content
197
        try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
198
            process = subprocess.Popen(self._command_line(),
199
                                       stdin=subprocess.PIPE,
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
200
                                       stdout=subprocess.PIPE,
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
201
                                       preexec_fn=preexec_fn)
1442.1.58 by Robert Collins
gpg signing of content
202
            try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
203
                result = process.communicate(content)[0]
1442.1.58 by Robert Collins
gpg signing of content
204
                if process.returncode is None:
205
                    process.wait()
206
                if process.returncode != 0:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
207
                    raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
208
                return result
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
209
            except OSError, e:
1442.1.58 by Robert Collins
gpg signing of content
210
                if e.errno == errno.EPIPE:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
211
                    raise errors.SigningFailed(self._command_line())
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
212
                else:
213
                    raise
1442.1.58 by Robert Collins
gpg signing of content
214
        except ValueError:
215
            # bad subprocess parameters, should never happen.
216
            raise
217
        except OSError, e:
218
            if e.errno == errno.ENOENT:
219
                # gpg is not installed
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
220
                raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
221
            else:
222
                raise
5971.1.1 by Jonathan Riddell
add a verify command
223
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
224
    def verify(self, content, testament):
5971.1.7 by Jonathan Riddell
add method docs
225
        """Check content has a valid signature.
226
        
227
        :param content: the commit signature
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
228
        :param testament: the valid testament string for the commit
5971.1.7 by Jonathan Riddell
add method docs
229
        
5971.1.18 by Jonathan Riddell
add email to verbose output
230
        :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
5971.1.7 by Jonathan Riddell
add method docs
231
        """
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
232
        try:
233
            import gpgme
5971.1.41 by Jonathan Riddell
fix calling GpgmeNotInstalled
234
        except ImportError, error:
235
            raise errors.GpgmeNotInstalled(error)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
236
5971.1.1 by Jonathan Riddell
add a verify command
237
        signature = StringIO(content)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
238
        plain_output = StringIO()
5971.1.1 by Jonathan Riddell
add a verify command
239
        
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
240
        try:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
241
            result = self.context.verify(signature, None, plain_output)
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
242
        except gpgme.GpgmeError,error:
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
243
            raise errors.SignatureVerificationFailed(error[2])
5971.1.1 by Jonathan Riddell
add a verify command
244
5971.1.9 by Jonathan Riddell
add some tests
245
        if len(result) == 0:
5971.1.17 by Jonathan Riddell
add verbose option
246
            return SIGNATURE_NOT_VALID, None
5971.1.13 by Jonathan Riddell
return missing if not in acceptable keys
247
        fingerprint = result[0].fpr
248
        if self.acceptable_keys is not None:
249
            if not fingerprint in self.acceptable_keys:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
250
                return SIGNATURE_KEY_MISSING, fingerprint[-8:]
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
251
        if testament != plain_output.getvalue():
252
            return SIGNATURE_NOT_VALID, None
5971.1.1 by Jonathan Riddell
add a verify command
253
        if result[0].summary & gpgme.SIGSUM_VALID:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
254
            key = self.context.get_key(fingerprint)
5971.1.17 by Jonathan Riddell
add verbose option
255
            name = key.uids[0].name
5971.1.18 by Jonathan Riddell
add email to verbose output
256
            email = key.uids[0].email
257
            return SIGNATURE_VALID, name + " <" + email + ">"
5971.1.1 by Jonathan Riddell
add a verify command
258
        if result[0].summary & gpgme.SIGSUM_RED:
5971.1.17 by Jonathan Riddell
add verbose option
259
            return SIGNATURE_NOT_VALID, None
5971.1.1 by Jonathan Riddell
add a verify command
260
        if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
261
            return SIGNATURE_KEY_MISSING, fingerprint[-8:]
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
262
        #summary isn't set if sig is valid but key is untrusted
263
        if result[0].summary == 0 and self.acceptable_keys is not None:
264
            if fingerprint in self.acceptable_keys:
5971.1.17 by Jonathan Riddell
add verbose option
265
                return SIGNATURE_VALID, None
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
266
        else:
5971.1.17 by Jonathan Riddell
add verbose option
267
            return SIGNATURE_KEY_MISSING, None
5971.1.42 by Jonathan Riddell
fix string formatting
268
        raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
269
                                                 "verification result")
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
270
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
271
    def set_acceptable_keys(self, command_line_input):
272
        """sets the acceptable keys for verifying with this GPGStrategy
273
        
274
        :param command_line_input: comma separated list of patterns from
275
                                command line
276
        :return: nothing
277
        """
278
        key_patterns = None
279
        acceptable_keys_config = self._config.acceptable_keys()
280
        try:
281
            if isinstance(acceptable_keys_config, unicode):
282
                acceptable_keys_config = str(acceptable_keys_config)
283
        except UnicodeEncodeError:
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
284
            #gpg Context.keylist(pattern) does not like unicode
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
285
            raise errors.BzrCommandError('Only ASCII permitted in option names')
286
287
        if acceptable_keys_config is not None:
288
            key_patterns = acceptable_keys_config
289
        if command_line_input is not None: #command line overrides config
290
            key_patterns = command_line_input
291
        if key_patterns is not None:
292
            patterns = key_patterns.split(",")
293
294
            self.acceptable_keys = []
295
            for pattern in patterns:
296
                result = self.context.keylist(pattern)
297
                found_key = False
298
                for key in result:
299
                    found_key = True
300
                    self.acceptable_keys.append(key.subkeys[0].fpr)
301
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
302
                if not found_key:
303
                    trace.note(i18n.gettext(
304
                            "No GnuPG key results for pattern: {}"
305
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
306
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
307
    def do_verifications(self, revisions, repository,
308
                            process_events_callback=None):
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
309
        """do verifications on a set of revisions
310
        
311
        :param revisions: list of revision ids to verify
312
        :param repository: repository object
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
313
        :param process_events_callback: method to call for GUI frontends that
314
                                                want to keep their UI refreshed
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
315
        
316
        :return: count dictionary of results of each type,
317
                 result list for each revision,
318
                 boolean True if all results are verified successfully
319
        """
320
        count = {SIGNATURE_VALID: 0,
321
                 SIGNATURE_KEY_MISSING: 0,
322
                 SIGNATURE_NOT_VALID: 0,
323
                 SIGNATURE_NOT_SIGNED: 0}
324
        result = []
325
        all_verifiable = True
326
        for rev_id in revisions:
327
            verification_result, uid =\
328
                                repository.verify_revision(rev_id,self)
329
            result.append([rev_id, verification_result, uid])
330
            count[verification_result] += 1
331
            if verification_result != SIGNATURE_VALID:
332
                all_verifiable = False
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
333
            if process_events_callback is not None:
334
                process_events_callback()
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
335
        return (count, result, all_verifiable)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
336
337
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
338
        """takes a verify result and returns list of signed commits strings"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
339
        signers = {}
340
        for rev_id, validity, uid in result:
341
            if validity == SIGNATURE_VALID:
342
                signers.setdefault(uid, 0)
343
                signers[uid] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
344
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
345
        for uid, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
346
             result.append( i18n.ngettext(u"{0} signed {1} commit", 
347
                             u"{0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
348
                             number).format(uid, number) )
349
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
350
351
352
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
353
        """takes a verify result and returns list of not valid commit info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
354
        signers = {}
355
        for rev_id, validity, empty in result:
356
            if validity == SIGNATURE_NOT_VALID:
357
                revision = repo.get_revision(rev_id)
358
                authors = ', '.join(revision.get_apparent_authors())
359
                signers.setdefault(authors, 0)
360
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
361
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
362
        for authors, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
363
            result.append( i18n.ngettext(u"{0} commit by author {1}", 
364
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
365
                                 number).format(number, authors) )
366
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
367
368
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
369
        """takes a verify result and returns list of not signed commit info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
370
        signers = {}
371
        for rev_id, validity, empty in result:
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
372
            if validity == SIGNATURE_NOT_SIGNED:
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
373
                revision = repo.get_revision(rev_id)
374
                authors = ', '.join(revision.get_apparent_authors())
375
                signers.setdefault(authors, 0)
376
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
377
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
378
        for authors, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
379
            result.append( i18n.ngettext(u"{0} commit by author {1}", 
380
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
381
                                 number).format(number, authors) )
382
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
383
384
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
385
        """takes a verify result and returns list of missing key info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
386
        signers = {}
387
        for rev_id, validity, fingerprint in result:
388
            if validity == SIGNATURE_KEY_MISSING:
389
                signers.setdefault(fingerprint, 0)
390
                signers[fingerprint] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
391
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
392
        for fingerprint, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
393
            result.append( i18n.ngettext(u"Unknown key {0} signed {1} commit", 
394
                                 u"Unknown key {0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
395
                                 number).format(fingerprint, number) )
396
        return result
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
397
398
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
399
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
400
        return i18n.gettext(u"{0} commits with valid signatures").format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
401
                                        count[SIGNATURE_VALID])
402
403
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
404
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
405
        return i18n.ngettext(u"{0} commit with unknown key",
406
                             u"{0} commits with unknown keys",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
407
                             count[SIGNATURE_KEY_MISSING]).format(
408
                                        count[SIGNATURE_KEY_MISSING])
409
410
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
411
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
412
        return i18n.ngettext(u"{0} commit not valid",
413
                             u"{0} commits not valid",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
414
                             count[SIGNATURE_NOT_VALID]).format(
415
                                            count[SIGNATURE_NOT_VALID])
416
417
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
418
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
419
        return i18n.ngettext(u"{0} commit not signed",
420
                             u"{0} commits not signed",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
421
                             count[SIGNATURE_NOT_SIGNED]).format(
422
                                        count[SIGNATURE_NOT_SIGNED])