196 lines
4.6 KiB
Go
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
|
|
}
|