OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 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 754 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
765 The value of the property, which will be the empty string if the property | 765 The value of the property, which will be the empty string if the property |
766 is not set on the file. If the file is not under version control, the | 766 is not set on the file. If the file is not under version control, the |
767 empty string is also returned. | 767 empty string is also returned. |
768 """ | 768 """ |
769 try: | 769 try: |
770 return SVN.Capture(['propget', property_name, filename], cwd) | 770 return SVN.Capture(['propget', property_name, filename], cwd) |
771 except subprocess2.CalledProcessError: | 771 except subprocess2.CalledProcessError: |
772 return '' | 772 return '' |
773 | 773 |
774 @staticmethod | 774 @staticmethod |
775 def DiffItem(filename, cwd, full_move, revision): | 775 def GenerateDiff(filenames, cwd, full_move, revision): |
776 """Diffs a single file. | 776 """Returns a string containing the diff for the given file list. |
777 | 777 |
778 Should be simple, eh? No it isn't. | 778 The files in the list should either be absolute paths or relative to the |
779 Be sure to be in the appropriate directory before calling to have the | 779 given root. If no root directory is provided, the repository root will be |
780 expected relative path. | 780 used. |
781 full_move means that move or copy operations should completely recreate the | 781 The diff will always use relative paths. |
782 files, usually in the prospect to apply the patch for a try job.""" | 782 """ |
783 assert isinstance(filenames, (list, tuple)) | |
783 # If the user specified a custom diff command in their svn config file, | 784 # If the user specified a custom diff command in their svn config file, |
784 # then it'll be used when we do svn diff, which we don't want to happen | 785 # then it'll be used when we do svn diff, which we don't want to happen |
785 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 786 # since we want the unified diff. On svn >= 1.7, the "--internal-diff" flag |
M-A Ruel
2013/04/19 16:17:08
Could you split the comments, move the svn >= 1.7
| |
786 # work, since they can have another diff executable in their path that | 787 # will solve this. On svn < 1.7, however, this flag doesn't exist. Using |
787 # gives different line endings. So we use a bogus temp directory as the | 788 # --diff-cmd=diff doesn't always work, since some users (e.g. Windows cmd |
788 # config directory, which gets around these problems. | 789 # users) may have no diff executable in their paths at all, and even people |
789 bogus_dir = tempfile.mkdtemp() | 790 # who do can have a diff executable in their path that uses unexpected line |
790 try: | 791 # endings. So we use a bogus temp directory as the config directory, which |
791 # Use "svn info" output instead of os.path.isdir because the latter fails | 792 # bypasses any user settings for the diff-cmd. |
792 # when the file is deleted. | 793 if SVN.AssertVersion("1.7")[0]: |
793 return SVN._DiffItemInternal( | 794 return SVN._GenerateDiffInternal(filenames, cwd, full_move, revision, |
794 filename, | 795 ["diff", "--internal-diff"]) |
795 cwd, | 796 else: |
796 SVN.CaptureLocalInfo([filename], cwd), | 797 bogus_dir = tempfile.mkdtemp() |
797 bogus_dir, | 798 try: |
798 full_move, | 799 return SVN._GenerateDiffInternal(filenames, cwd, full_move, revision, |
799 revision) | 800 ["diff", "--config_dir", bogus_dir]) |
800 finally: | 801 finally: |
801 gclient_utils.RemoveDirectory(bogus_dir) | 802 gclient_utils.RemoveDirectory(bogus_dir) |
802 | 803 |
803 @staticmethod | 804 @staticmethod |
804 def _DiffItemInternal(filename, cwd, info, bogus_dir, full_move, revision): | 805 def _GenerateDiffInternal(filenames, cwd, full_move, revision, diff_command): |
806 root = os.path.normcase(os.path.join(cwd, '')) | |
807 def RelativePath(path, root): | |
808 """We must use relative paths.""" | |
809 if os.path.normcase(path).startswith(root): | |
810 return path[len(root):] | |
811 return path | |
812 # Cleanup filenames | |
813 filenames = [RelativePath(f, root) for f in filenames] | |
814 # Get information about the modified items (files and directories) | |
815 data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) | |
M-A Ruel
2013/04/19 16:17:08
data = dict((f, SVN.CaptureLocalInfo([f], root)) f
| |
816 diffs = [] | |
817 if full_move: | |
818 # Eliminate modified files inside moved/copied directory. | |
819 for (filename, info) in data.iteritems(): | |
820 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": | |
821 # Remove files inside the directory. | |
822 filenames = [f for f in filenames | |
823 if not f.startswith(filename + os.path.sep)] | |
824 for filename in data.keys(): | |
825 if not filename in filenames: | |
826 # Remove filtered out items. | |
827 del data[filename] | |
828 else: | |
829 metaheaders = [] | |
830 for (filename, info) in data.iteritems(): | |
831 if SVN.IsMovedInfo(info): | |
832 # for now, the most common case is a head copy, | |
833 # so let's just encode that as a straight up cp. | |
834 srcurl = info.get('Copied From URL') | |
835 file_root = info.get('Repository Root') | |
836 rev = int(info.get('Copied From Rev')) | |
837 assert srcurl.startswith(file_root) | |
838 src = srcurl[len(file_root)+1:] | |
839 try: | |
840 srcinfo = SVN.CaptureRemoteInfo(srcurl) | |
841 except subprocess2.CalledProcessError, e: | |
842 if not 'Not a valid URL' in e.stderr: | |
843 raise | |
844 # Assume the file was deleted. No idea how to figure out at which | |
845 # revision the file was deleted. | |
846 srcinfo = {'Revision': rev} | |
847 if (srcinfo.get('Revision') != rev and | |
848 SVN.Capture(diff_command + ['-r', '%d:head' % rev, srcurl], cwd)): | |
849 metaheaders.append("#$ svn cp -r %d %s %s " | |
850 "### WARNING: note non-trunk copy\n" % | |
851 (rev, src, filename)) | |
852 else: | |
853 metaheaders.append("#$ cp %s %s\n" % (src, | |
854 filename)) | |
855 if metaheaders: | |
856 diffs.append("### BEGIN SVN COPY METADATA\n") | |
857 diffs.extend(metaheaders) | |
858 diffs.append("### END SVN COPY METADATA\n") | |
859 # Now ready to do the actual diff. | |
860 for filename in sorted(data.iterkeys()): | |
M-A Ruel
2013/04/19 16:17:08
for filename in sorted(data):
| |
861 diffs.append(SVN._DiffItemInternal( | |
862 filename, cwd, data[filename], diff_command, full_move, revision)) | |
863 # Use StringIO since it can be messy when diffing a directory move with | |
864 # full_move=True. | |
865 buf = cStringIO.StringIO() | |
866 for d in filter(None, diffs): | |
867 buf.write(d) | |
868 result = buf.getvalue() | |
869 buf.close() | |
870 return result | |
871 | |
872 @staticmethod | |
873 def _DiffItemInternal(filename, cwd, info, diff_command, full_move, revision): | |
805 """Grabs the diff data.""" | 874 """Grabs the diff data.""" |
806 command = ["diff", "--config-dir", bogus_dir, filename] | 875 diff_command.append(filename) |
807 if revision: | 876 if revision: |
808 command.extend(['--revision', revision]) | 877 diff_command.extend(['--revision', revision]) |
809 data = None | 878 data = None |
810 if SVN.IsMovedInfo(info): | 879 if SVN.IsMovedInfo(info): |
811 if full_move: | 880 if full_move: |
812 if info.get("Node Kind") == "directory": | 881 if info.get("Node Kind") == "directory": |
813 # Things become tricky here. It's a directory copy/move. We need to | 882 # Things become tricky here. It's a directory copy/move. We need to |
814 # diff all the files inside it. | 883 # diff all the files inside it. |
815 # This will put a lot of pressure on the heap. This is why StringIO | 884 # This will put a lot of pressure on the heap. This is why StringIO |
816 # is used and converted back into a string at the end. The reason to | 885 # is used and converted back into a string at the end. The reason to |
817 # return a string instead of a StringIO is that StringIO.write() | 886 # return a string instead of a StringIO is that StringIO.write() |
818 # doesn't accept a StringIO object. *sigh*. | 887 # doesn't accept a StringIO object. *sigh*. |
(...skipping 11 matching lines...) Expand all Loading... | |
830 data.write(GenFakeDiff(os.path.join(dirpath, f))) | 899 data.write(GenFakeDiff(os.path.join(dirpath, f))) |
831 if data: | 900 if data: |
832 tmp = data.getvalue() | 901 tmp = data.getvalue() |
833 data.close() | 902 data.close() |
834 data = tmp | 903 data = tmp |
835 else: | 904 else: |
836 data = GenFakeDiff(filename) | 905 data = GenFakeDiff(filename) |
837 else: | 906 else: |
838 if info.get("Node Kind") != "directory": | 907 if info.get("Node Kind") != "directory": |
839 # svn diff on a mv/cp'd file outputs nothing if there was no change. | 908 # svn diff on a mv/cp'd file outputs nothing if there was no change. |
840 data = SVN.Capture(command, cwd) | 909 data = SVN.Capture(diff_command, cwd) |
841 if not data: | 910 if not data: |
842 # We put in an empty Index entry so upload.py knows about them. | 911 # We put in an empty Index entry so upload.py knows about them. |
843 data = "Index: %s\n" % filename.replace(os.sep, '/') | 912 data = "Index: %s\n" % filename.replace(os.sep, '/') |
844 # Otherwise silently ignore directories. | 913 # Otherwise silently ignore directories. |
845 else: | 914 else: |
846 if info.get("Node Kind") != "directory": | 915 if info.get("Node Kind") != "directory": |
847 # Normal simple case. | 916 # Normal simple case. |
848 try: | 917 try: |
849 data = SVN.Capture(command, cwd) | 918 data = SVN.Capture(diff_command, cwd) |
850 except subprocess2.CalledProcessError: | 919 except subprocess2.CalledProcessError: |
851 if revision: | 920 if revision: |
852 data = GenFakeDiff(filename) | 921 data = GenFakeDiff(filename) |
853 else: | 922 else: |
854 raise | 923 raise |
855 # Otherwise silently ignore directories. | 924 # Otherwise silently ignore directories. |
856 return data | 925 return data |
857 | 926 |
858 @staticmethod | 927 @staticmethod |
859 def GenerateDiff(filenames, cwd, full_move, revision): | |
860 """Returns a string containing the diff for the given file list. | |
861 | |
862 The files in the list should either be absolute paths or relative to the | |
863 given root. If no root directory is provided, the repository root will be | |
864 used. | |
865 The diff will always use relative paths. | |
866 """ | |
867 assert isinstance(filenames, (list, tuple)) | |
868 root = os.path.normcase(os.path.join(cwd, '')) | |
869 def RelativePath(path, root): | |
870 """We must use relative paths.""" | |
871 if os.path.normcase(path).startswith(root): | |
872 return path[len(root):] | |
873 return path | |
874 # If the user specified a custom diff command in their svn config file, | |
875 # then it'll be used when we do svn diff, which we don't want to happen | |
876 # since we want the unified diff. Using --diff-cmd=diff doesn't always | |
877 # work, since they can have another diff executable in their path that | |
878 # gives different line endings. So we use a bogus temp directory as the | |
879 # config directory, which gets around these problems. | |
880 bogus_dir = tempfile.mkdtemp() | |
881 try: | |
882 # Cleanup filenames | |
883 filenames = [RelativePath(f, root) for f in filenames] | |
884 # Get information about the modified items (files and directories) | |
885 data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) | |
886 diffs = [] | |
887 if full_move: | |
888 # Eliminate modified files inside moved/copied directory. | |
889 for (filename, info) in data.iteritems(): | |
890 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": | |
891 # Remove files inside the directory. | |
892 filenames = [f for f in filenames | |
893 if not f.startswith(filename + os.path.sep)] | |
894 for filename in data.keys(): | |
895 if not filename in filenames: | |
896 # Remove filtered out items. | |
897 del data[filename] | |
898 else: | |
899 metaheaders = [] | |
900 for (filename, info) in data.iteritems(): | |
901 if SVN.IsMovedInfo(info): | |
902 # for now, the most common case is a head copy, | |
903 # so let's just encode that as a straight up cp. | |
904 srcurl = info.get('Copied From URL') | |
905 file_root = info.get('Repository Root') | |
906 rev = int(info.get('Copied From Rev')) | |
907 assert srcurl.startswith(file_root) | |
908 src = srcurl[len(file_root)+1:] | |
909 try: | |
910 srcinfo = SVN.CaptureRemoteInfo(srcurl) | |
911 except subprocess2.CalledProcessError, e: | |
912 if not 'Not a valid URL' in e.stderr: | |
913 raise | |
914 # Assume the file was deleted. No idea how to figure out at which | |
915 # revision the file was deleted. | |
916 srcinfo = {'Revision': rev} | |
917 if (srcinfo.get('Revision') != rev and | |
918 SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl], cwd)): | |
919 metaheaders.append("#$ svn cp -r %d %s %s " | |
920 "### WARNING: note non-trunk copy\n" % | |
921 (rev, src, filename)) | |
922 else: | |
923 metaheaders.append("#$ cp %s %s\n" % (src, | |
924 filename)) | |
925 | |
926 if metaheaders: | |
927 diffs.append("### BEGIN SVN COPY METADATA\n") | |
928 diffs.extend(metaheaders) | |
929 diffs.append("### END SVN COPY METADATA\n") | |
930 # Now ready to do the actual diff. | |
931 for filename in sorted(data.iterkeys()): | |
932 diffs.append(SVN._DiffItemInternal( | |
933 filename, cwd, data[filename], bogus_dir, full_move, revision)) | |
934 # Use StringIO since it can be messy when diffing a directory move with | |
935 # full_move=True. | |
936 buf = cStringIO.StringIO() | |
937 for d in filter(None, diffs): | |
938 buf.write(d) | |
939 result = buf.getvalue() | |
940 buf.close() | |
941 return result | |
942 finally: | |
943 gclient_utils.RemoveDirectory(bogus_dir) | |
944 | |
945 @staticmethod | |
946 def GetEmail(cwd): | 928 def GetEmail(cwd): |
947 """Retrieves the svn account which we assume is an email address.""" | 929 """Retrieves the svn account which we assume is an email address.""" |
948 try: | 930 try: |
949 infos = SVN.CaptureLocalInfo([], cwd) | 931 infos = SVN.CaptureLocalInfo([], cwd) |
950 except subprocess2.CalledProcessError: | 932 except subprocess2.CalledProcessError: |
951 return None | 933 return None |
952 | 934 |
953 # Should check for uuid but it is incorrectly saved for https creds. | 935 # Should check for uuid but it is incorrectly saved for https creds. |
954 root = infos['Repository Root'] | 936 root = infos['Repository Root'] |
955 realm = root.rsplit('/', 1)[0] | 937 realm = root.rsplit('/', 1)[0] |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1096 # revert, like for properties. | 1078 # revert, like for properties. |
1097 if not os.path.isdir(cwd): | 1079 if not os.path.isdir(cwd): |
1098 # '.' was deleted. It's not worth continuing. | 1080 # '.' was deleted. It's not worth continuing. |
1099 return | 1081 return |
1100 try: | 1082 try: |
1101 SVN.Capture(['revert', file_status[1]], cwd=cwd) | 1083 SVN.Capture(['revert', file_status[1]], cwd=cwd) |
1102 except subprocess2.CalledProcessError: | 1084 except subprocess2.CalledProcessError: |
1103 if not os.path.exists(file_path): | 1085 if not os.path.exists(file_path): |
1104 continue | 1086 continue |
1105 raise | 1087 raise |
OLD | NEW |