Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Side by Side Diff: scm.py

Issue 14247007: Use --internal-diff only for svn >= 1.7. (Closed) Base URL: http://src.chromium.org/svn/trunk/tools/depot_tools/
Patch Set: Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/scm_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
OLDNEW
« no previous file with comments | « no previous file | tests/scm_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698