OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Gclient-specific SCM-specific operations.""" | 5 """Gclient-specific SCM-specific operations.""" |
6 | 6 |
7 import collections | 7 import collections |
8 import logging | 8 import logging |
9 import os | 9 import os |
10 import posixpath | 10 import posixpath |
11 import re | 11 import re |
12 import sys | 12 import sys |
13 import tempfile | |
13 import threading | 14 import threading |
14 import time | 15 import time |
15 | 16 |
16 import gclient_utils | 17 import gclient_utils |
17 import scm | 18 import scm |
18 import subprocess2 | 19 import subprocess2 |
19 | 20 |
20 | 21 |
21 THIS_FILE_PATH = os.path.abspath(__file__) | 22 THIS_FILE_PATH = os.path.abspath(__file__) |
22 | 23 |
(...skipping 309 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
332 files = [] if file_list is not None else None | 333 files = [] if file_list is not None else None |
333 | 334 |
334 printed_path = False | 335 printed_path = False |
335 verbose = [] | 336 verbose = [] |
336 if options.verbose: | 337 if options.verbose: |
337 print('\n_____ %s%s' % (self.relpath, rev_str)) | 338 print('\n_____ %s%s' % (self.relpath, rev_str)) |
338 verbose = ['--verbose'] | 339 verbose = ['--verbose'] |
339 printed_path = True | 340 printed_path = True |
340 | 341 |
341 url = self._CreateOrUpdateCache(url, options) | 342 url = self._CreateOrUpdateCache(url, options) |
342 | 343 if (not os.path.exists(self.checkout_path) or |
343 if revision.startswith('refs/'): | 344 (os.path.isdir(self.check_path) and |
M-A Ruel
2013/07/18 14:55:04
What's check_path? Was this code path ever exercis
Isaac (away)
2013/07/19 20:13:23
should be checkout_path, I uploaded w/ bypass-hook
| |
344 rev_type = "branch" | 345 not os.path.exists(os.path.join(self.checkout_path, '.git')))): |
345 elif revision.startswith('origin/'): | |
346 # For compatability with old naming, translate 'origin' to 'refs/heads' | |
347 revision = revision.replace('origin/', 'refs/heads/') | |
348 rev_type = "branch" | |
349 else: | |
350 # hash is also a tag, only make a distinction at checkout | |
351 rev_type = "hash" | |
352 | |
353 if not os.path.exists(self.checkout_path) or ( | |
354 os.path.isdir(self.checkout_path) and | |
355 not os.listdir(self.checkout_path)): | |
356 gclient_utils.safe_makedirs(os.path.dirname(self.checkout_path)) | |
357 self._Clone(revision, url, options) | 346 self._Clone(revision, url, options) |
358 self.UpdateSubmoduleConfig() | 347 self.UpdateSubmoduleConfig() |
359 if file_list is not None: | 348 if file_list is not None: |
360 files = self._Capture(['ls-files']).splitlines() | 349 files = self._Capture(['ls-files']).splitlines() |
361 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) | 350 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) |
362 if not verbose: | 351 if not verbose: |
363 # Make the output a little prettier. It's nice to have some whitespace | 352 # Make the output a little prettier. It's nice to have some whitespace |
364 # between projects when cloning. | 353 # between projects when cloning. |
365 print('') | 354 print('') |
366 return | 355 return |
(...skipping 26 matching lines...) Expand all Loading... | |
393 ['git', 'config', 'remote.origin.gclient-auto-fix-url'], | 382 ['git', 'config', 'remote.origin.gclient-auto-fix-url'], |
394 cwd=self.checkout_path).strip() != 'False'): | 383 cwd=self.checkout_path).strip() != 'False'): |
395 print('_____ switching %s to a new upstream' % self.relpath) | 384 print('_____ switching %s to a new upstream' % self.relpath) |
396 # Make sure it's clean | 385 # Make sure it's clean |
397 self._CheckClean(rev_str) | 386 self._CheckClean(rev_str) |
398 # Switch over to the new upstream | 387 # Switch over to the new upstream |
399 self._Run(['remote', 'set-url', 'origin', url], options) | 388 self._Run(['remote', 'set-url', 'origin', url], options) |
400 self._FetchAndReset(revision, file_list, options) | 389 self._FetchAndReset(revision, file_list, options) |
401 return | 390 return |
402 | 391 |
403 if not self._IsValidGitRepo(): | |
404 # .git directory is hosed for some reason, set it back up. | |
405 print('_____ %s/.git is corrupted, rebuilding' % self.relpath) | |
406 self._Run(['init'], options) | |
407 self._Run(['remote', 'set-url', 'origin', url], options) | |
408 | |
409 if not self._HasHead(): | 392 if not self._HasHead(): |
410 # Previous checkout was aborted before branches could be created in repo, | 393 # Previous checkout was aborted before branches could be created in repo, |
411 # so we need to reconstruct them here. | 394 # so we need to reconstruct them here. |
395 self._Run(['init'], options) | |
396 self._Run(['remote', 'set-url', 'origin', url], options) | |
412 self._Run(['-c', 'core.deltaBaseCacheLimit=2g', 'pull', 'origin', | 397 self._Run(['-c', 'core.deltaBaseCacheLimit=2g', 'pull', 'origin', |
413 'master'], options) | 398 'master'], options) |
414 self._FetchAndReset(revision, file_list, options) | 399 self._FetchAndReset(revision, file_list, options) |
415 | 400 |
416 cur_branch = self._GetCurrentBranch() | 401 cur_branch = self._GetCurrentBranch() |
417 | 402 |
418 # Cases: | 403 # Cases: |
419 # 0) HEAD is detached. Probably from our initial clone. | 404 # 0) HEAD is detached. Probably from our initial clone. |
420 # - make sure HEAD is contained by a named ref, then update. | 405 # - make sure HEAD is contained by a named ref, then update. |
421 # Cases 1-4. HEAD is a branch. | 406 # Cases 1-4. HEAD is a branch. |
422 # 1) current branch is not tracking a remote branch (could be git-svn) | 407 # 1) current branch is not tracking a remote branch (could be git-svn) |
423 # - try to rebase onto the new hash or branch | 408 # - try to rebase onto the new hash or branch |
424 # 2) current branch is tracking a remote branch with local committed | 409 # 2) current branch is tracking a remote branch with local committed |
425 # changes, but the DEPS file switched to point to a hash | 410 # changes, but the DEPS file switched to point to a hash |
426 # - rebase those changes on top of the hash | 411 # - rebase those changes on top of the hash |
427 # 3) current branch is tracking a remote branch w/or w/out changes, | 412 # 3) current branch is tracking a remote branch w/or w/out changes, |
428 # no switch | 413 # no switch |
429 # - see if we can FF, if not, prompt the user for rebase, merge, or stop | 414 # - see if we can FF, if not, prompt the user for rebase, merge, or stop |
430 # 4) current branch is tracking a remote branch, switches to a different | 415 # 4) current branch is tracking a remote branch, switches to a different |
431 # remote branch | 416 # remote branch |
432 # - exit | 417 # - exit |
433 | 418 |
434 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for | 419 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for |
435 # a tracking branch | 420 # a tracking branch |
436 # or 'master' if not a tracking branch (it's based on a specific rev/hash) | 421 # or 'master' if not a tracking branch (it's based on a specific rev/hash) |
437 # or it returns None if it couldn't find an upstream | 422 # or it returns None if it couldn't find an upstream |
423 if revision.startswith('refs/'): | |
424 rev_type = "branch" | |
425 elif revision.startswith('origin/'): | |
426 # For compatability with old naming, translate 'origin' to 'refs/heads' | |
427 revision = revision.replace('origin/', 'refs/heads/') | |
428 rev_type = "branch" | |
429 else: | |
430 # hash is also a tag, only make a distinction at checkout | |
431 rev_type = "hash" | |
432 | |
438 if cur_branch is None: | 433 if cur_branch is None: |
439 upstream_branch = None | 434 upstream_branch = None |
440 current_type = "detached" | 435 current_type = "detached" |
441 logging.debug("Detached HEAD") | 436 logging.debug("Detached HEAD") |
442 else: | 437 else: |
443 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path) | 438 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path) |
444 if not upstream_branch or not upstream_branch.startswith('refs/remotes'): | 439 if not upstream_branch or not upstream_branch.startswith('refs/remotes'): |
445 current_type = "hash" | 440 current_type = "hash" |
446 logging.debug("Current branch is not tracking an upstream (remote)" | 441 logging.debug("Current branch is not tracking an upstream (remote)" |
447 " branch.") | 442 " branch.") |
(...skipping 320 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
768 cwd=folder) | 763 cwd=folder) |
769 assert self._NormalizeGitURL(existing_url) == self._NormalizeGitURL(url) | 764 assert self._NormalizeGitURL(existing_url) == self._NormalizeGitURL(url) |
770 | 765 |
771 # Would normally use `git remote update`, but it doesn't support | 766 # Would normally use `git remote update`, but it doesn't support |
772 # --progress, so use fetch instead. | 767 # --progress, so use fetch instead. |
773 self._Run(['fetch'] + v + ['--multiple', '--progress', '--all'], | 768 self._Run(['fetch'] + v + ['--multiple', '--progress', '--all'], |
774 options, git_filter=True, filter_fn=filter_fn, cwd=folder) | 769 options, git_filter=True, filter_fn=filter_fn, cwd=folder) |
775 return folder | 770 return folder |
776 | 771 |
777 def _Clone(self, revision, url, options): | 772 def _Clone(self, revision, url, options): |
778 """Clone a git repository from the given URL. | 773 """Clone a git repository from the given URL.""" |
779 | |
780 Once we've cloned the repo, we checkout a working branch if the specified | |
781 revision is a branch head. If it is a tag or a specific commit, then we | |
782 leave HEAD detached as it makes future updates simpler -- in this case the | |
783 user should first create a new branch or switch to an existing branch before | |
784 making changes in the repo.""" | |
785 if not options.verbose: | 774 if not options.verbose: |
786 # git clone doesn't seem to insert a newline properly before printing | 775 # git clone doesn't seem to insert a newline properly before printing |
787 # to stdout | 776 # to stdout |
788 print('') | 777 print('') |
789 template_path = os.path.join( | 778 template_path = os.path.join( |
790 os.path.dirname(THIS_FILE_PATH), 'git-templates') | 779 os.path.dirname(THIS_FILE_PATH), 'git-templates') |
791 clone_cmd = ['-c', 'core.deltaBaseCacheLimit=2g', 'clone', '--progress', | 780 clone_cmd = ['-c', 'core.deltaBaseCacheLimit=2g', 'clone', '--no-checkout', |
792 '--template=%s' % template_path] | 781 '--progress', '--template=%s' % template_path] |
793 if self.cache_dir: | 782 if self.cache_dir: |
794 clone_cmd.append('--shared') | 783 clone_cmd.append('--shared') |
795 if revision.startswith('refs/heads/'): | |
796 clone_cmd.extend(['-b', revision.replace('refs/heads/', '')]) | |
797 detach_head = False | |
798 else: | |
799 detach_head = True | |
800 if options.verbose: | 784 if options.verbose: |
801 clone_cmd.append('--verbose') | 785 clone_cmd.append('--verbose') |
802 clone_cmd.extend([url, self.checkout_path]) | 786 clone_cmd.append(url) |
803 | |
804 # If the parent directory does not exist, Git clone on Windows will not | 787 # If the parent directory does not exist, Git clone on Windows will not |
805 # create it, so we need to do it manually. | 788 # create it, so we need to do it manually. |
806 parent_dir = os.path.dirname(self.checkout_path) | 789 parent_dir = os.path.dirname(self.checkout_path) |
807 if not os.path.exists(parent_dir): | 790 if not os.path.exists(parent_dir): |
808 gclient_utils.safe_makedirs(parent_dir) | 791 gclient_utils.safe_makedirs(parent_dir) |
809 | 792 try: |
810 for _ in range(3): | 793 tmp_dir = tempfile.mkdtemp( |
M-A Ruel
2013/07/18 14:55:04
In practice, you want to have this just before the
Isaac (away)
2013/07/19 20:13:23
Done.
| |
811 try: | 794 suffix='_%s' % os.path.basename(self.checkout_path), |
M-A Ruel
2013/07/18 14:55:04
I'd also prefer a prefix, so it's easier to remove
Isaac (away)
2013/07/19 20:13:23
Done.
| |
812 self._Run(clone_cmd, options, cwd=self._root_dir, git_filter=True) | 795 dir=parent_dir) |
813 break | 796 clone_cmd.append(tmp_dir) |
814 except subprocess2.CalledProcessError, e: | 797 for _ in range(3): |
815 # Too bad we don't have access to the actual output yet. | 798 try: |
816 # We should check for "transfer closed with NNN bytes remaining to | 799 self._Run(clone_cmd, options, cwd=None, git_filter=True) |
817 # read". In the meantime, just make sure .git exists. | 800 break |
818 if (e.returncode == 128 and | 801 except subprocess2.CalledProcessError, e: |
819 os.path.exists(os.path.join(self.checkout_path, '.git'))): | 802 # Too bad we don't have access to the actual output yet. |
820 print(str(e)) | 803 # We should check for "transfer closed with NNN bytes remaining to |
821 print('Retrying...') | 804 # read". In the meantime, just make sure .git exists. |
822 continue | 805 if (e.returncode == 128 and |
823 raise e | 806 os.path.exists(os.path.join(tmp_dir, '.git'))): |
824 | 807 print(str(e)) |
825 # Update the "branch-heads" remote-tracking branches, since we might need it | 808 print('Retrying...') |
826 # to checkout a specific revision below. | 809 continue |
827 self._UpdateBranchHeads(options, fetch=True) | 810 raise e |
828 | 811 os.rename(os.path.join(tmp_dir, '.git'), |
829 if detach_head: | 812 os.path.join(self.checkout_path, '.git')) |
813 finally: | |
814 if os.listdir(tmp_dir): | |
815 print('\n_____ removing non-empty tmp dir %s' % tmp_dir) | |
816 gclient_utils.rmtree(tmp_dir) | |
817 if revision.startswith('refs/heads/'): | |
818 self._Run(['checkout', revision.replace('refs/heads/', '')]) | |
819 else: | |
830 # Squelch git's very verbose detached HEAD warning and use our own | 820 # Squelch git's very verbose detached HEAD warning and use our own |
831 self._Capture(['checkout', '--quiet', '%s' % revision]) | 821 self._Run(['checkout', '--quiet', revision]) |
832 print( | 822 print( |
833 ('Checked out %s to a detached HEAD. Before making any commits\n' | 823 ('Checked out %s to a detached HEAD. Before making any commits\n' |
834 'in this repo, you should use \'git checkout <branch>\' to switch to\n' | 824 'in this repo, you should use \'git checkout <branch>\' to switch to\n' |
835 'an existing branch or use \'git checkout origin -b <branch>\' to\n' | 825 'an existing branch or use \'git checkout origin -b <branch>\' to\n' |
836 'create a new branch for your work.') % revision) | 826 'create a new branch for your work.') % revision) |
837 | 827 |
828 | |
M-A Ruel
2013/07/18 14:55:04
Remove, it's not between file level symbols.
| |
838 def _AttemptRebase(self, upstream, files, options, newbase=None, | 829 def _AttemptRebase(self, upstream, files, options, newbase=None, |
839 branch=None, printed_path=False): | 830 branch=None, printed_path=False): |
840 """Attempt to rebase onto either upstream or, if specified, newbase.""" | 831 """Attempt to rebase onto either upstream or, if specified, newbase.""" |
841 if files is not None: | 832 if files is not None: |
842 files.extend(self._Capture(['diff', upstream, '--name-only']).split()) | 833 files.extend(self._Capture(['diff', upstream, '--name-only']).split()) |
843 revision = upstream | 834 revision = upstream |
844 if newbase: | 835 if newbase: |
845 revision = newbase | 836 revision = newbase |
846 if not printed_path: | 837 if not printed_path: |
847 print('\n_____ %s : Attempting rebase onto %s...' % ( | 838 print('\n_____ %s : Attempting rebase onto %s...' % ( |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
899 "manually.\ncd %s && git " % | 890 "manually.\ncd %s && git " % |
900 self.checkout_path | 891 self.checkout_path |
901 + "%s" % ' '.join(rebase_cmd)) | 892 + "%s" % ' '.join(rebase_cmd)) |
902 | 893 |
903 print(rebase_output.strip()) | 894 print(rebase_output.strip()) |
904 if not options.verbose: | 895 if not options.verbose: |
905 # Make the output a little prettier. It's nice to have some | 896 # Make the output a little prettier. It's nice to have some |
906 # whitespace between projects when syncing. | 897 # whitespace between projects when syncing. |
907 print('') | 898 print('') |
908 | 899 |
909 def _IsValidGitRepo(self): | |
910 """Returns if the directory is a valid git repository. | |
911 | |
912 Checks if git status works. | |
913 """ | |
914 try: | |
915 self._Capture(['status']) | |
916 return True | |
917 except subprocess2.CalledProcessError: | |
918 return False | |
919 | |
920 def _HasHead(self): | 900 def _HasHead(self): |
921 """Returns True if any commit is checked out. | 901 """Returns True if any commit is checked out. |
922 | 902 |
923 This is done by checking if rev-parse HEAD works in the current repository. | 903 This is done by checking if rev-parse HEAD works in the current repository. |
924 """ | 904 """ |
925 try: | 905 try: |
926 self._GetCurrentBranch() | 906 self._GetCurrentBranch() |
927 return True | 907 return True |
928 except subprocess2.CalledProcessError: | 908 except subprocess2.CalledProcessError: |
929 return False | 909 return False |
(...skipping 505 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1435 new_command.append('--force') | 1415 new_command.append('--force') |
1436 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1416 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1437 new_command.extend(('--accept', 'theirs-conflict')) | 1417 new_command.extend(('--accept', 'theirs-conflict')) |
1438 elif options.manually_grab_svn_rev: | 1418 elif options.manually_grab_svn_rev: |
1439 new_command.append('--force') | 1419 new_command.append('--force') |
1440 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1420 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1441 new_command.extend(('--accept', 'postpone')) | 1421 new_command.extend(('--accept', 'postpone')) |
1442 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1422 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1443 new_command.extend(('--accept', 'postpone')) | 1423 new_command.extend(('--accept', 'postpone')) |
1444 return new_command | 1424 return new_command |
OLD | NEW |