summaryrefslogtreecommitdiff
path: root/patchid.go
blob: 80f2cdeeeee7de1cc7f23c793aae25cb7e27d8a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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:
// <stable_patch_id> <unstable_patch_id>
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
}