blob: d8beb8d330f17f71743dea9f53abb5835f55c0a5 [file] [log] [blame]
epoger@google.com2e0a0612012-05-25 19:48:05 +00001'''
epoger@google.com6dbf6cd2012-05-29 21:28:12 +00002Generates a visual diff of all pending changes in the local SVN checkout.
epoger@google.com2e0a0612012-05-25 19:48:05 +00003
4Launch with --help to see more information.
5
6
7Copyright 2012 Google Inc.
8
9Use of this source code is governed by a BSD-style license that can be
10found in the LICENSE file.
11'''
12
13# common Python modules
14import optparse
15import os
16import re
17import shutil
18import tempfile
19
20# modules declared within this same directory
21import svn
22
23USAGE_STRING = 'Usage: %s [options]'
24HELP_STRING = '''
25
epoger@google.com6dbf6cd2012-05-29 21:28:12 +000026Generates a visual diff of all pending changes in the local SVN checkout.
27
28This 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
30be generated.
epoger@google.com2e0a0612012-05-25 19:48:05 +000031
32'''
33
34TRUNK_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
35
36OPTION_DEST_DIR = '--dest-dir'
37# default DEST_DIR is determined at runtime
38OPTION_PATH_TO_SKDIFF = '--path-to-skdiff'
39# default PATH_TO_SKDIFF is determined at runtime
40
41def 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
51def 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
73def SvnDiff(path_to_skdiff, dest_dir):
epoger@google.com6dbf6cd2012-05-29 21:28:12 +000074 """Generates a visual diff of all pending changes in the local SVN checkout.
epoger@google.com2e0a0612012-05-25 19:48:05 +000075
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.com6dbf6cd2012-05-29 21:28:12 +000092 # Get a list of all locally modified (including added/deleted) files,
93 # descending subdirectories.
epoger@google.com2e0a0612012-05-25 19:48:05 +000094 svn_repo = svn.Svn('.')
epoger@google.com6dbf6cd2012-05-29 21:28:12 +000095 modified_file_paths = svn_repo.GetFilesWithStatus(
96 svn.STATUS_ADDED | svn.STATUS_DELETED | svn.STATUS_MODIFIED)
epoger@google.com2e0a0612012-05-25 19:48:05 +000097
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.com6dbf6cd2012-05-29 21:28:12 +0000103 # 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.com2e0a0612012-05-25 19:48:05 +0000107 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
116def RaiseUsageException():
117 raise Exception('%s\nRun with --help for more detail.' % (
118 USAGE_STRING % __file__))
119
120def 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
128if __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)