~achipa/web2py/versioncontrol

« back to all changes in this revision

Viewing changes to gluon/contrib/VCSS/Subversion.py

still merging stuff

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
from VCS import VCS, VCSAuthError
 
3
from gluon.html import FORM, TABLE, TR, INPUT
 
4
from gluon.validators import IS_NOT_EMPTY
 
5
try:
 
6
    import pysvn
 
7
    __all__ = [ 'Subversion' ]
 
8
except: pass
 
9
import logging
 
10
import time
 
11
import os
 
12
 
 
13
class Subversion (VCS):
 
14
 
 
15
    @classmethod
 
16
    def checkoutform( self ):
 
17
        return FORM(TABLE(TR("Repository URI ", INPUT(_name = "url", requires = IS_NOT_EMPTY())),
 
18
            TR("User ", INPUT(_name = "username")),
 
19
            TR("Password ", INPUT(_type = "password", _name = "passwd")),
 
20
            TR("",INPUT(_type="submit",_value="Checkout"))))
 
21
 
 
22
 
 
23
    def printNotifyMessages( self ):
 
24
        # different versions of SVN notify messages in different orders
 
25
        # by sorting before printing we hope to have one set of regression
 
26
        # test data for multiple versions of SVN
 
27
        self.notify_message_list.sort()
 
28
#        for msg in self.notify_message_list:
 
29
#            logging.warning(msg)
 
30
#        self.notify_message_list = []
 
31
 
 
32
    def callback_notify( self, arg_dict ):
 
33
        if arg_dict['action'] == pysvn.wc_notify_action.update_completed:
 
34
            self.revision_update_complete = arg_dict['revision']
 
35
        elif arg_dict['path'] != '' and self.wc_notify_action_map[ arg_dict['action'] ] is not None:
 
36
            msg = '%s %s' % (self.wc_notify_action_map[ arg_dict['action'] ], arg_dict['path'])
 
37
            if self.pysvn_testing != '99.99.99':
 
38
                self.notify_message_list.append( msg )
 
39
            else:
 
40
                logging.warning(msg)
 
41
 
 
42
    def callback_getLogin( self, realm, username, may_save ):
 
43
        try:
 
44
           if self.firsttry and self.username is not None and len(self.username) > 0 and self.password is not None and len(self.password) > 0:
 
45
               self.firsttry = False
 
46
               return 1, self.username, self.password, False
 
47
           else:
 
48
               self.firsttry = False
 
49
               return 0, "", "", False
 
50
        except Exception, e:
 
51
           logging.warning(e)
 
52
#        return False, "", "", False
 
53
#        if self.username is not None and self.password is not None:
 
54
#            return True, self.username, self.password, save_password in ['y','ye','yes']
 
55
 
 
56
    def callback_ssl_server_trust_prompt( self, trust_data ):
 
57
        for key,value in trust_data.items():
 
58
            self.notify_message_list.append( '%s: %s\n' % (key, value) )
 
59
        return True, trust_data['failures'], False
 
60
 
 
61
    def callback_ssl_client_cert_password_prompt( self ):
 
62
        logging.warning( 'callback_ssl_client_cert_password_prompt not implemented' )
 
63
 
 
64
    def callback_ssl_client_cert_prompt( self ):
 
65
        logging.warning( 'callback_ssl_client_cert_prompt not implemented' )
 
66
 
 
67
    def callback_ssl_server_prompt( self ):
 
68
        logging.warning( 'callback_ssl_server_prompt not implemented' )
 
69
 
 
70
    def callback_cancel( self ):
 
71
        return False
 
72
 
 
73
    def __init__(self, appname = "Subversion", path = ".", location = None, username = None, password = None):
 
74
        self.firsttry = True
 
75
        self.name = appname
 
76
        self.revision_update_complete = None
 
77
        self.notify_message_list = []
 
78
        self.path = path
 
79
        self.client = pysvn.Client( )
 
80
        self.client.exception_style = 1
 
81
        self.client.callback_notify = self.callback_notify
 
82
        self.client.callback_get_login = self.callback_getLogin
 
83
        self.client.callback_ssl_server_trust_prompt = self.callback_ssl_server_trust_prompt
 
84
        self.client.callback_ssl_client_cert_password_prompt = self.callback_ssl_client_cert_password_prompt
 
85
        self.client.callback_ssl_client_cert_prompt = self.callback_ssl_client_cert_prompt
 
86
        self.client.callback_cancel = self.callback_cancel
 
