+4
appview/db/pulls.go
+4
appview/db/pulls.go
+87
appview/db/repos.go
+87
appview/db/repos.go
···
190
return nullableSource.String, nil
191
}
192
193
+
func GetForksByDid(e Execer, did string) ([]Repo, error) {
194
+
var repos []Repo
195
+
196
+
rows, err := e.Query(
197
+
`select did, name, knot, rkey, description, created, at_uri, source
198
+
from repos
199
+
where did = ? and source is not null and source != ''
200
+
order by created desc`,
201
+
did,
202
+
)
203
+
if err != nil {
204
+
return nil, err
205
+
}
206
+
defer rows.Close()
207
+
208
+
for rows.Next() {
209
+
var repo Repo
210
+
var createdAt string
211
+
var nullableDescription sql.NullString
212
+
var nullableSource sql.NullString
213
+
214
+
err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource)
215
+
if err != nil {
216
+
return nil, err
217
+
}
218
+
219
+
if nullableDescription.Valid {
220
+
repo.Description = nullableDescription.String
221
+
}
222
+
223
+
if nullableSource.Valid {
224
+
repo.Source = nullableSource.String
225
+
}
226
+
227
+
createdAtTime, err := time.Parse(time.RFC3339, createdAt)
228
+
if err != nil {
229
+
repo.Created = time.Now()
230
+
} else {
231
+
repo.Created = createdAtTime
232
+
}
233
+
234
+
repos = append(repos, repo)
235
+
}
236
+
237
+
if err := rows.Err(); err != nil {
238
+
return nil, err
239
+
}
240
+
241
+
return repos, nil
242
+
}
243
+
244
+
func GetForkByDid(e Execer, did string, name string) (*Repo, error) {
245
+
var repo Repo
246
+
var createdAt string
247
+
var nullableDescription sql.NullString
248
+
var nullableSource sql.NullString
249
+
250
+
row := e.QueryRow(
251
+
`select did, name, knot, rkey, description, created, at_uri, source
252
+
from repos
253
+
where did = ? and name = ? and source is not null and source != ''`,
254
+
did, name,
255
+
)
256
+
257
+
err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource)
258
+
if err != nil {
259
+
return nil, err
260
+
}
261
+
262
+
if nullableDescription.Valid {
263
+
repo.Description = nullableDescription.String
264
+
}
265
+
266
+
if nullableSource.Valid {
267
+
repo.Source = nullableSource.String
268
+
}
269
+
270
+
createdAtTime, err := time.Parse(time.RFC3339, createdAt)
271
+
if err != nil {
272
+
repo.Created = time.Now()
273
+
} else {
274
+
repo.Created = createdAtTime
275
+
}
276
+
277
+
return &repo, nil
278
+
}
279
+
280
func AddCollaborator(e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error {
281
_, err := e.Exec(
282
`insert into collaborators (did, repo)
+1
appview/pages/pages.go
+1
appview/pages/pages.go
+1
-1
appview/pages/templates/fragments/pullActions.html
+1
-1
appview/pages/templates/fragments/pullActions.html
+1
-1
appview/pages/templates/fragments/pullPatchUpload.html
+1
-1
appview/pages/templates/fragments/pullPatchUpload.html
+6
-4
appview/pages/templates/repo/pulls/new.html
+6
-4
appview/pages/templates/repo/pulls/new.html
···
1
-
{{ define "title" }}new pull | {{ .RepoInfo.FullName }}{{ end }}
2
3
{{ define "repoContent" }}
4
<section class="prose dark:prose-invert">
···
12
<li class="leading-relaxed">Paste the diff output in the form below.</li>
13
</ul>
14
</p>
15
-
</section>
16
<form
17
hx-post="/{{ .RepoInfo.FullName }}/pulls/new"
18
class="mt-6 space-y-6"
···
41
{{ template "fragments/pullCompareBranches" . }}
42
{{ end }}
43
44
-
<div class="flex justify-end items-center gap-2">
45
-
<button type="submit" class="btn">create</button>
46
</div>
47
48
</div>
···
1
+
{{ define "title" }}new pull · {{ .RepoInfo.FullName }}{{ end }}
2
3
{{ define "repoContent" }}
4
<section class="prose dark:prose-invert">
···
12
<li class="leading-relaxed">Paste the diff output in the form below.</li>
13
</ul>
14
</p>
15
+
</section>
16
<form
17
hx-post="/{{ .RepoInfo.FullName }}/pulls/new"
18
class="mt-6 space-y-6"
···
41
{{ template "fragments/pullCompareBranches" . }}
42
{{ end }}
43
44
+
Or, select a fork to create a pull request from.
45
+
46
+
<div class="flex justify-start items-center gap-2">
47
+
<button type="submit" class="btn">create pull</button>
48
</div>
49
50
</div>
+7
-1
appview/pages/templates/repo/pulls/pull.html
+7
-1
appview/pages/templates/repo/pulls/pull.html
···
45
{{ .Pull.TargetBranch }}
46
</span>
47
</span>
48
+
{{ if not .Pull.IsPatch }}
49
<span>from
50
+
{{ if not .Pull.IsSameRepoBranch }}
51
+
<span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
52
+
{{ .Pull.PullSource.Repo }}
53
+
</span>
54
+
<span class="select-none">/</span>
55
+
{{ end }}
56
<span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
57
{{ .Pull.PullSource.Branch }}
58
</span>
+332
-130
appview/state/pull.go
+332
-130
appview/state/pull.go
···
1
package state
2
3
import (
4
"encoding/json"
5
"fmt"
6
"io"
7
"log"
8
"net/http"
9
"strconv"
10
"strings"
11
"time"
12
13
"github.com/go-chi/chi/v5"
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview/db"
16
"tangled.sh/tangled.sh/core/appview/pages"
17
"tangled.sh/tangled.sh/core/types"
18
19
comatproto "github.com/bluesky-social/indigo/api/atproto"
20
lexutil "github.com/bluesky-social/indigo/lex/util"
21
)
22
···
186
}
187
188
func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult {
189
-
if pull.State == db.PullMerged {
190
return pages.Unknown
191
}
192
193
-
if pull.PullSource == nil {
194
-
return pages.Unknown
195
}
196
197
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
198
if err != nil {
199
-
log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err)
200
return pages.Unknown
201
}
202
203
-
resp, err := us.Branch(f.OwnerDid(), f.RepoName, pull.PullSource.Branch)
204
if err != nil {
205
log.Println("failed to reach knotserver", err)
206
return pages.Unknown
···
208
209
body, err := io.ReadAll(resp.Body)
210
if err != nil {
211
-
log.Printf("Error reading response body: %v", err)
212
return pages.Unknown
213
}
214
215
var result types.RepoBranchResponse
216
-
err = json.Unmarshal(body, &result)
217
-
if err != nil {
218
log.Println("failed to parse response:", err)
219
return pages.Unknown
220
}
221
222
-
if pull.Submissions[pull.LastRoundNumber()].SourceRev != result.Branch.Hash {
223
-
log.Println(pull.Submissions[pull.LastRoundNumber()].SourceRev, result.Branch.Hash)
224
return pages.ShouldResubmit
225
-
} else {
226
-
return pages.ShouldNotResubmit
227
}
228
}
229
230
func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
···
468
return
469
}
470
471
switch r.Method {
472
case http.MethodGet:
473
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
···
499
s.pages.RepoNewPull(w, pages.RepoNewPullParams{
500
LoggedInUser: user,
501
RepoInfo: f.RepoInfo(s, user),
502
Branches: result.Branches,
503
})
504
case http.MethodPost:
505
-
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
506
title := r.FormValue("title")
507
body := r.FormValue("body")
508
targetBranch := r.FormValue("targetBranch")
509
sourceBranch := r.FormValue("sourceBranch")
510
patch := r.FormValue("patch")
511
512
-
isBranchBased := isPushAllowed && (sourceBranch != "")
513
isPatchBased := patch != ""
514
515
-
if !isBranchBased && !isPatchBased {
516
s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.")
517
return
518
}
···
527
return
528
}
529
530
-
// TODO: check if knot has this capability
531
-
var sourceRev string
532
-
var pullSource *db.PullSource
533
-
var recordPullSource *tangled.RepoPull_Source
534
if isBranchBased {
535
-
pullSource = &db.PullSource{
536
-
Branch: sourceBranch,
537
-
}
538
-
recordPullSource = &tangled.RepoPull_Source{
539
-
Branch: sourceBranch,
540
-
}
541
-
// generate a patch using /compare
542
-
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
543
-
if err != nil {
544
-
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
545
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
546
-
return
547
-
}
548
549
-
resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)
550
-
switch resp.StatusCode {
551
-
case 404:
552
-
case 400:
553
-
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
554
-
}
555
556
-
respBody, err := io.ReadAll(resp.Body)
557
-
if err != nil {
558
-
log.Println("failed to compare across branches")
559
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
560
-
}
561
-
defer resp.Body.Close()
562
563
-
var diffTreeResponse types.RepoDiffTreeResponse
564
-
err = json.Unmarshal(respBody, &diffTreeResponse)
565
-
if err != nil {
566
-
log.Println("failed to unmarshal diff tree response", err)
567
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
568
-
}
569
570
-
sourceRev = diffTreeResponse.DiffTree.Rev2
571
-
patch = diffTreeResponse.DiffTree.Patch
572
-
}
573
574
-
// Validate patch format
575
-
if !isPatchValid(patch) {
576
-
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
577
-
return
578
-
}
579
580
-
tx, err := s.db.BeginTx(r.Context(), nil)
581
-
if err != nil {
582
-
log.Println("failed to start tx")
583
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
584
-
return
585
-
}
586
-
defer tx.Rollback()
587
588
-
rkey := s.TID()
589
-
initialSubmission := db.PullSubmission{
590
-
Patch: patch,
591
-
SourceRev: sourceRev,
592
-
}
593
-
err = db.NewPull(tx, &db.Pull{
594
-
Title: title,
595
-
Body: body,
596
-
TargetBranch: targetBranch,
597
-
OwnerDid: user.Did,
598
-
RepoAt: f.RepoAt,
599
-
Rkey: rkey,
600
-
Submissions: []*db.PullSubmission{
601
-
&initialSubmission,
602
-
},
603
-
PullSource: pullSource,
604
-
})
605
-
if err != nil {
606
-
log.Println("failed to create pull request", err)
607
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
608
-
return
609
-
}
610
-
client, _ := s.auth.AuthorizedClient(r)
611
-
pullId, err := db.NextPullId(s.db, f.RepoAt)
612
-
if err != nil {
613
-
log.Println("failed to get pull id", err)
614
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
615
-
return
616
-
}
617
618
-
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
619
-
Collection: tangled.RepoPullNSID,
620
-
Repo: user.Did,
621
-
Rkey: rkey,
622
-
Record: &lexutil.LexiconTypeDecoder{
623
-
Val: &tangled.RepoPull{
624
-
Title: title,
625
-
PullId: int64(pullId),
626
-
TargetRepo: string(f.RepoAt),
627
-
TargetBranch: targetBranch,
628
-
Patch: patch,
629
-
Source: recordPullSource,
630
-
},
631
-
},
632
-
})
633
634
-
err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri)
635
-
if err != nil {
636
-
log.Println("failed to get pull id", err)
637
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
638
-
return
639
-
}
640
641
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))
642
return
643
}
644
}
645
646
func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
···
723
var sourceRev string
724
var recordPullSource *tangled.RepoPull_Source
725
726
-
// this pull is a branch based pull
727
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
728
-
if pull.IsSameRepoBranch() && isPushAllowed {
729
-
sourceBranch := pull.PullSource.Branch
730
-
targetBranch := pull.TargetBranch
731
-
recordPullSource = &tangled.RepoPull_Source{
732
-
Branch: sourceBranch,
733
}
734
// extract patch by performing compare
735
-
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
736
if err != nil {
737
-
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
738
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
739
return
740
}
741
742
-
resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)
743
-
switch resp.StatusCode {
744
case 404:
745
case 400:
746
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
747
}
748
749
-
respBody, err := io.ReadAll(resp.Body)
750
if err != nil {
751
log.Println("failed to compare across branches")
752
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
753
}
754
-
defer resp.Body.Close()
755
756
var diffTreeResponse types.RepoDiffTreeResponse
757
err = json.Unmarshal(respBody, &diffTreeResponse)
758
if err != nil {
759
log.Println("failed to unmarshal diff tree response", err)
760
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
761
}
762
763
sourceRev = diffTreeResponse.DiffTree.Rev2
···
779
return
780
}
781
782
-
// Validate patch format
783
if !isPatchValid(patch) {
784
s.pages.Notice(w, "resubmit-error", "Invalid patch format. Please provide a valid diff.")
785
return
···
1
package state
2
3
import (
4
+
"database/sql"
5
"encoding/json"
6
+
"errors"
7
"fmt"
8
"io"
9
"log"
10
"net/http"
11
+
"net/url"
12
"strconv"
13
"strings"
14
"time"
15
16
"github.com/go-chi/chi/v5"
17
"tangled.sh/tangled.sh/core/api/tangled"
18
+
"tangled.sh/tangled.sh/core/appview/auth"
19
"tangled.sh/tangled.sh/core/appview/db"
20
"tangled.sh/tangled.sh/core/appview/pages"
21
"tangled.sh/tangled.sh/core/types"
22
23
comatproto "github.com/bluesky-social/indigo/api/atproto"
24
+
"github.com/bluesky-social/indigo/atproto/syntax"
25
lexutil "github.com/bluesky-social/indigo/lex/util"
26
)
27
···
191
}
192
193
func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult {
194
+
if pull.State == db.PullMerged || pull.PullSource == nil {
195
return pages.Unknown
196
}
197
198
+
var knot, ownerDid, repoName string
199
+
200
+
if pull.PullSource.Repo != nil {
201
+
// fork-based pulls
202
+
sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.Repo.String())
203
+
if err != nil {
204
+
log.Println("failed to get source repo", err)
205
+
return pages.Unknown
206
+
}
207
+
208
+
knot = sourceRepo.Knot
209
+
ownerDid = sourceRepo.Did
210
+
repoName = sourceRepo.Name
211
+
} else {
212
+
// pulls within the same repo
213
+
knot = f.Knot
214
+
ownerDid = f.OwnerDid()
215
+
repoName = f.RepoName
216
}
217
218
+
us, err := NewUnsignedClient(knot, s.config.Dev)
219
if err != nil {
220
+
log.Printf("failed to setup client for %s; ignoring: %v", knot, err)
221
return pages.Unknown
222
}
223
224
+
resp, err := us.Branch(ownerDid, repoName, pull.PullSource.Branch)
225
if err != nil {
226
log.Println("failed to reach knotserver", err)
227
return pages.Unknown
···
229
230
body, err := io.ReadAll(resp.Body)
231
if err != nil {
232
+
log.Printf("error reading response body: %v", err)
233
return pages.Unknown
234
}
235
+
defer resp.Body.Close()
236
237
var result types.RepoBranchResponse
238
+
if err := json.Unmarshal(body, &result); err != nil {
239
log.Println("failed to parse response:", err)
240
return pages.Unknown
241
}
242
243
+
latestSubmission := pull.Submissions[pull.LastRoundNumber()]
244
+
if latestSubmission.SourceRev != result.Branch.Hash {
245
return pages.ShouldResubmit
246
}
247
+
248
+
return pages.ShouldNotResubmit
249
}
250
251
func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
···
489
return
490
}
491
492
+
forks, err := db.GetForksByDid(s.db, user.Did)
493
+
if err != nil {
494
+
log.Println("failed to get forks", err)
495
+
return
496
+
}
497
+
498
switch r.Method {
499
case http.MethodGet:
500
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
···
526
s.pages.RepoNewPull(w, pages.RepoNewPullParams{
527
LoggedInUser: user,
528
RepoInfo: f.RepoInfo(s, user),
529
+
Forks: forks,
530
Branches: result.Branches,
531
})
532
case http.MethodPost:
533
title := r.FormValue("title")
534
body := r.FormValue("body")
535
targetBranch := r.FormValue("targetBranch")
536
+
fromFork := r.FormValue("fromFork")
537
sourceBranch := r.FormValue("sourceBranch")
538
patch := r.FormValue("patch")
539
540
+
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
541
+
isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == ""
542
isPatchBased := patch != ""
543
+
isForkBased := fromFork != "" && sourceBranch != ""
544
545
+
if !isBranchBased && !isPatchBased && !isForkBased {
546
s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.")
547
return
548
}
···
557
return
558
}
559
560
if isBranchBased {
561
+
s.handleBranchBasedPull(w, r, f, user, title, body, targetBranch, sourceBranch)
562
+
} else if isPatchBased {
563
+
s.handlePatchBasedPull(w, r, f, user, title, body, targetBranch, patch)
564
+
} else if isForkBased {
565
+
s.handleForkBasedPull(w, r, f, user, fromFork, title, body, targetBranch, sourceBranch)
566
+
}
567
+
return
568
+
}
569
+
}
570
571
+
func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) {
572
+
pullSource := &db.PullSource{
573
+
Branch: sourceBranch,
574
+
}
575
+
recordPullSource := &tangled.RepoPull_Source{
576
+
Branch: sourceBranch,
577
+
}
578
+
579
+
// Generate a patch using /compare
580
+
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
581
+
if err != nil {
582
+
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
583
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
584
+
return
585
+
}
586
587
+
resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)
588
+
switch resp.StatusCode {
589
+
case 404:
590
+
case 400:
591
+
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
592
+
return
593
+
}
594
595
+
respBody, err := io.ReadAll(resp.Body)
596
+
if err != nil {
597
+
log.Println("failed to compare across branches")
598
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
599
+
return
600
+
}
601
+
defer resp.Body.Close()
602
603
+
var diffTreeResponse types.RepoDiffTreeResponse
604
+
err = json.Unmarshal(respBody, &diffTreeResponse)
605
+
if err != nil {
606
+
log.Println("failed to unmarshal diff tree response", err)
607
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
608
+
return
609
+
}
610
611
+
sourceRev := diffTreeResponse.DiffTree.Rev2
612
+
patch := diffTreeResponse.DiffTree.Patch
613
614
+
if !isPatchValid(patch) {
615
+
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
616
+
return
617
+
}
618
619
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource)
620
+
}
621
622
+
func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) {
623
+
if !isPatchValid(patch) {
624
+
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
625
+
return
626
+
}
627
628
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil)
629
+
}
630
631
+
func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) {
632
+
fork, err := db.GetForkByDid(s.db, user.Did, forkRepo)
633
+
if errors.Is(err, sql.ErrNoRows) {
634
+
s.pages.Notice(w, "pull", "No such fork.")
635
+
return
636
+
} else if err != nil {
637
+
log.Println("failed to fetch fork:", err)
638
+
s.pages.Notice(w, "pull", "Failed to fetch fork.")
639
return
640
}
641
+
642
+
secret, err := db.GetRegistrationKey(s.db, fork.Knot)
643
+
if err != nil {
644
+
log.Println("failed to fetch registration key:", err)
645
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
646
+
return
647
+
}
648
+
649
+
sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev)
650
+
if err != nil {
651
+
log.Println("failed to create signed client:", err)
652
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
653
+
return
654
+
}
655
+
656
+
us, err := NewUnsignedClient(fork.Knot, s.config.Dev)
657
+
if err != nil {
658
+
log.Println("failed to create unsigned client:", err)
659
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
660
+
return
661
+
}
662
+
663
+
resp, err := sc.NewHiddenRef(user.Did, fork.Name, sourceBranch, targetBranch)
664
+
if err != nil {
665
+
log.Println("failed to create hidden ref:", err, resp.StatusCode)
666
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
667
+
return
668
+
}
669
+
670
+
switch resp.StatusCode {
671
+
case 404:
672
+
case 400:
673
+
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
674
+
return
675
+
}
676
+
677
+
hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch))
678
+
// We're now comparing the sourceBranch (on the fork) against the hiddenRef which is tracking
679
+
// the targetBranch on the target repository. This code is a bit confusing, but here's an example:
680
+
// hiddenRef: hidden/feature-1/main (on repo-fork)
681
+
// targetBranch: main (on repo-1)
682
+
// sourceBranch: feature-1 (on repo-fork)
683
+
diffResp, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch)
684
+
if err != nil {
685
+
log.Println("failed to compare across branches", err)
686
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
687
+
return
688
+
}
689
+
690
+
respBody, err := io.ReadAll(diffResp.Body)
691
+
if err != nil {
692
+
log.Println("failed to read response body", err)
693
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
694
+
return
695
+
}
696
+
697
+
defer resp.Body.Close()
698
+
699
+
var diffTreeResponse types.RepoDiffTreeResponse
700
+
err = json.Unmarshal(respBody, &diffTreeResponse)
701
+
if err != nil {
702
+
log.Println("failed to unmarshal diff tree response", err)
703
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
704
+
return
705
+
}
706
+
707
+
sourceRev := diffTreeResponse.DiffTree.Rev2
708
+
patch := diffTreeResponse.DiffTree.Patch
709
+
710
+
if !isPatchValid(patch) {
711
+
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
712
+
return
713
+
}
714
+
715
+
forkAtUri, err := syntax.ParseATURI(fork.AtUri)
716
+
if err != nil {
717
+
log.Println("failed to parse fork AT URI", err)
718
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
719
+
return
720
+
}
721
+
722
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{
723
+
Branch: sourceBranch,
724
+
Repo: &forkAtUri,
725
+
}, &tangled.RepoPull_Source{Branch: sourceBranch, Repo: &fork.AtUri})
726
+
}
727
+
728
+
func (s *State) createPullRequest(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch, sourceRev string, pullSource *db.PullSource, recordPullSource *tangled.RepoPull_Source) {
729
+
tx, err := s.db.BeginTx(r.Context(), nil)
730
+
if err != nil {
731
+
log.Println("failed to start tx")
732
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
733
+
return
734
+
}
735
+
defer tx.Rollback()
736
+
737
+
rkey := s.TID()
738
+
initialSubmission := db.PullSubmission{
739
+
Patch: patch,
740
+
SourceRev: sourceRev,
741
+
}
742
+
err = db.NewPull(tx, &db.Pull{
743
+
Title: title,
744
+
Body: body,
745
+
TargetBranch: targetBranch,
746
+
OwnerDid: user.Did,
747
+
RepoAt: f.RepoAt,
748
+
Rkey: rkey,
749
+
Submissions: []*db.PullSubmission{
750
+
&initialSubmission,
751
+
},
752
+
PullSource: pullSource,
753
+
})
754
+
if err != nil {
755
+
log.Println("failed to create pull request", err)
756
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
757
+
return
758
+
}
759
+
client, _ := s.auth.AuthorizedClient(r)
760
+
pullId, err := db.NextPullId(s.db, f.RepoAt)
761
+
if err != nil {
762
+
log.Println("failed to get pull id", err)
763
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
764
+
return
765
+
}
766
+
767
+
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
768
+
Collection: tangled.RepoPullNSID,
769
+
Repo: user.Did,
770
+
Rkey: rkey,
771
+
Record: &lexutil.LexiconTypeDecoder{
772
+
Val: &tangled.RepoPull{
773
+
Title: title,
774
+
PullId: int64(pullId),
775
+
TargetRepo: string(f.RepoAt),
776
+
TargetBranch: targetBranch,
777
+
Patch: patch,
778
+
Source: recordPullSource,
779
+
},
780
+
},
781
+
})
782
+
783
+
err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri)
784
+
if err != nil {
785
+
log.Println("failed to get pull id", err)
786
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
787
+
return
788
+
}
789
+
790
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))
791
}
792
793
func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
···
870
var sourceRev string
871
var recordPullSource *tangled.RepoPull_Source
872
873
+
var ownerDid, repoName, knotName string
874
+
var isSameRepo bool = pull.IsSameRepoBranch()
875
+
sourceBranch := pull.PullSource.Branch
876
+
targetBranch := pull.TargetBranch
877
+
recordPullSource = &tangled.RepoPull_Source{
878
+
Branch: sourceBranch,
879
+
}
880
+
881
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
882
+
if isSameRepo && isPushAllowed {
883
+
ownerDid = f.OwnerDid()
884
+
repoName = f.RepoName
885
+
knotName = f.Knot
886
+
} else if !isSameRepo {
887
+
sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.Repo.String())
888
+
if err != nil {
889
+
log.Println("failed to get source repo", err)
890
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
891
+
return
892
}
893
+
ownerDid = sourceRepo.Did
894
+
repoName = sourceRepo.Name
895
+
knotName = sourceRepo.Knot
896
+
}
897
+
898
+
if sourceBranch != "" && knotName != "" {
899
// extract patch by performing compare
900
+
ksClient, err := NewUnsignedClient(knotName, s.config.Dev)
901
+
if err != nil {
902
+
log.Printf("failed to create client for %s: %s", knotName, err)
903
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
904
+
return
905
+
}
906
+
907
+
if !isSameRepo {
908
+
secret, err := db.GetRegistrationKey(s.db, knotName)
909
+
if err != nil {
910
+
log.Printf("failed to get registration key for %s: %s", knotName, err)
911
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
912
+
return
913
+
}
914
+
// update the hidden tracking branch to latest
915
+
signedClient, err := NewSignedClient(knotName, secret, s.config.Dev)
916
+
if err != nil {
917
+
log.Printf("failed to create signed client for %s: %s", knotName, err)
918
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
919
+
return
920
+
}
921
+
resp, err := signedClient.NewHiddenRef(ownerDid, repoName, sourceBranch, targetBranch)
922
+
if err != nil || resp.StatusCode != http.StatusNoContent {
923
+
log.Printf("failed to update tracking branch: %s", err)
924
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
925
+
return
926
+
}
927
+
}
928
+
929
+
var compareResp *http.Response
930
+
if !isSameRepo {
931
+
hiddenRef := url.QueryEscape(fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch))
932
+
compareResp, err = ksClient.Compare(ownerDid, repoName, hiddenRef, sourceBranch)
933
+
} else {
934
+
compareResp, err = ksClient.Compare(ownerDid, repoName, targetBranch, sourceBranch)
935
+
}
936
if err != nil {
937
+
log.Printf("failed to compare branches: %s", err)
938
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
939
return
940
}
941
+
defer compareResp.Body.Close()
942
943
+
switch compareResp.StatusCode {
944
case 404:
945
case 400:
946
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
947
+
return
948
}
949
950
+
respBody, err := io.ReadAll(compareResp.Body)
951
if err != nil {
952
log.Println("failed to compare across branches")
953
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
954
+
return
955
}
956
+
defer compareResp.Body.Close()
957
958
var diffTreeResponse types.RepoDiffTreeResponse
959
err = json.Unmarshal(respBody, &diffTreeResponse)
960
if err != nil {
961
log.Println("failed to unmarshal diff tree response", err)
962
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
963
+
return
964
}
965
966
sourceRev = diffTreeResponse.DiffTree.Rev2
···
982
return
983
}
984
985
if !isPatchValid(patch) {
986
s.pages.Notice(w, "resubmit-error", "Invalid patch format. Please provide a valid diff.")
987
return
+1
-1
appview/state/signer.go
+1
-1
appview/state/signer.go