1package patchutil
2
3import (
4 "fmt"
5 "regexp"
6 "strings"
7
8 "github.com/bluekeyes/go-gitdiff/gitdiff"
9)
10
11type FormatPatch struct {
12 Files []*gitdiff.File
13 *gitdiff.PatchHeader
14}
15
16func ExtractPatches(formatPatch string) ([]FormatPatch, error) {
17 patches := splitFormatPatch(formatPatch)
18
19 result := []FormatPatch{}
20
21 for _, patch := range patches {
22 files, headerStr, err := gitdiff.Parse(strings.NewReader(patch))
23 if err != nil {
24 return nil, fmt.Errorf("failed to parse patch: %w", err)
25 }
26
27 header, err := gitdiff.ParsePatchHeader(headerStr)
28 if err != nil {
29 return nil, fmt.Errorf("failed to parse patch header: %w", err)
30 }
31
32 result = append(result, FormatPatch{
33 Files: files,
34 PatchHeader: header,
35 })
36 }
37
38 return result, nil
39}
40
41// IsPatchValid checks if the given patch string is valid.
42// It performs very basic sniffing for either git-diff or git-format-patch
43// header lines.
44func IsPatchValid(patch string) bool {
45 if len(patch) == 0 {
46 return false
47 }
48
49 lines := strings.Split(patch, "\n")
50 if len(lines) < 2 {
51 return false
52 }
53
54 firstLine := strings.TrimSpace(lines[0])
55 return strings.HasPrefix(firstLine, "diff ") ||
56 strings.HasPrefix(firstLine, "--- ") ||
57 strings.HasPrefix(firstLine, "Index: ") ||
58 strings.HasPrefix(firstLine, "+++ ") ||
59 strings.HasPrefix(firstLine, "@@ ") ||
60 strings.HasPrefix(firstLine, "From ") && strings.Contains(firstLine, " Mon Sep 17 00:00:00 2001") ||
61 strings.HasPrefix(firstLine, "From: ")
62}
63
64func IsFormatPatch(patch string) bool {
65 lines := strings.Split(patch, "\n")
66 if len(lines) < 2 {
67 return false
68 }
69
70 firstLine := strings.TrimSpace(lines[0])
71 if strings.HasPrefix(firstLine, "From ") && strings.Contains(firstLine, " Mon Sep 17 00:00:00 2001") {
72 return true
73 }
74
75 headerCount := 0
76 for i := range min(10, len(lines)) {
77 line := strings.TrimSpace(lines[i])
78 if strings.HasPrefix(line, "From: ") ||
79 strings.HasPrefix(line, "Date: ") ||
80 strings.HasPrefix(line, "Subject: ") ||
81 strings.HasPrefix(line, "commit ") {
82 headerCount++
83 }
84 if strings.HasPrefix(line, "diff --git ") {
85 return true
86 }
87 }
88
89 return headerCount >= 2
90}
91
92func splitFormatPatch(patchText string) []string {
93 re := regexp.MustCompile(`(?m)^From [0-9a-f]{40} .*$`)
94
95 indexes := re.FindAllStringIndex(patchText, -1)
96
97 if len(indexes) == 0 {
98 return []string{}
99 }
100
101 patches := make([]string, len(indexes))
102
103 for i := range indexes {
104 startPos := indexes[i][0]
105 endPos := len(patchText)
106
107 if i < len(indexes)-1 {
108 endPos = indexes[i+1][0]
109 }
110
111 patches[i] = strings.TrimSpace(patchText[startPos:endPos])
112 }
113 return patches
114}