OLD | NEW |
1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """SCM-specific utility classes.""" | 5 """SCM-specific utility classes.""" |
6 | 6 |
7 import cStringIO | 7 import cStringIO |
8 import glob | 8 import glob |
9 import os | 9 import os |
10 import re | 10 import re |
(...skipping 288 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 return (False, current_version) | 299 return (False, current_version) |
300 elif ver > min_ver: | 300 elif ver > min_ver: |
301 return (True, current_version) | 301 return (True, current_version) |
302 return (True, current_version) | 302 return (True, current_version) |
303 | 303 |
304 | 304 |
305 class SVN(object): | 305 class SVN(object): |
306 current_version = None | 306 current_version = None |
307 | 307 |
308 @staticmethod | 308 @staticmethod |
309 def Capture(args, in_directory=None, print_error=True): | 309 def Capture(args, **kwargs): |
310 """Runs svn, capturing output sent to stdout as a string. | 310 """Always redirect stderr. |
311 | 311 |
312 Args: | 312 Throws an exception if non-0 is returned.""" |
313 args: A sequence of command line parameters to be passed to svn. | 313 return gclient_utils.CheckCall(['svn'] + args, print_error=False, |
314 in_directory: The directory where svn is to be run. | 314 **kwargs)[0] |
315 | |
316 Returns: | |
317 The output sent to stdout as a string. | |
318 """ | |
319 stderr = None | |
320 if not print_error: | |
321 stderr = subprocess.PIPE | |
322 return gclient_utils.Popen(['svn'] + args, cwd=in_directory, | |
323 stdout=subprocess.PIPE, stderr=stderr).communicate()[0] | |
324 | 315 |
325 @staticmethod | 316 @staticmethod |
326 def RunAndGetFileList(verbose, args, cwd, file_list, stdout=None): | 317 def RunAndGetFileList(verbose, args, cwd, file_list, stdout=None): |
327 """Runs svn checkout, update, or status, output to stdout. | 318 """Runs svn checkout, update, or status, output to stdout. |
328 | 319 |
329 The first item in args must be either "checkout", "update", or "status". | 320 The first item in args must be either "checkout", "update", or "status". |
330 | 321 |
331 svn's stdout is parsed to collect a list of files checked out or updated. | 322 svn's stdout is parsed to collect a list of files checked out or updated. |
332 These files are appended to file_list. svn's stdout is also printed to | 323 These files are appended to file_list. svn's stdout is also printed to |
333 sys.stdout as in Run. | 324 sys.stdout as in Run. |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
418 if len(file_list) == previous_list_len and not IsKnownFailure(): | 409 if len(file_list) == previous_list_len and not IsKnownFailure(): |
419 # No known svn error was found and no progress, bail out. | 410 # No known svn error was found and no progress, bail out. |
420 raise | 411 raise |
421 print "Sleeping %.1f seconds and retrying...." % backoff_time | 412 print "Sleeping %.1f seconds and retrying...." % backoff_time |
422 time.sleep(backoff_time) | 413 time.sleep(backoff_time) |
423 backoff_time *= 1.3 | 414 backoff_time *= 1.3 |
424 continue | 415 continue |
425 break | 416 break |
426 | 417 |
427 @staticmethod | 418 @staticmethod |
428 def CaptureInfo(relpath, in_directory=None, print_error=True): | 419 def CaptureInfo(cwd): |
429 """Returns a dictionary from the svn info output for the given file. | 420 """Returns a dictionary from the svn info output for the given file. |
430 | 421 |
431 Args: | 422 Throws an exception if svn info fails.""" |
432 relpath: The directory where the working copy resides relative to | 423 output = SVN.Capture(['info', '--xml', cwd]) |
433 the directory given by in_directory. | |
434 in_directory: The directory where svn is to be run. | |
435 """ | |
436 output = SVN.Capture(["info", "--xml", relpath], in_directory, print_error) | |
437 dom = gclient_utils.ParseXML(output) | 424 dom = gclient_utils.ParseXML(output) |
438 result = {} | 425 result = {} |
439 if dom: | 426 if dom: |
440 GetNamedNodeText = gclient_utils.GetNamedNodeText | 427 GetNamedNodeText = gclient_utils.GetNamedNodeText |
441 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText | 428 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText |
442 def C(item, f): | 429 def C(item, f): |
443 if item is not None: | 430 if item is not None: |
444 return f(item) | 431 return f(item) |
445 # /info/entry/ | 432 # /info/entry/ |
446 # url | 433 # url |
(...skipping 14 matching lines...) Expand all Loading... |
461 # Differs across versions. | 448 # Differs across versions. |
462 if result['Node Kind'] == 'dir': | 449 if result['Node Kind'] == 'dir': |
463 result['Node Kind'] = 'directory' | 450 result['Node Kind'] = 'directory' |
464 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) | 451 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) |
465 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) | 452 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) |
466 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) | 453 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) |
467 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) | 454 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) |
468 return result | 455 return result |
469 | 456 |
470 @staticmethod | 457 @staticmethod |
471 def CaptureHeadRevision(url): | 458 def CaptureRevision(cwd): |
472 """Get the head revision of a SVN repository. | |
473 | |
474 Returns: | |
475 Int head revision | |
476 """ | |
477 info = SVN.Capture(["info", "--xml", url], os.getcwd()) | |
478 dom = xml.dom.minidom.parseString(info) | |
479 return dom.getElementsByTagName('entry')[0].getAttribute('revision') | |
480 | |
481 @staticmethod | |
482 def CaptureBaseRevision(cwd): | |
483 """Get the base revision of a SVN repository. | 459 """Get the base revision of a SVN repository. |
484 | 460 |
485 Returns: | 461 Returns: |
486 Int base revision | 462 Int base revision |
487 """ | 463 """ |
488 info = SVN.Capture(["info", "--xml"], cwd) | 464 info = SVN.Capture(['info', '--xml'], cwd=cwd) |
489 dom = xml.dom.minidom.parseString(info) | 465 dom = xml.dom.minidom.parseString(info) |
490 return dom.getElementsByTagName('entry')[0].getAttribute('revision') | 466 return dom.getElementsByTagName('entry')[0].getAttribute('revision') |
491 | 467 |
492 @staticmethod | 468 @staticmethod |
493 def CaptureStatus(files): | 469 def CaptureStatus(files): |
494 """Returns the svn 1.5 svn status emulated output. | 470 """Returns the svn 1.5 svn status emulated output. |
495 | 471 |
496 @files can be a string (one file) or a list of files. | 472 @files can be a string (one file) or a list of files. |
497 | 473 |
498 Returns an array of (status, file) tuples.""" | 474 Returns an array of (status, file) tuples.""" |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
532 file_path = entry.getAttribute('path') | 508 file_path = entry.getAttribute('path') |
533 wc_status = entry.getElementsByTagName('wc-status') | 509 wc_status = entry.getElementsByTagName('wc-status') |
534 assert len(wc_status) == 1 | 510 assert len(wc_status) == 1 |
535 # Emulate svn 1.5 status ouput... | 511 # Emulate svn 1.5 status ouput... |
536 statuses = [' '] * 7 | 512 statuses = [' '] * 7 |
537 # Col 0 | 513 # Col 0 |
538 xml_item_status = wc_status[0].getAttribute('item') | 514 xml_item_status = wc_status[0].getAttribute('item') |
539 if xml_item_status in status_letter: | 515 if xml_item_status in status_letter: |
540 statuses[0] = status_letter[xml_item_status] | 516 statuses[0] = status_letter[xml_item_status] |
541 else: | 517 else: |
542 raise Exception('Unknown item status "%s"; please implement me!' % | 518 raise gclient_utils.Error( |
543 xml_item_status) | 519 'Unknown item status "%s"; please implement me!' % |
| 520 xml_item_status) |
544 # Col 1 | 521 # Col 1 |
545 xml_props_status = wc_status[0].getAttribute('props') | 522 xml_props_status = wc_status[0].getAttribute('props') |
546 if xml_props_status == 'modified': | 523 if xml_props_status == 'modified': |
547 statuses[1] = 'M' | 524 statuses[1] = 'M' |
548 elif xml_props_status == 'conflicted': | 525 elif xml_props_status == 'conflicted': |
549 statuses[1] = 'C' | 526 statuses[1] = 'C' |
550 elif (not xml_props_status or xml_props_status == 'none' or | 527 elif (not xml_props_status or xml_props_status == 'none' or |
551 xml_props_status == 'normal'): | 528 xml_props_status == 'normal'): |
552 pass | 529 pass |
553 else: | 530 else: |
554 raise Exception('Unknown props status "%s"; please implement me!' % | 531 raise gclient_utils.Error( |
555 xml_props_status) | 532 'Unknown props status "%s"; please implement me!' % |
| 533 xml_props_status) |
556 # Col 2 | 534 # Col 2 |
557 if wc_status[0].getAttribute('wc-locked') == 'true': | 535 if wc_status[0].getAttribute('wc-locked') == 'true': |
558 statuses[2] = 'L' | 536 statuses[2] = 'L' |
559 # Col 3 | 537 # Col 3 |
560 if wc_status[0].getAttribute('copied') == 'true': | 538 if wc_status[0].getAttribute('copied') == 'true': |
561 statuses[3] = '+' | 539 statuses[3] = '+' |
562 # Col 4 | 540 # Col 4 |
563 if wc_status[0].getAttribute('switched') == 'true': | 541 if wc_status[0].getAttribute('switched') == 'true': |
564 statuses[4] = 'S' | 542 statuses[4] = 'S' |
565 # TODO(maruel): Col 5 and 6 | 543 # TODO(maruel): Col 5 and 6 |
(...skipping 19 matching lines...) Expand all Loading... |
585 | 563 |
586 Args: | 564 Args: |
587 filename: The file to check | 565 filename: The file to check |
588 property_name: The name of the SVN property, e.g. "svn:mime-type" | 566 property_name: The name of the SVN property, e.g. "svn:mime-type" |
589 | 567 |
590 Returns: | 568 Returns: |
591 The value of the property, which will be the empty string if the property | 569 The value of the property, which will be the empty string if the property |
592 is not set on the file. If the file is not under version control, the | 570 is not set on the file. If the file is not under version control, the |
593 empty string is also returned. | 571 empty string is also returned. |
594 """ | 572 """ |
595 output = SVN.Capture(["propget", property_name, filename]) | 573 try: |
596 if (output.startswith("svn: ") and | 574 return SVN.Capture(['propget', property_name, filename]) |
597 output.endswith("is not under version control")): | 575 except gclient_utils.Error: |
598 return "" | 576 return '' |
599 else: | |
600 return output | |
601 | 577 |
602 @staticmethod | 578 @staticmethod |
603 def DiffItem(filename, full_move=False, revision=None): | 579 def DiffItem(filename, full_move=False, revision=None): |
604 """Diffs a single file. | 580 """Diffs a single file. |
605 | 581 |
606 Should be simple, eh? No it isn't. | 582 Should be simple, eh? No it isn't. |
607 Be sure to be in the appropriate directory before calling to have the | 583 Be sure to be in the appropriate directory before calling to have the |
608 expected relative path. | 584 expected relative path. |
609 full_move means that move or copy operations should completely recreate the | 585 full_move means that move or copy operations should completely recreate the |
610 files, usually in the prospect to apply the patch for a try job.""" | 586 files, usually in the prospect to apply the patch for a try job.""" |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
655 data.write(GenFakeDiff(os.path.join(dirpath, f))) | 631 data.write(GenFakeDiff(os.path.join(dirpath, f))) |
656 if data: | 632 if data: |
657 tmp = data.getvalue() | 633 tmp = data.getvalue() |
658 data.close() | 634 data.close() |
659 data = tmp | 635 data = tmp |
660 else: | 636 else: |
661 data = GenFakeDiff(filename) | 637 data = GenFakeDiff(filename) |
662 else: | 638 else: |
663 if info.get("Node Kind") != "directory": | 639 if info.get("Node Kind") != "directory": |
664 # svn diff on a mv/cp'd file outputs nothing if there was no change. | 640 # svn diff on a mv/cp'd file outputs nothing if there was no change. |
665 data = SVN.Capture(command, None) | 641 data = SVN.Capture(command) |
666 if not data: | 642 if not data: |
667 # We put in an empty Index entry so upload.py knows about them. | 643 # We put in an empty Index entry so upload.py knows about them. |
668 data = "Index: %s\n" % filename.replace(os.sep, '/') | 644 data = "Index: %s\n" % filename.replace(os.sep, '/') |
669 # Otherwise silently ignore directories. | 645 # Otherwise silently ignore directories. |
670 else: | 646 else: |
671 if info.get("Node Kind") != "directory": | 647 if info.get("Node Kind") != "directory": |
672 # Normal simple case. | 648 # Normal simple case. |
673 data = SVN.Capture(command, None) | 649 data = SVN.Capture(command) |
674 # Otherwise silently ignore directories. | 650 # Otherwise silently ignore directories. |
675 return data | 651 return data |
676 | 652 |
677 @staticmethod | 653 @staticmethod |
678 def GenerateDiff(filenames, root=None, full_move=False, revision=None): | 654 def GenerateDiff(filenames, root=None, full_move=False, revision=None): |
679 """Returns a string containing the diff for the given file list. | 655 """Returns a string containing the diff for the given file list. |
680 | 656 |
681 The files in the list should either be absolute paths or relative to the | 657 The files in the list should either be absolute paths or relative to the |
682 given root. If no root directory is provided, the repository root will be | 658 given root. If no root directory is provided, the repository root will be |
683 used. | 659 used. |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
754 result = buf.getvalue() | 730 result = buf.getvalue() |
755 buf.close() | 731 buf.close() |
756 return result | 732 return result |
757 finally: | 733 finally: |
758 os.chdir(previous_cwd) | 734 os.chdir(previous_cwd) |
759 shutil.rmtree(bogus_dir) | 735 shutil.rmtree(bogus_dir) |
760 | 736 |
761 @staticmethod | 737 @staticmethod |
762 def GetEmail(repo_root): | 738 def GetEmail(repo_root): |
763 """Retrieves the svn account which we assume is an email address.""" | 739 """Retrieves the svn account which we assume is an email address.""" |
764 infos = SVN.CaptureInfo(repo_root) | 740 try: |
765 uuid = infos.get('UUID') | 741 infos = SVN.CaptureInfo(repo_root) |
766 root = infos.get('Repository Root') | 742 except gclient_utils.Error: |
767 if not root: | |
768 return None | 743 return None |
769 | 744 |
770 # Should check for uuid but it is incorrectly saved for https creds. | 745 # Should check for uuid but it is incorrectly saved for https creds. |
| 746 root = infos['Repository Root'] |
771 realm = root.rsplit('/', 1)[0] | 747 realm = root.rsplit('/', 1)[0] |
| 748 uuid = infos['UUID'] |
772 if root.startswith('https') or not uuid: | 749 if root.startswith('https') or not uuid: |
773 regexp = re.compile(r'<%s:\d+>.*' % realm) | 750 regexp = re.compile(r'<%s:\d+>.*' % realm) |
774 else: | 751 else: |
775 regexp = re.compile(r'<%s:\d+> %s' % (realm, uuid)) | 752 regexp = re.compile(r'<%s:\d+> %s' % (realm, uuid)) |
776 if regexp is None: | 753 if regexp is None: |
777 return None | 754 return None |
778 if sys.platform.startswith('win'): | 755 if sys.platform.startswith('win'): |
779 if not 'APPDATA' in os.environ: | 756 if not 'APPDATA' in os.environ: |
780 return None | 757 return None |
781 auth_dir = os.path.join(os.environ['APPDATA'], 'Subversion', 'auth', | 758 auth_dir = os.path.join(os.environ['APPDATA'], 'Subversion', 'auth', |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
813 values[key] = value | 790 values[key] = value |
814 return values | 791 return values |
815 | 792 |
816 @staticmethod | 793 @staticmethod |
817 def GetCheckoutRoot(directory): | 794 def GetCheckoutRoot(directory): |
818 """Returns the top level directory of the current repository. | 795 """Returns the top level directory of the current repository. |
819 | 796 |
820 The directory is returned as an absolute path. | 797 The directory is returned as an absolute path. |
821 """ | 798 """ |
822 directory = os.path.abspath(directory) | 799 directory = os.path.abspath(directory) |
823 infos = SVN.CaptureInfo(directory, print_error=False) | 800 try: |
824 cur_dir_repo_root = infos.get("Repository Root") | 801 cur_dir_repo_root = SVN.CaptureInfo(directory)['Repository Root'] |
825 if not cur_dir_repo_root: | 802 except gclient_utils.Error: |
826 return None | 803 return None |
827 | |
828 while True: | 804 while True: |
829 parent = os.path.dirname(directory) | 805 parent = os.path.dirname(directory) |
830 if (SVN.CaptureInfo(parent, print_error=False).get( | 806 try: |
831 "Repository Root") != cur_dir_repo_root): | 807 if SVN.CaptureInfo(parent)['Repository Root'] != cur_dir_repo_root: |
| 808 break |
| 809 except gclient_utils.Error: |
832 break | 810 break |
833 directory = parent | 811 directory = parent |
834 return GetCasedPath(directory) | 812 return GetCasedPath(directory) |
835 | 813 |
836 @staticmethod | 814 @staticmethod |
837 def AssertVersion(min_version): | 815 def AssertVersion(min_version): |
838 """Asserts svn's version is at least min_version.""" | 816 """Asserts svn's version is at least min_version.""" |
839 def only_int(val): | 817 def only_int(val): |
840 if val.isdigit(): | 818 if val.isdigit(): |
841 return int(val) | 819 return int(val) |
842 else: | 820 else: |
843 return 0 | 821 return 0 |
844 if not SVN.current_version: | 822 if not SVN.current_version: |
845 SVN.current_version = SVN.Capture(['--version']).split()[2] | 823 SVN.current_version = SVN.Capture(['--version']).split()[2] |
846 current_version_list = map(only_int, SVN.current_version.split('.')) | 824 current_version_list = map(only_int, SVN.current_version.split('.')) |
847 for min_ver in map(int, min_version.split('.')): | 825 for min_ver in map(int, min_version.split('.')): |
848 ver = current_version_list.pop(0) | 826 ver = current_version_list.pop(0) |
849 if ver < min_ver: | 827 if ver < min_ver: |
850 return (False, SVN.current_version) | 828 return (False, SVN.current_version) |
851 elif ver > min_ver: | 829 elif ver > min_ver: |
852 return (True, SVN.current_version) | 830 return (True, SVN.current_version) |
853 return (True, SVN.current_version) | 831 return (True, SVN.current_version) |
OLD | NEW |