| Index: git_cache.py | 
| diff --git a/git_cache.py b/git_cache.py | 
| index 52e42c59274781f580491428b67afa4d6bf66280..4b9c1d8945c626a36af96ef9a3e62aad90d1d929 100755 | 
| --- a/git_cache.py | 
| +++ b/git_cache.py | 
| @@ -10,6 +10,7 @@ import errno | 
| import logging | 
| import optparse | 
| import os | 
| +import re | 
| import tempfile | 
| import time | 
| import subprocess | 
| @@ -151,6 +152,10 @@ class Mirror(object): | 
| self.mirror_path = os.path.join(self.GetCachePath(), self.basedir) | 
| self.print = print_func or print | 
|  | 
| +  @classmethod | 
| +  def FromPath(cls, path): | 
| +    return cls(cls.CacheDirToUrl(path)) | 
| + | 
| @staticmethod | 
| def UrlToCacheDir(url): | 
| """Convert a git url to a normalized form for the cache dir path.""" | 
| @@ -161,6 +166,12 @@ class Mirror(object): | 
| return norm_url.replace('-', '--').replace('/', '-').lower() | 
|  | 
| @staticmethod | 
| +  def CacheDirToUrl(path): | 
| +    """Convert a cache dir path to its corresponding url.""" | 
| +    netpath = re.sub(r'\b-\b', '/', os.path.basename(path)).replace('--', '-') | 
| +    return 'https://%s' % netpath | 
| + | 
| +  @staticmethod | 
| def FindExecutable(executable): | 
| """This mimics the "which" utility.""" | 
| path_folders = os.environ.get('PATH').split(os.pathsep) | 
| @@ -346,12 +357,41 @@ class Mirror(object): | 
| gsutil.call('cp', tmp_zipfile, dest_name) | 
| os.remove(tmp_zipfile) | 
|  | 
| + | 
| +  @staticmethod | 
| +  def BreakLocks(path): | 
| +    did_unlock = False | 
| +    lf = Lockfile(path) | 
| +    if lf.break_lock(): | 
| +      did_unlock = True | 
| +    # Look for lock files that might have been left behind by an interrupted | 
| +    # git process. | 
| +    lf = os.path.join(path, 'config.lock') | 
| +    if os.path.exists(lf): | 
| +      os.remove(lf) | 
| +      did_unlock = True | 
| +    return did_unlock | 
| + | 
| def unlock(self): | 
| -    lf = Lockfile(self.mirror_path) | 
| -    config_lock = os.path.join(self.mirror_path, 'config.lock') | 
| -    if os.path.exists(config_lock): | 
| -      os.remove(config_lock) | 
| -    lf.break_lock() | 
| +    return self.BreakLocks(self.mirror_path) | 
| + | 
| +  @classmethod | 
| +  def UnlockAll(cls): | 
| +    cachepath = cls.GetCachePath() | 
| +    dirlist = os.listdir(cachepath) | 
| +    repo_dirs = set([os.path.join(cachepath, path) for path in dirlist | 
| +                     if os.path.isdir(os.path.join(cachepath, path))]) | 
| +    for dirent in dirlist: | 
| +      if (dirent.endswith('.lock') and | 
| +          os.path.isfile(os.path.join(cachepath, dirent))): | 
| +        repo_dirs.add(os.path.join(cachepath, dirent[:-5])) | 
| + | 
| +    unlocked_repos = [] | 
| +    for repo_dir in repo_dirs: | 
| +      if cls.BreakLocks(repo_dir): | 
| +        unlocked_repos.append(repo_dir) | 
| + | 
| +    return unlocked_repos | 
|  | 
| @subcommand.usage('[url of repo to check for caching]') | 
| def CMDexists(parser, args): | 
| @@ -427,54 +467,26 @@ def CMDunlock(parser, args): | 
| if len(args) > 1 or (len(args) == 0 and not options.all): | 
| parser.error('git cache unlock takes exactly one repo url, or --all') | 
|  | 
| -  repo_dirs = [] | 
| -  if not options.all: | 
| -    url = args[0] | 
| -    repo_dirs.append(Mirror(url).mirror_path) | 
| -  else: | 
| +  if not options.force: | 
| cachepath = Mirror.GetCachePath() | 
| -    repo_dirs = [os.path.join(cachepath, path) | 
| +    lockfiles = [os.path.join(cachepath, path) | 
| for path in os.listdir(cachepath) | 
| -                 if os.path.isdir(os.path.join(cachepath, path))] | 
| -    repo_dirs.extend([os.path.join(cachepath, | 
| -                                   lockfile.replace('.lock', '')) | 
| -                      for lockfile in os.listdir(cachepath) | 
| -                      if os.path.isfile(os.path.join(cachepath, | 
| -                                                     lockfile)) | 
| -                      and lockfile.endswith('.lock') | 
| -                      and os.path.join(cachepath, lockfile) | 
| -                          not in repo_dirs]) | 
| -  lockfiles = [repo_dir + '.lock' for repo_dir in repo_dirs | 
| -               if os.path.exists(repo_dir + '.lock')] | 
| - | 
| -  if not options.force: | 
| +                 if path.endswith('.lock') and os.path.isfile(path)] | 
| parser.error('git cache unlock requires -f|--force to do anything. ' | 
| 'Refusing to unlock the following repo caches: ' | 
| ', '.join(lockfiles)) | 
|  | 
| unlocked_repos = [] | 
| -  untouched_repos = [] | 
| -  for repo_dir in repo_dirs: | 
| -    lf = Lockfile(repo_dir) | 
| -    config_lock = os.path.join(repo_dir, 'config.lock') | 
| -    unlocked = False | 
| -    if os.path.exists(config_lock): | 
| -      os.remove(config_lock) | 
| -      unlocked = True | 
| -    if lf.break_lock(): | 
| -      unlocked = True | 
| - | 
| -    if unlocked: | 
| -      unlocked_repos.append(repo_dir) | 
| -    else: | 
| -      untouched_repos.append(repo_dir) | 
| +  if options.all: | 
| +    unlocked_repos.extend(Mirror.UnlockAll()) | 
| +  else: | 
| +    m = Mirror(args[0]) | 
| +    if m.unlock(): | 
| +      unlocked_repos.append(m.mirror_path) | 
|  | 
| if unlocked_repos: | 
| logging.info('Broke locks on these caches:\n  %s' % '\n  '.join( | 
| unlocked_repos)) | 
| -  if untouched_repos: | 
| -    logging.debug('Did not touch these caches:\n %s' % '\n  '.join( | 
| -        untouched_repos)) | 
|  | 
|  | 
| class OptionParser(optparse.OptionParser): | 
|  |