diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/tools/git/mffr.py | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/tools/git/mffr.py')
-rwxr-xr-x | chromium/tools/git/mffr.py | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/chromium/tools/git/mffr.py b/chromium/tools/git/mffr.py new file mode 100755 index 00000000000..d5b67c8c3f1 --- /dev/null +++ b/chromium/tools/git/mffr.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +# Copyright (c) 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Usage: mffr.py [-d] [-g *.h] [-g *.cc] REGEXP REPLACEMENT + +This tool performs a fast find-and-replace operation on files in +the current git repository. + +The -d flag selects a default set of globs (C++ and Objective-C/C++ +source files). The -g flag adds a single glob to the list and may +be used multiple times. If neither -d nor -g is specified, the tool +searches all files (*.*). + +REGEXP uses full Python regexp syntax. REPLACEMENT can use +back-references. +""" + +import optparse +import re +import subprocess +import sys + + +# We need to use shell=True with subprocess on Windows so that it +# finds 'git' from the path, but can lead to undesired behavior on +# Linux. +_USE_SHELL = (sys.platform == 'win32') + + +def MultiFileFindReplace(original, replacement, file_globs): + """Implements fast multi-file find and replace. + + Given an |original| string and a |replacement| string, find matching + files by running git grep on |original| in files matching any + pattern in |file_globs|. + + Once files are found, |re.sub| is run to replace |original| with + |replacement|. |replacement| may use capture group back-references. + + Args: + original: '(#(include|import)\s*["<])chrome/browser/ui/browser.h([>"])' + replacement: '\1chrome/browser/ui/browser/browser.h\3' + file_globs: ['*.cc', '*.h', '*.m', '*.mm'] + + Returns the list of files modified. + + Raises an exception on error. + """ + # Posix extended regular expressions do not reliably support the "\s" + # shorthand. + posix_ere_original = re.sub(r"\\s", "[[:space:]]", original) + if sys.platform == 'win32': + posix_ere_original = posix_ere_original.replace('"', '""') + out, err = subprocess.Popen( + ['git', 'grep', '-E', '--name-only', posix_ere_original, + '--'] + file_globs, + stdout=subprocess.PIPE, + shell=_USE_SHELL).communicate() + referees = out.splitlines() + + for referee in referees: + with open(referee) as f: + original_contents = f.read() + contents = re.sub(original, replacement, original_contents) + if contents == original_contents: + raise Exception('No change in file %s although matched in grep' % + referee) + with open(referee, 'wb') as f: + f.write(contents) + + return referees + + +def main(): + parser = optparse.OptionParser(usage=''' +(1) %prog <options> REGEXP REPLACEMENT +REGEXP uses full Python regexp syntax. REPLACEMENT can use back-references. + +(2) %prog <options> -i <file> +<file> should contain a list (in Python syntax) of +[REGEXP, REPLACEMENT, [GLOBS]] lists, e.g.: +[ + [r"(foo|bar)", r"\1baz", ["*.cc", "*.h"]], + ["54", "42"], +] +As shown above, [GLOBS] can be omitted for a given search-replace list, in which +case the corresponding search-replace will use the globs specified on the +command line.''') + parser.add_option('-d', action='store_true', + dest='use_default_glob', + help='Perform the change on C++ and Objective-C(++) source ' + 'and header files.') + parser.add_option('-f', action='store_true', + dest='force_unsafe_run', + help='Perform the run even if there are uncommitted local ' + 'changes.') + parser.add_option('-g', action='append', + type='string', + default=[], + metavar="<glob>", + dest='user_supplied_globs', + help='Perform the change on the specified glob. Can be ' + 'specified multiple times, in which case the globs are ' + 'unioned.') + parser.add_option('-i', "--input_file", + type='string', + action='store', + default='', + metavar="<file>", + dest='input_filename', + help='Read arguments from <file> rather than the command ' + 'line. NOTE: To be sure of regular expressions being ' + 'interpreted correctly, use raw strings.') + opts, args = parser.parse_args() + if opts.use_default_glob and opts.user_supplied_globs: + print '"-d" and "-g" cannot be used together' + parser.print_help() + return 1 + + from_file = opts.input_filename != "" + if (from_file and len(args) != 0) or (not from_file and len(args) != 2): + parser.print_help() + return 1 + + if not opts.force_unsafe_run: + out, err = subprocess.Popen(['git', 'status', '--porcelain'], + stdout=subprocess.PIPE, + shell=_USE_SHELL).communicate() + if out: + print 'ERROR: This tool does not print any confirmation prompts,' + print 'so you should only run it with a clean staging area and cache' + print 'so that reverting a bad find/replace is as easy as running' + print ' git checkout -- .' + print '' + print 'To override this safeguard, pass the -f flag.' + return 1 + + global_file_globs = ['*.*'] + if opts.use_default_glob: + global_file_globs = ['*.cc', '*.h', '*.m', '*.mm'] + elif opts.user_supplied_globs: + global_file_globs = opts.user_supplied_globs + + # Construct list of search-replace tasks. + search_replace_tasks = [] + if opts.input_filename == '': + original = args[0] + replacement = args[1] + search_replace_tasks.append([original, replacement, global_file_globs]) + else: + f = open(opts.input_filename) + search_replace_tasks = eval("".join(f.readlines())) + for task in search_replace_tasks: + if len(task) == 2: + task.append(global_file_globs) + f.close() + + for (original, replacement, file_globs) in search_replace_tasks: + print 'File globs: %s' % file_globs + print 'Original: %s' % original + print 'Replacement: %s' % replacement + MultiFileFindReplace(original, replacement, file_globs) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) |