@@ -11,7 +11,7 @@ import type {
1111 ContributorStatsResult ,
1212 LanguageStat ,
1313 LanguageStatsResult
14- } from '../views/stats ' ;
14+ } from '../types ' ;
1515
1616const GIT_AUTHOR_PREFIX = '--AUTHOR--' ;
1717
@@ -41,10 +41,14 @@ export class StatsService {
4141
4242 const stats = new Map < string , number > ( ) ;
4343 for ( const uri of filtered ) {
44- const document = await vscode . workspace . openTextDocument ( uri ) ;
45- const languageId = document . languageId || 'unknown' ;
46- const codeLines = this . lineCounter . countDocumentLines ( document ) ;
47- stats . set ( languageId , ( stats . get ( languageId ) ?? 0 ) + codeLines ) ;
44+ try {
45+ const document = await vscode . workspace . openTextDocument ( uri ) ;
46+ const languageId = document . languageId || 'unknown' ;
47+ const codeLines = this . lineCounter . countDocumentLines ( document ) ;
48+ stats . set ( languageId , ( stats . get ( languageId ) ?? 0 ) + codeLines ) ;
49+ } catch {
50+ continue ;
51+ }
4852 }
4953
5054 const entries = Array . from ( stats . entries ( ) )
@@ -75,6 +79,22 @@ export class StatsService {
7579 return { available : true , stats } ;
7680 }
7781
82+ async getContributorStatsAll ( ) : Promise < ContributorStatsResult > {
83+ const rootPath = await this . getGitRoot ( ) ;
84+ if ( ! rootPath ) {
85+ return { available : false , stats : [ ] } ;
86+ }
87+
88+ let stats : Array < { name : string ; linesAdded : number } > = [ ] ;
89+ try {
90+ stats = await this . getContributorStatsFromGitAll ( rootPath ) ;
91+ } catch {
92+ return { available : true , stats : [ ] } ;
93+ }
94+
95+ return { available : true , stats } ;
96+ }
97+
7898 async getContributorLanguageStats ( authorName : string ) : Promise < { available : boolean ; stats : ContributorLanguageStat [ ] } > {
7999 const rootPath = await this . getGitRoot ( ) ;
80100 if ( ! rootPath ) {
@@ -190,6 +210,53 @@ export class StatsService {
190210 rootPath
191211 ) ;
192212
213+ const stats = new Map < string , number > ( ) ;
214+ const existsCache = new Map < string , boolean > ( ) ;
215+ let currentAuthor = 'Unknown' ;
216+ const lines = output . split ( / \r ? \n / ) ;
217+ for ( const line of lines ) {
218+ if ( line . startsWith ( GIT_AUTHOR_PREFIX ) ) {
219+ currentAuthor = line . slice ( GIT_AUTHOR_PREFIX . length ) . trim ( ) || 'Unknown' ;
220+ if ( ! stats . has ( currentAuthor ) ) {
221+ stats . set ( currentAuthor , 0 ) ;
222+ }
223+ continue ;
224+ }
225+
226+ if ( ! line . trim ( ) ) {
227+ continue ;
228+ }
229+
230+ const [ addedText , , filePath ] = line . split ( '\t' ) ;
231+ if ( ! addedText || addedText === '-' || ! filePath ) {
232+ continue ;
233+ }
234+
235+ const normalizedPath = this . normalizeGitPath ( filePath ) ;
236+ const exists = await this . fileExists ( rootPath , normalizedPath , existsCache ) ;
237+ if ( ! exists ) {
238+ continue ;
239+ }
240+
241+ const added = Number ( addedText ) ;
242+ if ( Number . isNaN ( added ) ) {
243+ continue ;
244+ }
245+
246+ stats . set ( currentAuthor , ( stats . get ( currentAuthor ) ?? 0 ) + added ) ;
247+ }
248+
249+ return Array . from ( stats . entries ( ) )
250+ . map ( ( [ name , linesAdded ] ) => ( { name, linesAdded } ) )
251+ . sort ( ( a , b ) => b . linesAdded - a . linesAdded ) ;
252+ }
253+
254+ private async getContributorStatsFromGitAll ( rootPath : string ) : Promise < ContributorStat [ ] > {
255+ const output = await this . execGit (
256+ [ 'log' , '--numstat' , `--pretty=${ GIT_AUTHOR_PREFIX } %an` ] ,
257+ rootPath
258+ ) ;
259+
193260 const stats = new Map < string , number > ( ) ;
194261 let currentAuthor = 'Unknown' ;
195262 const lines = output . split ( / \r ? \n / ) ;
@@ -235,6 +302,7 @@ export class StatsService {
235302
236303 const languageTotals = new Map < string , number > ( ) ;
237304 const languageByPath = new Map < string , string > ( ) ;
305+ const existsCache = new Map < string , boolean > ( ) ;
238306 let currentAuthor = 'Unknown' ;
239307 const lines = output . split ( / \r ? \n / ) ;
240308 for ( const line of lines ) {
@@ -262,6 +330,10 @@ export class StatsService {
262330 }
263331
264332 const normalizedPath = this . normalizeGitPath ( filePath ) ;
333+ const exists = await this . fileExists ( rootPath , normalizedPath , existsCache ) ;
334+ if ( ! exists ) {
335+ continue ;
336+ }
265337 const languageId = await this . getLanguageForPath ( rootPath , normalizedPath , languageByPath ) ;
266338 languageTotals . set ( languageId , ( languageTotals . get ( languageId ) ?? 0 ) + added ) ;
267339 }
@@ -283,6 +355,7 @@ export class StatsService {
283355
284356 const fileTotals = new Map < string , { added : number ; deleted : number } > ( ) ;
285357 const languageByPath = new Map < string , string > ( ) ;
358+ const existsCache = new Map < string , boolean > ( ) ;
286359 let currentAuthor = 'Unknown' ;
287360 const lines = output . split ( / \r ? \n / ) ;
288361 for ( const line of lines ) {
@@ -314,6 +387,10 @@ export class StatsService {
314387 }
315388
316389 const normalizedPath = this . normalizeGitPath ( filePath ) ;
390+ const exists = await this . fileExists ( rootPath , normalizedPath , existsCache ) ;
391+ if ( ! exists ) {
392+ continue ;
393+ }
317394 const fileLanguageId = await this . getLanguageForPath ( rootPath , normalizedPath , languageByPath ) ;
318395 if ( fileLanguageId !== languageId ) {
319396 continue ;
@@ -363,6 +440,27 @@ export class StatsService {
363440 return languageId ;
364441 }
365442
443+ private async fileExists (
444+ rootPath : string ,
445+ filePath : string ,
446+ cache : Map < string , boolean >
447+ ) : Promise < boolean > {
448+ if ( cache . has ( filePath ) ) {
449+ return cache . get ( filePath ) ?? false ;
450+ }
451+ const absolutePath = path . isAbsolute ( filePath )
452+ ? filePath
453+ : path . join ( rootPath , filePath ) ;
454+ try {
455+ await vscode . workspace . fs . stat ( vscode . Uri . file ( absolutePath ) ) ;
456+ cache . set ( filePath , true ) ;
457+ return true ;
458+ } catch {
459+ cache . set ( filePath , false ) ;
460+ return false ;
461+ }
462+ }
463+
366464 private languageFromExtension ( filePath : string ) : string {
367465 const ext = path . extname ( filePath ) . toLowerCase ( ) ;
368466 if ( ! ext ) {
0 commit comments