hgext/remotefilelog/basestore.py
changeset 43076 2372284d9457
parent 41501 13dad5cb4b99
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    16 )
    16 )
    17 from . import (
    17 from . import (
    18     constants,
    18     constants,
    19     shallowutil,
    19     shallowutil,
    20 )
    20 )
       
    21 
    21 
    22 
    22 class basestore(object):
    23 class basestore(object):
    23     def __init__(self, repo, path, reponame, shared=False):
    24     def __init__(self, repo, path, reponame, shared=False):
    24         """Creates a remotefilelog store object for the given repo name.
    25         """Creates a remotefilelog store object for the given repo name.
    25 
    26 
    35         self._path = path
    36         self._path = path
    36         self._reponame = reponame
    37         self._reponame = reponame
    37         self._shared = shared
    38         self._shared = shared
    38         self._uid = os.getuid() if not pycompat.iswindows else None
    39         self._uid = os.getuid() if not pycompat.iswindows else None
    39 
    40 
    40         self._validatecachelog = self.ui.config("remotefilelog",
    41         self._validatecachelog = self.ui.config(
    41                                                 "validatecachelog")
    42             "remotefilelog", "validatecachelog"
    42         self._validatecache = self.ui.config("remotefilelog", "validatecache",
    43         )
    43                                              'on')
    44         self._validatecache = self.ui.config(
       
    45             "remotefilelog", "validatecache", 'on'
       
    46         )
    44         if self._validatecache not in ('on', 'strict', 'off'):
    47         if self._validatecache not in ('on', 'strict', 'off'):
    45             self._validatecache = 'on'
    48             self._validatecache = 'on'
    46         if self._validatecache == 'off':
    49         if self._validatecache == 'off':
    47             self._validatecache = False
    50             self._validatecache = False
    48 
    51 
    52     def getmissing(self, keys):
    55     def getmissing(self, keys):
    53         missing = []
    56         missing = []
    54         for name, node in keys:
    57         for name, node in keys:
    55             filepath = self._getfilepath(name, node)
    58             filepath = self._getfilepath(name, node)
    56             exists = os.path.exists(filepath)
    59             exists = os.path.exists(filepath)
    57             if (exists and self._validatecache == 'strict' and
    60             if (
    58                 not self._validatekey(filepath, 'contains')):
    61                 exists
       
    62                 and self._validatecache == 'strict'
       
    63                 and not self._validatekey(filepath, 'contains')
       
    64             ):
    59                 exists = False
    65                 exists = False
    60             if not exists:
    66             if not exists:
    61                 missing.append((name, node))
    67                 missing.append((name, node))
    62 
    68 
    63         return missing
    69         return missing
    75 
    81 
    76     def cleanup(self, ledger):
    82     def cleanup(self, ledger):
    77         ui = self.ui
    83         ui = self.ui
    78         entries = ledger.sources.get(self, [])
    84         entries = ledger.sources.get(self, [])
    79         count = 0
    85         count = 0
    80         progress = ui.makeprogress(_("cleaning up"), unit="files",
    86         progress = ui.makeprogress(
    81                                    total=len(entries))
    87             _("cleaning up"), unit="files", total=len(entries)
       
    88         )
    82         for entry in entries:
    89         for entry in entries:
    83             if entry.gced or (entry.datarepacked and entry.historyrepacked):
    90             if entry.gced or (entry.datarepacked and entry.historyrepacked):
    84                 progress.update(count)
    91                 progress.update(count)
    85                 path = self._getfilepath(entry.filename, entry.node)
    92                 path = self._getfilepath(entry.filename, entry.node)
    86                 util.tryunlink(path)
    93                 util.tryunlink(path)
   176                     missingfilename.discard(sha)
   183                     missingfilename.discard(sha)
   177 
   184 
   178         return filenames
   185         return filenames
   179 
   186 
   180     def _getrepocachepath(self):
   187     def _getrepocachepath(self):
   181         return os.path.join(
   188         return (
   182             self._path, self._reponame) if self._shared else self._path
   189             os.path.join(self._path, self._reponame)
       
   190             if self._shared
       
   191             else self._path
       
   192         )
   183 
   193 
   184     def _listkeys(self):
   194     def _listkeys(self):
   185         """List all the remotefilelog keys that exist in the store.
   195         """List all the remotefilelog keys that exist in the store.
   186 
   196 
   187         Returns a iterator of (filename hash, filecontent hash) tuples.
   197         Returns a iterator of (filename hash, filecontent hash) tuples.
   217                     with open(self._validatecachelog, 'a+') as f:
   227                     with open(self._validatecachelog, 'a+') as f:
   218                         f.write("corrupt %s during read\n" % filepath)
   228                         f.write("corrupt %s during read\n" % filepath)
   219                 os.rename(filepath, filepath + ".corrupt")
   229                 os.rename(filepath, filepath + ".corrupt")
   220                 raise KeyError("corrupt local cache file %s" % filepath)
   230                 raise KeyError("corrupt local cache file %s" % filepath)
   221         except IOError:
   231         except IOError:
   222             raise KeyError("no file found at %s for %s:%s" % (filepath, name,
   232             raise KeyError(
   223                                                               hex(node)))
   233                 "no file found at %s for %s:%s" % (filepath, name, hex(node))
       
   234             )
   224 
   235 
   225         return data
   236         return data
   226 
   237 
   227     def addremotefilelognode(self, name, node, data):
   238     def addremotefilelognode(self, name, node, data):
   228         filepath = self._getfilepath(name, node)
   239         filepath = self._getfilepath(name, node)
   242             shallowutil.mkstickygroupdir(self.ui, os.path.dirname(filepath))
   253             shallowutil.mkstickygroupdir(self.ui, os.path.dirname(filepath))
   243             shallowutil.writefile(filepath, data, readonly=True)
   254             shallowutil.writefile(filepath, data, readonly=True)
   244 
   255 
   245             if self._validatecache:
   256             if self._validatecache:
   246                 if not self._validatekey(filepath, 'write'):
   257                 if not self._validatekey(filepath, 'write'):
   247                     raise error.Abort(_("local cache write was corrupted %s") %
   258                     raise error.Abort(
   248                                       filepath)
   259                         _("local cache write was corrupted %s") % filepath
       
   260                     )
   249         finally:
   261         finally:
   250             os.umask(oldumask)
   262             os.umask(oldumask)
   251 
   263 
   252     def markrepo(self, path):
   264     def markrepo(self, path):
   253         """Call this to add the given repo path to the store's list of
   265         """Call this to add the given repo path to the store's list of
   286                     # it is truncated
   298                     # it is truncated
   287                     return False
   299                     return False
   288 
   300 
   289                 # extract the node from the metadata
   301                 # extract the node from the metadata
   290                 offset += size
   302                 offset += size
   291                 datanode = data[offset:offset + 20]
   303                 datanode = data[offset : offset + 20]
   292 
   304 
   293                 # and compare against the path
   305                 # and compare against the path
   294                 if os.path.basename(path) == hex(datanode):
   306                 if os.path.basename(path) == hex(datanode):
   295                     # Content matches the intended path
   307                     # Content matches the intended path
   296                     return True
   308                     return True
   312         removed = 0
   324         removed = 0
   313 
   325 
   314         # keep files newer than a day even if they aren't needed
   326         # keep files newer than a day even if they aren't needed
   315         limit = time.time() - (60 * 60 * 24)
   327         limit = time.time() - (60 * 60 * 24)
   316 
   328 
   317         progress = ui.makeprogress(_("removing unnecessary files"),
   329         progress = ui.makeprogress(
   318                                    unit="files")
   330             _("removing unnecessary files"), unit="files"
       
   331         )
   319         progress.update(0)
   332         progress.update(0)
   320         for root, dirs, files in os.walk(cachepath):
   333         for root, dirs, files in os.walk(cachepath):
   321             for file in files:
   334             for file in files:
   322                 if file == 'repos':
   335                 if file == 'repos':
   323                     continue
   336                     continue
   350                         shallowutil.unlinkfile(path)
   363                         shallowutil.unlinkfile(path)
   351                     except OSError as e:
   364                     except OSError as e:
   352                         # errno.ENOENT = no such file or directory
   365                         # errno.ENOENT = no such file or directory
   353                         if e.errno != errno.ENOENT:
   366                         if e.errno != errno.ENOENT:
   354                             raise
   367                             raise
   355                         msg = _("warning: file %s was removed by another "
   368                         msg = _(
   356                                 "process\n")
   369                             "warning: file %s was removed by another "
       
   370                             "process\n"
       
   371                         )
   357                         ui.warn(msg % path)
   372                         ui.warn(msg % path)
   358                         continue
   373                         continue
   359                     removed += 1
   374                     removed += 1
   360         progress.complete()
   375         progress.complete()
   361 
   376 
   362         # remove oldest files until under limit
   377         # remove oldest files until under limit
   363         limit = ui.configbytes("remotefilelog", "cachelimit")
   378         limit = ui.configbytes("remotefilelog", "cachelimit")
   364         if size > limit:
   379         if size > limit:
   365             excess = size - limit
   380             excess = size - limit
   366             progress = ui.makeprogress(_("enforcing cache limit"), unit="bytes",
   381             progress = ui.makeprogress(
   367                                        total=excess)
   382                 _("enforcing cache limit"), unit="bytes", total=excess
       
   383             )
   368             removedexcess = 0
   384             removedexcess = 0
   369             while queue and size > limit and size > 0:
   385             while queue and size > limit and size > 0:
   370                 progress.update(removedexcess)
   386                 progress.update(removedexcess)
   371                 atime, oldpath, oldpathstat = queue.get()
   387                 atime, oldpath, oldpathstat = queue.get()
   372                 try:
   388                 try:
   380                 size -= oldpathstat.st_size
   396                 size -= oldpathstat.st_size
   381                 removed += 1
   397                 removed += 1
   382                 removedexcess += oldpathstat.st_size
   398                 removedexcess += oldpathstat.st_size
   383             progress.complete()
   399             progress.complete()
   384 
   400 
   385         ui.status(_("finished: removed %d of %d files (%0.2f GB to %0.2f GB)\n")
   401         ui.status(
   386                   % (removed, count,
   402             _("finished: removed %d of %d files (%0.2f GB to %0.2f GB)\n")
   387                      float(originalsize) / 1024.0 / 1024.0 / 1024.0,
   403             % (
   388                      float(size) / 1024.0 / 1024.0 / 1024.0))
   404                 removed,
       
   405                 count,
       
   406                 float(originalsize) / 1024.0 / 1024.0 / 1024.0,
       
   407                 float(size) / 1024.0 / 1024.0 / 1024.0,
       
   408             )
       
   409         )
       
   410 
   389 
   411 
   390 class baseunionstore(object):
   412 class baseunionstore(object):
   391     def __init__(self, *args, **kwargs):
   413     def __init__(self, *args, **kwargs):
   392         # If one of the functions that iterates all of the stores is about to
   414         # If one of the functions that iterates all of the stores is about to
   393         # throw a KeyError, try this many times with a full refresh between
   415         # throw a KeyError, try this many times with a full refresh between
   405 
   427 
   406     @staticmethod
   428     @staticmethod
   407     def retriable(fn):
   429     def retriable(fn):
   408         def noop(*args):
   430         def noop(*args):
   409             pass
   431             pass
       
   432 
   410         def wrapped(self, *args, **kwargs):
   433         def wrapped(self, *args, **kwargs):
   411             retrylog = self.retrylog or noop
   434             retrylog = self.retrylog or noop
   412             funcname = fn.__name__
   435             funcname = fn.__name__
   413             i = 0
   436             i = 0
   414             while i < self.numattempts:
   437             while i < self.numattempts:
   419                 try:
   442                 try:
   420                     return fn(self, *args, **kwargs)
   443                     return fn(self, *args, **kwargs)
   421                 except KeyError:
   444                 except KeyError:
   422                     if i == self.numattempts:
   445                     if i == self.numattempts:
   423                         # retries exhausted
   446                         # retries exhausted
   424                         retrylog('retries exhausted in %s, raising KeyError\n' %
   447                         retrylog(
   425                                  pycompat.sysbytes(funcname))
   448                             'retries exhausted in %s, raising KeyError\n'
       
   449                             % pycompat.sysbytes(funcname)
       
   450                         )
   426                         raise
   451                         raise
       
   452 
   427         return wrapped
   453         return wrapped