epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 1 | ''' |
epoger@google.com | 6dbf6cd | 2012-05-29 21:28:12 +0000 | [diff] [blame] | 2 | Generates a visual diff of all pending changes in the local SVN checkout. |
epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 3 | |
| 4 | Launch with --help to see more information. |
| 5 | |
| 6 | |
| 7 | Copyright 2012 Google Inc. |
| 8 | |
| 9 | Use of this source code is governed by a BSD-style license that can be |
| 10 | found in the LICENSE file. |
| 11 | ''' |
| 12 | |
| 13 | # common Python modules |
| 14 | import optparse |
| 15 | import os |
| 16 | import re |
| 17 | import shutil |
| 18 | import tempfile |
| 19 | |
| 20 | # modules declared within this same directory |
| 21 | import svn |
| 22 | |
| 23 | USAGE_STRING = 'Usage: %s [options]' |
| 24 | HELP_STRING = ''' |
| 25 | |
epoger@google.com | 6dbf6cd | 2012-05-29 21:28:12 +0000 | [diff] [blame] | 26 | Generates a visual diff of all pending changes in the local SVN checkout. |
| 27 | |
| 28 | This includes a list of all files that have been added, deleted, or modified |
| 29 | (as far as SVN knows about). For any image modifications, pixel diffs will |
| 30 | be generated. |
epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 31 | |
| 32 | ''' |
| 33 | |
| 34 | TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir) |
| 35 | |
| 36 | OPTION_DEST_DIR = '--dest-dir' |
| 37 | # default DEST_DIR is determined at runtime |
| 38 | OPTION_PATH_TO_SKDIFF = '--path-to-skdiff' |
| 39 | # default PATH_TO_SKDIFF is determined at runtime |
| 40 | |
| 41 | def RunCommand(command): |
| 42 | """Run a command, raising an exception if it fails. |
| 43 | |
| 44 | @param command the command as a single string |
| 45 | """ |
| 46 | print 'running command [%s]...' % command |
| 47 | retval = os.system(command) |
| 48 | if retval is not 0: |
| 49 | raise Exception('command [%s] failed' % command) |
| 50 | |
| 51 | def FindPathToSkDiff(user_set_path=None): |
| 52 | """Return path to an existing skdiff binary, or raise an exception if we |
| 53 | cannot find one. |
| 54 | |
| 55 | @param user_set_path if None, the user did not specify a path, so look in |
| 56 | some likely places; otherwise, only check at this path |
| 57 | """ |
| 58 | if user_set_path is not None: |
| 59 | if os.path.isfile(user_set_path): |
| 60 | return user_set_path |
| 61 | raise Exception('unable to find skdiff at user-set path %s' % |
| 62 | user_set_path) |
| 63 | trunk_path = os.path.join(os.path.dirname(__file__), os.pardir) |
| 64 | possible_paths = [os.path.join(trunk_path, 'out', 'Release', 'skdiff'), |
| 65 | os.path.join(trunk_path, 'out', 'Debug', 'skdiff')] |
| 66 | for try_path in possible_paths: |
| 67 | if os.path.isfile(try_path): |
| 68 | return try_path |
| 69 | raise Exception('cannot find skdiff in paths %s; maybe you need to ' |
| 70 | 'specify the %s option or build skdiff?' % ( |
| 71 | possible_paths, OPTION_PATH_TO_SKDIFF)) |
| 72 | |
| 73 | def SvnDiff(path_to_skdiff, dest_dir): |
epoger@google.com | 6dbf6cd | 2012-05-29 21:28:12 +0000 | [diff] [blame] | 74 | """Generates a visual diff of all pending changes in the local SVN checkout. |
epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 75 | |
| 76 | @param path_to_skdiff |
| 77 | @param dest_dir existing directory within which to write results |
| 78 | """ |
| 79 | # Validate parameters, filling in default values if necessary and possible. |
| 80 | path_to_skdiff = FindPathToSkDiff(path_to_skdiff) |
| 81 | if not dest_dir: |
| 82 | dest_dir = tempfile.mkdtemp() |
| 83 | |
| 84 | # Prepare temporary directories. |
| 85 | modified_flattened_dir = os.path.join(dest_dir, 'modified_flattened') |
| 86 | original_flattened_dir = os.path.join(dest_dir, 'original_flattened') |
| 87 | diff_dir = os.path.join(dest_dir, 'diffs') |
| 88 | for dir in [modified_flattened_dir, original_flattened_dir, diff_dir] : |
| 89 | shutil.rmtree(dir, ignore_errors=True) |
| 90 | os.mkdir(dir) |
| 91 | |
epoger@google.com | 6dbf6cd | 2012-05-29 21:28:12 +0000 | [diff] [blame] | 92 | # Get a list of all locally modified (including added/deleted) files, |
| 93 | # descending subdirectories. |
epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 94 | svn_repo = svn.Svn('.') |
epoger@google.com | 6dbf6cd | 2012-05-29 21:28:12 +0000 | [diff] [blame] | 95 | modified_file_paths = svn_repo.GetFilesWithStatus( |
| 96 | svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED) |
epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 97 | |
| 98 | # For each modified file: |
| 99 | # 1. copy its current contents into modified_flattened_dir |
| 100 | # 2. copy its original contents into original_flattened_dir |
| 101 | for modified_file_path in modified_file_paths: |
| 102 | dest_filename = re.sub(os.sep, '__', modified_file_path) |
epoger@google.com | 6dbf6cd | 2012-05-29 21:28:12 +0000 | [diff] [blame] | 103 | # If the file had STATUS_DELETED, it won't exist anymore... |
| 104 | if os.path.isfile(modified_file_path): |
| 105 | shutil.copyfile(modified_file_path, |
| 106 | os.path.join(modified_flattened_dir, dest_filename)) |
epoger@google.com | 2e0a061 | 2012-05-25 19:48:05 +0000 | [diff] [blame] | 107 | svn_repo.ExportBaseVersionOfFile( |
| 108 | modified_file_path, |
| 109 | os.path.join(original_flattened_dir, dest_filename)) |
| 110 | |
| 111 | # Run skdiff: compare original_flattened_dir against modified_flattened_dir |
| 112 | RunCommand('%s %s %s %s' % (path_to_skdiff, original_flattened_dir, |
| 113 | modified_flattened_dir, diff_dir)) |
| 114 | print '\nskdiff results are ready in file://%s/index.html' % diff_dir |
| 115 | |
| 116 | def RaiseUsageException(): |
| 117 | raise Exception('%s\nRun with --help for more detail.' % ( |
| 118 | USAGE_STRING % __file__)) |
| 119 | |
| 120 | def Main(options, args): |
| 121 | """Allow other scripts to call this script with fake command-line args. |
| 122 | """ |
| 123 | num_args = len(args) |
| 124 | if num_args != 0: |
| 125 | RaiseUsageException() |
| 126 | SvnDiff(path_to_skdiff=options.path_to_skdiff, dest_dir=options.dest_dir) |
| 127 | |
| 128 | if __name__ == '__main__': |
| 129 | parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING) |
| 130 | parser.add_option(OPTION_DEST_DIR, |
| 131 | action='store', type='string', default=None, |
| 132 | help='existing directory within which to write results; ' |
| 133 | 'if not set, will create a temporary directory which ' |
| 134 | 'will remain in place after this script completes') |
| 135 | parser.add_option(OPTION_PATH_TO_SKDIFF, |
| 136 | action='store', type='string', default=None, |
| 137 | help='path to already-built skdiff tool; if not set, ' |
| 138 | 'will search for it in typical directories near this ' |
| 139 | 'script') |
| 140 | (options, args) = parser.parse_args() |
| 141 | Main(options, args) |