OLD | NEW |
1 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2011 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 |
(...skipping 380 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 return (False, cls.current_version) | 391 return (False, cls.current_version) |
392 elif ver > min_ver: | 392 elif ver > min_ver: |
393 return (True, cls.current_version) | 393 return (True, cls.current_version) |
394 return (True, cls.current_version) | 394 return (True, cls.current_version) |
395 | 395 |
396 | 396 |
397 class SVN(object): | 397 class SVN(object): |
398 current_version = None | 398 current_version = None |
399 | 399 |
400 @staticmethod | 400 @staticmethod |
401 def Capture(args, **kwargs): | 401 def Capture(args, cwd, **kwargs): |
402 """Always redirect stderr. | 402 """Always redirect stderr. |
403 | 403 |
404 Throws an exception if non-0 is returned. | 404 Throws an exception if non-0 is returned. |
405 """ | 405 """ |
406 return subprocess2.check_output( | 406 return subprocess2.check_output( |
407 ['svn'] + args, stderr=subprocess2.PIPE, **kwargs) | 407 ['svn'] + args, stderr=subprocess2.PIPE, cwd=cwd, **kwargs) |
408 | 408 |
409 @staticmethod | 409 @staticmethod |
410 def RunAndGetFileList(verbose, args, cwd, file_list, stdout=None): | 410 def RunAndGetFileList(verbose, args, cwd, file_list, stdout=None): |
411 """Runs svn checkout, update, or status, output to stdout. | 411 """Runs svn checkout, update, or status, output to stdout. |
412 | 412 |
413 The first item in args must be either "checkout", "update", or "status". | 413 The first item in args must be either "checkout", "update", or "status". |
414 | 414 |
415 svn's stdout is parsed to collect a list of files checked out or updated. | 415 svn's stdout is parsed to collect a list of files checked out or updated. |
416 These files are appended to file_list. svn's stdout is also printed to | 416 These files are appended to file_list. svn's stdout is also printed to |
417 sys.stdout as in Run. | 417 sys.stdout as in Run. |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
507 raise | 507 raise |
508 if retries == 10: | 508 if retries == 10: |
509 raise | 509 raise |
510 print "Sleeping %.1f seconds and retrying...." % backoff_time | 510 print "Sleeping %.1f seconds and retrying...." % backoff_time |
511 time.sleep(backoff_time) | 511 time.sleep(backoff_time) |
512 backoff_time *= 1.3 | 512 backoff_time *= 1.3 |
513 continue | 513 continue |
514 break | 514 break |
515 | 515 |
516 @staticmethod | 516 @staticmethod |
517 def CaptureInfo(cwd): | 517 def CaptureRemoteInfo(url): |
| 518 """Returns a dictionary from the svn info output for the given url. |
| 519 |
| 520 Throws an exception if svn info fails. |
| 521 """ |
| 522 assert isinstance(url, str) |
| 523 return SVN._CaptureInfo([url], None) |
| 524 |
| 525 @staticmethod |
| 526 def CaptureLocalInfo(files, cwd): |
| 527 """Returns a dictionary from the svn info output for the given files. |
| 528 |
| 529 Throws an exception if svn info fails. |
| 530 """ |
| 531 assert isinstance(files, (list, tuple)) |
| 532 return SVN._CaptureInfo(files, cwd) |
| 533 |
| 534 @staticmethod |
| 535 def _CaptureInfo(files, cwd): |
518 """Returns a dictionary from the svn info output for the given file. | 536 """Returns a dictionary from the svn info output for the given file. |
519 | 537 |
520 Throws an exception if svn info fails.""" | 538 Throws an exception if svn info fails.""" |
521 result = {} | 539 result = {} |
522 output = SVN.Capture(['info', '--xml', cwd]) | 540 info = ElementTree.XML(SVN.Capture(['info', '--xml'] + files, cwd)) |
523 info = ElementTree.XML(output) | |
524 if info is None: | 541 if info is None: |
525 return result | 542 return result |
526 entry = info.find('entry') | 543 entry = info.find('entry') |
527 if entry is None: | 544 if entry is None: |
528 return result | 545 return result |
529 | 546 |
530 # Use .text when the item is not optional. | 547 # Use .text when the item is not optional. |
531 result['Path'] = entry.attrib['path'] | 548 result['Path'] = entry.attrib['path'] |
532 result['Revision'] = int(entry.attrib['revision']) | 549 result['Revision'] = int(entry.attrib['revision']) |
533 result['Node Kind'] = entry.attrib['kind'] | 550 result['Node Kind'] = entry.attrib['kind'] |
(...skipping 22 matching lines...) Expand all Loading... |
556 result[key] = getattr(result[key], 'text', result[key]) | 573 result[key] = getattr(result[key], 'text', result[key]) |
557 return result | 574 return result |
558 | 575 |
559 @staticmethod | 576 @staticmethod |
560 def CaptureRevision(cwd): | 577 def CaptureRevision(cwd): |
561 """Get the base revision of a SVN repository. | 578 """Get the base revision of a SVN repository. |
562 | 579 |
563 Returns: | 580 Returns: |
564 Int base revision | 581 Int base revision |
565 """ | 582 """ |
566 return SVN.CaptureInfo(cwd).get('Revision') | 583 return SVN.CaptureLocalInfo([], cwd).get('Revision') |
567 | 584 |
568 @staticmethod | 585 @staticmethod |
569 def CaptureStatus(files): | 586 def CaptureStatus(files, cwd): |
570 """Returns the svn 1.5 svn status emulated output. | 587 """Returns the svn 1.5 svn status emulated output. |
571 | 588 |
572 @files can be a string (one file) or a list of files. | 589 @files can be a string (one file) or a list of files. |
573 | 590 |
574 Returns an array of (status, file) tuples.""" | 591 Returns an array of (status, file) tuples.""" |
575 command = ["status", "--xml"] | 592 command = ["status", "--xml"] |
576 if not files: | 593 if not files: |
577 pass | 594 pass |
578 elif isinstance(files, basestring): | 595 elif isinstance(files, basestring): |
579 command.append(files) | 596 command.append(files) |
(...skipping 11 matching lines...) Expand all Loading... |
591 'incomplete': '!', | 608 'incomplete': '!', |
592 'merged': 'G', | 609 'merged': 'G', |
593 'missing': '!', | 610 'missing': '!', |
594 'modified': 'M', | 611 'modified': 'M', |
595 'none': ' ', | 612 'none': ' ', |
596 'normal': ' ', | 613 'normal': ' ', |
597 'obstructed': '~', | 614 'obstructed': '~', |
598 'replaced': 'R', | 615 'replaced': 'R', |
599 'unversioned': '?', | 616 'unversioned': '?', |
600 } | 617 } |
601 dom = ElementTree.XML(SVN.Capture(command)) | 618 dom = ElementTree.XML(SVN.Capture(command, cwd)) |
602 results = [] | 619 results = [] |
603 if dom is None: | 620 if dom is None: |
604 return results | 621 return results |
605 # /status/target/entry/(wc-status|commit|author|date) | 622 # /status/target/entry/(wc-status|commit|author|date) |
606 for target in dom.findall('target'): | 623 for target in dom.findall('target'): |
607 for entry in target.findall('entry'): | 624 for entry in target.findall('entry'): |
608 file_path = entry.attrib['path'] | 625 file_path = entry.attrib['path'] |
609 wc_status = entry.find('wc-status') | 626 wc_status = entry.find('wc-status') |
610 # Emulate svn 1.5 status ouput... | 627 # Emulate svn 1.5 status ouput... |
611 statuses = [' '] * 7 | 628 statuses = [' '] * 7 |
(...skipping 26 matching lines...) Expand all Loading... |
638 statuses[3] = '+' | 655 statuses[3] = '+' |
639 # Col 4 | 656 # Col 4 |
640 if wc_status.attrib.get('switched') == 'true': | 657 if wc_status.attrib.get('switched') == 'true': |
641 statuses[4] = 'S' | 658 statuses[4] = 'S' |
642 # TODO(maruel): Col 5 and 6 | 659 # TODO(maruel): Col 5 and 6 |
643 item = (''.join(statuses), file_path) | 660 item = (''.join(statuses), file_path) |
644 results.append(item) | 661 results.append(item) |
645 return results | 662 return results |
646 | 663 |
647 @staticmethod | 664 @staticmethod |
648 def IsMoved(filename): | 665 def IsMoved(filename, cwd): |
649 """Determine if a file has been added through svn mv""" | 666 """Determine if a file has been added through svn mv""" |
650 return SVN.IsMovedInfo(SVN.CaptureInfo(filename)) | 667 assert isinstance(filename, basestring) |
| 668 return SVN.IsMovedInfo(SVN.CaptureLocalInfo([filename], cwd)) |
651 | 669 |
652 @staticmethod | 670 @staticmethod |
653 def IsMovedInfo(info): | 671 def IsMovedInfo(info): |
654 """Determine if a file has been added through svn mv""" | 672 """Determine if a file has been added through svn mv""" |
655 return (info.get('Copied From URL') and | 673 return (info.get('Copied From URL') and |
656 info.get('Copied From Rev') and | 674 info.get('Copied From Rev') and |
657 info.get('Schedule') == 'add') | 675 info.get('Schedule') == 'add') |
658 | 676 |
659 @staticmethod | 677 @staticmethod |
660 def GetFileProperty(filename, property_name): | 678 def GetFileProperty(filename, property_name, cwd): |
661 """Returns the value of an SVN property for the given file. | 679 """Returns the value of an SVN property for the given file. |
662 | 680 |
663 Args: | 681 Args: |
664 filename: The file to check | 682 filename: The file to check |
665 property_name: The name of the SVN property, e.g. "svn:mime-type" | 683 property_name: The name of the SVN property, e.g. "svn:mime-type" |
666 | 684 |
667 Returns: | 685 Returns: |
668 The value of the property, which will be the empty string if the property | 686 The value of the property, which will be the empty string if the property |
669 is not set on the file. If the file is not under version control, the | 687 is not set on the file. If the file is not under version control, the |
670 empty string is also returned. | 688 empty string is also returned. |
671 """ | 689 """ |
672 try: | 690 try: |
673 return SVN.Capture(['propget', property_name, filename]) | 691 return SVN.Capture(['propget', property_name, filename], cwd) |
674 except subprocess2.CalledProcessError: | 692 except subprocess2.CalledProcessError: |
675 return '' | 693 return '' |
676 | 694 |
677 @staticmethod | 695 @staticmethod |
678 def DiffItem(filename, full_move=False, revision=None): | 696 def DiffItem(filename, cwd, full_move, revision): |
679 """Diffs a single file. | 697 """Diffs a single file. |
680 | 698 |
681 Should be simple, eh? No it isn't. | 699 Should be simple, eh? No it isn't. |
682 Be sure to be in the appropriate directory before calling to have the | 700 Be sure to be in the appropriate directory before calling to have the |
683 expected relative path. | 701 expected relative path. |
684 full_move means that move or copy operations should completely recreate the | 702 full_move means that move or copy operations should completely recreate the |
685 files, usually in the prospect to apply the patch for a try job.""" | 703 files, usually in the prospect to apply the patch for a try job.""" |
686 # If the user specified a custom diff command in their svn config file, | 704 # If the user specified a custom diff command in their svn config file, |
687 # then it'll be used when we do svn diff, which we don't want to happen | 705 # then it'll be used when we do svn diff, which we don't want to happen |
688 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 706 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
689 # work, since they can have another diff executable in their path that | 707 # work, since they can have another diff executable in their path that |
690 # gives different line endings. So we use a bogus temp directory as the | 708 # gives different line endings. So we use a bogus temp directory as the |
691 # config directory, which gets around these problems. | 709 # config directory, which gets around these problems. |
692 bogus_dir = tempfile.mkdtemp() | 710 bogus_dir = tempfile.mkdtemp() |
693 try: | 711 try: |
694 # Use "svn info" output instead of os.path.isdir because the latter fails | 712 # Use "svn info" output instead of os.path.isdir because the latter fails |
695 # when the file is deleted. | 713 # when the file is deleted. |
696 return SVN._DiffItemInternal(filename, SVN.CaptureInfo(filename), | 714 return SVN._DiffItemInternal( |
697 bogus_dir, | 715 filename, |
698 full_move=full_move, revision=revision) | 716 cwd, |
| 717 SVN.CaptureLocalInfo([filename], cwd), |
| 718 bogus_dir, |
| 719 full_move, |
| 720 revision) |
699 finally: | 721 finally: |
700 gclient_utils.RemoveDirectory(bogus_dir) | 722 gclient_utils.RemoveDirectory(bogus_dir) |
701 | 723 |
702 @staticmethod | 724 @staticmethod |
703 def _DiffItemInternal(filename, info, bogus_dir, full_move=False, | 725 def _DiffItemInternal(filename, cwd, info, bogus_dir, full_move, revision): |
704 revision=None): | |
705 """Grabs the diff data.""" | 726 """Grabs the diff data.""" |
706 command = ["diff", "--config-dir", bogus_dir, filename] | 727 command = ["diff", "--config-dir", bogus_dir, filename] |
707 if revision: | 728 if revision: |
708 command.extend(['--revision', revision]) | 729 command.extend(['--revision', revision]) |
709 data = None | 730 data = None |
710 if SVN.IsMovedInfo(info): | 731 if SVN.IsMovedInfo(info): |
711 if full_move: | 732 if full_move: |
712 if info.get("Node Kind") == "directory": | 733 if info.get("Node Kind") == "directory": |
713 # Things become tricky here. It's a directory copy/move. We need to | 734 # Things become tricky here. It's a directory copy/move. We need to |
714 # diff all the files inside it. | 735 # diff all the files inside it. |
(...skipping 15 matching lines...) Expand all Loading... |
730 data.write(GenFakeDiff(os.path.join(dirpath, f))) | 751 data.write(GenFakeDiff(os.path.join(dirpath, f))) |
731 if data: | 752 if data: |
732 tmp = data.getvalue() | 753 tmp = data.getvalue() |
733 data.close() | 754 data.close() |
734 data = tmp | 755 data = tmp |
735 else: | 756 else: |
736 data = GenFakeDiff(filename) | 757 data = GenFakeDiff(filename) |
737 else: | 758 else: |
738 if info.get("Node Kind") != "directory": | 759 if info.get("Node Kind") != "directory": |
739 # svn diff on a mv/cp'd file outputs nothing if there was no change. | 760 # svn diff on a mv/cp'd file outputs nothing if there was no change. |
740 data = SVN.Capture(command) | 761 data = SVN.Capture(command, cwd) |
741 if not data: | 762 if not data: |
742 # We put in an empty Index entry so upload.py knows about them. | 763 # We put in an empty Index entry so upload.py knows about them. |
743 data = "Index: %s\n" % filename.replace(os.sep, '/') | 764 data = "Index: %s\n" % filename.replace(os.sep, '/') |
744 # Otherwise silently ignore directories. | 765 # Otherwise silently ignore directories. |
745 else: | 766 else: |
746 if info.get("Node Kind") != "directory": | 767 if info.get("Node Kind") != "directory": |
747 # Normal simple case. | 768 # Normal simple case. |
748 try: | 769 try: |
749 data = SVN.Capture(command) | 770 data = SVN.Capture(command, cwd) |
750 except subprocess2.CalledProcessError: | 771 except subprocess2.CalledProcessError: |
751 if revision: | 772 if revision: |
752 data = GenFakeDiff(filename) | 773 data = GenFakeDiff(filename) |
753 else: | 774 else: |
754 raise | 775 raise |
755 # Otherwise silently ignore directories. | 776 # Otherwise silently ignore directories. |
756 return data | 777 return data |
757 | 778 |
758 @staticmethod | 779 @staticmethod |
759 def GenerateDiff(filenames, root=None, full_move=False, revision=None): | 780 def GenerateDiff(filenames, cwd, full_move, revision): |
760 """Returns a string containing the diff for the given file list. | 781 """Returns a string containing the diff for the given file list. |
761 | 782 |
762 The files in the list should either be absolute paths or relative to the | 783 The files in the list should either be absolute paths or relative to the |
763 given root. If no root directory is provided, the repository root will be | 784 given root. If no root directory is provided, the repository root will be |
764 used. | 785 used. |
765 The diff will always use relative paths. | 786 The diff will always use relative paths. |
766 """ | 787 """ |
767 assert isinstance(filenames, (list, tuple)) | 788 assert isinstance(filenames, (list, tuple)) |
768 previous_cwd = os.getcwd() | 789 root = os.path.normcase(os.path.join(cwd, '')) |
769 root = root or SVN.GetCheckoutRoot(previous_cwd) | |
770 root = os.path.normcase(os.path.join(root, '')) | |
771 def RelativePath(path, root): | 790 def RelativePath(path, root): |
772 """We must use relative paths.""" | 791 """We must use relative paths.""" |
773 if os.path.normcase(path).startswith(root): | 792 if os.path.normcase(path).startswith(root): |
774 return path[len(root):] | 793 return path[len(root):] |
775 return path | 794 return path |
776 # If the user specified a custom diff command in their svn config file, | 795 # If the user specified a custom diff command in their svn config file, |
777 # then it'll be used when we do svn diff, which we don't want to happen | 796 # then it'll be used when we do svn diff, which we don't want to happen |
778 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 797 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
779 # work, since they can have another diff executable in their path that | 798 # work, since they can have another diff executable in their path that |
780 # gives different line endings. So we use a bogus temp directory as the | 799 # gives different line endings. So we use a bogus temp directory as the |
781 # config directory, which gets around these problems. | 800 # config directory, which gets around these problems. |
782 bogus_dir = tempfile.mkdtemp() | 801 bogus_dir = tempfile.mkdtemp() |
783 try: | 802 try: |
784 os.chdir(root) | |
785 # Cleanup filenames | 803 # Cleanup filenames |
786 filenames = [RelativePath(f, root) for f in filenames] | 804 filenames = [RelativePath(f, root) for f in filenames] |
787 # Get information about the modified items (files and directories) | 805 # Get information about the modified items (files and directories) |
788 data = dict([(f, SVN.CaptureInfo(f)) for f in filenames]) | 806 data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) |
789 diffs = [] | 807 diffs = [] |
790 if full_move: | 808 if full_move: |
791 # Eliminate modified files inside moved/copied directory. | 809 # Eliminate modified files inside moved/copied directory. |
792 for (filename, info) in data.iteritems(): | 810 for (filename, info) in data.iteritems(): |
793 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": | 811 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": |
794 # Remove files inside the directory. | 812 # Remove files inside the directory. |
795 filenames = [f for f in filenames | 813 filenames = [f for f in filenames |
796 if not f.startswith(filename + os.path.sep)] | 814 if not f.startswith(filename + os.path.sep)] |
797 for filename in data.keys(): | 815 for filename in data.keys(): |
798 if not filename in filenames: | 816 if not filename in filenames: |
799 # Remove filtered out items. | 817 # Remove filtered out items. |
800 del data[filename] | 818 del data[filename] |
801 else: | 819 else: |
802 metaheaders = [] | 820 metaheaders = [] |
803 for (filename, info) in data.iteritems(): | 821 for (filename, info) in data.iteritems(): |
804 if SVN.IsMovedInfo(info): | 822 if SVN.IsMovedInfo(info): |
805 # for now, the most common case is a head copy, | 823 # for now, the most common case is a head copy, |
806 # so let's just encode that as a straight up cp. | 824 # so let's just encode that as a straight up cp. |
807 srcurl = info.get('Copied From URL') | 825 srcurl = info.get('Copied From URL') |
808 root = info.get('Repository Root') | 826 file_root = info.get('Repository Root') |
809 rev = int(info.get('Copied From Rev')) | 827 rev = int(info.get('Copied From Rev')) |
810 assert srcurl.startswith(root) | 828 assert srcurl.startswith(file_root) |
811 src = srcurl[len(root)+1:] | 829 src = srcurl[len(file_root)+1:] |
812 try: | 830 try: |
813 srcinfo = SVN.CaptureInfo(srcurl) | 831 srcinfo = SVN.CaptureRemoteInfo(srcurl) |
814 except subprocess2.CalledProcessError, e: | 832 except subprocess2.CalledProcessError, e: |
815 if not 'Not a valid URL' in e.stderr: | 833 if not 'Not a valid URL' in e.stderr: |
816 raise | 834 raise |
817 # Assume the file was deleted. No idea how to figure out at which | 835 # Assume the file was deleted. No idea how to figure out at which |
818 # revision the file was deleted. | 836 # revision the file was deleted. |
819 srcinfo = {'Revision': rev} | 837 srcinfo = {'Revision': rev} |
820 if (srcinfo.get('Revision') != rev and | 838 if (srcinfo.get('Revision') != rev and |
821 SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl])): | 839 SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl], cwd)): |
822 metaheaders.append("#$ svn cp -r %d %s %s " | 840 metaheaders.append("#$ svn cp -r %d %s %s " |
823 "### WARNING: note non-trunk copy\n" % | 841 "### WARNING: note non-trunk copy\n" % |
824 (rev, src, filename)) | 842 (rev, src, filename)) |
825 else: | 843 else: |
826 metaheaders.append("#$ cp %s %s\n" % (src, | 844 metaheaders.append("#$ cp %s %s\n" % (src, |
827 filename)) | 845 filename)) |
828 | 846 |
829 if metaheaders: | 847 if metaheaders: |
830 diffs.append("### BEGIN SVN COPY METADATA\n") | 848 diffs.append("### BEGIN SVN COPY METADATA\n") |
831 diffs.extend(metaheaders) | 849 diffs.extend(metaheaders) |
832 diffs.append("### END SVN COPY METADATA\n") | 850 diffs.append("### END SVN COPY METADATA\n") |
833 # Now ready to do the actual diff. | 851 # Now ready to do the actual diff. |
834 for filename in sorted(data.iterkeys()): | 852 for filename in sorted(data.iterkeys()): |
835 diffs.append(SVN._DiffItemInternal(filename, data[filename], bogus_dir, | 853 diffs.append(SVN._DiffItemInternal( |
836 full_move=full_move, | 854 filename, cwd, data[filename], bogus_dir, full_move, revision)) |
837 revision=revision)) | |
838 # Use StringIO since it can be messy when diffing a directory move with | 855 # Use StringIO since it can be messy when diffing a directory move with |
839 # full_move=True. | 856 # full_move=True. |
840 buf = cStringIO.StringIO() | 857 buf = cStringIO.StringIO() |
841 for d in filter(None, diffs): | 858 for d in filter(None, diffs): |
842 buf.write(d) | 859 buf.write(d) |
843 result = buf.getvalue() | 860 result = buf.getvalue() |
844 buf.close() | 861 buf.close() |
845 return result | 862 return result |
846 finally: | 863 finally: |
847 os.chdir(previous_cwd) | |
848 gclient_utils.RemoveDirectory(bogus_dir) | 864 gclient_utils.RemoveDirectory(bogus_dir) |
849 | 865 |
850 @staticmethod | 866 @staticmethod |
851 def GetEmail(repo_root): | 867 def GetEmail(cwd): |
852 """Retrieves the svn account which we assume is an email address.""" | 868 """Retrieves the svn account which we assume is an email address.""" |
853 try: | 869 try: |
854 infos = SVN.CaptureInfo(repo_root) | 870 infos = SVN.CaptureLocalInfo([], cwd) |
855 except subprocess2.CalledProcessError: | 871 except subprocess2.CalledProcessError: |
856 return None | 872 return None |
857 | 873 |
858 # Should check for uuid but it is incorrectly saved for https creds. | 874 # Should check for uuid but it is incorrectly saved for https creds. |
859 root = infos['Repository Root'] | 875 root = infos['Repository Root'] |
860 realm = root.rsplit('/', 1)[0] | 876 realm = root.rsplit('/', 1)[0] |
861 uuid = infos['UUID'] | 877 uuid = infos['UUID'] |
862 if root.startswith('https') or not uuid: | 878 if root.startswith('https') or not uuid: |
863 regexp = re.compile(r'<%s:\d+>.*' % realm) | 879 regexp = re.compile(r'<%s:\d+>.*' % realm) |
864 else: | 880 else: |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
897 key = ReadOneItem('K') | 913 key = ReadOneItem('K') |
898 if not key: | 914 if not key: |
899 break | 915 break |
900 value = ReadOneItem('V') | 916 value = ReadOneItem('V') |
901 if not value: | 917 if not value: |
902 break | 918 break |
903 values[key] = value | 919 values[key] = value |
904 return values | 920 return values |
905 | 921 |
906 @staticmethod | 922 @staticmethod |
907 def GetCheckoutRoot(directory): | 923 def GetCheckoutRoot(cwd): |
908 """Returns the top level directory of the current repository. | 924 """Returns the top level directory of the current repository. |
909 | 925 |
910 The directory is returned as an absolute path. | 926 The directory is returned as an absolute path. |
911 """ | 927 """ |
912 directory = os.path.abspath(directory) | 928 cwd = os.path.abspath(cwd) |
913 try: | 929 try: |
914 info = SVN.CaptureInfo(directory) | 930 info = SVN.CaptureLocalInfo([], cwd) |
915 cur_dir_repo_root = info['Repository Root'] | 931 cur_dir_repo_root = info['Repository Root'] |
916 url = info['URL'] | 932 url = info['URL'] |
917 except subprocess2.CalledProcessError: | 933 except subprocess2.CalledProcessError: |
918 return None | 934 return None |
919 while True: | 935 while True: |
920 parent = os.path.dirname(directory) | 936 parent = os.path.dirname(cwd) |
921 try: | 937 try: |
922 info = SVN.CaptureInfo(parent) | 938 info = SVN.CaptureLocalInfo([], parent) |
923 if (info['Repository Root'] != cur_dir_repo_root or | 939 if (info['Repository Root'] != cur_dir_repo_root or |
924 info['URL'] != os.path.dirname(url)): | 940 info['URL'] != os.path.dirname(url)): |
925 break | 941 break |
926 url = info['URL'] | 942 url = info['URL'] |
927 except subprocess2.CalledProcessError: | 943 except subprocess2.CalledProcessError: |
928 break | 944 break |
929 directory = parent | 945 cwd = parent |
930 return GetCasedPath(directory) | 946 return GetCasedPath(cwd) |
931 | 947 |
932 @classmethod | 948 @classmethod |
933 def AssertVersion(cls, min_version): | 949 def AssertVersion(cls, min_version): |
934 """Asserts svn's version is at least min_version.""" | 950 """Asserts svn's version is at least min_version.""" |
935 if cls.current_version is None: | 951 if cls.current_version is None: |
936 cls.current_version = cls.Capture(['--version']).split()[2] | 952 cls.current_version = cls.Capture(['--version'], None).split()[2] |
937 current_version_list = map(only_int, cls.current_version.split('.')) | 953 current_version_list = map(only_int, cls.current_version.split('.')) |
938 for min_ver in map(int, min_version.split('.')): | 954 for min_ver in map(int, min_version.split('.')): |
939 ver = current_version_list.pop(0) | 955 ver = current_version_list.pop(0) |
940 if ver < min_ver: | 956 if ver < min_ver: |
941 return (False, cls.current_version) | 957 return (False, cls.current_version) |
942 elif ver > min_ver: | 958 elif ver > min_ver: |
943 return (True, cls.current_version) | 959 return (True, cls.current_version) |
944 return (True, cls.current_version) | 960 return (True, cls.current_version) |
945 | 961 |
946 @staticmethod | 962 @staticmethod |
947 def Revert(repo_root, callback=None, ignore_externals=False): | 963 def Revert(cwd, callback=None, ignore_externals=False): |
948 """Reverts all svn modifications in repo_root, including properties. | 964 """Reverts all svn modifications in cwd, including properties. |
949 | 965 |
950 Deletes any modified files or directory. | 966 Deletes any modified files or directory. |
951 | 967 |
952 A "svn update --revision BASE" call is required after to revive deleted | 968 A "svn update --revision BASE" call is required after to revive deleted |
953 files. | 969 files. |
954 """ | 970 """ |
955 for file_status in SVN.CaptureStatus(repo_root): | 971 for file_status in SVN.CaptureStatus(None, cwd): |
956 file_path = os.path.join(repo_root, file_status[1]) | 972 file_path = os.path.join(cwd, file_status[1]) |
957 if (ignore_externals and | 973 if (ignore_externals and |
958 file_status[0][0] == 'X' and | 974 file_status[0][0] == 'X' and |
959 file_status[0][1:].isspace()): | 975 file_status[0][1:].isspace()): |
960 # Ignore externals. | 976 # Ignore externals. |
961 logging.info('Ignoring external %s' % file_status[1]) | 977 logging.info('Ignoring external %s' % file_status[1]) |
962 continue | 978 continue |
963 | 979 |
964 if callback: | 980 if callback: |
965 callback(file_status) | 981 callback(file_status) |
966 | 982 |
(...skipping 11 matching lines...) Expand all Loading... |
978 gclient_utils.RemoveDirectory(file_path) | 994 gclient_utils.RemoveDirectory(file_path) |
979 else: | 995 else: |
980 logging.critical( | 996 logging.critical( |
981 ('No idea what is %s.\nYou just found a bug in gclient' | 997 ('No idea what is %s.\nYou just found a bug in gclient' |
982 ', please ping maruel@chromium.org ASAP!') % file_path) | 998 ', please ping maruel@chromium.org ASAP!') % file_path) |
983 | 999 |
984 if (file_status[0][0] in ('D', 'A', '!') or | 1000 if (file_status[0][0] in ('D', 'A', '!') or |
985 not file_status[0][1:].isspace()): | 1001 not file_status[0][1:].isspace()): |
986 # Added, deleted file requires manual intervention and require calling | 1002 # Added, deleted file requires manual intervention and require calling |
987 # revert, like for properties. | 1003 # revert, like for properties. |
988 if not os.path.isdir(repo_root): | 1004 if not os.path.isdir(cwd): |
989 # '.' was deleted. It's not worth continuing. | 1005 # '.' was deleted. It's not worth continuing. |
990 return | 1006 return |
991 try: | 1007 try: |
992 SVN.Capture(['revert', file_status[1]], cwd=repo_root) | 1008 SVN.Capture(['revert', file_status[1]], cwd=cwd) |
993 except subprocess2.CalledProcessError: | 1009 except subprocess2.CalledProcessError: |
994 if not os.path.exists(file_path): | 1010 if not os.path.exists(file_path): |
995 continue | 1011 continue |
996 raise | 1012 raise |
OLD | NEW |