OLD | NEW |
1 # coding=utf8 | 1 # coding=utf8 |
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 """Manages a project checkout. | 5 """Manages a project checkout. |
6 | 6 |
7 Includes support for svn, git-svn and git. | 7 Includes support for svn, git-svn and git. |
8 """ | 8 """ |
9 | 9 |
10 import ConfigParser | 10 import ConfigParser |
11 import fnmatch | 11 import fnmatch |
12 import logging | 12 import logging |
13 import os | 13 import os |
14 import re | 14 import re |
15 import shutil | 15 import shutil |
16 import subprocess | 16 import subprocess |
17 import sys | 17 import sys |
18 import tempfile | 18 import tempfile |
19 | 19 |
20 import patch | 20 import patch |
21 import scm | 21 import scm |
22 import subprocess2 | 22 import subprocess2 |
23 | 23 |
24 | 24 |
| 25 if sys.platform in ('cygwin', 'win32'): |
| 26 # Disable timeouts on Windows since we can't have shells with timeouts. |
| 27 GLOBAL_TIMEOUT = None |
| 28 FETCH_TIMEOUT = None |
| 29 else: |
| 30 # Default timeout of 15 minutes. |
| 31 GLOBAL_TIMEOUT = 15*60 |
| 32 # Use a larger timeout for checkout since it can be a genuinely slower |
| 33 # operation. |
| 34 FETCH_TIMEOUT = 30*60 |
| 35 |
| 36 |
25 def get_code_review_setting(path, key, | 37 def get_code_review_setting(path, key, |
26 codereview_settings_file='codereview.settings'): | 38 codereview_settings_file='codereview.settings'): |
27 """Parses codereview.settings and return the value for the key if present. | 39 """Parses codereview.settings and return the value for the key if present. |
28 | 40 |
29 Don't cache the values in case the file is changed.""" | 41 Don't cache the values in case the file is changed.""" |
30 # TODO(maruel): Do not duplicate code. | 42 # TODO(maruel): Do not duplicate code. |
31 settings = {} | 43 settings = {} |
32 try: | 44 try: |
33 settings_file = open(os.path.join(path, codereview_settings_file), 'r') | 45 settings_file = open(os.path.join(path, codereview_settings_file), 'r') |
34 try: | 46 try: |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) | 202 stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) |
191 if p.diff_hunks: | 203 if p.diff_hunks: |
192 cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel] | 204 cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel] |
193 if verbose: | 205 if verbose: |
194 cmd.append('--verbose') | 206 cmd.append('--verbose') |
195 stdout.append( | 207 stdout.append( |
196 subprocess2.check_output( | 208 subprocess2.check_output( |
197 cmd, | 209 cmd, |
198 stdin=p.get(False), | 210 stdin=p.get(False), |
199 stderr=subprocess2.STDOUT, | 211 stderr=subprocess2.STDOUT, |
200 cwd=self.project_path)) | 212 cwd=self.project_path, |
| 213 timeout=GLOBAL_TIMEOUT)) |
201 elif p.is_new and not os.path.exists(filepath): | 214 elif p.is_new and not os.path.exists(filepath): |
202 # There is only a header. Just create the file. | 215 # There is only a header. Just create the file. |
203 open(filepath, 'w').close() | 216 open(filepath, 'w').close() |
204 stdout.append('Created an empty file.') | 217 stdout.append('Created an empty file.') |
205 for post in post_processors: | 218 for post in post_processors: |
206 post(self, p) | 219 post(self, p) |
207 if verbose: | 220 if verbose: |
208 print p.filename | 221 print p.filename |
209 print align_stdout(stdout) | 222 print align_stdout(stdout) |
210 except OSError, e: | 223 except OSError, e: |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
269 if self.commit_user: | 282 if self.commit_user: |
270 args.extend(['--username', self.commit_user]) | 283 args.extend(['--username', self.commit_user]) |
271 if self.commit_pwd: | 284 if self.commit_pwd: |
272 args.extend(['--password', self.commit_pwd]) | 285 args.extend(['--password', self.commit_pwd]) |
273 return args | 286 return args |
274 | 287 |
275 def _check_call_svn(self, args, **kwargs): | 288 def _check_call_svn(self, args, **kwargs): |
276 """Runs svn and throws an exception if the command failed.""" | 289 """Runs svn and throws an exception if the command failed.""" |
277 kwargs.setdefault('cwd', self.project_path) | 290 kwargs.setdefault('cwd', self.project_path) |
278 kwargs.setdefault('stdout', self.VOID) | 291 kwargs.setdefault('stdout', self.VOID) |
| 292 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
279 return subprocess2.check_call_out( | 293 return subprocess2.check_call_out( |
280 self._add_svn_flags(args, False), **kwargs) | 294 self._add_svn_flags(args, False), **kwargs) |
281 | 295 |
282 def _check_output_svn(self, args, credentials=True, **kwargs): | 296 def _check_output_svn(self, args, credentials=True, **kwargs): |
283 """Runs svn and throws an exception if the command failed. | 297 """Runs svn and throws an exception if the command failed. |
284 | 298 |
285 Returns the output. | 299 Returns the output. |
286 """ | 300 """ |
287 kwargs.setdefault('cwd', self.project_path) | 301 kwargs.setdefault('cwd', self.project_path) |
288 return subprocess2.check_output( | 302 return subprocess2.check_output( |
289 self._add_svn_flags(args, True, credentials), | 303 self._add_svn_flags(args, True, credentials), |
290 stderr=subprocess2.STDOUT, | 304 stderr=subprocess2.STDOUT, |
| 305 timeout=GLOBAL_TIMEOUT, |
291 **kwargs) | 306 **kwargs) |
292 | 307 |
293 @staticmethod | 308 @staticmethod |
294 def _parse_svn_info(output, key): | 309 def _parse_svn_info(output, key): |
295 """Returns value for key from svn info output. | 310 """Returns value for key from svn info output. |
296 | 311 |
297 Case insensitive. | 312 Case insensitive. |
298 """ | 313 """ |
299 values = {} | 314 values = {} |
300 key = key.lower() | 315 key = key.lower() |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
380 if p.diff_hunks: | 395 if p.diff_hunks: |
381 cmd = [ | 396 cmd = [ |
382 'patch', | 397 'patch', |
383 '-p%s' % p.patchlevel, | 398 '-p%s' % p.patchlevel, |
384 '--forward', | 399 '--forward', |
385 '--force', | 400 '--force', |
386 '--no-backup-if-mismatch', | 401 '--no-backup-if-mismatch', |
387 ] | 402 ] |
388 stdout.append( | 403 stdout.append( |
389 subprocess2.check_output( | 404 subprocess2.check_output( |
390 cmd, stdin=p.get(False), cwd=self.project_path)) | 405 cmd, |
| 406 stdin=p.get(False), |
| 407 cwd=self.project_path, |
| 408 timeout=GLOBAL_TIMEOUT)) |
391 elif p.is_new and not os.path.exists(filepath): | 409 elif p.is_new and not os.path.exists(filepath): |
392 # There is only a header. Just create the file if it doesn't | 410 # There is only a header. Just create the file if it doesn't |
393 # exist. | 411 # exist. |
394 open(filepath, 'w').close() | 412 open(filepath, 'w').close() |
395 stdout.append('Created an empty file.') | 413 stdout.append('Created an empty file.') |
396 if p.is_new and not p.source_filename: | 414 if p.is_new and not p.source_filename: |
397 # Do not run it if p.source_filename is defined, since svn copy was | 415 # Do not run it if p.source_filename is defined, since svn copy was |
398 # using above. | 416 # using above. |
399 stdout.append( | 417 stdout.append( |
400 self._check_output_svn( | 418 self._check_output_svn( |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
478 """ | 496 """ |
479 flags = ['--ignore-externals'] | 497 flags = ['--ignore-externals'] |
480 if revision: | 498 if revision: |
481 flags.extend(['--revision', str(revision)]) | 499 flags.extend(['--revision', str(revision)]) |
482 if os.path.isdir(self.project_path): | 500 if os.path.isdir(self.project_path): |
483 # This may remove any part (or all) of the checkout. | 501 # This may remove any part (or all) of the checkout. |
484 scm.SVN.Revert(self.project_path, no_ignore=True) | 502 scm.SVN.Revert(self.project_path, no_ignore=True) |
485 | 503 |
486 if os.path.isdir(self.project_path): | 504 if os.path.isdir(self.project_path): |
487 # Revive files that were deleted in scm.SVN.Revert(). | 505 # Revive files that were deleted in scm.SVN.Revert(). |
488 self._check_call_svn(['update', '--force'] + flags) | 506 self._check_call_svn(['update', '--force'] + flags, |
| 507 timeout=FETCH_TIMEOUT) |
489 else: | 508 else: |
490 logging.info( | 509 logging.info( |
491 'Directory %s is not present, checking it out.' % self.project_path) | 510 'Directory %s is not present, checking it out.' % self.project_path) |
492 self._check_call_svn( | 511 self._check_call_svn( |
493 ['checkout', self.svn_url, self.project_path] + flags, cwd=None) | 512 ['checkout', self.svn_url, self.project_path] + flags, cwd=None, |
| 513 timeout=FETCH_TIMEOUT) |
494 return self._get_revision() | 514 return self._get_revision() |
495 | 515 |
496 def _get_revision(self): | 516 def _get_revision(self): |
497 out = self._check_output_svn(['info', '.']) | 517 out = self._check_output_svn(['info', '.']) |
498 revision = int(self._parse_svn_info(out, 'revision')) | 518 revision = int(self._parse_svn_info(out, 'revision')) |
499 if revision != self._last_seen_revision: | 519 if revision != self._last_seen_revision: |
500 logging.info('Updated to revision %d' % revision) | 520 logging.info('Updated to revision %d' % revision) |
501 self._last_seen_revision = revision | 521 self._last_seen_revision = revision |
502 return revision | 522 return revision |
503 | 523 |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
645 | 665 |
646 Subclass needs to dcommit or push. | 666 Subclass needs to dcommit or push. |
647 """ | 667 """ |
648 assert isinstance(commit_message, unicode) | 668 assert isinstance(commit_message, unicode) |
649 self._check_call_git(['commit', '--amend', '-m', commit_message]) | 669 self._check_call_git(['commit', '--amend', '-m', commit_message]) |
650 return self._check_output_git(['rev-parse', 'HEAD']).strip() | 670 return self._check_output_git(['rev-parse', 'HEAD']).strip() |
651 | 671 |
652 def _check_call_git(self, args, **kwargs): | 672 def _check_call_git(self, args, **kwargs): |
653 kwargs.setdefault('cwd', self.project_path) | 673 kwargs.setdefault('cwd', self.project_path) |
654 kwargs.setdefault('stdout', self.VOID) | 674 kwargs.setdefault('stdout', self.VOID) |
| 675 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
655 return subprocess2.check_call_out(['git'] + args, **kwargs) | 676 return subprocess2.check_call_out(['git'] + args, **kwargs) |
656 | 677 |
657 def _call_git(self, args, **kwargs): | 678 def _call_git(self, args, **kwargs): |
658 """Like check_call but doesn't throw on failure.""" | 679 """Like check_call but doesn't throw on failure.""" |
659 kwargs.setdefault('cwd', self.project_path) | 680 kwargs.setdefault('cwd', self.project_path) |
660 kwargs.setdefault('stdout', self.VOID) | 681 kwargs.setdefault('stdout', self.VOID) |
| 682 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
661 return subprocess2.call(['git'] + args, **kwargs) | 683 return subprocess2.call(['git'] + args, **kwargs) |
662 | 684 |
663 def _check_output_git(self, args, **kwargs): | 685 def _check_output_git(self, args, **kwargs): |
664 kwargs.setdefault('cwd', self.project_path) | 686 kwargs.setdefault('cwd', self.project_path) |
| 687 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
665 return subprocess2.check_output( | 688 return subprocess2.check_output( |
666 ['git'] + args, stderr=subprocess2.STDOUT, **kwargs) | 689 ['git'] + args, stderr=subprocess2.STDOUT, **kwargs) |
667 | 690 |
668 def _branches(self): | 691 def _branches(self): |
669 """Returns the list of branches and the active one.""" | 692 """Returns the list of branches and the active one.""" |
670 out = self._check_output_git(['branch']).splitlines(False) | 693 out = self._check_output_git(['branch']).splitlines(False) |
671 branches = [l[2:] for l in out] | 694 branches = [l[2:] for l in out] |
672 active = None | 695 active = None |
673 for l in out: | 696 for l in out: |
674 if l.startswith('*'): | 697 if l.startswith('*'): |
(...skipping 16 matching lines...) Expand all Loading... |
691 | 714 |
692 def _fetch_remote(self): | 715 def _fetch_remote(self): |
693 """Fetches the remote without rebasing.""" | 716 """Fetches the remote without rebasing.""" |
694 raise NotImplementedError() | 717 raise NotImplementedError() |
695 | 718 |
696 | 719 |
697 class GitCheckout(GitCheckoutBase): | 720 class GitCheckout(GitCheckoutBase): |
698 """Git checkout implementation.""" | 721 """Git checkout implementation.""" |
699 def _fetch_remote(self): | 722 def _fetch_remote(self): |
700 # git fetch is always verbose even with -q -q so redirect its output. | 723 # git fetch is always verbose even with -q -q so redirect its output. |
701 self._check_output_git(['fetch', self.remote, self.remote_branch]) | 724 self._check_output_git(['fetch', self.remote, self.remote_branch], |
| 725 timeout=FETCH_TIMEOUT) |
702 | 726 |
703 | 727 |
704 class ReadOnlyCheckout(object): | 728 class ReadOnlyCheckout(object): |
705 """Converts a checkout into a read-only one.""" | 729 """Converts a checkout into a read-only one.""" |
706 def __init__(self, checkout, post_processors=None): | 730 def __init__(self, checkout, post_processors=None): |
707 super(ReadOnlyCheckout, self).__init__() | 731 super(ReadOnlyCheckout, self).__init__() |
708 self.checkout = checkout | 732 self.checkout = checkout |
709 self.post_processors = (post_processors or []) + ( | 733 self.post_processors = (post_processors or []) + ( |
710 self.checkout.post_processors or []) | 734 self.checkout.post_processors or []) |
711 | 735 |
(...skipping 15 matching lines...) Expand all Loading... |
727 def revisions(self, rev1, rev2): | 751 def revisions(self, rev1, rev2): |
728 return self.checkout.revisions(rev1, rev2) | 752 return self.checkout.revisions(rev1, rev2) |
729 | 753 |
730 @property | 754 @property |
731 def project_name(self): | 755 def project_name(self): |
732 return self.checkout.project_name | 756 return self.checkout.project_name |
733 | 757 |
734 @property | 758 @property |
735 def project_path(self): | 759 def project_path(self): |
736 return self.checkout.project_path | 760 return self.checkout.project_path |
OLD | NEW |