| 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 |