2
from VCS import VCS, VCSAuthError
3
from gluon.html import FORM, TABLE, TR, INPUT
4
from gluon.validators import IS_NOT_EMPTY
7
__all__ = [ 'Subversion' ]
13
class Subversion (VCS):
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"))))
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 = []
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 )
42
def callback_getLogin( self, realm, username, may_save ):
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:
46
return 1, self.username, self.password, False
49
return 0, "", "", False
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']
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
61
def callback_ssl_client_cert_password_prompt( self ):
62
logging.warning( 'callback_ssl_client_cert_password_prompt not implemented' )
64
def callback_ssl_client_cert_prompt( self ):
65
logging.warning( 'callback_ssl_client_cert_prompt not implemented' )
67
def callback_ssl_server_prompt( self ):
68
logging.warning( 'callback_ssl_server_prompt not implemented' )
70
def callback_cancel( self ):
73
def __init__(self, appname = "Subversion", path = ".", location = None, username = None, password = None):
76
self.revision_update_complete = None
77
self.notify_message_list = []
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
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',
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'
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: '?',
141
if location is not None: # if we initialize with a location, it means we are checking out
143
self.client.checkout( location, path, recurse=True )
144
except pysvn.ClientError, e:
146
self.failcode = 'unknown'
147
# or process the error list
148
for message, code in e.args[-1]:
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]
156
# return [False, 'Checked out unknown revision - checkout failed?']
158
self.printNotifyMessages()
159
self.entry = self.client.info( self.path )
162
def underControl(self, path = '.', verbose=False):
163
content = os.listdir(path)
164
mdir = filter(lambda dir: dir == '.svn', content)
169
def get_info(self, verbose=False):
170
if self.entry is None:
171
self.entry = self.client.info( self.path )
174
def get_version(self, verbose=False):
175
return self.get_info().revision.number
177
def update(self, verbose=False):
178
recurse = True # args.getBooleanOption( '--non-recursive', False )
179
current_ver = self.get_version()
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()
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])
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]
195
return [False, 'Updated to unknown revision - update failed?']
197
def commit(self, message, verbose=False):
198
rev = self.client.checkin( self.path, message, recurse=True )
199
self.printNotifyMessages()
202
return [True, 'Nothing to commit']
204
return [True, 'Commited as revision %s' % rev.number]
206
return [False, 'Commit failed']
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] == '}':
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
235
return pysvn.Revision( pysvn.opt_revision_kind.number, int(rev_string) )
238
logging.warning( 'Cannot parse %s as a revision value' % rev_string )
241
def fmtDateTime( self, t ):
242
return time.strftime( '%d-%b-%Y %H:%M:%S', time.localtime( t ) )
244
def get_log(self,vfrom='prev',vto='head',reverse=True, verbose=False):
245
if vfrom == vto == '' :
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()
258
self.printNotifyMessages()
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'))))
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))
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))
280
msg.append(log.message)
283
return "\n".join(msg)
285
def by_path( self, a, b ):
286
return cmp( a.path, b.path )
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 )
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:
297
if file.text_status == pysvn.wc_status_kind.normal and not verbose:
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 ])
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 ])
314
if file.entry is not None and hasattr( file.entry, 'lock_token' ):
315
if file.entry.lock_token is not None:
318
if hasattr( file, 'repos_lock' ) and file.repos_lock is not None:
321
if file.entry is not None:
322
msg.append( '%s%s %s %6d %6d %-14s %s' % (state, lock_state,
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')))
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')))
335
# msg.append( '%s%s %s %6s %6s %-14s %s' % (state, lock_state,
340
# file.path.encode('utf-8')))
342
return "\n".join(msg)
344
# print '%s%s %s %s' % (state, lock_state,
346
# file.path.encode('utf-8'))