Something went wrong. Try again.
A terminal client for Tangled. forked from aly.codes/tg
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214package cli
import ( "context" "fmt" "os" "strings" "time"
"github.com/alyraffauf/tg/atproto" "github.com/alyraffauf/tg/internal/gitutil" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/spf13/cobra")
const patchMimeType = "application/gzip"
var ( prCreateTitle string prCreateBody string prCreateBodyFile string prCreateBase string prCreateHead string prCreateRepo string)
var prCreateCmd = &cobra.Command{ Use: "create", Short: "Create a pull request from the current branch", Long: "Create a pull request by uploading a gzipped git patch and writing a sh.tangled.repo.pull record. " + "The source and target repository are the same. By default, the current branch is the source and " + "origin's default branch is the target. Use --repo to target a different Tangled repository.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() if auth == nil || !auth.IsAuthenticated() { return fmt.Errorf("not logged in; run \"tg auth login\" first") }
repoDir, err := os.Getwd() if err != nil { return fmt.Errorf("get current directory: %w", err) } head, err := prSourceBranch(ctx, repoDir) if err != nil { return err } base, err := prTargetBranch(ctx, repoDir) if err != nil { return err } body, err := commandBody(prCreateBody, prCreateBodyFile) if err != nil { return err }
targetArgs := []string{} if prCreateRepo != "" { targetArgs = []string{prCreateRepo} } handle, repo, err := resolveTarget(ctx, targetArgs) if err != nil { return err } target, err := resolveRepoRecord(ctx, handle, repo) if err != nil { return err } if !strings.HasPrefix(target.URI, "at://") { return fmt.Errorf("target repository %q has no strong at:// URI", repo) }
patch, err := gitutil.GeneratePatch(ctx, repoDir, base, head) if err != nil { return fmt.Errorf("generate pull request patch: %w", err) } pds, err := auth.APIClient(ctx) if err != nil { return fmt.Errorf("get auth client: %w", err) } atClient := &atproto.ATProto{Client: pds} blob, err := atClient.UploadBlob(ctx, patch, patchMimeType) if err != nil { return err }
uri, err := createPullRecord(ctx, atClient, auth.CurrentDID().String(), prCreateRecord{ Title: prCreateTitle, Body: body, RepoDid: target.Value.RepoDid, Base: base, Head: head, Patch: blob, }) if err != nil { return err } result := prCreateResult{URI: uri, Title: prCreateTitle, Base: base, Head: head} return output(result, func(created prCreateResult) { fmt.Printf("Created pull request %s (%s -> %s)\n", created.URI, created.Head, created.Base) }) },}
func init() { prCreateCmd.Flags().StringVarP(&prCreateTitle, "title", "t", "", "Pull request title") prCreateCmd.Flags().StringVarP(&prCreateBody, "body", "b", "", "Pull request body") prCreateCmd.Flags().StringVarP(&prCreateBodyFile, "body-file", "F", "", "Read pull request body from file") prCreateCmd.Flags().StringVarP(&prCreateBase, "base", "B", "", "Target branch (default: origin's default branch)") prCreateCmd.Flags().StringVarP(&prCreateHead, "head", "H", "", "Source branch (default: current branch)") prCreateCmd.Flags().StringVarP(&prCreateRepo, "repo", "R", "", "Target repository as handle/repo") prCreateCmd.MarkFlagRequired("title")}
type prCreateRecord struct { Title string Body string RepoDid string Base string Head string Patch *atproto.Blob}
// pullRecord is the sh.tangled.repo.pull lexicon shape used for record writes.type pullRecord struct { Type string `json:"$type"` Title string `json:"title"` Body string `json:"body,omitempty"` CreatedAt string `json:"createdAt"` Target pullTarget `json:"target"` Source pullSource `json:"source"` Rounds []pullRound `json:"rounds"`}
type pullTarget struct { Repo string `json:"repo"` RepoDid string `json:"repoDid"` Branch string `json:"branch"`}
type pullSource struct { Repo string `json:"repo"` RepoDid string `json:"repoDid"` Branch string `json:"branch"`}
type pullRound struct { CreatedAt string `json:"createdAt"` PatchBlob *atproto.Blob `json:"patchBlob"`}
type prCreateResult struct { URI string `json:"uri"` Title string `json:"title"` Base string `json:"base"` Head string `json:"head"`}
func prSourceBranch(ctx context.Context, repoDir string) (string, error) { if prCreateHead != "" { return prCreateHead, nil } branch, err := gitutil.CurrentBranch(ctx, repoDir) if err != nil { return "", fmt.Errorf("determine source branch: %w", err) } return branch, nil}
func prTargetBranch(ctx context.Context, repoDir string) (string, error) { if prCreateBase != "" { return prCreateBase, nil } branch, err := gitutil.DefaultBranch(ctx, repoDir) if err != nil { return "", fmt.Errorf("determine target branch; set --base explicitly: %w", err) } return branch, nil}
func createPullRecord(ctx context.Context, atClient *atproto.ATProto, did string, input prCreateRecord) (string, error) { now := time.Now().UTC().Format(time.RFC3339) record := pullRecord{ Type: "sh.tangled.repo.pull", Title: input.Title, Body: input.Body, CreatedAt: now, Target: pullTarget{ Repo: input.RepoDid, RepoDid: input.RepoDid, Branch: input.Base, }, Source: pullSource{ Repo: input.RepoDid, RepoDid: input.RepoDid, Branch: input.Head, }, Rounds: []pullRound{{ CreatedAt: now, PatchBlob: input.Patch, }}, } uri, _, err := atClient.PutRecord(ctx, atproto.PutRecordInput{ Repo: did, Collection: "sh.tangled.repo.pull", Rkey: string(syntax.NewTIDNow(0)), Record: record, }) if err != nil { return "", fmt.Errorf("create pull request record: %w", err) } return uri, nil}