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 from __future__ import print_function | 7 from __future__ import print_function |
8 | 8 |
9 import logging | 9 import logging |
10 import os | 10 import os |
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
269 | 269 |
270 The patch file is generated from a diff of the merge base of HEAD and | 270 The patch file is generated from a diff of the merge base of HEAD and |
271 its upstream branch. | 271 its upstream branch. |
272 """ | 272 """ |
273 merge_base = self._Capture(['merge-base', 'HEAD', self.remote]) | 273 merge_base = self._Capture(['merge-base', 'HEAD', self.remote]) |
274 gclient_utils.CheckCallAndFilter( | 274 gclient_utils.CheckCallAndFilter( |
275 ['git', 'diff', merge_base], | 275 ['git', 'diff', merge_base], |
276 cwd=self.checkout_path, | 276 cwd=self.checkout_path, |
277 filter_fn=GitDiffFilterer(self.relpath).Filter, print_func=self.Print) | 277 filter_fn=GitDiffFilterer(self.relpath).Filter, print_func=self.Print) |
278 | 278 |
| 279 def _ParseRevision(self, revision): |
| 280 """Analyzes a revision string and determines what the repository-local |
| 281 refspec is for that revision. |
| 282 |
| 283 This function handles: |
| 284 - Standard local revision paths (refs/heads, refs/remotes) |
| 285 (e.g., refs/heads/master) |
| 286 - Remote-relative mirrored revision paths (e.g., origin/master) |
| 287 - Non-remote revision paths (anything else begining with 'refs/') are |
| 288 assumed to be refs on our remote. |
| 289 - Everything else (branches, commits) |
| 290 |
| 291 Params: |
| 292 revision: The revision string, fitting into one of the categories above, |
| 293 to resolve |
| 294 |
| 295 Return (dict): Refspec analysis dictionary including: |
| 296 - 'revision' (str): The refspec to use to reference the revision in the |
| 297 local repository (after it's been fetched). |
| 298 - 'type' (str): The type of revision, one of ('branch', 'hash') |
| 299 - 'mapped' (bool): If 'True', the revision is part of the default mapped |
| 300 revision set ('refs/heads/*'); if 'False', it needs to be |
| 301 explicitly fetched before it can be referenced at 'revision'. |
| 302 - 'fetch_refspec' (str): The parameter to use to fetch this revision from |
| 303 its origin. |
| 304 """ |
| 305 result = { |
| 306 'revision': revision, |
| 307 'type': 'branch', |
| 308 'mapped': True, |
| 309 'fetch_refspec': revision, |
| 310 } |
| 311 # |
| 312 # If 'revision' begins with some prefixes, chop them off to reference |
| 313 # local refs. |
| 314 for prefix, replacement in ( |
| 315 ('refs/heads/', None), |
| 316 ('refs/remotes/%s/' % (self.remote), None), |
| 317 ('%s/' % (self.remote), 'refs/heads/'), |
| 318 ): |
| 319 if revision.startswith(prefix): |
| 320 local_revision = revision[len(prefix):] |
| 321 if replacement is not None: |
| 322 revision = revision.replace(prefix, replacement) |
| 323 |
| 324 # Trim 'prefix' to make 'revision' relative to our standard |
| 325 # 'refs/heads/*' fetch mapping. |
| 326 result['revision'] = revision |
| 327 result['fetch_refspec'] = local_revision |
| 328 return result |
| 329 |
| 330 # If 'revision' begins with 'refs/', treat it as an unmapped remote ref |
| 331 if revision.startswith('refs/'): |
| 332 result['fetch_refspec'] = '%s:%s' % (revision, revision) |
| 333 result['mapped'] = False |
| 334 return result |
| 335 |
| 336 # Assume 'revision' is either a local branch or a commit |
| 337 result['type'] = 'hash' |
| 338 return result |
| 339 |
| 340 def _Fetch(self, options, revision=None): |
| 341 cfg = gclient_utils.DefaultIndexPackConfig() |
| 342 fetch_cmd = cfg + ['fetch', self.remote] |
| 343 |
| 344 if revision is not None: |
| 345 refdict = self._ParseRevision(revision) |
| 346 fetch_cmd.append(refdict['fetch_refspec']) |
| 347 |
| 348 if not options.verbose: |
| 349 fetch_cmd.append('--quiet') |
| 350 fetch_cmd.append('--prune') |
| 351 self._Run(fetch_cmd, options, retry=True) |
| 352 |
| 353 def _FetchIfRemote(self, options, revision): |
| 354 refdict = self._ParseRevision(revision) |
| 355 if not refdict['mapped']: |
| 356 self._Fetch(options, revision) |
| 357 |
279 def _FetchAndReset(self, revision, file_list, options): | 358 def _FetchAndReset(self, revision, file_list, options): |
280 """Equivalent to git fetch; git reset.""" | 359 """Equivalent to git fetch; git reset.""" |
| 360 self._UpdateBranchHeads(options) |
| 361 |
| 362 # Fetch default fetch targets ('master'...) |
| 363 self._Fetch(options) |
| 364 # Fetch our specific revision from the remote, if it's not in the default |
| 365 # fetch set. |
| 366 self._FetchIfRemote(options, revision) |
| 367 |
281 quiet = [] | 368 quiet = [] |
282 if not options.verbose: | 369 if not options.verbose: |
283 quiet = ['--quiet'] | 370 quiet.append('--quiet') |
284 self._UpdateBranchHeads(options, fetch=False) | |
285 | |
286 cfg = gclient_utils.DefaultIndexPackConfig(self.url) | |
287 fetch_cmd = cfg + ['fetch', self.remote, '--prune'] | |
288 self._Run(fetch_cmd + quiet, options, retry=True) | |
289 self._Run(['reset', '--hard', revision] + quiet, options) | 371 self._Run(['reset', '--hard', revision] + quiet, options) |
290 if file_list is not None: | 372 if file_list is not None: |
291 files = self._Capture(['ls-files']).splitlines() | 373 files = self._Capture(['ls-files']).splitlines() |
292 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) | 374 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) |
293 | 375 |
294 def update(self, options, args, file_list): | 376 def update(self, options, args, file_list): |
295 """Runs git to update or transparently checkout the working copy. | 377 """Runs git to update or transparently checkout the working copy. |
296 | 378 |
297 All updated files will be appended to file_list. | 379 All updated files will be appended to file_list. |
298 | 380 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
332 | 414 |
333 printed_path = False | 415 printed_path = False |
334 verbose = [] | 416 verbose = [] |
335 if options.verbose: | 417 if options.verbose: |
336 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) | 418 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) |
337 verbose = ['--verbose'] | 419 verbose = ['--verbose'] |
338 printed_path = True | 420 printed_path = True |
339 | 421 |
340 url = self._CreateOrUpdateCache(url, options) | 422 url = self._CreateOrUpdateCache(url, options) |
341 | 423 |
342 if revision.startswith('refs/'): | 424 refdict = self._ParseRevision(revision) |
343 rev_type = "branch" | 425 revision = refdict['revision'] |
344 elif revision.startswith(self.remote + '/'): | 426 rev_type = refdict['type'] |
345 # For compatibility with old naming, translate 'origin' to 'refs/heads' | |
346 revision = revision.replace(self.remote + '/', 'refs/heads/') | |
347 rev_type = "branch" | |
348 else: | |
349 # hash is also a tag, only make a distinction at checkout | |
350 rev_type = "hash" | |
351 | 427 |
352 if (not os.path.exists(self.checkout_path) or | 428 if (not os.path.exists(self.checkout_path) or |
353 (os.path.isdir(self.checkout_path) and | 429 (os.path.isdir(self.checkout_path) and |
354 not os.path.exists(os.path.join(self.checkout_path, '.git')))): | 430 not os.path.exists(os.path.join(self.checkout_path, '.git')))): |
355 if (os.path.isdir(self.checkout_path) and | 431 if (os.path.isdir(self.checkout_path) and |
356 not os.path.exists(os.path.join(self.checkout_path, '.git')) and | 432 not os.path.exists(os.path.join(self.checkout_path, '.git')) and |
357 os.listdir(self.checkout_path)): | 433 os.listdir(self.checkout_path)): |
358 # This is a little hack to work around checkouts which are created | 434 # This is a little hack to work around checkouts which are created |
359 # using "gclient config --name ." | 435 # using "gclient config --name ." |
360 if not self.relpath == '.': | 436 if not self.relpath == '.': |
361 self._DeleteOrMove(options.force) | 437 self._DeleteOrMove(options.force) |
362 self._Clone(revision, url, options) | 438 self._Clone(revision, url, options) |
363 self._UpdateBranchHeads(options, fetch=True) | 439 self._UpdateBranchHeads(options, fetch_revision=revision) |
364 if file_list is not None: | 440 if file_list is not None: |
365 files = self._Capture(['ls-files']).splitlines() | 441 files = self._Capture(['ls-files']).splitlines() |
366 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) | 442 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) |
367 if not verbose: | 443 if not verbose: |
368 # Make the output a little prettier. It's nice to have some whitespace | 444 # Make the output a little prettier. It's nice to have some whitespace |
369 # between projects when cloning. | 445 # between projects when cloning. |
370 self.Print('') | 446 self.Print('') |
371 return self._Capture(['rev-parse', '--verify', 'HEAD']) | 447 return self._Capture(['rev-parse', '--verify', 'HEAD']) |
372 | 448 |
373 if not managed: | 449 if not managed: |
374 self._UpdateBranchHeads(options, fetch=False) | 450 self._UpdateBranchHeads(options) |
375 self.Print('________ unmanaged solution; skipping %s' % self.relpath) | 451 self.Print('________ unmanaged solution; skipping %s' % self.relpath) |
376 return self._Capture(['rev-parse', '--verify', 'HEAD']) | 452 return self._Capture(['rev-parse', '--verify', 'HEAD']) |
377 | 453 |
378 # See if the url has changed (the unittests use git://foo for the url, let | 454 # See if the url has changed (the unittests use git://foo for the url, let |
379 # that through). | 455 # that through). |
380 current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) | 456 current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) |
381 return_early = False | 457 return_early = False |
382 # TODO(maruel): Delete url != 'git://foo' since it's just to make the | 458 # TODO(maruel): Delete url != 'git://foo' since it's just to make the |
383 # unit test pass. (and update the comment above) | 459 # unit test pass. (and update the comment above) |
384 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. | 460 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
437 else: | 513 else: |
438 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch) | 514 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch) |
439 | 515 |
440 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True): | 516 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True): |
441 # Update the remotes first so we have all the refs. | 517 # Update the remotes first so we have all the refs. |
442 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'], | 518 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'], |
443 cwd=self.checkout_path) | 519 cwd=self.checkout_path) |
444 if verbose: | 520 if verbose: |
445 self.Print(remote_output) | 521 self.Print(remote_output) |
446 | 522 |
447 self._UpdateBranchHeads(options, fetch=True) | 523 self._UpdateBranchHeads(options, fetch_revision=revision) |
448 | 524 |
449 # This is a big hammer, debatable if it should even be here... | 525 # This is a big hammer, debatable if it should even be here... |
450 if options.force or options.reset: | 526 if options.force or options.reset: |
451 target = 'HEAD' | 527 target = 'HEAD' |
452 if options.upstream and upstream_branch: | 528 if options.upstream and upstream_branch: |
453 target = upstream_branch | 529 target = upstream_branch |
454 self._Run(['reset', '--hard', target], options) | 530 self._Run(['reset', '--hard', target], options) |
455 | 531 |
456 if current_type == 'detached': | 532 if current_type == 'detached': |
457 # case 0 | 533 # case 0 |
(...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
783 try: | 859 try: |
784 clone_cmd.append(tmp_dir) | 860 clone_cmd.append(tmp_dir) |
785 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True) | 861 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True) |
786 gclient_utils.safe_makedirs(self.checkout_path) | 862 gclient_utils.safe_makedirs(self.checkout_path) |
787 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'), | 863 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'), |
788 os.path.join(self.checkout_path, '.git')) | 864 os.path.join(self.checkout_path, '.git')) |
789 except: | 865 except: |
790 traceback.print_exc(file=self.out_fh) | 866 traceback.print_exc(file=self.out_fh) |
791 raise | 867 raise |
792 finally: | 868 finally: |
793 if os.listdir(tmp_dir): | 869 if os.path.exists(tmp_dir) and os.listdir(tmp_dir): |
794 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir) | 870 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir) |
795 gclient_utils.rmtree(tmp_dir) | 871 gclient_utils.rmtree(tmp_dir) |
| 872 |
| 873 # Fetch our specific revision |
| 874 self._FetchIfRemote(options, revision) |
| 875 |
796 if revision.startswith('refs/heads/'): | 876 if revision.startswith('refs/heads/'): |
797 self._Run( | 877 self._Run( |
798 ['checkout', '--quiet', revision.replace('refs/heads/', '')], options) | 878 ['checkout', '--quiet', revision.replace('refs/heads/', '')], options) |
799 else: | 879 else: |
800 # Squelch git's very verbose detached HEAD warning and use our own | 880 # Squelch git's very verbose detached HEAD warning and use our own |
801 self._Run(['checkout', '--quiet', revision], options) | 881 self._Run(['checkout', '--quiet', revision], options) |
802 self.Print( | 882 self.Print( |
803 ('Checked out %s to a detached HEAD. Before making any commits\n' | 883 ('Checked out %s to a detached HEAD. Before making any commits\n' |
804 'in this repo, you should use \'git checkout <branch>\' to switch to\n' | 884 'in this repo, you should use \'git checkout <branch>\' to switch to\n' |
805 'an existing branch or use \'git checkout %s -b <branch>\' to\n' | 885 'an existing branch or use \'git checkout %s -b <branch>\' to\n' |
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
962 if branch == 'HEAD': | 1042 if branch == 'HEAD': |
963 return None | 1043 return None |
964 return branch | 1044 return branch |
965 | 1045 |
966 def _Capture(self, args, **kwargs): | 1046 def _Capture(self, args, **kwargs): |
967 kwargs.setdefault('cwd', self.checkout_path) | 1047 kwargs.setdefault('cwd', self.checkout_path) |
968 kwargs.setdefault('stderr', subprocess2.PIPE) | 1048 kwargs.setdefault('stderr', subprocess2.PIPE) |
969 env = scm.GIT.ApplyEnvVars(kwargs) | 1049 env = scm.GIT.ApplyEnvVars(kwargs) |
970 return subprocess2.check_output(['git'] + args, env=env, **kwargs).strip() | 1050 return subprocess2.check_output(['git'] + args, env=env, **kwargs).strip() |
971 | 1051 |
972 def _UpdateBranchHeads(self, options, fetch=False): | 1052 def _UpdateBranchHeads(self, options, fetch_revision=None): |
973 """Adds, and optionally fetches, "branch-heads" refspecs if requested.""" | 1053 """Adds, and optionally fetches, "branch-heads" refspecs if requested.""" |
974 if hasattr(options, 'with_branch_heads') and options.with_branch_heads: | 1054 if hasattr(options, 'with_branch_heads') and options.with_branch_heads: |
975 config_cmd = ['config', 'remote.%s.fetch' % self.remote, | 1055 config_cmd = ['config', 'remote.%s.fetch' % self.remote, |
976 '+refs/branch-heads/*:refs/remotes/branch-heads/*', | 1056 '+refs/branch-heads/*:refs/remotes/branch-heads/*', |
977 '^\\+refs/branch-heads/\\*:.*$'] | 1057 '^\\+refs/branch-heads/\\*:.*$'] |
978 self._Run(config_cmd, options) | 1058 self._Run(config_cmd, options) |
979 if fetch: | 1059 if fetch_revision is not None: |
980 cfg = gclient_utils.DefaultIndexPackConfig(self.url) | 1060 self._Fetch(options) |
981 fetch_cmd = cfg + ['fetch', self.remote] | 1061 |
982 if options.verbose: | 1062 if fetch_revision is not None: |
983 fetch_cmd.append('--verbose') | 1063 self._FetchIfRemote(options, fetch_revision) |
984 self._Run(fetch_cmd, options, retry=True) | |
985 | 1064 |
986 def _Run(self, args, options, **kwargs): | 1065 def _Run(self, args, options, **kwargs): |
987 cwd = kwargs.setdefault('cwd', self.checkout_path) | 1066 cwd = kwargs.setdefault('cwd', self.checkout_path) |
988 kwargs.setdefault('stdout', self.out_fh) | 1067 kwargs.setdefault('stdout', self.out_fh) |
989 kwargs['filter_fn'] = self.filter | 1068 kwargs['filter_fn'] = self.filter |
990 kwargs.setdefault('print_stdout', False) | 1069 kwargs.setdefault('print_stdout', False) |
991 env = scm.GIT.ApplyEnvVars(kwargs) | 1070 env = scm.GIT.ApplyEnvVars(kwargs) |
992 cmd = ['git'] + args | 1071 cmd = ['git'] + args |
993 header = "running '%s' in '%s'" % (' '.join(cmd), cwd) | 1072 header = "running '%s' in '%s'" % (' '.join(cmd), cwd) |
994 self.filter(header) | 1073 self.filter(header) |
(...skipping 462 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1457 new_command.append('--force') | 1536 new_command.append('--force') |
1458 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1537 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1459 new_command.extend(('--accept', 'theirs-conflict')) | 1538 new_command.extend(('--accept', 'theirs-conflict')) |
1460 elif options.manually_grab_svn_rev: | 1539 elif options.manually_grab_svn_rev: |
1461 new_command.append('--force') | 1540 new_command.append('--force') |
1462 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1541 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1463 new_command.extend(('--accept', 'postpone')) | 1542 new_command.extend(('--accept', 'postpone')) |
1464 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1543 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1465 new_command.extend(('--accept', 'postpone')) | 1544 new_command.extend(('--accept', 'postpone')) |
1466 return new_command | 1545 return new_command |
OLD | NEW |