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

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: Address review 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):
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
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 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.
64 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 and .gyp(i)
103 files in the same git repository, with the assumption that they
104 include it using the Chromium style guide-standard full path from
105 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.
106 component.
107 """
108 # Include paths always use forward slashes.
109 from_path = from_path.replace('\\', '/')
110 to_path = to_path.replace('\\', '/')
111
112 if os.path.splitext(from_path)[1] in ['.h', '.hh']:
47 UpdateIncludeGuard(from_path, to_path) 113 UpdateIncludeGuard(from_path, to_path)
48 UpdateIncludes(from_path, to_path) 114
115 # Update include/import references.
116 MultiFileFindReplace(
117 from_path,
118 to_path,
119 r'#(include|import)\s*["<]%s[>"]',
120 ['*.cc', '*.h', '*.m', '*.mm'],
121 ['"%s"', '<%s>'])
122
123 # Update references in .gyp(i) files.
124 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.
125 parts = re.split(r"[/\\]", path, 1)
126 if len(parts) == 2:
127 return parts[1]
128 else:
129 return parts[0]
130 MultiFileFindReplace(PathMinusFirstComponent(from_path),
131 PathMinusFirstComponent(to_path),
132 r'[\'"]%s[\'"]',
133 ['*.gyp*'],
134 ["'%s'", '"%s"'])
49 135
50 136
51 def MakeIncludeGuardName(path_from_root): 137 def MakeIncludeGuardName(path_from_root):
52 """Returns an include guard name given a path from root.""" 138 """Returns an include guard name given a path from root."""
53 guard = path_from_root.replace('/', '_') 139 guard = path_from_root.replace('/', '_')
54 guard = guard.replace('\\', '_') 140 guard = guard.replace('\\', '_')
55 guard = guard.replace('.', '_') 141 guard = guard.replace('.', '_')
56 guard += '_' 142 guard += '_'
57 return guard.upper() 143 return guard.upper()
58 144
(...skipping 13 matching lines...) Expand all
72 158
73 new_contents = contents.replace(old_guard, new_guard) 159 new_contents = contents.replace(old_guard, new_guard)
74 if new_contents == contents: 160 if new_contents == contents:
75 raise Exception( 161 raise Exception(
76 'Error updating include guard; perhaps old guard is not per style guide?') 162 'Error updating include guard; perhaps old guard is not per style guide?')
77 163
78 with open(new_path, 'w') as f: 164 with open(new_path, 'w') as f:
79 f.write(new_contents) 165 f.write(new_contents)
80 166
81 167
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(): 168 def main():
109 if not os.path.isdir('.git'): 169 if not os.path.isdir('.git'):
110 print 'Fatal: You must run from the root of a git checkout.' 170 print 'Fatal: You must run from the root of a git checkout.'
111 return 1 171 return 1
112 args = sys.argv[1:] 172 args = sys.argv[1:]
113 if len(args) != 2: 173 if not len(args) in [2, 3]:
114 print 'Usage: move_file.py FROM_PATH TO_PATH\n\n%s' % __doc__ 174 print ('Usage: move_source_file.py [--already-moved] FROM_PATH TO_PATH'
175 '\n\n%s' % __doc__)
115 return 1 176 return 1
116 MoveFile(args[0], args[1]) 177
178 already_moved = False
179 if args[0] == '--already-moved':
180 args = args[1:]
181 already_moved = True
182
183 from_path = args[0]
184 to_path = args[1]
185
186 to_path = MakeDestinationPath(from_path, to_path)
187 if not already_moved:
188 MoveFile(from_path, to_path)
189 UpdatePostMove(from_path, to_path)
117 return 0 190 return 0
118 191
119 192
120 if __name__ == '__main__': 193 if __name__ == '__main__':
121 sys.exit(main()) 194 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