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