From 2342d2c652d2fbecb54d77c5e8d5b1634682f8a6 Mon Sep 17 00:00:00 2001 From: Amolith Date: Fri, 17 Oct 2025 10:09:32 -0600 Subject: [PATCH] feat(web): add .patch and .diff for commit URLs Add route handlers to serve raw git patches and diffs when accessing commit URLs with .patch or .diff extensions. This allows users to download patches directly using curl and pipe them to git apply. Implements: bug-d552262 Co-Authored-By: Crush --- git/repo.go | 14 +++++++++++ pkg/web/webui.go | 6 +++++ pkg/web/webui_commit.go | 54 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/git/repo.go b/git/repo.go index 382f07ee0df1b9d0e2df26940ec37b8cf4b1a4e4..574284ac13a9f4f527d40ca753f5b75eb154dfbe 100644 --- a/git/repo.go +++ b/git/repo.go @@ -1,6 +1,7 @@ package git import ( + "io" "path/filepath" "strings" @@ -160,6 +161,19 @@ func (r *Repository) Patch(commit *Commit) (string, error) { return diff.Patch(), err } +// RawDiffFormat represents the format type for raw diffs. +type RawDiffFormat = git.RawDiffFormat + +const ( + RawDiffNormal RawDiffFormat = git.RawDiffNormal + RawDiffPatch RawDiffFormat = git.RawDiffPatch +) + +// RawDiff writes raw diff output to the provided writer. +func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, opts ...git.RawDiffOptions) error { + return r.Repository.RawDiff(rev, diffType, w, opts...) +} + // CountCommits returns the number of commits in the repository. func (r *Repository) CountCommits(ref *Reference) (int64, error) { return r.RevListCount([]string{ref.Name().String()}) diff --git a/pkg/web/webui.go b/pkg/web/webui.go index 742256810f676792ac1fe0bede2984b1ef56f6ab..e9653a8baa34603c4f47da946391503c77473298 100644 --- a/pkg/web/webui.go +++ b/pkg/web/webui.go @@ -278,6 +278,12 @@ func WebUIController(ctx context.Context, r *mux.Router) { r.Handle(basePrefix+"/commit/{hash:[0-9a-f]+}", withRepoVars(withWebUIAccess(http.HandlerFunc(repoCommit)))). Methods(http.MethodGet) + // Commit patch and diff routes + r.Handle(basePrefix+"/commit/{hash:[0-9a-f]+}.patch", withRepoVars(withWebUIAccess(http.HandlerFunc(repoCommitPatch)))). + Methods(http.MethodGet) + r.Handle(basePrefix+"/commit/{hash:[0-9a-f]+}.diff", withRepoVars(withWebUIAccess(http.HandlerFunc(repoCommitDiff)))). + Methods(http.MethodGet) + // Branches route r.Handle(basePrefix+"/branches", withRepoVars(withWebUIAccess(http.HandlerFunc(repoBranches)))). Methods(http.MethodGet) diff --git a/pkg/web/webui_commit.go b/pkg/web/webui_commit.go index 985bcbd4478ba223a070bc0096754842862ce0e7..869d759de13d730ca54aebae737e463aa035de5c 100644 --- a/pkg/web/webui_commit.go +++ b/pkg/web/webui_commit.go @@ -73,3 +73,57 @@ func repoCommit(w http.ResponseWriter, r *http.Request) { renderHTML(w, "commit.html", data) } + +func repoCommitPatch(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := log.FromContext(ctx) + repo := proto.RepositoryFromContext(ctx) + vars := mux.Vars(r) + hash := vars["hash"] + + gr, err := openRepository(repo) + if err != nil { + logger.Debug("failed to open repository", "repo", repo.Name(), "err", err) + renderInternalServerError(w, r) + return + } + + _, err = gr.CatFileCommit(hash) + if err != nil { + logger.Debug("failed to get commit", "repo", repo.Name(), "hash", hash, "err", err) + renderNotFound(w, r) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + if err := gr.RawDiff(hash, git.RawDiffPatch, w); err != nil { + logger.Debug("failed to generate patch", "repo", repo.Name(), "hash", hash, "err", err) + } +} + +func repoCommitDiff(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := log.FromContext(ctx) + repo := proto.RepositoryFromContext(ctx) + vars := mux.Vars(r) + hash := vars["hash"] + + gr, err := openRepository(repo) + if err != nil { + logger.Debug("failed to open repository", "repo", repo.Name(), "err", err) + renderInternalServerError(w, r) + return + } + + _, err = gr.CatFileCommit(hash) + if err != nil { + logger.Debug("failed to get commit", "repo", repo.Name(), "hash", hash, "err", err) + renderNotFound(w, r) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + if err := gr.RawDiff(hash, git.RawDiffNormal, w); err != nil { + logger.Debug("failed to generate diff", "repo", repo.Name(), "hash", hash, "err", err) + } +}