87
        self.pysvn_testing = False
 
88
        self.debug_enabled = False
 
89
        self.username = username
 
90
        self.password = password
 
91
        self.failcode = None
 
92
        self.failmsg = ""
 
93
 
 
94
        self.wc_notify_action_map = {
 
95
        pysvn.wc_notify_action.add: 'A',
 
96
        pysvn.wc_notify_action.commit_added: 'A',
 
97
        pysvn.wc_notify_action.commit_deleted: 'D',
 
98
        pysvn.wc_notify_action.commit_modified: 'M',
 
99
        pysvn.wc_notify_action.commit_postfix_txdelta: None,
 
100
        pysvn.wc_notify_action.commit_replaced: 'R',
 
101
        pysvn.wc_notify_action.copy: 'c',
 
102
        pysvn.wc_notify_action.delete: 'D',
 
103
        pysvn.wc_notify_action.failed_revert: 'F',
 
104
        pysvn.wc_notify_action.resolved: 'R',
 
105
        pysvn.wc_notify_action.restore: 'R',
 
106
        pysvn.wc_notify_action.revert: 'R',
 
107
        pysvn.wc_notify_action.skip: 'skip',
 
108
        pysvn.wc_notify_action.status_completed: None,
 
109
        pysvn.wc_notify_action.status_external: 'X',
 
110
        pysvn.wc_notify_action.update_add: 'A',
 
111
        pysvn.wc_notify_action.update_completed: None,
 
112
        pysvn.wc_notify_action.update_delete: 'D',
 
113
        pysvn.wc_notify_action.update_external: 'X',
 
114
        pysvn.wc_notify_action.update_update: 'U',
 
115
        pysvn.wc_notify_action.annotate_revision: 'A',
 
116
        }
 
117
        if hasattr( pysvn.wc_notify_action, 'locked' ):
 
118
            self.wc_notify_action_map[ pysvn.wc_notify_action.locked ] = 'locked'
 
119
            self.wc_notify_action_map[ pysvn.wc_notify_action.unlocked ] = 'unlocked'
 
120
            self.wc_notify_action_map[ pysvn.wc_notify_action.failed_lock ] = 'failed_lock'
 
121
            self.wc_notify_action_map[ pysvn.wc_notify_action.failed_unlock ] = 'failed_unlock'
 
122
 
 
123
        self.wc_status_kind_map = {
 
124
        pysvn.wc_status_kind.added: 'A',
 
125
        pysvn.wc_status_kind.conflicted: 'C',
 
126
        pysvn.wc_status_kind.deleted: 'D',
 
127
        pysvn.wc_status_kind.external: 'X',
 
128
        pysvn.wc_status_kind.ignored: 'I',
 
129
        pysvn.wc_status_kind.incomplete: '!',
 
130
        pysvn.wc_status_kind.missing: '!',
 
131
        pysvn.wc_status_kind.merged: 'G',
 
132
        pysvn.wc_status_kind.modified: 'M',
 
133
        pysvn.wc_status_kind.none: ' ',
 
134
        pysvn.wc_status_kind.normal: ' ',
 
135
        pysvn.wc_status_kind.obstructed: '~',
 
136
        pysvn.wc_status_kind.replaced: 'R',
 
137
        pysvn.wc_status_kind.unversioned: '?',
 
138
        }
 
139
 
 
140
 
 
141
        if location is not None:    # if we initialize with a location, it means we are checking out
 
142
            try:
 
143
                self.client.checkout( location, path, recurse=True )
 
144
            except pysvn.ClientError, e:
 
145
                logging.warning(e)
 
146
                self.failcode = 'unknown'
 
147
                # or process the error list
 
148
                for message, code in e.args[-1]:
 
149
                    if code == 170001:
 
150
                        self.failcode = 'auth'
 
151
                    self.failmsg += "\n" + message
 
152
#                    print 'Code:',code,'Message:',message
 
153
#            if self.revision_update_complete is not None:
 
154
#                    return [True, 'Checked out revision %s' % self.revision_update_complete.number]
 
155
#            else:
 
156
#                    return [False, 'Checked out unknown revision - checkout failed?']
 
157
        
 
158
        self.printNotifyMessages()
 
159
        self.entry = self.client.info( self.path )
 
160
 
 
161
    @classmethod
 
162
    def underControl(self, path = '.', verbose=False):
 
163
        content = os.listdir(path)
 
164
        mdir = filter(lambda dir: dir == '.svn', content)
 
