OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
epoger
2013/07/17 04:38:25
Tested with JSON expectations as follows:
# Rever
| |
2 ''' | 2 ''' |
3 Copyright 2012 Google Inc. | 3 Copyright 2012 Google Inc. |
4 | 4 |
5 Use of this source code is governed by a BSD-style license that can be | 5 Use of this source code is governed by a BSD-style license that can be |
6 found in the LICENSE file. | 6 found in the LICENSE file. |
7 ''' | 7 ''' |
8 | 8 |
9 ''' | 9 ''' |
10 Generates a visual diff of all pending changes in the local SVN checkout. | 10 Generates a visual diff of all pending changes in the local SVN (or git!) |
11 checkout. | |
11 | 12 |
12 Launch with --help to see more information. | 13 Launch with --help to see more information. |
13 | 14 |
15 TODO(epoger): Now that this tool supports either git or svn, rename it. | |
14 TODO(epoger): Fix indentation in this file (2-space indents, not 4-space). | 16 TODO(epoger): Fix indentation in this file (2-space indents, not 4-space). |
15 ''' | 17 ''' |
16 | 18 |
17 # common Python modules | 19 # common Python modules |
18 import optparse | 20 import optparse |
19 import os | 21 import os |
20 import re | 22 import re |
21 import shutil | 23 import shutil |
24 import subprocess | |
22 import sys | 25 import sys |
23 import tempfile | 26 import tempfile |
24 import urllib2 | 27 import urllib2 |
25 | 28 |
26 # Imports from within Skia | 29 # Imports from within Skia |
27 # | 30 # |
28 # We need to add the 'gm' directory, so that we can import gm_json.py within | 31 # We need to add the 'gm' directory, so that we can import gm_json.py within |
29 # that directory. That script allows us to parse the actual-results.json file | 32 # that directory. That script allows us to parse the actual-results.json file |
30 # written out by the GM tool. | 33 # written out by the GM tool. |
31 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* | 34 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* |
32 # so any dirs that are already in the PYTHONPATH will be preferred. | 35 # so any dirs that are already in the PYTHONPATH will be preferred. |
33 # | 36 # |
34 # This assumes that the 'gm' directory has been checked out as a sibling of | 37 # This assumes that the 'gm' directory has been checked out as a sibling of |
35 # the 'tools' directory containing this script, which will be the case if | 38 # the 'tools' directory containing this script, which will be the case if |
36 # 'trunk' was checked out as a single unit. | 39 # 'trunk' was checked out as a single unit. |
37 GM_DIRECTORY = os.path.realpath( | 40 GM_DIRECTORY = os.path.realpath( |
38 os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm')) | 41 os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gm')) |
39 if GM_DIRECTORY not in sys.path: | 42 if GM_DIRECTORY not in sys.path: |
40 sys.path.append(GM_DIRECTORY) | 43 sys.path.append(GM_DIRECTORY) |
41 import gm_json | 44 import gm_json |
42 import jsondiff | 45 import jsondiff |
43 import svn | 46 import svn |
44 | 47 |
45 USAGE_STRING = 'Usage: %s [options]' | 48 USAGE_STRING = 'Usage: %s [options]' |
46 HELP_STRING = ''' | 49 HELP_STRING = ''' |
47 | 50 |
48 Generates a visual diff of all pending changes in the local SVN checkout. | 51 Generates a visual diff of all pending changes in the local SVN/git checkout. |
49 | 52 |
50 This includes a list of all files that have been added, deleted, or modified | 53 This includes a list of all files that have been added, deleted, or modified |
51 (as far as SVN knows about). For any image modifications, pixel diffs will | 54 (as far as SVN/git knows about). For any image modifications, pixel diffs will |
52 be generated. | 55 be generated. |
53 | 56 |
54 ''' | 57 ''' |
55 | 58 |
56 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) | 59 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) |
57 | 60 |
58 TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir) | 61 TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir) |
59 | 62 |
60 OPTION_DEST_DIR = '--dest-dir' | 63 OPTION_DEST_DIR = '--dest-dir' |
61 OPTION_PATH_TO_SKDIFF = '--path-to-skdiff' | 64 OPTION_PATH_TO_SKDIFF = '--path-to-skdiff' |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
153 if new_checksum: | 156 if new_checksum: |
154 new_image_url = _CreateGSUrl( | 157 new_image_url = _CreateGSUrl( |
155 imagename=imagename, | 158 imagename=imagename, |
156 hash_type=gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, | 159 hash_type=gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, |
157 hash_digest=new_checksum) | 160 hash_digest=new_checksum) |
158 _DownloadUrlToFile( | 161 _DownloadUrlToFile( |
159 source_url=new_image_url, | 162 source_url=new_image_url, |
160 dest_path=os.path.join(new_flattened_dir, | 163 dest_path=os.path.join(new_flattened_dir, |
161 filename_prefix + imagename)) | 164 filename_prefix + imagename)) |
162 | 165 |
166 def _RunCommand(args): | |
167 """Run a command (from self._directory) and return stdout as a single | |
168 string. | |
169 | |
170 @param args a list of arguments | |
171 """ | |
172 proc = subprocess.Popen(args, | |
173 stdout=subprocess.PIPE, | |
174 stderr=subprocess.PIPE) | |
175 (stdout, stderr) = proc.communicate() | |
176 if proc.returncode is not 0: | |
177 raise Exception('command "%s" failed: %s' % (args, stderr)) | |
178 return stdout | |
179 | |
180 def _GitGetModifiedFiles(): | |
181 """Returns a list of locally modified files within the current working dir. | |
182 | |
183 TODO(epoger): Move this into a git utility package? | |
184 """ | |
185 status_regex_string = '^...(\S+)' | |
186 stdout = _RunCommand(['git', 'status', '-s']) | |
Stephen White
2013/07/17 10:44:40
GitNit(tm): git status is a "porcelain" command, a
epoger
2013/07/18 15:00:04
Ah, thanks for teaching this git n00b something.
| |
187 status_regex = re.compile(status_regex_string, re.MULTILINE) | |
188 return status_regex.findall(stdout) | |
189 | |
190 def _GitExportBaseVersionOfFile(file_within_repo, dest_path): | |
191 """Retrieves a copy of the base version of a file within the repository. | |
192 | |
193 @param file_within_repo path to the file within the repo whose base | |
194 version you wish to obtain | |
195 @param dest_path destination to which to write the base content | |
196 | |
197 TODO(epoger): Move this into a git utility package? | |
198 """ | |
199 args = ['git', 'show', os.path.join('HEAD:.', file_within_repo)] | |
Stephen White
2013/07/17 10:44:40
Same here. I think the plumbing equivalent is "git
epoger
2013/07/18 15:00:04
I tried "git cat-file" in various permutations, bu
Stephen White
2013/07/18 17:32:38
Sure. Maybe just add a FIXME that we should try to
epoger
2013/07/18 17:40:35
Good idea... done.
| |
200 with open(dest_path, 'wb') as outfile: | |
201 proc = subprocess.Popen(args, stdout=outfile) | |
202 proc.communicate() | |
203 if proc.returncode is not 0: | |
204 raise Exception('command "%s" failed' % args) | |
205 | |
163 def SvnDiff(path_to_skdiff, dest_dir, source_dir): | 206 def SvnDiff(path_to_skdiff, dest_dir, source_dir): |
164 """Generates a visual diff of all pending changes in source_dir. | 207 """Generates a visual diff of all pending changes in source_dir. |
165 | 208 |
166 @param path_to_skdiff | 209 @param path_to_skdiff |
167 @param dest_dir existing directory within which to write results | 210 @param dest_dir existing directory within which to write results |
168 @param source_dir | 211 @param source_dir |
169 """ | 212 """ |
170 # Validate parameters, filling in default values if necessary and possible. | 213 # Validate parameters, filling in default values if necessary and possible. |
171 path_to_skdiff = os.path.abspath(FindPathToSkDiff(path_to_skdiff)) | 214 path_to_skdiff = os.path.abspath(FindPathToSkDiff(path_to_skdiff)) |
172 if not dest_dir: | 215 if not dest_dir: |
173 dest_dir = tempfile.mkdtemp() | 216 dest_dir = tempfile.mkdtemp() |
174 dest_dir = os.path.abspath(dest_dir) | 217 dest_dir = os.path.abspath(dest_dir) |
175 | 218 |
176 os.chdir(source_dir) | 219 os.chdir(source_dir) |
220 using_svn = os.path.isdir('.svn') | |
177 | 221 |
178 # Prepare temporary directories. | 222 # Prepare temporary directories. |
179 modified_flattened_dir = os.path.join(dest_dir, 'modified_flattened') | 223 modified_flattened_dir = os.path.join(dest_dir, 'modified_flattened') |
180 original_flattened_dir = os.path.join(dest_dir, 'original_flattened') | 224 original_flattened_dir = os.path.join(dest_dir, 'original_flattened') |
181 diff_dir = os.path.join(dest_dir, 'diffs') | 225 diff_dir = os.path.join(dest_dir, 'diffs') |
182 for dir in [modified_flattened_dir, original_flattened_dir, diff_dir] : | 226 for dir in [modified_flattened_dir, original_flattened_dir, diff_dir] : |
183 shutil.rmtree(dir, ignore_errors=True) | 227 shutil.rmtree(dir, ignore_errors=True) |
184 os.mkdir(dir) | 228 os.mkdir(dir) |
185 | 229 |
186 # Get a list of all locally modified (including added/deleted) files, | 230 # Get a list of all locally modified (including added/deleted) files, |
187 # descending subdirectories. | 231 # descending subdirectories. |
188 svn_repo = svn.Svn('.') | 232 if using_svn: |
189 modified_file_paths = svn_repo.GetFilesWithStatus( | 233 svn_repo = svn.Svn('.') |
190 svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED) | 234 modified_file_paths = svn_repo.GetFilesWithStatus( |
235 svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED) | |
236 else: | |
237 modified_file_paths = _GitGetModifiedFiles() | |
191 | 238 |
192 # For each modified file: | 239 # For each modified file: |
193 # 1. copy its current contents into modified_flattened_dir | 240 # 1. copy its current contents into modified_flattened_dir |
194 # 2. copy its original contents into original_flattened_dir | 241 # 2. copy its original contents into original_flattened_dir |
195 for modified_file_path in modified_file_paths: | 242 for modified_file_path in modified_file_paths: |
196 if modified_file_path.endswith('.json'): | 243 if modified_file_path.endswith('.json'): |
197 # Special handling for JSON files, in the hopes that they | 244 # Special handling for JSON files, in the hopes that they |
198 # contain GM result summaries. | 245 # contain GM result summaries. |
199 (_unused, original_file_path) = tempfile.mkstemp() | 246 (_unused, original_file_path) = tempfile.mkstemp() |
200 svn_repo.ExportBaseVersionOfFile(modified_file_path, | 247 if using_svn: |
201 original_file_path) | 248 svn_repo.ExportBaseVersionOfFile( |
249 modified_file_path, original_file_path) | |
250 else: | |
251 _GitExportBaseVersionOfFile( | |
252 modified_file_path, original_file_path) | |
202 platform_prefix = re.sub(os.sep, '__', | 253 platform_prefix = re.sub(os.sep, '__', |
203 os.path.dirname(modified_file_path)) + '__' | 254 os.path.dirname(modified_file_path)) + '__' |
204 _CallJsonDiff(old_json_path=original_file_path, | 255 _CallJsonDiff(old_json_path=original_file_path, |
205 new_json_path=modified_file_path, | 256 new_json_path=modified_file_path, |
206 old_flattened_dir=original_flattened_dir, | 257 old_flattened_dir=original_flattened_dir, |
207 new_flattened_dir=modified_flattened_dir, | 258 new_flattened_dir=modified_flattened_dir, |
208 filename_prefix=platform_prefix) | 259 filename_prefix=platform_prefix) |
209 os.remove(original_file_path) | 260 os.remove(original_file_path) |
210 else: | 261 else: |
211 dest_filename = re.sub(os.sep, '__', modified_file_path) | 262 dest_filename = re.sub(os.sep, '__', modified_file_path) |
212 # If the file had STATUS_DELETED, it won't exist anymore... | 263 # If the file had STATUS_DELETED, it won't exist anymore... |
213 if os.path.isfile(modified_file_path): | 264 if os.path.isfile(modified_file_path): |
214 shutil.copyfile(modified_file_path, | 265 shutil.copyfile(modified_file_path, |
215 os.path.join(modified_flattened_dir, | 266 os.path.join(modified_flattened_dir, |
216 dest_filename)) | 267 dest_filename)) |
217 svn_repo.ExportBaseVersionOfFile( | 268 if using_svn: |
218 modified_file_path, | 269 svn_repo.ExportBaseVersionOfFile( |
219 os.path.join(original_flattened_dir, dest_filename)) | 270 modified_file_path, |
271 os.path.join(original_flattened_dir, dest_filename)) | |
272 else: | |
273 _GitExportBaseVersionOfFile( | |
274 modified_file_path, | |
275 os.path.join(original_flattened_dir, dest_filename)) | |
220 | 276 |
221 # Run skdiff: compare original_flattened_dir against modified_flattened_dir | 277 # Run skdiff: compare original_flattened_dir against modified_flattened_dir |
222 RunCommand('%s %s %s %s' % (path_to_skdiff, original_flattened_dir, | 278 RunCommand('%s %s %s %s' % (path_to_skdiff, original_flattened_dir, |
223 modified_flattened_dir, diff_dir)) | 279 modified_flattened_dir, diff_dir)) |
224 print '\nskdiff results are ready in file://%s/index.html' % diff_dir | 280 print '\nskdiff results are ready in file://%s/index.html' % diff_dir |
225 | 281 |
226 def RaiseUsageException(): | 282 def RaiseUsageException(): |
227 raise Exception('%s\nRun with --help for more detail.' % ( | 283 raise Exception('%s\nRun with --help for more detail.' % ( |
228 USAGE_STRING % __file__)) | 284 USAGE_STRING % __file__)) |
229 | 285 |
(...skipping 17 matching lines...) Expand all Loading... | |
247 action='store', type='string', default=None, | 303 action='store', type='string', default=None, |
248 help='path to already-built skdiff tool; if not set, ' | 304 help='path to already-built skdiff tool; if not set, ' |
249 'will search for it in typical directories near this ' | 305 'will search for it in typical directories near this ' |
250 'script') | 306 'script') |
251 parser.add_option(OPTION_SOURCE_DIR, | 307 parser.add_option(OPTION_SOURCE_DIR, |
252 action='store', type='string', default='.', | 308 action='store', type='string', default='.', |
253 help='root directory within which to compare all ' + | 309 help='root directory within which to compare all ' + |
254 'files; defaults to "%default"') | 310 'files; defaults to "%default"') |
255 (options, args) = parser.parse_args() | 311 (options, args) = parser.parse_args() |
256 Main(options, args) | 312 Main(options, args) |
OLD | NEW |