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