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 """Client-side script to send a try job to the try server. It communicates to | 5 """Client-side script to send a try job to the try server. It communicates to |
6 the try server by either writting to a svn repository or by directly connecting | 6 the try server by either writting to a svn repository or by directly connecting |
7 to the server by HTTP. | 7 to the server by HTTP. |
8 """ | 8 """ |
9 | 9 |
10 import datetime | 10 import datetime |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
65 def __str__(self): | 65 def __str__(self): |
66 return self.args[0] + '\n' + HELP_STRING | 66 return self.args[0] + '\n' + HELP_STRING |
67 | 67 |
68 | 68 |
69 def EscapeDot(name): | 69 def EscapeDot(name): |
70 return name.replace('.', '-') | 70 return name.replace('.', '-') |
71 | 71 |
72 | 72 |
73 class SCM(object): | 73 class SCM(object): |
74 """Simplistic base class to implement one function: ProcessOptions.""" | 74 """Simplistic base class to implement one function: ProcessOptions.""" |
75 def __init__(self, options): | 75 def __init__(self, options, cwd): |
| 76 self.checkout_root = cwd |
76 self.options = options | 77 self.options = options |
| 78 self.files = self.options.files |
| 79 self.options.files = None |
77 | 80 |
78 def GetFileNames(self): | 81 def GetFileNames(self): |
79 """Return the list of files in the diff.""" | 82 """Return the list of files in the diff.""" |
80 return self.options.files | 83 return self.files |
81 | 84 |
82 | 85 |
83 class SVN(SCM): | 86 class SVN(SCM): |
84 """Gathers the options and diff for a subversion checkout.""" | 87 """Gathers the options and diff for a subversion checkout.""" |
85 def __init__(self, *args, **kwargs): | 88 def __init__(self, *args, **kwargs): |
86 SCM.__init__(self, *args, **kwargs) | 89 SCM.__init__(self, *args, **kwargs) |
87 self.checkout_root = scm.SVN.GetCheckoutRoot(os.getcwd()) | 90 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) |
88 if not self.options.diff: | |
89 # Generate the diff from the scm. | |
90 self.options.diff = self._GenerateDiff() | |
91 if not self.options.email: | 91 if not self.options.email: |
92 # Assumes the svn credential is an email address. | 92 # Assumes the svn credential is an email address. |
93 self.options.email = scm.SVN.GetEmail(self.checkout_root) | 93 self.options.email = scm.SVN.GetEmail(self.checkout_root) |
94 | 94 |
95 def _GenerateDiff(self): | 95 def GenerateDiff(self): |
96 """Returns a string containing the diff for the given file list. | 96 """Returns a string containing the diff for the given file list. |
97 | 97 |
98 The files in the list should either be absolute paths or relative to the | 98 The files in the list should either be absolute paths or relative to the |
99 given root. | 99 given root. |
100 """ | 100 """ |
101 if not self.options.files: | 101 if not self.files: |
102 previous_cwd = os.getcwd() | 102 previous_cwd = os.getcwd() |
103 os.chdir(self.checkout_root) | 103 os.chdir(self.checkout_root) |
104 excluded = ['!', '?', 'X', ' ', '~'] | 104 excluded = ['!', '?', 'X', ' ', '~'] |
105 self.options.files = [ | 105 self.files = [ |
106 f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) | 106 f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) |
107 if f[0][0] not in excluded | 107 if f[0][0] not in excluded |
108 ] | 108 ] |
109 os.chdir(previous_cwd) | 109 os.chdir(previous_cwd) |
110 return scm.SVN.GenerateDiff(self.options.files, full_move=True) | 110 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True) |
111 | 111 |
112 def GetLocalRoot(self): | 112 def GetLocalRoot(self): |
113 """Return the path of the repository root.""" | 113 """Return the path of the repository root.""" |
114 return self.checkout_root | 114 return self.checkout_root |
115 | 115 |
116 def GetBots(self): | 116 def GetBots(self): |
117 try: | 117 try: |
118 # Try to search on the subversion repository for the file. | 118 # Try to search on the subversion repository for the file. |
119 import gcl | 119 import gcl |
120 return gcl.GetCachedFile('PRESUBMIT.py', use_root=True) | 120 return gcl.GetCachedFile('PRESUBMIT.py', use_root=True) |
121 except ImportError: | 121 except ImportError: |
122 try: | 122 try: |
123 return gclient_utils.FileRead(os.path.join(self.checkout_root, | 123 return gclient_utils.FileRead(os.path.join(self.checkout_root, |
124 'PRESUBMIT.py')) | 124 'PRESUBMIT.py')) |
125 except (IOError, OSError): | 125 except (IOError, OSError): |
126 return None | 126 return None |
127 | 127 |
128 | 128 |
129 class GIT(SCM): | 129 class GIT(SCM): |
130 """Gathers the options and diff for a git checkout.""" | 130 """Gathers the options and diff for a git checkout.""" |
131 def __init__(self, *args, **kwargs): | 131 def __init__(self, *args, **kwargs): |
132 SCM.__init__(self, *args, **kwargs) | 132 SCM.__init__(self, *args, **kwargs) |
133 self.checkout_root = scm.GIT.GetCheckoutRoot(os.getcwd()) | 133 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) |
134 if not self.options.diff: | |
135 self.options.diff = scm.GIT.GenerateDiff(self.checkout_root, | |
136 full_move=True) | |
137 if not self.options.name: | 134 if not self.options.name: |
138 self.options.name = scm.GIT.GetPatchName(self.checkout_root) | 135 self.options.name = scm.GIT.GetPatchName(self.checkout_root) |
139 if not self.options.email: | 136 if not self.options.email: |
140 self.options.email = scm.GIT.GetEmail(self.checkout_root) | 137 self.options.email = scm.GIT.GetEmail(self.checkout_root) |
141 | 138 |
142 def GetLocalRoot(self): | 139 def GetLocalRoot(self): |
143 """Return the path of the repository root.""" | 140 """Return the path of the repository root.""" |
144 return self.checkout_root | 141 return self.checkout_root |
145 | 142 |
| 143 def GenerateDiff(self): |
| 144 # For now, ignores self.files |
| 145 return scm.GIT.GenerateDiff(self.checkout_root, full_move=True) |
| 146 |
146 def GetBots(self): | 147 def GetBots(self): |
147 try: | 148 try: |
148 # A git checkout is always a full checkout. | 149 # A git checkout is always a full checkout. |
149 return gclient_utils.FileRead(os.path.join(self.checkout_root, | 150 return gclient_utils.FileRead(os.path.join(self.checkout_root, |
150 'PRESUBMIT.py')) | 151 'PRESUBMIT.py')) |
151 except (IOError, OSError): | 152 except (IOError, OSError): |
152 return None | 153 return None |
153 | 154 |
154 | 155 |
155 def _ParseSendChangeOptions(options): | 156 def _ParseSendChangeOptions(options): |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
284 gclient_utils.CheckCall(["svn", "commit", full_path, '--file', | 285 gclient_utils.CheckCall(["svn", "commit", full_path, '--file', |
285 temp_file.name], print_error=False) | 286 temp_file.name], print_error=False) |
286 except gclient_utils.CheckCallError, e: | 287 except gclient_utils.CheckCallError, e: |
287 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' + | 288 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' + |
288 e.stdout) | 289 e.stdout) |
289 finally: | 290 finally: |
290 temp_file.close() | 291 temp_file.close() |
291 shutil.rmtree(temp_dir, True) | 292 shutil.rmtree(temp_dir, True) |
292 | 293 |
293 | 294 |
294 def GuessVCS(options): | 295 def GuessVCS(options, cwd): |
295 """Helper to guess the version control system. | 296 """Helper to guess the version control system. |
296 | 297 |
297 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't | 298 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't |
298 support it yet. | 299 support it yet. |
299 | 300 |
300 This examines the current directory, guesses which SCM we're using, and | 301 This examines the current directory, guesses which SCM we're using, and |
301 returns an instance of the appropriate class. Exit with an error if we can't | 302 returns an instance of the appropriate class. Exit with an error if we can't |
302 figure it out. | 303 figure it out. |
303 | 304 |
304 Returns: | 305 Returns: |
305 A SCM instance. Exits if the SCM can't be guessed. | 306 A SCM instance. Exits if the SCM can't be guessed. |
306 """ | 307 """ |
307 __pychecker__ = 'no-returnvalues' | 308 __pychecker__ = 'no-returnvalues' |
308 # Subversion has a .svn in all working directories. | 309 # Subversion has a .svn in all working directories. |
309 if os.path.isdir('.svn'): | 310 if os.path.isdir(os.path.join(cwd, '.svn')): |
310 logging.info("Guessed VCS = Subversion") | 311 logging.info("Guessed VCS = Subversion") |
311 return SVN(options) | 312 return SVN(options, cwd) |
312 | 313 |
313 # Git has a command to test if you're in a git tree. | 314 # Git has a command to test if you're in a git tree. |
314 # Try running it, but don't die if we don't have git installed. | 315 # Try running it, but don't die if we don't have git installed. |
315 try: | 316 try: |
316 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"]) | 317 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"], cwd) |
317 logging.info("Guessed VCS = Git") | 318 logging.info("Guessed VCS = Git") |
318 return GIT(options) | 319 return GIT(options, cwd) |
319 except gclient_utils.CheckCallError, e: | 320 except gclient_utils.CheckCallError, e: |
320 if e.retcode != 2: # ENOENT -- they don't have git installed. | 321 if e.retcode != 2: # ENOENT -- they don't have git installed. |
321 raise | 322 raise |
322 raise NoTryServerAccess("Could not guess version control system. " | 323 raise NoTryServerAccess("Could not guess version control system. " |
323 "Are you in a working copy directory?") | 324 "Are you in a working copy directory?") |
324 | 325 |
325 | 326 |
326 def TryChange(argv, | 327 def TryChange(argv, |
327 file_list, | 328 file_list, |
328 swallow_exception, | 329 swallow_exception, |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 "try, relative to the repository root") | 392 "try, relative to the repository root") |
392 group.add_option("--diff", | 393 group.add_option("--diff", |
393 help="File containing the diff to try") | 394 help="File containing the diff to try") |
394 group.add_option("--url", | 395 group.add_option("--url", |
395 help="Url where to grab a patch") | 396 help="Url where to grab a patch") |
396 group.add_option("--root", | 397 group.add_option("--root", |
397 help="Root to use for the patch; base subdirectory for " | 398 help="Root to use for the patch; base subdirectory for " |
398 "patch created in a subdirectory") | 399 "patch created in a subdirectory") |
399 group.add_option("--patchlevel", type='int', metavar="LEVEL", | 400 group.add_option("--patchlevel", type='int', metavar="LEVEL", |
400 help="Used as -pN parameter to patch") | 401 help="Used as -pN parameter to patch") |
| 402 group.add_option("--sub_rep", action="append", default=["."], |
| 403 help="Subcheckout to use in addition. This is mainly " |
| 404 "useful for gclient-style checkouts.") |
401 parser.add_option_group(group) | 405 parser.add_option_group(group) |
402 | 406 |
403 group = optparse.OptionGroup(parser, "Access the try server by HTTP") | 407 group = optparse.OptionGroup(parser, "Access the try server by HTTP") |
404 group.add_option("--use_http", | 408 group.add_option("--use_http", |
405 action="store_const", | 409 action="store_const", |
406 const=_SendChangeHTTP, | 410 const=_SendChangeHTTP, |
407 dest="send_patch", | 411 dest="send_patch", |
408 help="Use HTTP to talk to the try server [default]") | 412 help="Use HTTP to talk to the try server [default]") |
409 group.add_option("--host", | 413 group.add_option("--host", |
410 help="Host address") | 414 help="Host address") |
(...skipping 22 matching lines...) Expand all Loading... |
433 # Switch the default accordingly if there was no default send_patch. | 437 # Switch the default accordingly if there was no default send_patch. |
434 if not options.send_patch: | 438 if not options.send_patch: |
435 if options.port and options.host: | 439 if options.port and options.host: |
436 options.send_patch = _SendChangeHTTP | 440 options.send_patch = _SendChangeHTTP |
437 elif options.svn_repo: | 441 elif options.svn_repo: |
438 options.send_patch = _SendChangeSVN | 442 options.send_patch = _SendChangeSVN |
439 else: | 443 else: |
440 parser.error('Please specify an access method.') | 444 parser.error('Please specify an access method.') |
441 | 445 |
442 try: | 446 try: |
| 447 # Process the VCS in any case at least to retrieve the email address. |
| 448 checkouts = [] |
| 449 for item in options.sub_rep: |
| 450 checkout = GuessVCS(options, item) |
| 451 if checkout.GetLocalRoot() in [c.GetLocalRoot() for c in checkouts]: |
| 452 parser.error('Specified the root %s two times.' % |
| 453 checkout.GetLocalRoot()) |
| 454 checkouts.append(checkout) |
| 455 |
443 # Convert options.diff into the content of the diff. | 456 # Convert options.diff into the content of the diff. |
444 if options.url: | 457 if options.url: |
445 if options.files: | 458 if options.files: |
446 parser.error('You cannot specify files and --url at the same time.') | 459 parser.error('You cannot specify files and --url at the same time.') |
447 options.diff = urllib.urlopen(options.url).read() | 460 options.diff = urllib.urlopen(options.url).read() |
448 elif options.diff: | 461 elif options.diff: |
449 if options.files: | 462 if options.files: |
450 parser.error('You cannot specify files and --diff at the same time.') | 463 parser.error('You cannot specify files and --diff at the same time.') |
451 options.diff = gclient_utils.FileRead(options.diff, 'rb') | 464 options.diff = gclient_utils.FileRead(options.diff, 'rb') |
452 # Process the VCS in any case at least to retrieve the email address. | 465 else: |
453 try: | 466 # Use this as the base. |
454 options.scm = GuessVCS(options) | 467 root = checkouts[0].GetLocalRoot() |
455 except NoTryServerAccess, e: | 468 diffs = [] |
456 # If we got the diff, we don't care. | 469 for checkout in checkouts: |
457 if not options.diff: | 470 diff = checkout.GenerateDiff().splitlines(True) |
458 # TODO(maruel): Raise what? | 471 # Munge it. |
459 raise | 472 path_diff = gclient_utils.PathDifference(root, checkout.GetLocalRoot()) |
| 473 for i in range(len(diff)): |
| 474 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
| 475 diff[i] = diff[i][0:3] + path_diff + diff[i][4:] |
| 476 diffs.extend(diff) |
| 477 options.diff = ''.join(diffs) |
460 | 478 |
461 # Get try slaves from PRESUBMIT.py files if not specified. | |
462 if not options.bot: | 479 if not options.bot: |
| 480 # Get try slaves from PRESUBMIT.py files if not specified. |
463 # Even if the diff comes from options.url, use the local checkout for bot | 481 # Even if the diff comes from options.url, use the local checkout for bot |
464 # selection. | 482 # selection. |
465 try: | 483 try: |
466 # Get try slaves from PRESUBMIT.py files if not specified. | |
467 import presubmit_support | 484 import presubmit_support |
468 root_presubmit = options.scm.GetBots() | 485 root_presubmit = checkouts[0].GetBots() |
469 options.bot = presubmit_support.DoGetTrySlaves( | 486 options.bot = presubmit_support.DoGetTrySlaves( |
470 options.scm.GetFileNames(), | 487 checkouts[0].GetFileNames(), |
471 options.scm.GetLocalRoot(), | 488 checkouts[0].GetLocalRoot(), |
472 root_presubmit, | 489 root_presubmit, |
473 False, | 490 False, |
474 sys.stdout) | 491 sys.stdout) |
475 except ImportError: | 492 except ImportError: |
476 pass | 493 pass |
477 # If no bot is specified, either the default pool will be selected or the | 494 # If no bot is specified, either the default pool will be selected or the |
478 # try server will refuse the job. Either case we don't need to interfere. | 495 # try server will refuse the job. Either case we don't need to interfere. |
479 | 496 |
480 if options.name is None: | 497 if options.name is None: |
481 if options.issue: | 498 if options.issue: |
(...skipping 17 matching lines...) Expand all Loading... |
499 except (InvalidScript, NoTryServerAccess), e: | 516 except (InvalidScript, NoTryServerAccess), e: |
500 if swallow_exception: | 517 if swallow_exception: |
501 return 1 | 518 return 1 |
502 print e | 519 print e |
503 return 1 | 520 return 1 |
504 return 0 | 521 return 0 |
505 | 522 |
506 | 523 |
507 if __name__ == "__main__": | 524 if __name__ == "__main__": |
508 sys.exit(TryChange(None, [], False)) | 525 sys.exit(TryChange(None, [], False)) |
OLD | NEW |