161 lines
3.7 KiB
Go
161 lines
3.7 KiB
Go
package gitcmd
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Contributor struct {
|
|
Author string `json:"author"`
|
|
Email string `json:"email"`
|
|
Commits int `json:"commits"`
|
|
Additions int `json:"additions"`
|
|
Deletions int `json:"deletions"`
|
|
TotalLines int `json:"total_lines"`
|
|
}
|
|
|
|
type RepoStats struct {
|
|
TotalCommits int `json:"total_commits"`
|
|
TotalBranches int `json:"total_branches"`
|
|
TotalTags int `json:"total_tags"`
|
|
TotalSize int64 `json:"total_size"`
|
|
Contributors []Contributor `json:"contributors"`
|
|
FirstCommit time.Time `json:"first_commit"`
|
|
LastCommit time.Time `json:"last_commit"`
|
|
}
|
|
|
|
func (r *Repository) GetContributors(ref string) ([]Contributor, error) {
|
|
|
|
if empty, err := r.IsEmpty(); err != nil {
|
|
return nil, err
|
|
} else if empty {
|
|
return []Contributor{}, nil
|
|
}
|
|
|
|
if ref == "" {
|
|
ref = "HEAD"
|
|
}
|
|
|
|
out, _, err := NewCommand("log").
|
|
Add("--format=----COMMIT%x1f%an%x1f%ae").
|
|
Add("--numstat").
|
|
AddDynamicArguments(ref).
|
|
WithDir(r.Path).
|
|
RunStdString(r.ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
byKey := map[string]*Contributor{}
|
|
var curAuthor, curEmail string
|
|
|
|
for _, line := range strings.Split(out, "\n") {
|
|
if strings.HasPrefix(line, "----COMMIT") {
|
|
fields := strings.Split(line, "\x1f")
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
curAuthor = fields[1]
|
|
curEmail = fields[2]
|
|
key := curAuthor + " <" + curEmail + ">"
|
|
if byKey[key] == nil {
|
|
byKey[key] = &Contributor{Author: curAuthor, Email: curEmail}
|
|
}
|
|
byKey[key].Commits++
|
|
continue
|
|
}
|
|
|
|
if curAuthor == "" {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, "\t", 3)
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
add, _ := strconv.Atoi(parts[0])
|
|
del, _ := strconv.Atoi(parts[1])
|
|
key := curAuthor + " <" + curEmail + ">"
|
|
byKey[key].Additions += add
|
|
byKey[key].Deletions += del
|
|
byKey[key].TotalLines += add + del
|
|
}
|
|
|
|
result := make([]Contributor, 0, len(byKey))
|
|
for _, c := range byKey {
|
|
result = append(result, *c)
|
|
}
|
|
sort.Slice(result, func(i, j int) bool {
|
|
if result[i].Commits != result[j].Commits {
|
|
return result[i].Commits > result[j].Commits
|
|
}
|
|
return result[i].TotalLines > result[j].TotalLines
|
|
})
|
|
return result, nil
|
|
}
|
|
|
|
func (r *Repository) GetStats() (*RepoStats, error) {
|
|
|
|
revOut, _, _ := NewCommand("rev-list", "--count", "HEAD").
|
|
WithDir(r.Path).
|
|
RunStdString(r.ctx)
|
|
totalCommits, _ := strconv.Atoi(strings.TrimSpace(revOut))
|
|
|
|
branchOut, _, _ := NewCommand("branch", "--list").
|
|
WithDir(r.Path).
|
|
RunStdString(r.ctx)
|
|
totalBranches := countNonEmptyLines(branchOut)
|
|
|
|
tagOut, _, _ := NewCommand("tag", "--list").
|
|
WithDir(r.Path).
|
|
RunStdString(r.ctx)
|
|
totalTags := countNonEmptyLines(tagOut)
|
|
|
|
firstOut, _, _ := NewCommand("log", "--reverse").
|
|
Add("--format=%aI").
|
|
Add("HEAD").
|
|
WithDir(r.Path).
|
|
RunStdString(r.ctx)
|
|
firstLine := strings.TrimSpace(firstOut)
|
|
if i := strings.IndexByte(firstLine, '\n'); i >= 0 {
|
|
firstLine = firstLine[:i]
|
|
}
|
|
firstCommit, _ := time.Parse(time.RFC3339, firstLine)
|
|
|
|
lastOut, _, _ := NewCommand("log").
|
|
Add("--format=%aI").
|
|
Add("-n", "1").
|
|
Add("HEAD").
|
|
WithDir(r.Path).
|
|
RunStdString(r.ctx)
|
|
lastCommit, _ := time.Parse(time.RFC3339, strings.TrimSpace(lastOut))
|
|
|
|
contributors, _ := r.GetContributors("HEAD")
|
|
|
|
var totalSize int64
|
|
if co, err := r.CountObjects(); err == nil {
|
|
totalSize = co.TotalSize()
|
|
}
|
|
|
|
return &RepoStats{
|
|
TotalCommits: totalCommits,
|
|
TotalBranches: totalBranches,
|
|
TotalTags: totalTags,
|
|
TotalSize: totalSize,
|
|
Contributors: contributors,
|
|
FirstCommit: firstCommit,
|
|
LastCommit: lastCommit,
|
|
}, nil
|
|
}
|
|
|
|
func countNonEmptyLines(s string) int {
|
|
count := 0
|
|
for _, line := range strings.Split(strings.TrimSpace(s), "\n") {
|
|
if line != "" {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|