44 ObjectDBR
55 )
66
7+ from gitdb .util import (
8+ to_bin_sha ,
9+ LazyMixin
10+ )
11+
712from gitdb .exc import (
13+ BadObject ,
814 UnsupportedOperation ,
915 )
1016
17+ from gitdb .pack import PackEntity
18+
19+ import os
20+ import glob
1121__all__ = ('PackedDB' , )
1222
13- class PackedDB (FileDBBase , ObjectDBR ):
23+
24+ #{ Utilities
25+
26+
27+ class PackedDB (FileDBBase , ObjectDBR , LazyMixin ):
1428 """A database operating on a set of object packs"""
1529
30+ # sort the priority list every N queries
31+ _sort_interval = 15
32+
1633 def __init__ (self , root_path ):
1734 super (PackedDB , self ).__init__ (root_path )
35+ # list of lists with three items:
36+ # * hits - number of times the pack was hit with a request
37+ # * entity - Pack entity instance
38+ # * sha_to_index - PackIndexFile.sha_to_index method for direct cache query
39+ # self._entities = list() # lazy loaded list
40+ self ._hit_count = 0 # amount of hits
41+ self ._st_mtime = 0 # last modification data of our root path
42+
43+ def _set_cache_ (self , attr ):
44+ # currently it can only be our _entities attribute
45+ self ._entities = list ()
46+ self .update_pack_entity_cache ()
47+
48+ def _sort_entities (self ):
49+ self ._entities .sort (key = lambda l : l [0 ], reverse = True )
50+
51+ def _pack_info (self , sha ):
52+ """:return: tuple(entity, index) for an item at the given sha
53+ :param sha: 20 or 40 byte sha
54+ :raise BadObject:
55+ :note: This method is not thread-safe, but may be hit in multi-threaded
56+ operation. The worst thing that can happen though is a counter that
57+ was not incremented, or the list being in wrong order. So we safe
58+ the time for locking here, lets see how that goes"""
59+ # presort ?
60+ if self ._hit_count % self ._sort_interval == 0 :
61+ self ._sort_entities ()
62+ # END update sorting
63+
64+ sha = to_bin_sha (sha )
65+ for item in self ._entities :
66+ index = item [2 ](sha )
67+ if index is not None :
68+ item [0 ] += 1 # one hit for you
69+ self ._hit_count += 1 # general hit count
70+ return (item [1 ], index )
71+ # END index found in pack
72+ # END for each item
1873
74+ # no hit, see whether we have to update packs
75+ # NOTE: considering packs don't change very often, we safe this call
76+ # and leave it to the super-caller to trigger that
77+ raise BadObject (sha )
1978
2079 #{ Object DB Read
2180
2281 def has_object (self , sha ):
23- raise NotImplementedError ()
82+ try :
83+ self ._pack_info (sha )
84+ return True
85+ except BadObject :
86+ return False
87+ # END exception handling
2488
2589 def info (self , sha ):
26- raise NotImplementedError ()
90+ entity , index = self ._pack_info (sha )
91+ return entity .info_at_index (index )
2792
2893 def stream (self , sha ):
29- raise NotImplementedError ()
94+ entity , index = self ._pack_info (sha )
95+ return entity .stream_at_index (index )
3096
3197 #} END object db read
3298
@@ -39,6 +105,55 @@ def store(self, istream):
39105 raise UnsupportedOperation ()
40106
41107 def store_async (self , reader ):
108+ # TODO: add ObjectDBRW before implementing this
42109 raise NotImplementedError ()
43110
44111 #} END object db write
112+
113+
114+ #{ Interface
115+
116+ def update_pack_entity_cache (self , force = False ):
117+ """Update our cache with the acutally existing packs on disk. Add new ones,
118+ and remove deleted ones. We keep the unchanged ones
119+ :param force: If True, the cache will be updated even though the directory
120+ does not appear to have changed according to its modification timestamp.
121+ :return: True if the packs have been updated so there is new information,
122+ False if there was no change to the pack database"""
123+ stat = os .stat (self .root_path ())
124+ if not force and stat .st_mtime <= self ._st_mtime :
125+ return False
126+ # END abort early on no change
127+ self ._st_mtime = stat .st_mtime
128+
129+ # packs are supposed to be prefixed with pack- by git-convention
130+ # get all pack files, figure out what changed
131+ pack_files = set (glob .glob (os .path .join (self .root_path (), "pack-*.pack" )))
132+ our_pack_files = set (item [1 ].pack ().path () for item in self ._entities )
133+
134+ # new packs
135+ for pack_file in (pack_files - our_pack_files ):
136+ # init the hit-counter/priority with the size, a good measure for hit-
137+ # probability. Its implemented so that only 12 bytes will be read
138+ entity = PackEntity (pack_file )
139+ self ._entities .append ([entity .pack ().size (), entity , entity .index ().sha_to_index ])
140+ # END for each new packfile
141+
142+ # removed packs
143+ for pack_file in (our_pack_files - pack_files ):
144+ del_index = - 1
145+ for i , item in enumerate (self ._entities ):
146+ if item [1 ].pack ().path () == pack_file :
147+ del_index = i
148+ break
149+ # END found index
150+ # END for each entity
151+ assert del_index != - 1
152+ del (self ._entities [del_index ])
153+ # END for each removed pack
154+
155+ # reinitialize prioritiess
156+ self ._sort_entities ()
157+ return True
158+
159+ #} END interface
0 commit comments