package pulls import ( "context" "fmt" "net/http" "time" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/oauth" "tangled.org/core/appview/reporesolver" comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/syntax" lexutil "github.com/bluesky-social/indigo/lex/util" ) func (s *Pulls) ResubmitPull(w http.ResponseWriter, r *http.Request) { l := s.logger.With("handler", "ResubmitPull") noticeId := "pull-action-error" user := s.oauth.GetMultiAccountUser(r) if user != nil { l = l.With("user", user.Did) } repo, err := s.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) return } pull, ok := r.Context().Value("pull").(*models.Pull) if !ok { l.Error("failed to get pull") s.pages.Notice(w, noticeId, "Failed to get PR. Try again later.") return } l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid) if pull.OwnerDid != syntax.DID(user.Did) { s.pages.Notice(w, noticeId, "Unauthorized user. Try again later.") return } if pull.SourceBranch == nil { // can't resubmit if source is unknown. fail earlier s.pages.Notice(w, noticeId, "PR source branch is unknown.") return } sourceBranch := *pull.SourceBranch var sourceRepo *models.Repo if pull.SourceRepo == syntax.DID(repo.RepoDid) { sourceRepo = repo } else { var err error sourceRepo, err = db.GetRepoByDid(s.db, pull.SourceRepo.String()) if err != nil { s.pages.Notice(w, noticeId, fmt.Sprintf("Unknown source repository: %q", pull.SourceRepo)) return } } ctx := r.Context() var base, head string { xrpcc := s.knotMirrorXRPC branch, err := tangled.GitTempGetBranch(ctx, xrpcc, sourceBranch, pull.RepoDid.String()) if err != nil { s.pages.Notice(w, noticeId, "Failed to get source branch") return } out, err := tangled.GitTempGetMergeBase(ctx, xrpcc, pull.TargetBranch, branch.Hash, pull.RepoDid.String()) if err != nil { s.pages.Notice(w, noticeId, "Failed to compute merge-base.") return } head = branch.Hash base = out.Commit } newVersion := models.PullVersion{ ID: pull.LatestVersionNumber() + 1, Base: base, Head: head, Created: time.Now(), } pull.Versions = append(pull.Versions, newVersion) // keep new version head in Knot { 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 create service auth", "err", err) s.pages.Notice(w, noticeId, "Failed to create service auth. 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, noticeId, "Failed to resubmit pull request. Try again later.") return } } // update PDS record { client, err := s.oauth.AuthorizedClient(r) if err != nil { s.pages.Notice(w, noticeId, "Unauthorized user. Try again later.") return } // NOTE: some old PR records are missing CID if err := s.ensurePullCid(ctx, client, pull); err != nil { s.pages.Notice(w, noticeId, "Failed to get existing PR record. Is PR deleted from PDS?") return } record := pull.AsRecord() exCid := pull.Cid.String() out, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.RepoPullNSID, Repo: pull.OwnerDid.String(), Rkey: pull.Rkey.String(), SwapRecord: &exCid, Record: &lexutil.LexiconTypeDecoder{Val: &record}, }) if err != nil { l.Error("failed to create PDS record", "err", err) s.pages.Notice(w, noticeId, "Failed to resubmit pull request. Try again later.") return } pull.Cid = syntax.CID(out.Cid) } if err := db.SubmitPullVersion(ctx, s.db, pull.AtUri(), newVersion); err != nil { l.Error("failed to update PR in DB", "err", err) s.pages.Notice(w, noticeId, "Failed to resubmit pull request. Try again later.") return } s.notifier.ResubmitPull(r.Context(), pull) ownerSlashRepo := reporesolver.GetBaseRepoPath(r, repo) s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId)) } func (s *Pulls) ensurePullCid(ctx context.Context, client lexutil.LexClient, pull *models.Pull) error { out, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, pull.OwnerDid.String(), pull.Rkey.String()) if err != nil { return err } if out.Cid == nil { return fmt.Errorf("record CID is empty") } pull.Cid = syntax.CID(*out.Cid) return nil }