A CLI for tangled operations.
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

Surface pull request merge checks

onevcat a30bf5f1 14892753

+39 -1
+1
ai-docs/2026-05-02-PHASE_3_repo_pr.md
··· 35 35 - Completed: `tang repo create tang-phase3-e2e-20260502143344 --knot knot1.tangled.sh ...` created `at://did:plc:kl2ejrmz5zmxnno3ll4luz76/sh.tangled.repo/3mkuueutmtb22`, and `tang repo view onev.cat/tang-phase3-e2e-20260502143344` found it. This empty repo is intentionally left as an E2E artifact because no repo delete command exists in scope. 36 36 - Completed: `tang pr list --state all --json=number,title,status,uri` against `tangled.org/core` returned 35 PRs. 37 37 - Completed: `tang pr view 1 --json=title,status,branch` against `tangled.org/core` returned a real PR. 38 + - Completed: `tang pr view 3mkuu6q672u22 --json=title,mergeable` on the Phase 3 E2E PR returned a merge status field. Live Knot `mergeCheck` returned a 502 decode error for that PR, so the CLI surfaces it as `unknown: ...` instead of silently omitting status. 38 39 - Completed: PR create/diff/comment/close/reopen E2E on `onev.cat/tang`: 39 40 - Created temporary branch `tang-phase3-e2e-20260502143010` from a worktree and pushed it. 40 41 - `tang pr create --base main --head tang-phase3-e2e-20260502143010 --json=uri,title,status,branch` created `at://did:plc:kl2ejrmz5zmxnno3ll4luz76/sh.tangled.repo.pull/3mkuu6q672u22`.
+10 -1
internal/cli/pr.go
··· 78 78 if web { 79 79 return openBrowser(strings.TrimRight(cfg.AppView.URL, "/") + "/" + context.Owner + "/" + context.Name + "/pulls/" + fmt.Sprint(pull.Number)) 80 80 } 81 + if repoRecord, err := tangled.NewRepoService(cfg, nil).GetRepo(cmd.Context(), context.Owner, context.Name); err == nil { 82 + if ownerDID, _, err := resolveRepoOwnerForCLI(cmd, context.Owner); err == nil { 83 + if mergeable, err := service.MergeCheck(cmd.Context(), *repoRecord, ownerDID, pull); err == nil { 84 + pull.Mergeable = mergeable 85 + } else { 86 + pull.Mergeable = "unknown: " + err.Error() 87 + } 88 + } 89 + } 81 90 if rendered, err := renderJSONIfRequested(cmd, opts, pull); rendered || err != nil { 82 91 return err 83 92 } 84 - _, err = fmt.Fprintf(cmd.OutOrStdout(), "Pull #%d %s\nTitle: %s\nAuthor: %s\nTarget: %s\nSource: %s\nURI: %s\n\n%s\n", pull.Number, pull.Status, pull.Title, pull.Author, pull.Target, pull.Branch, pull.URI, pull.Body) 93 + _, err = fmt.Fprintf(cmd.OutOrStdout(), "Pull #%d %s\nTitle: %s\nAuthor: %s\nTarget: %s\nSource: %s\nMerge: %s\nURI: %s\n\n%s\n", pull.Number, pull.Status, pull.Title, pull.Author, pull.Target, pull.Branch, pull.Mergeable, pull.URI, pull.Body) 85 94 return err 86 95 }, 87 96 }
+4
internal/tangled/knot_client.go
··· 57 57 func (c *KnotClient) Merge(ctx context.Context, input *core.RepoMerge_Input) error { 58 58 return core.RepoMerge(ctx, c.client, input) 59 59 } 60 + 61 + func (c *KnotClient) MergeCheck(ctx context.Context, input *core.RepoMergeCheck_Input) (*core.RepoMergeCheck_Output, error) { 62 + return core.RepoMergeCheck(ctx, c.client, input) 63 + }
+24
internal/tangled/pulls.go
··· 36 36 Target string `json:"target"` 37 37 Source string `json:"source,omitempty"` 38 38 Branch string `json:"branch,omitempty"` 39 + Mergeable string `json:"mergeable,omitempty"` 39 40 } 40 41 41 42 type PullCreateOptions struct { ··· 265 266 return "", err 266 267 } 267 268 return string(out), nil 269 + } 270 + 271 + func (s *PullService) MergeCheck(ctx context.Context, repo Repo, ownerDID string, pull Pull) (string, error) { 272 + patch, err := s.FetchPullPatch(ctx, pull.URI) 273 + if err != nil { 274 + return "", err 275 + } 276 + out, err := NewKnotClient(repo.Knot, WithKnotHTTPClient(s.HTTPClient)).MergeCheck(ctx, &core.RepoMergeCheck_Input{ 277 + Did: ownerDID, 278 + Name: repo.Name, 279 + Branch: pull.Target, 280 + Patch: patch, 281 + }) 282 + if err != nil { 283 + return "", err 284 + } 285 + if out.Is_conflicted { 286 + return "conflicted", nil 287 + } 288 + if out.Error != nil && *out.Error != "" { 289 + return "error: " + *out.Error, nil 290 + } 291 + return "clean", nil 268 292 } 269 293 270 294 func (s *PullService) getPullByParts(ctx context.Context, did, collection, rkey string) (*Pull, error) {