An unofficial terminal client for Tangled, optimized for humans and agents. tgcli.wisp.place
cli atproto go tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161package app
import ( "context" "encoding/json" "fmt" "time"
"github.com/alyraffauf/tg/atproto" "github.com/alyraffauf/tg/internal/tangledlex" "github.com/bluesky-social/indigo/atproto/syntax" lexutil "github.com/bluesky-social/indigo/lex/util")
// CreatePullInput configures pull request creation.type CreatePullInput struct { RepoDir string // local git repository (for branch detection + patch) Title string Body string Base string // empty: detect origin's default branch Head string // empty: current branch Target Target Source *Target // nil: same as Target}
// pullRecordInput is the write-side input to newPullRecord.type pullRecordInput struct { Title string Body string TargetRepoDid string SourceRepoDid string Base string Head string Patch *atproto.Blob}
// CreatePull generates a patch from the local repository, uploads it, and// writes a pull record.func (s *Service) CreatePull(ctx context.Context, in CreatePullInput) (*PRCreateResult, error) { atClient, did, err := s.authenticatedPDS(ctx) if err != nil { return nil, err }
head := in.Head if head == "" { head, err = s.git.CurrentBranch(ctx, in.RepoDir) if err != nil { return nil, fmt.Errorf("determine source branch: %w", err) } } base := in.Base if base == "" { base, err = s.git.DefaultBranch(ctx, in.RepoDir) if err != nil { return nil, fmt.Errorf("determine target branch; set --base explicitly: %w", err) } }
target, err := s.resolveRepo(ctx, in.Target) if err != nil { return nil, err } if !atURIPrefix(target.URI) { return nil, fmt.Errorf("target repository %q has no strong at:// URI", in.Target.Repo) } source := target if in.Source != nil { source, err = s.resolveRepo(ctx, *in.Source) if err != nil { return nil, fmt.Errorf("resolve source repository: %w", err) } } if stringValue(source.Value.RepoDid) == "" { return nil, fmt.Errorf("source repository has no repo DID") }
patch, err := s.git.GeneratePatch(ctx, in.RepoDir, base, head) if err != nil { return nil, fmt.Errorf("generate pull request patch: %w", err) } blob, err := atClient.UploadBlob(ctx, patch, patchMimeType) if err != nil { return nil, err }
uri, err := createPullRecord(ctx, atClient, did, pullRecordInput{ Title: in.Title, Body: in.Body, TargetRepoDid: stringValue(target.Value.RepoDid), SourceRepoDid: stringValue(source.Value.RepoDid), Base: base, Head: head, Patch: blob, }) if err != nil { return nil, err } return &PRCreateResult{URI: uri, Title: in.Title, Base: base, Head: head}, nil}
func atURIPrefix(uri string) bool { return len(uri) >= 5 && uri[:5] == "at://" }
func createPullRecord(ctx context.Context, atClient pdsClient, did string, input pullRecordInput) (string, error) { record, err := newPullRecord(input, time.Now().UTC()) if err != nil { return "", err } uri, _, err := atClient.PutRecord(ctx, atproto.PutRecordInput{ Repo: did, Collection: pullCollection, Rkey: string(syntax.NewTIDNow(0)), Record: record, }) if err != nil { return "", fmt.Errorf("create pull request record: %w", err) } return uri, nil}
func newPullRecord(input pullRecordInput, createdAt time.Time) (tangledlex.RepoPull, error) { now := createdAt.Format(time.RFC3339) patchBlob, err := patchBlob(input.Patch) if err != nil { return tangledlex.RepoPull{}, err } return tangledlex.RepoPull{ LexiconTypeID: pullCollection, Title: input.Title, Body: optionalString(input.Body), CreatedAt: now, Target: &tangledlex.RepoPull_Target{ Repo: input.TargetRepoDid, Branch: input.Base, }, Source: &tangledlex.RepoPull_Source{ Repo: optionalString(input.SourceRepoDid), Branch: input.Head, }, Rounds: []*tangledlex.RepoPull_Round{{ CreatedAt: now, PatchBlob: &patchBlob, }}, }, nil}
func patchBlob(blob *atproto.Blob) (lexutil.LexBlob, error) { if blob == nil || blob.Ref == nil { return lexutil.LexBlob{}, nil } data, err := json.Marshal(blob) if err != nil { return lexutil.LexBlob{}, fmt.Errorf("encode pull patch blob: %w", err) } var result lexutil.LexBlob if err := json.Unmarshal(data, &result); err != nil { return lexutil.LexBlob{}, fmt.Errorf("decode pull patch blob: %w", err) } return result, nil}