2019-05-04 15:39:03 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bufio"
2020-09-05 23:12:14 +03:00
"context"
2019-05-04 15:39:03 +03:00
"fmt"
2020-09-05 23:12:14 +03:00
"os"
2020-01-20 13:07:30 +03:00
"sort"
2019-05-04 15:39:03 +03:00
"strconv"
"strings"
"time"
)
// CodeActivityStats represents git statistics data
type CodeActivityStats struct {
AuthorCount int64
CommitCount int64
ChangedFiles int64
Additions int64
Deletions int64
CommitCountInAllBranches int64
2020-01-20 13:07:30 +03:00
Authors [ ] * CodeActivityAuthor
}
// CodeActivityAuthor represents git statistics data for commit authors
type CodeActivityAuthor struct {
Name string
Email string
Commits int64
2019-05-04 15:39:03 +03:00
}
2021-07-08 14:38:13 +03:00
// GetCodeActivityStats returns code statistics for activity page
2019-05-04 15:39:03 +03:00
func ( repo * Repository ) GetCodeActivityStats ( fromTime time . Time , branch string ) ( * CodeActivityStats , error ) {
stats := & CodeActivityStats { }
since := fromTime . Format ( time . RFC3339 )
2022-04-01 05:55:30 +03:00
stdout , _ , runErr := NewCommand ( repo . Ctx , "rev-list" , "--count" , "--no-merges" , "--branches=*" , "--date=iso" , fmt . Sprintf ( "--since='%s'" , since ) ) . RunStdString ( & RunOpts { Dir : repo . Path } )
if runErr != nil {
return nil , runErr
2019-05-04 15:39:03 +03:00
}
2022-04-01 05:55:30 +03:00
c , err := strconv . ParseInt ( strings . TrimSpace ( stdout ) , 10 , 64 )
2019-05-04 15:39:03 +03:00
if err != nil {
return nil , err
}
stats . CommitCountInAllBranches = c
2020-09-05 23:12:14 +03:00
stdoutReader , stdoutWriter , err := os . Pipe ( )
if err != nil {
return nil , err
}
defer func ( ) {
_ = stdoutReader . Close ( )
_ = stdoutWriter . Close ( )
} ( )
2021-03-18 00:00:26 +03:00
args := [ ] string { "log" , "--numstat" , "--no-merges" , "--pretty=format:---%n%h%n%aN%n%aE%n" , "--date=iso" , fmt . Sprintf ( "--since='%s'" , since ) }
2019-05-04 15:39:03 +03:00
if len ( branch ) == 0 {
args = append ( args , "--branches=*" )
} else {
args = append ( args , "--first-parent" , branch )
}
2020-09-05 23:12:14 +03:00
stderr := new ( strings . Builder )
2022-04-01 05:55:30 +03:00
err = NewCommand ( repo . Ctx , args ... ) . Run ( & RunOpts {
Env : [ ] string { } ,
Dir : repo . Path ,
Stdout : stdoutWriter ,
Stderr : stderr ,
2022-02-11 15:47:22 +03:00
PipelineFunc : func ( ctx context . Context , cancel context . CancelFunc ) error {
2020-09-05 23:12:14 +03:00
_ = stdoutWriter . Close ( )
scanner := bufio . NewScanner ( stdoutReader )
scanner . Split ( bufio . ScanLines )
stats . CommitCount = 0
stats . Additions = 0
stats . Deletions = 0
authors := make ( map [ string ] * CodeActivityAuthor )
files := make ( map [ string ] bool )
var author string
p := 0
for scanner . Scan ( ) {
l := strings . TrimSpace ( scanner . Text ( ) )
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p ++
2020-01-20 13:07:30 +03:00
}
2020-09-05 23:12:14 +03:00
if p > 4 && len ( l ) == 0 {
continue
2019-05-04 15:39:03 +03:00
}
2020-09-05 23:12:14 +03:00
switch p {
case 1 : // Separator
case 2 : // Commit sha-1
stats . CommitCount ++
case 3 : // Author
author = l
case 4 : // E-mail
email := strings . ToLower ( l )
if _ , ok := authors [ email ] ; ! ok {
2022-02-11 15:47:22 +03:00
authors [ email ] = & CodeActivityAuthor { Name : author , Email : email , Commits : 0 }
2020-09-05 23:12:14 +03:00
}
authors [ email ] . Commits ++
default : // Changed file
if parts := strings . Fields ( l ) ; len ( parts ) >= 3 {
if parts [ 0 ] != "-" {
if c , err := strconv . ParseInt ( strings . TrimSpace ( parts [ 0 ] ) , 10 , 64 ) ; err == nil {
stats . Additions += c
}
}
if parts [ 1 ] != "-" {
if c , err := strconv . ParseInt ( strings . TrimSpace ( parts [ 1 ] ) , 10 , 64 ) ; err == nil {
stats . Deletions += c
}
}
if _ , ok := files [ parts [ 2 ] ] ; ! ok {
files [ parts [ 2 ] ] = true
}
2019-05-04 15:39:03 +03:00
}
}
}
2020-09-05 23:12:14 +03:00
a := make ( [ ] * CodeActivityAuthor , 0 , len ( authors ) )
for _ , v := range authors {
a = append ( a , v )
}
// Sort authors descending depending on commit count
sort . Slice ( a , func ( i , j int ) bool {
return a [ i ] . Commits > a [ j ] . Commits
} )
stats . AuthorCount = int64 ( len ( authors ) )
stats . ChangedFiles = int64 ( len ( files ) )
stats . Authors = a
_ = stdoutReader . Close ( )
return nil
2022-02-11 15:47:22 +03:00
} ,
} )
2020-09-05 23:12:14 +03:00
if err != nil {
return nil , fmt . Errorf ( "Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s" , err , stderr )
2020-01-20 13:07:30 +03:00
}
2019-05-04 15:39:03 +03:00
return stats , nil
}