@@ -122,7 +122,14 @@ export class StatsService {
122122 return { available : true , stats : [ ] } ;
123123 }
124124
125- return { available : true , stats } ;
125+ let branch : string | undefined ;
126+ try {
127+ branch = ( await this . execGit ( [ 'rev-parse' , '--abbrev-ref' , 'HEAD' ] , rootPath ) ) . trim ( ) ;
128+ } catch {
129+ branch = undefined ;
130+ }
131+
132+ return { available : true , stats, branch } ;
126133 }
127134
128135 async getContributorStatsAll ( ) : Promise < ContributorStatsResult > {
@@ -138,7 +145,9 @@ export class StatsService {
138145 return { available : true , stats : [ ] } ;
139146 }
140147
141- return { available : true , stats } ;
148+
149+ // mark as 'all' to indicate repo-wide data
150+ return { available : true , stats, branch : 'all' } ;
142151 }
143152
144153 async getContributorLanguageStats ( authorName : string ) : Promise < { available : boolean ; stats : ContributorLanguageStat [ ] } > {
@@ -566,7 +575,7 @@ export class StatsService {
566575 }
567576
568577 return Array . from ( stats . entries ( ) )
569- . map ( ( [ key , v ] ) => ( { name : v . name , added : v . added , deleted : v . deleted } ) )
578+ . map ( ( [ key , v ] ) => ( { name : v . name , email : key && key . includes ( '@' ) ? key : undefined , added : v . added , deleted : v . deleted } ) )
570579 // Filter out authors with no recorded changes (added + deleted === 0)
571580 . filter ( item => ( item . added + item . deleted ) > 0 )
572581 . sort ( ( a , b ) => ( b . added + b . deleted ) - ( a . added + a . deleted ) ) ;
@@ -622,7 +631,7 @@ export class StatsService {
622631 }
623632
624633 return Array . from ( stats . entries ( ) )
625- . map ( ( [ key , v ] ) => ( { name : v . name , added : v . added , deleted : v . deleted } ) )
634+ . map ( ( [ key , v ] ) => ( { name : v . name , email : key && key . includes ( '@' ) ? key : undefined , added : v . added , deleted : v . deleted } ) )
626635 // Exclude authors with zero total changes when aggregating across all refs
627636 . filter ( item => ( item . added + item . deleted ) > 0 )
628637 . sort ( ( a , b ) => ( b . added + b . deleted ) - ( a . added + a . deleted ) ) ;
@@ -632,28 +641,36 @@ export class StatsService {
632641 rootPath : string ,
633642 authorName : string
634643 ) : Promise < ContributorLanguageStat [ ] > {
644+ // Include author email so we can match by name or email when authorName may be an email
635645 const output = await this . execGit (
636- [ 'log' , 'HEAD' , '--first-parent' , '--numstat' , `--pretty=${ GIT_AUTHOR_PREFIX } %an` ] ,
646+ [ 'log' , 'HEAD' , '--first-parent' , '--numstat' , `--pretty=${ GIT_AUTHOR_PREFIX } %an|%aE ` ] ,
637647 rootPath
638648 ) ;
639649
640650 const languageTotals = new Map < string , number > ( ) ;
641651 const languageByPath = new Map < string , string > ( ) ;
642652 const existsCache = new Map < string , boolean > ( ) ;
643- let currentAuthor = 'Unknown' ;
653+ let currentAuthorName = 'Unknown' ;
654+ let currentAuthorEmail = '' ;
644655 const lines = output . split ( / \r ? \n / ) ;
645656 for ( const line of lines ) {
646657 if ( line . startsWith ( GIT_AUTHOR_PREFIX ) ) {
647- currentAuthor = line . slice ( GIT_AUTHOR_PREFIX . length ) . trim ( ) || 'Unknown' ;
658+ const payload = line . slice ( GIT_AUTHOR_PREFIX . length ) . trim ( ) ;
659+ const [ namePart , emailPart ] = payload . split ( '|' ) ;
660+ currentAuthorName = ( namePart ?? 'Unknown' ) . trim ( ) || 'Unknown' ;
661+ currentAuthorEmail = ( emailPart ?? '' ) . trim ( ) ;
648662 continue ;
649663 }
650664
651665 if ( ! line . trim ( ) ) {
652666 continue ;
653667 }
654668
655- if ( currentAuthor !== authorName ) {
656- continue ;
669+ // Match either by provided name or email
670+ if ( authorName . includes ( '@' ) ) {
671+ if ( currentAuthorEmail !== authorName ) continue ;
672+ } else {
673+ if ( currentAuthorName !== authorName ) continue ;
657674 }
658675
659676 const [ addedText , , filePath ] = line . split ( '\t' ) ;
@@ -685,28 +702,36 @@ export class StatsService {
685702 authorName : string ,
686703 languageId : string
687704 ) : Promise < ContributorFileStat [ ] > {
705+ // Include author email so we can match by name or email when authorName may be an email
688706 const output = await this . execGit (
689- [ 'log' , 'HEAD' , '--first-parent' , '--numstat' , `--pretty=${ GIT_AUTHOR_PREFIX } %an` ] ,
707+ [ 'log' , 'HEAD' , '--first-parent' , '--numstat' , `--pretty=${ GIT_AUTHOR_PREFIX } %an|%aE ` ] ,
690708 rootPath
691709 ) ;
692710
693711 const fileTotals = new Map < string , { added : number ; deleted : number } > ( ) ;
694712 const languageByPath = new Map < string , string > ( ) ;
695713 const existsCache = new Map < string , boolean > ( ) ;
696- let currentAuthor = 'Unknown' ;
714+ let currentAuthorName = 'Unknown' ;
715+ let currentAuthorEmail = '' ;
697716 const lines = output . split ( / \r ? \n / ) ;
698717 for ( const line of lines ) {
699718 if ( line . startsWith ( GIT_AUTHOR_PREFIX ) ) {
700- currentAuthor = line . slice ( GIT_AUTHOR_PREFIX . length ) . trim ( ) || 'Unknown' ;
719+ const payload = line . slice ( GIT_AUTHOR_PREFIX . length ) . trim ( ) ;
720+ const [ namePart , emailPart ] = payload . split ( '|' ) ;
721+ currentAuthorName = ( namePart ?? 'Unknown' ) . trim ( ) || 'Unknown' ;
722+ currentAuthorEmail = ( emailPart ?? '' ) . trim ( ) ;
701723 continue ;
702724 }
703725
704726 if ( ! line . trim ( ) ) {
705727 continue ;
706728 }
707729
708- if ( currentAuthor !== authorName ) {
709- continue ;
730+ // Match either by provided name or email
731+ if ( authorName . includes ( '@' ) ) {
732+ if ( currentAuthorEmail !== authorName ) continue ;
733+ } else {
734+ if ( currentAuthorName !== authorName ) continue ;
710735 }
711736
712737 const [ addedText , deletedText , filePath ] = line . split ( '\t' ) ;
0 commit comments