Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).

appview: fork-based pull requests

anirudh.fi 5959c27f 1287e6b7

verified
+451 -149
+4
appview/db/pulls.go
··· 128 128 return false 129 129 } 130 130 131 + func (p *Pull) IsPatch() bool { 132 + return p.PullSource == nil 133 + } 134 + 131 135 func (s PullSubmission) AsNiceDiff(targetBranch string) types.NiceDiff { 132 136 patch := s.Patch 133 137
+87
appview/db/repos.go
··· 190 190 return nullableSource.String, nil 191 191 } 192 192 193 + func GetForksByDid(e Execer, did string) ([]Repo, error) { 194 + var repos []Repo 195 + 196 + rows, err := e.Query( 197 + `select did, name, knot, rkey, description, created, at_uri, source 198 + from repos 199 + where did = ? and source is not null and source != '' 200 + order by created desc`, 201 + did, 202 + ) 203 + if err != nil { 204 + return nil, err 205 + } 206 + defer rows.Close() 207 + 208 + for rows.Next() { 209 + var repo Repo 210 + var createdAt string 211 + var nullableDescription sql.NullString 212 + var nullableSource sql.NullString 213 + 214 + err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource) 215 + if err != nil { 216 + return nil, err 217 + } 218 + 219 + if nullableDescription.Valid { 220 + repo.Description = nullableDescription.String 221 + } 222 + 223 + if nullableSource.Valid { 224 + repo.Source = nullableSource.String 225 + } 226 + 227 + createdAtTime, err := time.Parse(time.RFC3339, createdAt) 228 + if err != nil { 229 + repo.Created = time.Now() 230 + } else { 231 + repo.Created = createdAtTime 232 + } 233 + 234 + repos = append(repos, repo) 235 + } 236 + 237 + if err := rows.Err(); err != nil { 238 + return nil, err 239 + } 240 + 241 + return repos, nil 242 + } 243 + 244 + func GetForkByDid(e Execer, did string, name string) (*Repo, error) { 245 + var repo Repo 246 + var createdAt string 247 + var nullableDescription sql.NullString 248 + var nullableSource sql.NullString 249 + 250 + row := e.QueryRow( 251 + `select did, name, knot, rkey, description, created, at_uri, source 252 + from repos 253 + where did = ? and name = ? and source is not null and source != ''`, 254 + did, name, 255 + ) 256 + 257 + err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource) 258 + if err != nil { 259 + return nil, err 260 + } 261 + 262 + if nullableDescription.Valid { 263 + repo.Description = nullableDescription.String 264 + } 265 + 266 + if nullableSource.Valid { 267 + repo.Source = nullableSource.String 268 + } 269 + 270 + createdAtTime, err := time.Parse(time.RFC3339, createdAt) 271 + if err != nil { 272 + repo.Created = time.Now() 273 + } else { 274 + repo.Created = createdAtTime 275 + } 276 + 277 + return &repo, nil 278 + } 279 + 193 280 func AddCollaborator(e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error { 194 281 _, err := e.Exec( 195 282 `insert into collaborators (did, repo)
+1
appview/pages/pages.go
··· 575 575 type RepoNewPullParams struct { 576 576 LoggedInUser *auth.User 577 577 RepoInfo RepoInfo 578 + Forks []db.Repo 578 579 Branches []types.Branch 579 580 Active string 580 581 }
+1 -1
appview/pages/templates/fragments/pullActions.html
··· 43 43 {{ $disabled = "disabled" }} 44 44 {{ end }} 45 45 <button id="resubmitBtn" 46 - {{ if $isSameRepoBranch }} 46 + {{ if not .Pull.IsPatch }} 47 47 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit" 48 48 {{ else }} 49 49 hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit"
+1 -1
appview/pages/templates/fragments/pullPatchUpload.html
··· 11 11 12 12 {{ if .RepoInfo.Roles.IsPushAllowed }} 13 13 <div class="mt-4 text-sm"> 14 - you can also submit a pull request from a branch 14 + You can also submit a pull request from a branch. 15 15 <button 16 16 class="btn text-sm" 17 17 hx-get="/{{ .RepoInfo.FullName }}/pulls/new/compare-branches"
+6 -4
appview/pages/templates/repo/pulls/new.html
··· 1 - {{ define "title" }}new pull | {{ .RepoInfo.FullName }}{{ end }} 1 + {{ define "title" }}new pull &middot; {{ .RepoInfo.FullName }}{{ end }} 2 2 3 3 {{ define "repoContent" }} 4 4 <section class="prose dark:prose-invert"> ··· 12 12 <li class="leading-relaxed">Paste the diff output in the form below.</li> 13 13 </ul> 14 14 </p> 15 - </section> 15 + </section> 16 16 <form 17 17 hx-post="/{{ .RepoInfo.FullName }}/pulls/new" 18 18 class="mt-6 space-y-6" ··· 41 41 {{ template "fragments/pullCompareBranches" . }} 42 42 {{ end }} 43 43 44 - <div class="flex justify-end items-center gap-2"> 45 - <button type="submit" class="btn">create</button> 44 + Or, select a fork to create a pull request from. 45 + 46 + <div class="flex justify-start items-center gap-2"> 47 + <button type="submit" class="btn">create pull</button> 46 48 </div> 47 49 48 50 </div>
+7 -1
appview/pages/templates/repo/pulls/pull.html
··· 45 45 {{ .Pull.TargetBranch }} 46 46 </span> 47 47 </span> 48 - {{ if .Pull.IsSameRepoBranch }} 48 + {{ if not .Pull.IsPatch }} 49 49 <span>from 50 + {{ if not .Pull.IsSameRepoBranch }} 51 + <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"> 52 + {{ .Pull.PullSource.Repo }} 53 + </span> 54 + <span class="select-none">/</span> 55 + {{ end }} 50 56 <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"> 51 57 {{ .Pull.PullSource.Branch }} 52 58 </span>
+343 -141
appview/state/pull.go
··· 1 1 package state 2 2 3 3 import ( 4 + "database/sql" 4 5 "encoding/json" 6 + "errors" 5 7 "fmt" 6 8 "io" 7 9 "log" 8 10 "net/http" 11 + "net/url" 9 12 "strconv" 10 13 "strings" 11 14 "time" 12 15 13 16 "github.com/go-chi/chi/v5" 14 17 "tangled.sh/tangled.sh/core/api/tangled" 18 + "tangled.sh/tangled.sh/core/appview/auth" 15 19 "tangled.sh/tangled.sh/core/appview/db" 16 20 "tangled.sh/tangled.sh/core/appview/pages" 17 21 "tangled.sh/tangled.sh/core/types" 18 22 19 23 comatproto "github.com/bluesky-social/indigo/api/atproto" 24 + "github.com/bluesky-social/indigo/atproto/syntax" 20 25 lexutil "github.com/bluesky-social/indigo/lex/util" 21 26 ) 22 27 ··· 191 186 } 192 187 193 188 func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult { 194 - if pull.State == db.PullMerged { 189 + if pull.State == db.PullMerged || pull.PullSource == nil { 195 190 return pages.Unknown 196 191 } 197 192 198 - if pull.PullSource == nil { 199 - return pages.Unknown 193 + var knot, ownerDid, repoName string 194 + 195 + if pull.PullSource.Repo != nil { 196 + // fork-based pulls 197 + sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.Repo.String()) 198 + if err != nil { 199 + log.Println("failed to get source repo", err) 200 + return pages.Unknown 201 + } 202 + 203 + knot = sourceRepo.Knot 204 + ownerDid = sourceRepo.Did 205 + repoName = sourceRepo.Name 206 + } else { 207 + // pulls within the same repo 208 + knot = f.Knot 209 + ownerDid = f.OwnerDid() 210 + repoName = f.RepoName 200 211 } 201 212 202 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 213 + us, err := NewUnsignedClient(knot, s.config.Dev) 203 214 if err != nil { 204 - log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err) 215 + log.Printf("failed to setup client for %s; ignoring: %v", knot, err) 205 216 return pages.Unknown 206 217 } 207 218 208 - resp, err := us.Branch(f.OwnerDid(), f.RepoName, pull.PullSource.Branch) 219 + resp, err := us.Branch(ownerDid, repoName, pull.PullSource.Branch) 209 220 if err != nil { 210 221 log.Println("failed to reach knotserver", err) 211 222 return pages.Unknown ··· 229 208 230 209 body, err := io.ReadAll(resp.Body) 231 210 if err != nil { 232 - log.Printf("Error reading response body: %v", err) 211 + log.Printf("error reading response body: %v", err) 233 212 return pages.Unknown 234 213 } 214 + defer resp.Body.Close() 235 215 236 216 var result types.RepoBranchResponse 237 - err = json.Unmarshal(body, &result) 238 - if err != nil { 217 + if err := json.Unmarshal(body, &result); err != nil { 239 218 log.Println("failed to parse response:", err) 240 219 return pages.Unknown 241 220 } 242 221 243 - if pull.Submissions[pull.LastRoundNumber()].SourceRev != result.Branch.Hash { 244 - log.Println(pull.Submissions[pull.LastRoundNumber()].SourceRev, result.Branch.Hash) 222 + latestSubmission := pull.Submissions[pull.LastRoundNumber()] 223 + if latestSubmission.SourceRev != result.Branch.Hash { 245 224 return pages.ShouldResubmit 246 - } else { 247 - return pages.ShouldNotResubmit 248 225 } 226 + 227 + return pages.ShouldNotResubmit 249 228 } 250 229 251 230 func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) { ··· 489 468 return 490 469 } 491 470 471 + forks, err := db.GetForksByDid(s.db, user.Did) 472 + if err != nil { 473 + log.Println("failed to get forks", err) 474 + return 475 + } 476 + 492 477 switch r.Method { 493 478 case http.MethodGet: 494 479 us, err := NewUnsignedClient(f.Knot, s.config.Dev) ··· 526 499 s.pages.RepoNewPull(w, pages.RepoNewPullParams{ 527 500 LoggedInUser: user, 528 501 RepoInfo: f.RepoInfo(s, user), 502 + Forks: forks, 529 503 Branches: result.Branches, 530 504 }) 531 505 case http.MethodPost: 532 - isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed() 533 506 title := r.FormValue("title") 534 507 body := r.FormValue("body") 535 508 targetBranch := r.FormValue("targetBranch") 509 + fromFork := r.FormValue("fromFork") 536 510 sourceBranch := r.FormValue("sourceBranch") 537 511 patch := r.FormValue("patch") 538 512 539 - isBranchBased := isPushAllowed && (sourceBranch != "") 513 + isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed() 514 + isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == "" 540 515 isPatchBased := patch != "" 516 + isForkBased := fromFork != "" && sourceBranch != "" 541 517 542 - if !isBranchBased && !isPatchBased { 518 + if !isBranchBased && !isPatchBased && !isForkBased { 543 519 s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.") 544 520 return 545 521 } ··· 557 527 return 558 528 } 559 529 560 - // TODO: check if knot has this capability 561 - var sourceRev string 562 - var pullSource *db.PullSource 563 - var recordPullSource *tangled.RepoPull_Source 564 530 if isBranchBased { 565 - pullSource = &db.PullSource{ 566 - Branch: sourceBranch, 567 - } 568 - recordPullSource = &tangled.RepoPull_Source{ 569 - Branch: sourceBranch, 570 - } 571 - // generate a patch using /compare 572 - ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 573 - if err != nil { 574 - log.Printf("failed to create signed client for %s: %s", f.Knot, err) 575 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 576 - return 577 - } 578 - 579 - resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch) 580 - switch resp.StatusCode { 581 - case 404: 582 - case 400: 583 - s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 584 - } 585 - 586 - respBody, err := io.ReadAll(resp.Body) 587 - if err != nil { 588 - log.Println("failed to compare across branches") 589 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 590 - } 591 - defer resp.Body.Close() 592 - 593 - var diffTreeResponse types.RepoDiffTreeResponse 594 - err = json.Unmarshal(respBody, &diffTreeResponse) 595 - if err != nil { 596 - log.Println("failed to unmarshal diff tree response", err) 597 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 598 - } 599 - 600 - sourceRev = diffTreeResponse.DiffTree.Rev2 601 - patch = diffTreeResponse.DiffTree.Patch 531 + s.handleBranchBasedPull(w, r, f, user, title, body, targetBranch, sourceBranch) 532 + } else if isPatchBased { 533 + s.handlePatchBasedPull(w, r, f, user, title, body, targetBranch, patch) 534 + } else if isForkBased { 535 + s.handleForkBasedPull(w, r, f, user, fromFork, title, body, targetBranch, sourceBranch) 602 536 } 603 - 604 - // Validate patch format 605 - if !isPatchValid(patch) { 606 - s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 607 - return 608 - } 609 - 610 - tx, err := s.db.BeginTx(r.Context(), nil) 611 - if err != nil { 612 - log.Println("failed to start tx") 613 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 614 - return 615 - } 616 - defer tx.Rollback() 617 - 618 - rkey := s.TID() 619 - initialSubmission := db.PullSubmission{ 620 - Patch: patch, 621 - SourceRev: sourceRev, 622 - } 623 - err = db.NewPull(tx, &db.Pull{ 624 - Title: title, 625 - Body: body, 626 - TargetBranch: targetBranch, 627 - OwnerDid: user.Did, 628 - RepoAt: f.RepoAt, 629 - Rkey: rkey, 630 - Submissions: []*db.PullSubmission{ 631 - &initialSubmission, 632 - }, 633 - PullSource: pullSource, 634 - }) 635 - if err != nil { 636 - log.Println("failed to create pull request", err) 637 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 638 - return 639 - } 640 - client, _ := s.auth.AuthorizedClient(r) 641 - pullId, err := db.NextPullId(s.db, f.RepoAt) 642 - if err != nil { 643 - log.Println("failed to get pull id", err) 644 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 645 - return 646 - } 647 - 648 - atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 649 - Collection: tangled.RepoPullNSID, 650 - Repo: user.Did, 651 - Rkey: rkey, 652 - Record: &lexutil.LexiconTypeDecoder{ 653 - Val: &tangled.RepoPull{ 654 - Title: title, 655 - PullId: int64(pullId), 656 - TargetRepo: string(f.RepoAt), 657 - TargetBranch: targetBranch, 658 - Patch: patch, 659 - Source: recordPullSource, 660 - }, 661 - }, 662 - }) 663 - 664 - err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri) 665 - if err != nil { 666 - log.Println("failed to get pull id", err) 667 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 668 - return 669 - } 670 - 671 - s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId)) 672 537 return 673 538 } 539 + } 540 + 541 + func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) { 542 + pullSource := &db.PullSource{ 543 + Branch: sourceBranch, 544 + } 545 + recordPullSource := &tangled.RepoPull_Source{ 546 + Branch: sourceBranch, 547 + } 548 + 549 + // Generate a patch using /compare 550 + ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 551 + if err != nil { 552 + log.Printf("failed to create signed client for %s: %s", f.Knot, err) 553 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 554 + return 555 + } 556 + 557 + resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch) 558 + switch resp.StatusCode { 559 + case 404: 560 + case 400: 561 + s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 562 + return 563 + } 564 + 565 + respBody, err := io.ReadAll(resp.Body) 566 + if err != nil { 567 + log.Println("failed to compare across branches") 568 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 569 + return 570 + } 571 + defer resp.Body.Close() 572 + 573 + var diffTreeResponse types.RepoDiffTreeResponse 574 + err = json.Unmarshal(respBody, &diffTreeResponse) 575 + if err != nil { 576 + log.Println("failed to unmarshal diff tree response", err) 577 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 578 + return 579 + } 580 + 581 + sourceRev := diffTreeResponse.DiffTree.Rev2 582 + patch := diffTreeResponse.DiffTree.Patch 583 + 584 + if !isPatchValid(patch) { 585 + s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 586 + return 587 + } 588 + 589 + s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource) 590 + } 591 + 592 + func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) { 593 + if !isPatchValid(patch) { 594 + s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 595 + return 596 + } 597 + 598 + s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil) 599 + } 600 + 601 + func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) { 602 + fork, err := db.GetForkByDid(s.db, user.Did, forkRepo) 603 + if errors.Is(err, sql.ErrNoRows) { 604 + s.pages.Notice(w, "pull", "No such fork.") 605 + return 606 + } else if err != nil { 607 + log.Println("failed to fetch fork:", err) 608 + s.pages.Notice(w, "pull", "Failed to fetch fork.") 609 + return 610 + } 611 + 612 + secret, err := db.GetRegistrationKey(s.db, fork.Knot) 613 + if err != nil { 614 + log.Println("failed to fetch registration key:", err) 615 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 616 + return 617 + } 618 + 619 + sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev) 620 + if err != nil { 621 + log.Println("failed to create signed client:", err) 622 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 623 + return 624 + } 625 + 626 + us, err := NewUnsignedClient(fork.Knot, s.config.Dev) 627 + if err != nil { 628 + log.Println("failed to create unsigned client:", err) 629 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 630 + return 631 + } 632 + 633 + resp, err := sc.NewHiddenRef(user.Did, fork.Name, sourceBranch, targetBranch) 634 + if err != nil { 635 + log.Println("failed to create hidden ref:", err, resp.StatusCode) 636 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 637 + return 638 + } 639 + 640 + switch resp.StatusCode { 641 + case 404: 642 + case 400: 643 + s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 644 + return 645 + } 646 + 647 + hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch)) 648 + // We're now comparing the sourceBranch (on the fork) against the hiddenRef which is tracking 649 + // the targetBranch on the target repository. This code is a bit confusing, but here's an example: 650 + // hiddenRef: hidden/feature-1/main (on repo-fork) 651 + // targetBranch: main (on repo-1) 652 + // sourceBranch: feature-1 (on repo-fork) 653 + diffResp, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch) 654 + if err != nil { 655 + log.Println("failed to compare across branches", err) 656 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 657 + return 658 + } 659 + 660 + respBody, err := io.ReadAll(diffResp.Body) 661 + if err != nil { 662 + log.Println("failed to read response body", err) 663 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 664 + return 665 + } 666 + 667 + defer resp.Body.Close() 668 + 669 + var diffTreeResponse types.RepoDiffTreeResponse 670 + err = json.Unmarshal(respBody, &diffTreeResponse) 671 + if err != nil { 672 + log.Println("failed to unmarshal diff tree response", err) 673 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 674 + return 675 + } 676 + 677 + sourceRev := diffTreeResponse.DiffTree.Rev2 678 + patch := diffTreeResponse.DiffTree.Patch 679 + 680 + if !isPatchValid(patch) { 681 + s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 682 + return 683 + } 684 + 685 + forkAtUri, err := syntax.ParseATURI(fork.AtUri) 686 + if err != nil { 687 + log.Println("failed to parse fork AT URI", err) 688 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 689 + return 690 + } 691 + 692 + s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{ 693 + Branch: sourceBranch, 694 + Repo: &forkAtUri, 695 + }, &tangled.RepoPull_Source{Branch: sourceBranch, Repo: &fork.AtUri}) 696 + } 697 + 698 + 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) { 699 + tx, err := s.db.BeginTx(r.Context(), nil) 700 + if err != nil { 701 + log.Println("failed to start tx") 702 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 703 + return 704 + } 705 + defer tx.Rollback() 706 + 707 + rkey := s.TID() 708 + initialSubmission := db.PullSubmission{ 709 + Patch: patch, 710 + SourceRev: sourceRev, 711 + } 712 + err = db.NewPull(tx, &db.Pull{ 713 + Title: title, 714 + Body: body, 715 + TargetBranch: targetBranch, 716 + OwnerDid: user.Did, 717 + RepoAt: f.RepoAt, 718 + Rkey: rkey, 719 + Submissions: []*db.PullSubmission{ 720 + &initialSubmission, 721 + }, 722 + PullSource: pullSource, 723 + }) 724 + if err != nil { 725 + log.Println("failed to create pull request", err) 726 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 727 + return 728 + } 729 + client, _ := s.auth.AuthorizedClient(r) 730 + pullId, err := db.NextPullId(s.db, f.RepoAt) 731 + if err != nil { 732 + log.Println("failed to get pull id", err) 733 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 734 + return 735 + } 736 + 737 + atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 738 + Collection: tangled.RepoPullNSID, 739 + Repo: user.Did, 740 + Rkey: rkey, 741 + Record: &lexutil.LexiconTypeDecoder{ 742 + Val: &tangled.RepoPull{ 743 + Title: title, 744 + PullId: int64(pullId), 745 + TargetRepo: string(f.RepoAt), 746 + TargetBranch: targetBranch, 747 + Patch: patch, 748 + Source: recordPullSource, 749 + }, 750 + }, 751 + }) 752 + 753 + err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri) 754 + if err != nil { 755 + log.Println("failed to get pull id", err) 756 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 757 + return 758 + } 759 + 760 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId)) 674 761 } 675 762 676 763 func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) { ··· 870 723 var sourceRev string 871 724 var recordPullSource *tangled.RepoPull_Source 872 725 873 - // this pull is a branch based pull 726 + var ownerDid, repoName, knotName string 727 + var isSameRepo bool = pull.IsSameRepoBranch() 728 + sourceBranch := pull.PullSource.Branch 729 + targetBranch := pull.TargetBranch 730 + recordPullSource = &tangled.RepoPull_Source{ 731 + Branch: sourceBranch, 732 + } 733 + 874 734 isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed() 875 - if pull.IsSameRepoBranch() && isPushAllowed { 876 - sourceBranch := pull.PullSource.Branch 877 - targetBranch := pull.TargetBranch 878 - recordPullSource = &tangled.RepoPull_Source{ 879 - Branch: sourceBranch, 880 - } 881 - // extract patch by performing compare 882 - ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 735 + if isSameRepo && isPushAllowed { 736 + ownerDid = f.OwnerDid() 737 + repoName = f.RepoName 738 + knotName = f.Knot 739 + } else if !isSameRepo { 740 + sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.Repo.String()) 883 741 if err != nil { 884 - log.Printf("failed to create signed client for %s: %s", f.Knot, err) 742 + log.Println("failed to get source repo", err) 743 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 744 + return 745 + } 746 + ownerDid = sourceRepo.Did 747 + repoName = sourceRepo.Name 748 + knotName = sourceRepo.Knot 749 + } 750 + 751 + if sourceBranch != "" && knotName != "" { 752 + // extract patch by performing compare 753 + ksClient, err := NewUnsignedClient(knotName, s.config.Dev) 754 + if err != nil { 755 + log.Printf("failed to create client for %s: %s", knotName, err) 885 756 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 886 757 return 887 758 } 888 759 889 - resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch) 890 - switch resp.StatusCode { 760 + if !isSameRepo { 761 + secret, err := db.GetRegistrationKey(s.db, knotName) 762 + if err != nil { 763 + log.Printf("failed to get registration key for %s: %s", knotName, err) 764 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 765 + return 766 + } 767 + // update the hidden tracking branch to latest 768 + signedClient, err := NewSignedClient(knotName, secret, s.config.Dev) 769 + if err != nil { 770 + log.Printf("failed to create signed client for %s: %s", knotName, err) 771 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 772 + return 773 + } 774 + resp, err := signedClient.NewHiddenRef(ownerDid, repoName, sourceBranch, targetBranch) 775 + if err != nil || resp.StatusCode != http.StatusNoContent { 776 + log.Printf("failed to update tracking branch: %s", err) 777 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 778 + return 779 + } 780 + } 781 + 782 + var compareResp *http.Response 783 + if !isSameRepo { 784 + hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch)) 785 + compareResp, err = ksClient.Compare(ownerDid, repoName, hiddenRef, sourceBranch) 786 + } else { 787 + compareResp, err = ksClient.Compare(ownerDid, repoName, targetBranch, sourceBranch) 788 + } 789 + if err != nil { 790 + log.Printf("failed to compare branches: %s", err) 791 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 792 + return 793 + } 794 + defer compareResp.Body.Close() 795 + 796 + switch compareResp.StatusCode { 891 797 case 404: 892 798 case 400: 893 799 s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 800 + return 894 801 } 895 802 896 - respBody, err := io.ReadAll(resp.Body) 803 + respBody, err := io.ReadAll(compareResp.Body) 897 804 if err != nil { 898 805 log.Println("failed to compare across branches") 899 806 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 807 + return 900 808 } 901 - defer resp.Body.Close() 809 + defer compareResp.Body.Close() 902 810 903 811 var diffTreeResponse types.RepoDiffTreeResponse 904 812 err = json.Unmarshal(respBody, &diffTreeResponse) 905 813 if err != nil { 906 814 log.Println("failed to unmarshal diff tree response", err) 907 815 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 816 + return 908 817 } 909 818 910 819 sourceRev = diffTreeResponse.DiffTree.Rev2 ··· 982 779 return 983 780 } 984 781 985 - // Validate patch format 986 782 if !isPatchValid(patch) { 987 783 s.pages.Notice(w, "resubmit-error", "Invalid patch format. Please provide a valid diff.") 988 784 return
+1 -1
appview/state/signer.go
··· 370 370 Method = "GET" 371 371 ) 372 372 373 - endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, rev1, rev2) 373 + endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2)) 374 374 375 375 req, err := us.newRequest(Method, endpoint, nil) 376 376 if err != nil {