Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(98)

Side by Side Diff: scm.py

Issue 8771042: Enforces using cwd in all svn calls. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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
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
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
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
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
OLDNEW
« gclient_scm.py ('K') | « presubmit_support.py ('k') | testing_support/fake_repos.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698