Skip to content

feat(tangled): add scm and pullrequest support#8965

Open
shanduur wants to merge 3 commits into
updatecli:mainfrom
shanduur:tangled
Open

feat(tangled): add scm and pullrequest support#8965
shanduur wants to merge 3 commits into
updatecli:mainfrom
shanduur:tangled

Conversation

@shanduur

@shanduur shanduur commented May 24, 2026

Copy link
Copy Markdown

Fix #8678

Test

To test this pull request, you can run the following commands:

cd <to_package_directory>
go test

Additional Information

Checklist

Tradeoff

Potential improvement

Add Tangled (AT protocol git host) as an scm kind plus a
tangled/pullrequest action. Pull requests are written as
sh.tangled.repo.pull records on the author's PDS using an atproto
app password session; patches go up as gzipped blobs.

  • scm clone over HTTPS, push over SSH (knot rejects HTTPS push)
  • reuse core lex types (tangled.org/core/api/tangled) and indigo's atclient/comatproto/syntax to avoid duplicating the schema
  • idempotent CreateAction: listRecords on the author DID detects open PRs for the same target repo + branch pair

Signed-off-by: Mateusz Urbanek [email protected]

Add Tangled (AT protocol git host) as an scm kind plus a
tangled/pullrequest action. Pull requests are written as
sh.tangled.repo.pull records on the author's PDS using an atproto
app password session; patches go up as gzipped blobs.

- scm clone over HTTPS, push over SSH (knot rejects HTTPS push)
- reuse core lex types (tangled.org/core/api/tangled) and indigo's
  atclient/comatproto/syntax to avoid duplicating the schema
- idempotent CreateAction: listRecords on the author DID detects
  open PRs for the same target repo + branch pair

Signed-off-by: Mateusz Urbanek <[email protected]>
@shanduur shanduur marked this pull request as ready for review May 25, 2026 19:51
@shanduur

Copy link
Copy Markdown
Author

Tested on my Homelab repo: https://tangled.org/shanduur.net/homelab

@olblak

olblak commented May 26, 2026

Copy link
Copy Markdown
Member

Thanks for the pull request, I'll try to test this in the coming days

return "", fmt.Errorf("scm working directory is empty")
}
revRange := fmt.Sprintf("%s..%s", t.TargetBranch, t.SourceBranch)
cmd := exec.Command("git", "-C", dir, "format-patch", "--stdout", revRange)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid relying on this git command? As it would bring a dependency on the git command
Excepted from some autodiscovery plugin, Updatecli is a self-contained binary, and I would prefer to keep it this way.

}

// generateFormatPatch runs `git format-patch` between target and source branches.
func (t *Tangled) generateFormatPatch() (string, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this sounds a nice thing, this introduce a critical dependency on the git command and could probably be remove

@olblak

olblak commented Jun 2, 2026

Copy link
Copy Markdown
Member

@shanduur while testing this pullrequest, I notice that pullrequest is not using the Updatecli http client so I just added one commit to fix

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 7 comments.

Comment on lines +232 to +243
// Resolve once now to cache knot + repoDid; per-action PDS lookups would
// otherwise burst com.atproto.server.createSession rate limits.
{
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := t.resolveRepoRecord(ctx); err != nil {
if t.Spec.Knot == "" && t.Spec.CloneURL == "" {
return nil, fmt.Errorf("resolve repo record for %s/%s: %w", t.Spec.Owner, t.Spec.Repository, err)
}
logrus.Debugf("tangled: repo record lookup failed (continuing with explicit spec): %s", err)
}
}
Comment on lines +121 to +130
// "cloneURL" optionally overrides the URL used to clone the repository.
//
// default:
// https://<knot>/<owner>/<repository>
//
// remark:
// Tangled knots reject pushes over HTTPS. When updatecli needs to push a
// working branch, set this to an SSH URL such as
// git@<knot>:<owner>/<repository> and ensure SSH agent forwarding is
// available.
Comment on lines +61 to +66
sshHost := t.Spec.Knot
if sshHost == "knot1.tangled.sh" || sshHost == "" {
sshHost = "tangled.org"
}
return fmt.Sprintf("git@%s:%s/%s", sshHost, t.Spec.Owner, t.Spec.Repository)
}
Comment on lines +53 to +65
func New(s Spec) (*Client, error) {
key := cacheKey(s)

clientCacheMu.Lock()
defer clientCacheMu.Unlock()
if existing, ok := clientCache[key]; ok {
return existing, nil
}

c := &Client{spec: s}
clientCache[key] = c
return c, nil
}
Comment on lines +19 to +42
func (t *Tangled) inheritFromScm() {
if t.scm != nil {
_, t.SourceBranch, t.TargetBranch = t.scm.GetBranches()
t.Knot = t.scm.Spec.Knot
t.Owner = t.scm.Spec.Owner
t.Repository = t.scm.Spec.Repository
}

if t.spec.SourceBranch != "" {
t.SourceBranch = t.spec.SourceBranch
}
if t.spec.TargetBranch != "" {
t.TargetBranch = t.spec.TargetBranch
}
if t.spec.Knot != "" {
t.Knot = t.spec.Knot
}
if t.spec.Owner != "" {
t.Owner = t.spec.Owner
}
if t.spec.Repository != "" {
t.Repository = t.spec.Repository
}
}
Comment on lines +47 to +58
func (t *Tangled) repoOverridesScm() bool {
if t.scm == nil {
return false
}
if t.spec.Owner != "" && t.spec.Owner != t.scm.Spec.Owner {
return true
}
if t.spec.Repository != "" && t.spec.Repository != t.scm.Spec.Repository {
return true
}
return false
}
Comment on lines +51 to +55
// New returns a Client. Clients constructed with the same (PDS, identifier,
// appview) triple share an authenticated atproto session.
func New(s Spec) (*Client, error) {
key := cacheKey(s)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add support for Tangled

3 participants