/brz/remove-bazaar

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