~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):
6012.2.4 by Jonathan Riddell
set default for signing_key to user_email
178
        
6012.2.1 by Jonathan Riddell
sign using gpg key matching config e-mail by default
179
        return [self._config.gpg_signing_command(), '--clearsign', '-u',
6012.2.11 by Jonathan Riddell
rename config option signing_key to gpg_signing_key
180
                                                self._config.gpg_signing_key()]
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
181
182
    def __init__(self, config):
183
        self._config = config
5971.1.61 by Jonathan Riddell
make gpgme context global to class
184
        try:
185
            import gpgme
186
            self.context = gpgme.Context()
187
        except ImportError, error:
188
            pass # can't use verify()
1442.1.58 by Robert Collins
gpg signing of content
189
190
    def sign(self, content):
2273.1.1 by John Arbash Meinel
``GPGStrategy.sign()`` will now raise ``BzrBadParameterUnicode`` if
191
        if isinstance(content, unicode):
192
            raise errors.BzrBadParameterUnicode('content')
1551.8.11 by Aaron Bentley
Clear terminal before signing
193
        ui.ui_factory.clear_term()
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
194
195
        preexec_fn = _set_gpg_tty
196
        if sys.platform == 'win32':
197
            # Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
198
            preexec_fn = None
1442.1.58 by Robert Collins
gpg signing of content
199
        try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
200
            process = subprocess.Popen(self._command_line(),
201
                                       stdin=subprocess.PIPE,
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
202
                                       stdout=subprocess.PIPE,
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
203
                                       preexec_fn=preexec_fn)
1442.1.58 by Robert Collins
gpg signing of content
204
            try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
205
                result = process.communicate(content)[0]
1442.1.58 by Robert Collins
gpg signing of content
206
                if process.returncode is None:
207
                    process.wait()
208
                if process.returncode != 0:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
209
                    raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
210
                return result
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
211
            except OSError, e:
1442.1.58 by Robert Collins
gpg signing of content
212
                if e.errno == errno.EPIPE:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
213
                    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.
214
                else:
215
                    raise
1442.1.58 by Robert Collins
gpg signing of content
216
        except ValueError:
217
            # bad subprocess parameters, should never happen.
218
            raise
219
        except OSError, e:
220
            if e.errno == errno.ENOENT:
221
                # gpg is not installed
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
222
                raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
223
            else:
224
                raise
5971.1.1 by Jonathan Riddell
add a verify command
225
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
226
    def verify(self, content, testament):
5971.1.7 by Jonathan Riddell
add method docs
227
        """Check content has a valid signature.
228
        
229
        :param content: the commit signature
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
230
        :param testament: the valid testament string for the commit
5971.1.7 by Jonathan Riddell
add method docs
231
        
5971.1.18 by Jonathan Riddell
add email to verbose output
232
        :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
5971.1.7 by Jonathan Riddell
add method docs
233
        """
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
234
        try:
235
            import gpgme
5971.1.41 by Jonathan Riddell
fix calling GpgmeNotInstalled
236
        except ImportError, error:
237
            raise errors.GpgmeNotInstalled(error)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
238
5971.1.1 by Jonathan Riddell
add a verify command
239
        signature = StringIO(content)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
240
        plain_output = StringIO()
5971.1.1 by Jonathan Riddell
add a verify command
241
        
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
242
        try:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
243
            result = self.context.verify(signature, None, plain_output)
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
244
        except gpgme.GpgmeError,error:
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
245
            raise errors.SignatureVerificationFailed(error[2])
5971.1.1 by Jonathan Riddell
add a verify command
246
5971.1.9 by Jonathan Riddell
add some tests
247
        if len(result) == 0:
5971.1.17 by Jonathan Riddell
add verbose option
248
            return SIGNATURE_NOT_VALID, None
5971.1.13 by Jonathan Riddell
return missing if not in acceptable keys
249
        fingerprint = result[0].fpr
250
        if self.acceptable_keys is not None:
251
            if not fingerprint in self.acceptable_keys:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
252
                return SIGNATURE_KEY_MISSING, fingerprint[-8:]
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
253
        if testament != plain_output.getvalue():
254
            return SIGNATURE_NOT_VALID, None
5971.1.1 by Jonathan Riddell
add a verify command
255
        if result[0].summary & gpgme.SIGSUM_VALID:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
256
            key = self.context.get_key(fingerprint)
5971.1.17 by Jonathan Riddell
add verbose option
257
            name = key.uids[0].name
5971.1.18 by Jonathan Riddell
add email to verbose output
258
            email = key.uids[0].email
259
            return SIGNATURE_VALID, name + " <" + email + ">"
5971.1.1 by Jonathan Riddell
add a verify command
260
        if result[0].summary & gpgme.SIGSUM_RED:
5971.1.17 by Jonathan Riddell
add verbose option
261
            return SIGNATURE_NOT_VALID, None
5971.1.1 by Jonathan Riddell
add a verify command
262
        if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
263
            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
264
        #summary isn't set if sig is valid but key is untrusted
265
        if result[0].summary == 0 and self.acceptable_keys is not None:
266
            if fingerprint in self.acceptable_keys:
5971.1.17 by Jonathan Riddell
add verbose option
267
                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
268
        else:
5971.1.17 by Jonathan Riddell
add verbose option
269
            return SIGNATURE_KEY_MISSING, None
