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

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: Add support for comments. Created 8 years, 1 month 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 matchers=None):
61 """Implements fast multi-file find and replace with optional guards.
62
63 Given an |original| string and a |replacement| string, search for
64 them by formatting |grep_pattern| with |original| and running git
65 grep on the result, for files matching any of |file_globs|.
66
67 Once files are found, two things are done in this sequence:
68
69 a) Search for any of |guard_formats| formatted with |original| and
70 replace each match with the same guard format as matched, formatted
71 with |replacement|.
72
73 b) Call re.sub(matcher, ...) for each of |matchers| in a way that
74 replaces the matched string with the matched string with |original|
75 replaced with |replacement|.
76
77 Args:
78 original: 'chrome/browser/ui/browser.h'
79 replacement: 'chrome/browser/ui/browser/browser.h'
80 grep_pattern: r'#\(include\|import\)\s*["<]%s[>"]'
81 file_globs: ['*.cc', '*.h', '*.m', '*.mm']
82 guard_formats: None or ('"%s"', '<%s>')
83 matchers: None or ('//.*%s' % original, )
84
85 Raises an exception on error.
86 """
87 out, err = subprocess.Popen(
88 ['git', 'grep', '--name-only',
89 grep_pattern % original, '--'] + file_globs,
Nico 2012/11/15 18:19:02 If you pass -E, then the called doesn't have to es
Jói 2012/11/20 14:43:12 Done.
90 stdout=subprocess.PIPE).communicate()
91 referees = out.splitlines()
92
93 for referee in referees:
94 with open(referee) as f:
95 original_contents = f.read()
96 contents = original_contents
97 for guard_format in guard_formats or []:
98 contents = contents.replace(guard_format % original,
99 guard_format % replacement)
100 for matcher in matchers or []:
101 def ReplaceMatch(match_obj):
102 return match_obj.group(0).replace(original, replacement)
103 contents = re.sub(matcher, ReplaceMatch, contents)
104 if contents == original_contents:
105 raise Exception('No change in file %s although matched in grep' %
106 referee)
107 with open(referee, 'w') as f:
108 f.write(contents)
109
110
111 def UpdatePostMove(from_path, to_path):
112 """Given a file that has moved from |from_path| to |to_path|,
113 updates the moved file's include guard to match the new path and
114 updates all references to the file in other source files and .gyp(i)
115 files in the same git repository, with the assumption that they
116 include it using the Chromium style guide-standard full path from
117 root, or the .gyp(i) standard full path from root minus first path
118 component.
119 """
120 # Include paths always use forward slashes.
121 from_path = from_path.replace('\\', '/')
122 to_path = to_path.replace('\\', '/')
123
124 if os.path.splitext(from_path)[1] in ['.h', '.hh']:
47 UpdateIncludeGuard(from_path, to_path) 125 UpdateIncludeGuard(from_path, to_path)
48 UpdateIncludes(from_path, to_path) 126
127 # Update include/import references.
128 MultiFileFindReplace(
129 from_path,
130 to_path,
131 r'#\(include\|import\)\s*["<]%s[>"]',
132 ['*.cc', '*.h', '*.m', '*.mm'],
133 ['"%s"', '<%s>'])
134
135 # Update comments; only supports // comments, which are primarily
136 # used in our code.
137 MultiFileFindReplace(
138 from_path,
139 to_path,
140 r'//.*%s',
141 ['*.cc', '*.h', '*.m', '*.mm'],
142 guard_formats=None,
143 matchers=['//.*%s' % from_path])
144
145 # Update references in .gyp(i) files.
146 def PathMinusFirstComponent(path):
147 parts = re.split(r"[/\\]", path, 1)
148 if len(parts) == 2:
149 return parts[1]
150 else:
151 return parts[0]
152 MultiFileFindReplace(PathMinusFirstComponent(from_path),
153 PathMinusFirstComponent(to_path),
154 r'[\'"]%s[\'"]',
155 ['*.gyp*'],
156 ["'%s'", '"%s"'])
49 157
50 158
51 def MakeIncludeGuardName(path_from_root): 159 def MakeIncludeGuardName(path_from_root):
52 """Returns an include guard name given a path from root.""" 160 """Returns an include guard name given a path from root."""
53 guard = path_from_root.replace('/', '_') 161 guard = path_from_root.replace('/', '_')
54 guard = guard.replace('\\', '_') 162 guard = guard.replace('\\', '_')
55 guard = guard.replace('.', '_') 163 guard = guard.replace('.', '_')
56 guard += '_' 164 guard += '_'
57 return guard.upper() 165 return guard.upper()
58 166
(...skipping 13 matching lines...) Expand all
72 180
73 new_contents = contents.replace(old_guard, new_guard) 181 new_contents = contents.replace(old_guard, new_guard)
74 if new_contents == contents: 182 if new_contents == contents:
75 raise Exception( 183 raise Exception(
76 'Error updating include guard; perhaps old guard is not per style guide?') 184 'Error updating include guard; perhaps old guard is not per style guide?')
77 185
78 with open(new_path, 'w') as f: 186 with open(new_path, 'w') as f:
79 f.write(new_contents) 187 f.write(new_contents)
80 188
81 189
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(): 190 def main():
109 if not os.path.isdir('.git'): 191 if not os.path.isdir('.git'):
110 print 'Fatal: You must run from the root of a git checkout.' 192 print 'Fatal: You must run from the root of a git checkout.'
111 return 1 193 return 1
112 args = sys.argv[1:] 194 args = sys.argv[1:]
113 if len(args) != 2: 195 if not len(args) in [2, 3]:
114 print 'Usage: move_file.py FROM_PATH TO_PATH\n\n%s' % __doc__ 196 print ('Usage: move_source_file.py [--already-moved] FROM_PATH TO_PATH'
197 '\n\n%s' % __doc__)
115 return 1 198 return 1
116 MoveFile(args[0], args[1]) 199
200 already_moved = False
201 if args[0] == '--already-moved':
202 args = args[1:]
203 already_moved = True
204
205 from_path = args[0]
206 to_path = args[1]
207
208 to_path = MakeDestinationPath(from_path, to_path)
209 if not already_moved:
210 MoveFile(from_path, to_path)
211 UpdatePostMove(from_path, to_path)
117 return 0 212 return 0
118 213
119 214
120 if __name__ == '__main__': 215 if __name__ == '__main__':
121 sys.exit(main()) 216 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