1package patchutil
2
3import (
4 "fmt"
5 "regexp"
6 "strings"
7
8 "github.com/bluekeyes/go-gitdiff/gitdiff"
9)
10
11type FormatPatch struct {
12 *gitdiff.PatchHeader
13 Patch string
14}
15
16func ExtractPatches(formatPatch string) ([]FormatPatch, error) {
17 patches := splitFormatPatch(formatPatch)
18
19 result := []FormatPatch{}
20
21 for _, patch := range patches {
22 _, 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 PatchHeader: header,
34 Patch: patch,
35 })
36 }
37
38 return result, nil
39}
40
41// Very basic validation to check if it looks like a diff/patch
42// A valid patch usually starts with diff or --- lines or git format-patch header
43func IsPatchValid(patch string) bool {
44 // Basic validation to check if it looks like a diff/patch
45 // A valid patch usually starts with diff or --- lines
46 if len(patch) == 0 {
47 return false
48 }
49
50 lines := strings.Split(patch, "\n")
51 if len(lines) < 2 {
52 return false
53 }
54
55 // Check for common patch format markers
56 firstLine := strings.TrimSpace(lines[0])
57 return strings.HasPrefix(firstLine, "diff ") ||
58 strings.HasPrefix(firstLine, "--- ") ||
59 strings.HasPrefix(firstLine, "Index: ") ||
60 strings.HasPrefix(firstLine, "+++ ") ||
61 strings.HasPrefix(firstLine, "@@ ") ||
62 strings.HasPrefix(firstLine, "From ") && strings.Contains(firstLine, " Mon Sep 17 00:00:00 2001") ||
63 strings.HasPrefix(firstLine, "From: ")
64}
65
66func IsFormatPatch(patch string) bool {
67 lines := strings.Split(patch, "\n")
68 if len(lines) < 2 {
69 return false
70 }
71
72 firstLine := strings.TrimSpace(lines[0])
73 if strings.HasPrefix(firstLine, "From ") && strings.Contains(firstLine, " Mon Sep 17 00:00:00 2001") {
74 return true
75 }
76
77 headerCount := 0
78 for i := range min(10, len(lines)) {
79 line := strings.TrimSpace(lines[i])
80 if strings.HasPrefix(line, "From: ") ||
81 strings.HasPrefix(line, "Date: ") ||
82 strings.HasPrefix(line, "Subject: ") ||
83 strings.HasPrefix(line, "commit ") {
84 headerCount++
85 }
86 if strings.HasPrefix(line, "diff --git ") {
87 return true
88 }
89 }
90
91 return headerCount >= 2
92}
93
94func splitFormatPatch(patchText string) []string {
95 // The pattern to match is "From " followed by a commit hash and the rest of that line
96 re := regexp.MustCompile(`(?m)^From [0-9a-f]{40} .*$`)
97
98 // Find all starting positions of patches
99 indexes := re.FindAllStringIndex(patchText, -1)
100
101 if len(indexes) == 0 {
102 // No patches found
103 return []string{}
104 }
105
106 patches := make([]string, len(indexes))
107
108 for i := range indexes {
109 startPos := indexes[i][0]
110 endPos := len(patchText)
111
112 // If there's a next patch, set end position to the start of the next patch
113 if i < len(indexes)-1 {
114 endPos = indexes[i+1][0]
115 }
116
117 // Extract the patch and trim any whitespace
118 patches[i] = strings.TrimSpace(patchText[startPos:endPos])
119 }
120 return patches
121}