5971.1.42 by Jonathan Riddell
fix string formatting
270
        raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
271
                                                 "verification result")
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
272
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
273
    def set_acceptable_keys(self, command_line_input):
274
        """sets the acceptable keys for verifying with this GPGStrategy
275
        
276
        :param command_line_input: comma separated list of patterns from
277
                                command line
278
        :return: nothing
279
        """
280
        key_patterns = None
281
        acceptable_keys_config = self._config.acceptable_keys()
282
        try:
283
            if isinstance(acceptable_keys_config, unicode):
284
                acceptable_keys_config = str(acceptable_keys_config)
285
        except UnicodeEncodeError:
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
286
            #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
287
            raise errors.BzrCommandError('Only ASCII permitted in option names')
288
289
        if acceptable_keys_config is not None:
290
            key_patterns = acceptable_keys_config
291
        if command_line_input is not None: #command line overrides config
292
            key_patterns = command_line_input
293
        if key_patterns is not None:
294
            patterns = key_patterns.split(",")
295
296
            self.acceptable_keys = []
297
            for pattern in patterns:
298
                result = self.context.keylist(pattern)
299
                found_key = False
300
                for key in result:
301
                    found_key = True
302
                    self.acceptable_keys.append(key.subkeys[0].fpr)
303
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
304
                if not found_key:
305
                    trace.note(i18n.gettext(
306
                            "No GnuPG key results for pattern: {}"
307
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
308
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
309
    def do_verifications(self, revisions, repository,
310
                            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
311
        """do verifications on a set of revisions
312
        
313
        :param revisions: list of revision ids to verify
314
        :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
315
        :param process_events_callback: method to call for GUI frontends that
316
                                                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
317
        
318
        :return: count dictionary of results of each type,
319
                 result list for each revision,
320
                 boolean True if all results are verified successfully
321
        """
322
        count = {SIGNATURE_VALID: 0,
323
                 SIGNATURE_KEY_MISSING: 0,
324
                 SIGNATURE_NOT_VALID: 0,
325
                 SIGNATURE_NOT_SIGNED: 0}
326
        result = []
327
        all_verifiable = True
328
        for rev_id in revisions:
329
            verification_result, uid =\
330
                                repository.verify_revision(rev_id,self)
331
            result.append([rev_id, verification_result, uid])
332
            count[verification_result] += 1
333
            if verification_result != SIGNATURE_VALID:
334
                all_verifiable = False
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
335
            if process_events_callback is not None:
336
                process_events_callback()
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
337
        return (count, result, all_verifiable)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
338
339
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
340
        """takes a verify result and returns list of signed commits strings"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
341
        signers = {}
342
        for rev_id, validity, uid in result:
343
            if validity == SIGNATURE_VALID:
344
                signers.setdefault(uid, 0)
345
                signers[uid] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
346
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
347
        for uid, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
348
             result.append( i18n.ngettext(u"{0} signed {1} commit", 
349
                             u"{0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
350
                             number).format(uid, number) )
351
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
352
353
354
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
355
        """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
356
        signers = {}
357
        for rev_id, validity, empty in result:
358
            if validity == SIGNATURE_NOT_VALID:
359
                revision = repo.get_revision(rev_id)
360
                authors = ', '.join(revision.get_apparent_authors())
361
                signers.setdefault(authors, 0)
362
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
363
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
364
        for authors, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
365
            result.append( i18n.ngettext(u"{0} commit by author {1}", 
366
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
367
                                 number).format(number, authors) )
368
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
369
370
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
371
        """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
372
        signers = {}
373
        for rev_id, validity, empty in result:
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
374
            if validity == SIGNATURE_NOT_SIGNED:
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
375
                revision = repo.get_revision(rev_id)
376
                authors = ', '.join(revision.get_apparent_authors())
377
                signers.setdefault(authors, 0)
378
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
379
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
380
        for authors, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
381
            result.append( i18n.ngettext(u"{0} commit by author {1}", 
382
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
383
                                 number).format(number, authors) )
384
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
385
386
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
387
        """takes a verify result and returns list of missing key info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
388
        signers = {}
389
        for rev_id, validity, fingerprint in result:
390
            if validity == SIGNATURE_KEY_MISSING:
391
                signers.setdefault(fingerprint, 0)
392
                signers[fingerprint] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
393
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
394
        for fingerprint, number in signers.items():
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
395
            result.append( i18n.ngettext(u"Unknown key {0} signed {1} commit", 
396
                                 u"Unknown key {0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
397
                                 number).format(fingerprint, number) )
398
        return result
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
399
400
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
401
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
402
        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
403
                                        count[SIGNATURE_VALID])
404
405
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
406
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
407
        return i18n.ngettext(u"{0} commit with unknown key",
408
                             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
409
                             count[SIGNATURE_KEY_MISSING]).format(
410
                                        count[SIGNATURE_KEY_MISSING])
411
412
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
413
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
414
        return i18n.ngettext(u"{0} commit not valid",
415
                             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
416
                             count[SIGNATURE_NOT_VALID]).format(
417
                                            count[SIGNATURE_NOT_VALID])
418
419
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
420
        """returns message for number of commits"""
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
421
        return i18n.ngettext(u"{0} commit not signed",
422
                             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
423
                             count[SIGNATURE_NOT_SIGNED]).format(
424
                                        count[SIGNATURE_NOT_SIGNED])