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

Side by Side Diff: git_cache.py

Issue 1320383004: Allow 'git cache fetch' to re-bootstrap. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Added warning in _preserve_fetchspec Created 5 years, 3 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 | tests/git_cache_test.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 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 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 5
6 """A git command for managing a local cache of git repositories.""" 6 """A git command for managing a local cache of git repositories."""
7 7
8 from __future__ import print_function 8 from __future__ import print_function
9 import errno 9 import errno
10 import logging 10 import logging
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after
138 return self.is_locked() and self.pid == self._read_pid() 138 return self.is_locked() and self.pid == self._read_pid()
139 139
140 140
141 class Mirror(object): 141 class Mirror(object):
142 142
143 git_exe = 'git.bat' if sys.platform.startswith('win') else 'git' 143 git_exe = 'git.bat' if sys.platform.startswith('win') else 'git'
144 gsutil_exe = os.path.join( 144 gsutil_exe = os.path.join(
145 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py') 145 os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
146 cachepath_lock = threading.Lock() 146 cachepath_lock = threading.Lock()
147 147
148 @staticmethod
149 def parse_fetch_spec(spec):
150 """Parses and canonicalizes a fetch spec.
151
152 Returns (fetchspec, value_regex), where value_regex can be used
153 with 'git config --replace-all'.
154 """
155 parts = spec.split(':', 1)
156 src = parts[0].lstrip('+').rstrip('/')
157 if not src.startswith('refs/'):
158 src = 'refs/heads/%s' % src
159 dest = parts[1].rstrip('/') if len(parts) > 1 else src
160 regex = r'\+%s:.*' % src.replace('*', r'\*')
161 return ('+%s:%s' % (src, dest), regex)
162
148 def __init__(self, url, refs=None, print_func=None): 163 def __init__(self, url, refs=None, print_func=None):
149 self.url = url 164 self.url = url
150 self.refs = refs or [] 165 self.fetch_specs = set([self.parse_fetch_spec(ref) for ref in (refs or [])])
151 self.basedir = self.UrlToCacheDir(url) 166 self.basedir = self.UrlToCacheDir(url)
152 self.mirror_path = os.path.join(self.GetCachePath(), self.basedir) 167 self.mirror_path = os.path.join(self.GetCachePath(), self.basedir)
153 if print_func: 168 if print_func:
154 self.print = self.print_without_file 169 self.print = self.print_without_file
155 self.print_func = print_func 170 self.print_func = print_func
156 else: 171 else:
157 self.print = print 172 self.print = print
158 173
159 def print_without_file(self, message, **kwargs): 174 def print_without_file(self, message, **kwargs):
160 self.print_func(message) 175 self.print_func(message)
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
229 self.RunGit(['config', 'gc.autopacklimit', '0'], cwd=cwd) 244 self.RunGit(['config', 'gc.autopacklimit', '0'], cwd=cwd)
230 245
231 # Allocate more RAM for cache-ing delta chains, for better performance 246 # Allocate more RAM for cache-ing delta chains, for better performance
232 # of "Resolving deltas". 247 # of "Resolving deltas".
233 self.RunGit(['config', 'core.deltaBaseCacheLimit', 248 self.RunGit(['config', 'core.deltaBaseCacheLimit',
234 gclient_utils.DefaultDeltaBaseCacheLimit()], cwd=cwd) 249 gclient_utils.DefaultDeltaBaseCacheLimit()], cwd=cwd)
235 250
236 self.RunGit(['config', 'remote.origin.url', self.url], cwd=cwd) 251 self.RunGit(['config', 'remote.origin.url', self.url], cwd=cwd)
237 self.RunGit(['config', '--replace-all', 'remote.origin.fetch', 252 self.RunGit(['config', '--replace-all', 'remote.origin.fetch',
238 '+refs/heads/*:refs/heads/*', r'\+refs/heads/\*:.*'], cwd=cwd) 253 '+refs/heads/*:refs/heads/*', r'\+refs/heads/\*:.*'], cwd=cwd)
239 for ref in self.refs: 254 for spec, value_regex in self.fetch_specs:
240 ref = ref.lstrip('+').rstrip('/')
241 if ref.startswith('refs/'):
242 refspec = '+%s:%s' % (ref, ref)
243 regex = r'\+%s:.*' % ref.replace('*', r'\*')
244 else:
245 refspec = '+refs/%s/*:refs/%s/*' % (ref, ref)
246 regex = r'\+refs/heads/%s:.*' % ref.replace('*', r'\*')
247 self.RunGit( 255 self.RunGit(
248 ['config', '--replace-all', 'remote.origin.fetch', refspec, regex], 256 ['config', '--replace-all', 'remote.origin.fetch', spec, value_regex],
249 cwd=cwd) 257 cwd=cwd)
250 258
251 def bootstrap_repo(self, directory): 259 def bootstrap_repo(self, directory):
252 """Bootstrap the repo from Google Stroage if possible. 260 """Bootstrap the repo from Google Stroage if possible.
253 261
254 More apt-ly named bootstrap_repo_from_cloud_if_possible_else_do_nothing(). 262 More apt-ly named bootstrap_repo_from_cloud_if_possible_else_do_nothing().
255 """ 263 """
256 264
257 python_fallback = False 265 python_fallback = False
258 if (sys.platform.startswith('win') and 266 if (sys.platform.startswith('win') and
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
307 if retcode: 315 if retcode:
308 self.print( 316 self.print(
309 'Extracting bootstrap zipfile %s failed.\n' 317 'Extracting bootstrap zipfile %s failed.\n'
310 'Resuming normal operations.' % filename) 318 'Resuming normal operations.' % filename)
311 return False 319 return False
312 return True 320 return True
313 321
314 def exists(self): 322 def exists(self):
315 return os.path.isfile(os.path.join(self.mirror_path, 'config')) 323 return os.path.isfile(os.path.join(self.mirror_path, 'config'))
316 324
325 def _preserve_fetchspec(self):
326 """Read and preserve remote.origin.fetch from an existing mirror.
327
328 This modifies self.fetch_specs.
329 """
330 if not self.exists():
331 return
332 try:
333 config_fetchspecs = subprocess.check_output(
334 [self.git_exe, 'config', '--get-all', 'remote.origin.fetch'],
335 cwd=self.mirror_path)
336 for fetchspec in config_fetchspecs.splitlines():
337 self.fetch_specs.add(self.parse_fetch_spec(fetchspec))
338 except subprocess.CalledProcessError:
339 logging.warn('Tried and failed to preserve remote.origin.fetch from the '
340 'existing cache directory. You may need to manually edit '
341 '%s and "git cache fetch" again.'
342 % os.path.join(self.mirror_path, 'config'))
343
317 def _ensure_bootstrapped(self, depth, bootstrap, force=False): 344 def _ensure_bootstrapped(self, depth, bootstrap, force=False):
318 tempdir = None 345 tempdir = None
319 config_file = os.path.join(self.mirror_path, 'config')
320 pack_dir = os.path.join(self.mirror_path, 'objects', 'pack') 346 pack_dir = os.path.join(self.mirror_path, 'objects', 'pack')
321 pack_files = [] 347 pack_files = []
322 348
323 if os.path.isdir(pack_dir): 349 if os.path.isdir(pack_dir):
324 pack_files = [f for f in os.listdir(pack_dir) if f.endswith('.pack')] 350 pack_files = [f for f in os.listdir(pack_dir) if f.endswith('.pack')]
325 351
326 should_bootstrap = (force or 352 should_bootstrap = (force or
327 not os.path.exists(config_file) or 353 not self.exists() or
328 len(pack_files) > GC_AUTOPACKLIMIT) 354 len(pack_files) > GC_AUTOPACKLIMIT)
329 if should_bootstrap: 355 if should_bootstrap:
356 if self.exists():
357 # Re-bootstrapping an existing mirror; preserve existing fetch spec.
358 self._preserve_fetchspec()
330 tempdir = tempfile.mkdtemp( 359 tempdir = tempfile.mkdtemp(
331 prefix='_cache_tmp', suffix=self.basedir, dir=self.GetCachePath()) 360 prefix='_cache_tmp', suffix=self.basedir, dir=self.GetCachePath())
332 bootstrapped = not depth and bootstrap and self.bootstrap_repo(tempdir) 361 bootstrapped = not depth and bootstrap and self.bootstrap_repo(tempdir)
333 if bootstrapped: 362 if bootstrapped:
334 # Bootstrap succeeded; delete previous cache, if any. 363 # Bootstrap succeeded; delete previous cache, if any.
335 gclient_utils.rmtree(self.mirror_path) 364 gclient_utils.rmtree(self.mirror_path)
336 elif not os.path.exists(config_file): 365 elif not self.exists():
337 # Bootstrap failed, no previous cache; start with a bare git dir. 366 # Bootstrap failed, no previous cache; start with a bare git dir.
338 self.RunGit(['init', '--bare'], cwd=tempdir) 367 self.RunGit(['init', '--bare'], cwd=tempdir)
339 else: 368 else:
340 # Bootstrap failed, previous cache exists; warn and continue. 369 # Bootstrap failed, previous cache exists; warn and continue.
341 logging.warn( 370 logging.warn(
342 'Git cache has a lot of pack files (%d). Tried to re-bootstrap ' 371 'Git cache has a lot of pack files (%d). Tried to re-bootstrap '
343 'but failed. Continuing with non-optimized repository.' 372 'but failed. Continuing with non-optimized repository.'
344 % len(pack_files)) 373 % len(pack_files))
345 gclient_utils.rmtree(tempdir) 374 gclient_utils.rmtree(tempdir)
346 tempdir = None 375 tempdir = None
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after
556 } 585 }
557 if options.depth: 586 if options.depth:
558 kwargs['depth'] = options.depth 587 kwargs['depth'] = options.depth
559 mirror.populate(**kwargs) 588 mirror.populate(**kwargs)
560 589
561 590
562 @subcommand.usage('Fetch new commits into cache and current checkout') 591 @subcommand.usage('Fetch new commits into cache and current checkout')
563 def CMDfetch(parser, args): 592 def CMDfetch(parser, args):
564 """Update mirror, and fetch in cwd.""" 593 """Update mirror, and fetch in cwd."""
565 parser.add_option('--all', action='store_true', help='Fetch all remotes') 594 parser.add_option('--all', action='store_true', help='Fetch all remotes')
595 parser.add_option('--no_bootstrap', '--no-bootstrap',
596 action='store_true',
597 help='Don\'t (re)bootstrap from Google Storage')
566 options, args = parser.parse_args(args) 598 options, args = parser.parse_args(args)
567 599
568 # Figure out which remotes to fetch. This mimics the behavior of regular 600 # Figure out which remotes to fetch. This mimics the behavior of regular
569 # 'git fetch'. Note that in the case of "stacked" or "pipelined" branches, 601 # 'git fetch'. Note that in the case of "stacked" or "pipelined" branches,
570 # this will NOT try to traverse up the branching structure to find the 602 # this will NOT try to traverse up the branching structure to find the
571 # ultimate remote to update. 603 # ultimate remote to update.
572 remotes = [] 604 remotes = []
573 if options.all: 605 if options.all:
574 assert not args, 'fatal: fetch --all does not take a repository argument' 606 assert not args, 'fatal: fetch --all does not take a repository argument'
575 remotes = subprocess.check_output([Mirror.git_exe, 'remote']).splitlines() 607 remotes = subprocess.check_output([Mirror.git_exe, 'remote']).splitlines()
(...skipping 10 matching lines...) Expand all
586 remotes = [upstream] 618 remotes = [upstream]
587 if not remotes: 619 if not remotes:
588 remotes = ['origin'] 620 remotes = ['origin']
589 621
590 cachepath = Mirror.GetCachePath() 622 cachepath = Mirror.GetCachePath()
591 git_dir = os.path.abspath(subprocess.check_output( 623 git_dir = os.path.abspath(subprocess.check_output(
592 [Mirror.git_exe, 'rev-parse', '--git-dir'])) 624 [Mirror.git_exe, 'rev-parse', '--git-dir']))
593 git_dir = os.path.abspath(git_dir) 625 git_dir = os.path.abspath(git_dir)
594 if git_dir.startswith(cachepath): 626 if git_dir.startswith(cachepath):
595 mirror = Mirror.FromPath(git_dir) 627 mirror = Mirror.FromPath(git_dir)
596 mirror.populate() 628 mirror.populate(bootstrap=not options.no_bootstrap)
597 return 0 629 return 0
598 for remote in remotes: 630 for remote in remotes:
599 remote_url = subprocess.check_output( 631 remote_url = subprocess.check_output(
600 [Mirror.git_exe, 'config', 'remote.%s.url' % remote]).strip() 632 [Mirror.git_exe, 'config', 'remote.%s.url' % remote]).strip()
601 if remote_url.startswith(cachepath): 633 if remote_url.startswith(cachepath):
602 mirror = Mirror.FromPath(remote_url) 634 mirror = Mirror.FromPath(remote_url)
603 mirror.print = lambda *args: None 635 mirror.print = lambda *args: None
604 print('Updating git cache...') 636 print('Updating git cache...')
605 mirror.populate() 637 mirror.populate(bootstrap=not options.no_bootstrap)
606 subprocess.check_call([Mirror.git_exe, 'fetch', remote]) 638 subprocess.check_call([Mirror.git_exe, 'fetch', remote])
607 return 0 639 return 0
608 640
609 641
610 @subcommand.usage('[url of repo to unlock, or -a|--all]') 642 @subcommand.usage('[url of repo to unlock, or -a|--all]')
611 def CMDunlock(parser, args): 643 def CMDunlock(parser, args):
612 """Unlock one or all repos if their lock files are still around.""" 644 """Unlock one or all repos if their lock files are still around."""
613 parser.add_option('--force', '-f', action='store_true', 645 parser.add_option('--force', '-f', action='store_true',
614 help='Actually perform the action') 646 help='Actually perform the action')
615 parser.add_option('--all', '-a', action='store_true', 647 parser.add_option('--all', '-a', action='store_true',
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
678 dispatcher = subcommand.CommandDispatcher(__name__) 710 dispatcher = subcommand.CommandDispatcher(__name__)
679 return dispatcher.execute(OptionParser(), argv) 711 return dispatcher.execute(OptionParser(), argv)
680 712
681 713
682 if __name__ == '__main__': 714 if __name__ == '__main__':
683 try: 715 try:
684 sys.exit(main(sys.argv[1:])) 716 sys.exit(main(sys.argv[1:]))
685 except KeyboardInterrupt: 717 except KeyboardInterrupt:
686 sys.stderr.write('interrupted\n') 718 sys.stderr.write('interrupted\n')
687 sys.exit(1) 719 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | tests/git_cache_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698