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 763 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
774 @staticmethod | 774 @staticmethod |
775 def GenerateDiff(filenames, cwd, full_move, revision): | 775 def GenerateDiff(filenames, cwd, full_move, revision): |
776 """Returns a string containing the diff for the given file list. | 776 """Returns a string containing the diff for the given file list. |
777 | 777 |
778 The files in the list should either be absolute paths or relative to the | 778 The files in the list should either be absolute paths or relative to the |
779 given root. If no root directory is provided, the repository root will be | 779 given root. If no root directory is provided, the repository root will be |
780 used. | 780 used. |
781 The diff will always use relative paths. | 781 The diff will always use relative paths. |
782 """ | 782 """ |
783 assert isinstance(filenames, (list, tuple)) | 783 assert isinstance(filenames, (list, tuple)) |
| 784 # If the user specified a custom diff command in their svn config file, |
| 785 # then it'll be used when we do svn diff, which we don't want to happen |
| 786 # since we want the unified diff. |
| 787 if SVN.AssertVersion("1.7")[0]: |
| 788 # On svn >= 1.7, the "--internal-diff" flag will solve this. |
| 789 return SVN._GenerateDiffInternal(filenames, cwd, full_move, revision, |
| 790 ["diff", "--internal-diff"], |
| 791 ["diff", "--internal-diff"]) |
| 792 else: |
| 793 # On svn < 1.7, the "--internal-diff" flag doesn't exist. Using |
| 794 # --diff-cmd=diff doesn't always work, since e.g. Windows cmd users may |
| 795 # not have a "diff" executable in their path at all. So we use an empty |
| 796 # temporary directory as the config directory, which bypasses any user |
| 797 # settings for the diff-cmd. However, we don't pass this for the |
| 798 # remote_safe_diff_command parameter, since when a new config-dir is |
| 799 # specified for an svn diff against a remote URL, it triggers |
| 800 # authentication prompts. In this case there isn't really a good |
| 801 # alternative to svn 1.7's --internal-diff flag. |
| 802 bogus_dir = tempfile.mkdtemp() |
| 803 try: |
| 804 return SVN._GenerateDiffInternal(filenames, cwd, full_move, revision, |
| 805 ["diff", "--config-dir", bogus_dir], |
| 806 ["diff"]) |
| 807 finally: |
| 808 gclient_utils.rmtree(bogus_dir) |
| 809 |
| 810 @staticmethod |
| 811 def _GenerateDiffInternal(filenames, cwd, full_move, revision, diff_command, |
| 812 remote_safe_diff_command): |
784 root = os.path.normcase(os.path.join(cwd, '')) | 813 root = os.path.normcase(os.path.join(cwd, '')) |
785 def RelativePath(path, root): | 814 def RelativePath(path, root): |
786 """We must use relative paths.""" | 815 """We must use relative paths.""" |
787 if os.path.normcase(path).startswith(root): | 816 if os.path.normcase(path).startswith(root): |
788 return path[len(root):] | 817 return path[len(root):] |
789 return path | 818 return path |
790 # If the user specified a custom diff command in their svn config file, | 819 # Cleanup filenames |
791 # then it'll be used when we do svn diff, which we don't want to happen | 820 filenames = [RelativePath(f, root) for f in filenames] |
792 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 821 # Get information about the modified items (files and directories) |
793 # work, since they can have another diff executable in their path that | 822 data = dict((f, SVN.CaptureLocalInfo([f], root)) for f in filenames) |
794 # gives different line endings. So we use a bogus temp directory as the | 823 diffs = [] |
795 # config directory, which gets around these problems. | 824 if full_move: |
796 bogus_dir = tempfile.mkdtemp() | 825 # Eliminate modified files inside moved/copied directory. |
797 try: | 826 for (filename, info) in data.iteritems(): |
798 # Cleanup filenames | 827 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": |
799 filenames = [RelativePath(f, root) for f in filenames] | 828 # Remove files inside the directory. |
800 # Get information about the modified items (files and directories) | 829 filenames = [f for f in filenames |
801 data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) | 830 if not f.startswith(filename + os.path.sep)] |
802 diffs = [] | 831 for filename in data.keys(): |
803 if full_move: | 832 if not filename in filenames: |
804 # Eliminate modified files inside moved/copied directory. | 833 # Remove filtered out items. |
805 for (filename, info) in data.iteritems(): | 834 del data[filename] |
806 if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": | 835 else: |
807 # Remove files inside the directory. | 836 metaheaders = [] |
808 filenames = [f for f in filenames | 837 for (filename, info) in data.iteritems(): |
809 if not f.startswith(filename + os.path.sep)] | 838 if SVN.IsMovedInfo(info): |
810 for filename in data.keys(): | 839 # for now, the most common case is a head copy, |
811 if not filename in filenames: | 840 # so let's just encode that as a straight up cp. |
812 # Remove filtered out items. | 841 srcurl = info.get('Copied From URL') |
813 del data[filename] | 842 file_root = info.get('Repository Root') |
814 else: | 843 rev = int(info.get('Copied From Rev')) |
815 metaheaders = [] | 844 assert srcurl.startswith(file_root) |
816 for (filename, info) in data.iteritems(): | 845 src = srcurl[len(file_root)+1:] |
817 if SVN.IsMovedInfo(info): | 846 try: |
818 # for now, the most common case is a head copy, | 847 srcinfo = SVN.CaptureRemoteInfo(srcurl) |
819 # so let's just encode that as a straight up cp. | 848 except subprocess2.CalledProcessError, e: |
820 srcurl = info.get('Copied From URL') | 849 if not 'Not a valid URL' in e.stderr: |
821 file_root = info.get('Repository Root') | 850 raise |
822 rev = int(info.get('Copied From Rev')) | 851 # Assume the file was deleted. No idea how to figure out at which |
823 assert srcurl.startswith(file_root) | 852 # revision the file was deleted. |
824 src = srcurl[len(file_root)+1:] | 853 srcinfo = {'Revision': rev} |
825 try: | 854 if (srcinfo.get('Revision') != rev and |
826 srcinfo = SVN.CaptureRemoteInfo(srcurl) | 855 SVN.Capture(remote_safe_diff_command + ['-r', '%d:head' % rev, |
827 except subprocess2.CalledProcessError, e: | 856 srcurl], cwd)): |
828 if not 'Not a valid URL' in e.stderr: | 857 metaheaders.append("#$ svn cp -r %d %s %s " |
829 raise | 858 "### WARNING: note non-trunk copy\n" % |
830 # Assume the file was deleted. No idea how to figure out at which | 859 (rev, src, filename)) |
831 # revision the file was deleted. | 860 else: |
832 srcinfo = {'Revision': rev} | 861 metaheaders.append("#$ cp %s %s\n" % (src, |
833 if (srcinfo.get('Revision') != rev and | 862 filename)) |
834 SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl], cwd)): | 863 if metaheaders: |
835 metaheaders.append("#$ svn cp -r %d %s %s " | 864 diffs.append("### BEGIN SVN COPY METADATA\n") |
836 "### WARNING: note non-trunk copy\n" % | 865 diffs.extend(metaheaders) |
837 (rev, src, filename)) | 866 diffs.append("### END SVN COPY METADATA\n") |
838 else: | 867 # Now ready to do the actual diff. |
839 metaheaders.append("#$ cp %s %s\n" % (src, | 868 for filename in sorted(data): |
840 filename)) | 869 diffs.append(SVN._DiffItemInternal( |
841 | 870 filename, cwd, data[filename], diff_command, full_move, revision)) |
842 if metaheaders: | 871 # Use StringIO since it can be messy when diffing a directory move with |
843 diffs.append("### BEGIN SVN COPY METADATA\n") | 872 # full_move=True. |
844 diffs.extend(metaheaders) | 873 buf = cStringIO.StringIO() |
845 diffs.append("### END SVN COPY METADATA\n") | 874 for d in filter(None, diffs): |
846 # Now ready to do the actual diff. | 875 buf.write(d) |
847 for filename in sorted(data.iterkeys()): | 876 result = buf.getvalue() |
848 diffs.append(SVN._DiffItemInternal( | 877 buf.close() |
849 filename, cwd, data[filename], bogus_dir, full_move, revision)) | 878 return result |
850 # Use StringIO since it can be messy when diffing a directory move with | |
851 # full_move=True. | |
852 buf = cStringIO.StringIO() | |
853 for d in filter(None, diffs): | |
854 buf.write(d) | |
855 result = buf.getvalue() | |
856 buf.close() | |
857 return result | |
858 finally: | |
859 gclient_utils.rmtree(bogus_dir) | |
860 | 879 |
861 @staticmethod | 880 @staticmethod |
862 def _DiffItemInternal(filename, cwd, info, bogus_dir, full_move, revision): | 881 def _DiffItemInternal(filename, cwd, info, diff_command, full_move, revision): |
863 """Grabs the diff data.""" | 882 """Grabs the diff data.""" |
864 command = ["diff", "--config-dir", bogus_dir, filename] | 883 command = diff_command + [filename] |
865 if revision: | 884 if revision: |
866 command.extend(['--revision', revision]) | 885 command.extend(['--revision', revision]) |
867 data = None | 886 data = None |
868 if SVN.IsMovedInfo(info): | 887 if SVN.IsMovedInfo(info): |
869 if full_move: | 888 if full_move: |
870 if info.get("Node Kind") == "directory": | 889 if info.get("Node Kind") == "directory": |
871 # Things become tricky here. It's a directory copy/move. We need to | 890 # Things become tricky here. It's a directory copy/move. We need to |
872 # diff all the files inside it. | 891 # diff all the files inside it. |
873 # This will put a lot of pressure on the heap. This is why StringIO | 892 # This will put a lot of pressure on the heap. This is why StringIO |
874 # is used and converted back into a string at the end. The reason to | 893 # is used and converted back into a string at the end. The reason to |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1067 # revert, like for properties. | 1086 # revert, like for properties. |
1068 if not os.path.isdir(cwd): | 1087 if not os.path.isdir(cwd): |
1069 # '.' was deleted. It's not worth continuing. | 1088 # '.' was deleted. It's not worth continuing. |
1070 return | 1089 return |
1071 try: | 1090 try: |
1072 SVN.Capture(['revert', file_status[1]], cwd=cwd) | 1091 SVN.Capture(['revert', file_status[1]], cwd=cwd) |
1073 except subprocess2.CalledProcessError: | 1092 except subprocess2.CalledProcessError: |
1074 if not os.path.exists(file_path): | 1093 if not os.path.exists(file_path): |
1075 continue | 1094 continue |
1076 raise | 1095 raise |
OLD | NEW |