| OLD | NEW |
| 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2010 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 logging | 9 import logging |
| 10 import os | 10 import os |
| 11 import re | 11 import re |
| 12 import shutil | 12 import shutil |
| 13 import subprocess | 13 import subprocess |
| 14 import sys | 14 import sys |
| 15 import tempfile | 15 import tempfile |
| 16 import time | 16 import time |
| 17 from xml.etree import ElementTree | 17 import xml.dom.minidom |
| 18 | 18 |
| 19 import gclient_utils | 19 import gclient_utils |
| 20 import subprocess2 | 20 import subprocess2 |
| 21 | 21 |
| 22 | 22 |
| 23 def ValidateEmail(email): | 23 def ValidateEmail(email): |
| 24 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) | 24 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) |
| 25 is not None) | 25 is not None) |
| 26 | 26 |
| 27 | 27 |
| (...skipping 477 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 505 time.sleep(backoff_time) | 505 time.sleep(backoff_time) |
| 506 backoff_time *= 1.3 | 506 backoff_time *= 1.3 |
| 507 continue | 507 continue |
| 508 break | 508 break |
| 509 | 509 |
| 510 @staticmethod | 510 @staticmethod |
| 511 def CaptureInfo(cwd): | 511 def CaptureInfo(cwd): |
| 512 """Returns a dictionary from the svn info output for the given file. | 512 """Returns a dictionary from the svn info output for the given file. |
| 513 | 513 |
| 514 Throws an exception if svn info fails.""" | 514 Throws an exception if svn info fails.""" |
| 515 output = SVN.Capture(['info', '--xml', cwd]) |
| 516 dom = gclient_utils.ParseXML(output) |
| 515 result = {} | 517 result = {} |
| 516 output = SVN.Capture(['info', '--xml', cwd]) | 518 if dom: |
| 517 info = ElementTree.XML(output) | 519 GetNamedNodeText = gclient_utils.GetNamedNodeText |
| 518 if info is None: | 520 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText |
| 519 return result | 521 def C(item, f): |
| 520 entry = info.find('entry') | 522 if item is not None: |
| 521 | 523 return f(item) |
| 522 # Use .text when the item is not optional. | 524 # /info/entry/ |
| 523 result['Path'] = entry.attrib['path'] | 525 # url |
| 524 result['Revision'] = int(entry.attrib['revision']) | 526 # reposityory/(root|uuid) |
| 525 result['Node Kind'] = entry.attrib['kind'] | 527 # wc-info/(schedule|depth) |
| 526 # Differs across versions. | 528 # commit/(author|date) |
| 527 if result['Node Kind'] == 'dir': | 529 # str() the results because they may be returned as Unicode, which |
| 528 result['Node Kind'] = 'directory' | 530 # interferes with the higher layers matching up things in the deps |
| 529 result['URL'] = entry.find('url').text | 531 # dictionary. |
| 530 repository = entry.find('repository') | 532 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) |
| 531 result['Repository Root'] = repository.find('root').text | 533 result['URL'] = C(GetNamedNodeText(dom, 'url'), str) |
| 532 result['UUID'] = repository.find('uuid') | 534 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) |
| 533 wc_info = entry.find('wc-info') | 535 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', |
| 534 result['Schedule'] = wc_info.find('schedule').text | 536 'revision'), |
| 535 result['Copied From URL'] = wc_info.find('copy-from-url') | 537 int) |
| 536 result['Copied From Rev'] = wc_info.find('copy-from-rev') | 538 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), |
| 537 for key in result.keys(): | 539 str) |
| 538 if isinstance(result[key], unicode): | 540 # Differs across versions. |
| 539 # Unicode results interferes with the higher layers matching up things | 541 if result['Node Kind'] == 'dir': |
| 540 # in the deps dictionary. | 542 result['Node Kind'] = 'directory' |
| 541 result[key] = result[key].encode() | 543 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) |
| 542 # Automatic conversion of optional parameters. | 544 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) |
| 543 result[key] = getattr(result[key], 'text', result[key]) | 545 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) |
| 546 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) |
| 544 return result | 547 return result |
| 545 | 548 |
| 546 @staticmethod | 549 @staticmethod |
| 547 def CaptureRevision(cwd): | 550 def CaptureRevision(cwd): |
| 548 """Get the base revision of a SVN repository. | 551 """Get the base revision of a SVN repository. |
| 549 | 552 |
| 550 Returns: | 553 Returns: |
| 551 Int base revision | 554 Int base revision |
| 552 """ | 555 """ |
| 553 return SVN.CaptureInfo(cwd).get('Revision') | 556 info = SVN.Capture(['info', '--xml'], cwd=cwd) |
| 557 dom = xml.dom.minidom.parseString(info) |
| 558 return dom.getElementsByTagName('entry')[0].getAttribute('revision') |
| 554 | 559 |
| 555 @staticmethod | 560 @staticmethod |
| 556 def CaptureStatus(files): | 561 def CaptureStatus(files): |
| 557 """Returns the svn 1.5 svn status emulated output. | 562 """Returns the svn 1.5 svn status emulated output. |
| 558 | 563 |
| 559 @files can be a string (one file) or a list of files. | 564 @files can be a string (one file) or a list of files. |
| 560 | 565 |
| 561 Returns an array of (status, file) tuples.""" | 566 Returns an array of (status, file) tuples.""" |
| 562 command = ["status", "--xml"] | 567 command = ["status", "--xml"] |
| 563 if not files: | 568 if not files: |
| (...skipping 14 matching lines...) Expand all Loading... |
| 578 'incomplete': '!', | 583 'incomplete': '!', |
| 579 'merged': 'G', | 584 'merged': 'G', |
| 580 'missing': '!', | 585 'missing': '!', |
| 581 'modified': 'M', | 586 'modified': 'M', |
| 582 'none': ' ', | 587 'none': ' ', |
| 583 'normal': ' ', | 588 'normal': ' ', |
| 584 'obstructed': '~', | 589 'obstructed': '~', |
| 585 'replaced': 'R', | 590 'replaced': 'R', |
| 586 'unversioned': '?', | 591 'unversioned': '?', |
| 587 } | 592 } |
| 588 dom = ElementTree.XML(SVN.Capture(command)) | 593 dom = gclient_utils.ParseXML(SVN.Capture(command)) |
| 589 results = [] | 594 results = [] |
| 590 if dom is None: | 595 if dom: |
| 591 return results | 596 # /status/target/entry/(wc-status|commit|author|date) |
| 592 # /status/target/entry/(wc-status|commit|author|date) | 597 for target in dom.getElementsByTagName('target'): |
| 593 for target in dom.findall('target'): | 598 #base_path = target.getAttribute('path') |
| 594 for entry in target.findall('entry'): | 599 for entry in target.getElementsByTagName('entry'): |
| 595 file_path = entry.attrib['path'] | 600 file_path = entry.getAttribute('path') |
| 596 wc_status = entry.find('wc-status') | 601 wc_status = entry.getElementsByTagName('wc-status') |
| 597 # Emulate svn 1.5 status ouput... | 602 assert len(wc_status) == 1 |
| 598 statuses = [' '] * 7 | 603 # Emulate svn 1.5 status ouput... |
| 599 # Col 0 | 604 statuses = [' '] * 7 |
| 600 xml_item_status = wc_status.attrib['item'] | 605 # Col 0 |
| 601 if xml_item_status in status_letter: | 606 xml_item_status = wc_status[0].getAttribute('item') |
| 602 statuses[0] = status_letter[xml_item_status] | 607 if xml_item_status in status_letter: |
| 603 else: | 608 statuses[0] = status_letter[xml_item_status] |
| 604 raise gclient_utils.Error( | 609 else: |
| 605 'Unknown item status "%s"; please implement me!' % | 610 raise gclient_utils.Error( |
| 606 xml_item_status) | 611 'Unknown item status "%s"; please implement me!' % |
| 607 # Col 1 | 612 xml_item_status) |
| 608 xml_props_status = wc_status.attrib['props'] | 613 # Col 1 |
| 609 if xml_props_status == 'modified': | 614 xml_props_status = wc_status[0].getAttribute('props') |
| 610 statuses[1] = 'M' | 615 if xml_props_status == 'modified': |
| 611 elif xml_props_status == 'conflicted': | 616 statuses[1] = 'M' |
| 612 statuses[1] = 'C' | 617 elif xml_props_status == 'conflicted': |
| 613 elif (not xml_props_status or xml_props_status == 'none' or | 618 statuses[1] = 'C' |
| 614 xml_props_status == 'normal'): | 619 elif (not xml_props_status or xml_props_status == 'none' or |
| 615 pass | 620 xml_props_status == 'normal'): |
| 616 else: | 621 pass |
| 617 raise gclient_utils.Error( | 622 else: |
| 618 'Unknown props status "%s"; please implement me!' % | 623 raise gclient_utils.Error( |
| 619 xml_props_status) | 624 'Unknown props status "%s"; please implement me!' % |
| 620 # Col 2 | 625 xml_props_status) |
| 621 if wc_status.attrib.get('wc-locked') == 'true': | 626 # Col 2 |
| 622 statuses[2] = 'L' | 627 if wc_status[0].getAttribute('wc-locked') == 'true': |
| 623 # Col 3 | 628 statuses[2] = 'L' |
| 624 if wc_status.attrib.get('copied') == 'true': | 629 # Col 3 |
| 625 statuses[3] = '+' | 630 if wc_status[0].getAttribute('copied') == 'true': |
| 626 # Col 4 | 631 statuses[3] = '+' |
| 627 if wc_status.attrib.get('switched') == 'true': | 632 # Col 4 |
| 628 statuses[4] = 'S' | 633 if wc_status[0].getAttribute('switched') == 'true': |
| 629 # TODO(maruel): Col 5 and 6 | 634 statuses[4] = 'S' |
| 630 item = (''.join(statuses), file_path) | 635 # TODO(maruel): Col 5 and 6 |
| 631 results.append(item) | 636 item = (''.join(statuses), file_path) |
| 637 results.append(item) |
| 632 return results | 638 return results |
| 633 | 639 |
| 634 @staticmethod | 640 @staticmethod |
| 635 def IsMoved(filename): | 641 def IsMoved(filename): |
| 636 """Determine if a file has been added through svn mv""" | 642 """Determine if a file has been added through svn mv""" |
| 637 return SVN.IsMovedInfo(SVN.CaptureInfo(filename)) | 643 return SVN.IsMovedInfo(SVN.CaptureInfo(filename)) |
| 638 | 644 |
| 639 @staticmethod | 645 @staticmethod |
| 640 def IsMovedInfo(info): | 646 def IsMovedInfo(info): |
| 641 """Determine if a file has been added through svn mv""" | 647 """Determine if a file has been added through svn mv""" |
| (...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 976 if (file_status[0][0] in ('D', 'A', '!') or | 982 if (file_status[0][0] in ('D', 'A', '!') or |
| 977 not file_status[0][1:].isspace()): | 983 not file_status[0][1:].isspace()): |
| 978 # Added, deleted file requires manual intervention and require calling | 984 # Added, deleted file requires manual intervention and require calling |
| 979 # revert, like for properties. | 985 # revert, like for properties. |
| 980 try: | 986 try: |
| 981 SVN.Capture(['revert', file_status[1]], cwd=repo_root) | 987 SVN.Capture(['revert', file_status[1]], cwd=repo_root) |
| 982 except gclient_utils.CheckCallError: | 988 except gclient_utils.CheckCallError: |
| 983 if not os.path.exists(file_path): | 989 if not os.path.exists(file_path): |
| 984 continue | 990 continue |
| 985 raise | 991 raise |
| OLD | NEW |