| 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 |