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 |