Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(323)

Side by Side Diff: tools/git/move_source_file.py

Issue 11358216: Consolidate mass-rename.sh and move_source_file.py (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Respond to review comments. Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/git/mass-rename.sh ('k') | tools/refactor/move_file.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Moves a C++ file to a new location, updating any include paths that 6 """Moves a C++ file to a new location, updating any include paths that
7 point to it. Updates include guards in moved header files. Assumes 7 point to it. Updates include guards in moved header files. Assumes
8 Chromium coding style. 8 Chromium coding style.
9 9
10 Does not reorder headers (you can use tools/sort-headers.py), and does 10 Does not reorder headers; instead, use this after committing all of
11 not update .gypi files. 11 your moves:
12 ./tools/git/for-all-touched-files.py -c "tools/sort-headers.py [[FILENAME]]"
12 13
13 Relies on git for a fast way to find files that include the moved file. 14 Updates paths used in .gyp(i) files, but does not reorder or
15 restructure .gyp(i) files in any way.
16
17 Must run in a git checkout, as it relies on git for a fast way to find
18 files that reference the moved file.
14 """ 19 """
15 20
16 21
17 import os 22 import os
23 import re
18 import subprocess 24 import subprocess
19 import sys 25 import sys
20 26
21
22 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] 27 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh']
23 28
24 29
25 def MoveFile(from_path, to_path): 30 def MakeDestinationPath(from_path, to_path):
26 """Moves a file from |from_path| to |to_path|, updating its include 31 """Given the from and to paths, return a correct destination path.
27 guard to match the new path and updating all #includes and #imports 32
28 of the file in other files in the same git repository, with the 33 The initial destination path may either a full path or a directory,
29 assumption that they include it using the Chromium style 34 in which case the path must end with /. Also does basic sanity
30 guide-standard full path from root. 35 checks.
31 """ 36 """
32 extension = os.path.splitext(from_path)[1] 37 if os.path.splitext(from_path)[1] not in HANDLED_EXTENSIONS:
33 if extension not in HANDLED_EXTENSIONS:
34 raise Exception('Only intended to move individual source files.') 38 raise Exception('Only intended to move individual source files.')
35
36 dest_extension = os.path.splitext(to_path)[1] 39 dest_extension = os.path.splitext(to_path)[1]
37 if dest_extension not in HANDLED_EXTENSIONS: 40 if dest_extension not in HANDLED_EXTENSIONS:
38 if to_path.endswith('/') or to_path.endswith('\\'): 41 if to_path.endswith('/') or to_path.endswith('\\'):
39 to_path += os.path.basename(from_path) 42 to_path += os.path.basename(from_path)
40 else: 43 else:
41 raise Exception('Destination must be either full path or end with /.') 44 raise Exception('Destination must be either full path or end with /.')
45 return to_path
42 46
47
48 def MoveFile(from_path, to_path):
49 """Performs a git mv command to move a file from |from_path| to |to_path|.
50 """
43 if not os.system('git mv %s %s' % (from_path, to_path)) == 0: 51 if not os.system('git mv %s %s' % (from_path, to_path)) == 0:
44 raise Exception('Fatal: Failed to run git mv command.') 52 raise Exception('Fatal: Failed to run git mv command.')
45 53
46 if extension in ['.h', '.hh']: 54
55 def MultiFileFindReplace(original,
56 replacement,
57 grep_pattern,
58 file_globs,
59 guard_formats):
60 """Implements fast multi-file find and replace with optional guards.
61
62 Given an |original| string and a |replacement| string, search for
63 them by formatting |grep_pattern| with |original| and running
64 git grep on the result, for files matching any of |file_globs|.
65
66 Once files are found, the function searches for any of
67 |guard_formats| formatted with |original| and replaces each match
68 with the same guard format as matched, formatted with |replacement|.
69
70 Args:
71 original: 'chrome/browser/ui/browser.h'
72 replacement: 'chrome/browser/ui/browser/browser.h'
73 grep_pattern: r'#(include|import)\s*["<]%s[>"]'
74 file_globs: ['*.cc', '*.h', '*.m', '*.mm']
75 guard_formats: None or ('"%s"', '<%s>')
76
77 Raises an exception on error.
78 """
79 out, err = subprocess.Popen(
80 ['git', 'grep', '-E', '--name-only',
81 grep_pattern % re.escape(original), '--'] + file_globs,
82 stdout=subprocess.PIPE).communicate()
83 referees = out.splitlines()
84
85 for referee in referees:
86 with open(referee) as f:
87 original_contents = f.read()
88 contents = original_contents
89 for guard_format in guard_formats or []:
90 contents = contents.replace(guard_format % original,
91 guard_format % replacement)
92 if contents == original_contents:
93 raise Exception('No change in file %s although matched in grep' %
94 referee)
95 with open(referee, 'w') as f:
96 f.write(contents)
97
98
99 def UpdatePostMove(from_path, to_path):
100 """Given a file that has moved from |from_path| to |to_path|,
101 updates the moved file's include guard to match the new path and
102 updates all references to the file in other source files. Also tries
103 to update references in .gyp(i) files using a heuristic.
104 """
105 # Include paths always use forward slashes.
106 from_path = from_path.replace('\\', '/')
107 to_path = to_path.replace('\\', '/')
108
109 if os.path.splitext(from_path)[1] in ['.h', '.hh']:
47 UpdateIncludeGuard(from_path, to_path) 110 UpdateIncludeGuard(from_path, to_path)
48 UpdateIncludes(from_path, to_path) 111
112 # Update include/import references.
113 MultiFileFindReplace(
114 from_path,
115 to_path,
116 r'#(include|import)\s*["<]%s[>"]',
117 ['*.cc', '*.h', '*.m', '*.mm'],
118 ['"%s"', '<%s>'])
119
120 # Update references in .gyp(i) files.
121 def PathMinusFirstComponent(path):
122 """foo/bar/baz -> bar/baz"""
123 parts = re.split(r"[/\\]", path, 1)
124 if len(parts) == 2:
125 return parts[1]
126 else:
127 return parts[0]
128 MultiFileFindReplace(PathMinusFirstComponent(from_path),
129 PathMinusFirstComponent(to_path),
130 r'[\'"]%s[\'"]',
131 ['*.gyp*'],
132 ["'%s'", '"%s"'])
49 133
50 134
51 def MakeIncludeGuardName(path_from_root): 135 def MakeIncludeGuardName(path_from_root):
52 """Returns an include guard name given a path from root.""" 136 """Returns an include guard name given a path from root."""
53 guard = path_from_root.replace('/', '_') 137 guard = path_from_root.replace('/', '_')
54 guard = guard.replace('\\', '_') 138 guard = guard.replace('\\', '_')
55 guard = guard.replace('.', '_') 139 guard = guard.replace('.', '_')
56 guard += '_' 140 guard += '_'
57 return guard.upper() 141 return guard.upper()
58 142
(...skipping 13 matching lines...) Expand all
72 156
73 new_contents = contents.replace(old_guard, new_guard) 157 new_contents = contents.replace(old_guard, new_guard)
74 if new_contents == contents: 158 if new_contents == contents:
75 raise Exception( 159 raise Exception(
76 'Error updating include guard; perhaps old guard is not per style guide?') 160 'Error updating include guard; perhaps old guard is not per style guide?')
77 161
78 with open(new_path, 'w') as f: 162 with open(new_path, 'w') as f:
79 f.write(new_contents) 163 f.write(new_contents)
80 164
81 165
82 def UpdateIncludes(old_path, new_path):
83 """Given the |old_path| and |new_path| of a file being moved, update
84 #include and #import statements in all files in the same git
85 repository referring to the moved file.
86 """
87 # Include paths always use forward slashes.
88 old_path = old_path.replace('\\', '/')
89 new_path = new_path.replace('\\', '/')
90
91 out, err = subprocess.Popen(
92 ['git', 'grep', '--name-only',
93 r'#\(include\|import\)\s*["<]%s[>"]' % old_path],
94 stdout=subprocess.PIPE).communicate()
95 includees = out.splitlines()
96
97 for includee in includees:
98 with open(includee) as f:
99 contents = f.read()
100 new_contents = contents.replace('"%s"' % old_path, '"%s"' % new_path)
101 new_contents = new_contents.replace('<%s>' % old_path, '<%s>' % new_path)
102 if new_contents == contents:
103 raise Exception('Error updating include in file %s' % includee)
104 with open(includee, 'w') as f:
105 f.write(new_contents)
106
107
108 def main(): 166 def main():
109 if not os.path.isdir('.git'): 167 if not os.path.isdir('.git'):
110 print 'Fatal: You must run from the root of a git checkout.' 168 print 'Fatal: You must run from the root of a git checkout.'
111 return 1 169 return 1
112 args = sys.argv[1:] 170 args = sys.argv[1:]
113 if len(args) != 2: 171 if not len(args) in [2, 3]:
114 print 'Usage: move_file.py FROM_PATH TO_PATH\n\n%s' % __doc__ 172 print ('Usage: move_source_file.py [--already-moved] FROM_PATH TO_PATH'
173 '\n\n%s' % __doc__)
115 return 1 174 return 1
116 MoveFile(args[0], args[1]) 175
176 already_moved = False
177 if args[0] == '--already-moved':
178 args = args[1:]
179 already_moved = True
180
181 from_path = args[0]
182 to_path = args[1]
183
184 to_path = MakeDestinationPath(from_path, to_path)
185 if not already_moved:
186 MoveFile(from_path, to_path)
187 UpdatePostMove(from_path, to_path)
117 return 0 188 return 0
118 189
119 190
120 if __name__ == '__main__': 191 if __name__ == '__main__':
121 sys.exit(main()) 192 sys.exit(main())
OLDNEW
« no previous file with comments | « tools/git/mass-rename.sh ('k') | tools/refactor/move_file.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698