1''' 2 ==================================================================== 3 Copyright (c) 2003-2009 Barry A Scott. All rights reserved. 4 5 This software is licensed as described in the file LICENSE.txt, 6 which you should have received as part of this distribution. 7 8 ==================================================================== 9''' 10import pysvn 11import time 12import sys 13import os 14import parse_datetime 15import glob 16import locale 17import types 18 19if sys.version_info.major == 2: 20 # suport cp65001 aka utf-8 21 import codecs 22 codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) 23 24try: 25 sorted( [] ) 26except NameError: 27 def sorted( list_in ): 28 list_out = list( list_in ) 29 list_out.sort() 30 return list_out 31 32if hasattr( types, 'StringTypes' ): 33 StringTypes = types.StringTypes 34else: 35 StringTypes = [type( '' )] 36 37if hasattr( types, 'DictType' ): 38 DictType = types.DictType 39else: 40 DictType = type( {} ) 41 42class CommandError( Exception ): 43 def __init__( self, reason ): 44 Exception.__init__( self ) 45 self._reason = reason 46 47 def reason( self ): 48 return self._reason 49 50 def __str__( self ): 51 return self._reason 52 53def main( args ): 54 progname = os.path.basename( args[0] ) 55 pause = False 56 if args[1:2] == ['--pause']: 57 del args[1] 58 pause = True 59 60 # if the locale is not setup SVN can report errors handling non ascii file names 61 initLocale() 62 63 svn_cmd = SvnCommand( progname ) 64 rc = svn_cmd.dispatch( args[1:] ) 65 if pause: 66 sys.stdin.readline() 67 return rc 68 69def initLocale(): 70 # init the locale 71 if sys.platform in ['win32','cygwin']: 72 locale.setlocale( locale.LC_ALL, '' ) 73 74 else: 75 language_code, encoding = locale.getdefaultlocale() 76 if language_code is None: 77 language_code = 'en_GB' 78 79 if encoding is None: 80 encoding = 'UTF-8' 81 if encoding.lower() == 'utf': 82 encoding = 'UTF-8' 83 84 try: 85 # setlocale fails when params it does not understand are passed 86 locale.setlocale( locale.LC_ALL, '%s.%s' % (language_code, encoding) ) 87 except locale.Error: 88 # force a locale that will work 89 locale.setlocale( locale.LC_ALL, 'en_GB.UTF-8' ) 90 91def fmtDateTime( t ): 92 return time.strftime( '%d-%b-%Y %H:%M:%S', time.localtime( t ) ) 93 94wc_status_kind_map = { 95pysvn.wc_status_kind.added: 'A', 96pysvn.wc_status_kind.conflicted: 'C', 97pysvn.wc_status_kind.deleted: 'D', 98pysvn.wc_status_kind.external: 'X', 99pysvn.wc_status_kind.ignored: 'I', 100pysvn.wc_status_kind.incomplete: '!', 101pysvn.wc_status_kind.missing: '!', 102pysvn.wc_status_kind.merged: 'G', 103pysvn.wc_status_kind.modified: 'M', 104pysvn.wc_status_kind.none: ' ', 105pysvn.wc_status_kind.normal: ' ', 106pysvn.wc_status_kind.obstructed: '~', 107pysvn.wc_status_kind.replaced: 'R', 108pysvn.wc_status_kind.unversioned: '?', 109} 110 111wc_notify_action_map = { 112pysvn.wc_notify_action.add: 'A', 113pysvn.wc_notify_action.commit_added: 'A', 114pysvn.wc_notify_action.commit_deleted: 'D', 115pysvn.wc_notify_action.commit_modified: 'M', 116pysvn.wc_notify_action.commit_postfix_txdelta: None, 117pysvn.wc_notify_action.commit_replaced: 'R', 118pysvn.wc_notify_action.copy: 'c', 119pysvn.wc_notify_action.delete: 'D', 120pysvn.wc_notify_action.failed_revert: 'F', 121pysvn.wc_notify_action.resolved: 'R', 122pysvn.wc_notify_action.restore: 'R', 123pysvn.wc_notify_action.revert: 'R', 124pysvn.wc_notify_action.skip: 'skip', 125pysvn.wc_notify_action.status_completed: None, 126pysvn.wc_notify_action.status_external: 'X', 127pysvn.wc_notify_action.update_add: 'A', 128pysvn.wc_notify_action.update_completed: None, 129pysvn.wc_notify_action.update_delete: 'D', 130pysvn.wc_notify_action.update_external: 'X', 131pysvn.wc_notify_action.update_update: 'U', 132pysvn.wc_notify_action.annotate_revision: 'A', 133} 134 135# new in svn 1.4? 136if hasattr( pysvn.wc_notify_action, 'locked' ): 137 wc_notify_action_map[ pysvn.wc_notify_action.locked ] = 'locked' 138 wc_notify_action_map[ pysvn.wc_notify_action.unlocked ] = 'unlocked' 139 wc_notify_action_map[ pysvn.wc_notify_action.failed_lock ] = 'failed_lock' 140 wc_notify_action_map[ pysvn.wc_notify_action.failed_unlock ] = 'failed_unlock' 141 142# new in svn 1.5 143if hasattr( pysvn.wc_notify_action, 'exists' ): 144 wc_notify_action_map[ pysvn.wc_notify_action.exists ] = 'exists' 145 wc_notify_action_map[ pysvn.wc_notify_action.changelist_set ] = 'changelist_set' 146 wc_notify_action_map[ pysvn.wc_notify_action.changelist_clear ] = 'changelist_clear' 147 wc_notify_action_map[ pysvn.wc_notify_action.changelist_moved ] = 'changelist_moved' 148 wc_notify_action_map[ pysvn.wc_notify_action.foreign_merge_begin ] = 'foreign_merge_begin' 149 wc_notify_action_map[ pysvn.wc_notify_action.merge_begin ] = 'merge_begin' 150 wc_notify_action_map[ pysvn.wc_notify_action.update_replace ] = 'update_replace' 151 152# new in svn 1.6 153if hasattr( pysvn.wc_notify_action, 'property_added' ): 154 wc_notify_action_map[ pysvn.wc_notify_action.property_added ] = 'property_added' 155 wc_notify_action_map[ pysvn.wc_notify_action.property_modified ] = 'property_modified' 156 wc_notify_action_map[ pysvn.wc_notify_action.property_deleted ] = 'property_deleted' 157 wc_notify_action_map[ pysvn.wc_notify_action.property_deleted_nonexistent ] = 'property_deleted_nonexistent' 158 wc_notify_action_map[ pysvn.wc_notify_action.revprop_set ] = 'revprop_set' 159 wc_notify_action_map[ pysvn.wc_notify_action.revprop_deleted ] = 'revprop_deleted' 160 wc_notify_action_map[ pysvn.wc_notify_action.merge_completed ] = 'merge_completed' 161 wc_notify_action_map[ pysvn.wc_notify_action.tree_conflict ] = 'tree_conflict' 162 wc_notify_action_map[ pysvn.wc_notify_action.failed_external ] = 'failed_external' 163 164# new in svn 1.7 165if hasattr( pysvn.wc_notify_action, 'update_started' ): 166 wc_notify_action_map[ pysvn.wc_notify_action.update_started ] = 'update_started' 167 wc_notify_action_map[ pysvn.wc_notify_action.update_skip_obstruction ] = 'update_skip_obstruction' 168 wc_notify_action_map[ pysvn.wc_notify_action.update_skip_working_only ] = 'update_skip_working_only' 169 wc_notify_action_map[ pysvn.wc_notify_action.update_external_removed ] = 'update_external_removed' 170 wc_notify_action_map[ pysvn.wc_notify_action.update_shadowed_add ] = 'update_shadowed_add' 171 wc_notify_action_map[ pysvn.wc_notify_action.update_shadowed_update ] = 'update_shadowed_update' 172 wc_notify_action_map[ pysvn.wc_notify_action.update_shadowed_delete ] = 'update_shadowed_delete' 173 wc_notify_action_map[ pysvn.wc_notify_action.merge_record_info ] = 'merge_record_info' 174 wc_notify_action_map[ pysvn.wc_notify_action.upgraded_path ] = 'upgraded_path' 175 wc_notify_action_map[ pysvn.wc_notify_action.merge_record_info_begin ] = 'merge_record_info_begin' 176 wc_notify_action_map[ pysvn.wc_notify_action.merge_elide_info ] = 'merge_elide_info' 177 wc_notify_action_map[ pysvn.wc_notify_action.patch ] = 'patch' 178 wc_notify_action_map[ pysvn.wc_notify_action.patch_applied_hunk ] = 'patch_applied_hunk' 179 wc_notify_action_map[ pysvn.wc_notify_action.patch_rejected_hunk ] = 'patch_rejected_hunk' 180 wc_notify_action_map[ pysvn.wc_notify_action.patch_hunk_already_applied ] = 'patch_hunk_already_applied' 181 wc_notify_action_map[ pysvn.wc_notify_action.commit_copied ] = 'commit_copied' 182 wc_notify_action_map[ pysvn.wc_notify_action.commit_copied_replaced ] = 'commit_copied_replaced' 183 wc_notify_action_map[ pysvn.wc_notify_action.url_redirect ] = 'url_redirect' 184 wc_notify_action_map[ pysvn.wc_notify_action.path_nonexistent ] = 'path_nonexistent' 185 wc_notify_action_map[ pysvn.wc_notify_action.exclude ] = 'exclude' 186 wc_notify_action_map[ pysvn.wc_notify_action.failed_conflict ] = 'failed_conflict' 187 wc_notify_action_map[ pysvn.wc_notify_action.failed_missing ] = 'failed_missing' 188 wc_notify_action_map[ pysvn.wc_notify_action.failed_out_of_date ] = 'failed_out_of_date' 189 wc_notify_action_map[ pysvn.wc_notify_action.failed_no_parent ] = 'failed_no_parent' 190 191# new in svn 1.7.1+? 192if hasattr( pysvn.wc_notify_action, 'failed_locked' ): 193 wc_notify_action_map[ pysvn.wc_notify_action.failed_locked ] = 'failed_locked' 194 wc_notify_action_map[ pysvn.wc_notify_action.failed_forbidden_by_server ] = 'failed_forbidden_by_server' 195 wc_notify_action_map[ pysvn.wc_notify_action.skip_conflicted ] = 'skip_conflicted' 196 197# new in svn 1.8 198if hasattr( pysvn.wc_notify_action, 'update_broken_lock' ): 199 wc_notify_action_map[ pysvn.wc_notify_action.update_broken_lock ] = 'update_broken_lock' 200 wc_notify_action_map[ pysvn.wc_notify_action.failed_obstruction ] = 'failed_obstruction' 201 wc_notify_action_map[ pysvn.wc_notify_action.conflict_resolver_starting ] = 'conflict_resolver_starting' 202 wc_notify_action_map[ pysvn.wc_notify_action.conflict_resolver_done ] = 'conflict_resolver_done' 203 wc_notify_action_map[ pysvn.wc_notify_action.left_local_modifications ] = 'left_local_modifications' 204 wc_notify_action_map[ pysvn.wc_notify_action.foreign_copy_begin ] = 'foreign_copy_begin' 205 wc_notify_action_map[ pysvn.wc_notify_action.move_broken ] = 'move_broken' 206 207# new in svn 1.9 208if hasattr( pysvn.wc_notify_action, 'cleanup_external' ): 209 wc_notify_action_map[ pysvn.wc_notify_action.cleanup_external ] = 'cleanup_external' 210 wc_notify_action_map[ pysvn.wc_notify_action.failed_requires_target ] = 'failed_requires_target' 211 wc_notify_action_map[ pysvn.wc_notify_action.info_external ] = 'info_external' 212 wc_notify_action_map[ pysvn.wc_notify_action.commit_finalizing ] = 'commit_finalizing' 213 214class SvnCommand: 215 def __init__( self, progname ): 216 self.progname = progname 217 self.client = None 218 self.revision_update_complete = None 219 self.notify_message_list = [] 220 self.pysvn_testing = False 221 self.debug_enabled = False 222 self.next_log_message = None 223 224 def debug( self, msg, args=() ): 225 if self.debug_enabled: 226 print( 'Debug: %s' % (msg % args) ) 227 228 def initClient( self, config_dir ): 229 self.client = pysvn.Client( config_dir ) 230 self.client.exception_style = 1 231 self.client.commit_info_style = 1 232 self.client.callback_get_login = self.callback_getLogin 233 self.client.callback_get_log_message = self.callback_getLogMessage 234 self.client.callback_notify = self.callback_notify 235 self.client.callback_cancel = self.callback_cancel 236 if hasattr( self.client, 'callback_conflict_resolver' ): 237 self.client.callback_conflict_resolver = self.callback_conflict_resolver 238 self.client.callback_cancel = self.callback_cancel 239 self.client.callback_ssl_client_cert_password_prompt = self.callback_ssl_client_cert_password_prompt 240 self.client.callback_ssl_client_cert_prompt = self.callback_ssl_client_cert_prompt 241 self.client.callback_ssl_server_prompt = self.callback_ssl_server_prompt 242 self.client.callback_ssl_server_trust_prompt = self.callback_ssl_server_trust_prompt 243 244 def callback_ssl_client_cert_password_prompt( self ): 245 print( 'callback_ssl_client_cert_password_prompt' ) 246 247 def callback_ssl_client_cert_prompt( self ): 248 print( 'callback_ssl_client_cert_prompt' ) 249 250 def callback_ssl_server_prompt( self ): 251 print( 'callback_ssl_server_prompt' ) 252 253 def callback_ssl_server_trust_prompt( self, trust_data ): 254 for key,value in trust_data.items(): 255 print( '%s: %s' % (key, value) ) 256 print('') 257 answer = '' 258 while answer.lower() not in ['p','t','r']: 259 sys.stdout.write( '(P)ermanent accept, (T)emporary accept or (R)eject: ' ) 260 answer = sys.stdin.readline().strip() 261 if answer.lower() == 'p': 262 return True, trust_data['failures'], True 263 if answer.lower() == 't': 264 return True, trust_data['failures'], False 265 return False, 0, False 266 267 def callback_cancel( self ): 268 return False 269 270 def callback_notify( self, arg_dict ): 271 if arg_dict['action'] == pysvn.wc_notify_action.update_completed: 272 self.revision_update_complete = arg_dict['revision'] 273 elif arg_dict['path'] != '' and wc_notify_action_map[ arg_dict['action'] ] is not None: 274 msg = '%s %s' % (wc_notify_action_map[ arg_dict['action'] ], arg_dict['path']) 275 if self.pysvn_testing != '99.99.99': 276 self.notify_message_list.append( msg ) 277 else: 278 print( msg ) 279 280 def callback_conflict_resolver( self, arg_dict ): 281 print( 'callback_conflict_resolver' ) 282 for key in sorted( arg_dict.keys() ): 283 value = arg_dict[ key ] 284 285 if type(value) == DictType: 286 value = '{%s}' % (', '.join( ['%r: %r' % (key, value) for key, value in sorted( value.items() )] ),) 287 288 elif type(value) not in StringTypes: 289 value = repr(value) 290 291 print( ' %s: %s' % (key, value) ) 292 293 return pysvn.wc_conflict_choice.postpone, None, False 294 295 def callback_getLogin( self, realm, username, may_save ): 296 print( 'May save: %s ' % may_save ) 297 print( 'Realm: %s ' % realm ) 298 if username: 299 print( 'Username: %s' % username ) 300 else: 301 sys.stdout.write( 'Username: ' ) 302 username = sys.stdin.readline().strip() 303 if len(username) == 0: 304 return 0, '', '', False 305 306 sys.stdout.write( 'Password: ' ) 307 password = sys.stdin.readline().strip() 308 309 save_password = 'x' 310 while save_password.lower() not in ['y','ye','yes','n', 'no','']: 311 sys.stdout.write( 'Save password? [y/n] ' ) 312 save_password = sys.stdin.readline().strip() 313 314 return 1, username, password, save_password in ['y','ye','yes'] 315 316 def getLogMessage( self ): 317 if self.next_log_message is not None: 318 message = self.next_log_message 319 self.next_log_message = None 320 return message 321 322 sys.stdout.write( 'Log message\n' ) 323 sys.stdout.write( '--- -------\n' ) 324 message = sys.stdin.read() 325 return message 326 327 def callback_getLogMessage( self ): 328 return True, self.getLogMessage() 329 330 def dispatch( self, argv ): 331 try: 332 args = SvnArguments( argv ) 333 cmd_name = 'cmd_%s' % args.getCommandName( 'help' ) 334 335 self.initClient( args.getOptionalValue( '--config-dir', '' ) ) 336 self.client.set_auth_cache( args.getBooleanOption( '--no-auth-cache', False ) ) 337 self.pysvn_testing = args.getOptionalValue( '--pysvn-testing', '99.99.99' ) 338 self.debug_enabled = args.getBooleanOption( '--debug', True ) 339 340 getattr( self, cmd_name, self.cmd_help )( args ) 341 342 self.printNotifyMessages() 343 344 except pysvn.ClientError as e: 345 self.printNotifyMessages() 346 print( e.args[0] ) 347 return 1 348 349 except CommandError as e: 350 self.printNotifyMessages() 351 print( e.reason() ) 352 return 1 353 354 return 0 355 356 def printNotifyMessages( self ): 357 # different versions of SVN notify messages in different orders 358 # by sorting before printing we hope to have one set of regression 359 # test data for multiple versions of SVN 360 self.notify_message_list.sort() 361 for msg in self.notify_message_list: 362 print( msg ) 363 self.notify_message_list = [] 364 365 def cmd_version( self, args ): 366 print( 'PYSVN Version: %r' % (pysvn.version,) ) 367 print( 'SVN Version: %r' % (pysvn.svn_version,) ) 368 if hasattr( pysvn, 'svn_api_version' ): 369 print( 'SVN API Version: %r' % (pysvn.svn_api_version,) ) 370 print( 'pysvn._pysvn %r' % (pysvn._pysvn,) ) 371 372 373 def cmd_is_url( self, args ): 374 path = args.getPositionalArgs( 1 )[0] 375 is_url = self.client.is_url( path ) 376 if is_url: 377 print( 'url %s' % path ) 378 else: 379 print( 'path %s' % path ) 380 381 def cmd_add( self, args ): 382 recurse = args.getBooleanOption( '--non-recursive', False ) 383 force = args.getBooleanOption( '--force', False ) 384 385 self.client.add( args.getPositionalArgs( 1 ), recurse=recurse, force=force ) 386 387 def cmd_add_to_changelist( self, args ): 388 if not hasattr( self.client, add_to_changelist ): 389 print( 'Error: add_to_changelist is not supported by this version of Subversion' ) 390 return 391 392 path, changelist = args.getPositionalArgs( 2, 2 ) 393 self.client.add_to_changelist( path, changelist ) 394 395 def cmd_annotate( self, args ): 396 start_revision, end_revision = args.getOptionalRevisionPair( '--revision', '0', 'head' ) 397 positional_args = args.getPositionalArgs( 1, 1 ) 398 all_lines = self.client.annotate( 399 positional_args[0], 400 revision_start=start_revision, 401 revision_end=end_revision ) 402 self.printNotifyMessages() 403 404 for line in all_lines: 405 print( '%d| r%d | %s | %s | %s' % 406 (line['number']+1 407 ,line['revision'].number 408 ,line['author'] 409 ,line['date'] 410 ,line['line']) ) 411 cmd_ann = cmd_annotate 412 413 def cmd_annotate2( self, args ): 414 if not hasattr( self.client, 'annotate2' ): 415 print( 'annotate2 is not available in this version of subversion' ) 416 return 417 418 start_revision, end_revision = args.getOptionalRevisionPair( '--revision', '0', 'head' ) 419 positional_args = args.getPositionalArgs( 1, 1 ) 420 421 all_lines = self.client.annotate2( 422 positional_args[0], 423 revision_start=start_revision, 424 revision_end=end_revision ) 425 426 self.printNotifyMessages() 427 428 for line in all_lines: 429 print( '%d| r%d | %s' % 430 (line['number']+1 431 ,line['revision'].number 432 ,line['line']) ) 433 if line['merged_revision'] is not None: 434 print( ' Merged from r%d %s' % 435 (line['merged_revision'] 436 ,line['merged_path']) ) 437 438 cmd_ann2 = cmd_annotate2 439 440 def cmd_cat( self, args ): 441 revision = args.getOptionalRevision( '--revision', 'head' ) 442 text = self.client.cat( args.getPositionalArgs( 1, 1 )[0], revision=revision ) 443 print( text.decode( 'utf-8' ).replace( '\r\n', '\n' ) ) 444 445 def cmd_checkout( self, args ): 446 recurse = args.getBooleanOption( '--non-recursive', False ) 447 positional_args = args.getPositionalArgs( 1, 2 ) 448 if len(positional_args) == 1: 449 positional_args.append( os.path.basename( positional_args[0] ) ) 450 451 self.revision_update_complete = None 452 self.client.checkout( positional_args[0], positional_args[1], recurse=recurse ) 453 self.printNotifyMessages() 454 455 if self.revision_update_complete is not None: 456 print( 'Checked out revision %s' % self.revision_update_complete.number ) 457 else: 458 print( 'Checked out unknown revision - checkout failed?' ) 459 460 cmd_co = cmd_checkout 461 462 def cmd_cleanup( self, args ): 463 positional_args = args.getPositionalArgs( 0, 1 ) 464 if len(positional_args) == 0: 465 positional_args.append( '.' ) 466 467 self.client.cleanup( positional_args[0] ) 468 469 def cmd_checkin( self, args ): 470 msg = args.getOptionalValue( '--message', '' ) 471 472 recurse = args.getBooleanOption( '--non-recursive', False ) 473 positional_args = args.getPositionalArgs( 0 ) 474 if len(positional_args) == 0: 475 positional_args.append( '.' ) 476 if msg == '': 477 msg = self.getLogMessage() 478 479 commit_info = self.client.checkin( positional_args, msg, recurse=recurse ) 480 rev = commit_info["revision"] 481 self.printNotifyMessages() 482 483 if commit_info['post_commit_err'] is not None: 484 print( commit_info['post_commit_err'] ) 485 486 if rev is None: 487 print( 'Nothing to commit' ) 488 elif rev.number > 0: 489 print( 'Revision %s' % rev.number ) 490 else: 491 print( 'Commit failed' ) 492 493 494 cmd_commit = cmd_checkin 495 cmd_ci = cmd_checkin 496 497 def cmd_copy( self, args ): 498 positional_args = args.getPositionalArgs( 2, 2 ) 499 self.client.copy( positional_args[0], positional_args[1] ) 500 cmd_cp = cmd_copy 501 502 def cmd_diff( self, args ): 503 recurse = args.getBooleanOption( '--non-recursive', False ) 504 revision1, revision2 = args.getOptionalRevisionPair( '--revision', 'base', 'working' ) 505 positional_args = args.getPositionalArgs( 0, 1 ) 506 if len(positional_args) == 0: 507 positional_args.append( '.' ) 508 509 if 'TEMP' in os.environ: 510 tmpdir = os.environ['TEMP'] 511 elif 'TMPDIR' in os.environ: 512 tmpdir = os.environ['TMPDIR'] 513 elif 'TMP' in os.environ: 514 tmpdir = os.environ['TMP'] 515 elif os.path.exists( '/usr/tmp' ): 516 tmpdir = '/usr/tmp' 517 elif os.path.exists( '/tmp' ): 518 tmpdir = '/tmp' 519 else: 520 print( 'No tmp dir!' ) 521 return 522 523 self.debug( 'cmd_diff %r, %r, %r, %r, %r' % (tmpdir, positional_args[0], recurse, revision1, revision2) ) 524 diff_text = self.client.diff( tmpdir, positional_args[0], recurse=recurse, 525 revision1=revision1, revision2=revision2, 526 diff_options=['-u'] ) 527 print( diff_text.replace( '\r\n', '\n' ) ) 528 529 def cmd_export( self, args ): 530 force = args.getBooleanOption( '--force', False ) 531 revision_url = args.getOptionalRevision( '--revision', 'head' ) 532 revision_wc = args.getOptionalRevision( '--revision', 'working' ) 533 native_eol = args.getOptionalValue( '--native-eol', None ) 534 positional_args = args.getPositionalArgs( 2, 2 ) 535 if self.client.is_url( positional_args[0] ): 536 revision = revision_url 537 else: 538 revision = revision_wc 539 540 self.client.export( positional_args[0], positional_args[1], revision=revision, force=force, native_eol=native_eol ) 541 542 def cmd_info( self, args ): 543 positional_args = args.getPositionalArgs( 0, 1 ) 544 if len(positional_args) == 0: 545 positional_args.append( '.' ) 546 547 path = positional_args[0] 548 549 entry = self.client.info( path ) 550 551 print( 'Path: %s' % path ) 552 if entry.name and entry.name != 'svn:this_dir': 553 print( 'Name: %s' % entry.name ) 554 if entry.url: 555 print( 'Url: %s' % entry.url ) 556 if entry.repos and self.pysvn_testing >= '01.03.00': 557 print( 'Repository: %s' % entry.repos ) 558 if entry.uuid: 559 print( 'Repository UUID: %s' % entry.uuid ) 560 if entry.revision.kind == pysvn.opt_revision_kind.number: 561 print( 'Revision: %s' % entry.revision.number ) 562 if entry.kind == pysvn.node_kind.file: 563 print( 'Node kind: file' ) 564 elif entry.kind == pysvn.node_kind.dir: 565 print( 'Node kind: directory' ) 566 elif entry.kind == pysvn.node_kind.none: 567 print( 'Node kind: none' ) 568 else: 569 print( 'Node kind: unknown' ) 570 571 if entry.schedule == pysvn.wc_schedule.normal: 572 print( "Schedule: normal" ) 573 elif entry.schedule == pysvn.wc_schedule.add: 574 print( "Schedule: add" ) 575 elif entry.schedule == pysvn.wc_schedule.delete: 576 print( "Schedule: delete" ) 577 elif entry.schedule == pysvn.wc_schedule.replace: 578 print( "Schedule: replace" ) 579 if entry.is_copied: 580 if entry.copyfrom_url: 581 print( 'Copied From URL: %s' % entry.copyfrom_url ) 582 if entry.copyfrom_rev.number: 583 print( 'Copied From Rev: %s' % entry.copyfrom_rev.number ) 584 if entry.commit_author: 585 print( 'Last Changed Author: %s' % entry.commit_author ) 586 if entry.commit_revision.number: 587 print( 'Last Changed Rev: %s' % entry.commit_revision.number ) 588 if entry.commit_time: 589 print( 'Last Changed Date: %s' % fmtDateTime( entry.commit_time ) ) 590 if entry.text_time: 591 print( 'Text Last Updated: %s' % fmtDateTime( entry.text_time ) ) 592 if entry.properties_time and self.pysvn_testing == '99.99.99': 593 print( 'Properties Last Updated: %s' % fmtDateTime( entry.properties_time ) ) 594 if entry.checksum: 595 print( 'Checksum: %s' % entry.checksum ) 596 597 def cmd_info2( self, args ): 598 recurse = args.getBooleanOption( '--recursive', True ) 599 revision_url = args.getOptionalRevision( '--revision', 'head' ) 600 revision_path = args.getOptionalRevision( '--revision', 'unspecified' ) 601 602 positional_args = args.getPositionalArgs( 0, 1 ) 603 if len(positional_args) == 0: 604 positional_args.append( '.' ) 605 606 path = positional_args[0] 607 608 if self.client.is_url( path ): 609 revision = revision_url 610 else: 611 revision = revision_path 612 613 all_entries = self.client.info2( path, revision=revision, recurse=recurse ) 614 615 for path, info in all_entries: 616 print('') 617 print( 'Path: %s' % path ) 618 619 if info.URL: 620 print( 'Url: %s' % info.URL ) 621 if info.rev: 622 print( 'Revision: %s' % info.rev.number ) 623 if info.repos_root_URL and self.pysvn_testing >= '01.03.00': 624 print( 'Repository root_URL: %s' % info.repos_root_URL ) 625 if info.repos_UUID: 626 print( 'Repository UUID: %s' % info.repos_UUID ) 627 if info.last_changed_author: 628 print( 'Last changed author: %s' % info.last_changed_author ) 629 if info.last_changed_date: 630 print( 'Last Changed Date: %s' % fmtDateTime( info.last_changed_date ) ) 631 if info.last_changed_rev.kind == pysvn.opt_revision_kind.number: 632 print( 'Last changed revision: %s' % info.last_changed_rev.number ) 633 if info.kind == pysvn.node_kind.file: 634 print( 'Node kind: file' ) 635 elif info.kind == pysvn.node_kind.dir: 636 print( 'Node kind: directory' ) 637 elif info.kind == pysvn.node_kind.none: 638 print( 'Node kind: none' ) 639 else: 640 print( 'Node kind: unknown' ) 641 if info.lock: 642 print( 'Lock Owner: %s' % info.lock.owner ) 643 print( 'Lock Creation Date: %s' % fmtDateTime( info.lock.creation_date ) ) 644 if info.lock.expiration_date is not None: 645 print( 'Lock Expiration Date: %s' % fmtDateTime( info.lock.expiration_date ) ) 646 print( 'Lock Token: %s' % info.lock.token ) 647 print( 'Lock Comment:' ) 648 if info.lock.comment not in ['', None]: 649 print( info.lock.comment ) 650 if info.wc_info: 651 wc_info = info.wc_info 652 if wc_info.schedule == pysvn.wc_schedule.normal: 653 print( "Schedule: normal" ) 654 elif wc_info.schedule == pysvn.wc_schedule.add: 655 print( "Schedule: add" ) 656 elif wc_info.schedule == pysvn.wc_schedule.delete: 657 print( "Schedule: delete" ) 658 elif wc_info.schedule == pysvn.wc_schedule.replace: 659 print( "Schedule: replace" ) 660 if wc_info.copyfrom_url: 661 print( 'Copied From URL: %s' % wc_info.copyfrom_url ) 662 print( 'Copied From Rev: %s' % wc_info.copyfrom_rev.number ) 663 if wc_info.text_time: 664 print( 'Text Last Updated: %s' % fmtDateTime( wc_info.text_time ) ) 665 if wc_info.prop_time and self.pysvn_testing == '99.99.99': 666 print( 'Properties Last Updated: %s' % fmtDateTime( wc_info.prop_time ) ) 667 if wc_info.checksum: 668 print( 'Checksum: %s' % wc_info.checksum ) 669 670 def cmd_import( self, args ): 671 msg = args.getOptionalValue( '--message', '' ) 672 recurse = args.getBooleanOption( '--non-recursive', False ) 673 positional_args = args.getPositionalArgs( 2, 2 ) 674 self.client.import_( positional_args[0], positional_args[1], msg, recurse=recurse ) 675 676 def cmd_lock( self, args ): 677 msg = args.getOptionalValue( '--message', '' ) 678 force = args.getBooleanOption( '--force', True ) 679 positional_args = args.getPositionalArgs( 1, 1 ) 680 self.client.lock( positional_args[0], msg, force ); 681 682 def cmd_log( self, args ): 683 start_revision, end_revision = args.getOptionalRevisionPair( '--revision', 'head', '0' ) 684 limit = args.getOptionalValue( '--limit', 0 ) 685 verbose = args.getBooleanOption( '--verbose', True ) 686 positional_args = args.getPositionalArgs( 1, 1 ) 687 all_logs = self.client.log( positional_args[0], 688 revision_start=start_revision, 689 revision_end=end_revision, 690 discover_changed_paths=verbose, 691 limit=limit ) 692 693 for log in all_logs: 694 print( '-'*60 ) 695 print( 'rev %d: %s | %s | %d lines' % 696 (log.revision.number 697 ,log.author 698 ,fmtDateTime( log.date ) 699 ,len( log.message.split('\n') )) ) 700 701 if len( log.changed_paths ) > 0: 702 print( 'Changed paths:' ) 703 for change_info in log.changed_paths: 704 if change_info.copyfrom_path is None: 705 print( ' %s %s' % (change_info.action, change_info.path) ) 706 else: 707 print( ' %s %s (from %s:%d)' % 708 (change_info.action 709 ,change_info.path 710 ,change_info.copyfrom_path 711 ,change_info.copyfrom_revision.number) ) 712 713 print( log.message ) 714 715 print( '-'*60 ) 716 717 def cmd_ls( self, args ): 718 recurse = args.getBooleanOption( '--recursive', True ) 719 revision = args.getOptionalRevision( '--revision', 'head' ) 720 verbose = args.getBooleanOption( '--verbose', True ) 721 positional_args = args.getPositionalArgs( 0 ) 722 if len(positional_args) == 0: 723 positional_args.append( '.' ) 724 725 for arg in positional_args: 726 all_entries = self.client.ls( arg, revision=revision, recurse=recurse ) 727 all_entries.sort( key=self.__sortKeyLsList ) 728 if verbose: 729 for entry in all_entries: 730 args = {} 731 args.update( entry ) 732 args['time_str'] = fmtDateTime( entry.time ) 733 args['created_rev_num'] = entry.created_rev.number 734 if args['size'] is None: 735 args['size'] = '-' 736 else: 737 args['size'] = '%d' % (args['size'],) 738 print( '%(created_rev_num)7d %(last_author)-10s %(size)6s %(time_str)s %(name)s' % args ) 739 740 else: 741 for entry in all_entries: 742 print( '%(name)s' % entry ) 743 744 def __sortKeyLsList( self, entry ): 745 return entry['name'] 746 747 def cmd_list( self, args ): 748 recurse = args.getBooleanOption( '--recursive', True ) 749 revision = args.getOptionalRevision( '--revision', 'head' ) 750 verbose = args.getBooleanOption( '--verbose', True ) 751 fetch_locks = args.getBooleanOption( '--fetch-locks', True ) 752 include_externals = args.getBooleanOption( '--include-externals', True ) 753 search_pattern = args.getOptionalValue( '--search', None ) 754 positional_args = args.getPositionalArgs( 0 ) 755 if len(positional_args) == 0: 756 positional_args.append( '.' ) 757 758 for arg in positional_args: 759 if self.pysvn_testing >= '01.10.00': 760 all_entries = self.client.list( arg, revision=revision, recurse=recurse, fetch_locks=fetch_locks, include_externals=include_externals, patterns=search_pattern ) 761 762 elif self.pysvn_testing >= '01.08.00': 763 all_entries = self.client.list( arg, revision=revision, recurse=recurse, fetch_locks=fetch_locks, include_externals=include_externals ) 764 765 else: 766 all_entries = self.client.list( arg, revision=revision, recurse=recurse, fetch_locks=fetch_locks ) 767 768 if verbose: 769 for entry_tuple in all_entries: 770 entry = entry_tuple[0] 771 lock_info = entry_tuple[1] 772 args = {} 773 args.update( entry ) 774 args['time_str'] = fmtDateTime( entry.time ) 775 args['created_rev_num'] = entry.created_rev.number 776 if args['size'] is None: 777 args['size'] = '-' 778 else: 779 args['size'] = '%d' % (args['size'],) 780 print( '%(created_rev_num)7d %(last_author)-10s %(size)6s %(time_str)s %(path)s' % args ) 781 if lock_info is not None: 782 print( ' Lock owner: %s' % (lock_info.owner,) ) 783 print( ' Lock comment: %s' % (lock_info.comment,) ) 784 if lock_info.creation_date is not None: 785 print( ' Lock created: %s' % (time.strftime( '%Y-%m-%d %H:%M:%S', time.localtime( lock_info.creation_date ) ),) ) 786 if lock_info.expiration_date is not None: 787 print( ' Lock expires: %s' % (time.strftime( '%Y-%m-%d %H:%M:%S', time.localtime( lock_info.expiration_date ) ),) ) 788 if self.pysvn_testing >= '01.08.00' and include_externals and entry_tuple[2] is not None: 789 print( ' External target %s' % (entry_tuple[3],) ) 790 print( ' External URL %s' % (entry_tuple[2],) ) 791 792 else: 793 for entry_tuple in all_entries: 794 print( '%(path)s' % entry_tuple[0] ) 795 796 def cmd_merge( self, args ): 797 recurse = args.getBooleanOption( '--recursive', True ) 798 dry_run = args.getBooleanOption( '--dry-run', False ) 799 notice_ancestry = args.getBooleanOption( '--notice-ancestry', False ) 800 801 # need to figure out which variaty of the merge command this is 802 if args.haveOption( '--revision' ): 803 # its merge -r N:M SOURCE [WCPATH] 804 revision1, revision2 = args.getMandatoryRevisionPair( '--revision' ) 805 positional_args = args.getPositionalArgs( 1, 2 ) 806 if len(positional_args) == 1: 807 positional_args.append( '.' ) 808 path1 = positional_args[0] 809 path2 = positional_args[0] 810 wcpath = positional_args[1] 811 else: 812 # its merge sourceURL1[@N] sourceURL2[@M] [WCPATH] 813 positional_args = args.getPositionalArgs( 2, 3 ) 814 if len(positional_args) == 2: 815 positional_args.append( '.' ) 816 817 path1, rev1 = self.parsePathWithRevision( positional_args[0] ) 818 path2, rev2 = self.parsePathWithRevision( positional_args[1] ) 819 wcpath = positional_args[2] 820 821 self.client.merge( path1, revision1, path2, revision2, wcpath, 822 recurse=recurse, dry_run=dry_run, notice_ancestry=notice_ancestry ) 823 824 def cmd_mkdir( self, args ): 825 if args.haveOption( '--message' ): 826 msg = args.getOptionalValue( '--message', '' ) 827 if msg == '': 828 msg = self.getLogMessage() 829 830 else: 831 msg = '' 832 833 self.client.mkdir( args.getPositionalArgs( 1, 1 )[0], msg ) 834 835 def cmd_move( self, args ): 836 positional_args = args.getPositionalArgs( 2, 2 ) 837 self.client.move( positional_args[0], positional_args[1] ) 838 cmd_mv = cmd_move 839 840 def cmd_patch( self, args ): 841 dry_run = args.getBooleanOption( '--dry-run', True ) 842 reverse = args.getBooleanOption( '--reverse', True ) 843 ignore_whitespace = args.getBooleanOption( '--ignore-whitespace', True ) 844 remove_tempfiles = not args.getBooleanOption( '--no-remove-tempfiles', True ) 845 846 patch_path, wc_dir_path = args.getPositionalArgs( 2, 2 ) 847 abs_patch_path = os.path.abspath( patch_path ) 848 abs_wc_dir_path = os.path.abspath( wc_dir_path ) 849 850 self.client.patch( abs_patch_path, abs_wc_dir_path, 851 dry_run=dry_run, 852 reverse=reverse, 853 ignore_whitespace=ignore_whitespace, 854 remove_tempfiles=remove_tempfiles ) 855 856 def key_props_by_path( self, a ): 857 return a[0] 858 859 def cmd_proplist( self, args ): 860 recurse = args.getBooleanOption( '--recursive', True ) 861 revision = args.getOptionalRevision( '--revision', 'working' ) 862 verbose = args.getBooleanOption( '--verbose', True ) 863 positional_args = args.getPositionalArgs( 0, 0 ) 864 if len(positional_args) == 0: 865 positional_args.append( '.' ) 866 867 for arg in positional_args: 868 869 if self.client.is_url( arg ): 870 revision = args.getOptionalRevision( '--revision', 'head' ) 871 872 all_props = self.client.proplist( arg, revision=revision, recurse=recurse ) 873 all_props.sort( key=self.key_props_by_path ) 874 875 for path, props in all_props: 876 print( "Properties on '%s':" % path ) 877 prop_names = sorted( props.keys() ) 878 for name in prop_names: 879 if verbose: 880 print( ' %s: %s' % (name, props[name]) ) 881 else: 882 print( ' %s' % name ) 883 884 cmd_pl = cmd_proplist 885 886 def cmd_propget( self, args ): 887 recurse = args.getBooleanOption( '--recursive', True ) 888 revision = args.getOptionalRevision( '--revision', 'working' ) 889 if self.pysvn_testing >= '01.08.00' and args.getBooleanOption( '--show-inherited-props' ): 890 get_inherited_props = True 891 else: 892 get_inherited_props = False 893 positional_args = args.getPositionalArgs( 1, 2 ) 894 if len(positional_args) == 1: 895 positional_args.append( '.' ) 896 if self.client.is_url( positional_args[0] ): 897 revision = args.getOptionalRevision( '--revision', 'head' ) 898 899 if get_inherited_props: 900 props, inherited_props = self.client.propget( 901 positional_args[0], positional_args[1], 902 revision=revision, 903 recurse=recurse, 904 get_inherited_props=True ) 905 else: 906 props = self.client.propget( 907 positional_args[0], positional_args[1], 908 revision=revision, 909 recurse=recurse ) 910 inherited_props = {} 911 912 prop_names = sorted( props.keys() ) 913 for name in prop_names: 914 print( '%s: %s' % (name, props[name]) ) 915 916 if len(inherited_props) > 0: 917 print( 'Inherited props' ) 918 prop_names = sorted( inherited_props.keys() ) 919 for name in prop_names: 920 print( '%s: %s' % (name, inherited_props[name]) ) 921 922 cmd_pg = cmd_propget 923 924 def cmd_propset( self, args ): 925 recurse = args.getBooleanOption( '--recursive', True ) 926 revision = args.getOptionalRevision( '--revision', 'working' ) 927 positional_args = args.getPositionalArgs( 2, 3 ) 928 if len(positional_args) == 2: 929 positional_args.append( '.' ) 930 if self.client.is_url( positional_args[0] ): 931 revision = args.getOptionalRevision( '--revision', 'head' ) 932 933 self.client.propset( positional_args[0], positional_args[1], positional_args[2], revision=revision, recurse=recurse ) 934 cmd_ps = cmd_propset 935 936 def cmd_propdel( self, args ): 937 recurse = args.getBooleanOption( '--recursive', True ) 938 revision = args.getOptionalRevision( '--revision', 'working' ) 939 positional_args = args.getPositionalArgs( 1, 2 ) 940 if len(positional_args) == 1: 941 positional_args.append( '.' ) 942 if self.client.is_url( positional_args[0] ): 943 revision = args.getOptionalRevision( '--revision', 'head' ) 944 945 self.client.propdel( positional_args[0], positional_args[1], revision=revision, recurse=recurse ) 946 cmd_pd = cmd_propdel 947 948 def cmd_propset_local( self, args ): 949 skip_checks = args.getBooleanOption( '--skip-checks', True ) 950 changelist = args.getOptionalValue( '--change-list', None ) 951 positional_args = args.getPositionalArgs( 3, 3 ) 952 953 if changelist is not None: 954 changelist = [s.strip() for s in changelist.split(',')] 955 self.client.propset_local( positional_args[0], positional_args[1], positional_args[2], skip_checks=skip_checks, changelist=changelist ) 956 957 else: 958 self.client.propset_local( positional_args[0], positional_args[1], positional_args[2], skip_checks=skip_checks ) 959 960 def cmd_propdel_local( self, args ): 961 changelist = args.getOptionalValue( '--change-list', None ) 962 positional_args = args.getPositionalArgs( 2, 2 ) 963 964 if changelist is not None: 965 changelist = [s.strip() for s in changelist.split(',')] 966 self.client.propdel_local( positional_args[0], positional_args[1], changelist=changelist ) 967 968 else: 969 self.client.propdel_local( positional_args[0], positional_args[1] ) 970 971 def cmd_propset_remote( self, args ): 972 self.next_log_message = args.getOptionalValue( '--message', None ) 973 974 skip_checks = args.getBooleanOption( '--skip-checks', True ) 975 revision = args.getOptionalRevision( '--revision', '0' ) 976 positional_args = args.getPositionalArgs( 3, 3 ) 977 978 if args.haveOption( '--revision' ): 979 self.client.propset_remote( positional_args[0], positional_args[1], positional_args[2], skip_checks=skip_checks, base_revision_for_url=revision ) 980 981 else: 982 self.client.propset_remote( positional_args[0], positional_args[1], positional_args[2], skip_checks=skip_checks ) 983 984 def cmd_propdel_remote( self, args ): 985 self.next_log_message = args.getOptionalValue( '--message', None ) 986 987 revision = args.getOptionalRevision( '--revision', '0' ) 988 positional_args = args.getPositionalArgs( 2, 2 ) 989 990 self.client.propdel_remote( positional_args[0], positional_args[1], base_revision_for_url=revision ) 991 992 def cmd_revproplist( self, args ): 993 revision = args.getOptionalRevision( '--revision', 'head' ) 994 verbose = args.getBooleanOption( '--verbose', False ) 995 positional_args = args.getPositionalArgs( 0, 1 ) 996 if len(positional_args) == 0: 997 positional_args.append( '.' ) 998 999 rev, prop_dict = self.client.revproplist( positional_args[0], revision=revision ) 1000 print( 'Revision: %s' % rev.number ) 1001 prop_keys = prop_dict.keys() 1002 for key in sorted( prop_keys ): 1003 print( '%s: %s' % (key, prop_dict[ key ]) ) 1004 1005 cmd_rpl = cmd_revproplist 1006 1007 def cmd_revpropget( self, args ): 1008 revision = args.getOptionalRevision( '--revision', 'head' ) 1009 positional_args = args.getPositionalArgs( 1, 2 ) 1010 if len(positional_args) == 1: 1011 positional_args.append( '.' ) 1012 1013 rev, value = self.client.revpropget( positional_args[0], positional_args[1], revision=revision ) 1014 print( 'Revision: %s' % rev.number ) 1015 print( '%s: %s' % (positional_args[0], value) ) 1016 1017 cmd_rpg = cmd_revpropget 1018 1019 def cmd_revpropset( self, args ): 1020 force = args.getBooleanOption( '--force', False ) 1021 revision = args.getOptionalRevision( '--revision', 'head' ) 1022 positional_args = args.getPositionalArgs( 2, 3 ) 1023 if len(positional_args) == 2: 1024 positional_args.append( '.' ) 1025 1026 rev = self.client.revpropset( positional_args[0], positional_args[1], positional_args[2], revision=revision, force=force ) 1027 cmd_rps = cmd_revpropset 1028 1029 def cmd_revpropdel( self, args ): 1030 force = args.getBooleanOption( '--force', False ) 1031 revision = args.getOptionalRevision( '--revision', 'head' ) 1032 positional_args = args.getPositionalArgs( 1, 2 ) 1033 if len(positional_args) == 1: 1034 positional_args.append( '.' ) 1035 1036 self.client.revpropdel( positional_args[0], positional_args[1], revision=revision, force=force ) 1037 cmd_rpd = cmd_revpropdel 1038 1039 def cmd_remove( self, args ): 1040 force = args.getBooleanOption( '--force', True ) 1041 positional_args = args.getPositionalArgs( 1, 0 ) 1042 self.client.remove( positional_args, force=force ) 1043 cmd_rm = cmd_remove 1044 1045 def cmd_remove_from_changelists( self, args ): 1046 if not hasattr( self.client, remove_from_changelists ): 1047 print( 'Error: remove_from_changelists is not supported by this version of Subversion' ) 1048 return 1049 1050 path = args.getPositionalArgs( 1, 1 )[0] 1051 self.client.remove_from_changelists( path ) 1052 1053 def cmd_resolved( self, args ): 1054 recurse = args.getBooleanOption( '--recursive', True ) 1055 positional_args = args.getPositionalArgs( 1, 1 ) 1056 self.client.resolved( positional_args[0], recurse=recurse ) 1057 1058 def cmd_revert( self, args ): 1059 recurse = args.getBooleanOption( '--recursive', True ) 1060 positional_args = args.getPositionalArgs( 1, 1 ) 1061 self.client.revert( positional_args[0], recurse=recurse ) 1062 1063 def key_by_path( self, a ): 1064 return a.path 1065 1066 def cmd_status( self, args ): 1067 recurse = args.getBooleanOption( '--non-recursive', False ) 1068 verbose = args.getBooleanOption( '--verbose', True ) 1069 quiet = args.getBooleanOption( '--quiet', True ) 1070 ignore = args.getBooleanOption( '--no-ignore', False ) 1071 update = args.getBooleanOption( '--show-updates', True ) 1072 1073 positional_args = args.getPositionalArgs( 0 ) 1074 if len(positional_args) == 0: 1075 all_entries = self.client.status( '', recurse=recurse, get_all=verbose, ignore=ignore, update=update ) 1076 self._cmd_status_print( all_entries, verbose, update, ignore, quiet ) 1077 else: 1078 for arg in positional_args: 1079 all_entries = self.client.status( arg, recurse=recurse, get_all=verbose, ignore=ignore, update=update ) 1080 self._cmd_status_print( all_entries, verbose, update, ignore, quiet ) 1081 1082 def _cmd_status_print( self, all_entries, detailed, update, ignore, quiet ): 1083 all_entries.sort( key=self.key_by_path ) 1084 for entry in all_entries: 1085 if entry.text_status == pysvn.wc_status_kind.ignored and ignore: 1086 continue 1087 1088 if entry.text_status == pysvn.wc_status_kind.unversioned and quiet: 1089 continue 1090 1091 state = '%s%s%s%s%s' % (wc_status_kind_map[ entry.text_status ], 1092 wc_status_kind_map[ entry.prop_status ], 1093 ' L'[ entry.is_locked ], 1094 ' +'[ entry.is_copied ], 1095 ' S'[ entry.is_switched ]) 1096 1097 if( entry.repos_text_status != pysvn.wc_status_kind.none 1098 or entry.repos_prop_status != pysvn.wc_status_kind.none ): 1099 odd_status = '%s%s' % (wc_status_kind_map[ entry.repos_text_status ], 1100 wc_status_kind_map[ entry.repos_prop_status ]) 1101 else: 1102 odd_status = ' ' 1103 1104 lock_state = ' ' 1105 if entry.entry is not None and hasattr( entry.entry, 'lock_token' ): 1106 if entry.entry.lock_token is not None: 1107 lock_state = 'K' 1108 1109 if hasattr( entry, 'repos_lock' ) and entry.repos_lock is not None: 1110 lock_state = 'O' 1111 1112 if entry.entry is not None and detailed: 1113 print( '%s%s %s %6d %6d %-14s %s' % 1114 (state, 1115 lock_state, 1116 odd_status, 1117 entry.entry.revision.number, 1118 entry.entry.commit_revision.number, 1119 entry.entry.commit_author, 1120 entry.path) ) 1121 1122 elif detailed: 1123 print( '%s%s %s %6s %6s %-14s %s' % 1124 (state, 1125 lock_state, 1126 odd_status, 1127 '', 1128 '', 1129 '', 1130 entry.path) ) 1131 1132 elif update: 1133 print( '%s%s %s %s' % 1134 (state, 1135 lock_state, 1136 odd_status, 1137 entry.path) ) 1138 1139 else: 1140 if( entry.text_status != pysvn.wc_status_kind.normal 1141 or entry.prop_status != pysvn.wc_status_kind.normal 1142 or lock_state.strip() != ''): 1143 print( '%s%s %s' % (state, lock_state, entry.path) ) 1144 1145 cmd_st = cmd_status 1146 cmd_stat = cmd_status 1147 1148 def cmd_status2( self, args ): 1149 recurse = args.getBooleanOption( '--non-recursive', False ) 1150 verbose = args.getBooleanOption( '--verbose', True ) 1151 quiet = args.getBooleanOption( '--quiet', True ) 1152 ignore = args.getBooleanOption( '--no-ignore', False ) 1153 update = args.getBooleanOption( '--show-updates', True ) 1154 1155 positional_args = args.getPositionalArgs( 0 ) 1156 if len(positional_args) == 0: 1157 all_entries = self.client.status2( '', recurse=recurse, get_all=verbose, ignore=ignore, update=update ) 1158 self._cmd_status2_print( all_entries, verbose, update, ignore, quiet ) 1159 else: 1160 for arg in positional_args: 1161 all_entries = self.client.status2( arg, recurse=recurse, get_all=verbose, ignore=ignore, update=update ) 1162 self._cmd_status2_print( all_entries, verbose, update, ignore, quiet ) 1163 1164 def _cmd_status2_print( self, all_entries, detailed, update, ignore, quiet ): 1165 all_entries.sort( key=self.key_by_path ) 1166 for entry in all_entries: 1167 if entry.text_status == pysvn.wc_status_kind.ignored and ignore: 1168 continue 1169 1170 if entry.text_status == pysvn.wc_status_kind.unversioned and quiet: 1171 continue 1172 1173 if entry.text_status == pysvn.wc_status_kind.none and quiet: 1174 continue 1175 1176 if entry.text_status == pysvn.wc_status_kind.none: 1177 text_status = '?' 1178 else: 1179 text_status = wc_status_kind_map[ entry.text_status ] 1180 1181 state = '%s%s%s%s%s' % (text_status, 1182 wc_status_kind_map[ entry.prop_status ], 1183 ' L'[ entry.wc_is_locked ], 1184 ' +'[ entry.is_copied ], 1185 ' S'[ entry.is_switched ]) 1186 1187 if( entry.repos_text_status != pysvn.wc_status_kind.none 1188 or entry.repos_prop_status != pysvn.wc_status_kind.none ): 1189 odd_status = '%s%s' % (wc_status_kind_map[ entry.repos_text_status ], 1190 wc_status_kind_map[ entry.repos_prop_status ]) 1191 else: 1192 odd_status = ' ' 1193 1194 lock_state = ' ' 1195 if entry.lock is not None and entry.lock.token is not None: 1196 lock_state = 'K' 1197 1198 if entry.repos_lock is not None: 1199 lock_state = 'O' 1200 1201 # convert from abs to rel path 1202 cwd = os.getcwd() 1203 if entry.path == cwd: 1204 path = '.' 1205 1206 elif entry.path.startswith( cwd + '/' ): 1207 path = entry.path[len(cwd)+1:] 1208 1209 else: 1210 path = entry.path 1211 1212 if detailed and entry.is_versioned: 1213 print( '%s%s %s %6d %6d %-14s %s' % 1214 (state, 1215 lock_state, 1216 odd_status, 1217 entry.revision.number, 1218 entry.changed_revision.number, 1219 entry.changed_author, 1220 path) ) 1221 1222 elif detailed: 1223 print( '%s%s %s %6s %6s %-14s %s' % 1224 (state, 1225 lock_state, 1226 odd_status, 1227 '', 1228 '', 1229 '', 1230 path) ) 1231 1232 elif update: 1233 print( '%s%s %s %s' % 1234 (state, 1235 lock_state, 1236 odd_status, 1237 path) ) 1238 1239 else: 1240 if( entry.text_status != pysvn.wc_status_kind.normal 1241 or entry.prop_status != pysvn.wc_status_kind.normal 1242 or lock_state.strip() != ''): 1243 print( '%s%s %s' % (state, lock_state, path) ) 1244 1245 def cmd_switch( self, args ): 1246 recurse = args.getBooleanOption( '--non-recursive', False ) 1247 revision = args.getOptionalRevision( '--revision', 'head' ) 1248 positional_args = args.getPositionalArgs( 1, 2 ) 1249 if len(positional_args) == 1: 1250 positional_args.append( '.' ) 1251 self.client.switch( positional_args[0], positional_args[1], 1252 recurse=recurse, revision=revision ) 1253 1254 def cmd_relocate( self, args ): 1255 recurse = args.getBooleanOption( '--non-recursive', False ) 1256 positional_args = args.getPositionalArgs( 2, 3 ) 1257 if len(positional_args) == 2: 1258 positional_args.append( '.' ) 1259 self.client.relocate( positional_args[0], positional_args[1], 1260 positional_args[2], recurse=recurse ) 1261 1262 def cmd_unlock( self, args ): 1263 force = args.getBooleanOption( '--force', False ) 1264 positional_args = args.getPositionalArgs( 1, 1 ) 1265 self.client.unlock( positional_args[0], force ); 1266 1267 def cmd_update( self, args ): 1268 recurse = args.getBooleanOption( '--non-recursive', False ) 1269 positional_args = args.getPositionalArgs( 0 ) 1270 if len(positional_args) == 0: 1271 positional_args.append( '.' ) 1272 1273 rev_list = self.client.update( positional_args[0], recurse=recurse ) 1274 self.printNotifyMessages() 1275 if type(rev_list) == type([]) and len(rev_list) != 1: 1276 print( 'rev_list = %r' % [rev.number for rev in rev_list] ) 1277 1278 if self.revision_update_complete is not None: 1279 print( 'Updated to revision %s' % self.revision_update_complete.number ) 1280 else: 1281 print( 'Updated to unknown revision - update failed?' ) 1282 1283 cmd_up = cmd_update 1284 1285 def cmd_vacuum( self, args ): 1286 remove_unversioned_items = args.getBooleanOption( '--remove-unversioned-items', True ) 1287 remove_ignored_items = args.getBooleanOption( '--remove-ignored-items', True ) 1288 fix_recorded_timestamps = args.getBooleanOption( '--fix-recorded-timestamps', True ) 1289 vacuum_pristines = args.getBooleanOption( '--vacuum-pristines', True ) 1290 include_externals = args.getBooleanOption( '--include-externals', True ) 1291 1292 positional_args = args.getPositionalArgs( 0, 1 ) 1293 if len(positional_args) == 0: 1294 positional_args.append( '.' ) 1295 1296 self.client.vacuum( 1297 positional_args[0], 1298 remove_unversioned_items=remove_unversioned_items, 1299 remove_ignored_items=remove_ignored_items, 1300 fix_recorded_timestamps=fix_recorded_timestamps, 1301 vacuum_pristines=vacuum_pristines, 1302 include_externals=include_externals 1303 ) 1304 1305 def cmd_help( self, args ): 1306 print( 'Version: pysvn %d.%d.%d-%d' % pysvn.version,'svn %d.%d.%d-%s' % pysvn.svn_version ) 1307 valid_cmd_names = [name for name in SvnCommand.__dict__.keys() if name.find('cmd_') == 0] 1308 valid_cmd_names.sort() 1309 print( 'Available subcommands:' ) 1310 index = 0 1311 line = '' 1312 for name in valid_cmd_names: 1313 line = line + (' %-12s' % name[4:]) 1314 if index % 4 == 3: 1315 print( line ) 1316 line = '' 1317 index += 1 1318 1319# key is long option name, value is 1 if need next arg as value 1320long_opt_info = { 1321 # svn_cmd.py control 1322 '--pause': 0, 1323 '--pysvn-testing': 1, # modify behaviour to assist testing pysvn 1324 '--debug': 0, # do debug stuff 1325 1326 # command options 1327 '--auto-props': 0, # enable automatic properties 1328 '--change-list': 1, # changelist 1329 '--config-dir': 1, # read user configuration files from directory ARG 1330 '--diff-cmd': 1, # use ARG as diff command 1331 '--diff3-cmd': 1, # use ARG as merge command 1332 '--dry-run': 0, # try operation but make no changes 1333 '--editor-cmd': 1, # use ARG as external editor 1334 '--encoding': 1, # treat value as being in charset encoding ARG 1335 '--extensions': 1, # pass ARG as bundled options to GNU diff 1336 '--fetch-locks': 0, # as list to fetch lock info 1337 '--file': 1, # read data from file ARG 1338 '--fix-recorded-timestamps': 0, 1339 '--force': 0, # force operation to run 1340 '--force-log': 0, # force validity of log message source 1341 '--include-externals': 0, 1342 '--include-externals': 0, # include external information in list() output 1343 '--incremental': 0, # give output suitable for concatenation 1344 '--limit': 1, # number of logs to fetch 1345 '--message': 1, # specify commit message ARG 1346 '--native-eol': 1, # native eol ARG 1347 '--new': 1, # use ARG as the newer target 1348 '--no-auth-cache': 0, # do not cache authentication tokens 1349 '--no-auto-props': 0, # disable automatic properties 1350 '--no-diff-deleted': 0, # do not print differences for deleted files 1351 '--no-ignore': 0, # disregard default and svn:ignore property ignores 1352 '--no-remove-tempfiles': 0, # do not remove temp files 1353 '--non-interactive': 0, # do no interactive prompting 1354 '--non-recursive': 0, # operate on single directory only 1355 '--notice-ancestry': 0, # notice ancestry when calculating differences 1356 '--old': 1, # use ARG as the older target 1357 '--password': 1, # specify a password ARG 1358 '--quiet': 0, # print as little as possible 1359 '--recursive': 0, # descend recursively 1360 '--relocate': 0, # relocate via URL-rewriting 1361 '--remove-ignored-items': 0, 1362 '--remove-unversioned-items': 0, 1363 '--revision': 1, # revision X or X:Y range. X or Y can be one of: 1364 '--revprop': 0, # operate on a revision property (use with -r) 1365 '--search': 2, # search for pattern 1366 '--show-inherited-props': 0, # show inherited props 1367 '--show-updates': 0, # display update information 1368 '--skip-checks': 0, # skip-checks 1369 '--strict': 0, # use strict semantics 1370 '--targets': 1, # pass contents of file ARG as additional args 1371 '--username': 1, # specify a username ARG 1372 '--vacuum-pristines': 0, 1373 '--verbose': 0, # print extra information 1374 '--version': 0, # print client version info 1375 '--xml': 0, # output in xml 1376} 1377 1378# map short name to long 1379short_opt_info = { 1380 '-F': '--file', 1381 '-N': '--non-recursive', 1382 '-R': '--recursive', 1383 '-m': '--message', 1384 '-q': '--quiet', 1385 '-r': '--revision', 1386 '-u': '--show-updates', 1387 '-v': '--verbose', 1388 '-x': '--extensions', 1389} 1390 1391 1392# 1393# Usage: 1394# Construct with a commnad list 1395# call getCommandName() 1396# call getBooleanOption() and getOptionalValue() as needed 1397# finally call getPositionalArgs() 1398# 1399# 1400class SvnArguments: 1401 def __init__( self, all_args ): 1402 self.positional_args = [] 1403 self.named_options = {} 1404 self.used_named_options = {} 1405 1406 need_next_arg = 0 1407 name = '' 1408 1409 for arg in all_args: 1410 if need_next_arg > 1: 1411 self.named_options.setdefault( name, [] ).append( arg ) 1412 need_next_arg = 0 1413 1414 elif need_next_arg: 1415 self.named_options[ name ] = arg 1416 need_next_arg = 0 1417 1418 elif self._isOption( arg ): 1419 name, need_next_arg = self._optionInfo( arg ) 1420 if not need_next_arg: 1421 self.named_options[ name ] = None 1422 1423 else: 1424 expanded_arg = glob.glob( arg ) 1425 if len(expanded_arg) > 0: 1426 self.positional_args.extend( expanded_arg ) 1427 else: 1428 self.positional_args.append( arg ) 1429 if need_next_arg: 1430 raise CommandError( 'Missing arg to option %s' % name ) 1431 1432 def _isOption( self, arg ): 1433 return arg[0] == '-' 1434 1435 def _optionInfo( self, opt ): 1436 # return long_name, arg_needed 1437 long_opt = short_opt_info.get( opt, opt ) 1438 if long_opt in long_opt_info: 1439 return long_opt, long_opt_info[ long_opt ] 1440 raise CommandError( 'unknown option %s' % opt ) 1441 1442 def _checkOptionsUsed( self ): 1443 # check all options have been used 1444 for opt_name in self.named_options.keys(): 1445 if opt_name not in self.used_named_options: 1446 raise CommandError( 'unused option %s' % opt_name ) 1447 1448 def parsePathWithRevision( self, path_rev, default_rev ): 1449 if '@' in path_rev: 1450 path = path_rev[:path_rev.find('@')] 1451 rev = self._parseRevisionArg( path_rev[path_rev.find('@')+1:] ) 1452 else: 1453 path = path_rev 1454 rev = self._parseRevisionArg( default_rev ) 1455 return path, rev 1456 1457 def _parseRevisionArg( self, rev_string ): 1458 if rev_string.lower() == 'base': 1459 return pysvn.Revision( pysvn.opt_revision_kind.base ) 1460 if rev_string.lower() == 'head': 1461 return pysvn.Revision( pysvn.opt_revision_kind.head ) 1462 if rev_string.lower() == 'working': 1463 return pysvn.Revision( pysvn.opt_revision_kind.working ) 1464 if rev_string.lower() == 'committed': 1465 return pysvn.Revision( pysvn.opt_revision_kind.committed ) 1466 if rev_string.lower() == 'prev': 1467 return pysvn.Revision( pysvn.opt_revision_kind.prev ) 1468 if rev_string.lower() == 'unspecified': 1469 return pysvn.Revision( pysvn.opt_revision_kind.unspecified ) 1470 if rev_string[0] == '{' and rev_string[-1] == '}': 1471 try: 1472 date = parse_datetime.parse_time( rev_string[1:-2] ) 1473 return pysvn.Revision( pysvn.opt_revision_kind.date, date ) 1474 except parse_datetime.DateTimeSyntaxError as e: 1475 raise CommandError( e.reason() ) 1476 # either a rev number or a date 1477 try: 1478 return pysvn.Revision( pysvn.opt_revision_kind.number, int(rev_string) ) 1479 except ValueError: 1480 pass 1481 raise CommandError( 'Cannot parse %s as a revision value' % rev_string ) 1482 1483 1484 def _splitRevisionString( self, rev_string ): 1485 # split the string at the first : that is not inside a {} pair 1486 if rev_string[0] == '{': 1487 # the : may be after the closing } 1488 close_paren_index = rev_string.find( '}' ) 1489 if close_paren_index == -1: 1490 # error leave to others to report 1491 return [rev_string] 1492 1493 if close_paren_index == len(rev_string ): 1494 # its just one revision 1495 return [rev_string] 1496 1497 if rev_string[close_paren_index+1] == ':': 1498 return [rev_string[:close_paren_index+1], rev_string[close_paren_index+2:]] 1499 1500 # another error case 1501 return [rev_string] 1502 else: 1503 return rev_string.split(':',1) 1504 1505 def getCommandName( self, default_command ): 1506 if len(self.positional_args) > 0: 1507 return self.positional_args.pop( 0 ) 1508 else: 1509 return default_command 1510 1511 def haveOption( self, opt_name ): 1512 return opt_name in self.named_options 1513 1514 def getBooleanOption( self, opt_name, present_value=True ): 1515 if opt_name in self.named_options: 1516 self.used_named_options[ opt_name ] = None 1517 return present_value 1518 else: 1519 return not present_value 1520 1521 def getOptionalValue( self, opt_name, default ): 1522 if opt_name in self.named_options: 1523 self.used_named_options[ opt_name ] = None 1524 return self.named_options[ opt_name ] 1525 else: 1526 return default 1527 1528 def getOptionalRevision( self, opt_name, start_default ): 1529 if opt_name in self.named_options: 1530 self.used_named_options[ opt_name ] = None 1531 rev_string = self.named_options[ opt_name ] 1532 1533 return self._parseRevisionArg( rev_string ) 1534 else: 1535 return self._parseRevisionArg( start_default ) 1536 1537 def getMandatoryRevisionPair( self, opt_name ): 1538 # parse a M:N or M as revision pair 1539 if opt_name not in self.named_options: 1540 raise CommandError( 'mandatory %s required' % opt_name ) 1541 1542 self.used_named_options[ opt_name ] = None 1543 1544 rev_strings = self._splitRevisionString( self.named_options[ opt_name ] ) 1545 if len(rev_strings) == 1: 1546 raise CommandError( 'mandatory %s requires a pair of revisions' % opt_name ) 1547 1548 return [self._parseRevisionArg( rev_strings[0] ), 1549 self._parseRevisionArg( rev_strings[1] )] 1550 1551 def getOptionalRevisionPair( self, opt_name, start_default, end_default=None ): 1552 # parse a M:N or M as revision pair 1553 if opt_name in self.named_options: 1554 self.used_named_options[ opt_name ] = None 1555 rev_strings = self._splitRevisionString( self.named_options[ opt_name ] ) 1556 if len(rev_strings) == 1: 1557 if end_default is None: 1558 # M means M:M 1559 rev_strings.append( rev_strings[0] ) 1560 else: 1561 # M means M:end_default 1562 rev_strings.append( end_default ) 1563 1564 return [self._parseRevisionArg( rev_strings[0] ), 1565 self._parseRevisionArg( rev_strings[1] )] 1566 else: 1567 return (self._parseRevisionArg( start_default ), 1568 self._parseRevisionArg( end_default )) 1569 1570 def getPositionalArgs( self, min_args, max_args=0 ): 1571 # check min and max then return the list 1572 if len(self.positional_args) < min_args: 1573 raise CommandError( 'too few arguments - need at least %d' % min_args ) 1574 if max_args != 0 and len(self.positional_args) > max_args: 1575 raise CommandError( 'too many arguments - need no more then %d' % max_args ) 1576 1577 # as this is the last call on the args object we check the option where all used 1578 self._checkOptionsUsed() 1579 1580 return self.positional_args 1581 1582if __name__ == '__main__': 1583 sys.exit( main( sys.argv ) ) 1584