165
        if len(mdir) > 0:
 
166
            return True
 
167
        return False
 
168
 
 
169
    def get_info(self, verbose=False):
 
170
        if self.entry is None:
 
171
            self.entry = self.client.info( self.path )
 
172
        return self.entry
 
173
    
 
174
    def get_version(self, verbose=False):
 
175
        return self.get_info().revision.number
 
176
    
 
177
    def update(self, verbose=False):
 
178
        recurse = True # args.getBooleanOption( '--non-recursive', False )
 
179
        current_ver = self.get_version()
 
180
        try:
 
181
            self.rev_list = self.client.update( self.path , recurse=recurse )
 
182
        except pysvn.ClientError, e:
 
183
            for message, code in e.args[-1]:
 
184
                if code == 170001: raise VCSAuthError()
 
185
            raise e
 
186
        self.printNotifyMessages()
 
187
        if type(self.rev_list) == type([]) and len(self.rev_list) != 1:
 
188
            logging.warning( 'rev_list = %r' % [rev.number for rev in self.rev_list])
 
189
#        
 
190
        if current_ver == self.revision_update_complete.number:
 
191
                return [True, 'No updates found']
 
192
        if self.revision_update_complete is not None:
 
193
                return [True, 'Updated to revision %s' % self.revision_update_complete.number]
 
194
        else:
 
195
            return [False, 'Updated to unknown revision - update failed?']
 
196
 
 
197
    def commit(self, message, verbose=False):
 
198
        rev = self.client.checkin( self.path,  message, recurse=True )
 
199
        self.printNotifyMessages()
 
200
        
 
201
        if rev is None:
 
202
            return [True, 'Nothing to commit']
 
203
        elif rev.number > 0:
 
204
            return [True, 'Commited as revision %s' % rev.number]
 
205
        else:
 
206
            return [False, 'Commit failed']
 
207
 
 
208
 
 
209
 
 
210
    def _parseRevisionArg( self, rev_string ):
 
211
        if rev_string == None or rev_string == 'None':
 
212
            return pysvn.Revision( pysvn.opt_revision_kind.head )
 
213
        if rev_string.strip() == '':
 
214
            return pysvn.Revision( pysvn.opt_revision_kind.head )
 
215
        if rev_string.lower() == 'base':
 
216
            return pysvn.Revision( pysvn.opt_revision_kind.base )
 
217
        if rev_string.lower() == 'head':
 
218
            return pysvn.Revision( pysvn.opt_revision_kind.head )
 
219
        if rev_string.lower() == 'working':
 
220
            return pysvn.Revision( pysvn.opt_revision_kind.working )
 
221
        if rev_string.lower() == 'committed':
 
222
            return pysvn.Revision( pysvn.opt_revision_kind.committed )
 
223
        if rev_string.lower() == 'prev' or rev_string.lower() == 'previous':
 
224
            return pysvn.Revision( pysvn.opt_revision_kind.previous )
 
225
        if rev_string.lower() == 'unspecified':
 
226
            return pysvn.Revision( pysvn.opt_revision_kind.unspecified )
 
227
#        if rev_string[0] == '{' and rev_string[-1] == '}':
 
228
#            try:
 
229
#                date = parse_datetime.parse_time( rev_string[1:-2] )
 
230
#                return pysvn.Revision( pysvn.opt_revision_kind.date, date )
 
231
#            except parse_datetime.DateTimeSyntaxError, e:
 
232
#                raise CommandError, e.reason()
 
233
        # either a rev number or a date
 
234
        try:
 
235
            return pysvn.Revision( pysvn.opt_revision_kind.number, int(rev_string) )
 
236
        except ValueError:
 
237
            pass
 
238
        logging.warning( 'Cannot parse %s as a revision value' % rev_string )
 
239
 
 
240
 
 
241
    def fmtDateTime( self, t ):
 
242
        return time.strftime( '%d-%b-%Y %H:%M:%S', time.localtime( t ) )
 
243
 
 
244
    def get_log(self,vfrom='prev',vto='head',reverse=True, verbose=False):
 
245
        if vfrom == vto == '' :
 
246
            vfrom = 'prev'
 
247
            vto = 'head'
 
248
        try:
 
249
            all_logs = self.client.log( self.path,
 
250
                revision_start = self._parseRevisionArg(vfrom),
 
251
                revision_end = self._parseRevisionArg(vto),
 
252
                discover_changed_paths=True )
 
