OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 # Copyright 2013 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Script that can be used to filter out files from a patch/diff. | |
7 | |
8 Usage: pipe the patch contents to stdin and the filtered output will be written | |
9 to stdout. | |
10 The output will be compatible with the patch program, both for Subversion and | |
11 Git patches as input. | |
12 """ | |
13 | |
14 import optparse | |
15 import os | |
16 import re | |
17 import sys | |
18 | |
19 from depot_tools_patch import patch | |
20 | |
21 # Subversion patch entries always start with either of the following, according | |
22 # to depot_tools/third_party/upload.py. | |
23 _SVN_PREFIXES = ('Index: ', 'Property changes on: ') | |
24 _GIT_PREFIX = 'diff --git ' | |
25 | |
26 _SVN_FILENAME_REGEX = re.compile(r'^.*: ([^\t]+).*\n$') | |
27 | |
28 # The Git patches generated from depot_tools/git_cl.py has the a/ and b/ | |
29 # prefixes for the source filenames stripped out. To support both normal patches | |
30 # and such patches, theyse prefixes are put in optional non-capturing groups. | |
31 _GIT_FILENAME_REGEX = re.compile(r'^diff --git (?:a/)?.* (?:b/)?(.*)\n$') | |
32 | |
33 | |
34 def parse_git_patch_set(patch_contents): | |
35 return _parse_patch_set(_GIT_PREFIX, _GIT_FILENAME_REGEX, patch_contents) | |
36 | |
37 | |
38 def parse_svn_patch_set(patch_contents): | |
39 return _parse_patch_set(_SVN_PREFIXES, _SVN_FILENAME_REGEX, patch_contents) | |
40 | |
41 | |
42 def _parse_patch_set(prefix, filename_regex, patch_contents): | |
43 # To support both normal Git patches and ones that has been uploaded with | |
44 # depot_tools/third_party/upload.py (which adds an Subversion-style Index: | |
45 # line before each file entry) we strip out the Index: lines if they exist for | |
46 # Git patches, so we can parse each entry properly. Then they're readded in | |
47 # the convert_to_patch_compatible_diff funtion. | |
48 if prefix == _GIT_PREFIX: | |
49 filtered_lines = filter(lambda line: not line.startswith(_SVN_PREFIXES), | |
50 patch_contents.splitlines(True)) | |
51 patch_contents = ''.join(filtered_lines) | |
52 | |
53 patch_chunks = [] | |
54 current_chunk = [] | |
55 for line in patch_contents.splitlines(True): | |
56 if line.startswith(prefix) and current_chunk: | |
57 patch_chunks.insert(0, current_chunk) | |
58 current_chunk = [] | |
59 current_chunk.append(line) | |
60 | |
61 if current_chunk: | |
62 patch_chunks.insert(0, current_chunk) | |
63 | |
64 # Parse filename for each patch chunk and create FilePatchDiff objects | |
65 patches = [] | |
66 for chunk in patch_chunks: | |
67 match = filename_regex.match(chunk[0]) | |
68 if not match: | |
69 raise Exception('Did not find any filename in line "%s"' % chunk[0]) | |
70 filename = match.group(1).replace('\\', '/') | |
71 patches.append(patch.FilePatchDiff(filename=filename, diff=''.join(chunk), | |
72 svn_properties=[])) | |
73 return patch.PatchSet(patches) | |
74 | |
75 | |
76 def convert_to_patch_compatible_diff(filename, patch_entry): | |
77 """Convert patch data to be compatible with the standard patch program. | |
78 | |
79 This will remove the "a/" and "b/" prefixes added by Git, so the patch becomes | |
80 compatible with the standard patch program. | |
81 It will also add an Index: line at the first line if not already present, to | |
82 make the patch entry compatible a Subversion patch (so it can be used by the | |
83 standard patch program). | |
84 """ | |
85 diff = '' | |
86 patch_lines = patch_entry.splitlines(True) | |
87 if not patch_lines[0].startswith(_SVN_PREFIXES): | |
88 diff += _SVN_PREFIXES[0] + filename + '\n' | |
89 | |
90 for line in patch_lines: | |
91 if line.startswith('---'): | |
92 line = line.replace('a/' + filename, filename) | |
93 elif line.startswith('+++'): | |
94 line = line.replace('b/' + filename, filename) | |
95 diff += line | |
96 return diff | |
97 | |
98 | |
99 def main(): | |
100 usage = '%s -f <path-filter>' % os.path.basename(sys.argv[0]) | |
101 parser = optparse.OptionParser(usage=usage) | |
102 parser.add_option('-f', '--path-filter', | |
103 help=('The path filter (POSIX paths) that all file paths ' | |
104 'are required to have to pass this filter (no ' | |
105 'regexp).')) | |
106 options, args = parser.parse_args() | |
107 if args: | |
108 parser.error('Unused args: %s' % args) | |
109 if not options.path_filter: | |
110 parser.error('A path filter must be be specified.') | |
111 | |
112 patch_contents = sys.stdin.read() | |
113 | |
114 # Find out if it's a Git or Subversion patch set. | |
115 is_git = any(l.startswith(_GIT_PREFIX) for l in patch_contents.splitlines()) | |
116 | |
117 if is_git: | |
118 patchset = parse_git_patch_set(patch_contents) | |
119 else: | |
120 patchset = parse_svn_patch_set(patch_contents) | |
121 | |
122 # Only print the patch entries that passes our path filter. | |
123 for patch_entry in patchset: | |
124 if patch_entry.filename.startswith(options.path_filter): | |
125 print convert_to_patch_compatible_diff(patch_entry.filename, | |
126 patch_entry.get(for_git=False)), | |
127 | |
128 if __name__ == '__main__': | |
129 sys.exit(main()) | |
OLD | NEW |