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 # Default timeout of 15 minutes. | |
26 GLOBAL_TIMEOUT = 15*60 | |
27 # Use a larger timeout for checkout since it can be a genuinely slower | |
28 # operation. | |
29 FETCH_TIMEOUT = 30*60 | |
30 | |
31 | |
25 def get_code_review_setting(path, key, | 32 def get_code_review_setting(path, key, |
26 codereview_settings_file='codereview.settings'): | 33 codereview_settings_file='codereview.settings'): |
27 """Parses codereview.settings and return the value for the key if present. | 34 """Parses codereview.settings and return the value for the key if present. |
28 | 35 |
29 Don't cache the values in case the file is changed.""" | 36 Don't cache the values in case the file is changed.""" |
30 # TODO(maruel): Do not duplicate code. | 37 # TODO(maruel): Do not duplicate code. |
31 settings = {} | 38 settings = {} |
32 try: | 39 try: |
33 settings_file = open(os.path.join(path, codereview_settings_file), 'r') | 40 settings_file = open(os.path.join(path, codereview_settings_file), 'r') |
34 try: | 41 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)) | 197 stdout.append('Copied %s -> %s' % (p.source_filename, p.filename)) |
191 if p.diff_hunks: | 198 if p.diff_hunks: |
192 cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel] | 199 cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel] |
193 if verbose: | 200 if verbose: |
194 cmd.append('--verbose') | 201 cmd.append('--verbose') |
195 stdout.append( | 202 stdout.append( |
196 subprocess2.check_output( | 203 subprocess2.check_output( |
197 cmd, | 204 cmd, |
198 stdin=p.get(False), | 205 stdin=p.get(False), |
199 stderr=subprocess2.STDOUT, | 206 stderr=subprocess2.STDOUT, |
200 cwd=self.project_path)) | 207 cwd=self.project_path, |
208 timeout=GLOBAL_TIMEOUT)) | |
201 elif p.is_new and not os.path.exists(filepath): | 209 elif p.is_new and not os.path.exists(filepath): |
202 # There is only a header. Just create the file. | 210 # There is only a header. Just create the file. |
203 open(filepath, 'w').close() | 211 open(filepath, 'w').close() |
204 stdout.append('Created an empty file.') | 212 stdout.append('Created an empty file.') |
205 for post in post_processors: | 213 for post in post_processors: |
206 post(self, p) | 214 post(self, p) |
207 if verbose: | 215 if verbose: |
208 print p.filename | 216 print p.filename |
209 print align_stdout(stdout) | 217 print align_stdout(stdout) |
210 except OSError, e: | 218 except OSError, e: |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
269 if self.commit_user: | 277 if self.commit_user: |
270 args.extend(['--username', self.commit_user]) | 278 args.extend(['--username', self.commit_user]) |
271 if self.commit_pwd: | 279 if self.commit_pwd: |
272 args.extend(['--password', self.commit_pwd]) | 280 args.extend(['--password', self.commit_pwd]) |
273 return args | 281 return args |
274 | 282 |
275 def _check_call_svn(self, args, **kwargs): | 283 def _check_call_svn(self, args, **kwargs): |
276 """Runs svn and throws an exception if the command failed.""" | 284 """Runs svn and throws an exception if the command failed.""" |
277 kwargs.setdefault('cwd', self.project_path) | 285 kwargs.setdefault('cwd', self.project_path) |
278 kwargs.setdefault('stdout', self.VOID) | 286 kwargs.setdefault('stdout', self.VOID) |
287 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) | |
279 return subprocess2.check_call_out( | 288 return subprocess2.check_call_out( |
280 self._add_svn_flags(args, False), **kwargs) | 289 self._add_svn_flags(args, False), |
290 **kwargs) | |
281 | 291 |
282 def _check_output_svn(self, args, credentials=True, **kwargs): | 292 def _check_output_svn(self, args, credentials=True, **kwargs): |
283 """Runs svn and throws an exception if the command failed. | 293 """Runs svn and throws an exception if the command failed. |
284 | 294 |
285 Returns the output. | 295 Returns the output. |
286 """ | 296 """ |
287 kwargs.setdefault('cwd', self.project_path) | 297 kwargs.setdefault('cwd', self.project_path) |
288 return subprocess2.check_output( | 298 return subprocess2.check_output( |
289 self._add_svn_flags(args, True, credentials), | 299 self._add_svn_flags(args, True, credentials), |
290 stderr=subprocess2.STDOUT, | 300 stderr=subprocess2.STDOUT, |
301 timeout=GLOBAL_TIMEOUT, | |
291 **kwargs) | 302 **kwargs) |
292 | 303 |
293 @staticmethod | 304 @staticmethod |
294 def _parse_svn_info(output, key): | 305 def _parse_svn_info(output, key): |
295 """Returns value for key from svn info output. | 306 """Returns value for key from svn info output. |
296 | 307 |
297 Case insensitive. | 308 Case insensitive. |
298 """ | 309 """ |
299 values = {} | 310 values = {} |
300 key = key.lower() | 311 key = key.lower() |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
380 if p.diff_hunks: | 391 if p.diff_hunks: |
381 cmd = [ | 392 cmd = [ |
382 'patch', | 393 'patch', |
383 '-p%s' % p.patchlevel, | 394 '-p%s' % p.patchlevel, |
384 '--forward', | 395 '--forward', |
385 '--force', | 396 '--force', |
386 '--no-backup-if-mismatch', | 397 '--no-backup-if-mismatch', |
387 ] | 398 ] |
388 stdout.append( | 399 stdout.append( |
389 subprocess2.check_output( | 400 subprocess2.check_output( |
390 cmd, stdin=p.get(False), cwd=self.project_path)) | 401 cmd, |
402 stdin=p.get(False), | |
403 cwd=self.project_path, | |
404 timeout=GLOBAL_TIMEOUT)) | |
391 elif p.is_new and not os.path.exists(filepath): | 405 elif p.is_new and not os.path.exists(filepath): |
392 # There is only a header. Just create the file if it doesn't | 406 # There is only a header. Just create the file if it doesn't |
393 # exist. | 407 # exist. |
394 open(filepath, 'w').close() | 408 open(filepath, 'w').close() |
395 stdout.append('Created an empty file.') | 409 stdout.append('Created an empty file.') |
396 if p.is_new and not p.source_filename: | 410 if p.is_new and not p.source_filename: |
397 # Do not run it if p.source_filename is defined, since svn copy was | 411 # Do not run it if p.source_filename is defined, since svn copy was |
398 # using above. | 412 # using above. |
399 stdout.append( | 413 stdout.append( |
400 self._check_output_svn( | 414 self._check_output_svn( |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
476 """Reverts local modifications or checks out if the directory is not | 490 """Reverts local modifications or checks out if the directory is not |
477 present. Use depot_tools's functionality to do this. | 491 present. Use depot_tools's functionality to do this. |
478 """ | 492 """ |
479 flags = ['--ignore-externals'] | 493 flags = ['--ignore-externals'] |
480 if revision: | 494 if revision: |
481 flags.extend(['--revision', str(revision)]) | 495 flags.extend(['--revision', str(revision)]) |
482 if not os.path.isdir(self.project_path): | 496 if not os.path.isdir(self.project_path): |
483 logging.info( | 497 logging.info( |
484 'Directory %s is not present, checking it out.' % self.project_path) | 498 'Directory %s is not present, checking it out.' % self.project_path) |
485 self._check_call_svn( | 499 self._check_call_svn( |
486 ['checkout', self.svn_url, self.project_path] + flags, cwd=None) | 500 ['checkout', self.svn_url, self.project_path] + flags, |
501 cwd=None, | |
502 timeout=FETCH_TIMEOUT) | |
Peter Mayo
2012/11/08 19:04:59
I would think checkout and update may be different
M-A Ruel
2012/11/08 19:07:57
I think it's fine. I'll change otherwise but I'm c
| |
487 else: | 503 else: |
504 # TODO(maruel): This command will shell out without a timeout. | |
488 scm.SVN.Revert(self.project_path, no_ignore=True) | 505 scm.SVN.Revert(self.project_path, no_ignore=True) |
489 # Revive files that were deleted in scm.SVN.Revert(). | 506 # Revive files that were deleted in scm.SVN.Revert(). |
490 self._check_call_svn(['update', '--force'] + flags) | 507 self._check_call_svn(['update', '--force'] + flags, timeout=FETCH_TIMEOUT) |
491 return self._get_revision() | 508 return self._get_revision() |
492 | 509 |
493 def _get_revision(self): | 510 def _get_revision(self): |
494 out = self._check_output_svn(['info', '.']) | 511 out = self._check_output_svn(['info', '.']) |
495 revision = int(self._parse_svn_info(out, 'revision')) | 512 revision = int(self._parse_svn_info(out, 'revision')) |
496 if revision != self._last_seen_revision: | 513 if revision != self._last_seen_revision: |
497 logging.info('Updated to revision %d' % revision) | 514 logging.info('Updated to revision %d' % revision) |
498 self._last_seen_revision = revision | 515 self._last_seen_revision = revision |
499 return revision | 516 return revision |
500 | 517 |
(...skipping 29 matching lines...) Expand all Loading... | |
530 | 547 |
531 Checks it out if not present and deletes the working branch. | 548 Checks it out if not present and deletes the working branch. |
532 """ | 549 """ |
533 assert self.remote_branch | 550 assert self.remote_branch |
534 assert os.path.isdir(self.project_path) | 551 assert os.path.isdir(self.project_path) |
535 self._check_call_git(['reset', '--hard', '--quiet']) | 552 self._check_call_git(['reset', '--hard', '--quiet']) |
536 if revision: | 553 if revision: |
537 try: | 554 try: |
538 revision = self._check_output_git(['rev-parse', revision]) | 555 revision = self._check_output_git(['rev-parse', revision]) |
539 except subprocess.CalledProcessError: | 556 except subprocess.CalledProcessError: |
540 self._check_call_git( | 557 self._fetch_remote() |
541 ['fetch', self.remote, self.remote_branch, '--quiet']) | |
542 revision = self._check_output_git(['rev-parse', revision]) | 558 revision = self._check_output_git(['rev-parse', revision]) |
543 self._check_call_git(['checkout', '--force', '--quiet', revision]) | 559 self._check_call_git(['checkout', '--force', '--quiet', revision]) |
544 else: | 560 else: |
545 branches, active = self._branches() | 561 branches, active = self._branches() |
546 if active != 'master': | 562 if active != 'master': |
547 self._check_call_git(['checkout', '--force', '--quiet', 'master']) | 563 self._check_call_git(['checkout', '--force', '--quiet', 'master']) |
548 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) | 564 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) |
549 if self.working_branch in branches: | 565 if self.working_branch in branches: |
550 self._call_git(['branch', '-D', self.working_branch]) | 566 self._call_git(['branch', '-D', self.working_branch]) |
551 | 567 |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
642 | 658 |
643 Subclass needs to dcommit or push. | 659 Subclass needs to dcommit or push. |
644 """ | 660 """ |
645 assert isinstance(commit_message, unicode) | 661 assert isinstance(commit_message, unicode) |
646 self._check_call_git(['commit', '--amend', '-m', commit_message]) | 662 self._check_call_git(['commit', '--amend', '-m', commit_message]) |
647 return self._check_output_git(['rev-parse', 'HEAD']).strip() | 663 return self._check_output_git(['rev-parse', 'HEAD']).strip() |
648 | 664 |
649 def _check_call_git(self, args, **kwargs): | 665 def _check_call_git(self, args, **kwargs): |
650 kwargs.setdefault('cwd', self.project_path) | 666 kwargs.setdefault('cwd', self.project_path) |
651 kwargs.setdefault('stdout', self.VOID) | 667 kwargs.setdefault('stdout', self.VOID) |
652 return subprocess2.check_call_out(['git'] + args, **kwargs) | 668 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
669 return subprocess2.check_call_out( | |
670 ['git'] + args, **kwargs) | |
653 | 671 |
654 def _call_git(self, args, **kwargs): | 672 def _call_git(self, args, **kwargs): |
655 """Like check_call but doesn't throw on failure.""" | 673 """Like check_call but doesn't throw on failure.""" |
656 kwargs.setdefault('cwd', self.project_path) | 674 kwargs.setdefault('cwd', self.project_path) |
657 kwargs.setdefault('stdout', self.VOID) | 675 kwargs.setdefault('stdout', self.VOID) |
658 return subprocess2.call(['git'] + args, **kwargs) | 676 return subprocess2.call(['git'] + args, timeout=GLOBAL_TIMEOUT, **kwargs) |
659 | 677 |
660 def _check_output_git(self, args, **kwargs): | 678 def _check_output_git(self, args, **kwargs): |
661 kwargs.setdefault('cwd', self.project_path) | 679 kwargs.setdefault('cwd', self.project_path) |
662 return subprocess2.check_output( | 680 return subprocess2.check_output( |
663 ['git'] + args, stderr=subprocess2.STDOUT, **kwargs) | 681 ['git'] + args, |
682 stderr=subprocess2.STDOUT, | |
683 timeout=GLOBAL_TIMEOUT, | |
684 **kwargs) | |
664 | 685 |
665 def _branches(self): | 686 def _branches(self): |
666 """Returns the list of branches and the active one.""" | 687 """Returns the list of branches and the active one.""" |
667 out = self._check_output_git(['branch']).splitlines(False) | 688 out = self._check_output_git(['branch']).splitlines(False) |
668 branches = [l[2:] for l in out] | 689 branches = [l[2:] for l in out] |
669 active = None | 690 active = None |
670 for l in out: | 691 for l in out: |
671 if l.startswith('*'): | 692 if l.startswith('*'): |
672 active = l[2:] | 693 active = l[2:] |
673 break | 694 break |
(...skipping 14 matching lines...) Expand all Loading... | |
688 | 709 |
689 def _fetch_remote(self): | 710 def _fetch_remote(self): |
690 """Fetches the remote without rebasing.""" | 711 """Fetches the remote without rebasing.""" |
691 raise NotImplementedError() | 712 raise NotImplementedError() |
692 | 713 |
693 | 714 |
694 class GitCheckout(GitCheckoutBase): | 715 class GitCheckout(GitCheckoutBase): |
695 """Git checkout implementation.""" | 716 """Git checkout implementation.""" |
696 def _fetch_remote(self): | 717 def _fetch_remote(self): |
697 # git fetch is always verbose even with -q -q so redirect its output. | 718 # git fetch is always verbose even with -q -q so redirect its output. |
698 self._check_output_git(['fetch', self.remote, self.remote_branch]) | 719 self._check_call_git( |
720 ['fetch', self.remote, self.remote_branch, '--quiet'], | |
Peter Mayo
2012/11/08 19:04:59
Add quiet to CL description.
| |
721 timeout=FETCH_TIMEOUT) | |
699 | 722 |
700 | 723 |
701 class ReadOnlyCheckout(object): | 724 class ReadOnlyCheckout(object): |
702 """Converts a checkout into a read-only one.""" | 725 """Converts a checkout into a read-only one.""" |
703 def __init__(self, checkout, post_processors=None): | 726 def __init__(self, checkout, post_processors=None): |
704 super(ReadOnlyCheckout, self).__init__() | 727 super(ReadOnlyCheckout, self).__init__() |
705 self.checkout = checkout | 728 self.checkout = checkout |
706 self.post_processors = (post_processors or []) + ( | 729 self.post_processors = (post_processors or []) + ( |
707 self.checkout.post_processors or []) | 730 self.checkout.post_processors or []) |
708 | 731 |
(...skipping 15 matching lines...) Expand all Loading... | |
724 def revisions(self, rev1, rev2): | 747 def revisions(self, rev1, rev2): |
725 return self.checkout.revisions(rev1, rev2) | 748 return self.checkout.revisions(rev1, rev2) |
726 | 749 |
727 @property | 750 @property |
728 def project_name(self): | 751 def project_name(self): |
729 return self.checkout.project_name | 752 return self.checkout.project_name |
730 | 753 |
731 @property | 754 @property |
732 def project_path(self): | 755 def project_path(self): |
733 return self.checkout.project_path | 756 return self.checkout.project_path |
OLD | NEW |