package gitpb import ( "bytes" "crypto/sha1" "encoding/hex" "fmt" "os/exec" "strings" "go.wit.com/log" ) // forged patch: "working on ping pong" // The --stable flag is an important detail. When you use it, git patch-id outputs two hashes: // func (repo *Repo) FindPatchIdByHash(hash string) (string, error) { if hash == "" { return "", log.Errorf("commit hash blank") } // 1. Create the command to get the diff for the commit. // "git show" is the perfect tool for this. cmdShow := exec.Command("git", "show", hash) cmdShow.Dir = repo.GetFullPath() // 2. Create the command to calculate the patch-id from stdin. cmdPipeID := exec.Command("git", "patch-id", "--stable") cmdPipeID.Dir = repo.GetFullPath() // 3. Connect the output of "git show" to the input of "git patch-id". // This is the Go equivalent of the shell pipe `|`. pipe, err := cmdShow.StdoutPipe() if err != nil { return "", fmt.Errorf("failed to create pipe: %w", err) } cmdPipeID.Stdin = pipe // 4. We need a buffer to capture the final output from git patch-id. var output bytes.Buffer cmdPipeID.Stdout = &output // 5. Start the reading command (patch-id) first. if err := cmdPipeID.Start(); err != nil { return "", fmt.Errorf("failed to start git-patch-id: %w", err) } // 6. Run the writing command (show). This will block until it's done. if err := cmdShow.Run(); err != nil { return "", fmt.Errorf("failed to run git-show: %w", err) } // 7. Wait for the reading command to finish. if err := cmdPipeID.Wait(); err != nil { return "", fmt.Errorf("failed to wait for git-patch-id: %w", err) } fields := strings.Fields(output.String()) if len(fields) != 2 { return "", fmt.Errorf("git-patch-id produced empty output") } if fields[1] != hash { return "", fmt.Errorf("patchid did not match %s != %v", hash, fields) } return fields[0], nil } func (repo *Repo) FindPatchIdFromGitAm(gitam []byte) (string, string, error) { return FindPatchIdFromGitAm(gitam) } func FindPatchIdFromGitAm(gitam []byte) (string, string, error) { // 1. Create the command. cmd := exec.Command("git", "patch-id", "--stable") // 2. Set the command's Stdin to a reader created from our string. // This is the key change. cmd.Stdin = strings.NewReader(string(gitam)) // 3. We need buffers to capture the command's output. var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr // 4. Run the command and wait for it to finish. // cmd.Run() is a convenient wrapper around Start() and Wait(). err := cmd.Run() if err != nil { // Include stderr in the error message for better debugging. return "", "", fmt.Errorf("git patch-id command failed: %w\nStderr: %s", err, stderr.String()) } fields := strings.Fields(stdout.String()) if len(fields) != 2 { return "", "", fmt.Errorf("git-patch-id produced empty output") } return fields[0], fields[1], nil } // computes the stable git patch-id from the output of git-am // doesn't work. it needs to add the SHA-1 parts together // cat WTF.2.diff | git patch-id --stable // go.wit.com/apps/utils/forged // git show 5b277e7686974d2195586d5f5b82838ee9ddb036 |git patch-id --stable // bf86be06af03b1a89ee155b214358362ec76f7b6 5b277e7686974d2195586d5f5b82838ee9ddb036 // 73d73e12dcd727721253140ea68441dd0b824f8c 0000000000000000000000000000000000000000 // 515792a0c4965b69f6b9e7f89e2f896148b03c97 0000000000000000000000000000000000000000 // 1dd8b4a7d7d42a78fd1ff84dcc2ac87bc7318ee6 0000000000000000000000000000000000000000 // 8478bad3e1f97818a68a014a8f30f1b2951b026b 0000000000000000000000000000000000000000 func FindPatchIdFromGitAmBroken(gitAmData []byte) string { var lines []string scanner := NewLinesScanner(strings.Split(string(gitAmData), "\n")) // 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, "diff ") { inPatchBody = true } if !inPatchBody { continue } if line == "-- " { break } lines = append(lines, line) /* // Skip headers and metadata lines if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "+++") || strings.HasPrefix(line, "index") || strings.HasPrefix(line, "diff --git") { continue } normalizedPatch.WriteString(line + "\n") */ /* // 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. */ } // var fullhash [20]byte var final string for _, line := range lines { if strings.HasPrefix(line, "---") { // final += "---\n" final += line + "\n" continue } if strings.HasPrefix(line, "+++") { // final += "+++\n" final += line + "\n" continue } if strings.HasPrefix(line, "index") { // final += "index\n" // final += line + "\n" continue } if strings.HasPrefix(line, "@@") { // final += "@@\n" continue } if strings.HasPrefix(line, "diff --git") { if len(final) != 0 { hash := sha1.Sum([]byte(final)) // fullhash ?? hash patchId := hex.EncodeToString(hash[:]) log.Info("partial hash", patchId) } log.Printf("%s", final) // final += "diff --git\n" final = line + "\n" // final = "" continue } final += line + "\n" } // final = strings.TrimSuffix(final, "\n") // Calculate the SHA-1 hash of the normalized patch content // hash := sha1.Sum(normalizedPatch.Bytes()) hash := sha1.Sum([]byte(final)) patchID := hex.EncodeToString(hash[:]) // log.Printf("%s", final) // log.Info(strings.Join(lines, "\n")) return patchID }