diff --git a/knotmirror/xrpc/git_format_patch.go b/knotmirror/xrpc/git_format_patch.go new file mode 100644 index 00000000..4386dcf0 --- /dev/null +++ b/knotmirror/xrpc/git_format_patch.go @@ -0,0 +1,54 @@ +package xrpc + +import ( + "bytes" + "fmt" + "net/http" + "os/exec" + "strings" + + "github.com/bluesky-social/indigo/atproto/atclient" + "github.com/bluesky-social/indigo/atproto/syntax" +) + +func (x *Xrpc) FormatPatch(w http.ResponseWriter, r *http.Request) { + var ( + repoQuery = r.URL.Query().Get("repo") + from = r.URL.Query().Get("from") + to = r.URL.Query().Get("to") + ) + + repo, err := syntax.ParseDID(repoQuery) + if err != nil { + writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)}) + return + } + + // reject leading dashes so user input can't be parsed as git flags + if from == "" || to == "" || strings.HasPrefix(from, "-") || strings.HasPrefix(to, "-") { + writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "missing or invalid from/to parameter"}) + return + } + + l := x.logger.With("method", "git.formatPatch", "repo", repo, "from", from, "to", to) + l.Debug("request") + + ctx := r.Context() + + repoPath, err := x.makeRepoPath(ctx, repo) + if err != nil { + writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "RepoNotFound", Message: fmt.Sprintf("unknown repository: %q", repo)}) + return + } + + cmd := exec.CommandContext(ctx, "git", "-C", repoPath, "format-patch", "--stdout", fmt.Sprintf("%s..%s", from, to)) + stderr := new(bytes.Buffer) + cmd.Stderr = stderr + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + cmd.Stdout = w + if err := cmd.Run(); err != nil { + l.Error("running format-patch", "err", err, "stderr", stderr.String()) + w.WriteHeader(http.StatusInternalServerError) + } +} diff --git a/knotmirror/xrpc/xrpc.go b/knotmirror/xrpc/xrpc.go index aa2ebc6c..b94b015e 100644 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -66,6 +66,7 @@ func (x *Xrpc) Router() http.Handler { // r.Get("/"+tangled.GitTempGetCommitNSID, x.GetCommit) // todo // r.Get("/"+tangled.GitTempGetDiffNSID, x.GetDiff) // todo r.Get("/"+tangled.GitTempGetEntryNSID, x.GetEntry) // todo + r.Get("/"+tangled.GitTempFormatPatchNSID, x.FormatPatch) // r.Get("/"+tangled.GitTempGetHeadNSID, x.GetHead) // todo r.Get("/"+tangled.GitTempGetMergeBaseNSID, x.GetMergeBase) r.Get("/"+tangled.GitTempGetTagNSID, x.GetTag) // using types.Response