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 |