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 self.files = self.options.files |
104 self._file_tuples = [('M', f) for f in self.files] | |
105 self.options.files = None | 104 self.options.files = None |
106 self.codereview_settings = None | 105 self.codereview_settings = None |
107 self.codereview_settings_file = 'codereview.settings' | 106 self.codereview_settings_file = 'codereview.settings' |
108 self.gclient_root = None | 107 self.gclient_root = None |
109 | 108 |
110 def GetFileNames(self): | 109 def GetFileNames(self): |
111 """Return the list of files in the diff.""" | 110 """Return the list of files in the diff.""" |
112 return self.files | 111 return self.files |
113 | 112 |
114 def GetCodeReviewSetting(self, key): | 113 def GetCodeReviewSetting(self, key): |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
181 assert cur.startswith(root), (root, cur) | 180 assert cur.startswith(root), (root, cur) |
182 while cur.startswith(root): | 181 while cur.startswith(root): |
183 filepath = os.path.join(cur, filename) | 182 filepath = os.path.join(cur, filename) |
184 if os.path.isfile(filepath): | 183 if os.path.isfile(filepath): |
185 logging.info('Found %s at %s' % (filename, cur)) | 184 logging.info('Found %s at %s' % (filename, cur)) |
186 return gclient_utils.FileRead(filepath) | 185 return gclient_utils.FileRead(filepath) |
187 cur = os.path.dirname(cur) | 186 cur = os.path.dirname(cur) |
188 logging.warning('Didn\'t find %s' % filename) | 187 logging.warning('Didn\'t find %s' % filename) |
189 return None | 188 return None |
190 | 189 |
191 def _SetFileTuples(self, file_tuples): | |
192 excluded = ['!', '?', 'X', ' ', '~'] | |
193 def Excluded(f): | |
194 if f[0][0] in excluded: | |
195 return True | |
196 for r in self.options.exclude: | |
197 if re.search(r, f[1]): | |
198 logging.info('Ignoring "%s"' % f[1]) | |
199 return True | |
200 return False | |
201 | |
202 self._file_tuples = [f for f in file_tuples if not Excluded(f)] | |
203 self._files = [f[1] for f in self.file_tuples] | |
204 | |
205 def CaptureStatus(self): | |
206 """Returns the 'svn status' emulated output as an array of (status, file) | |
207 tuples.""" | |
208 raise NotImplementedError( | |
209 "abstract method -- subclass %s must override" % self.__class__) | |
210 | |
211 @property | |
212 def files(self): | |
213 if not self._files: | |
214 self._SetFileTuples(self.CaptureStatus()) | |
215 return self._files | |
216 | |
217 @property | |
218 def file_tuples(self): | |
219 if not self._file_tuples: | |
220 self._SetFileTuples(self.CaptureStatus()) | |
221 return self._file_tuples | |
222 | |
223 | 190 |
224 class SVN(SCM): | 191 class SVN(SCM): |
225 """Gathers the options and diff for a subversion checkout.""" | 192 """Gathers the options and diff for a subversion checkout.""" |
226 def __init__(self, *args, **kwargs): | 193 def __init__(self, *args, **kwargs): |
227 SCM.__init__(self, *args, **kwargs) | 194 SCM.__init__(self, *args, **kwargs) |
228 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) | 195 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root) |
229 if not self.options.email: | 196 if not self.options.email: |
230 # Assumes the svn credential is an email address. | 197 # Assumes the svn credential is an email address. |
231 self.options.email = scm.SVN.GetEmail(self.checkout_root) | 198 self.options.email = scm.SVN.GetEmail(self.checkout_root) |
232 logging.info("SVN(%s)" % self.checkout_root) | 199 logging.info("SVN(%s)" % self.checkout_root) |
233 | 200 |
234 def ReadRootFile(self, filename): | 201 def ReadRootFile(self, filename): |
235 data = SCM.ReadRootFile(self, filename) | 202 data = SCM.ReadRootFile(self, filename) |
236 if data: | 203 if data: |
237 return data | 204 return data |
238 | 205 |
239 # Try to search on the subversion repository for the file. | 206 # Try to search on the subversion repository for the file. |
240 if not gcl: | 207 if not gcl: |
241 return None | 208 return None |
242 data = gcl.GetCachedFile(filename) | 209 data = gcl.GetCachedFile(filename) |
243 logging.debug('%s:\n%s' % (filename, data)) | 210 logging.debug('%s:\n%s' % (filename, data)) |
244 return data | 211 return data |
245 | 212 |
246 def CaptureStatus(self): | |
247 previous_cwd = os.getcwd() | |
248 os.chdir(self.checkout_root) | |
249 result = scm.SVN.CaptureStatus(self.checkout_root) | |
250 os.chdir(previous_cwd) | |
251 return result | |
252 | |
253 def GenerateDiff(self): | 213 def GenerateDiff(self): |
254 """Returns a string containing the diff for the given file list. | 214 """Returns a string containing the diff for the given file list. |
255 | 215 |
256 The files in the list should either be absolute paths or relative to the | 216 The files in the list should either be absolute paths or relative to the |
257 given root. | 217 given root. |
258 """ | 218 """ |
| 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) |
259 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True, | 236 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True, |
260 revision=self.diff_against) | 237 revision=self.diff_against) |
261 | 238 |
262 | 239 |
263 class GIT(SCM): | 240 class GIT(SCM): |
264 """Gathers the options and diff for a git checkout.""" | 241 """Gathers the options and diff for a git checkout.""" |
265 def __init__(self, *args, **kwargs): | 242 def __init__(self, *args, **kwargs): |
266 SCM.__init__(self, *args, **kwargs) | 243 SCM.__init__(self, *args, **kwargs) |
267 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) | 244 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root) |
268 if not self.options.name: | 245 if not self.options.name: |
269 self.options.name = scm.GIT.GetPatchName(self.checkout_root) | 246 self.options.name = scm.GIT.GetPatchName(self.checkout_root) |
270 if not self.options.email: | 247 if not self.options.email: |
271 self.options.email = scm.GIT.GetEmail(self.checkout_root) | 248 self.options.email = scm.GIT.GetEmail(self.checkout_root) |
272 if not self.diff_against: | 249 if not self.diff_against: |
273 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root) | 250 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root) |
274 if not self.diff_against: | 251 if not self.diff_against: |
275 raise NoTryServerAccess( | 252 raise NoTryServerAccess( |
276 "Unable to determine default branch to diff against. " | 253 "Unable to determine default branch to diff against. " |
277 "Verify this branch is set up to track another" | 254 "Verify this branch is set up to track another" |
278 "(via the --track argument to \"git checkout -b ...\"") | 255 "(via the --track argument to \"git checkout -b ...\"") |
279 logging.info("GIT(%s)" % self.checkout_root) | 256 logging.info("GIT(%s)" % self.checkout_root) |
280 | 257 |
281 def CaptureStatus(self): | 258 def GenerateDiff(self): |
282 return scm.GIT.CaptureStatus(self.checkout_root, self.diff_against) | 259 if not self.files: |
| 260 self.files = scm.GIT.GetDifferentFiles(self.checkout_root, |
| 261 branch=self.diff_against) |
283 | 262 |
284 def GenerateDiff(self): | 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) |
285 return scm.GIT.GenerateDiff(self.checkout_root, files=self.files, | 271 return scm.GIT.GenerateDiff(self.checkout_root, files=self.files, |
286 full_move=True, | 272 full_move=True, |
287 branch=self.diff_against) | 273 branch=self.diff_against) |
288 | 274 |
289 | 275 |
290 def _ParseSendChangeOptions(options): | 276 def _ParseSendChangeOptions(options): |
291 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" | 277 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" |
292 values = {} | 278 values = {} |
293 if options.email: | 279 if options.email: |
294 values['email'] = options.email | 280 values['email'] = options.email |
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
471 def GetMungedDiff(path_diff, diff): | 457 def GetMungedDiff(path_diff, diff): |
472 # Munge paths to match svn. | 458 # Munge paths to match svn. |
473 for i in range(len(diff)): | 459 for i in range(len(diff)): |
474 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): | 460 if diff[i].startswith('--- ') or diff[i].startswith('+++ '): |
475 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/') | 461 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/') |
476 diff[i] = diff[i][0:4] + new_file | 462 diff[i] = diff[i][0:4] + new_file |
477 return diff | 463 return diff |
478 | 464 |
479 | 465 |
480 def TryChange(argv, | 466 def TryChange(argv, |
481 change, | |
482 file_list, | 467 file_list, |
483 swallow_exception, | 468 swallow_exception, |
484 prog=None, | 469 prog=None, |
485 extra_epilog=None): | 470 extra_epilog=None): |
486 """ | 471 """ |
487 Args: | 472 Args: |
488 argv: Arguments and options. | 473 argv: Arguments and options. |
489 file_list: Default value to pass to --file. | 474 file_list: Default value to pass to --file. |
490 swallow_exception: Whether we raise or swallow exceptions. | 475 swallow_exception: Whether we raise or swallow exceptions. |
491 """ | 476 """ |
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
709 # Use this as the base. | 694 # Use this as the base. |
710 root = checkouts[0].checkout_root | 695 root = checkouts[0].checkout_root |
711 diffs = [] | 696 diffs = [] |
712 for checkout in checkouts: | 697 for checkout in checkouts: |
713 diff = checkout.GenerateDiff().splitlines(True) | 698 diff = checkout.GenerateDiff().splitlines(True) |
714 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) | 699 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root) |
715 # Munge it. | 700 # Munge it. |
716 diffs.extend(GetMungedDiff(path_diff, diff)) | 701 diffs.extend(GetMungedDiff(path_diff, diff)) |
717 options.diff = ''.join(diffs) | 702 options.diff = ''.join(diffs) |
718 | 703 |
719 if not options.name: | |
720 if options.issue: | |
721 options.name = 'Issue %s' % options.issue | |
722 else: | |
723 options.name = 'Unnamed' | |
724 print('Note: use --name NAME to change the try job name.') | |
725 | |
726 if not options.email: | |
727 parser.error('Using an anonymous checkout. Please use --email or set ' | |
728 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') | |
729 print('Results will be emailed to: ' + options.email) | |
730 | |
731 if not options.bot: | 704 if not options.bot: |
732 # Get try slaves from PRESUBMIT.py files if not specified. | 705 # Get try slaves from PRESUBMIT.py files if not specified. |
733 # Even if the diff comes from options.url, use the local checkout for bot | 706 # Even if the diff comes from options.url, use the local checkout for bot |
734 # selection. | 707 # selection. |
735 try: | 708 try: |
736 import presubmit_support | 709 import presubmit_support |
737 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') | 710 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py') |
738 if change is None: | |
739 change = presubmit_support.Change(options.name, | |
740 '', | |
741 checkouts[0].checkout_root, | |
742 checkouts[0].file_tuples, | |
743 options.issue, | |
744 options.patchset, | |
745 options.email) | |
746 options.bot = presubmit_support.DoGetTrySlaves( | 711 options.bot = presubmit_support.DoGetTrySlaves( |
747 change, | |
748 checkouts[0].GetFileNames(), | 712 checkouts[0].GetFileNames(), |
749 checkouts[0].checkout_root, | 713 checkouts[0].checkout_root, |
750 root_presubmit, | 714 root_presubmit, |
751 options.project, | 715 options.project, |
752 False, | 716 False, |
753 sys.stdout) | 717 sys.stdout) |
754 except ImportError: | 718 except ImportError: |
755 pass | 719 pass |
756 # If no bot is specified, either the default pool will be selected or the | 720 # If no bot is specified, either the default pool will be selected or the |
757 # try server will refuse the job. Either case we don't need to interfere. | 721 # try server will refuse the job. Either case we don't need to interfere. |
758 | 722 |
| 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 |
759 # Prevent rietveld updates if we aren't running all the tests. | 735 # Prevent rietveld updates if we aren't running all the tests. |
760 if options.testfilter is not None: | 736 if options.testfilter is not None: |
761 options.issue = None | 737 options.issue = None |
762 options.patchset = None | 738 options.patchset = None |
763 | 739 |
764 # Send the patch. | 740 # Send the patch. |
765 if options.send_patch: | 741 if options.send_patch: |
766 # If forced. | 742 # If forced. |
767 options.send_patch(options) | 743 options.send_patch(options) |
768 PrintSuccess(options) | 744 PrintSuccess(options) |
(...skipping 15 matching lines...) Expand all Loading... |
784 print >> sys.stderr, e | 760 print >> sys.stderr, e |
785 return 1 | 761 return 1 |
786 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 762 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
787 print >> sys.stderr, e | 763 print >> sys.stderr, e |
788 return 1 | 764 return 1 |
789 return 0 | 765 return 0 |
790 | 766 |
791 | 767 |
792 if __name__ == "__main__": | 768 if __name__ == "__main__": |
793 fix_encoding.fix_encoding() | 769 fix_encoding.fix_encoding() |
794 sys.exit(TryChange(None, None, [], False)) | 770 sys.exit(TryChange(None, [], False)) |
OLD | NEW |