- commit
- caaef17
- parent
- 02823e3
- author
- Eric Bower
- date
- 2025-08-28 09:51:02 -0400 EDT
feat: range-diff tool This tool enables anyone to quick paste their patches and have us perform a range-diff on them
4 files changed,
+141,
-4
+1,
-0
1@@ -15,3 +15,4 @@ __debug_bin
2 /public/
3 test.db
4 review.patch
5+.aider*
+6,
-4
1@@ -58,10 +58,11 @@
2 {{range .Files}}
3 <div class="flex gap">
4 <div class="flex-1" style="width: 48%;">
5+ <h3 class="text-md">old</h3>
6 <div>
7 {{if .OldFile}}
8- {{if .OldFile.OldName}}<code>{{.OldFile.OldName}}</code>{{end}}
9- {{if .OldFile.NewName}}<code>{{.OldFile.NewName}}</code>{{end}}
10+ {{if .OldFile.OldName}}old:<code>{{.OldFile.OldName}}</code>{{end}}
11+ {{if .OldFile.NewName}}new:<code>{{.OldFile.NewName}}</code>{{end}}
12 {{end}}
13 </div>
14 <pre class="m-0">{{- range .Diff -}}
15@@ -79,9 +80,10 @@
16 </div>
17
18 <div class="flex-1" style="width: 48%;">
19+ <h3 class="text-md">new</h3>
20 <div>
21- {{if .NewFile.OldName}}<code>{{.NewFile.OldName}}</code>{{end}}
22- {{if .NewFile.NewName}}<code>{{.NewFile.NewName}}</code>{{end}}
23+ {{if .NewFile.OldName}}old:<code>{{.NewFile.OldName}}</code>{{end}}
24+ {{if .NewFile.NewName}}new:<code>{{.NewFile.NewName}}</code>{{end}}
25 </div>
26 <pre class="m-0">{{- range .Diff -}}
27 {{- if eq .OuterType "insert" -}}
+53,
-0
1@@ -0,0 +1,53 @@
2+{{template "base" .}}
3+
4+{{define "title"}}range-diff tool{{end}}
5+
6+{{define "meta"}}
7+<meta property="og:title" content="range-diff tool" />
8+<meta property="og:url" content="https://{{.MetaData.URL}}/tool" />
9+<meta property="og:type" content="object" />
10+<meta property="og:site_name" content="{{.MetaData.URL}}" />
11+{{end}}
12+
13+{{define "body"}}
14+ {{if .PatchsetData.RangeDiff}}
15+ <div class="p-1">
16+ {{template "range-diff" .}}
17+ </div>
18+ {{else}}
19+ <main class="flex justify-center">
20+ <div class="container mt">
21+ <div>
22+ <h1 class="text-xl">range-diff</h1>
23+ <div>
24+ A tool to compare two commit ranges (e.g. two versions of a branch) by cross-referencing similar commits and then diff'ing them.
25+ This means the old patches and the new patches do not need to be in the same order.
26+ You can read the <a href="https://git-scm.com/docs/git-range-diff">git <code>range-diff</code> docs</a> to learn more about it.
27+ </div>
28+ <div class="mb">
29+ In order to use this tool, you need to print both versions of a branch by running <a href="https://git-scm.com/docs/git-format-patch">git <code>format-patch</code></a>.
30+ </div>
31+ <pre>git switch branch-1
32+git format-patch --stdout origin/main # paste into old box
33+git switch branch-2
34+git format-patch --stdout origin/main # paste into new box</pre>
35+ </div>
36+
37+ <form method="post" action="/tool" style="text-align: right;">
38+ <div class="flex gap items-center">
39+ <div class="flex-1" >
40+ <h2 class="text-lg">old patchset</h2>
41+ <textarea name="prev_patchset" style="width: 100%; height: 100vh; max-height: 500px;"></textarea>
42+ </div>
43+ <div>>></div>
44+ <div class="flex-1" >
45+ <h2 class="text-lg">new patchset</h2>
46+ <textarea name="next_patchset" style="width: 100%; height: 100vh; max-height: 500px;"></textarea>
47+ </div>
48+ </div>
49+ <button type="submit" class="btn mt" style="background-color: transparent;">submit</button>
50+ </form>
51+ </div>
52+ </main>
53+ {{end}}
54+{{end}}
M
web.go
+81,
-0
1@@ -34,6 +34,7 @@ var (
2 prTmpl = getTemplate("pr.html")
3 userTmpl = getTemplate("user.html")
4 repoTmpl = getTemplate("repo.html")
5+ toolTmpl = getTemplate("tool.html")
6 )
7
8 func getTemplate(page string) *template.Template {
9@@ -567,6 +568,12 @@ type PrDetailData struct {
10 MetaData
11 }
12
13+type ToolData struct {
14+ Patchset *Patchset
15+ PatchsetData *PatchsetData
16+ MetaData
17+}
18+
19 func createPrDetail(page string) http.HandlerFunc {
20 return func(w http.ResponseWriter, r *http.Request) {
21 id := r.PathValue("id")
22@@ -873,6 +880,78 @@ func createPrDetail(page string) http.HandlerFunc {
23 }
24 }
25
26+func toolHandlerGet(w http.ResponseWriter, r *http.Request) {
27+ web, err := getWebCtx(r)
28+ if err != nil {
29+ w.WriteHeader(http.StatusUnprocessableEntity)
30+ return
31+ }
32+
33+ err = toolTmpl.Execute(w, ToolData{
34+ MetaData: MetaData{
35+ URL: web.Backend.Cfg.Url,
36+ },
37+ Patchset: &Patchset{
38+ ID: 0,
39+ },
40+ PatchsetData: &PatchsetData{
41+ RangeDiff: []*RangeDiffOutput{},
42+ },
43+ })
44+ if err != nil {
45+ web.Backend.Logger.Error("cannot execute template", "err", err)
46+ }
47+}
48+
49+func toolHandlerPost(w http.ResponseWriter, r *http.Request) {
50+ web, err := getWebCtx(r)
51+ if err != nil {
52+ web.Backend.Logger.Error("web ctx not found", "err", err)
53+ w.WriteHeader(http.StatusUnprocessableEntity)
54+ return
55+ }
56+
57+ if err := r.ParseForm(); err != nil {
58+ web.Backend.Logger.Error("parse form", "err", err)
59+ http.Error(w, "Failed to parse form", http.StatusBadRequest)
60+ return
61+ }
62+
63+ prevPs := r.PostFormValue("prev_patchset")
64+ prevPs = strings.ReplaceAll(prevPs, "\r", "")
65+ nextPs := r.PostFormValue("next_patchset")
66+ nextPs = strings.ReplaceAll(nextPs, "\r", "")
67+
68+ prevPatchset, err := ParsePatchset(strings.NewReader(prevPs))
69+ if err != nil {
70+ web.Backend.Logger.Error("parse prev patchset", "err", err)
71+ w.WriteHeader(http.StatusUnprocessableEntity)
72+ return
73+ }
74+ nextPatchset, err := ParsePatchset(strings.NewReader(nextPs))
75+ if err != nil {
76+ web.Backend.Logger.Error("parse next patchset", "err", err)
77+ w.WriteHeader(http.StatusUnprocessableEntity)
78+ return
79+ }
80+ rangeDiff := RangeDiff(prevPatchset, nextPatchset)
81+
82+ err = toolTmpl.Execute(w, ToolData{
83+ MetaData: MetaData{
84+ URL: web.Backend.Cfg.Url,
85+ },
86+ Patchset: &Patchset{
87+ ID: 0,
88+ },
89+ PatchsetData: &PatchsetData{
90+ RangeDiff: rangeDiff,
91+ },
92+ })
93+ if err != nil {
94+ web.Backend.Logger.Error("cannot execute template", "err", err)
95+ }
96+}
97+
98 func rssHandler(w http.ResponseWriter, r *http.Request) {
99 web, err := getWebCtx(r)
100 if err != nil {
101@@ -1127,6 +1206,8 @@ func GitWebServer(cfg *GitCfg) http.Handler {
102 mux.HandleFunc("GET /r/{user}", ctxMdw(ctx, userDetailHandler))
103 mux.HandleFunc("GET /rss/{user}", ctxMdw(ctx, rssHandler))
104 mux.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler))
105+ mux.HandleFunc("GET /tool", ctxMdw(ctx, toolHandlerGet))
106+ mux.HandleFunc("POST /tool", ctxMdw(ctx, toolHandlerPost))
107 mux.HandleFunc("GET /", ctxMdw(ctx, indexHandler))
108 mux.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler))
109 embedFS, err := getEmbedFS(embedStaticFS, "static")