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

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: Address comments 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
« no previous file with comments | « presubmit_support.py ('k') | testing_support/fake_repos.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 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
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
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
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
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
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
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
OLDNEW
« no previous file with comments | « presubmit_support.py ('k') | testing_support/fake_repos.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698