Monorepo for Tangled tangled.org
3

Configure Feed

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

knotserver: create a fork PR link

Signed-off-by: Will Andrews <did:plc:dadhhalkfcq3gucaq25hjqon>

author willdot.net committer
Seongmin Lee
date (May 22, 2026, 8:02 PM +0900) commit f2e69cb0 parent e636df2c change-id ztyompqr
+148 -7
+22
knotserver/git/git.go
··· 13 13 "time" 14 14 15 15 "github.com/go-git/go-git/v5" 16 + gogit "github.com/go-git/go-git/v5" 16 17 "github.com/go-git/go-git/v5/config" 17 18 "github.com/go-git/go-git/v5/plumbing" 18 19 "github.com/go-git/go-git/v5/plumbing/object" ··· 279 280 } 280 281 281 282 return strings.TrimSpace(string(output)), nil 283 + } 284 + 285 + func (g *GitRepo) Remote() (string, error) { 286 + remote, err := g.r.Remote("origin") 287 + if errors.Is(err, gogit.ErrRemoteNotFound) { 288 + return "", nil 289 + } 290 + if err != nil { 291 + return "", err 292 + } 293 + 294 + if remote == nil { 295 + return "", nil 296 + } 297 + 298 + urls := remote.Config().URLs 299 + if len(urls) == 0 { 300 + return "", nil 301 + } 302 + 303 + return urls[0], nil 282 304 } 283 305 284 306 // WriteTar writes itself from a tree into a binary tar file format.
+67 -7
knotserver/internal.go
··· 8 8 "net/http" 9 9 "net/url" 10 10 "os" 11 + "path" 11 12 "path/filepath" 12 13 "strings" 13 14 ··· 460 461 return err 461 462 } 462 463 464 + remote, err := gr.Remote() 465 + if err != nil { 466 + return fmt.Errorf("checking for upstream remote: %w", err) 467 + } 468 + 463 469 defaultBranch, err := gr.FindMainBranch() 464 470 if err != nil { 465 471 return err ··· 478 484 user = userIdent.Handle.String() 479 485 } 480 486 481 - query := url.Values{} 482 - query.Set("source", "branch") 483 - query.Set("sourceBranch", pushedBranch) 484 - query.Set("targetBranch", defaultBranch) 485 - 486 - basePath, err := url.JoinPath(h.c.AppViewEndpoint, user, repoName, "pulls", "new") 487 + pullURL, err := h.createPullURL(h.c.AppViewEndpoint, remote, user, ownerDid, repoName, pushedBranch, defaultBranch) 487 488 if err != nil { 488 489 return err 489 490 } 490 - pullURL := basePath + "?" + query.Encode() 491 491 492 492 ZWS := "\u200B" 493 493 *clientMsgs = append(*clientMsgs, ZWS) ··· 495 495 *clientMsgs = append(*clientMsgs, " "+pullURL) 496 496 *clientMsgs = append(*clientMsgs, ZWS) 497 497 return nil 498 + } 499 + 500 + func (h *InternalHandle) createPullURL(appviewURL, remote, user, ownerDID, repoName, pushedBranch, defaultBranch string) (string, error) { 501 + if remote != "" { 502 + return h.createForkPullURL(appviewURL, remote, ownerDID, repoName, pushedBranch, defaultBranch) 503 + } 504 + 505 + query := url.Values{} 506 + 507 + query.Set("source", "branch") 508 + query.Set("sourceBranch", pushedBranch) 509 + query.Set("targetBranch", defaultBranch) 510 + 511 + basePath, err := url.JoinPath(appviewURL, user, repoName, "pulls", "new") 512 + if err != nil { 513 + return "", err 514 + } 515 + pullURL := basePath + "?" + query.Encode() 516 + return pullURL, nil 517 + } 518 + 519 + func (h *InternalHandle) createForkPullURL(appviewURL, remote, ownerDID, repoName, pushedBranch, defaultBranch string) (string, error) { 520 + query := url.Values{} 521 + 522 + query.Set("fork", fmt.Sprintf("%s/%s", ownerDID, repoName)) 523 + query.Set("source", "fork") 524 + query.Set("sourceBranch", pushedBranch) 525 + query.Set("targetBranch", defaultBranch) 526 + 527 + repoPath, err := h.getRemoteOwnerRepoNamePath(remote) 528 + if err != nil { 529 + return "", err 530 + } 531 + 532 + basePath, err := url.JoinPath(appviewURL, repoPath, "pulls", "new") 533 + if err != nil { 534 + return "", err 535 + } 536 + pullURL := basePath + "?" + query.Encode() 537 + return pullURL, nil 538 + } 539 + 540 + func (h *InternalHandle) getRemoteOwnerRepoNamePath(remote string) (string, error) { 541 + u, err := url.Parse(remote) 542 + if err != nil { 543 + return "", fmt.Errorf("invalid remote: %w", err) 544 + } 545 + 546 + if u.Scheme != "file" { 547 + return u.Path, nil 548 + } 549 + 550 + repoDid := path.Base(u.String()) 551 + 552 + owner, name, err := h.db.GetRepoKeyOwner(repoDid) 553 + if err != nil { 554 + return "", err 555 + } 556 + 557 + return fmt.Sprintf("%s/%s", owner, name), nil 498 558 } 499 559 500 560 func Internal(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, n *notifier.Notifier, res *idresolver.Resolver) http.Handler {
+59
knotserver/internal_test.go
··· 1 + package knotserver 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/alecthomas/assert/v2" 7 + "github.com/stretchr/testify/require" 8 + "tangled.org/core/knotserver/db" 9 + ) 10 + 11 + const ( 12 + appviewURL = "https://tangled.org/" 13 + user = "willdot.net" 14 + userDID = "did:plc:dadhhalkfcq3gucaq25hjqon" 15 + pushedBranch = "feature-abc" 16 + defaultBranch = "main" 17 + ) 18 + 19 + func TestCreatePullURL(t *testing.T) { 20 + 21 + tt := map[string]struct { 22 + repoName string 23 + remote string 24 + expectedURL string 25 + }{ 26 + "not a fork": { 27 + repoName: "knot-testing", 28 + remote: "", 29 + expectedURL: "https://tangled.org/willdot.net/knot-testing/pulls/new?source=branch&sourceBranch=feature-abc&targetBranch=main", 30 + }, 31 + "is fork": { 32 + repoName: "knot-testing-fork", 33 + remote: "https://knot1.tangled.sh/did:plc:dadhhalkfcq3gucaq25hjqon/knot-testing", 34 + expectedURL: "https://tangled.org/did:plc:dadhhalkfcq3gucaq25hjqon/knot-testing/pulls/new?fork=did%3Aplc%3Adadhhalkfcq3gucaq25hjqon%2Fknot-testing-fork&source=fork&sourceBranch=feature-abc&targetBranch=main", 35 + }, 36 + "is fork on same knot": { 37 + repoName: "knot-testing-fork", 38 + remote: "file:///home/git/repositories/did:plc:ixran6dpypl5lslliiqceshs", 39 + expectedURL: "https://tangled.org/did:plc:dadhhalkfcq3gucaq25hjqon/knot-testing/pulls/new?fork=did%3Aplc%3Adadhhalkfcq3gucaq25hjqon%2Fknot-testing-fork&source=fork&sourceBranch=feature-abc&targetBranch=main", 40 + }, 41 + } 42 + 43 + for name, tc := range tt { 44 + t.Run(name, func(t *testing.T) { 45 + database, err := db.Setup(t.Context(), ":memory:") 46 + require.NoError(t, err) 47 + err = database.StoreRepoKey("did:plc:ixran6dpypl5lslliiqceshs", []byte{}, "did:plc:dadhhalkfcq3gucaq25hjqon", "knot-testing", "at://uri") 48 + require.NoError(t, err) 49 + 50 + h := InternalHandle{ 51 + db: database, 52 + } 53 + res, err := h.createPullURL(appviewURL, tc.remote, user, userDID, tc.repoName, pushedBranch, defaultBranch) 54 + require.NoError(t, err) 55 + 56 + assert.Equal(t, tc.expectedURL, res) 57 + }) 58 + } 59 + }