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