summaryrefslogtreecommitdiff
path: root/patchid.go
diff options
context:
space:
mode:
Diffstat (limited to 'patchid.go')
-rw-r--r--patchid.go135
1 files changed, 131 insertions, 4 deletions
diff --git a/patchid.go b/patchid.go
index 28c8313..72761a9 100644
--- a/patchid.go
+++ b/patchid.go
@@ -1,18 +1,18 @@
package gitpb
import (
+ "bufio"
"bytes"
- "errors"
+ "crypto/sha1"
+ "encoding/hex"
"fmt"
- "os"
+ "io"
"os/exec"
- "path/filepath"
"strings"
"go.wit.com/log"
)
-
// git show 5b277e7686974d2195586d5f5b82838ee9ddb036 |git patch-id --stable
// bf86be06af03b1a89ee155b214358362ec76f7b6 5b277e7686974d2195586d5f5b82838ee9ddb036
// forged patch: "working on ping pong"
@@ -70,3 +70,130 @@ func (repo *Repo) FindPatchIdByHash(hash string) (string, error) {
return fields[0], nil
}
+
+// ComputePatchID calculates the patch ID for a given patch file's content.
+// It mimics the behavior of the `git patch-id` command by normalizing the
+// patch content before hashing.
+//
+// Normalization rules:
+// 1. It ignores all lines until the first "--- " line is encountered.
+// 2. It ignores diff headers (lines starting with "---", "+++", "diff --git", "index").
+// 3. For content lines ('+', '-', or ' '), it keeps the first character and
+// trims any trailing whitespace from the rest of the line before hashing.
+// 4. All other lines (like hunk headers "@@ ... @@") are ignored.
+func FindPatchIdByBytes(patchData io.Reader) (string, error) {
+ scanner := bufio.NewScanner(patchData)
+ var normalizedPatch bytes.Buffer
+ var inPatchBody bool
+
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ // The patch content officially starts at the first `---` line.
+ // This helps skip email headers in patches generated by `git format-patch`.
+ if !inPatchBody && strings.HasPrefix(line, "---") {
+ inPatchBody = true
+ }
+
+ if !inPatchBody {
+ continue
+ }
+
+ // Skip headers and metadata lines
+ if strings.HasPrefix(line, "---") ||
+ strings.HasPrefix(line, "+++") ||
+ strings.HasPrefix(line, "index") ||
+ strings.HasPrefix(line, "diff --git") {
+ continue
+ }
+
+ // Process only the actual content lines (context, addition, deletion)
+ if len(line) > 0 && (line[0] == ' ' || line[0] == '+' || line[0] == '-') {
+ // Keep the first character (' ', '+', or '-')
+ firstChar := line[0]
+ content := line[1:]
+
+ // Trim trailing whitespace from the content part of the line
+ trimmedContent := strings.TrimRight(content, " ")
+
+ // Write the normalized line to our buffer for hashing
+ normalizedPatch.WriteByte(firstChar)
+ normalizedPatch.WriteString(trimmedContent)
+ normalizedPatch.WriteByte('\n')
+ }
+ // All other lines (e.g., "@@ ... @@") are ignored.
+ }
+
+ if err := scanner.Err(); err != nil {
+ return "", fmt.Errorf("error reading patch data: %w", err)
+ }
+
+ // Calculate the SHA-1 hash of the normalized patch content
+ hash := sha1.Sum(normalizedPatch.Bytes())
+ patchID := hex.EncodeToString(hash[:])
+
+ return patchID, nil
+}
+
+/*
+// --- Example Usage ---
+
+func main() {
+ // Example 1: Using a string as input
+ patchString := `
+From 2f42795678c7113f7437c04f56570c08b68850f4 Mon Sep 17 00:00:00 2001
+From: A Developer <[email protected]>
+Subject: [PATCH] Example change to demonstrate patch-id
+
+--- a/README.md
++++ b/README.md
+@@ -1,3 +1,4 @@
+ # My Project
+
+-This is a sample project.
++This is a sample project.
++It has an additional line.
+`
+ stringReader := strings.NewReader(patchString)
+ patchID, err := ComputePatchID(stringReader)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error computing patch ID from string: %v\n", err)
+ return
+ }
+ fmt.Printf("Patch ID from string: %s\n", patchID)
+
+ // Example 2: Reading from a file
+ // Create a temporary patch file for demonstration
+ patchContent := []byte(patchString)
+ tmpfile, err := os.CreateTemp("", "example.*.patch")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating temp file: %v\n", err)
+ return
+ }
+ defer os.Remove(tmpfile.Name()) // clean up
+
+ if _, err := tmpfile.Write(patchContent); err != nil {
+ fmt.Fprintf(os.Stderr, "Error writing to temp file: %v\n", err)
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error closing temp file: %v\n", err)
+ return
+ }
+
+ // Open the file to pass to our function
+ file, err := os.Open(tmpfile.Name())
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error opening temp file: %v\n", err)
+ return
+ }
+ defer file.Close()
+
+ patchIDFromFile, err := ComputePatchID(file)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error computing patch ID from file: %v\n", err)
+ return
+ }
+ fmt.Printf("Patch ID from file: %s\n", patchIDFromFile)
+}
+*/