@@ -25,11 +25,12 @@ func init() {
2525}
2626
2727type contributor struct {
28- name string
29- emails map [string ]bool
30- commits int
31- added int
32- deleted int
28+ name string
29+ emails map [string ]bool
30+ commits int
31+ added int
32+ deleted int
33+ lastActive string // ISO date
3334}
3435
3536func runWho (cmd * cobra.Command , args []string ) error {
@@ -51,19 +52,25 @@ func runWho(cmd *cobra.Command, args []string) error {
5152func whoRepo (n int , since string ) error {
5253 sp := ui .StartSpinner ("Analyzing contributors (this may take a moment)..." )
5354
54- // Get commit counts + emails
55+ // Run both git commands in parallel
5556 shortlogArgs := []string {"shortlog" , "-sne" , "--all" }
5657 if since != "" {
5758 shortlogArgs = append (shortlogArgs , "--since=" + since )
5859 }
59- shortlogOut , err := git .Run (shortlogArgs ... )
60-
61- // Get line stats per author (added/deleted)
62- numstatArgs := []string {"log" , "--all" , "--numstat" , "--format=%aE" }
60+ shortstatArgs := []string {"log" , "--all" , "--shortstat" , "--format=%aE|%aI" }
6361 if since != "" {
64- numstatArgs = append (numstatArgs , "--since=" + since )
62+ shortstatArgs = append (shortstatArgs , "--since=" + since )
6563 }
66- numstatOut := git .RunUnchecked (numstatArgs ... )
64+
65+ var shortlogOut , numstatOut string
66+ var err error
67+ done := make (chan struct {})
68+ go func () {
69+ shortlogOut , err = git .Run (shortlogArgs ... )
70+ close (done )
71+ }()
72+ numstatOut = git .RunUnchecked (shortstatArgs ... )
73+ <- done
6774
6875 // Parse shortlog
6976 type rawEntry struct {
@@ -97,26 +104,44 @@ func whoRepo(n int, since string) error {
97104 }
98105 }
99106
100- // Parse numstat to get lines per email
101- linesByEmail := map [string ][2 ]int {} // email -> [added, deleted]
107+ // Parse shortstat to get lines and last-active per email
108+ linesByEmail := map [string ][2 ]int {} // email -> [added, deleted]
109+ lastActiveByEmail := map [string ]string {} // email -> ISO date (first seen = most recent)
102110 if numstatOut != "" {
103- var currentEmail string
111+ var currentEmail , currentDate string
104112 for _ , line := range strings .Split (numstatOut , "\n " ) {
105113 line = strings .TrimSpace (line )
106114 if line == "" {
107115 continue
108116 }
109- // Email lines from --format=%aE
110- if ! strings .Contains (line , "\t " ) && strings .Contains (line , "@" ) {
111- currentEmail = strings .ToLower (line )
117+ // Format lines: "email|date" from --format=%aE|%aI
118+ if strings .Contains (line , "@" ) && strings .Contains (line , "|" ) {
119+ parts := strings .SplitN (line , "|" , 2 )
120+ currentEmail = strings .ToLower (parts [0 ])
121+ if len (parts ) > 1 {
122+ currentDate = parts [1 ]
123+ }
124+ // Track first (most recent) date per email
125+ if _ , exists := lastActiveByEmail [currentEmail ]; ! exists {
126+ lastActiveByEmail [currentEmail ] = currentDate
127+ }
112128 continue
113129 }
114- // Numstat lines: "added\tdeleted\tfile"
115- parts := strings .SplitN (line , "\t " , 3 )
116- if len (parts ) >= 2 && currentEmail != "" {
130+ // Shortstat lines
131+ if strings .Contains (line , "file" ) && strings .Contains (line , "changed" ) && currentEmail != "" {
117132 var a , d int
118- fmt .Sscanf (parts [0 ], "%d" , & a )
119- fmt .Sscanf (parts [1 ], "%d" , & d )
133+ if idx := strings .Index (line , "insertion" ); idx > 0 {
134+ sub := strings .TrimSpace (line [:idx ])
135+ if ci := strings .LastIndex (sub , " " ); ci >= 0 {
136+ fmt .Sscanf (strings .TrimSpace (sub [ci + 1 :]), "%d" , & a )
137+ }
138+ }
139+ if idx := strings .Index (line , "deletion" ); idx > 0 {
140+ sub := strings .TrimSpace (line [:idx ])
141+ if ci := strings .LastIndex (sub , " " ); ci >= 0 {
142+ fmt .Sscanf (strings .TrimSpace (sub [ci + 1 :]), "%d" , & d )
143+ }
144+ }
120145 stats := linesByEmail [currentEmail ]
121146 stats [0 ] += a
122147 stats [1 ] += d
@@ -192,12 +217,16 @@ func whoRepo(n int, since string) error {
192217 }
193218 for root , emails := range groupEmails {
194219 groups [root ].emails = emails
195- // Sum line stats across all emails in this group
196220 for email := range emails {
197221 if stats , ok := linesByEmail [email ]; ok {
198222 groups [root ].added += stats [0 ]
199223 groups [root ].deleted += stats [1 ]
200224 }
225+ if date , ok := lastActiveByEmail [email ]; ok {
226+ if groups [root ].lastActive == "" || date > groups [root ].lastActive {
227+ groups [root ].lastActive = date
228+ }
229+ }
201230 }
202231 }
203232
@@ -255,7 +284,10 @@ func whoRepo(n int, since string) error {
255284 }
256285 sort .Strings (emailList )
257286
258- lastActive := getAuthorLastEdit (c .name , "." )
287+ lastActive := "unknown"
288+ if c .lastActive != "" {
289+ lastActive = git .TimeAgo (c .lastActive )
290+ }
259291
260292 // Lines and percentage
261293 linesStr := ui .AddStyle .Render (fmt .Sprintf ("+%d" , c .added )) + " " + ui .DelStyle .Render (fmt .Sprintf ("-%d" , c .deleted ))
@@ -290,10 +322,3 @@ func whoRepo(n int, since string) error {
290322 return nil
291323}
292324
293- func getAuthorLastEdit (author , path string ) string {
294- out := git .RunUnchecked ("log" , "-1" , "--author=" + author , "--format=%aI" , "--" , path )
295- if out == "" {
296- return "unknown"
297- }
298- return git .TimeAgo (out )
299- }
0 commit comments