Chromium Code Reviews| Index: tools/git/move_source_file.py |
| diff --git a/tools/refactor/move_file.py b/tools/git/move_source_file.py |
| similarity index 36% |
| rename from tools/refactor/move_file.py |
| rename to tools/git/move_source_file.py |
| index c379cdf38af2521e3cda9c8b0f40962a85d32b60..7acbc304f6fc1144647f3c58298dbd79641b0857 100755 |
| --- a/tools/refactor/move_file.py |
| +++ b/tools/git/move_source_file.py |
| @@ -7,45 +7,131 @@ |
| point to it. Updates include guards in moved header files. Assumes |
| Chromium coding style. |
| -Does not reorder headers (you can use tools/sort-headers.py), and does |
| -not update .gypi files. |
| +Does not reorder headers; instead, use this after committing all of |
| +your moves: |
| + ./tools/git/for-all-touched-files.py -c "tools/sort-headers.py [[FILENAME]]" |
| -Relies on git for a fast way to find files that include the moved file. |
| +Updates paths used in .gyp(i) files, but does not reorder or |
| +restructure .gyp(i) files in any way. |
| + |
| +Must run in a git checkout, as it relies on git for a fast way to find |
| +files that reference the moved file. |
| """ |
| import os |
| +import re |
| import subprocess |
| import sys |
| - |
| HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] |
| -def MoveFile(from_path, to_path): |
| - """Moves a file from |from_path| to |to_path|, updating its include |
| - guard to match the new path and updating all #includes and #imports |
| - of the file in other files in the same git repository, with the |
| - assumption that they include it using the Chromium style |
| - guide-standard full path from root. |
| +def MakeDestinationPath(from_path, to_path): |
| + """Given the from and to paths, return a correct destination path. |
| + |
| + The initial destination path may either a full path or a directory, |
| + in which case the path must end with /. Also does basic sanity |
| + checks. |
| """ |
| - extension = os.path.splitext(from_path)[1] |
| - if extension not in HANDLED_EXTENSIONS: |
| + if os.path.splitext(from_path)[1] not in HANDLED_EXTENSIONS: |
| raise Exception('Only intended to move individual source files.') |
| - |
| dest_extension = os.path.splitext(to_path)[1] |
| if dest_extension not in HANDLED_EXTENSIONS: |
| if to_path.endswith('/') or to_path.endswith('\\'): |
| to_path += os.path.basename(from_path) |
| else: |
| raise Exception('Destination must be either full path or end with /.') |
| + return to_path |
| + |
| +def MoveFile(from_path, to_path): |
| + """Performs a git mv command to move a file from |from_path| to |to_path|. |
| + """ |
| if not os.system('git mv %s %s' % (from_path, to_path)) == 0: |
| raise Exception('Fatal: Failed to run git mv command.') |
| - if extension in ['.h', '.hh']: |
| + |
| +def MultiFileFindReplace(original, |
| + replacement, |
| + grep_pattern, |
| + file_globs, |
| + guard_formats): |
|
Nico
2012/11/20 18:32:39
For what it's worth, this interface looks more com
Jói
2012/11/20 22:21:03
That's a good point. Using re.sub seems like it m
|
| + """Implements fast multi-file find and replace with optional guards. |
| + |
| + Given an |original| string and a |replacement| string, search for |
| + them by formatting |grep_pattern| with |original| and running git |
|
Nico
2012/11/20 18:32:39
nit: Don't linebreak in the middle of `git grep`
Jói
2012/11/20 22:21:03
Done.
|
| + grep on the result, for files matching any of |file_globs|. |
| + |
| + Once files are found, the function searches for any of |
| + |guard_formats| formatted with |original| and replaces each match |
| + with the same guard format as matched, formatted with |replacement|. |
| + |
| + Args: |
| + original: 'chrome/browser/ui/browser.h' |
| + replacement: 'chrome/browser/ui/browser/browser.h' |
| + grep_pattern: r'#(include|import)\s*["<]%s[>"]' |
| + file_globs: ['*.cc', '*.h', '*.m', '*.mm'] |
| + guard_formats: None or ('"%s"', '<%s>') |
| + |
| + Raises an exception on error. |
| + """ |
| + out, err = subprocess.Popen( |
| + ['git', 'grep', '-E', '--name-only', |
| + grep_pattern % re.escape(original), '--'] + file_globs, |
| + stdout=subprocess.PIPE).communicate() |
| + referees = out.splitlines() |
| + |
| + for referee in referees: |
| + with open(referee) as f: |
| + original_contents = f.read() |
| + contents = original_contents |
| + for guard_format in guard_formats or []: |
| + contents = contents.replace(guard_format % original, |
| + guard_format % replacement) |
| + if contents == original_contents: |
| + raise Exception('No change in file %s although matched in grep' % |
| + referee) |
| + with open(referee, 'w') as f: |
| + f.write(contents) |
| + |
| + |
| +def UpdatePostMove(from_path, to_path): |
| + """Given a file that has moved from |from_path| to |to_path|, |
| + updates the moved file's include guard to match the new path and |
| + updates all references to the file in other source files and .gyp(i) |
| + files in the same git repository, with the assumption that they |
| + include it using the Chromium style guide-standard full path from |
| + root, or the .gyp(i) standard full path from root minus first path |
|
Nico
2012/11/20 18:32:39
The gyp standard is not full path from root minus
Jói
2012/11/20 22:21:03
Done.
|
| + component. |
| + """ |
| + # Include paths always use forward slashes. |
| + from_path = from_path.replace('\\', '/') |
| + to_path = to_path.replace('\\', '/') |
| + |
| + if os.path.splitext(from_path)[1] in ['.h', '.hh']: |
| UpdateIncludeGuard(from_path, to_path) |
| - UpdateIncludes(from_path, to_path) |
| + |
| + # Update include/import references. |
| + MultiFileFindReplace( |
| + from_path, |
| + to_path, |
| + r'#(include|import)\s*["<]%s[>"]', |
| + ['*.cc', '*.h', '*.m', '*.mm'], |
| + ['"%s"', '<%s>']) |
| + |
| + # Update references in .gyp(i) files. |
| + def PathMinusFirstComponent(path): |
|
Nico
2012/11/20 18:32:39
nit: Add "# foo/bar/baz -> bar/baz", this makes it
Jói
2012/11/20 22:21:03
Done.
|
| + parts = re.split(r"[/\\]", path, 1) |
| + if len(parts) == 2: |
| + return parts[1] |
| + else: |
| + return parts[0] |
| + MultiFileFindReplace(PathMinusFirstComponent(from_path), |
| + PathMinusFirstComponent(to_path), |
| + r'[\'"]%s[\'"]', |
| + ['*.gyp*'], |
| + ["'%s'", '"%s"']) |
| def MakeIncludeGuardName(path_from_root): |
| @@ -79,41 +165,28 @@ def UpdateIncludeGuard(old_path, new_path): |
| f.write(new_contents) |
| -def UpdateIncludes(old_path, new_path): |
| - """Given the |old_path| and |new_path| of a file being moved, update |
| - #include and #import statements in all files in the same git |
| - repository referring to the moved file. |
| - """ |
| - # Include paths always use forward slashes. |
| - old_path = old_path.replace('\\', '/') |
| - new_path = new_path.replace('\\', '/') |
| - |
| - out, err = subprocess.Popen( |
| - ['git', 'grep', '--name-only', |
| - r'#\(include\|import\)\s*["<]%s[>"]' % old_path], |
| - stdout=subprocess.PIPE).communicate() |
| - includees = out.splitlines() |
| - |
| - for includee in includees: |
| - with open(includee) as f: |
| - contents = f.read() |
| - new_contents = contents.replace('"%s"' % old_path, '"%s"' % new_path) |
| - new_contents = new_contents.replace('<%s>' % old_path, '<%s>' % new_path) |
| - if new_contents == contents: |
| - raise Exception('Error updating include in file %s' % includee) |
| - with open(includee, 'w') as f: |
| - f.write(new_contents) |
| - |
| - |
| def main(): |
| if not os.path.isdir('.git'): |
| print 'Fatal: You must run from the root of a git checkout.' |
| return 1 |
| args = sys.argv[1:] |
| - if len(args) != 2: |
| - print 'Usage: move_file.py FROM_PATH TO_PATH\n\n%s' % __doc__ |
| + if not len(args) in [2, 3]: |
| + print ('Usage: move_source_file.py [--already-moved] FROM_PATH TO_PATH' |
| + '\n\n%s' % __doc__) |
| return 1 |
| - MoveFile(args[0], args[1]) |
| + |
| + already_moved = False |
| + if args[0] == '--already-moved': |
| + args = args[1:] |
| + already_moved = True |
| + |
| + from_path = args[0] |
| + to_path = args[1] |
| + |
| + to_path = MakeDestinationPath(from_path, to_path) |
| + if not already_moved: |
| + MoveFile(from_path, to_path) |
| + UpdatePostMove(from_path, to_path) |
| return 0 |