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 |
11 import re | 11 import re |
12 import sys | 12 import sys |
13 import tempfile | |
14 import time | 13 import time |
15 from xml.etree import ElementTree | 14 from xml.etree import ElementTree |
16 | 15 |
17 import gclient_utils | 16 import gclient_utils |
18 import subprocess2 | 17 import subprocess2 |
19 | 18 |
20 | 19 |
21 def ValidateEmail(email): | 20 def ValidateEmail(email): |
22 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) | 21 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) |
23 is not None) | 22 is not None) |
(...skipping 749 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
773 | 772 |
774 @staticmethod | 773 @staticmethod |
775 def DiffItem(filename, cwd, full_move, revision): | 774 def DiffItem(filename, cwd, full_move, revision): |
776 """Diffs a single file. | 775 """Diffs a single file. |
777 | 776 |
778 Should be simple, eh? No it isn't. | 777 Should be simple, eh? No it isn't. |
779 Be sure to be in the appropriate directory before calling to have the | 778 Be sure to be in the appropriate directory before calling to have the |
780 expected relative path. | 779 expected relative path. |
781 full_move means that move or copy operations should completely recreate the | 780 full_move means that move or copy operations should completely recreate the |
782 files, usually in the prospect to apply the patch for a try job.""" | 781 files, usually in the prospect to apply the patch for a try job.""" |
783 # If the user specified a custom diff command in their svn config file, | 782 # Use "svn info" output instead of os.path.isdir because the latter fails |
784 # then it'll be used when we do svn diff, which we don't want to happen | 783 # when the file is deleted. |
785 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 784 return SVN._DiffItemInternal( |
786 # work, since they can have another diff executable in their path that | 785 filename, |
787 # gives different line endings. So we use a bogus temp directory as the | 786 cwd, |
788 # config directory, which gets around these problems. | 787 SVN.CaptureLocalInfo([filename], cwd), |
789 bogus_dir = tempfile.mkdtemp() | 788 full_move, |
790 try: | 789 revision) |
791 # Use "svn info" output instead of os.path.isdir because the latter fails | |
792 # when the file is deleted. | |
793 return SVN._DiffItemInternal( | |
794 filename, | |
795 cwd, | |
796 SVN.CaptureLocalInfo([filename], cwd), | |
797 bogus_dir, | |
798 full_move, | |
799 revision) | |
800 finally: | |
801 gclient_utils.RemoveDirectory(bogus_dir) | |
802 | 790 |
803 @staticmethod | 791 @staticmethod |
804 def _DiffItemInternal(filename, cwd, info, bogus_dir, full_move, revision): | 792 def _DiffItemInternal(filename, cwd, info, full_move, revision): |
805 """Grabs the diff data.""" | 793 """Grabs the diff data.""" |
806 command = ["diff", "--config-dir", bogus_dir, filename] | 794 command = ["diff", "--internal-diff", filename] |
807 if revision: | 795 if revision: |
808 command.extend(['--revision', revision]) | 796 command.extend(['--revision', revision]) |
809 data = None | 797 data = None |
810 if SVN.IsMovedInfo(info): | 798 if SVN.IsMovedInfo(info): |
811 if full_move: | 799 if full_move: |
812 if info.get("Node Kind") == "directory": | 800 if info.get("Node Kind") == "directory": |
813 # Things become tricky here. It's a directory copy/move. We need to | 801 # Things become tricky here. It's a directory copy/move. We need to |
814 # diff all the files inside it. | 802 # diff all the files inside it. |
815 # This will put a lot of pressure on the heap. This is why StringIO | 803 # 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 | 804 # is used and converted back into a string at the end. The reason to |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
864 used. | 852 used. |
865 The diff will always use relative paths. | 853 The diff will always use relative paths. |
866 """ | 854 """ |
867 assert isinstance(filenames, (list, tuple)) | 855 assert isinstance(filenames, (list, tuple)) |
868 root = os.path.normcase(os.path.join(cwd, '')) | 856 root = os.path.normcase(os.path.join(cwd, '')) |
869 def RelativePath(path, root): | 857 def RelativePath(path, root): |
870 """We must use relative paths.""" | 858 """We must use relative paths.""" |
871 if os.path.normcase(path).startswith(root): | 859 if os.path.normcase(path).startswith(root): |
872 return path[len(root):] | 860 return path[len(root):] |
873 return path | 861 return path |
874 # If the user specified a custom diff command in their svn config file, | 862 # Cleanup filenames |
875 # then it'll be used when we do svn diff, which we don't want to happen | 863 filenames = [RelativePath(f, root) for f in filenames] |
876 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 864 # Get information about the modified items (files and directories) |
877 # work, since they can have another diff executable in their path that | 865 data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) |
878 # gives different line endings. So we use a bogus temp directory as the | 866 diffs = [] |
879 # config directory, which gets around these problems. | 867 if full_move: |
880 bogus_dir = tempfile.mkdtemp() | 868 # Eliminate modified files inside moved/copied directory. |
881 try: | 869 for (filename, info) in data.iteritems(): |
882 # Cleanup filenames | 870 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": |
883 filenames = [RelativePath(f, root) for f in filenames] | 871 # Remove files inside the directory. |
884 # Get information about the modified items (files and directories) | 872 filenames = [f for f in filenames |
885 data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) | 873 if not f.startswith(filename + os.path.sep)] |
886 diffs = [] | 874 for filename in data.keys(): |
887 if full_move: | 875 if not filename in filenames: |
888 # Eliminate modified files inside moved/copied directory. | 876 # Remove filtered out items. |
889 for (filename, info) in data.iteritems(): | 877 del data[filename] |
890 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": | 878 else: |
891 # Remove files inside the directory. | 879 metaheaders = [] |
892 filenames = [f for f in filenames | 880 for (filename, info) in data.iteritems(): |
893 if not f.startswith(filename + os.path.sep)] | 881 if SVN.IsMovedInfo(info): |
894 for filename in data.keys(): | 882 # for now, the most common case is a head copy, |
895 if not filename in filenames: | 883 # so let's just encode that as a straight up cp. |
896 # Remove filtered out items. | 884 srcurl = info.get('Copied From URL') |
897 del data[filename] | 885 file_root = info.get('Repository Root') |
898 else: | 886 rev = int(info.get('Copied From Rev')) |
899 metaheaders = [] | 887 assert srcurl.startswith(file_root) |
900 for (filename, info) in data.iteritems(): | 888 src = srcurl[len(file_root)+1:] |
901 if SVN.IsMovedInfo(info): | 889 try: |
902 # for now, the most common case is a head copy, | 890 srcinfo = SVN.CaptureRemoteInfo(srcurl) |
903 # so let's just encode that as a straight up cp. | 891 except subprocess2.CalledProcessError, e: |
904 srcurl = info.get('Copied From URL') | 892 if not 'Not a valid URL' in e.stderr: |
905 file_root = info.get('Repository Root') | 893 raise |
906 rev = int(info.get('Copied From Rev')) | 894 # Assume the file was deleted. No idea how to figure out at which |
907 assert srcurl.startswith(file_root) | 895 # revision the file was deleted. |
908 src = srcurl[len(file_root)+1:] | 896 srcinfo = {'Revision': rev} |
909 try: | 897 if (srcinfo.get('Revision') != rev and |
910 srcinfo = SVN.CaptureRemoteInfo(srcurl) | 898 SVN.Capture(['diff', '--internal-diff', '-r', '%d:head' % rev, |
911 except subprocess2.CalledProcessError, e: | 899 srcurl], cwd)): |
912 if not 'Not a valid URL' in e.stderr: | 900 metaheaders.append("#$ svn cp -r %d %s %s " |
913 raise | 901 "### WARNING: note non-trunk copy\n" % |
914 # Assume the file was deleted. No idea how to figure out at which | 902 (rev, src, filename)) |
915 # revision the file was deleted. | 903 else: |
916 srcinfo = {'Revision': rev} | 904 metaheaders.append("#$ cp %s %s\n" % (src, filename)) |
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 | 905 |
926 if metaheaders: | 906 if metaheaders: |
927 diffs.append("### BEGIN SVN COPY METADATA\n") | 907 diffs.append("### BEGIN SVN COPY METADATA\n") |
928 diffs.extend(metaheaders) | 908 diffs.extend(metaheaders) |
929 diffs.append("### END SVN COPY METADATA\n") | 909 diffs.append("### END SVN COPY METADATA\n") |
930 # Now ready to do the actual diff. | 910 # Now ready to do the actual diff. |
931 for filename in sorted(data.iterkeys()): | 911 for filename in sorted(data.iterkeys()): |
932 diffs.append(SVN._DiffItemInternal( | 912 diffs.append(SVN._DiffItemInternal(filename, cwd, data[filename], |
933 filename, cwd, data[filename], bogus_dir, full_move, revision)) | 913 full_move, revision)) |
934 # Use StringIO since it can be messy when diffing a directory move with | 914 # Use StringIO since it can be messy when diffing a directory move with |
935 # full_move=True. | 915 # full_move=True. |
936 buf = cStringIO.StringIO() | 916 buf = cStringIO.StringIO() |
937 for d in filter(None, diffs): | 917 for d in filter(None, diffs): |
938 buf.write(d) | 918 buf.write(d) |
939 result = buf.getvalue() | 919 result = buf.getvalue() |
940 buf.close() | 920 buf.close() |
941 return result | 921 return result |
942 finally: | |
943 gclient_utils.RemoveDirectory(bogus_dir) | |
944 | 922 |
945 @staticmethod | 923 @staticmethod |
946 def GetEmail(cwd): | 924 def GetEmail(cwd): |
947 """Retrieves the svn account which we assume is an email address.""" | 925 """Retrieves the svn account which we assume is an email address.""" |
948 try: | 926 try: |
949 infos = SVN.CaptureLocalInfo([], cwd) | 927 infos = SVN.CaptureLocalInfo([], cwd) |
950 except subprocess2.CalledProcessError: | 928 except subprocess2.CalledProcessError: |
951 return None | 929 return None |
952 | 930 |
953 # Should check for uuid but it is incorrectly saved for https creds. | 931 # Should check for uuid but it is incorrectly saved for https creds. |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1096 # revert, like for properties. | 1074 # revert, like for properties. |
1097 if not os.path.isdir(cwd): | 1075 if not os.path.isdir(cwd): |
1098 # '.' was deleted. It's not worth continuing. | 1076 # '.' was deleted. It's not worth continuing. |
1099 return | 1077 return |
1100 try: | 1078 try: |
1101 SVN.Capture(['revert', file_status[1]], cwd=cwd) | 1079 SVN.Capture(['revert', file_status[1]], cwd=cwd) |
1102 except subprocess2.CalledProcessError: | 1080 except subprocess2.CalledProcessError: |
1103 if not os.path.exists(file_path): | 1081 if not os.path.exists(file_path): |
1104 continue | 1082 continue |
1105 raise | 1083 raise |
OLD | NEW |