Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 """Client-side script to send a try job to the try server. It communicates to | 6 """Client-side script to send a try job to the try server. It communicates to |
| 7 the try server by either writting to a svn repository or by directly connecting | 7 the try server by either writting to a svn repository or by directly connecting |
| 8 to the server by HTTP. | 8 to the server by HTTP. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 93 | 93 |
| 94 class SCM(object): | 94 class SCM(object): |
| 95 """Simplistic base class to implement one function: ProcessOptions.""" | 95 """Simplistic base class to implement one function: ProcessOptions.""" |
| 96 def __init__(self, options, path): | 96 def __init__(self, options, path): |
| 97 items = path.split('@') | 97 items = path.split('@') |
| 98 assert len(items) <= 2 | 98 assert len(items) <= 2 |
| 99 self.checkout_root = items[0] | 99 self.checkout_root = items[0] |
| 100 items.append(None) | 100 items.append(None) |
| 101 self.diff_against = items[1] | 101 self.diff_against = items[1] |
| 102 self.options = options | 102 self.options = options |
| 103 self.files = self.options.files | 103 # Lazy-load file list from the SCM unless files were specified in options. |
| 104 self._files = None | |
| 105 self._file_tuples = None | |
| 106 if self.options.files: | |
| 107 self._files = self.options.files | |
| 108 self._file_tuples = [('M', f) for f in self.files] | |
| 104 self.options.files = None | 109 self.options.files = None |
| 105 self.codereview_settings = None | 110 self.codereview_settings = None |
| 106 self.codereview_settings_file = 'codereview.settings' | 111 self.codereview_settings_file = 'codereview.settings' |
| 107 self.gclient_root = None | 112 self.gclient_root = None |
| 108 | 113 |
| 109 def GetFileNames(self): | 114 def GetFileNames(self): |
| 110 """Return the list of files in the diff.""" | 115 """Return the list of files in the diff.""" |
| 111 return self.files | 116 return self.files |
| 112 | 117 |
| 113 def GetCodeReviewSetting(self, key): | 118 def GetCodeReviewSetting(self, key): |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 180 assert cur.startswith(root), (root, cur) | 185 assert cur.startswith(root), (root, cur) |
| 181 while cur.startswith(root): | 186 while cur.startswith(root): |
| 182 filepath = os.path.join(cur, filename) | 187 filepath = os.path.join(cur, filename) |
| 183 if os.path.isfile(filepath): | 188 if os.path.isfile(filepath): |
| 184 logging.info('Found %s at %s' % (filename, cur)) | 189 logging.info('Found %s at %s' % (filename, cur)) |
| 185 return gclient_utils.FileRead(filepath) | 190 return gclient_utils.FileRead(filepath) |
| 186 cur = os.path.dirname(cur) | 191 cur = os.path.dirname(cur) |
| 187 logging.warning('Didn\'t find %s' % filename) | 192 logging.warning('Didn\'t find %s' % filename) |
| 188 return None | 193 return None |
| 189 | 194 |
| 195 def _SetFileTuples(self, file_tuples): | |
| 196 excluded = ['!', '?', 'X', ' ', '~'] | |
| 197 def Excluded(f): | |
| 198 if f[0][0] in excluded: | |
| 199 return True | |
| 200 for r in self.options.exclude: | |
| 201 if re.search(r, f[1]): | |
| 202 logging.info('Ignoring "%s"' % f[1]) | |
| 203 return True | |
| 204 return False | |
| 205 | |
| 206 self._file_tuples = [f for f in file_tuples if not Excluded(f)] | |
| 207 self._files = [f[1] for f in self._file_tuples] | |
| 208 | |
| 209 def CaptureStatus(self): | |
| 210 """Returns the 'svn status' emulated output as an array of (status, file) | |
| 211 tuples.""" | |
| 212 raise NotImplementedError( | |
| 213 "abstract method -- subclass %s must override" % self.__class__) | |
| 214 | |
| 215 @property | |
| 216 def files(self): | |
| 217 if self._files is None: | |
| 218 self._SetFileTuples(self.CaptureStatus()) | |
| 219 return self._files | |
| 220 | |
| 221 @property | |
| 222 def file_tuples(self): | |
| 223 if self._file_tuples is None: | |
| 224 self._SetFileTuples(self.CaptureStatus()) | |
| 225 return self._file_tuples | |
| 226 | |
| 190 | 227 |
| 191 class SVN(SCM): | 228 class SVN(SCM): |
| 192 """Gathers the options and diff for a subversion checkout.""" | 229 """Gathers the options and diff for a subversion checkout.""" |
| 193 def __init__(self, *args, **kwargs): | 230 def __init__(self, *args, **kwargs): |
| 194 SCM.__init__(self, *args, **kwargs) | 231 SCM.__init__(self, *args, **kwargs) |
| 195 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) | 232 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) |
| 196 if not self.options.email: | 233 if not self.options.email: |
| 197 # Assumes the svn credential is an email address. | 234 # Assumes the svn credential is an email address. |
| 198 self.options.email = scm.SVN.GetEmail(self.checkout_root) | 235 self.options.email = scm.SVN.GetEmail(self.checkout_root) |
| 199 logging.info("SVN(%s)" % self.checkout_root) | 236 logging.info("SVN(%s)" % self.checkout_root) |
| 200 | 237 |
| 201 def ReadRootFile(self, filename): | 238 def ReadRootFile(self, filename): |
| 202 data = SCM.ReadRootFile(self, filename) | 239 data = SCM.ReadRootFile(self, filename) |
| 203 if data: | 240 if data: |
| 204 return data | 241 return data |
| 205 | 242 |
| 206 # Try to search on the subversion repository for the file. | 243 # Try to search on the subversion repository for the file. |
| 207 if not gcl: | 244 if not gcl: |
| 208 return None | 245 return None |
| 209 data = gcl.GetCachedFile(filename) | 246 data = gcl.GetCachedFile(filename) |
| 210 logging.debug('%s:\n%s' % (filename, data)) | 247 logging.debug('%s:\n%s' % (filename, data)) |
| 211 return data | 248 return data |
| 212 | 249 |
| 250 def CaptureStatus(self): | |
| 251 previous_cwd = os.getcwd() | |
| 252 os.chdir(self.checkout_root) | |
| 253 result = scm.SVN.CaptureStatus(self.checkout_root) | |
| 254 os.chdir(previous_cwd) | |
| 255 return result | |
| 256 | |
| 213 def GenerateDiff(self): | 257 def GenerateDiff(self): |
| 214 """Returns a string containing the diff for the given file list. | 258 """Returns a string containing the diff for the given file list. |
| 215 | 259 |
| 216 The files in the list should either be absolute paths or relative to the | 260 The files in the list should either be absolute paths or relative to the |
| 217 given root. | 261 given root. |
| 218 """ | 262 """ |
| 219 if not self.files: | |
| 220 previous_cwd = os.getcwd() | |
| 221 os.chdir(self.checkout_root) | |
| 222 | |
| 223 excluded = ['!', '?', 'X', ' ', '~'] | |
| 224 def Excluded(f): | |
| 225 if f[0][0] in excluded: | |
| 226 return True | |
| 227 for r in self.options.exclude: | |
| 228 if re.search(r, f[1]): | |
| 229 logging.info('Ignoring "%s"' % f[1]) | |
| 230 return True | |
| 231 return False | |
| 232 | |
| 233 self.files = [f[1] for f in scm.SVN.CaptureStatus(self.checkout_root) | |
| 234 if not Excluded(f)] | |
| 235 os.chdir(previous_cwd) | |
| 236 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True, | 263 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True, |
| 237 revision=self.diff_against) | 264 revision=self.diff_against) |
| 238 | 265 |
| 239 | 266 |
| 240 class GIT(SCM): | 267 class GIT(SCM): |
| 241 """Gathers the options and diff for a git checkout.""" | 268 """Gathers the options and diff for a git checkout.""" |
| 242 def __init__(self, *args, **kwargs): | 269 def __init__(self, *args, **kwargs): |
| 243 SCM.__init__(self, *args, **kwargs) | 270 SCM.__init__(self, *args, **kwargs) |
| 244 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) | 271 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) |
| 245 if not self.options.name: | 272 if not self.options.name: |
| 246 self.options.name = scm.GIT.GetPatchName(self.checkout_root) | 273 self.options.name = scm.GIT.GetPatchName(self.checkout_root) |
| 247 if not self.options.email: | 274 if not self.options.email: |
| 248 self.options.email = scm.GIT.GetEmail(self.checkout_root) | 275 self.options.email = scm.GIT.GetEmail(self.checkout_root) |
| 249 if not self.diff_against: | 276 if not self.diff_against: |
| 250 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root) | 277 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root) |
| 251 if not self.diff_against: | 278 if not self.diff_against: |
| 252 raise NoTryServerAccess( | 279 raise NoTryServerAccess( |
| 253 "Unable to determine default branch to diff against. " | 280 "Unable to determine default branch to diff against. " |
| 254 "Verify this branch is set up to track another" | 281 "Verify this branch is set up to track another" |
| 255 "(via the --track argument to \"git checkout -b ...\"") | 282 "(via the --track argument to \"git checkout -b ...\"") |
| 256 logging.info("GIT(%s)" % self.checkout_root) | 283 logging.info("GIT(%s)" % self.checkout_root) |
| 257 | 284 |
| 285 def CaptureStatus(self): | |
| 286 return scm.GIT.CaptureStatus(self.checkout_root, self.diff_against) | |
| 287 | |
| 258 def GenerateDiff(self): | 288 def GenerateDiff(self): |
| 259 if not self.files: | |
| 260 self.files = scm.GIT.GetDifferentFiles(self.checkout_root, | |
| 261 branch=self.diff_against) | |
| 262 | |
| 263 def NotExcluded(f): | |
| 264 for r in self.options.exclude: | |
| 265 if re.search(r, f): | |
| 266 logging.info('Ignoring "%s"' % f) | |
| 267 return False | |
| 268 return True | |
| 269 | |
| 270 self.files = filter(NotExcluded, self.files) | |
| 271 return scm.GIT.GenerateDiff(self.checkout_root, files=self.files, | 289 return scm.GIT.GenerateDiff(self.checkout_root, files=self.files, |
| 272 full_move=True, | 290 full_move=True, |
| 273 branch=self.diff_against) | 291 branch=self.diff_against) |
| 274 | 292 |
| 275 | 293 |
| 276 def _ParseSendChangeOptions(options): | 294 def _ParseSendChangeOptions(options): |
| 277 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" | 295 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" |
| 278 values = {} | 296 values = {} |
| 279 if options.email: | 297 if options.email: |
| 280 values['email'] = options.email | 298 values['email'] = options.email |
| (...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 449 # ENOENT == 2 = they don't have git installed. | 467 # ENOENT == 2 = they don't have git installed. |
| 450 # 128 = git error code when not in a repo. | 468 # 128 = git error code when not in a repo. |
| 451 logging.warning('Unexpected error code: %s' % e.returncode) | 469 logging.warning('Unexpected error code: %s' % e.returncode) |
| 452 raise | 470 raise |
| 453 raise NoTryServerAccess("Could not guess version control system. " | 471 raise NoTryServerAccess("Could not guess version control system. " |
| 454 "Are you in a working copy directory?") | 472 "Are you in a working copy directory?") |
| 455 | 473 |
| 456 | 474 |
| 457 def GetMungedDiff(path_diff, diff): | 475 def GetMungedDiff(path_diff, diff): |
| 458 # Munge paths to match svn. | 476 # Munge paths to match svn. |
| 477 changed_files = [] | |
| 459 for i in range(len(diff)): | 478 for i in range(len(diff)): |
| 460 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): | 479 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
| 461 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/') | 480 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/') |
| 481 changed_files.append(('M', new_file.split('\t')[0])) | |
| 462 diff[i] = diff[i][0:4] + new_file | 482 diff[i] = diff[i][0:4] + new_file |
| 463 return diff | 483 return (diff, changed_files) |
| 464 | 484 |
| 465 | 485 |
| 466 def TryChange(argv, | 486 def TryChange(argv, |
| 487 change, | |
| 467 file_list, | 488 file_list, |
|
M-A Ruel
2011/09/30 15:38:44
I just realized this was an error. TryChange shoul
| |
| 468 swallow_exception, | 489 swallow_exception, |
| 469 prog=None, | 490 prog=None, |
| 470 extra_epilog=None): | 491 extra_epilog=None): |
| 471 """ | 492 """ |
| 472 Args: | 493 Args: |
| 473 argv: Arguments and options. | 494 argv: Arguments and options. |
| 474 file_list: Default value to pass to --file. | 495 file_list: Default value to pass to --file. |
| 475 swallow_exception: Whether we raise or swallow exceptions. | 496 swallow_exception: Whether we raise or swallow exceptions. |
| 476 """ | 497 """ |
| 477 # Parse argv | 498 # Parse argv |
| (...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 639 if not '://' in options.rietveld_url: | 660 if not '://' in options.rietveld_url: |
| 640 options.rietveld_url = 'http://' + options.rietveld_url | 661 options.rietveld_url = 'http://' + options.rietveld_url |
| 641 match = re.match(r'^(.*)/(\d+)/?$', options.rietveld_url) | 662 match = re.match(r'^(.*)/(\d+)/?$', options.rietveld_url) |
| 642 if match: | 663 if match: |
| 643 if options.issue or options.patchset: | 664 if options.issue or options.patchset: |
| 644 parser.error('Cannot use both --issue and use a review number url') | 665 parser.error('Cannot use both --issue and use a review number url') |
| 645 options.issue = int(match.group(2)) | 666 options.issue = int(match.group(2)) |
| 646 options.rietveld_url = match.group(1) | 667 options.rietveld_url = match.group(1) |
| 647 | 668 |
| 648 try: | 669 try: |
| 670 changed_files = None | |
| 649 # Always include os.getcwd() in the checkout settings. | 671 # Always include os.getcwd() in the checkout settings. |
| 650 checkouts = [] | 672 checkouts = [] |
| 651 path = os.getcwd() | 673 path = os.getcwd() |
| 652 if options.upstream_branch: | 674 if options.upstream_branch: |
| 653 path += '@' + options.upstream_branch | 675 path += '@' + options.upstream_branch |
| 654 checkouts.append(GuessVCS(options, path)) | 676 checkouts.append(GuessVCS(options, path)) |
| 655 checkouts[0].AutomagicalSettings() | 677 checkouts[0].AutomagicalSettings() |
| 656 for item in options.sub_rep: | 678 for item in options.sub_rep: |
| 657 checkout = GuessVCS(options, os.path.join(checkouts[0].checkout_root, | 679 checkout = GuessVCS(options, os.path.join(checkouts[0].checkout_root, |
| 658 item)) | 680 item)) |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 682 # When patchset is specified, it's because it's done by gcl/git-try. | 704 # When patchset is specified, it's because it's done by gcl/git-try. |
| 683 if json is None: | 705 if json is None: |
| 684 parser.error('json or simplejson library is missing, please install.') | 706 parser.error('json or simplejson library is missing, please install.') |
| 685 api_url = '%s/api/%d' % (options.rietveld_url, options.issue) | 707 api_url = '%s/api/%d' % (options.rietveld_url, options.issue) |
| 686 logging.debug(api_url) | 708 logging.debug(api_url) |
| 687 contents = json.loads(urllib.urlopen(api_url).read()) | 709 contents = json.loads(urllib.urlopen(api_url).read()) |
| 688 options.patchset = contents['patchsets'][-1] | 710 options.patchset = contents['patchsets'][-1] |
| 689 diff_url = ('%s/download/issue%d_%d.diff' % | 711 diff_url = ('%s/download/issue%d_%d.diff' % |
| 690 (options.rietveld_url, options.issue, options.patchset)) | 712 (options.rietveld_url, options.issue, options.patchset)) |
| 691 diff = GetMungedDiff('', urllib.urlopen(diff_url).readlines()) | 713 diff = GetMungedDiff('', urllib.urlopen(diff_url).readlines()) |
| 692 options.diff = ''.join(diff) | 714 options.diff = ''.join(diff[0]) |
| 715 changed_files = diff[1] | |
| 693 else: | 716 else: |
| 694 # Use this as the base. | 717 # Use this as the base. |
| 695 root = checkouts[0].checkout_root | 718 root = checkouts[0].checkout_root |
| 696 diffs = [] | 719 diffs = [] |
| 697 for checkout in checkouts: | 720 for checkout in checkouts: |
| 698 diff = checkout.GenerateDiff().splitlines(True) | 721 diff = checkout.GenerateDiff().splitlines(True) |
| 699 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) | 722 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) |
| 700 # Munge it. | 723 # Munge it. |
| 701 diffs.extend(GetMungedDiff(path_diff, diff)) | 724 diffs.extend(GetMungedDiff(path_diff, diff)[0]) |
| 702 options.diff = ''.join(diffs) | 725 options.diff = ''.join(diffs) |
| 703 | 726 |
| 727 if not options.name: | |
| 728 if options.issue: | |
| 729 options.name = 'Issue %s' % options.issue | |
| 730 else: | |
| 731 options.name = 'Unnamed' | |
| 732 print('Note: use --name NAME to change the try job name.') | |
| 733 | |
| 734 if not options.email: | |
| 735 parser.error('Using an anonymous checkout. Please use --email or set ' | |
| 736 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') | |
| 737 print('Results will be emailed to: ' + options.email) | |
| 738 | |
| 704 if not options.bot: | 739 if not options.bot: |
| 705 # Get try slaves from PRESUBMIT.py files if not specified. | 740 # Get try slaves from PRESUBMIT.py files if not specified. |
| 706 # Even if the diff comes from options.url, use the local checkout for bot | 741 # Even if the diff comes from options.url, use the local checkout for bot |
| 707 # selection. | 742 # selection. |
| 708 try: | 743 try: |
| 709 import presubmit_support | 744 import presubmit_support |
| 710 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') | 745 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') |
| 746 if not change: | |
| 747 if not changed_files: | |
| 748 changed_files = checkouts[0].file_tuples | |
| 749 change = presubmit_support.Change(options.name, | |
| 750 '', | |
| 751 checkouts[0].checkout_root, | |
| 752 changed_files, | |
| 753 options.issue, | |
| 754 options.patchset, | |
| 755 options.email) | |
| 711 options.bot = presubmit_support.DoGetTrySlaves( | 756 options.bot = presubmit_support.DoGetTrySlaves( |
| 757 change, | |
| 712 checkouts[0].GetFileNames(), | 758 checkouts[0].GetFileNames(), |
| 713 checkouts[0].checkout_root, | 759 checkouts[0].checkout_root, |
| 714 root_presubmit, | 760 root_presubmit, |
| 715 options.project, | 761 options.project, |
| 716 False, | 762 False, |
| 717 sys.stdout) | 763 sys.stdout) |
| 718 except ImportError: | 764 except ImportError: |
| 719 pass | 765 pass |
| 720 # If no bot is specified, either the default pool will be selected or the | 766 # If no bot is specified, either the default pool will be selected or the |
| 721 # try server will refuse the job. Either case we don't need to interfere. | 767 # try server will refuse the job. Either case we don't need to interfere. |
| 722 | 768 |
| 723 if options.name is None: | |
| 724 if options.issue: | |
| 725 options.name = 'Issue %s' % options.issue | |
| 726 else: | |
| 727 options.name = 'Unnamed' | |
| 728 print('Note: use --name NAME to change the try job name.') | |
| 729 if not options.email: | |
| 730 parser.error('Using an anonymous checkout. Please use --email or set ' | |
| 731 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') | |
| 732 else: | |
| 733 print('Results will be emailed to: ' + options.email) | |
| 734 | |
| 735 # Prevent rietveld updates if we aren't running all the tests. | 769 # Prevent rietveld updates if we aren't running all the tests. |
| 736 if options.testfilter is not None: | 770 if options.testfilter is not None: |
| 737 options.issue = None | 771 options.issue = None |
| 738 options.patchset = None | 772 options.patchset = None |
| 739 | 773 |
| 740 # Send the patch. | 774 # Send the patch. |
| 741 if options.send_patch: | 775 if options.send_patch: |
| 742 # If forced. | 776 # If forced. |
| 743 options.send_patch(options) | 777 options.send_patch(options) |
| 744 PrintSuccess(options) | 778 PrintSuccess(options) |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 760 print >> sys.stderr, e | 794 print >> sys.stderr, e |
| 761 return 1 | 795 return 1 |
| 762 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 796 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
| 763 print >> sys.stderr, e | 797 print >> sys.stderr, e |
| 764 return 1 | 798 return 1 |
| 765 return 0 | 799 return 0 |
| 766 | 800 |
| 767 | 801 |
| 768 if __name__ == "__main__": | 802 if __name__ == "__main__": |
| 769 fix_encoding.fix_encoding() | 803 fix_encoding.fix_encoding() |
| 770 sys.exit(TryChange(None, [], False)) | 804 sys.exit(TryChange(None, None, [], False)) |
| OLD | NEW |