OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2009 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 """Wrapper for trychange.py for git checkout.""" | 5 """Wrapper for trychange.py for git checkout.""" |
6 | 6 |
7 import getpass | |
8 import optparse | |
9 import os | |
10 import re | |
11 import subprocess | |
12 import sys | 7 import sys |
13 import tempfile | |
14 import traceback | |
15 import urllib | |
16 | 8 |
17 import breakpad | 9 try: |
| 10 import breakpad |
| 11 except ImportError: |
| 12 pass |
18 | 13 |
| 14 from scm import GIT |
19 import trychange | 15 import trychange |
20 | 16 |
21 | 17 |
22 def Backquote(cmd, cwd=None): | |
23 """Like running `cmd` in a shell script.""" | |
24 return subprocess.Popen(cmd, | |
25 cwd=cwd, | |
26 stdout=subprocess.PIPE).communicate()[0].strip() | |
27 | |
28 | |
29 def GetTryServerConfig(): | |
30 """Returns the dictionary of try server options or None if they | |
31 cannot be found.""" | |
32 script_path = 'tools/tryserver/tryserver.py' | |
33 root_dir = Backquote(['git', 'rev-parse', '--show-cdup']) | |
34 try: | |
35 script_file = open(os.path.join(root_dir, script_path)) | |
36 except IOError: | |
37 return None | |
38 locals = {} | |
39 try: | |
40 exec(script_file, locals) | |
41 except Exception, e: | |
42 return None | |
43 return locals | |
44 | |
45 | |
46 def GetBranchName(working_dir=None): | |
47 """Return name of current git branch.""" | |
48 branch = Backquote(['git', 'symbolic-ref', 'HEAD'], working_dir) | |
49 if not branch.startswith('refs/heads/'): | |
50 raise "Couldn't figure out branch name" | |
51 branch = branch[len('refs/heads/'):] | |
52 return branch | |
53 | |
54 | |
55 def GetPatchName(working_dir=None): | |
56 """Construct a name for this patch.""" | |
57 short_sha = Backquote(['git', 'rev-parse', '--short=4', 'HEAD'], working_dir) | |
58 return GetBranchName() + '-' + short_sha | |
59 | |
60 | |
61 def GetRietveldIssueNumber(): | 18 def GetRietveldIssueNumber(): |
62 return Backquote(['git', 'config', | 19 return GIT.Capture( |
63 'branch.%s.rietveldissue' % GetBranchName()]) | 20 ['config', 'branch.%s.rietveldissue' % GIT.GetBranch(None)], |
| 21 error_ok=True) |
64 | 22 |
65 | 23 |
66 def GetRietveldPatchsetNumber(): | 24 def GetRietveldPatchsetNumber(): |
67 return Backquote(['git', 'config', | 25 return GIT.Capture( |
68 'branch.%s.rietveldpatchset' % GetBranchName()]) | 26 ['config', 'branch.%s.rietveldpatchset' % GIT.GetBranch(None)], |
69 | 27 error_ok=True) |
70 def GetSubRepWorkingDir(sub_rep_path): | |
71 """Computes the path to the sub repository""" | |
72 if sub_rep_path: | |
73 root_dir = os.path.abspath(Backquote(['git', 'rev-parse', '--show-cdup'])) | |
74 return os.path.join(root_dir, sub_rep_path) | |
75 return None | |
76 | |
77 def GetMungedDiff(branch, prefix, sub_rep_path): | |
78 """Get the diff we'll send to the try server. We munge paths to match svn. | |
79 We add the prefix that the try bot is expecting. If sub_rep_path is | |
80 provided, diff will be calculated in the sub repository. | |
81 We also return the list of files in this diff, without munged paths.""" | |
82 # Make the following changes: | |
83 # - Prepend "src/" (or some other prefix) to paths as svn is expecting | |
84 # - In the case of added files, replace /dev/null with the path to the file | |
85 # being added. | |
86 | |
87 cwd = GetSubRepWorkingDir(sub_rep_path) | |
88 | |
89 output = [] | |
90 if not branch: | |
91 # Try to guess the upstream branch. | |
92 branch = Backquote(['git', 'cl', 'upstream'], cwd) | |
93 command = ['git', 'diff-tree', '-p'] | |
94 | |
95 new_cwd = None | |
96 if not sub_rep_path: | |
97 command.extend(['--no-prefix']) | |
98 else: | |
99 # Append / | |
100 sub_rep_path = os.path.join(sub_rep_path, '') | |
101 # Add the right prefix | |
102 command.extend(['--src-prefix=' + sub_rep_path]) | |
103 command.extend(['--dst-prefix=' + sub_rep_path]) | |
104 | |
105 command.extend([branch, 'HEAD']) | |
106 | |
107 # Run diff tree | |
108 diff = subprocess.Popen(command, | |
109 stdout=subprocess.PIPE, | |
110 cwd=cwd).stdout.readlines() | |
111 # Replace --- /dev/null with --- <new file name> | |
112 for i in range(len(diff)): | |
113 line = diff[i] | |
114 if line.startswith('--- /dev/null'): | |
115 line = '--- %s' % diff[i+1][4:] | |
116 output.append(line) | |
117 diff = output | |
118 | |
119 # Add root prefix | |
120 output = [] | |
121 file_set = set() | |
122 for line in diff: | |
123 if line.startswith('--- ') or line.startswith('+++ '): | |
124 filename = line[4:] | |
125 line = line[0:4] + os.path.join(prefix, filename) | |
126 file_set.add(filename.rstrip('\r\n')) | |
127 output.append(line) | |
128 | |
129 munged_diff = ''.join(output) | |
130 if len(munged_diff.strip()) == 0: | |
131 raise Exception("Patch was empty, did you give the right remote branch?") | |
132 | |
133 return (munged_diff, list(file_set)) | |
134 | |
135 def OneRepositoryDiff(diff_file, patch_names, branch, prefix, sub_rep_path): | |
136 """Computes a diff for one git repository at a given path against a given | |
137 branch. Writes the diff into diff_file and appends a name to the | |
138 patch_names list. | |
139 | |
140 Returns the list of files in the diff.""" | |
141 | |
142 (diff, file_list) = GetMungedDiff(branch, prefix, sub_rep_path) | |
143 | |
144 # Write the diff out | |
145 diff_file.write(diff) | |
146 | |
147 # Add patch name to list of patches | |
148 patch_name = GetPatchName(GetSubRepWorkingDir(sub_rep_path)) | |
149 patch_names.extend([patch_name]) | |
150 return file_list | |
151 | |
152 | |
153 def ValidEmail(email): | |
154 return re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) | |
155 | |
156 | |
157 def GetEmail(): | |
158 email = Backquote(['git', 'config', 'user.email']) | |
159 runmsg = "Try: git config user.email <EMAIL>" | |
160 assert ValidEmail(email), "Email '%s' is not valid. %s" % (email, runmsg) | |
161 return email | |
162 | |
163 | |
164 def TryChange(args, file_list): | |
165 """Put a patch on the try server.""" | |
166 trychange.TryChange(args, file_list, False) | |
167 | 28 |
168 | 29 |
169 if __name__ == '__main__': | 30 if __name__ == '__main__': |
170 parser = optparse.OptionParser( | 31 args = sys.argv[:] |
171 usage='git try [options] [branch]', | 32 patchset = GetRietveldPatchsetNumber() |
172 description='Upload the current diff of branch...HEAD to the try server.') | 33 if patchset: |
173 parser.add_option("-b", "--bot", action="append", | |
174 help="Force the use of a specific build slave (eg mac, " | |
175 "win, or linux)") | |
176 parser.add_option("-c", "--clobber", action="store_true", | |
177 help="Make the try run use be a clobber build") | |
178 parser.add_option("-r", "--revision", | |
179 help="Specify the SVN base revision to use") | |
180 parser.add_option("--root", default="src", metavar="PATH", | |
181 help="Specify the root prefix that is prepended to paths " | |
182 "in the patch") | |
183 parser.add_option("--dry_run", action="store_true", | |
184 help="Print the diff but don't send it to the try bots") | |
185 parser.add_option("--sub_rep", nargs=2, action="append", default=[], | |
186 metavar="PATH BRANCH", | |
187 help="Specify a path to a git sub-repository and a branch " | |
188 "to diff with in order to simultanously try changes " | |
189 "in multiple git repositories. Option may be " | |
190 "specified multiple times.") | |
191 parser.add_option("--webkit", metavar="BRANCH", | |
192 help="Specify webkit branch. Syntactic sugar for " | |
193 "--sub_rep third_party/WebKit/ <branch>") | |
194 | |
195 (options, args) = parser.parse_args(sys.argv) | |
196 | |
197 if options.webkit: | |
198 options.sub_rep.extend([('third_party/WebKit/', options.webkit)]) | |
199 | |
200 branch = None | |
201 if len(args) > 1: | |
202 branch = args[1] | |
203 patch_names = [] | |
204 | |
205 # Dump all diffs into one diff file. | |
206 diff_file = tempfile.NamedTemporaryFile() | |
207 | |
208 # Calculate diff for main git repository. | |
209 file_list = OneRepositoryDiff(diff_file, patch_names, branch, options.root, | |
210 None) | |
211 | |
212 # Calculate diff for each extra git repository. | |
213 for path_and_branch in options.sub_rep: | |
214 file_list.extend(OneRepositoryDiff(diff_file, | |
215 patch_names, | |
216 path_and_branch[1], | |
217 options.root, | |
218 path_and_branch[0])) | |
219 # Make diff file ready for reading. | |
220 diff_file.flush() | |
221 | |
222 # Concatenate patch names | |
223 # Prepare args for TryChange | |
224 email = GetEmail() | |
225 user = email.partition('@')[0] | |
226 args = [ | |
227 '-u', user, | |
228 '-e', email, | |
229 '-n', '-'.join(patch_names), | |
230 '--diff', diff_file.name, | |
231 ] | |
232 | |
233 # Send to try server via HTTP if we can parse the config, otherwise | |
234 # upload via SVN. | |
235 config = GetTryServerConfig() | |
236 if config is not None: | |
237 sendmsg = "Sending %s using HTTP..." % '-'.join(patch_names) | |
238 args.extend(['--use_http']) | |
239 if config['try_server_http_host'] is not None: | |
240 args.extend(['--host', config['try_server_http_host']]) | |
241 if config['try_server_http_port'] is not None: | |
242 args.extend(['--port', config['try_server_http_port']]) | |
243 | |
244 else: | |
245 print "Could not get server config -- if you're within Google, " | |
246 print "do you have have src-internal checked out?" | |
247 sendmsg = "Sending %s using SVN..." % '-'.join(patch_names) | |
248 args.extend([ | |
249 '--use_svn', '--svn_repo', | |
250 'svn://svn.chromium.org/chrome-try/try', | |
251 ]) | |
252 | |
253 if options.bot: | |
254 for bot in options.bot: | |
255 args.extend(['--bot', bot]) | |
256 if options.clobber: | |
257 args.append('--clobber') | |
258 if options.revision: | |
259 args.extend(['-r', options.revision]) | |
260 if GetRietveldPatchsetNumber(): | |
261 args.extend([ | 34 args.extend([ |
262 '--issue', GetRietveldIssueNumber(), | 35 '--issue', GetRietveldIssueNumber(), |
263 '--patchset', GetRietveldPatchsetNumber(), | 36 '--patchset', patchset, |
264 ]) | 37 ]) |
265 | 38 sys.exit(trychange.TryChange(args, [], False, 'git-try')) |
266 if options.dry_run: | |
267 print open(diff_file.name, 'r').read() | |
268 exit(0) | |
269 | |
270 print sendmsg | |
271 TryChange(args, file_list) | |
OLD | NEW |