OLD | NEW |
---|---|
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 | 27 |
22 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] | 28 HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh'] |
23 | 29 |
24 | 30 |
25 def MoveFile(from_path, to_path): | 31 def MoveFile(from_path, to_path, already_moved): |
Nico
2012/11/14 19:51:36
It's a bit confusing that this is called MoveFile
Jói
2012/11/15 15:13:10
Done.
| |
26 """Moves a file from |from_path| to |to_path|, updating its include | 32 """Moves a file from |from_path| to |to_path| (unless |
27 guard to match the new path and updating all #includes and #imports | 33 |already_moved|), then updates the moved file's include guard to |
28 of the file in other files in the same git repository, with the | 34 match the new path and updates all references to the file in other |
35 source files and .gyp(i) files in the same git repository, with the | |
29 assumption that they include it using the Chromium style | 36 assumption that they include it using the Chromium style |
30 guide-standard full path from root. | 37 guide-standard full path from root, or the .gyp(i) standard full |
38 path from root minus first path component. | |
31 """ | 39 """ |
32 extension = os.path.splitext(from_path)[1] | 40 extension = os.path.splitext(from_path)[1] |
33 if extension not in HANDLED_EXTENSIONS: | |
34 raise Exception('Only intended to move individual source files.') | |
35 | 41 |
36 dest_extension = os.path.splitext(to_path)[1] | 42 if not already_moved: |
37 if dest_extension not in HANDLED_EXTENSIONS: | 43 if extension not in HANDLED_EXTENSIONS: |
38 if to_path.endswith('/') or to_path.endswith('\\'): | 44 raise Exception('Only intended to move individual source files.') |
39 to_path += os.path.basename(from_path) | 45 dest_extension = os.path.splitext(to_path)[1] |
40 else: | 46 if dest_extension not in HANDLED_EXTENSIONS: |
41 raise Exception('Destination must be either full path or end with /.') | 47 if to_path.endswith('/') or to_path.endswith('\\'): |
48 to_path += os.path.basename(from_path) | |
49 else: | |
50 raise Exception('Destination must be either full path or end with /.') | |
42 | 51 |
43 if not os.system('git mv %s %s' % (from_path, to_path)) == 0: | 52 if not os.system('git mv %s %s' % (from_path, to_path)) == 0: |
44 raise Exception('Fatal: Failed to run git mv command.') | 53 raise Exception('Fatal: Failed to run git mv command.') |
45 | 54 |
46 if extension in ['.h', '.hh']: | 55 if extension in ['.h', '.hh']: |
47 UpdateIncludeGuard(from_path, to_path) | 56 UpdateIncludeGuard(from_path, to_path) |
48 UpdateIncludes(from_path, to_path) | 57 UpdateUsages(from_path, to_path, includes=True) |
58 | |
59 UpdateUsages(from_path, to_path, includes=False) | |
49 | 60 |
50 | 61 |
51 def MakeIncludeGuardName(path_from_root): | 62 def MakeIncludeGuardName(path_from_root): |
52 """Returns an include guard name given a path from root.""" | 63 """Returns an include guard name given a path from root.""" |
53 guard = path_from_root.replace('/', '_') | 64 guard = path_from_root.replace('/', '_') |
54 guard = guard.replace('\\', '_') | 65 guard = guard.replace('\\', '_') |
55 guard = guard.replace('.', '_') | 66 guard = guard.replace('.', '_') |
56 guard += '_' | 67 guard += '_' |
57 return guard.upper() | 68 return guard.upper() |
58 | 69 |
(...skipping 13 matching lines...) Expand all Loading... | |
72 | 83 |
73 new_contents = contents.replace(old_guard, new_guard) | 84 new_contents = contents.replace(old_guard, new_guard) |
74 if new_contents == contents: | 85 if new_contents == contents: |
75 raise Exception( | 86 raise Exception( |
76 'Error updating include guard; perhaps old guard is not per style guide?') | 87 'Error updating include guard; perhaps old guard is not per style guide?') |
77 | 88 |
78 with open(new_path, 'w') as f: | 89 with open(new_path, 'w') as f: |
79 f.write(new_contents) | 90 f.write(new_contents) |
80 | 91 |
81 | 92 |
82 def UpdateIncludes(old_path, new_path): | 93 def UpdateUsages(old_path, new_path, includes): |
83 """Given the |old_path| and |new_path| of a file being moved, update | 94 """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 | 95 usages (#include, #import, or reference in a .gyp(i) file) in all files |
85 repository referring to the moved file. | 96 in the same git repository referring to the moved file. |
97 | |
98 Updates includes when |includes| is True, .gyp(i) files when | |
99 |includes| is False. | |
86 """ | 100 """ |
87 # Include paths always use forward slashes. | 101 # Include paths always use forward slashes. |
88 old_path = old_path.replace('\\', '/') | 102 old_path = old_path.replace('\\', '/') |
89 new_path = new_path.replace('\\', '/') | 103 new_path = new_path.replace('\\', '/') |
90 | 104 |
91 out, err = subprocess.Popen( | 105 if includes: |
92 ['git', 'grep', '--name-only', | 106 out, err = subprocess.Popen( |
93 r'#\(include\|import\)\s*["<]%s[>"]' % old_path], | 107 ['git', 'grep', '--name-only', |
94 stdout=subprocess.PIPE).communicate() | 108 r'#\(include\|import\)\s*["<]%s[>"]' % old_path, |
95 includees = out.splitlines() | 109 '--', '*.cc', '*.h', '*.m', '*.mm'], |
110 stdout=subprocess.PIPE).communicate() | |
111 includees = out.splitlines() | |
112 else: | |
113 def PathMinusFirstComponent(path): | |
114 parts = re.split(r"[/\\]", path, 1) | |
115 if len(parts) == 2: | |
116 return parts[1] | |
117 else: | |
118 return parts[0] | |
119 old_path = PathMinusFirstComponent(old_path) | |
120 new_path = PathMinusFirstComponent(new_path) | |
121 out, err = subprocess.Popen( | |
122 ['git', 'grep', '--name-only', | |
123 r'[\'"]%s[\'"]' % old_path, '--', '*.gyp*'], | |
124 stdout=subprocess.PIPE).communicate() | |
125 includees = out.splitlines() | |
96 | 126 |
97 for includee in includees: | 127 for includee in includees: |
98 with open(includee) as f: | 128 with open(includee) as f: |
99 contents = f.read() | 129 contents = f.read() |
100 new_contents = contents.replace('"%s"' % old_path, '"%s"' % new_path) | 130 new_contents = contents.replace('"%s"' % old_path, '"%s"' % new_path) |
101 new_contents = new_contents.replace('<%s>' % old_path, '<%s>' % new_path) | 131 if includes: |
132 new_contents = new_contents.replace('<%s>' % old_path, '<%s>' % new_path) | |
133 else: | |
134 new_contents = new_contents.replace("'%s'" % old_path, "'%s'" % new_path) | |
102 if new_contents == contents: | 135 if new_contents == contents: |
103 raise Exception('Error updating include in file %s' % includee) | 136 raise Exception('Error updating usage in file %s' % includee) |
104 with open(includee, 'w') as f: | 137 with open(includee, 'w') as f: |
105 f.write(new_contents) | 138 f.write(new_contents) |
106 | 139 |
107 | 140 |
108 def main(): | 141 def main(): |
109 if not os.path.isdir('.git'): | 142 if not os.path.isdir('.git'): |
110 print 'Fatal: You must run from the root of a git checkout.' | 143 print 'Fatal: You must run from the root of a git checkout.' |
111 return 1 | 144 return 1 |
112 args = sys.argv[1:] | 145 args = sys.argv[1:] |
113 if len(args) != 2: | 146 if not len(args) in [2, 3]: |
114 print 'Usage: move_file.py FROM_PATH TO_PATH\n\n%s' % __doc__ | 147 print ('Usage: move_source_file.py [--already-moved] FROM_PATH TO_PATH' |
148 '\n\n%s' % __doc__) | |
115 return 1 | 149 return 1 |
116 MoveFile(args[0], args[1]) | 150 already_moved = False |
151 if args[0] == '--already-moved': | |
152 args = args[1:] | |
153 already_moved = True | |
154 | |
155 MoveFile(args[0], args[1], already_moved) | |
117 return 0 | 156 return 0 |
118 | 157 |
119 | 158 |
120 if __name__ == '__main__': | 159 if __name__ == '__main__': |
121 sys.exit(main()) | 160 sys.exit(main()) |
OLD | NEW |