···11111212 {{ if .RepoInfo.Roles.IsPushAllowed }}1313 <div class="mt-4 text-sm">1414- you can also submit a pull request from a branch1414+ You can also submit a pull request from a branch.1515 <button1616 class="btn text-sm"1717 hx-get="/{{ .RepoInfo.FullName }}/pulls/new/compare-branches"
+6-4
appview/pages/templates/repo/pulls/new.html
···11-{{ define "title" }}new pull | {{ .RepoInfo.FullName }}{{ end }}11+{{ define "title" }}new pull · {{ .RepoInfo.FullName }}{{ end }}2233{{ define "repoContent" }}44 <section class="prose dark:prose-invert">···1212 <li class="leading-relaxed">Paste the diff output in the form below.</li>1313 </ul>1414 </p>1515- </section> 1515+ </section>1616 <form1717 hx-post="/{{ .RepoInfo.FullName }}/pulls/new"1818 class="mt-6 space-y-6"···4141 {{ template "fragments/pullCompareBranches" . }}4242 {{ end }}43434444- <div class="flex justify-end items-center gap-2">4545- <button type="submit" class="btn">create</button>4444+ Or, select a fork to create a pull request from.4545+4646+ <div class="flex justify-start items-center gap-2">4747+ <button type="submit" class="btn">create pull</button>4648 </div>47494850 </div>
+7-1
appview/pages/templates/repo/pulls/pull.html
···4545 {{ .Pull.TargetBranch }}4646 </span>4747 </span>4848- {{ if .Pull.IsSameRepoBranch }}4848+ {{ if not .Pull.IsPatch }}4949 <span>from5050+ {{ if not .Pull.IsSameRepoBranch }}5151+ <span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">5252+ {{ .Pull.PullSource.Repo }}5353+ </span>5454+ <span class="select-none">/</span>5555+ {{ end }}5056 <span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">5157 {{ .Pull.PullSource.Branch }}5258 </span>
+343-141
appview/state/pull.go
···11package state2233import (44+ "database/sql"45 "encoding/json"66+ "errors"57 "fmt"68 "io"79 "log"810 "net/http"1111+ "net/url"912 "strconv"1013 "strings"1114 "time"12151316 "github.com/go-chi/chi/v5"1417 "tangled.sh/tangled.sh/core/api/tangled"1818+ "tangled.sh/tangled.sh/core/appview/auth"1519 "tangled.sh/tangled.sh/core/appview/db"1620 "tangled.sh/tangled.sh/core/appview/pages"1721 "tangled.sh/tangled.sh/core/types"18221923 comatproto "github.com/bluesky-social/indigo/api/atproto"2424+ "github.com/bluesky-social/indigo/atproto/syntax"2025 lexutil "github.com/bluesky-social/indigo/lex/util"2126)2227···191186}192187193188func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult {194194- if pull.State == db.PullMerged {189189+ if pull.State == db.PullMerged || pull.PullSource == nil {195190 return pages.Unknown196191 }197192198198- if pull.PullSource == nil {199199- return pages.Unknown193193+ var knot, ownerDid, repoName string194194+195195+ if pull.PullSource.Repo != nil {196196+ // fork-based pulls197197+ sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.Repo.String())198198+ if err != nil {199199+ log.Println("failed to get source repo", err)200200+ return pages.Unknown201201+ }202202+203203+ knot = sourceRepo.Knot204204+ ownerDid = sourceRepo.Did205205+ repoName = sourceRepo.Name206206+ } else {207207+ // pulls within the same repo208208+ knot = f.Knot209209+ ownerDid = f.OwnerDid()210210+ repoName = f.RepoName200211 }201212202202- us, err := NewUnsignedClient(f.Knot, s.config.Dev)213213+ us, err := NewUnsignedClient(knot, s.config.Dev)203214 if err != nil {204204- log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err)215215+ log.Printf("failed to setup client for %s; ignoring: %v", knot, err)205216 return pages.Unknown206217 }207218208208- resp, err := us.Branch(f.OwnerDid(), f.RepoName, pull.PullSource.Branch)219219+ resp, err := us.Branch(ownerDid, repoName, pull.PullSource.Branch)209220 if err != nil {210221 log.Println("failed to reach knotserver", err)211222 return pages.Unknown···229208230209 body, err := io.ReadAll(resp.Body)231210 if err != nil {232232- log.Printf("Error reading response body: %v", err)211211+ log.Printf("error reading response body: %v", err)233212 return pages.Unknown234213 }214214+ defer resp.Body.Close()235215236216 var result types.RepoBranchResponse237237- err = json.Unmarshal(body, &result)238238- if err != nil {217217+ if err := json.Unmarshal(body, &result); err != nil {239218 log.Println("failed to parse response:", err)240219 return pages.Unknown241220 }242221243243- if pull.Submissions[pull.LastRoundNumber()].SourceRev != result.Branch.Hash {244244- log.Println(pull.Submissions[pull.LastRoundNumber()].SourceRev, result.Branch.Hash)222222+ latestSubmission := pull.Submissions[pull.LastRoundNumber()]223223+ if latestSubmission.SourceRev != result.Branch.Hash {245224 return pages.ShouldResubmit246246- } else {247247- return pages.ShouldNotResubmit248225 }226226+227227+ return pages.ShouldNotResubmit249228}250229251230func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {···489468 return490469 }491470471471+ forks, err := db.GetForksByDid(s.db, user.Did)472472+ if err != nil {473473+ log.Println("failed to get forks", err)474474+ return475475+ }476476+492477 switch r.Method {493478 case http.MethodGet:494479 us, err := NewUnsignedClient(f.Knot, s.config.Dev)···526499 s.pages.RepoNewPull(w, pages.RepoNewPullParams{527500 LoggedInUser: user,528501 RepoInfo: f.RepoInfo(s, user),502502+ Forks: forks,529503 Branches: result.Branches,530504 })531505 case http.MethodPost:532532- isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()533506 title := r.FormValue("title")534507 body := r.FormValue("body")535508 targetBranch := r.FormValue("targetBranch")509509+ fromFork := r.FormValue("fromFork")536510 sourceBranch := r.FormValue("sourceBranch")537511 patch := r.FormValue("patch")538512539539- isBranchBased := isPushAllowed && (sourceBranch != "")513513+ isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()514514+ isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == ""540515 isPatchBased := patch != ""516516+ isForkBased := fromFork != "" && sourceBranch != ""541517542542- if !isBranchBased && !isPatchBased {518518+ if !isBranchBased && !isPatchBased && !isForkBased {543519 s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.")544520 return545521 }···557527 return558528 }559529560560- // TODO: check if knot has this capability561561- var sourceRev string562562- var pullSource *db.PullSource563563- var recordPullSource *tangled.RepoPull_Source564530 if isBranchBased {565565- pullSource = &db.PullSource{566566- Branch: sourceBranch,567567- }568568- recordPullSource = &tangled.RepoPull_Source{569569- Branch: sourceBranch,570570- }571571- // generate a patch using /compare572572- ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)573573- if err != nil {574574- log.Printf("failed to create signed client for %s: %s", f.Knot, err)575575- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")576576- return577577- }578578-579579- resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)580580- switch resp.StatusCode {581581- case 404:582582- case 400:583583- s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")584584- }585585-586586- respBody, err := io.ReadAll(resp.Body)587587- if err != nil {588588- log.Println("failed to compare across branches")589589- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")590590- }591591- defer resp.Body.Close()592592-593593- var diffTreeResponse types.RepoDiffTreeResponse594594- err = json.Unmarshal(respBody, &diffTreeResponse)595595- if err != nil {596596- log.Println("failed to unmarshal diff tree response", err)597597- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")598598- }599599-600600- sourceRev = diffTreeResponse.DiffTree.Rev2601601- patch = diffTreeResponse.DiffTree.Patch531531+ s.handleBranchBasedPull(w, r, f, user, title, body, targetBranch, sourceBranch)532532+ } else if isPatchBased {533533+ s.handlePatchBasedPull(w, r, f, user, title, body, targetBranch, patch)534534+ } else if isForkBased {535535+ s.handleForkBasedPull(w, r, f, user, fromFork, title, body, targetBranch, sourceBranch)602536 }603603-604604- // Validate patch format605605- if !isPatchValid(patch) {606606- s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")607607- return608608- }609609-610610- tx, err := s.db.BeginTx(r.Context(), nil)611611- if err != nil {612612- log.Println("failed to start tx")613613- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")614614- return615615- }616616- defer tx.Rollback()617617-618618- rkey := s.TID()619619- initialSubmission := db.PullSubmission{620620- Patch: patch,621621- SourceRev: sourceRev,622622- }623623- err = db.NewPull(tx, &db.Pull{624624- Title: title,625625- Body: body,626626- TargetBranch: targetBranch,627627- OwnerDid: user.Did,628628- RepoAt: f.RepoAt,629629- Rkey: rkey,630630- Submissions: []*db.PullSubmission{631631- &initialSubmission,632632- },633633- PullSource: pullSource,634634- })635635- if err != nil {636636- log.Println("failed to create pull request", err)637637- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")638638- return639639- }640640- client, _ := s.auth.AuthorizedClient(r)641641- pullId, err := db.NextPullId(s.db, f.RepoAt)642642- if err != nil {643643- log.Println("failed to get pull id", err)644644- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")645645- return646646- }647647-648648- atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{649649- Collection: tangled.RepoPullNSID,650650- Repo: user.Did,651651- Rkey: rkey,652652- Record: &lexutil.LexiconTypeDecoder{653653- Val: &tangled.RepoPull{654654- Title: title,655655- PullId: int64(pullId),656656- TargetRepo: string(f.RepoAt),657657- TargetBranch: targetBranch,658658- Patch: patch,659659- Source: recordPullSource,660660- },661661- },662662- })663663-664664- err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri)665665- if err != nil {666666- log.Println("failed to get pull id", err)667667- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")668668- return669669- }670670-671671- s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))672537 return673538 }539539+}540540+541541+func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) {542542+ pullSource := &db.PullSource{543543+ Branch: sourceBranch,544544+ }545545+ recordPullSource := &tangled.RepoPull_Source{546546+ Branch: sourceBranch,547547+ }548548+549549+ // Generate a patch using /compare550550+ ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)551551+ if err != nil {552552+ log.Printf("failed to create signed client for %s: %s", f.Knot, err)553553+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")554554+ return555555+ }556556+557557+ resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)558558+ switch resp.StatusCode {559559+ case 404:560560+ case 400:561561+ s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")562562+ return563563+ }564564+565565+ respBody, err := io.ReadAll(resp.Body)566566+ if err != nil {567567+ log.Println("failed to compare across branches")568568+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")569569+ return570570+ }571571+ defer resp.Body.Close()572572+573573+ var diffTreeResponse types.RepoDiffTreeResponse574574+ err = json.Unmarshal(respBody, &diffTreeResponse)575575+ if err != nil {576576+ log.Println("failed to unmarshal diff tree response", err)577577+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")578578+ return579579+ }580580+581581+ sourceRev := diffTreeResponse.DiffTree.Rev2582582+ patch := diffTreeResponse.DiffTree.Patch583583+584584+ if !isPatchValid(patch) {585585+ s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")586586+ return587587+ }588588+589589+ s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource)590590+}591591+592592+func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) {593593+ if !isPatchValid(patch) {594594+ s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")595595+ return596596+ }597597+598598+ s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil)599599+}600600+601601+func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) {602602+ fork, err := db.GetForkByDid(s.db, user.Did, forkRepo)603603+ if errors.Is(err, sql.ErrNoRows) {604604+ s.pages.Notice(w, "pull", "No such fork.")605605+ return606606+ } else if err != nil {607607+ log.Println("failed to fetch fork:", err)608608+ s.pages.Notice(w, "pull", "Failed to fetch fork.")609609+ return610610+ }611611+612612+ secret, err := db.GetRegistrationKey(s.db, fork.Knot)613613+ if err != nil {614614+ log.Println("failed to fetch registration key:", err)615615+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")616616+ return617617+ }618618+619619+ sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev)620620+ if err != nil {621621+ log.Println("failed to create signed client:", err)622622+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")623623+ return624624+ }625625+626626+ us, err := NewUnsignedClient(fork.Knot, s.config.Dev)627627+ if err != nil {628628+ log.Println("failed to create unsigned client:", err)629629+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")630630+ return631631+ }632632+633633+ resp, err := sc.NewHiddenRef(user.Did, fork.Name, sourceBranch, targetBranch)634634+ if err != nil {635635+ log.Println("failed to create hidden ref:", err, resp.StatusCode)636636+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")637637+ return638638+ }639639+640640+ switch resp.StatusCode {641641+ case 404:642642+ case 400:643643+ s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")644644+ return645645+ }646646+647647+ hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch))648648+ // We're now comparing the sourceBranch (on the fork) against the hiddenRef which is tracking649649+ // the targetBranch on the target repository. This code is a bit confusing, but here's an example:650650+ // hiddenRef: hidden/feature-1/main (on repo-fork)651651+ // targetBranch: main (on repo-1)652652+ // sourceBranch: feature-1 (on repo-fork)653653+ diffResp, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch)654654+ if err != nil {655655+ log.Println("failed to compare across branches", err)656656+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")657657+ return658658+ }659659+660660+ respBody, err := io.ReadAll(diffResp.Body)661661+ if err != nil {662662+ log.Println("failed to read response body", err)663663+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")664664+ return665665+ }666666+667667+ defer resp.Body.Close()668668+669669+ var diffTreeResponse types.RepoDiffTreeResponse670670+ err = json.Unmarshal(respBody, &diffTreeResponse)671671+ if err != nil {672672+ log.Println("failed to unmarshal diff tree response", err)673673+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")674674+ return675675+ }676676+677677+ sourceRev := diffTreeResponse.DiffTree.Rev2678678+ patch := diffTreeResponse.DiffTree.Patch679679+680680+ if !isPatchValid(patch) {681681+ s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")682682+ return683683+ }684684+685685+ forkAtUri, err := syntax.ParseATURI(fork.AtUri)686686+ if err != nil {687687+ log.Println("failed to parse fork AT URI", err)688688+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")689689+ return690690+ }691691+692692+ s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{693693+ Branch: sourceBranch,694694+ Repo: &forkAtUri,695695+ }, &tangled.RepoPull_Source{Branch: sourceBranch, Repo: &fork.AtUri})696696+}697697+698698+func (s *State) createPullRequest(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch, sourceRev string, pullSource *db.PullSource, recordPullSource *tangled.RepoPull_Source) {699699+ tx, err := s.db.BeginTx(r.Context(), nil)700700+ if err != nil {701701+ log.Println("failed to start tx")702702+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")703703+ return704704+ }705705+ defer tx.Rollback()706706+707707+ rkey := s.TID()708708+ initialSubmission := db.PullSubmission{709709+ Patch: patch,710710+ SourceRev: sourceRev,711711+ }712712+ err = db.NewPull(tx, &db.Pull{713713+ Title: title,714714+ Body: body,715715+ TargetBranch: targetBranch,716716+ OwnerDid: user.Did,717717+ RepoAt: f.RepoAt,718718+ Rkey: rkey,719719+ Submissions: []*db.PullSubmission{720720+ &initialSubmission,721721+ },722722+ PullSource: pullSource,723723+ })724724+ if err != nil {725725+ log.Println("failed to create pull request", err)726726+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")727727+ return728728+ }729729+ client, _ := s.auth.AuthorizedClient(r)730730+ pullId, err := db.NextPullId(s.db, f.RepoAt)731731+ if err != nil {732732+ log.Println("failed to get pull id", err)733733+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")734734+ return735735+ }736736+737737+ atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{738738+ Collection: tangled.RepoPullNSID,739739+ Repo: user.Did,740740+ Rkey: rkey,741741+ Record: &lexutil.LexiconTypeDecoder{742742+ Val: &tangled.RepoPull{743743+ Title: title,744744+ PullId: int64(pullId),745745+ TargetRepo: string(f.RepoAt),746746+ TargetBranch: targetBranch,747747+ Patch: patch,748748+ Source: recordPullSource,749749+ },750750+ },751751+ })752752+753753+ err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri)754754+ if err != nil {755755+ log.Println("failed to get pull id", err)756756+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")757757+ return758758+ }759759+760760+ s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))674761}675762676763func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {···870723 var sourceRev string871724 var recordPullSource *tangled.RepoPull_Source872725873873- // this pull is a branch based pull726726+ var ownerDid, repoName, knotName string727727+ var isSameRepo bool = pull.IsSameRepoBranch()728728+ sourceBranch := pull.PullSource.Branch729729+ targetBranch := pull.TargetBranch730730+ recordPullSource = &tangled.RepoPull_Source{731731+ Branch: sourceBranch,732732+ }733733+874734 isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()875875- if pull.IsSameRepoBranch() && isPushAllowed {876876- sourceBranch := pull.PullSource.Branch877877- targetBranch := pull.TargetBranch878878- recordPullSource = &tangled.RepoPull_Source{879879- Branch: sourceBranch,880880- }881881- // extract patch by performing compare882882- ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)735735+ if isSameRepo && isPushAllowed {736736+ ownerDid = f.OwnerDid()737737+ repoName = f.RepoName738738+ knotName = f.Knot739739+ } else if !isSameRepo {740740+ sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.Repo.String())883741 if err != nil {884884- log.Printf("failed to create signed client for %s: %s", f.Knot, err)742742+ log.Println("failed to get source repo", err)743743+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")744744+ return745745+ }746746+ ownerDid = sourceRepo.Did747747+ repoName = sourceRepo.Name748748+ knotName = sourceRepo.Knot749749+ }750750+751751+ if sourceBranch != "" && knotName != "" {752752+ // extract patch by performing compare753753+ ksClient, err := NewUnsignedClient(knotName, s.config.Dev)754754+ if err != nil {755755+ log.Printf("failed to create client for %s: %s", knotName, err)885756 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")886757 return887758 }888759889889- resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)890890- switch resp.StatusCode {760760+ if !isSameRepo {761761+ secret, err := db.GetRegistrationKey(s.db, knotName)762762+ if err != nil {763763+ log.Printf("failed to get registration key for %s: %s", knotName, err)764764+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")765765+ return766766+ }767767+ // update the hidden tracking branch to latest768768+ signedClient, err := NewSignedClient(knotName, secret, s.config.Dev)769769+ if err != nil {770770+ log.Printf("failed to create signed client for %s: %s", knotName, err)771771+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")772772+ return773773+ }774774+ resp, err := signedClient.NewHiddenRef(ownerDid, repoName, sourceBranch, targetBranch)775775+ if err != nil || resp.StatusCode != http.StatusNoContent {776776+ log.Printf("failed to update tracking branch: %s", err)777777+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")778778+ return779779+ }780780+ }781781+782782+ var compareResp *http.Response783783+ if !isSameRepo {784784+ hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch))785785+ compareResp, err = ksClient.Compare(ownerDid, repoName, hiddenRef, sourceBranch)786786+ } else {787787+ compareResp, err = ksClient.Compare(ownerDid, repoName, targetBranch, sourceBranch)788788+ }789789+ if err != nil {790790+ log.Printf("failed to compare branches: %s", err)791791+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")792792+ return793793+ }794794+ defer compareResp.Body.Close()795795+796796+ switch compareResp.StatusCode {891797 case 404:892798 case 400:893799 s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")800800+ return894801 }895802896896- respBody, err := io.ReadAll(resp.Body)803803+ respBody, err := io.ReadAll(compareResp.Body)897804 if err != nil {898805 log.Println("failed to compare across branches")899806 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")807807+ return900808 }901901- defer resp.Body.Close()809809+ defer compareResp.Body.Close()902810903811 var diffTreeResponse types.RepoDiffTreeResponse904812 err = json.Unmarshal(respBody, &diffTreeResponse)905813 if err != nil {906814 log.Println("failed to unmarshal diff tree response", err)907815 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")816816+ return908817 }909818910819 sourceRev = diffTreeResponse.DiffTree.Rev2···982779 return983780 }984781985985- // Validate patch format986782 if !isPatchValid(patch) {987783 s.pages.Notice(w, "resubmit-error", "Invalid patch format. Please provide a valid diff.")988784 return