package pulls import ( "fmt" "net/http" "time" "github.com/bluesky-social/indigo/atproto/syntax" "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" "tangled.org/core/orm" ) func (s *Pulls) MergePull(w http.ResponseWriter, r *http.Request) { l := s.logger.With("handler", "MergePull") user := s.oauth.GetMultiAccountUser(r) if user == nil { l.Error("nil user") s.pages.Notice(w, "pull-action-error", "You must be logged in to merge this pull.") return } l = l.With("user", user.Did) f, err := s.repoResolver.Resolve(r) if err != nil { l.Error("failed to resolve repo", "err", err) s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") return } l = l.With("repo", f.RepoDid) pull, ok := r.Context().Value("pull").(*models.Pull) if !ok { l.Error("failed to get pull") s.pages.Notice(w, "pull-action-error", "Failed to merge patch. Try again later.") return } l = l.With("pull_id", pull.PullId, "target_branch", pull.TargetBranch) // merge target branch { client, err := s.oauth.ServiceClient( r, oauth.WithService(f.Knot), oauth.WithLxm(tangled.GitMergeCommitNSID), oauth.WithDev(s.config.Core.Dev), oauth.WithTimeout(time.Second*20), // merge is quite slow on large repos, like witchsky ) if err != nil { l.Error("failed to connect to knot server", "err", err, "knot", f.Knot) s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") return } var mergeCommit *tangled.GitMergeCommit_Input_MergeCommit // TODO: pass custom merge commit body err = tangled.GitMergeCommit(r.Context(), client, &tangled.GitMergeCommit_Input{ Target: &tangled.GitMergeCommit_Input_Target{ Repo: pull.RepoDid.String(), Branch: pull.TargetBranch, }, Source: &tangled.GitMergeCommit_Input_Source{ Repo: pull.SourceRepo.String(), Commit: pull.LatestVersion().Head, }, MergeCommit: mergeCommit, Style: "rebase", }) if err != nil { s.logger.Error("failed to merge", "err", err) s.pages.Notice(w, "pull-action-error", err.Error()) return } } if err := s.writePullStatusRecord(r, user.Did, pull.AtUri(), models.StateMerged); err != nil { l.Error("failed to write issue state record", "err", err) s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") return } tx, err := s.db.Begin() if err != nil { l.Error("failed to start transaction", "err", err) s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") return } defer tx.Rollback() err = db.MergePulls( tx, orm.FilterEq("at_uri", pull.AtUri()), ) if err != nil { l.Error("failed to update pull request status in database", "err", err) s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") return } err = tx.Commit() if err != nil { // TODO: this is unsound, we should also revert the merge from the knotserver here l.Error("failed to commit merge transaction", "err", err) s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.") return } s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), pull) ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId)) }