+18
-2
appview/db/pulls.go
+18
-2
appview/db/pulls.go
···
271
}
272
}
273
274
_, err = tx.Exec(
275
`
276
-
insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, rkey, state, source_branch, source_repo_at)
277
-
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
278
pull.RepoAt,
279
pull.OwnerDid,
280
pull.PullId,
···
285
pull.State,
286
sourceBranch,
287
sourceRepoAt,
288
)
289
if err != nil {
290
return err
···
271
}
272
}
273
274
+
var stackId, changeId, parentChangeId *string
275
+
if pull.StackId != "" {
276
+
stackId = &pull.StackId
277
+
}
278
+
if pull.ChangeId != "" {
279
+
changeId = &pull.ChangeId
280
+
}
281
+
if pull.ParentChangeId != "" {
282
+
parentChangeId = &pull.ParentChangeId
283
+
}
284
+
285
_, err = tx.Exec(
286
`
287
+
insert into pulls (
288
+
repo_at, owner_did, pull_id, title, target_branch, body, rkey, state, source_branch, source_repo_at, stack_id, change_id, parent_change_id
289
+
)
290
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
291
pull.RepoAt,
292
pull.OwnerDid,
293
pull.PullId,
···
298
pull.State,
299
sourceBranch,
300
sourceRepoAt,
301
+
stackId,
302
+
changeId,
303
+
parentChangeId,
304
)
305
if err != nil {
306
return err
+147
appview/state/pull.go
+147
appview/state/pull.go
···
25
"github.com/bluesky-social/indigo/atproto/syntax"
26
lexutil "github.com/bluesky-social/indigo/lex/util"
27
"github.com/go-chi/chi/v5"
28
)
29
30
// htmx fragment
···
843
isStacked bool,
844
) {
845
if isStacked {
846
}
847
848
tx, err := s.db.BeginTx(r.Context(), nil)
···
933
}
934
935
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))
936
}
937
938
func (s *State) ValidatePatch(w http.ResponseWriter, r *http.Request) {
···
25
"github.com/bluesky-social/indigo/atproto/syntax"
26
lexutil "github.com/bluesky-social/indigo/lex/util"
27
"github.com/go-chi/chi/v5"
28
+
"github.com/google/uuid"
29
)
30
31
// htmx fragment
···
844
isStacked bool,
845
) {
846
if isStacked {
847
+
// creates a series of PRs, each linking to the previous, identified by jj's change-id
848
+
s.createStackedPulLRequest(
849
+
w,
850
+
r,
851
+
f,
852
+
user,
853
+
title, body, targetBranch,
854
+
patch,
855
+
sourceRev,
856
+
pullSource,
857
+
recordPullSource,
858
+
)
859
+
return
860
}
861
862
tx, err := s.db.BeginTx(r.Context(), nil)
···
947
}
948
949
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))
950
+
}
951
+
952
+
func (s *State) createStackedPulLRequest(
953
+
w http.ResponseWriter,
954
+
r *http.Request,
955
+
f *FullyResolvedRepo,
956
+
user *oauth.User,
957
+
title, body, targetBranch string,
958
+
patch string,
959
+
sourceRev string,
960
+
pullSource *db.PullSource,
961
+
recordPullSource *tangled.RepoPull_Source,
962
+
) {
963
+
// run some necessary checks for stacked-prs first
964
+
965
+
// must be branch or fork based
966
+
if sourceRev == "" {
967
+
log.Println("stacked PR from patch-based pull")
968
+
s.pages.Notice(w, "pull", "Stacking is only supported on branch and fork based pull-requests.")
969
+
return
970
+
}
971
+
972
+
formatPatches, err := patchutil.ExtractPatches(patch)
973
+
if err != nil {
974
+
s.pages.Notice(w, "pull", fmt.Sprintf("Failed to extract patches: %v", err))
975
+
return
976
+
}
977
+
978
+
// must have atleast 1 patch to begin with
979
+
if len(formatPatches) == 0 {
980
+
s.pages.Notice(w, "pull", "No patches found in the generated format-patch.")
981
+
return
982
+
}
983
+
984
+
tx, err := s.db.BeginTx(r.Context(), nil)
985
+
if err != nil {
986
+
log.Println("failed to start tx")
987
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
988
+
return
989
+
}
990
+
defer tx.Rollback()
991
+
992
+
// create a series of pull requests, and write records from them at once
993
+
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
994
+
995
+
// the stack is identified by a UUID
996
+
stackId := uuid.New()
997
+
parentChangeId := ""
998
+
for _, fp := range formatPatches {
999
+
// all patches must have a jj change-id
1000
+
changeId, err := fp.ChangeId()
1001
+
if err != nil {
1002
+
s.pages.Notice(w, "pull", "Stacking is only supported if all patches contain a change-id commit header.")
1003
+
return
1004
+
}
1005
+
1006
+
title = fp.Title
1007
+
body = fp.Body
1008
+
rkey := appview.TID()
1009
+
1010
+
// TODO: can we just use a format-patch string here?
1011
+
initialSubmission := db.PullSubmission{
1012
+
Patch: fp.Patch(),
1013
+
SourceRev: sourceRev,
1014
+
}
1015
+
err = db.NewPull(tx, &db.Pull{
1016
+
Title: title,
1017
+
Body: body,
1018
+
TargetBranch: targetBranch,
1019
+
OwnerDid: user.Did,
1020
+
RepoAt: f.RepoAt,
1021
+
Rkey: rkey,
1022
+
Submissions: []*db.PullSubmission{
1023
+
&initialSubmission,
1024
+
},
1025
+
PullSource: pullSource,
1026
+
1027
+
StackId: stackId.String(),
1028
+
ChangeId: changeId,
1029
+
ParentChangeId: parentChangeId,
1030
+
})
1031
+
if err != nil {
1032
+
log.Println("failed to create pull request", err)
1033
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1034
+
return
1035
+
}
1036
+
1037
+
record := tangled.RepoPull{
1038
+
Title: title,
1039
+
TargetRepo: string(f.RepoAt),
1040
+
TargetBranch: targetBranch,
1041
+
Patch: fp.Patch(),
1042
+
Source: recordPullSource,
1043
+
}
1044
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
1045
+
RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{
1046
+
Collection: tangled.RepoPullNSID,
1047
+
Rkey: &rkey,
1048
+
Value: &lexutil.LexiconTypeDecoder{
1049
+
Val: &record,
1050
+
},
1051
+
},
1052
+
})
1053
+
1054
+
parentChangeId = changeId
1055
+
}
1056
+
1057
+
client, err := s.oauth.AuthorizedClient(r)
1058
+
if err != nil {
1059
+
log.Println("failed to get authorized client", err)
1060
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1061
+
return
1062
+
}
1063
+
1064
+
// apply all record creations at once
1065
+
_, err = client.RepoApplyWrites(r.Context(), &comatproto.RepoApplyWrites_Input{
1066
+
Repo: user.Did,
1067
+
Writes: writes,
1068
+
})
1069
+
if err != nil {
1070
+
log.Println("failed to create stacked pull request", err)
1071
+
s.pages.Notice(w, "pull", "Failed to create stacked pull request. Try again later.")
1072
+
return
1073
+
}
1074
+
1075
+
// create all pulls at once
1076
+
if err = tx.Commit(); err != nil {
1077
+
log.Println("failed to create pull request", err)
1078
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1079
+
return
1080
+
}
1081
+
1082
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls", f.OwnerSlashRepo()))
1083
}
1084
1085
func (s *State) ValidatePatch(w http.ResponseWriter, r *http.Request) {
+9
appview/xrpcclient/xrpc.go
+9
appview/xrpcclient/xrpc.go
···
31
return &out, nil
32
}
33
34
+
func (c *Client) RepoApplyWrites(ctx context.Context, input *atproto.RepoApplyWrites_Input) (*atproto.RepoApplyWrites_Output, error) {
35
+
var out atproto.RepoApplyWrites_Output
36
+
if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.applyWrites", nil, input, &out); err != nil {
37
+
return nil, err
38
+
}
39
+
40
+
return &out, nil
41
+
}
42
+
43
func (c *Client) RepoGetRecord(ctx context.Context, cid string, collection string, repo string, rkey string) (*atproto.RepoGetRecord_Output, error) {
44
var out atproto.RepoGetRecord_Output
45
+2
flake.nix
+2
flake.nix
+16
patchutil/patchutil.go
+16
patchutil/patchutil.go
···
15
*gitdiff.PatchHeader
16
}
17
18
+
// Extracts just the diff from this format-patch
19
+
func (f FormatPatch) Patch() string {
20
+
var b strings.Builder
21
+
for _, p := range f.Files {
22
+
b.WriteString(p.String())
23
+
}
24
+
return b.String()
25
+
}
26
+
27
+
func (f FormatPatch) ChangeId() (string, error) {
28
+
if vals, ok := f.RawHeaders["Change-Id"]; ok && len(vals) == 1 {
29
+
return vals[0], nil
30
+
}
31
+
return "", fmt.Errorf("no change-id found")
32
+
}
33
+
34
func ExtractPatches(formatPatch string) ([]FormatPatch, error) {
35
patches := splitFormatPatch(formatPatch)
36