forked from tangled.org/core
this repo has no description

patchutil: move patch-related shared code to top-level package

authored by anirudh.fi and committed by oppi.li b229a0ea 5fcd43c1

Changed files
+331
patchutil
+121
patchutil/patchutil.go
··· 1 + package patchutil 2 + 3 + import ( 4 + "fmt" 5 + "regexp" 6 + "strings" 7 + 8 + "github.com/bluekeyes/go-gitdiff/gitdiff" 9 + ) 10 + 11 + type FormatPatch struct { 12 + *gitdiff.PatchHeader 13 + Patch string 14 + } 15 + 16 + func 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 43 + func 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 + 66 + func 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 + 94 + func 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 + }
+210
patchutil/patchutil_test.go
··· 1 + package patchutil 2 + 3 + import ( 4 + "reflect" 5 + "testing" 6 + ) 7 + 8 + func TestSplitPatches(t *testing.T) { 9 + tests := []struct { 10 + name string 11 + input string 12 + expected []string 13 + }{ 14 + { 15 + name: "Empty input", 16 + input: "", 17 + expected: []string{}, 18 + }, 19 + { 20 + name: "No valid patches", 21 + input: "This is not a patch\nJust some random text", 22 + expected: []string{}, 23 + }, 24 + { 25 + name: "Single patch", 26 + input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 27 + From: Author <author@example.com> 28 + Date: Wed, 16 Apr 2025 11:01:00 +0300 29 + Subject: [PATCH] Example patch 30 + 31 + diff --git a/file.txt b/file.txt 32 + index 123456..789012 100644 33 + --- a/file.txt 34 + +++ b/file.txt 35 + @@ -1 +1 @@ 36 + -old content 37 + +new content 38 + -- 39 + 2.48.1`, 40 + expected: []string{ 41 + `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 42 + From: Author <author@example.com> 43 + Date: Wed, 16 Apr 2025 11:01:00 +0300 44 + Subject: [PATCH] Example patch 45 + 46 + diff --git a/file.txt b/file.txt 47 + index 123456..789012 100644 48 + --- a/file.txt 49 + +++ b/file.txt 50 + @@ -1 +1 @@ 51 + -old content 52 + +new content 53 + -- 54 + 2.48.1`, 55 + }, 56 + }, 57 + { 58 + name: "Two patches", 59 + input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 60 + From: Author <author@example.com> 61 + Date: Wed, 16 Apr 2025 11:01:00 +0300 62 + Subject: [PATCH 1/2] First patch 63 + 64 + diff --git a/file1.txt b/file1.txt 65 + index 123456..789012 100644 66 + --- a/file1.txt 67 + +++ b/file1.txt 68 + @@ -1 +1 @@ 69 + -old content 70 + +new content 71 + -- 72 + 2.48.1 73 + From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 74 + From: Author <author@example.com> 75 + Date: Wed, 16 Apr 2025 11:03:11 +0300 76 + Subject: [PATCH 2/2] Second patch 77 + 78 + diff --git a/file2.txt b/file2.txt 79 + index abcdef..ghijkl 100644 80 + --- a/file2.txt 81 + +++ b/file2.txt 82 + @@ -1 +1 @@ 83 + -foo bar 84 + +baz qux 85 + -- 86 + 2.48.1`, 87 + expected: []string{ 88 + `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 89 + From: Author <author@example.com> 90 + Date: Wed, 16 Apr 2025 11:01:00 +0300 91 + Subject: [PATCH 1/2] First patch 92 + 93 + diff --git a/file1.txt b/file1.txt 94 + index 123456..789012 100644 95 + --- a/file1.txt 96 + +++ b/file1.txt 97 + @@ -1 +1 @@ 98 + -old content 99 + +new content 100 + -- 101 + 2.48.1`, 102 + `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 103 + From: Author <author@example.com> 104 + Date: Wed, 16 Apr 2025 11:03:11 +0300 105 + Subject: [PATCH 2/2] Second patch 106 + 107 + diff --git a/file2.txt b/file2.txt 108 + index abcdef..ghijkl 100644 109 + --- a/file2.txt 110 + +++ b/file2.txt 111 + @@ -1 +1 @@ 112 + -foo bar 113 + +baz qux 114 + -- 115 + 2.48.1`, 116 + }, 117 + }, 118 + { 119 + name: "Patches with additional text between them", 120 + input: `Some text before the patches 121 + 122 + From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 123 + From: Author <author@example.com> 124 + Subject: [PATCH] First patch 125 + 126 + diff content here 127 + -- 128 + 2.48.1 129 + 130 + Some text between patches 131 + 132 + From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 133 + From: Author <author@example.com> 134 + Subject: [PATCH] Second patch 135 + 136 + more diff content 137 + -- 138 + 2.48.1 139 + 140 + Text after patches`, 141 + expected: []string{ 142 + `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 143 + From: Author <author@example.com> 144 + Subject: [PATCH] First patch 145 + 146 + diff content here 147 + -- 148 + 2.48.1 149 + 150 + Some text between patches`, 151 + `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 152 + From: Author <author@example.com> 153 + Subject: [PATCH] Second patch 154 + 155 + more diff content 156 + -- 157 + 2.48.1 158 + 159 + Text after patches`, 160 + }, 161 + }, 162 + { 163 + name: "Patches with whitespace padding", 164 + input: ` 165 + 166 + From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 167 + From: Author <author@example.com> 168 + Subject: Patch 169 + 170 + content 171 + -- 172 + 2.48.1 173 + 174 + 175 + From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 176 + From: Author <author@example.com> 177 + Subject: Another patch 178 + 179 + content 180 + -- 181 + 2.48.1 182 + `, 183 + expected: []string{ 184 + `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001 185 + From: Author <author@example.com> 186 + Subject: Patch 187 + 188 + content 189 + -- 190 + 2.48.1`, 191 + `From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001 192 + From: Author <author@example.com> 193 + Subject: Another patch 194 + 195 + content 196 + -- 197 + 2.48.1`, 198 + }, 199 + }, 200 + } 201 + 202 + for _, tt := range tests { 203 + t.Run(tt.name, func(t *testing.T) { 204 + result := splitFormatPatch(tt.input) 205 + if !reflect.DeepEqual(result, tt.expected) { 206 + t.Errorf("splitPatches() = %v, want %v", result, tt.expected) 207 + } 208 + }) 209 + } 210 + }