Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 |
| 11 import optparse | 11 import optparse |
| 12 import os | 12 import os |
| 13 import re | 13 import re |
| 14 import tempfile | 14 import tempfile |
| 15 import time | 15 import time |
| 16 import subprocess | 16 import subprocess |
| 17 import sys | 17 import sys |
| 18 import urlparse | 18 import urlparse |
| 19 import zipfile | 19 import zipfile |
| 20 | 20 |
| 21 from download_from_google_storage import Gsutil | 21 from download_from_google_storage import Gsutil |
| 22 import gclient_utils | 22 import gclient_utils |
| 23 import subcommand | 23 import subcommand |
| 24 | 24 |
| 25 # Analogous to gc.autopacklimit git config. | 25 # Analogous to gc.autopacklimit git config. |
| 26 GC_AUTOPACKLIMIT = 50 | 26 GC_AUTOPACKLIMIT = 50 |
| 27 | 27 |
| 28 GIT_CACHE_CORRUPT_MESSAGE = 'The Git cache is corrupt!! Re-bootstrapping now.' | |
|
Ryan Tseng
2014/06/26 17:35:03
I was planning on tracking the occurrence of this
szager1
2014/06/26 20:25:36
That sounds good, but how about getting rid of the
Ryan Tseng
2014/06/26 21:37:58
So the point of having this as a macro up to is so
szager1
2014/06/26 21:47:31
That's fine, but it would be nice to be able to:
| |
| 29 | |
| 28 try: | 30 try: |
| 29 # pylint: disable=E0602 | 31 # pylint: disable=E0602 |
| 30 WinErr = WindowsError | 32 WinErr = WindowsError |
| 31 except NameError: | 33 except NameError: |
| 32 class WinErr(Exception): | 34 class WinErr(Exception): |
| 33 pass | 35 pass |
| 34 | 36 |
| 35 class LockError(Exception): | 37 class LockError(Exception): |
| 36 pass | 38 pass |
| 37 | 39 |
| 40 class RefsHeadsFailedToFetch(Exception): | |
| 41 pass | |
| 38 | 42 |
| 39 class Lockfile(object): | 43 class Lockfile(object): |
| 40 """Class to represent a cross-platform process-specific lockfile.""" | 44 """Class to represent a cross-platform process-specific lockfile.""" |
| 41 | 45 |
| 42 def __init__(self, path): | 46 def __init__(self, path): |
| 43 self.path = os.path.abspath(path) | 47 self.path = os.path.abspath(path) |
| 44 self.lockfile = self.path + ".lock" | 48 self.lockfile = self.path + ".lock" |
| 45 self.pid = os.getpid() | 49 self.pid = os.getpid() |
| 46 | 50 |
| 47 def _read_pid(self): | 51 def _read_pid(self): |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 241 '+refs/heads/*:refs/heads/*'], cwd=cwd) | 245 '+refs/heads/*:refs/heads/*'], cwd=cwd) |
| 242 for ref in self.refs: | 246 for ref in self.refs: |
| 243 ref = ref.lstrip('+').rstrip('/') | 247 ref = ref.lstrip('+').rstrip('/') |
| 244 if ref.startswith('refs/'): | 248 if ref.startswith('refs/'): |
| 245 refspec = '+%s:%s' % (ref, ref) | 249 refspec = '+%s:%s' % (ref, ref) |
| 246 else: | 250 else: |
| 247 refspec = '+refs/%s/*:refs/%s/*' % (ref, ref) | 251 refspec = '+refs/%s/*:refs/%s/*' % (ref, ref) |
| 248 self.RunGit(['config', '--add', 'remote.origin.fetch', refspec], cwd=cwd) | 252 self.RunGit(['config', '--add', 'remote.origin.fetch', refspec], cwd=cwd) |
| 249 | 253 |
| 250 def bootstrap_repo(self, directory): | 254 def bootstrap_repo(self, directory): |
| 251 """Bootstrap the repo from Google Stroage if possible.""" | 255 """Bootstrap the repo from Google Stroage if possible. |
| 256 | |
| 257 More apt-ly named bootstrap_repo_from_cloud_if_possible_else_do_nothing(). | |
| 258 """ | |
| 252 | 259 |
| 253 python_fallback = False | 260 python_fallback = False |
| 254 if sys.platform.startswith('win') and not self.FindExecutable('7z'): | 261 if sys.platform.startswith('win') and not self.FindExecutable('7z'): |
| 255 python_fallback = True | 262 python_fallback = True |
| 256 elif sys.platform.startswith('darwin'): | 263 elif sys.platform.startswith('darwin'): |
| 257 # The OSX version of unzip doesn't support zip64. | 264 # The OSX version of unzip doesn't support zip64. |
| 258 python_fallback = True | 265 python_fallback = True |
| 259 elif not self.FindExecutable('unzip'): | 266 elif not self.FindExecutable('unzip'): |
| 260 python_fallback = True | 267 python_fallback = True |
| 261 | 268 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 302 if retcode: | 309 if retcode: |
| 303 self.print( | 310 self.print( |
| 304 'Extracting bootstrap zipfile %s failed.\n' | 311 'Extracting bootstrap zipfile %s failed.\n' |
| 305 'Resuming normal operations.' % filename) | 312 'Resuming normal operations.' % filename) |
| 306 return False | 313 return False |
| 307 return True | 314 return True |
| 308 | 315 |
| 309 def exists(self): | 316 def exists(self): |
| 310 return os.path.isfile(os.path.join(self.mirror_path, 'config')) | 317 return os.path.isfile(os.path.join(self.mirror_path, 'config')) |
| 311 | 318 |
| 319 def _ensure_bootstrapped(self, depth, bootstrap, force=False): | |
| 320 tempdir = None | |
| 321 config_file = os.path.join(self.mirror_path, 'config') | |
| 322 pack_dir = os.path.join(self.mirror_path, 'objects', 'pack') | |
| 323 pack_files = [] | |
| 324 | |
| 325 if os.path.isdir(pack_dir): | |
| 326 pack_files = [f for f in os.listdir(pack_dir) if f.endswith('.pack')] | |
| 327 | |
| 328 should_bootstrap = (force or | |
| 329 not os.path.exists(config_file) or | |
| 330 len(pack_files) > GC_AUTOPACKLIMIT) | |
| 331 if should_bootstrap: | |
| 332 tempdir = tempfile.mkdtemp( | |
| 333 prefix='_cache_tmp', suffix=self.basedir, dir=self.GetCachePath()) | |
| 334 bootstrapped = not depth and bootstrap and self.bootstrap_repo(tempdir) | |
| 335 if bootstrapped: | |
| 336 # Bootstrap succeeded; delete previous cache, if any. | |
| 337 gclient_utils.rmtree(self.mirror_path) | |
| 338 elif not os.path.exists(config_file): | |
| 339 # Bootstrap failed, no previous cache; start with a bare git dir. | |
| 340 self.RunGit(['init', '--bare'], cwd=tempdir) | |
| 341 else: | |
| 342 # Bootstrap failed, previous cache exists; warn and continue. | |
| 343 logging.warn( | |
| 344 'Git cache has a lot of pack files (%d). Tried to re-bootstrap ' | |
| 345 'but failed. Continuing with non-optimized repository.' | |
| 346 % len(pack_files)) | |
| 347 gclient_utils.rmtree(tempdir) | |
| 348 tempdir = None | |
| 349 else: | |
| 350 if depth and os.path.exists(os.path.join(self.mirror_path, 'shallow')): | |
| 351 logging.warn( | |
| 352 'Shallow fetch requested, but repo cache already exists.') | |
| 353 return tempdir | |
| 354 | |
| 355 def _fetch(self, rundir, verbose, depth): | |
| 356 self.config(rundir) | |
| 357 v = [] | |
| 358 d = [] | |
| 359 if verbose: | |
| 360 v = ['-v', '--progress'] | |
| 361 if depth: | |
| 362 d = ['--depth', str(depth)] | |
| 363 fetch_cmd = ['fetch'] + v + d + ['origin'] | |
| 364 fetch_specs = subprocess.check_output( | |
| 365 [self.git_exe, 'config', '--get-all', 'remote.origin.fetch'], | |
| 366 cwd=rundir).strip().splitlines() | |
| 367 for spec in fetch_specs: | |
| 368 try: | |
| 369 self.print('Fetching %s' % spec) | |
| 370 self.RunGit(fetch_cmd + [spec], cwd=rundir, retry=True) | |
| 371 except subprocess.CalledProcessError: | |
| 372 if spec == '+refs/heads/*:refs/heads/*': | |
| 373 raise RefsHeadsFailedToFetch | |
| 374 logging.warn('Fetch of %s failed' % spec) | |
| 375 | |
| 312 def populate(self, depth=None, shallow=False, bootstrap=False, | 376 def populate(self, depth=None, shallow=False, bootstrap=False, |
| 313 verbose=False, ignore_lock=False): | 377 verbose=False, ignore_lock=False): |
| 314 assert self.GetCachePath() | 378 assert self.GetCachePath() |
| 315 if shallow and not depth: | 379 if shallow and not depth: |
| 316 depth = 10000 | 380 depth = 10000 |
| 317 gclient_utils.safe_makedirs(self.GetCachePath()) | 381 gclient_utils.safe_makedirs(self.GetCachePath()) |
| 318 | 382 |
| 319 v = [] | |
| 320 if verbose: | |
| 321 v = ['-v', '--progress'] | |
| 322 | |
| 323 d = [] | |
| 324 if depth: | |
| 325 d = ['--depth', str(depth)] | |
| 326 | |
| 327 | |
| 328 lockfile = Lockfile(self.mirror_path) | 383 lockfile = Lockfile(self.mirror_path) |
| 329 if not ignore_lock: | 384 if not ignore_lock: |
| 330 lockfile.lock() | 385 lockfile.lock() |
| 331 | 386 |
| 332 try: | 387 try: |
| 333 # Setup from scratch if the repo is new or is in a bad state. | 388 tempdir = self._ensure_bootstrapped(depth, bootstrap) |
| 334 tempdir = None | 389 try: |
|
szager1
2014/06/26 20:25:36
I don't think you really need nested 'try' here:
t
Ryan Tseng
2014/06/26 21:37:58
Done.
| |
| 335 config_file = os.path.join(self.mirror_path, 'config') | 390 self._fetch(tempdir or self.mirror_path, verbose, depth) |
| 336 pack_dir = os.path.join(self.mirror_path, 'objects', 'pack') | 391 except RefsHeadsFailedToFetch: |
| 337 pack_files = [] | 392 # This is a major failure, we need to clean and force a bootstrap. |
| 338 if os.path.isdir(pack_dir): | 393 self.print(GIT_CACHE_CORRUPT_MESSAGE) |
| 339 pack_files = [f for f in os.listdir(pack_dir) if f.endswith('.pack')] | 394 tempdir = self._ensure_bootstrapped(depth, bootstrap, force=True) |
| 340 | 395 self._fetch(tempdir or self.mirror_path, verbose, depth) |
| 341 should_bootstrap = (not os.path.exists(config_file) or | |
| 342 len(pack_files) > GC_AUTOPACKLIMIT) | |
| 343 if should_bootstrap: | |
| 344 tempdir = tempfile.mkdtemp( | |
| 345 prefix='_cache_tmp', suffix=self.basedir, dir=self.GetCachePath()) | |
| 346 bootstrapped = not depth and bootstrap and self.bootstrap_repo(tempdir) | |
| 347 if bootstrapped: | |
| 348 # Bootstrap succeeded; delete previous cache, if any. | |
| 349 gclient_utils.rmtree(self.mirror_path) | |
| 350 elif not os.path.exists(config_file): | |
| 351 # Bootstrap failed, no previous cache; start with a bare git dir. | |
| 352 self.RunGit(['init', '--bare'], cwd=tempdir) | |
| 353 else: | |
| 354 # Bootstrap failed, previous cache exists; warn and continue. | |
| 355 logging.warn( | |
| 356 'Git cache has a lot of pack files (%d). Tried to re-bootstrap ' | |
| 357 'but failed. Continuing with non-optimized repository.' | |
| 358 % len(pack_files)) | |
| 359 gclient_utils.rmtree(tempdir) | |
| 360 tempdir = None | |
| 361 else: | |
| 362 if depth and os.path.exists(os.path.join(self.mirror_path, 'shallow')): | |
| 363 logging.warn( | |
| 364 'Shallow fetch requested, but repo cache already exists.') | |
| 365 d = [] | |
| 366 | |
| 367 rundir = tempdir or self.mirror_path | |
| 368 self.config(rundir) | |
| 369 fetch_cmd = ['fetch'] + v + d + ['origin'] | |
| 370 fetch_specs = subprocess.check_output( | |
| 371 [self.git_exe, 'config', '--get-all', 'remote.origin.fetch'], | |
| 372 cwd=rundir).strip().splitlines() | |
| 373 for spec in fetch_specs: | |
| 374 try: | |
| 375 self.RunGit(fetch_cmd + [spec], cwd=rundir, retry=True) | |
| 376 except subprocess.CalledProcessError: | |
| 377 logging.warn('Fetch of %s failed' % spec) | |
| 378 if tempdir: | 396 if tempdir: |
| 379 os.rename(tempdir, self.mirror_path) | 397 os.rename(tempdir, self.mirror_path) |
| 380 finally: | 398 finally: |
| 381 if not ignore_lock: | 399 if not ignore_lock: |
| 382 lockfile.unlock() | 400 lockfile.unlock() |
| 383 | 401 |
| 384 def update_bootstrap(self, prune=False): | 402 def update_bootstrap(self, prune=False): |
| 385 # The files are named <git number>.zip | 403 # The files are named <git number>.zip |
| 386 gen_number = subprocess.check_output( | 404 gen_number = subprocess.check_output( |
| 387 [self.git_exe, 'number', 'master'], cwd=self.mirror_path).strip() | 405 [self.git_exe, 'number', 'master'], cwd=self.mirror_path).strip() |
| (...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 595 return options, args | 613 return options, args |
| 596 | 614 |
| 597 | 615 |
| 598 def main(argv): | 616 def main(argv): |
| 599 dispatcher = subcommand.CommandDispatcher(__name__) | 617 dispatcher = subcommand.CommandDispatcher(__name__) |
| 600 return dispatcher.execute(OptionParser(), argv) | 618 return dispatcher.execute(OptionParser(), argv) |
| 601 | 619 |
| 602 | 620 |
| 603 if __name__ == '__main__': | 621 if __name__ == '__main__': |
| 604 sys.exit(main(sys.argv[1:])) | 622 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |