Lindenii Project Forge
Commit info | |
---|---|
ID | 134b84f2672a9fe3e2e8a92b712261b47c4bd022 |
Author | Runxi Yu<me@runxiyu.org> |
Author date | Wed, 05 Mar 2025 09:32:40 +0800 |
Committer | Runxi Yu<me@runxiyu.org> |
Committer date | Wed, 05 Mar 2025 09:47:01 +0800 |
Actions | Get patch |
repo/*: Use var instead of :=
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "fmt" "net/http" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/format/diff"
"github.com/go-git/go-git/v5/plumbing/object"
"go.lindenii.runxiyu.org/lindenii-common/misc" ) // The file patch type from go-git isn't really usable in HTML templates // either.
type usable_file_patch struct {
type usable_file_patch_t struct {
From diff.File To diff.File Chunks []usable_chunk } type usable_chunk struct { Operation diff.Operation Content string } func handle_repo_commit(w http.ResponseWriter, r *http.Request, params map[string]any) {
repo, commit_id_specified_string := params["repo"].(*git.Repository), params["commit_id"].(string)
var repo *git.Repository var commit_id_specified_string, commit_id_specified_string_without_suffix string var commit_id plumbing.Hash var parent_commit_hash plumbing.Hash var commit_object *object.Commit var commit_id_string string var err error var patch *object.Patch repo, commit_id_specified_string = params["repo"].(*git.Repository), params["commit_id"].(string)
commit_id_specified_string_without_suffix := strings.TrimSuffix(commit_id_specified_string, ".patch") commit_id := plumbing.NewHash(commit_id_specified_string_without_suffix) commit_object, err := repo.CommitObject(commit_id) if err != nil {
commit_id_specified_string_without_suffix = strings.TrimSuffix(commit_id_specified_string, ".patch") commit_id = plumbing.NewHash(commit_id_specified_string_without_suffix) if commit_object, err = repo.CommitObject(commit_id); err != nil {
http.Error(w, "Error getting commit object: "+err.Error(), http.StatusInternalServerError) return } if commit_id_specified_string_without_suffix != commit_id_specified_string {
patch, err := format_patch_from_commit(commit_object) if err != nil {
var formatted_patch string if formatted_patch, err = format_patch_from_commit(commit_object); err != nil {
http.Error(w, "Error formatting patch: "+err.Error(), http.StatusInternalServerError) return }
fmt.Fprintln(w, patch)
fmt.Fprintln(w, formatted_patch)
return }
commit_id_string := commit_object.Hash.String()
commit_id_string = commit_object.Hash.String()
if commit_id_string != commit_id_specified_string { http.Redirect(w, r, commit_id_string, http.StatusSeeOther) return } params["commit_object"] = commit_object params["commit_id"] = commit_id_string
parent_commit_hash, patch, err := get_patch_from_commit(commit_object)
parent_commit_hash, patch, err = get_patch_from_commit(commit_object)
if err != nil { http.Error(w, "Error getting patch from commit: "+err.Error(), http.StatusInternalServerError) return } params["parent_commit_hash"] = parent_commit_hash.String() params["patch"] = patch params["file_patches"] = make_usable_file_patches(patch) render_template(w, "repo_commit", params) } type fake_diff_file struct { hash plumbing.Hash mode filemode.FileMode path string } func (f fake_diff_file) Hash() plumbing.Hash { return f.hash } func (f fake_diff_file) Mode() filemode.FileMode { return f.mode } func (f fake_diff_file) Path() string { return f.path } var fake_diff_file_null = fake_diff_file{ hash: plumbing.NewHash("0000000000000000000000000000000000000000"), mode: misc.First_or_panic(filemode.New("100644")), path: "", } func make_usable_file_patches(patch diff.Patch) (usable_file_patches []usable_file_patch) { // TODO: Remove unnecessary context // TODO: Prepend "+"/"-"/" " instead of solely distinguishing based on color
usable_file_patches = make([]usable_file_patch, 0)
for _, file_patch := range patch.FilePatches() {
from, to := file_patch.Files()
var from, to diff.File var usable_file_patch usable_file_patch_t chunks := []usable_chunk{} from, to = file_patch.Files()
if from == nil { from = fake_diff_file_null } if to == nil { to = fake_diff_file_null }
chunks := []usable_chunk{}
for _, chunk := range file_patch.Chunks() {
content := chunk.Content()
var content string content = chunk.Content()
if len(content) > 0 && content[0] == '\n' { content = "\n" + content } // Horrible hack to fix how browsers newlines that immediately proceed <pre> chunks = append(chunks, usable_chunk{ Operation: chunk.Type(), Content: content, }) }
usable_file_patch := usable_file_patch{
usable_file_patch = usable_file_patch_t{
Chunks: chunks, From: from, To: to, } usable_file_patches = append(usable_file_patches, usable_file_patch) } return }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "net/http"
"github.com/jackc/pgx/v5"
) type id_title_status_t struct { ID int Title string Status string } func handle_repo_contrib_index(w http.ResponseWriter, r *http.Request, params map[string]any) {
rows, err := database.Query(r.Context(), "SELECT id, COALESCE(title, 'Untitled'), status FROM merge_requests WHERE repo_id = $1", params["repo_id"]) if err != nil {
var rows pgx.Rows var result []id_title_status_t var err error if rows, err = database.Query(r.Context(), "SELECT id, COALESCE(title, 'Untitled'), status FROM merge_requests WHERE repo_id = $1", params["repo_id"], ); err != nil {
http.Error(w, "Error querying merge requests: "+err.Error(), http.StatusInternalServerError) return } defer rows.Close()
result := []id_title_status_t{}
for rows.Next() { var id int var title, status string
if err := rows.Scan(&id, &title, &status); err != nil {
if err = rows.Scan(&id, &title, &status); err != nil {
http.Error(w, "Error scanning merge request: "+err.Error(), http.StatusInternalServerError) return } result = append(result, id_title_status_t{id, title, status}) }
if err := rows.Err(); err != nil {
if err = rows.Err(); err != nil {
http.Error(w, "Error ranging over merge requests: "+err.Error(), http.StatusInternalServerError) return } params["merge_requests"] = result render_template(w, "repo_contrib_index", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "net/http" "strconv" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
) func handle_repo_contrib_one(w http.ResponseWriter, r *http.Request, params map[string]any) {
mr_id_string := params["mr_id"].(string) mr_id, err := strconv.ParseInt(mr_id_string, 10, strconv.IntSize)
var mr_id_string string var mr_id int var err error var title, status, source_ref, destination_branch string var repo *git.Repository var source_ref_hash plumbing.Hash var source_commit *object.Commit mr_id_string = params["mr_id"].(string) mr_id_int64, err := strconv.ParseInt(mr_id_string, 10, strconv.IntSize)
if err != nil { http.Error(w, "Merge request ID not an integer: "+err.Error(), http.StatusBadRequest) return }
mr_id = int(mr_id_int64)
var title, status, source_ref, destination_branch string err = database.QueryRow(r.Context(), "SELECT COALESCE(title, ''), status, source_ref, COALESCE(destination_branch, '') FROM merge_requests WHERE id = $1", mr_id).Scan(&title, &status, &source_ref, &destination_branch) if err != nil {
if err = database.QueryRow(r.Context(), "SELECT COALESCE(title, ''), status, source_ref, COALESCE(destination_branch, '') FROM merge_requests WHERE id = $1", mr_id, ).Scan(&title, &status, &source_ref, &destination_branch); err != nil {
http.Error(w, "Error querying merge request: "+err.Error(), http.StatusInternalServerError) return }
repo := params["repo"].(*git.Repository)
repo = params["repo"].(*git.Repository)
source_ref_hash, err := get_ref_hash_from_type_and_name(repo, "branch", source_ref) if err != nil {
if source_ref_hash, err = get_ref_hash_from_type_and_name(repo, "branch", source_ref); err != nil {
http.Error(w, "Error getting source ref hash: "+err.Error(), http.StatusInternalServerError) return }
source_commit, err := repo.CommitObject(source_ref_hash) if err != nil {
if source_commit, err = repo.CommitObject(source_ref_hash); err != nil {
http.Error(w, "Error getting source commit: "+err.Error(), http.StatusInternalServerError) return } params["source_commit"] = source_commit var destination_branch_hash plumbing.Hash if destination_branch == "" { destination_branch = "HEAD" destination_branch_hash, err = get_ref_hash_from_type_and_name(repo, "", "") } else { destination_branch_hash, err = get_ref_hash_from_type_and_name(repo, "branch", destination_branch) if err != nil { http.Error(w, "Error getting destination branch hash: "+err.Error(), http.StatusInternalServerError) return } } destination_commit, err := repo.CommitObject(destination_branch_hash) if err != nil { http.Error(w, "Error getting destination commit: "+err.Error(), http.StatusInternalServerError) return } params["destination_commit"] = destination_commit patch, err := destination_commit.Patch(source_commit) if err != nil { http.Error(w, "Error getting patch: "+err.Error(), http.StatusInternalServerError) return } params["file_patches"] = make_usable_file_patches(patch) params["mr_title"], params["mr_status"], params["mr_source_ref"], params["mr_destination_branch"] = title, status, source_ref, destination_branch render_template(w, "repo_contrib_one", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "net/http" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object"
) func handle_repo_index(w http.ResponseWriter, r *http.Request, params map[string]any) {
repo, repo_name, group_name := params["repo"].(*git.Repository), params["repo_name"].(string), params["group_name"].(string)
var repo *git.Repository var repo_name, group_name string var ref_hash plumbing.Hash var err error var recent_commits []*object.Commit var commit_object *object.Commit var tree *object.Tree
ref_hash, err := get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)) if err != nil {
repo, repo_name, group_name = params["repo"].(*git.Repository), params["repo_name"].(string), params["group_name"].(string) if ref_hash, err = get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil {
http.Error(w, "Error getting ref hash: "+err.Error(), http.StatusInternalServerError) return }
recent_commits, err := get_recent_commits(repo, ref_hash, 3) if err != nil {
if recent_commits, err = get_recent_commits(repo, ref_hash, 3); err != nil {
http.Error(w, "Error getting recent commits: "+err.Error(), http.StatusInternalServerError) return } params["commits"] = recent_commits
commit_object, err := repo.CommitObject(ref_hash)
commit_object, err = repo.CommitObject(ref_hash)
if err != nil { http.Error(w, "Error getting commit object: "+err.Error(), http.StatusInternalServerError) return }
tree, err := commit_object.Tree()
tree, err = commit_object.Tree()
if err != nil { http.Error(w, "Error getting file tree: "+err.Error(), http.StatusInternalServerError) return } params["readme_filename"], params["readme"] = render_readme_at_tree(tree) params["files"] = build_display_git_tree(tree) params["http_clone_url"] = generate_http_remote_url(group_name, repo_name) params["ssh_clone_url"] = generate_ssh_remote_url(group_name, repo_name) render_template(w, "repo_index", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "fmt" "io" "net/http" "os/exec" ) func handle_repo_info(w http.ResponseWriter, r *http.Request, params map[string]any) (err error) {
group_name, repo_name := params["group_name"].(string), params["repo_name"].(string) var repo_path string err = database.QueryRow(r.Context(), "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", group_name, repo_name).Scan(&repo_path) if err != nil {
var group_name, repo_name, repo_path string group_name, repo_name = params["group_name"].(string), params["repo_name"].(string) if err = database.QueryRow(r.Context(), "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", group_name, repo_name, ).Scan(&repo_path); err != nil {
return err } w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement") w.WriteHeader(http.StatusOK) cmd := exec.Command("git", "upload-pack", "--stateless-rpc", "--advertise-refs", repo_path)
stdout, err := cmd.StdoutPipe() if err != nil { return err }
cmd.Stderr = cmd.Stdout
defer func() { _ = stdout.Close() }()
cmd.Stderr = cmd.Stdout
err = cmd.Start() if err != nil {
if err = cmd.Start(); err != nil {
return err }
err = pack_line(w, "# service=git-upload-pack\n") if err != nil {
if err = pack_line(w, "# service=git-upload-pack\n"); err != nil {
return err }
err = pack_flush(w) if err != nil {
if err = pack_flush(w); err != nil {
return }
_, err = io.Copy(w, stdout) if err != nil {
if _, err = io.Copy(w, stdout); err != nil {
return err }
err = cmd.Wait() if err != nil {
if err = cmd.Wait(); err != nil {
return err } return nil } // Taken from https://github.com/icyphox/legit, MIT license func pack_line(w io.Writer, s string) error { _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) return err } // Taken from https://github.com/icyphox/legit, MIT license func pack_flush(w io.Writer) error { _, err := fmt.Fprint(w, "0000") return err }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "net/http" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object"
) // TODO: I probably shouldn't include *all* commits here... func handle_repo_log(w http.ResponseWriter, r *http.Request, params map[string]any) {
repo := params["repo"].(*git.Repository)
var repo *git.Repository var ref_hash plumbing.Hash var err error var commits []*object.Commit repo = params["repo"].(*git.Repository)
ref_hash, err := get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)) if err != nil {
if ref_hash, err = get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil {
http.Error(w, "Error getting ref hash: "+err.Error(), http.StatusInternalServerError) return }
commits, err := get_recent_commits(repo, ref_hash, -1) if err != nil {
if commits, err = get_recent_commits(repo, ref_hash, -1); err != nil {
http.Error(w, "Error getting recent commits: "+err.Error(), http.StatusInternalServerError) return } params["commits"] = commits render_template(w, "repo_log", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "fmt" "net/http" "path" "strings" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object" ) func handle_repo_raw(w http.ResponseWriter, r *http.Request, params map[string]any) {
raw_path_spec := params["rest"].(string) repo, path_spec := params["repo"].(*git.Repository), strings.TrimSuffix(raw_path_spec, "/")
var raw_path_spec, path_spec string var repo *git.Repository var ref_hash plumbing.Hash var commit_object *object.Commit var tree *object.Tree var err error raw_path_spec = params["rest"].(string) repo, path_spec = params["repo"].(*git.Repository), strings.TrimSuffix(raw_path_spec, "/")
params["path_spec"] = path_spec
ref_hash, err := get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)) if err != nil {
if ref_hash, err = get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil {
http.Error(w, "Error getting ref hash: "+err.Error(), http.StatusInternalServerError) return }
commit_object, err := repo.CommitObject(ref_hash) if err != nil {
if commit_object, err = repo.CommitObject(ref_hash); err != nil {
http.Error(w, "Error getting commit object: "+err.Error(), http.StatusInternalServerError) return }
tree, err := commit_object.Tree() if err != nil {
if tree, err = commit_object.Tree(); err != nil {
http.Error(w, "Error getting file tree: "+err.Error(), http.StatusInternalServerError) return } var target *object.Tree if path_spec == "" { target = tree } else {
target, err = tree.Tree(path_spec) if err != nil { file, err := tree.File(path_spec) if err != nil {
if target, err = tree.Tree(path_spec); err != nil { var file *object.File var file_contents string if file, err = tree.File(path_spec); err != nil {
http.Error(w, "Error retrieving path: "+err.Error(), http.StatusInternalServerError) return } if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] == '/' { http.Redirect(w, r, "../"+path_spec, http.StatusSeeOther) return }
file_contents, err := file.Contents() if err != nil {
if file_contents, err = file.Contents(); err != nil {
http.Error(w, "Error reading file: "+err.Error(), http.StatusInternalServerError) return }
fmt.Fprintln(w, file_contents)
fmt.Fprint(w, file_contents)
return } } if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] != '/' { http.Redirect(w, r, path.Base(path_spec)+"/", http.StatusSeeOther) return } params["files"] = build_display_git_tree(target) render_template(w, "repo_raw_dir", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "bytes" "html/template" "net/http" "path" "strings"
"github.com/alecthomas/chroma/v2"
chroma_formatters_html "github.com/alecthomas/chroma/v2/formatters/html" chroma_lexers "github.com/alecthomas/chroma/v2/lexers" chroma_styles "github.com/alecthomas/chroma/v2/styles" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object" ) func handle_repo_tree(w http.ResponseWriter, r *http.Request, params map[string]any) {
raw_path_spec := params["rest"].(string) repo, path_spec := params["repo"].(*git.Repository), strings.TrimSuffix(raw_path_spec, "/")
var raw_path_spec, path_spec string var repo *git.Repository var ref_hash plumbing.Hash var commit_object *object.Commit var tree *object.Tree var err error raw_path_spec = params["rest"].(string) repo, path_spec = params["repo"].(*git.Repository), strings.TrimSuffix(raw_path_spec, "/")
params["path_spec"] = path_spec
ref_hash, err := get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)) if err != nil {
if ref_hash, err = get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil {
http.Error(w, "Error getting ref hash: "+err.Error(), http.StatusInternalServerError) return }
commit_object, err := repo.CommitObject(ref_hash) if err != nil {
if commit_object, err = repo.CommitObject(ref_hash); err != nil {
http.Error(w, "Error getting commit object: "+err.Error(), http.StatusInternalServerError) return }
tree, err := commit_object.Tree() if err != nil {
if tree, err = commit_object.Tree(); err != nil {
http.Error(w, "Error getting file tree: "+err.Error(), http.StatusInternalServerError) return } var target *object.Tree if path_spec == "" { target = tree } else {
target, err = tree.Tree(path_spec) if err != nil { file, err := tree.File(path_spec) if err != nil {
if target, err = tree.Tree(path_spec); err != nil { var file *object.File var file_contents string var lexer chroma.Lexer var iterator chroma.Iterator var style *chroma.Style var formatter *chroma_formatters_html.Formatter var formatted_encapsulated template.HTML if file, err = tree.File(path_spec); err != nil {
http.Error(w, "Error retrieving path: "+err.Error(), http.StatusInternalServerError) return } if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] == '/' { http.Redirect(w, r, "../"+path_spec, http.StatusSeeOther) return }
file_contents, err := file.Contents() if err != nil {
if file_contents, err = file.Contents(); err != nil {
http.Error(w, "Error reading file: "+err.Error(), http.StatusInternalServerError) return }
lexer := chroma_lexers.Match(path_spec)
lexer = chroma_lexers.Match(path_spec)
if lexer == nil { lexer = chroma_lexers.Fallback }
iterator, err := lexer.Tokenise(nil, file_contents) if err != nil {
if iterator, err = lexer.Tokenise(nil, file_contents); err != nil {
http.Error(w, "Error tokenizing code: "+err.Error(), http.StatusInternalServerError) return } var formatted_unencapsulated bytes.Buffer
style := chroma_styles.Get("autumn") formatter := chroma_formatters_html.New(chroma_formatters_html.WithClasses(true), chroma_formatters_html.TabWidth(8)) err = formatter.Format(&formatted_unencapsulated, style, iterator) if err != nil {
style = chroma_styles.Get("autumn") formatter = chroma_formatters_html.New(chroma_formatters_html.WithClasses(true), chroma_formatters_html.TabWidth(8)) if err = formatter.Format(&formatted_unencapsulated, style, iterator); err != nil {
http.Error(w, "Error formatting code: "+err.Error(), http.StatusInternalServerError) return }
formatted_encapsulated := template.HTML(formatted_unencapsulated.Bytes())
formatted_encapsulated = template.HTML(formatted_unencapsulated.Bytes())
params["file_contents"] = formatted_encapsulated render_template(w, "repo_tree_file", params) return } } if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] != '/' { http.Redirect(w, r, path.Base(path_spec)+"/", http.StatusSeeOther) return } params["readme_filename"], params["readme"] = render_readme_at_tree(target) params["files"] = build_display_git_tree(target) render_template(w, "repo_tree_dir", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "io" "net/http" "os" "os/exec" ) func handle_upload_pack(w http.ResponseWriter, r *http.Request, params map[string]any) (err error) {
group_name, repo_name := params["group_name"].(string), params["repo_name"].(string)
var group_name, repo_name string
var repo_path string
err = database.QueryRow(r.Context(), "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", group_name, repo_name).Scan(&repo_path) if err != nil {
var stdout io.ReadCloser var stdin io.WriteCloser var cmd *exec.Cmd group_name, repo_name = params["group_name"].(string), params["repo_name"].(string) if err = database.QueryRow(r.Context(), "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", group_name, repo_name, ).Scan(&repo_path); err != nil {
return err } w.Header().Set("Content-Type", "application/x-git-upload-pack-result") w.Header().Set("Connection", "Keep-Alive") w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK)
cmd := exec.Command("git", "upload-pack", "--stateless-rpc", repo_path)
cmd = exec.Command("git", "upload-pack", "--stateless-rpc", repo_path)
cmd.Env = append(os.Environ(), "LINDENII_FORGE_HOOKS_SOCKET_PATH="+config.Hooks.Socket)
stdout, err := cmd.StdoutPipe() if err != nil {
if stdout, err = cmd.StdoutPipe(); err != nil {
return err } cmd.Stderr = cmd.Stdout defer func() { _ = stdout.Close() }()
stdin, err := cmd.StdinPipe() if err != nil {
if stdin, err = cmd.StdinPipe(); err != nil {
return err } defer func() { _ = stdin.Close() }()
err = cmd.Start() if err != nil {
if err = cmd.Start(); err != nil {
return err }
_, err = io.Copy(stdin, r.Body) if err != nil {
if _, err = io.Copy(stdin, r.Body); err != nil {
return err }
err = stdin.Close() if err != nil {
if err = stdin.Close(); err != nil {
return err }
_, err = io.Copy(w, stdout) if err != nil {
if _, err = io.Copy(w, stdout); err != nil {
return err }
err = cmd.Wait() if err != nil {
if err = cmd.Wait(); err != nil {
return err } return nil }