summaryrefslogtreecommitdiff
path: root/chromium/tools/git/mffr.py
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/tools/git/mffr.py
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/tools/git/mffr.py')
-rwxr-xr-xchromium/tools/git/mffr.py169
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())