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

196 lines
4.6 KiB
Go

package gitcmd
import (
"regexp"
"strconv"
"strings"
)
type DiffLineType string
const (
DiffLineContext DiffLineType = "context"
DiffLineAdd DiffLineType = "add"
DiffLineDelete DiffLineType = "delete"
)
type DiffLine struct {
Type DiffLineType `json:"type"`
Text string `json:"text"`
}
type Hunk struct {
OldStart int `json:"old_start"`
OldCount int `json:"old_count"`
NewStart int `json:"new_start"`
NewCount int `json:"new_count"`
Lines []DiffLine `json:"lines"`
}
type FileChange struct {
Path string `json:"path"`
OldPath string `json:"old_path"`
Status string `json:"status"`
IsBinary bool `json:"is_binary"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Hunks []Hunk `json:"hunks"`
}
type DiffResult struct {
Files []FileChange `json:"files"`
}
var hunkHeaderRe = regexp.MustCompile(`^@+ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @+`)
func (r *Repository) Diff(a, b string, paths []string) (*DiffResult, error) {
cmd := NewCommand("diff", "--unified=3", "--no-color", "--no-prefix").
AddDynamicArguments(a, b).
WithDir(r.Path)
if len(paths) > 0 {
cmd.AddDashesAndList(paths...)
}
out, _, err := cmd.RunStdString(r.ctx)
if err != nil {
return nil, err
}
return parseDiff(out), nil
}
func (r *Repository) CommitDiff(sha string) (*DiffResult, error) {
out, _, err := NewCommand("show", "--unified=3", "--no-color", "--no-prefix", "--format=").
AddDynamicArguments(sha).
WithDir(r.Path).
RunStdString(r.ctx)
if err != nil {
return nil, err
}
return parseDiff(out), nil
}
func parseDiff(out string) *DiffResult {
res := &DiffResult{}
if out == "" {
return res
}
var cur *FileChange
var curHunk *Hunk
finishHunk := func() {
if cur != nil && curHunk != nil {
cur.Hunks = append(cur.Hunks, *curHunk)
curHunk = nil
}
}
finishFile := func() {
finishHunk()
if cur != nil {
if cur.Status == "" {
cur.Status = "M"
}
if cur.Status != "R" {
cur.OldPath = ""
}
res.Files = append(res.Files, *cur)
cur = nil
}
}
for _, line := range strings.Split(out, "\n") {
switch {
case strings.HasPrefix(line, "diff --git "):
finishFile()
cur = &FileChange{}
rest := strings.TrimPrefix(line, "diff --git ")
if parts := strings.SplitN(rest, " ", 2); len(parts) == 2 {
cur.OldPath, cur.Path = parts[0], parts[1]
} else {
cur.Path = rest
}
case cur == nil:
continue
case strings.HasPrefix(line, "new file mode"):
cur.Status = "A"
case strings.HasPrefix(line, "deleted file mode"):
cur.Status = "D"
case strings.HasPrefix(line, "rename from "):
cur.OldPath = strings.TrimPrefix(line, "rename from ")
cur.Status = "R"
case strings.HasPrefix(line, "rename to "):
cur.Path = strings.TrimPrefix(line, "rename to ")
cur.Status = "R"
case strings.HasPrefix(line, "copy from "):
cur.OldPath = strings.TrimPrefix(line, "copy from ")
cur.Status = "R"
case strings.HasPrefix(line, "copy to "):
cur.Path = strings.TrimPrefix(line, "copy to ")
cur.Status = "R"
case strings.HasPrefix(line, "similarity index"):
if cur.Status == "" {
cur.Status = "R"
}
case strings.HasPrefix(line, "Binary files "):
cur.IsBinary = true
case strings.HasPrefix(line, "--- "):
p := strings.TrimPrefix(line, "--- ")
if p != "/dev/null" {
cur.OldPath = p
}
if p == "/dev/null" && cur.Status == "" {
cur.Status = "A"
}
case strings.HasPrefix(line, "+++ "):
p := strings.TrimPrefix(line, "+++ ")
if p != "/dev/null" {
cur.Path = p
}
if p == "/dev/null" && cur.Status == "" {
cur.Status = "D"
}
case hunkHeaderRe.MatchString(line):
finishHunk()
m := hunkHeaderRe.FindStringSubmatch(line)
os, _ := strconv.Atoi(m[1])
oc := 1
if m[2] != "" {
oc, _ = strconv.Atoi(m[2])
}
ns, _ := strconv.Atoi(m[3])
nc := 1
if m[4] != "" {
nc, _ = strconv.Atoi(m[4])
}
curHunk = &Hunk{OldStart: os, OldCount: oc, NewStart: ns, NewCount: nc}
case strings.HasPrefix(line, "\\ No newline"):
default:
if curHunk == nil {
continue
}
if line == "" {
continue
}
switch line[0] {
case ' ':
curHunk.Lines = append(curHunk.Lines, DiffLine{Type: DiffLineContext, Text: line[1:]})
case '+':
curHunk.Lines = append(curHunk.Lines, DiffLine{Type: DiffLineAdd, Text: line[1:]})
cur.Additions++
case '-':
curHunk.Lines = append(curHunk.Lines, DiffLine{Type: DiffLineDelete, Text: line[1:]})
cur.Deletions++
default:
curHunk.Lines = append(curHunk.Lines, DiffLine{Type: DiffLineContext, Text: line})
}
}
}
finishFile()
return res
}