| OLD | NEW |
| 1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2006-2009 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 glob | 8 import glob |
| 8 import os | 9 import os |
| 9 import re | 10 import re |
| 10 import shutil | 11 import shutil |
| 11 import subprocess | 12 import subprocess |
| 12 import sys | 13 import sys |
| 13 import tempfile | 14 import tempfile |
| 14 import time | 15 import time |
| 15 import xml.dom.minidom | 16 import xml.dom.minidom |
| 16 | 17 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 34 subpath = '\\'.join(paths[:i+1]) | 35 subpath = '\\'.join(paths[:i+1]) |
| 35 prev = len('\\'.join(paths[:i])) | 36 prev = len('\\'.join(paths[:i])) |
| 36 # glob.glob will return the cased path for the last item only. This is why | 37 # glob.glob will return the cased path for the last item only. This is why |
| 37 # we are calling it in a loop. Extract the data we want and put it back | 38 # we are calling it in a loop. Extract the data we want and put it back |
| 38 # into the list. | 39 # into the list. |
| 39 paths[i] = glob.glob(subpath + '*')[0][prev+1:len(subpath)] | 40 paths[i] = glob.glob(subpath + '*')[0][prev+1:len(subpath)] |
| 40 path = '\\'.join(paths) | 41 path = '\\'.join(paths) |
| 41 return path | 42 return path |
| 42 | 43 |
| 43 | 44 |
| 45 def GenFakeDiff(filename): |
| 46 """Generates a fake diff from a file.""" |
| 47 file_content = gclient_utils.FileRead(filename, 'rb').splitlines(True) |
| 48 nb_lines = len(file_content) |
| 49 # We need to use / since patch on unix will fail otherwise. |
| 50 data = cStringIO.StringIO() |
| 51 data.write("Index: %s\n" % filename) |
| 52 data.write('=' * 67 + '\n') |
| 53 # Note: Should we use /dev/null instead? |
| 54 data.write("--- %s\n" % filename) |
| 55 data.write("+++ %s\n" % filename) |
| 56 data.write("@@ -0,0 +1,%d @@\n" % nb_lines) |
| 57 # Prepend '+' to every lines. |
| 58 for line in file_content: |
| 59 data.write('+') |
| 60 data.write(line) |
| 61 result = data.getvalue() |
| 62 data.close() |
| 63 return result |
| 64 |
| 65 |
| 44 class GIT(object): | 66 class GIT(object): |
| 45 COMMAND = "git" | 67 COMMAND = "git" |
| 46 | 68 |
| 47 @staticmethod | 69 @staticmethod |
| 48 def Capture(args, in_directory=None, print_error=True, error_ok=False): | 70 def Capture(args, in_directory=None, print_error=True, error_ok=False): |
| 49 """Runs git, capturing output sent to stdout as a string. | 71 """Runs git, capturing output sent to stdout as a string. |
| 50 | 72 |
| 51 Args: | 73 Args: |
| 52 args: A sequence of command line parameters to be passed to git. | 74 args: A sequence of command line parameters to be passed to git. |
| 53 in_directory: The directory where git is to be run. | 75 in_directory: The directory where git is to be run. |
| (...skipping 548 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 602 if wc_status[0].getAttribute('switched') == 'true': | 624 if wc_status[0].getAttribute('switched') == 'true': |
| 603 statuses[4] = 'S' | 625 statuses[4] = 'S' |
| 604 # TODO(maruel): Col 5 and 6 | 626 # TODO(maruel): Col 5 and 6 |
| 605 item = (''.join(statuses), file_path) | 627 item = (''.join(statuses), file_path) |
| 606 results.append(item) | 628 results.append(item) |
| 607 return results | 629 return results |
| 608 | 630 |
| 609 @staticmethod | 631 @staticmethod |
| 610 def IsMoved(filename): | 632 def IsMoved(filename): |
| 611 """Determine if a file has been added through svn mv""" | 633 """Determine if a file has been added through svn mv""" |
| 612 info = SVN.CaptureInfo(filename) | 634 return SVN.IsMovedInfo(SVN.CaptureInfo(filename)) |
| 635 |
| 636 @staticmethod |
| 637 def IsMovedInfo(info): |
| 638 """Determine if a file has been added through svn mv""" |
| 613 return (info.get('Copied From URL') and | 639 return (info.get('Copied From URL') and |
| 614 info.get('Copied From Rev') and | 640 info.get('Copied From Rev') and |
| 615 info.get('Schedule') == 'add') | 641 info.get('Schedule') == 'add') |
| 616 | 642 |
| 617 @staticmethod | 643 @staticmethod |
| 618 def GetFileProperty(file, property_name): | 644 def GetFileProperty(file, property_name): |
| 619 """Returns the value of an SVN property for the given file. | 645 """Returns the value of an SVN property for the given file. |
| 620 | 646 |
| 621 Args: | 647 Args: |
| 622 file: The file to check | 648 file: The file to check |
| 623 property_name: The name of the SVN property, e.g. "svn:mime-type" | 649 property_name: The name of the SVN property, e.g. "svn:mime-type" |
| 624 | 650 |
| 625 Returns: | 651 Returns: |
| 626 The value of the property, which will be the empty string if the property | 652 The value of the property, which will be the empty string if the property |
| 627 is not set on the file. If the file is not under version control, the | 653 is not set on the file. If the file is not under version control, the |
| 628 empty string is also returned. | 654 empty string is also returned. |
| 629 """ | 655 """ |
| 630 output = SVN.Capture(["propget", property_name, file]) | 656 output = SVN.Capture(["propget", property_name, file]) |
| 631 if (output.startswith("svn: ") and | 657 if (output.startswith("svn: ") and |
| 632 output.endswith("is not under version control")): | 658 output.endswith("is not under version control")): |
| 633 return "" | 659 return "" |
| 634 else: | 660 else: |
| 635 return output | 661 return output |
| 636 | 662 |
| 637 @staticmethod | 663 @staticmethod |
| 638 def DiffItem(filename, full_move=False, revision=None): | 664 def DiffItem(filename, full_move=False, revision=None): |
| 639 """Diffs a single file. | 665 """Diffs a single file. |
| 640 | 666 |
| 667 Should be simple, eh? No it isn't. |
| 641 Be sure to be in the appropriate directory before calling to have the | 668 Be sure to be in the appropriate directory before calling to have the |
| 642 expected relative path. | 669 expected relative path. |
| 643 full_move means that move or copy operations should completely recreate the | 670 full_move means that move or copy operations should completely recreate the |
| 644 files, usually in the prospect to apply the patch for a try job.""" | 671 files, usually in the prospect to apply the patch for a try job.""" |
| 645 # Use svn info output instead of os.path.isdir because the latter fails | |
| 646 # when the file is deleted. | |
| 647 if SVN.CaptureInfo(filename).get("Node Kind") == "directory": | |
| 648 return None | |
| 649 # If the user specified a custom diff command in their svn config file, | 672 # If the user specified a custom diff command in their svn config file, |
| 650 # then it'll be used when we do svn diff, which we don't want to happen | 673 # then it'll be used when we do svn diff, which we don't want to happen |
| 651 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 674 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
| 652 # work, since they can have another diff executable in their path that | 675 # work, since they can have another diff executable in their path that |
| 653 # gives different line endings. So we use a bogus temp directory as the | 676 # gives different line endings. So we use a bogus temp directory as the |
| 654 # config directory, which gets around these problems. | 677 # config directory, which gets around these problems. |
| 655 bogus_dir = tempfile.mkdtemp() | 678 bogus_dir = tempfile.mkdtemp() |
| 656 try: | 679 try: |
| 657 # Grabs the diff data. | 680 # Use "svn info" output instead of os.path.isdir because the latter fails |
| 658 command = ["diff", "--config-dir", bogus_dir, filename] | 681 # when the file is deleted. |
| 659 if revision: | 682 return SVN._DiffItemInternal(SVN.CaptureInfo(filename), |
| 660 command.extend(['--revision', revision]) | 683 full_move=full_move, revision=revision) |
| 661 if SVN.IsMoved(filename): | 684 finally: |
| 662 if full_move: | 685 shutil.rmtree(bogus_dir) |
| 663 file_content = gclient_utils.FileRead(filename, 'rb') | 686 |
| 664 # Prepend '+' to every lines. | 687 @staticmethod |
| 665 file_content = ['+' + i for i in file_content.splitlines(True)] | 688 def _DiffItemInternal(filename, info, bogus_dir, full_move=False, |
| 666 nb_lines = len(file_content) | 689 revision=None): |
| 667 # We need to use / since patch on unix will fail otherwise. | 690 """Grabs the diff data.""" |
| 668 data = "Index: %s\n" % filename | 691 command = ["diff", "--config-dir", bogus_dir, filename] |
| 669 data += '=' * 67 + '\n' | 692 if revision: |
| 670 # Note: Should we use /dev/null instead? | 693 command.extend(['--revision', revision]) |
| 671 data += "--- %s\n" % filename | 694 data = None |
| 672 data += "+++ %s\n" % filename | 695 if SVN.IsMovedInfo(info): |
| 673 data += "@@ -0,0 +1,%d @@\n" % nb_lines | 696 if full_move: |
| 674 data += ''.join(file_content) | 697 if info.get("Node Kind") == "directory": |
| 698 # Things become tricky here. It's a directory copy/move. We need to |
| 699 # diff all the files inside it. |
| 700 # This will put a lot of pressure on the heap. This is why StringIO |
| 701 # is used and converted back into a string at the end. The reason to |
| 702 # return a string instead of a StringIO is that StringIO.write() |
| 703 # doesn't accept a StringIO object. *sigh*. |
| 704 for (dirpath, dirnames, filenames) in os.walk(filename): |
| 705 # Cleanup all files starting with a '.'. |
| 706 for d in dirnames: |
| 707 if d.startswith('.'): |
| 708 dirnames.remove(d) |
| 709 for f in filenames: |
| 710 if f.startswith('.'): |
| 711 filenames.remove(f) |
| 712 for f in filenames: |
| 713 if data is None: |
| 714 data = cStringIO.StringIO() |
| 715 data.write(GenFakeDiff(os.path.join(dirpath, f))) |
| 716 if data: |
| 717 tmp = data.getvalue() |
| 718 data.close() |
| 719 data = tmp |
| 675 else: | 720 else: |
| 721 data = GenFakeDiff(filename) |
| 722 else: |
| 723 if info.get("Node Kind") != "directory": |
| 676 # svn diff on a mv/cp'd file outputs nothing if there was no change. | 724 # svn diff on a mv/cp'd file outputs nothing if there was no change. |
| 677 data = SVN.Capture(command, None) | 725 data = SVN.Capture(command, None) |
| 678 if not data: | 726 if not data: |
| 679 # We put in an empty Index entry so upload.py knows about them. | 727 # We put in an empty Index entry so upload.py knows about them. |
| 680 data = "Index: %s\n" % filename | 728 data = "Index: %s\n" % filename |
| 681 else: | 729 # Otherwise silently ignore directories. |
| 730 else: |
| 731 if info.get("Node Kind") != "directory": |
| 732 # Normal simple case. |
| 682 data = SVN.Capture(command, None) | 733 data = SVN.Capture(command, None) |
| 683 finally: | 734 # Otherwise silently ignore directories. |
| 684 shutil.rmtree(bogus_dir) | |
| 685 return data | 735 return data |
| 686 | 736 |
| 687 @staticmethod | 737 @staticmethod |
| 688 def GenerateDiff(filenames, root=None, full_move=False, revision=None): | 738 def GenerateDiff(filenames, root=None, full_move=False, revision=None): |
| 689 """Returns a string containing the diff for the given file list. | 739 """Returns a string containing the diff for the given file list. |
| 690 | 740 |
| 691 The files in the list should either be absolute paths or relative to the | 741 The files in the list should either be absolute paths or relative to the |
| 692 given root. If no root directory is provided, the repository root will be | 742 given root. If no root directory is provided, the repository root will be |
| 693 used. | 743 used. |
| 694 The diff will always use relative paths. | 744 The diff will always use relative paths. |
| 695 """ | 745 """ |
| 696 previous_cwd = os.getcwd() | 746 previous_cwd = os.getcwd() |
| 697 root = root or SVN.GetCheckoutRoot(previous_cwd) | 747 root = root or SVN.GetCheckoutRoot(previous_cwd) |
| 698 root = os.path.normcase(os.path.join(root, '')) | 748 root = os.path.normcase(os.path.join(root, '')) |
| 699 def RelativePath(path, root): | 749 def RelativePath(path, root): |
| 700 """We must use relative paths.""" | 750 """We must use relative paths.""" |
| 701 if os.path.normcase(path).startswith(root): | 751 if os.path.normcase(path).startswith(root): |
| 702 return path[len(root):] | 752 return path[len(root):] |
| 703 return path | 753 return path |
| 754 # If the user specified a custom diff command in their svn config file, |
| 755 # then it'll be used when we do svn diff, which we don't want to happen |
| 756 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
| 757 # work, since they can have another diff executable in their path that |
| 758 # gives different line endings. So we use a bogus temp directory as the |
| 759 # config directory, which gets around these problems. |
| 760 bogus_dir = tempfile.mkdtemp() |
| 704 try: | 761 try: |
| 705 os.chdir(root) | 762 os.chdir(root) |
| 706 diff = "".join(filter(None, | 763 # Cleanup filenames |
| 707 [SVN.DiffItem(RelativePath(f, root), | 764 filenames = [RelativePath(f, root) for f in filenames] |
| 708 full_move=full_move, | 765 # Get information about the modified items (files and directories) |
| 709 revision=revision) | 766 data = dict([(f, SVN.CaptureInfo(f)) for f in filenames]) |
| 710 for f in filenames])) | 767 if full_move: |
| 768 # Eliminate modified files inside moved/copied directory. |
| 769 for (filename, info) in data.iteritems(): |
| 770 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": |
| 771 # Remove files inside the directory. |
| 772 filenames = [f for f in filenames |
| 773 if not f.startswith(filename + os.path.sep)] |
| 774 for filename in data.keys(): |
| 775 if not filename in filenames: |
| 776 # Remove filtered out items. |
| 777 del data[filename] |
| 778 # Now ready to do the actual diff. |
| 779 diffs = [] |
| 780 for filename in sorted(data.iterkeys()): |
| 781 diffs.append(SVN._DiffItemInternal(filename, data[filename], bogus_dir, |
| 782 full_move=full_move, |
| 783 revision=revision)) |
| 784 # Use StringIO since it can be messy when diffing a directory move with |
| 785 # full_move=True. |
| 786 buf = cStringIO.StringIO() |
| 787 for d in filter(None, diffs): |
| 788 buf.write(d) |
| 789 result = buf.getvalue() |
| 790 buf.close() |
| 791 return result |
| 711 finally: | 792 finally: |
| 712 os.chdir(previous_cwd) | 793 os.chdir(previous_cwd) |
| 713 return diff | 794 shutil.rmtree(bogus_dir) |
| 714 | |
| 715 | 795 |
| 716 @staticmethod | 796 @staticmethod |
| 717 def GetEmail(repo_root): | 797 def GetEmail(repo_root): |
| 718 """Retrieves the svn account which we assume is an email address.""" | 798 """Retrieves the svn account which we assume is an email address.""" |
| 719 infos = SVN.CaptureInfo(repo_root) | 799 infos = SVN.CaptureInfo(repo_root) |
| 720 uuid = infos.get('UUID') | 800 uuid = infos.get('UUID') |
| 721 root = infos.get('Repository Root') | 801 root = infos.get('Repository Root') |
| 722 if not root: | 802 if not root: |
| 723 return None | 803 return None |
| 724 | 804 |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 799 if not SVN.current_version: | 879 if not SVN.current_version: |
| 800 SVN.current_version = SVN.Capture(['--version']).split()[2] | 880 SVN.current_version = SVN.Capture(['--version']).split()[2] |
| 801 current_version_list = map(only_int, SVN.current_version.split('.')) | 881 current_version_list = map(only_int, SVN.current_version.split('.')) |
| 802 for min_ver in map(int, min_version.split('.')): | 882 for min_ver in map(int, min_version.split('.')): |
| 803 ver = current_version_list.pop(0) | 883 ver = current_version_list.pop(0) |
| 804 if ver < min_ver: | 884 if ver < min_ver: |
| 805 return (False, SVN.current_version) | 885 return (False, SVN.current_version) |
| 806 elif ver > min_ver: | 886 elif ver > min_ver: |
| 807 return (True, SVN.current_version) | 887 return (True, SVN.current_version) |
| 808 return (True, SVN.current_version) | 888 return (True, SVN.current_version) |
| OLD | NEW |