13
13
from klaus .diff import render_diff
14
14
15
15
16
+ NOT_SET = '__not_set__'
17
+
18
+
19
+ def cached_call (key , validator , producer , _cache = {}):
20
+ data , old_validator = _cache .get (key , (None , NOT_SET ))
21
+ if old_validator != validator :
22
+ data = producer ()
23
+ _cache [key ] = (data , validator )
24
+ return data
25
+
26
+
16
27
class FancyRepo (dulwich .repo .Repo ):
17
28
"""A wrapper around Dulwich's Repo that adds some helper methods."""
18
29
# TODO: factor out stuff into dulwich
@@ -31,30 +42,33 @@ def name(self):
31
42
32
43
def get_last_updated_at (self ):
33
44
"""Get datetime of last commit to this repository."""
34
- def _get_last_updated_at ():
35
- refs = []
36
- for ref_hash in self .get_refs ().values ():
37
- try :
38
- refs .append (self [ref_hash ])
39
- except KeyError :
40
- # Whoops. The ref points at a non-existant object
41
- pass
42
- refs .sort (key = lambda obj :getattr (obj , 'commit_time' , float ('-inf' )),
43
- reverse = True )
44
- for ref in refs :
45
- # Find the latest ref that has a commit_time; tags do not
46
- # have a commit time
47
- if hasattr (ref , "commit_time" ):
48
- return ref .commit_time
49
- return None
50
-
51
45
# Cache result to speed up repo_list.html template.
52
- # If self.refs.keys() as changed, we should invalidate the cache.
53
- cache_key = self .refs .keys ()
54
- if cache_key != getattr (self , '_last_updated_at_cache_key' , None ):
55
- self ._last_updated_at_cache_retval = _get_last_updated_at ()
56
- self ._last_updated_at_cache_key = cache_key
57
- return self ._last_updated_at_cache_retval
46
+ # If self.get_refs() has changed, we should invalidate the cache.
47
+ all_refs = self .get_refs ()
48
+ return cached_call (
49
+ key = (id (self ), 'get_last_updated_at' ),
50
+ validator = all_refs ,
51
+ producer = lambda : self ._get_last_updated_at (all_refs )
52
+ )
53
+
54
+ def _get_last_updated_at (self , all_refs ):
55
+ resolveable_refs = []
56
+ for ref_hash in all_refs :
57
+ try :
58
+ resolveable_refs .append (self [ref_hash ])
59
+ except KeyError :
60
+ # Whoops. The ref points at a non-existant object
61
+ pass
62
+ resolveable_refs .sort (
63
+ key = lambda obj :getattr (obj , 'commit_time' , float ('-inf' )),
64
+ reverse = True
65
+ )
66
+ for ref in resolveable_refs :
67
+ # Find the latest ref that has a commit_time; tags do not
68
+ # have a commit time
69
+ if hasattr (ref , "commit_time" ):
70
+ return ref .commit_time
71
+ return None
58
72
59
73
@property
60
74
def cloneurl (self ):
@@ -72,6 +86,21 @@ def get_description(self):
72
86
"""Like Dulwich's `get_description`, but returns None if the file
73
87
contains Git's default text "Unnamed repository[...]".
74
88
"""
89
+ # Cache result to speed up repo_list.html template.
90
+ # If description file mtime has changed, we should invalidate the cache.
91
+ description_file = os .path .join (self ._controldir , 'description' )
92
+ try :
93
+ description_mtime = os .stat (os .path .join (self ._controldir , 'description' )).st_mtime
94
+ except OSError :
95
+ description_mtime = None
96
+
97
+ return cached_call (
98
+ key = (id (self ), 'get_description' ),
99
+ validator = description_mtime ,
100
+ producer = self ._get_description
101
+ )
102
+
103
+ def _get_description (self ):
75
104
description = super (FancyRepo , self ).get_description ()
76
105
if description :
77
106
description = force_unicode (description )
@@ -275,3 +304,28 @@ def raw_commit_diff(self, commit):
275
304
bytesio = io .BytesIO ()
276
305
dulwich .patch .write_tree_diff (bytesio , self .object_store , parent_tree , commit .tree )
277
306
return bytesio .getvalue ()
307
+
308
+ def freeze (self ):
309
+ return FrozenFancyRepo (self )
310
+
311
+
312
+ class FrozenFancyRepo (object ):
313
+ """A special version of FancyRepo that assumes the underlying Git
314
+ repository does not change. Used for performance optimizations.
315
+ """
316
+ def __init__ (self , repo ):
317
+ self .__repo = repo
318
+ self .__last_updated_at = NOT_SET
319
+
320
+ def __setattr__ (self , name , value ):
321
+ if not name .startswith ('_FrozenFancyRepo__' ):
322
+ raise TypeError ("Can't set %s attribute on FrozenFancyRepo" % name )
323
+ super (FrozenFancyRepo , self ).__setattr__ (name , value )
324
+
325
+ def __getattr__ (self , name ):
326
+ return getattr (self .__repo , name )
327
+
328
+ def fast_get_last_updated_at (self ):
329
+ if self .__last_updated_at is NOT_SET :
330
+ self .__last_updated_at = self .__repo .get_last_updated_at ()
331
+ return self .__last_updated_at
0 commit comments