Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(86)

Side by Side Diff: gclient_scm.py

Issue 250523004: Added remote 'git' branch awareness to 'gclient' (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Updated to fetch on branch sync Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | testing_support/fake_repos.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
OLDNEW
« no previous file with comments | « no previous file | testing_support/fake_repos.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698