| Index: tools/git/mffr.py
|
| diff --git a/tools/git/mffr.py b/tools/git/mffr.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..d5b67c8c3f1d039be98132b6e8eacc170066f394
|
| --- /dev/null
|
| +++ b/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())
|
|
|