package pulls import ( "fmt" "net/http" "time" "golang.org/x/sync/errgroup" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/knotcompat" "tangled.org/core/appview/models" "tangled.org/core/appview/oauth" "tangled.org/core/appview/reporesolver" "tangled.org/core/tid" comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/syntax" ) func (s *Pulls) handlePull( w http.ResponseWriter, r *http.Request, userDid syntax.DID, targetRepo *models.Repo, targetBranch string, sourceRepo *models.Repo, sourceBranch string, title, body string, ) { l := s.logger.With("handler", "handlePull", "user", userDid) ctx := r.Context() client, err := s.oauth.AuthorizedClient(r) if err != nil { l.Error("failed to get authorized client", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } // 1. fetch heads of source & target branches var base, head string { xrpcc := s.knotMirrorXRPC g, gctx := errgroup.WithContext(ctx) g.Go(func() error { // find merge-base between targetBranch & sourceBranch out, err := tangled.GitTempGetMergeBase(gctx, xrpcc, targetBranch, sourceBranch, sourceRepo.RepoDid) if err != nil { return err } base = out.Commit return nil }) g.Go(func() error { out, err := tangled.GitTempGetBranch(gctx, xrpcc, sourceBranch, sourceRepo.RepoDid) if err != nil { return err } head = out.Hash return nil }) if err := g.Wait(); err != nil { l.Error("failed to fetch branch heads", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } } created := time.Now() pull := &models.Pull{ ID: -1, // uninitialized OwnerDid: userDid, Rkey: syntax.RecordKey(tid.TID()), Cid: "", // uninitialized RepoDid: syntax.DID(targetRepo.RepoDid), PullId: 0, // uninitialized Title: title, Body: body, TargetBranch: targetBranch, SourceRepo: syntax.DID(sourceRepo.RepoDid), SourceBranch: &sourceBranch, Versions: []models.PullVersion{ { ID: 0, Base: base, Head: head, Created: created, }, }, Created: created, State: models.PullOpen, Repo: targetRepo, } // 2. call git.keepCommit { client, err := s.oauth.ServiceClient( r, oauth.WithService(sourceRepo.Knot), oauth.WithLxm(tangled.GitKeepCommitNSID), oauth.WithDev(s.config.Core.Dev), ) if err != nil { l.Error("failed to comment to knot", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } _, err = tangled.GitKeepCommit(ctx, client, &tangled.GitKeepCommit_Input{ Repo: sourceRepo.RepoDid, Record: pull.AtUri().String(), Source: &tangled.GitKeepCommit_Input_Source{ GitKeepCommit_Commit: &tangled.GitKeepCommit_Commit{ Repo: sourceRepo.RepoDid, Oid: head, }, }, }) if err != nil { l.Error("failed to keep commit", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } } // 3. create PR record record := pull.AsRecord() out, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.RepoPullNSID, Repo: userDid.String(), Rkey: pull.Rkey.String(), Record: knotcompat.Pull(&record), }) pull.Cid = syntax.CID(out.Cid) tx, err := s.db.BeginTx(r.Context(), nil) if err != nil { l.Error("failed to start tx", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } defer tx.Rollback() var references []syntax.ATURI if pull.Body != "" { _, references = s.mentionsResolver.Resolve(ctx, pull.Body) } if err := db.PutPull(r.Context(), tx, pull, references); err != nil { l.Error("failed to create pull request in database", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } if err = tx.Commit(); err != nil { l.Error("failed to commit transaction for pull request", "err", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } s.notifier.NewPull(r.Context(), pull) s.applyCreationLabels(r.Context(), client, userDid, pull, r.Form, targetRepo) ownerSlashRepo := reporesolver.GetBaseRepoPath(r, targetRepo) s.pages.HxRedirect(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId)) }