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