253
        except pysvn.ClientError, e:
 
254
            for message, code in e.args[-1]:
 
255
                if code == 170001: raise VCSAuthError()
 
256
            raise e
 
257
        
 
258
        self.printNotifyMessages()
 
259
        msg = []
 
260
        if reverse:
 
261
            all_logs.reverse()
 
262
 
 
263
        for log in all_logs:
 
264
                msg.append( '-'*60 )
 
265
                msg.append(  'rev %d: %s | %s | %d lines' % \
 
266
                    (log.revision.number, log.author, self.fmtDateTime( log.date ),
 
267
                    len(log.message.split('\n'))))
 
268
        
 
269
                if len(log.changed_paths) > 0:
 
270
                        msg.append('Changed paths:')
 
271
                        for change_info in log.changed_paths:
 
272
                                if change_info.copyfrom_path is None:
 
273
                                    msg.append( '  %s %s' % (change_info.action, change_info.path))
 
274
                                else:
 
275
                                    msg.append( '  %s %s (from %s:%d)' % \
 
276
                                        (change_info.action, change_info.path,
 
277
                                        change_info.copyfrom_path, change_info.copyfrom_revision.number))
 
278
        
 
279
                msg.append("\n")
 
280
                msg.append(log.message)
 
281
        
 
282
        msg.append('-'*60)
 
283
        return "\n".join(msg)
 
284
 
 
285
    def by_path( self, a, b ):
 
286
        return cmp( a.path, b.path )
 
287
 
 
288
    def get_status(self, verbose = False):
 
289
        all_files = self.client.status( self.path, recurse=True, get_all=True, ignore=False, update=True )
 
290
        all_files.sort( self.by_path )
 
291
        msg = []
 
292
        msg.append( 'State Odd Update Change Author         File')
 
293
        for file in all_files:
 
294
                if file.text_status == pysvn.wc_status_kind.ignored:
 
295
                    continue
 
296
                
 
297
                if file.text_status == pysvn.wc_status_kind.normal and not verbose:
 
298
                    continue
 
299
 
 
300
                state = '%s%s%s%s%s' % (self.wc_status_kind_map[ file.text_status ],
 
301
                        self.wc_status_kind_map[ file.prop_status ],
 
302
                        ' L'[ file.is_locked ],
 
303
                        ' +'[ file.is_copied ],
 
304
                        ' S'[ file.is_switched ])
 
305
                
 
306
                if( file.repos_text_status != pysvn.wc_status_kind.none
 
307
                or  file.repos_prop_status != pysvn.wc_status_kind.none ):
 
308
                    odd_status = '%s%s' % (self.wc_status_kind_map[ file.repos_text_status ],
 
309
                        self.wc_status_kind_map[ file.repos_prop_status ])
 
310
                else:
 
311
                    odd_status = '  '
 
312
                
 
313
                lock_state = ' '
 
314
                if file.entry is not None and hasattr( file.entry, 'lock_token' ):
 
315
                    if file.entry.lock_token is not None:
 
316
                        lock_state = 'K'
 
317
                
 
318
                if hasattr( file, 'repos_lock' ) and file.repos_lock is not None:
 
319
                    lock_state = 'O'
 
320
                
 
321
                if file.entry is not None:
 
322
                    msg.append( '%s%s %s %6d %6d %-14s %s' % (state, lock_state,
 
323
                        odd_status,
 
324
                        file.entry.revision.number,
 
325
                        file.entry.commit_revision.number,
 
326
                        file.entry.commit_author.encode('utf-8'),
 
327
                        file.path.encode('utf-8')))
 
328
                
 
329
                else:
 
330
                    if( file.text_status != pysvn.wc_status_kind.normal
 
331
                    or file.prop_status != pysvn.wc_status_kind.normal
 
332
                    or lock_state.strip() != ''):
 
333
                        msg.append( '%s%s                                 %s' % (state, lock_state, file.path.encode('utf-8')))
 
334
#            else:
 
335
#                msg.append( '%s%s %s %6s %6s %-14s %s' % (state, lock_state,
 
336
#                    odd_status,
 
337
#                    '',
 
338
#                    '',
 
339
#                    '',
 
340
#                    file.path.encode('utf-8')))
 
341
 
 
342
        return "\n".join(msg)
 
343
#       elif update:
 
344
#           print '%s%s %s %s' % (state, lock_state,
 
345
#               odd_status,
 
346
#               file.path.encode('utf-8'))
 
347
#
 
348
 
 
349