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 tempfile | 14 import tempfile |
| 14 import time | 15 import time |
| 15 import subprocess | 16 import subprocess |
| 16 import sys | 17 import sys |
| 17 import urlparse | 18 import urlparse |
| 18 import zipfile | 19 import zipfile |
| 19 | 20 |
| 20 from download_from_google_storage import Gsutil | 21 from download_from_google_storage import Gsutil |
| 21 import gclient_utils | 22 import gclient_utils |
| 22 import subcommand | 23 import subcommand |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 144 'third_party', 'gsutil', 'gsutil') | 145 'third_party', 'gsutil', 'gsutil') |
| 145 bootstrap_bucket = 'chromium-git-cache' | 146 bootstrap_bucket = 'chromium-git-cache' |
| 146 | 147 |
| 147 def __init__(self, url, refs=None, print_func=None): | 148 def __init__(self, url, refs=None, print_func=None): |
| 148 self.url = url | 149 self.url = url |
| 149 self.refs = refs or [] | 150 self.refs = refs or [] |
| 150 self.basedir = self.UrlToCacheDir(url) | 151 self.basedir = self.UrlToCacheDir(url) |
| 151 self.mirror_path = os.path.join(self.GetCachePath(), self.basedir) | 152 self.mirror_path = os.path.join(self.GetCachePath(), self.basedir) |
| 152 self.print = print_func or print | 153 self.print = print_func or print |
| 153 | 154 |
| 155 @classmethod | |
| 156 def FromPath(cls, path): | |
|
Ryan Tseng
2014/05/13 21:05:28
This looks like dead code, whats this for?
szager1
2014/05/13 21:12:38
It's actually new code, along with CacheDirToUrl()
| |
| 157 return cls(cls.CacheDirToUrl(path)) | |
| 158 | |
| 154 @staticmethod | 159 @staticmethod |
| 155 def UrlToCacheDir(url): | 160 def UrlToCacheDir(url): |
| 156 """Convert a git url to a normalized form for the cache dir path.""" | 161 """Convert a git url to a normalized form for the cache dir path.""" |
| 157 parsed = urlparse.urlparse(url) | 162 parsed = urlparse.urlparse(url) |
| 158 norm_url = parsed.netloc + parsed.path | 163 norm_url = parsed.netloc + parsed.path |
| 159 if norm_url.endswith('.git'): | 164 if norm_url.endswith('.git'): |
| 160 norm_url = norm_url[:-len('.git')] | 165 norm_url = norm_url[:-len('.git')] |
| 161 return norm_url.replace('-', '--').replace('/', '-').lower() | 166 return norm_url.replace('-', '--').replace('/', '-').lower() |
| 162 | 167 |
| 163 @staticmethod | 168 @staticmethod |
| 169 def CacheDirToUrl(path): | |
| 170 """Convert a cache dir path to its corresponding url.""" | |
| 171 netpath = re.sub(r'\b-\b', '/', os.path.basename(path)).replace('--', '-') | |
| 172 return 'https://%s' % netpath | |
| 173 | |
| 174 @staticmethod | |
| 164 def FindExecutable(executable): | 175 def FindExecutable(executable): |
| 165 """This mimics the "which" utility.""" | 176 """This mimics the "which" utility.""" |
| 166 path_folders = os.environ.get('PATH').split(os.pathsep) | 177 path_folders = os.environ.get('PATH').split(os.pathsep) |
| 167 | 178 |
| 168 for path_folder in path_folders: | 179 for path_folder in path_folders: |
| 169 target = os.path.join(path_folder, executable) | 180 target = os.path.join(path_folder, executable) |
| 170 # Just incase we have some ~/blah paths. | 181 # Just incase we have some ~/blah paths. |
| 171 target = os.path.abspath(os.path.expanduser(target)) | 182 target = os.path.abspath(os.path.expanduser(target)) |
| 172 if os.path.isfile(target) and os.access(target, os.X_OK): | 183 if os.path.isfile(target) and os.access(target, os.X_OK): |
| 173 return target | 184 return target |
| (...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 339 # Creating a temp file and then deleting it ensures we can use this name. | 350 # Creating a temp file and then deleting it ensures we can use this name. |
| 340 _, tmp_zipfile = tempfile.mkstemp(suffix='.zip') | 351 _, tmp_zipfile = tempfile.mkstemp(suffix='.zip') |
| 341 os.remove(tmp_zipfile) | 352 os.remove(tmp_zipfile) |
| 342 subprocess.call(['zip', '-r', tmp_zipfile, '.'], cwd=self.mirror_path) | 353 subprocess.call(['zip', '-r', tmp_zipfile, '.'], cwd=self.mirror_path) |
| 343 gsutil = Gsutil(path=self.gsutil_exe, boto_path=None) | 354 gsutil = Gsutil(path=self.gsutil_exe, boto_path=None) |
| 344 dest_name = 'gs://%s/%s/%s.zip' % ( | 355 dest_name = 'gs://%s/%s/%s.zip' % ( |
| 345 self.bootstrap_bucket, self.basedir, gen_number) | 356 self.bootstrap_bucket, self.basedir, gen_number) |
| 346 gsutil.call('cp', tmp_zipfile, dest_name) | 357 gsutil.call('cp', tmp_zipfile, dest_name) |
| 347 os.remove(tmp_zipfile) | 358 os.remove(tmp_zipfile) |
| 348 | 359 |
| 360 | |
| 361 @staticmethod | |
| 362 def BreakLocks(path): | |
| 363 did_unlock = False | |
| 364 lf = Lockfile(path) | |
| 365 if lf.break_lock(): | |
| 366 did_unlock = True | |
| 367 # Look for lock files that might have been left behind by an interrupted | |
| 368 # git process. | |
| 369 for git_lock_file in ('config',): | |
|
Ryan Tseng
2014/05/13 21:05:28
Also "index"
szager1
2014/05/13 21:12:38
That one is debatable, I think. A left-over index
Ryan Tseng
2014/05/13 21:17:45
Fair enough. I've see that a couple of times, but
| |
| 370 lf = os.path.join(path, '%s.lock' % git_lock_file) | |
| 371 if os.path.exists(lf): | |
| 372 os.remove(lf) | |
| 373 did_unlock = True | |
| 374 return did_unlock | |
| 375 | |
| 349 def unlock(self): | 376 def unlock(self): |
| 350 lf = Lockfile(self.mirror_path) | 377 return self.BreakLocks(self.mirror_path) |
| 351 config_lock = os.path.join(self.mirror_path, 'config.lock') | 378 |
| 352 if os.path.exists(config_lock): | 379 @classmethod |
| 353 os.remove(config_lock) | 380 def UnlockAll(cls): |
| 354 lf.break_lock() | 381 cachepath = cls.GetCachePath() |
| 382 dirlist = os.listdir(cachepath) | |
| 383 repo_dirs = set([os.path.join(cachepath, path) for path in dirlist | |
| 384 if os.path.isdir(os.path.join(cachepath, path))]) | |
| 385 for dirent in dirlist: | |
| 386 if (dirent.endswith('.lock') and | |
| 387 os.path.isfile(os.path.join(cachepath, dirent))): | |
| 388 repo_dirs.add(os.path.join(cachepath, dirent[:-5])) | |
| 389 | |
| 390 unlocked_repos = [] | |
| 391 for repo_dir in repo_dirs: | |
| 392 if cls.BreakLocks(repo_dir): | |
| 393 unlocked_repos.append(repo_dir) | |
| 394 | |
| 395 return unlocked_repos | |
| 355 | 396 |
| 356 @subcommand.usage('[url of repo to check for caching]') | 397 @subcommand.usage('[url of repo to check for caching]') |
| 357 def CMDexists(parser, args): | 398 def CMDexists(parser, args): |
| 358 """Check to see if there already is a cache of the given repo.""" | 399 """Check to see if there already is a cache of the given repo.""" |
| 359 _, args = parser.parse_args(args) | 400 _, args = parser.parse_args(args) |
| 360 if not len(args) == 1: | 401 if not len(args) == 1: |
| 361 parser.error('git cache exists only takes exactly one repo url.') | 402 parser.error('git cache exists only takes exactly one repo url.') |
| 362 url = args[0] | 403 url = args[0] |
| 363 mirror = Mirror(url) | 404 mirror = Mirror(url) |
| 364 if mirror.exists(): | 405 if mirror.exists(): |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 420 def CMDunlock(parser, args): | 461 def CMDunlock(parser, args): |
| 421 """Unlock one or all repos if their lock files are still around.""" | 462 """Unlock one or all repos if their lock files are still around.""" |
| 422 parser.add_option('--force', '-f', action='store_true', | 463 parser.add_option('--force', '-f', action='store_true', |
| 423 help='Actually perform the action') | 464 help='Actually perform the action') |
| 424 parser.add_option('--all', '-a', action='store_true', | 465 parser.add_option('--all', '-a', action='store_true', |
| 425 help='Unlock all repository caches') | 466 help='Unlock all repository caches') |
| 426 options, args = parser.parse_args(args) | 467 options, args = parser.parse_args(args) |
| 427 if len(args) > 1 or (len(args) == 0 and not options.all): | 468 if len(args) > 1 or (len(args) == 0 and not options.all): |
| 428 parser.error('git cache unlock takes exactly one repo url, or --all') | 469 parser.error('git cache unlock takes exactly one repo url, or --all') |
| 429 | 470 |
| 430 repo_dirs = [] | 471 if not options.force: |
| 431 if not options.all: | |
| 432 url = args[0] | |
| 433 repo_dirs.append(Mirror(url).mirror_path) | |
| 434 else: | |
| 435 cachepath = Mirror.GetCachePath() | 472 cachepath = Mirror.GetCachePath() |
| 436 repo_dirs = [os.path.join(cachepath, path) | 473 lockfiles = [os.path.join(cachepath, path) |
| 437 for path in os.listdir(cachepath) | 474 for path in os.listdir(cachepath) |
| 438 if os.path.isdir(os.path.join(cachepath, path))] | 475 if path.endswith('.lock') and os.path.isfile(path)] |
| 439 repo_dirs.extend([os.path.join(cachepath, | |
| 440 lockfile.replace('.lock', '')) | |
| 441 for lockfile in os.listdir(cachepath) | |
| 442 if os.path.isfile(os.path.join(cachepath, | |
| 443 lockfile)) | |
| 444 and lockfile.endswith('.lock') | |
| 445 and os.path.join(cachepath, lockfile) | |
| 446 not in repo_dirs]) | |
| 447 lockfiles = [repo_dir + '.lock' for repo_dir in repo_dirs | |
| 448 if os.path.exists(repo_dir + '.lock')] | |
| 449 | |
| 450 if not options.force: | |
| 451 parser.error('git cache unlock requires -f|--force to do anything. ' | 476 parser.error('git cache unlock requires -f|--force to do anything. ' |
| 452 'Refusing to unlock the following repo caches: ' | 477 'Refusing to unlock the following repo caches: ' |
| 453 ', '.join(lockfiles)) | 478 ', '.join(lockfiles)) |
| 454 | 479 |
| 455 unlocked_repos = [] | 480 unlocked_repos = [] |
| 456 untouched_repos = [] | 481 if options.all: |
| 457 for repo_dir in repo_dirs: | 482 unlocked_repos.extend(Mirror.UnlockAll()) |
| 458 lf = Lockfile(repo_dir) | 483 else: |
| 459 config_lock = os.path.join(repo_dir, 'config.lock') | 484 m = Mirror(args[0]) |
| 460 unlocked = False | 485 if m.unlock(): |
| 461 if os.path.exists(config_lock): | 486 unlocked_repos.append(m.mirror_path) |
| 462 os.remove(config_lock) | |
| 463 unlocked = True | |
| 464 if lf.break_lock(): | |
| 465 unlocked = True | |
| 466 | |
| 467 if unlocked: | |
| 468 unlocked_repos.append(repo_dir) | |
| 469 else: | |
| 470 untouched_repos.append(repo_dir) | |
| 471 | 487 |
| 472 if unlocked_repos: | 488 if unlocked_repos: |
| 473 logging.info('Broke locks on these caches:\n %s' % '\n '.join( | 489 logging.info('Broke locks on these caches:\n %s' % '\n '.join( |
| 474 unlocked_repos)) | 490 unlocked_repos)) |
| 475 if untouched_repos: | |
| 476 logging.debug('Did not touch these caches:\n %s' % '\n '.join( | |
| 477 untouched_repos)) | |
| 478 | 491 |
| 479 | 492 |
| 480 class OptionParser(optparse.OptionParser): | 493 class OptionParser(optparse.OptionParser): |
| 481 """Wrapper class for OptionParser to handle global options.""" | 494 """Wrapper class for OptionParser to handle global options.""" |
| 482 | 495 |
| 483 def __init__(self, *args, **kwargs): | 496 def __init__(self, *args, **kwargs): |
| 484 optparse.OptionParser.__init__(self, *args, prog='git cache', **kwargs) | 497 optparse.OptionParser.__init__(self, *args, prog='git cache', **kwargs) |
| 485 self.add_option('-c', '--cache-dir', | 498 self.add_option('-c', '--cache-dir', |
| 486 help='Path to the directory containing the cache') | 499 help='Path to the directory containing the cache') |
| 487 self.add_option('-v', '--verbose', action='count', default=0, | 500 self.add_option('-v', '--verbose', action='count', default=0, |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 507 return options, args | 520 return options, args |
| 508 | 521 |
| 509 | 522 |
| 510 def main(argv): | 523 def main(argv): |
| 511 dispatcher = subcommand.CommandDispatcher(__name__) | 524 dispatcher = subcommand.CommandDispatcher(__name__) |
| 512 return dispatcher.execute(OptionParser(), argv) | 525 return dispatcher.execute(OptionParser(), argv) |
| 513 | 526 |
| 514 | 527 |
| 515 if __name__ == '__main__': | 528 if __name__ == '__main__': |
| 516 sys.exit(main(sys.argv[1:])) | 529 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |