1+ using System ;
2+ using System . Collections . Concurrent ;
3+ using System . Collections . Generic ;
4+ using System . Threading ;
5+ using System . Threading . Tasks ;
6+
7+ namespace SourceGit . Commands . Optimization
8+ {
9+ /// <summary>
10+ /// Intelligent caching layer for Git command results with Git-Flow awareness
11+ /// </summary>
12+ public class GitCommandCache : IDisposable
13+ {
14+ private static GitCommandCache _instance ;
15+ private static readonly object _lock = new object ( ) ;
16+
17+ private readonly ConcurrentDictionary < string , CacheEntry > _cache ;
18+ private readonly Timer _cleanupTimer ;
19+ private bool _disposed ;
20+
21+ public class CacheEntry
22+ {
23+ private int _hitCount ;
24+
25+ public string Data { get ; set ; }
26+ public DateTime ExpiresAt { get ; set ; }
27+ public CacheType Type { get ; set ; }
28+
29+ public int HitCount
30+ {
31+ get => _hitCount ;
32+ set => _hitCount = value ;
33+ }
34+
35+ public void IncrementHitCount ( )
36+ {
37+ Interlocked . Increment ( ref _hitCount ) ;
38+ }
39+ }
40+
41+ public enum CacheType
42+ {
43+ Config , // 15 minutes - rarely changes
44+ Remotes , // 10 minutes - relatively stable
45+ Branches , // 2 minutes - moderate volatility
46+ Tags , // 5 minutes - low volatility
47+ Status , // 30 seconds - high volatility
48+ GitFlowConfig , // 15 minutes - Git-Flow specific
49+ BranchRelations // 30 seconds - very volatile during Git-Flow ops
50+ }
51+
52+ public static GitCommandCache Instance
53+ {
54+ get
55+ {
56+ if ( _instance == null )
57+ {
58+ lock ( _lock )
59+ {
60+ _instance ??= new GitCommandCache ( ) ;
61+ }
62+ }
63+ return _instance ;
64+ }
65+ }
66+
67+ private GitCommandCache ( )
68+ {
69+ _cache = new ConcurrentDictionary < string , CacheEntry > ( ) ;
70+
71+ // Cleanup expired entries every minute
72+ _cleanupTimer = new Timer ( CleanupExpiredEntries , null , TimeSpan . FromMinutes ( 1 ) , TimeSpan . FromMinutes ( 1 ) ) ;
73+ }
74+
75+ /// <summary>
76+ /// Get cached result or execute command and cache
77+ /// </summary>
78+ public async Task < string > GetOrExecuteAsync ( string repository , string command , CacheType type , Func < Task < string > > executor )
79+ {
80+ var cacheKey = GetCacheKey ( repository , command ) ;
81+
82+ // Check if we have a valid cached entry
83+ if ( _cache . TryGetValue ( cacheKey , out var entry ) && entry . ExpiresAt > DateTime . UtcNow )
84+ {
85+ entry . IncrementHitCount ( ) ;
86+ Models . PerformanceMonitor . StartTimer ( $ "CacheHit_{ type } ") ;
87+ Models . PerformanceMonitor . StopTimer ( $ "CacheHit_{ type } ") ;
88+ return entry . Data ;
89+ }
90+
91+ // Execute the command
92+ Models . PerformanceMonitor . StartTimer ( $ "CacheMiss_{ type } ") ;
93+ var result = await executor ( ) ;
94+ Models . PerformanceMonitor . StopTimer ( $ "CacheMiss_{ type } ") ;
95+
96+ // Cache the result
97+ if ( ! string . IsNullOrEmpty ( result ) )
98+ {
99+ var expiration = GetExpiration ( type ) ;
100+ _cache [ cacheKey ] = new CacheEntry
101+ {
102+ Data = result ,
103+ ExpiresAt = DateTime . UtcNow . Add ( expiration ) ,
104+ Type = type ,
105+ HitCount = 0
106+ } ;
107+ }
108+
109+ return result ;
110+ }
111+
112+ /// <summary>
113+ /// Invalidate cache entries based on operation type
114+ /// </summary>
115+ public void InvalidateByOperation ( string repository , GitOperation operation )
116+ {
117+ var keysToRemove = new List < string > ( ) ;
118+
119+ foreach ( var kvp in _cache )
120+ {
121+ if ( ! kvp . Key . StartsWith ( repository ) )
122+ continue ;
123+
124+ var shouldInvalidate = ShouldInvalidate ( kvp . Value . Type , operation ) ;
125+ if ( shouldInvalidate )
126+ {
127+ keysToRemove . Add ( kvp . Key ) ;
128+ }
129+ }
130+
131+ foreach ( var key in keysToRemove )
132+ {
133+ _cache . TryRemove ( key , out _ ) ;
134+ }
135+ }
136+
137+ /// <summary>
138+ /// Invalidate all cache for a repository
139+ /// </summary>
140+ public void InvalidateRepository ( string repository )
141+ {
142+ var keysToRemove = new List < string > ( ) ;
143+
144+ foreach ( var key in _cache . Keys )
145+ {
146+ if ( key . StartsWith ( repository ) )
147+ {
148+ keysToRemove . Add ( key ) ;
149+ }
150+ }
151+
152+ foreach ( var key in keysToRemove )
153+ {
154+ _cache . TryRemove ( key , out _ ) ;
155+ }
156+ }
157+
158+ /// <summary>
159+ /// Invalidate specific cache type for a repository
160+ /// </summary>
161+ public void InvalidateCacheType ( string repository , CacheType type )
162+ {
163+ var keysToRemove = new List < string > ( ) ;
164+
165+ foreach ( var kvp in _cache )
166+ {
167+ if ( kvp . Key . StartsWith ( repository ) && kvp . Value . Type == type )
168+ {
169+ keysToRemove . Add ( kvp . Key ) ;
170+ }
171+ }
172+
173+ foreach ( var key in keysToRemove )
174+ {
175+ _cache . TryRemove ( key , out _ ) ;
176+ }
177+ }
178+
179+ /// <summary>
180+ /// Get cache statistics
181+ /// </summary>
182+ public CacheStatistics GetStatistics ( )
183+ {
184+ var stats = new CacheStatistics
185+ {
186+ TotalEntries = _cache . Count ,
187+ EntriesByType = new Dictionary < CacheType , int > ( ) ,
188+ TotalHits = 0 ,
189+ EstimatedMemoryMB = 0
190+ } ;
191+
192+ foreach ( var kvp in _cache )
193+ {
194+ var entry = kvp . Value ;
195+
196+ if ( ! stats . EntriesByType . ContainsKey ( entry . Type ) )
197+ stats . EntriesByType [ entry . Type ] = 0 ;
198+
199+ stats . EntriesByType [ entry . Type ] ++ ;
200+ stats . TotalHits += entry . HitCount ;
201+
202+ // Rough memory estimation (2 bytes per char)
203+ stats . EstimatedMemoryMB += ( entry . Data ? . Length ?? 0 ) * 2.0 / ( 1024 * 1024 ) ;
204+ }
205+
206+ return stats ;
207+ }
208+
209+ private string GetCacheKey ( string repository , string command )
210+ {
211+ // Create a unique key for this repository and command
212+ return $ "{ repository } |{ command } ";
213+ }
214+
215+ private TimeSpan GetExpiration ( CacheType type )
216+ {
217+ return type switch
218+ {
219+ CacheType . Config => TimeSpan . FromMinutes ( 15 ) ,
220+ CacheType . GitFlowConfig => TimeSpan . FromMinutes ( 15 ) ,
221+ CacheType . Remotes => TimeSpan . FromMinutes ( 10 ) ,
222+ CacheType . Tags => TimeSpan . FromMinutes ( 5 ) ,
223+ CacheType . Branches => TimeSpan . FromMinutes ( 2 ) ,
224+ CacheType . Status => TimeSpan . FromSeconds ( 30 ) ,
225+ CacheType . BranchRelations => TimeSpan . FromSeconds ( 30 ) ,
226+ _ => TimeSpan . FromMinutes ( 1 )
227+ } ;
228+ }
229+
230+ private bool ShouldInvalidate ( CacheType cacheType , GitOperation operation )
231+ {
232+ return operation switch
233+ {
234+ GitOperation . Checkout => cacheType is CacheType . Status or CacheType . BranchRelations ,
235+ GitOperation . BranchCreate => cacheType is CacheType . Branches or CacheType . BranchRelations ,
236+ GitOperation . BranchDelete => cacheType is CacheType . Branches or CacheType . BranchRelations ,
237+ GitOperation . Merge => cacheType is CacheType . Status or CacheType . Branches or CacheType . BranchRelations ,
238+ GitOperation . Rebase => cacheType is CacheType . Status or CacheType . Branches or CacheType . BranchRelations ,
239+ GitOperation . Commit => cacheType is CacheType . Status ,
240+ GitOperation . Push => cacheType is CacheType . Branches or CacheType . BranchRelations ,
241+ GitOperation . Pull => cacheType is CacheType . Branches or CacheType . Status or CacheType . BranchRelations ,
242+ GitOperation . Fetch => cacheType is CacheType . Branches or CacheType . Tags or CacheType . BranchRelations ,
243+ GitOperation . ConfigChange => cacheType is CacheType . Config or CacheType . GitFlowConfig ,
244+ GitOperation . GitFlowStart => cacheType is CacheType . Branches or CacheType . GitFlowConfig or CacheType . BranchRelations ,
245+ GitOperation . GitFlowFinish => cacheType is CacheType . Branches or CacheType . GitFlowConfig or CacheType . BranchRelations ,
246+ GitOperation . TagCreate => cacheType is CacheType . Tags ,
247+ GitOperation . TagDelete => cacheType is CacheType . Tags ,
248+ _ => false
249+ } ;
250+ }
251+
252+ private void CleanupExpiredEntries ( object state )
253+ {
254+ var now = DateTime . UtcNow ;
255+ var keysToRemove = new List < string > ( ) ;
256+
257+ foreach ( var kvp in _cache )
258+ {
259+ if ( kvp . Value . ExpiresAt <= now )
260+ {
261+ keysToRemove . Add ( kvp . Key ) ;
262+ }
263+ }
264+
265+ foreach ( var key in keysToRemove )
266+ {
267+ _cache . TryRemove ( key , out _ ) ;
268+ }
269+
270+ // Also cleanup if cache is too large (>100MB estimated)
271+ var stats = GetStatistics ( ) ;
272+ if ( stats . EstimatedMemoryMB > 100 )
273+ {
274+ // Remove least recently used entries
275+ var sortedEntries = new List < KeyValuePair < string , CacheEntry > > ( ) ;
276+ foreach ( var kvp in _cache )
277+ {
278+ sortedEntries . Add ( kvp ) ;
279+ }
280+
281+ sortedEntries . Sort ( ( a , b ) => a . Value . HitCount . CompareTo ( b . Value . HitCount ) ) ;
282+
283+ // Remove bottom 25% by hit count
284+ var removeCount = sortedEntries . Count / 4 ;
285+ for ( int i = 0 ; i < removeCount ; i ++ )
286+ {
287+ _cache . TryRemove ( sortedEntries [ i ] . Key , out _ ) ;
288+ }
289+ }
290+ }
291+
292+ public void Dispose ( )
293+ {
294+ if ( _disposed )
295+ return ;
296+
297+ _disposed = true ;
298+ _cleanupTimer ? . Dispose ( ) ;
299+ _cache . Clear ( ) ;
300+
301+ lock ( _lock )
302+ {
303+ if ( _instance == this )
304+ _instance = null ;
305+ }
306+ }
307+
308+ public class CacheStatistics
309+ {
310+ public int TotalEntries { get ; set ; }
311+ public Dictionary < CacheType , int > EntriesByType { get ; set ; }
312+ public int TotalHits { get ; set ; }
313+ public double EstimatedMemoryMB { get ; set ; }
314+ }
315+ }
316+
317+ public enum GitOperation
318+ {
319+ Checkout ,
320+ BranchCreate ,
321+ BranchDelete ,
322+ Merge ,
323+ Rebase ,
324+ Commit ,
325+ Push ,
326+ Pull ,
327+ Fetch ,
328+ ConfigChange ,
329+ GitFlowStart ,
330+ GitFlowFinish ,
331+ TagCreate ,
332+ TagDelete
333+ }
334+ }
0 commit comments