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