Files
simplegit/gitcmd/commit.go
T
2026-07-15 11:06:48 -04:00

163 lines
3.4 KiB
Go

package gitcmd
import (
"bytes"
"strconv"
"strings"
"time"
)
type Commit struct {
ID string `json:"id"`
ShortSHA string `json:"short_sha"`
Message string `json:"message"`
Author string `json:"author"`
AuthorEmail string `json:"author_email"`
When time.Time `json:"when"`
Parents []string `json:"parents"`
}
type CommitDetail struct {
Commit
Stats DiffStats `json:"stats"`
Files []FileDiff `json:"files"`
}
type DiffStats struct {
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Total int `json:"total"`
Files int `json:"files"`
}
type FileDiff struct {
Path string `json:"path"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Status string `json:"status"`
}
func (r *Repository) GetCommit(ref string) (*Commit, error) {
out, _, err := NewCommand("show", "-s").
Add("--format=%H%x1f%h%x1f%s%x1f%an%x1f%ae%x1f%aI%x1f%P").
AddDynamicArguments(ref).
WithDir(r.Path).
RunStdString(r.ctx)
if err != nil {
return nil, err
}
return parseCommitLine(strings.TrimSpace(out)), nil
}
func (r *Repository) ListCommits(ref string, limit int) ([]Commit, error) {
if empty, err := r.IsEmpty(); err != nil {
return nil, err
} else if empty {
return []Commit{}, nil
}
cmd := NewCommand("log").
Add("--format=%H%x1f%h%x1f%s%x1f%an%x1f%ae%x1f%aI%x1f%P").
WithDir(r.Path)
if limit > 0 {
cmd.Add("-n", strconv.Itoa(limit))
}
cmd.AddDynamicArguments(ref)
out, _, err := cmd.RunStdString(r.ctx)
if err != nil {
return nil, err
}
var commits []Commit
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
if line == "" {
continue
}
commits = append(commits, *parseCommitLine(line))
}
return commits, nil
}
func (r *Repository) GetCommitDetail(sha string) (*CommitDetail, error) {
commit, err := r.GetCommit(sha)
if err != nil {
return nil, err
}
out, _, err := NewCommand("show", "--numstat", "--format=").
AddDynamicArguments(sha).
WithDir(r.Path).
RunStdString(r.ctx)
if err != nil {
return nil, err
}
var files []FileDiff
var totalAdd, totalDel int
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
if line == "" {
continue
}
parts := strings.SplitN(line, "\t", 3)
if len(parts) < 3 {
continue
}
add, _ := strconv.Atoi(parts[0])
del, _ := strconv.Atoi(parts[1])
path := parts[2]
status := "M"
if add == 0 && del > 0 {
status = "D"
} else if del == 0 && add > 0 {
status = "A"
}
files = append(files, FileDiff{
Path: path,
Additions: add,
Deletions: del,
Status: status,
})
totalAdd += add
totalDel += del
}
return &CommitDetail{
Commit: *commit,
Stats: DiffStats{
Additions: totalAdd,
Deletions: totalDel,
Total: totalAdd + totalDel,
Files: len(files),
},
Files: files,
}, nil
}
func parseCommitLine(line string) *Commit {
fields := bytes.SplitN([]byte(line), []byte{0x1f}, 7)
if len(fields) < 6 {
return &Commit{Message: line}
}
when, _ := time.Parse(time.RFC3339, string(fields[5]))
parents := []string{}
if len(fields) >= 7 && len(fields[6]) > 0 {
parents = strings.Fields(string(fields[6]))
}
return &Commit{
ID: string(fields[0]),
ShortSHA: string(fields[1]),
Message: string(fields[2]),
Author: string(fields[3]),
AuthorEmail: string(fields[4]),
When: when,
Parents: parents,
}
}