tangled
alpha
login
or
join now
t0.lv
/
core
forked from
tangled.org/core
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
add `source` field to pull lexicon
oppi.li
11 months ago
0cb59585
d3bf20a0
+666
-132
19 changed files
expand all
collapse all
unified
split
api
tangled
cbor_gen.go
repopull.go
appview
db
db.go
pulls.go
pages
pages.go
templates
fragments
pullActions.html
repo
pulls
new.html
pull.html
pulls.html
state
pull.go
signer.go
cmd
gen.go
knotserver
git
diff.go
git.go
handler.go
routes.go
lexicons
pulls
pull.json
types
diff.go
repo.go
+188
-44
api/tangled/cbor_gen.go
···
2076
fieldCount--
2077
}
2078
2079
-
if t.SourceRepo == nil {
2080
fieldCount--
2081
}
2082
···
2203
}
2204
}
2205
2206
-
// t.CreatedAt (string) (string)
2207
-
if t.CreatedAt != nil {
2208
2209
-
if len("createdAt") > 1000000 {
2210
-
return xerrors.Errorf("Value in field \"createdAt\" was too long")
2211
}
2212
2213
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
2214
return err
2215
}
2216
-
if _, err := cw.WriteString(string("createdAt")); err != nil {
2217
return err
2218
}
2219
2220
-
if t.CreatedAt == nil {
2221
-
if _, err := cw.Write(cbg.CborNull); err != nil {
2222
-
return err
2223
-
}
2224
-
} else {
2225
-
if len(*t.CreatedAt) > 1000000 {
2226
-
return xerrors.Errorf("Value in field t.CreatedAt was too long")
2227
-
}
2228
-
2229
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.CreatedAt))); err != nil {
2230
-
return err
2231
-
}
2232
-
if _, err := cw.WriteString(string(*t.CreatedAt)); err != nil {
2233
-
return err
2234
-
}
2235
}
2236
}
2237
2238
-
// t.SourceRepo (string) (string)
2239
-
if t.SourceRepo != nil {
2240
2241
-
if len("sourceRepo") > 1000000 {
2242
-
return xerrors.Errorf("Value in field \"sourceRepo\" was too long")
2243
}
2244
2245
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sourceRepo"))); err != nil {
2246
return err
2247
}
2248
-
if _, err := cw.WriteString(string("sourceRepo")); err != nil {
2249
return err
2250
}
2251
2252
-
if t.SourceRepo == nil {
2253
if _, err := cw.Write(cbg.CborNull); err != nil {
2254
return err
2255
}
2256
} else {
2257
-
if len(*t.SourceRepo) > 1000000 {
2258
-
return xerrors.Errorf("Value in field t.SourceRepo was too long")
2259
}
2260
2261
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.SourceRepo))); err != nil {
2262
return err
2263
}
2264
-
if _, err := cw.WriteString(string(*t.SourceRepo)); err != nil {
2265
return err
2266
}
2267
}
···
2436
2437
t.PullId = int64(extraI)
2438
}
2439
-
// t.CreatedAt (string) (string)
2440
-
case "createdAt":
2441
2442
{
0
2443
b, err := cr.ReadByte()
2444
if err != nil {
2445
return err
···
2448
if err := cr.UnreadByte(); err != nil {
2449
return err
2450
}
2451
-
2452
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2453
-
if err != nil {
2454
-
return err
2455
}
2456
-
2457
-
t.CreatedAt = (*string)(&sval)
2458
}
0
2459
}
2460
-
// t.SourceRepo (string) (string)
2461
-
case "sourceRepo":
2462
2463
{
2464
b, err := cr.ReadByte()
···
2475
return err
2476
}
2477
2478
-
t.SourceRepo = (*string)(&sval)
2479
}
2480
}
2481
// t.TargetRepo (string) (string)
···
2499
}
2500
2501
t.TargetBranch = string(sval)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
2502
}
2503
2504
default:
···
2076
fieldCount--
2077
}
2078
2079
+
if t.Source == nil {
2080
fieldCount--
2081
}
2082
···
2203
}
2204
}
2205
2206
+
// t.Source (tangled.RepoPull_Source) (struct)
2207
+
if t.Source != nil {
2208
2209
+
if len("source") > 1000000 {
2210
+
return xerrors.Errorf("Value in field \"source\" was too long")
2211
}
2212
2213
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("source"))); err != nil {
2214
return err
2215
}
2216
+
if _, err := cw.WriteString(string("source")); err != nil {
2217
return err
2218
}
2219
2220
+
if err := t.Source.MarshalCBOR(cw); err != nil {
2221
+
return err
0
0
0
0
0
0
0
0
0
0
0
0
0
2222
}
2223
}
2224
2225
+
// t.CreatedAt (string) (string)
2226
+
if t.CreatedAt != nil {
2227
2228
+
if len("createdAt") > 1000000 {
2229
+
return xerrors.Errorf("Value in field \"createdAt\" was too long")
2230
}
2231
2232
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
2233
return err
2234
}
2235
+
if _, err := cw.WriteString(string("createdAt")); err != nil {
2236
return err
2237
}
2238
2239
+
if t.CreatedAt == nil {
2240
if _, err := cw.Write(cbg.CborNull); err != nil {
2241
return err
2242
}
2243
} else {
2244
+
if len(*t.CreatedAt) > 1000000 {
2245
+
return xerrors.Errorf("Value in field t.CreatedAt was too long")
2246
}
2247
2248
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.CreatedAt))); err != nil {
2249
return err
2250
}
2251
+
if _, err := cw.WriteString(string(*t.CreatedAt)); err != nil {
2252
return err
2253
}
2254
}
···
2423
2424
t.PullId = int64(extraI)
2425
}
2426
+
// t.Source (tangled.RepoPull_Source) (struct)
2427
+
case "source":
2428
2429
{
2430
+
2431
b, err := cr.ReadByte()
2432
if err != nil {
2433
return err
···
2436
if err := cr.UnreadByte(); err != nil {
2437
return err
2438
}
2439
+
t.Source = new(RepoPull_Source)
2440
+
if err := t.Source.UnmarshalCBOR(cr); err != nil {
2441
+
return xerrors.Errorf("unmarshaling t.Source pointer: %w", err)
0
2442
}
0
0
2443
}
2444
+
2445
}
2446
+
// t.CreatedAt (string) (string)
2447
+
case "createdAt":
2448
2449
{
2450
b, err := cr.ReadByte()
···
2461
return err
2462
}
2463
2464
+
t.CreatedAt = (*string)(&sval)
2465
}
2466
}
2467
// t.TargetRepo (string) (string)
···
2485
}
2486
2487
t.TargetBranch = string(sval)
2488
+
}
2489
+
2490
+
default:
2491
+
// Field doesn't exist on this type, so ignore it
2492
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2493
+
return err
2494
+
}
2495
+
}
2496
+
}
2497
+
2498
+
return nil
2499
+
}
2500
+
func (t *RepoPull_Source) MarshalCBOR(w io.Writer) error {
2501
+
if t == nil {
2502
+
_, err := w.Write(cbg.CborNull)
2503
+
return err
2504
+
}
2505
+
2506
+
cw := cbg.NewCborWriter(w)
2507
+
fieldCount := 2
2508
+
2509
+
if t.Repo == nil {
2510
+
fieldCount--
2511
+
}
2512
+
2513
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
2514
+
return err
2515
+
}
2516
+
2517
+
// t.Repo (string) (string)
2518
+
if t.Repo != nil {
2519
+
2520
+
if len("repo") > 1000000 {
2521
+
return xerrors.Errorf("Value in field \"repo\" was too long")
2522
+
}
2523
+
2524
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil {
2525
+
return err
2526
+
}
2527
+
if _, err := cw.WriteString(string("repo")); err != nil {
2528
+
return err
2529
+
}
2530
+
2531
+
if t.Repo == nil {
2532
+
if _, err := cw.Write(cbg.CborNull); err != nil {
2533
+
return err
2534
+
}
2535
+
} else {
2536
+
if len(*t.Repo) > 1000000 {
2537
+
return xerrors.Errorf("Value in field t.Repo was too long")
2538
+
}
2539
+
2540
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Repo))); err != nil {
2541
+
return err
2542
+
}
2543
+
if _, err := cw.WriteString(string(*t.Repo)); err != nil {
2544
+
return err
2545
+
}
2546
+
}
2547
+
}
2548
+
2549
+
// t.Branch (string) (string)
2550
+
if len("branch") > 1000000 {
2551
+
return xerrors.Errorf("Value in field \"branch\" was too long")
2552
+
}
2553
+
2554
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("branch"))); err != nil {
2555
+
return err
2556
+
}
2557
+
if _, err := cw.WriteString(string("branch")); err != nil {
2558
+
return err
2559
+
}
2560
+
2561
+
if len(t.Branch) > 1000000 {
2562
+
return xerrors.Errorf("Value in field t.Branch was too long")
2563
+
}
2564
+
2565
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Branch))); err != nil {
2566
+
return err
2567
+
}
2568
+
if _, err := cw.WriteString(string(t.Branch)); err != nil {
2569
+
return err
2570
+
}
2571
+
return nil
2572
+
}
2573
+
2574
+
func (t *RepoPull_Source) UnmarshalCBOR(r io.Reader) (err error) {
2575
+
*t = RepoPull_Source{}
2576
+
2577
+
cr := cbg.NewCborReader(r)
2578
+
2579
+
maj, extra, err := cr.ReadHeader()
2580
+
if err != nil {
2581
+
return err
2582
+
}
2583
+
defer func() {
2584
+
if err == io.EOF {
2585
+
err = io.ErrUnexpectedEOF
2586
+
}
2587
+
}()
2588
+
2589
+
if maj != cbg.MajMap {
2590
+
return fmt.Errorf("cbor input should be of type map")
2591
+
}
2592
+
2593
+
if extra > cbg.MaxLength {
2594
+
return fmt.Errorf("RepoPull_Source: map struct too large (%d)", extra)
2595
+
}
2596
+
2597
+
n := extra
2598
+
2599
+
nameBuf := make([]byte, 6)
2600
+
for i := uint64(0); i < n; i++ {
2601
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2602
+
if err != nil {
2603
+
return err
2604
+
}
2605
+
2606
+
if !ok {
2607
+
// Field doesn't exist on this type, so ignore it
2608
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2609
+
return err
2610
+
}
2611
+
continue
2612
+
}
2613
+
2614
+
switch string(nameBuf[:nameLen]) {
2615
+
// t.Repo (string) (string)
2616
+
case "repo":
2617
+
2618
+
{
2619
+
b, err := cr.ReadByte()
2620
+
if err != nil {
2621
+
return err
2622
+
}
2623
+
if b != cbg.CborNull[0] {
2624
+
if err := cr.UnreadByte(); err != nil {
2625
+
return err
2626
+
}
2627
+
2628
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2629
+
if err != nil {
2630
+
return err
2631
+
}
2632
+
2633
+
t.Repo = (*string)(&sval)
2634
+
}
2635
+
}
2636
+
// t.Branch (string) (string)
2637
+
case "branch":
2638
+
2639
+
{
2640
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2641
+
if err != nil {
2642
+
return err
2643
+
}
2644
+
2645
+
t.Branch = string(sval)
2646
}
2647
2648
default:
+15
-9
api/tangled/repopull.go
···
17
} //
18
// RECORDTYPE: RepoPull
19
type RepoPull struct {
20
-
LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull" cborgen:"$type,const=sh.tangled.repo.pull"`
21
-
Body *string `json:"body,omitempty" cborgen:"body,omitempty"`
22
-
CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"`
23
-
Patch string `json:"patch" cborgen:"patch"`
24
-
PullId int64 `json:"pullId" cborgen:"pullId"`
25
-
SourceRepo *string `json:"sourceRepo,omitempty" cborgen:"sourceRepo,omitempty"`
26
-
TargetBranch string `json:"targetBranch" cborgen:"targetBranch"`
27
-
TargetRepo string `json:"targetRepo" cborgen:"targetRepo"`
28
-
Title string `json:"title" cborgen:"title"`
0
0
0
0
0
0
29
}
···
17
} //
18
// RECORDTYPE: RepoPull
19
type RepoPull struct {
20
+
LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull" cborgen:"$type,const=sh.tangled.repo.pull"`
21
+
Body *string `json:"body,omitempty" cborgen:"body,omitempty"`
22
+
CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"`
23
+
Patch string `json:"patch" cborgen:"patch"`
24
+
PullId int64 `json:"pullId" cborgen:"pullId"`
25
+
Source *RepoPull_Source `json:"source,omitempty" cborgen:"source,omitempty"`
26
+
TargetBranch string `json:"targetBranch" cborgen:"targetBranch"`
27
+
TargetRepo string `json:"targetRepo" cborgen:"targetRepo"`
28
+
Title string `json:"title" cborgen:"title"`
29
+
}
30
+
31
+
// RepoPull_Source is a "source" in the sh.tangled.repo.pull schema.
32
+
type RepoPull_Source struct {
33
+
Branch string `json:"branch" cborgen:"branch"`
34
+
Repo *string `json:"repo,omitempty" cborgen:"repo,omitempty"`
35
}
+8
-1
appview/db/db.go
···
257
})
258
259
runMigration(db, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
260
-
// add unconstrained column
261
_, err := tx.Exec(`
262
alter table comments add column deleted text; -- timestamp
263
alter table comments add column edited text; -- timestamp
0
0
0
0
0
0
0
0
264
`)
265
return err
266
})
···
257
})
258
259
runMigration(db, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
0
260
_, err := tx.Exec(`
261
alter table comments add column deleted text; -- timestamp
262
alter table comments add column edited text; -- timestamp
263
+
`)
264
+
return err
265
+
})
266
+
267
+
runMigration(db, "add-source-info-to-pulls", func(tx *sql.Tx) error {
268
+
_, err := tx.Exec(`
269
+
alter table pulls add column source_branch text;
270
+
alter table pulls add column source_repo_at text;
271
`)
272
return err
273
})
+82
-7
appview/db/pulls.go
···
62
Submissions []*PullSubmission
63
64
// meta
65
-
Created time.Time
0
0
0
0
0
0
66
}
67
68
type PullSubmission struct {
···
107
108
func (p *Pull) LastRoundNumber() int {
109
return len(p.Submissions) - 1
0
0
0
0
0
0
0
0
0
0
0
0
110
}
111
112
func (s PullSubmission) AsNiceDiff(targetBranch string) types.NiceDiff {
···
175
pull.PullId = nextId
176
pull.State = PullOpen
177
178
-
_, err = tx.Exec(`
179
-
insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, rkey, state)
180
-
values (?, ?, ?, ?, ?, ?, ?, ?)
181
-
`, pull.RepoAt, pull.OwnerDid, pull.PullId, pull.Title, pull.TargetBranch, pull.Body, pull.Rkey, pull.State)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
182
if err != nil {
183
return err
184
}
···
228
target_branch,
229
pull_at,
230
body,
231
-
rkey
0
0
232
from
233
pulls
234
where
···
243
for rows.Next() {
244
var pull Pull
245
var createdAt string
0
246
err := rows.Scan(
247
&pull.OwnerDid,
248
&pull.PullId,
···
253
&pull.PullAt,
254
&pull.Body,
255
&pull.Rkey,
0
0
256
)
257
if err != nil {
258
return nil, err
···
264
}
265
pull.Created = createdTime
266
0
0
0
0
0
0
0
0
0
0
0
0
0
267
pulls = append(pulls, pull)
268
}
269
···
286
pull_at,
287
repo_at,
288
body,
289
-
rkey
0
0
290
from
291
pulls
292
where
···
296
297
var pull Pull
298
var createdAt string
0
299
err := row.Scan(
300
&pull.OwnerDid,
301
&pull.PullId,
···
307
&pull.RepoAt,
308
&pull.Body,
309
&pull.Rkey,
0
0
310
)
311
if err != nil {
312
return nil, err
···
317
return nil, err
318
}
319
pull.Created = createdTime
0
0
0
0
0
0
0
0
0
0
0
0
0
0
320
321
submissionsQuery := `
322
select
···
62
Submissions []*PullSubmission
63
64
// meta
65
+
Created time.Time
66
+
PullSource *PullSource
67
+
}
68
+
69
+
type PullSource struct {
70
+
Branch string
71
+
Repo *syntax.ATURI
72
}
73
74
type PullSubmission struct {
···
113
114
func (p *Pull) LastRoundNumber() int {
115
return len(p.Submissions) - 1
116
+
}
117
+
118
+
func (p *Pull) IsSameRepoBranch() bool {
119
+
if p.PullSource != nil {
120
+
if p.PullSource.Repo != nil {
121
+
return p.PullSource.Repo == &p.RepoAt
122
+
} else {
123
+
// no repo specified
124
+
return true
125
+
}
126
+
}
127
+
return false
128
}
129
130
func (s PullSubmission) AsNiceDiff(targetBranch string) types.NiceDiff {
···
193
pull.PullId = nextId
194
pull.State = PullOpen
195
196
+
var sourceBranch, sourceRepoAt *string
197
+
if pull.PullSource != nil {
198
+
sourceBranch = &pull.PullSource.Branch
199
+
if pull.PullSource.Repo != nil {
200
+
x := pull.PullSource.Repo.String()
201
+
sourceRepoAt = &x
202
+
}
203
+
}
204
+
205
+
_, err = tx.Exec(
206
+
`
207
+
insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, rkey, state, source_branch, source_repo_at)
208
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
209
+
pull.RepoAt,
210
+
pull.OwnerDid,
211
+
pull.PullId,
212
+
pull.Title,
213
+
pull.TargetBranch,
214
+
pull.Body,
215
+
pull.Rkey,
216
+
pull.State,
217
+
sourceBranch,
218
+
sourceRepoAt,
219
+
)
220
if err != nil {
221
return err
222
}
···
266
target_branch,
267
pull_at,
268
body,
269
+
rkey,
270
+
source_branch,
271
+
source_repo_at
272
from
273
pulls
274
where
···
283
for rows.Next() {
284
var pull Pull
285
var createdAt string
286
+
var sourceBranch, sourceRepoAt sql.NullString
287
err := rows.Scan(
288
&pull.OwnerDid,
289
&pull.PullId,
···
294
&pull.PullAt,
295
&pull.Body,
296
&pull.Rkey,
297
+
&sourceBranch,
298
+
&sourceRepoAt,
299
)
300
if err != nil {
301
return nil, err
···
307
}
308
pull.Created = createdTime
309
310
+
if sourceBranch.Valid {
311
+
pull.PullSource = &PullSource{
312
+
Branch: sourceBranch.String,
313
+
}
314
+
if sourceRepoAt.Valid {
315
+
sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String)
316
+
if err != nil {
317
+
return nil, err
318
+
}
319
+
pull.PullSource.Repo = &sourceRepoAtParsed
320
+
}
321
+
}
322
+
323
pulls = append(pulls, pull)
324
}
325
···
342
pull_at,
343
repo_at,
344
body,
345
+
rkey,
346
+
source_branch,
347
+
source_repo_at
348
from
349
pulls
350
where
···
354
355
var pull Pull
356
var createdAt string
357
+
var sourceBranch, sourceRepoAt sql.NullString
358
err := row.Scan(
359
&pull.OwnerDid,
360
&pull.PullId,
···
366
&pull.RepoAt,
367
&pull.Body,
368
&pull.Rkey,
369
+
&sourceBranch,
370
+
&sourceRepoAt,
371
)
372
if err != nil {
373
return nil, err
···
378
return nil, err
379
}
380
pull.Created = createdTime
381
+
382
+
// populate source
383
+
if sourceBranch.Valid {
384
+
pull.PullSource = &PullSource{
385
+
Branch: sourceBranch.String,
386
+
}
387
+
if sourceRepoAt.Valid {
388
+
sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String)
389
+
if err != nil {
390
+
return nil, err
391
+
}
392
+
pull.PullSource.Repo = &sourceRepoAtParsed
393
+
}
394
+
}
395
396
submissionsQuery := `
397
select
+2
-3
appview/pages/pages.go
···
591
RepoInfo RepoInfo
592
Active string
593
DidHandleMap map[string]string
594
-
595
-
Pull db.Pull
596
-
MergeCheck types.MergeCheckResponse
597
}
598
599
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
···
591
RepoInfo RepoInfo
592
Active string
593
DidHandleMap map[string]string
594
+
Pull *db.Pull
595
+
MergeCheck types.MergeCheckResponse
0
596
}
597
598
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
+15
-8
appview/pages/templates/fragments/pullActions.html
···
9
{{ $isConflicted := and .MergeCheck (or .MergeCheck.Error .MergeCheck.IsConflicted) }}
10
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
11
{{ $isLastRound := eq $roundNumber $lastIdx }}
0
12
<div class="relative w-fit">
13
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
14
<div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2">
···
36
{{ end }}
37
38
{{ if and $isPullAuthor $isOpen $isLastRound }}
39
-
<button
40
-
hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit"
41
-
hx-target="#actions-{{$roundNumber}}"
42
-
hx-swap="outerHtml"
43
-
class="btn p-2 flex items-center gap-2">
44
-
{{ i "rotate-ccw" "w-4 h-4" }}
45
-
<span>resubmit</span>
46
-
</button>
0
0
0
0
0
0
47
{{ end }}
48
49
{{ if and (or $isPullAuthor $isPushAllowed) $isOpen $isLastRound }}
···
9
{{ $isConflicted := and .MergeCheck (or .MergeCheck.Error .MergeCheck.IsConflicted) }}
10
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
11
{{ $isLastRound := eq $roundNumber $lastIdx }}
12
+
{{ $isSameRepoBranch := .Pull.IsSameRepoBranch }}
13
<div class="relative w-fit">
14
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
15
<div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2">
···
37
{{ end }}
38
39
{{ if and $isPullAuthor $isOpen $isLastRound }}
40
+
<button id="resubmitBtn"
41
+
{{ if $isSameRepoBranch }}
42
+
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit"
43
+
{{ else }}
44
+
hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit"
45
+
hx-target="#actions-{{$roundNumber}}"
46
+
hx-swap="outerHtml"
47
+
{{ end }}
48
+
49
+
hx-disabled-elt="#resubmitBtn"
50
+
class="btn p-2 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
51
+
{{ i "rotate-ccw" "w-4 h-4" }}
52
+
<span>resubmit</span>
53
+
</button>
54
{{ end }}
55
56
{{ if and (or $isPullAuthor $isPushAllowed) $isOpen $isLastRound }}
+73
-39
appview/pages/templates/repo/pulls/new.html
···
8
<ul class="list-decimal pl-10 space-y-2 text-gray-700 dark:text-gray-300">
9
<li class="leading-relaxed">Clone this repository.</li>
10
<li class="leading-relaxed">Make your changes in your local repository.</li>
11
-
<li class="leading-relaxed">Grab the diff using <code class="bg-gray-100 dark:bg-gray-700 px-1 py-0.5 rounded text-gray-800 dark:text-gray-200 font-mono text-sm">git diff</code>.</li>
12
<li class="leading-relaxed">Paste the diff output in the form below.</li>
13
</ul>
14
</p>
···
19
hx-swap="none"
20
>
21
<div class="flex flex-col gap-4">
22
-
<div>
23
-
<label for="title" class="dark:text-white">write a title</label>
24
-
<input type="text" name="title" id="title" class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600" />
0
25
26
-
<label for="targetBranch" class="dark:text-white">select a target branch</label>
27
-
<p class="text-gray-500 dark:text-gray-400">
28
-
The branch you want to make your change against.
29
-
</p>
30
-
<select
31
-
name="targetBranch"
32
-
class="p-1 mb-2 border border-gray-200 bg-white dark:bg-gray-700 dark:text-white dark:border-gray-600"
33
-
>
34
-
<option disabled selected>select a branch</option>
35
-
{{ range .Branches }}
36
-
<option value="{{ .Reference.Name }}" class="py-1">
37
-
{{ .Reference.Name }}
38
-
</option>
39
-
{{ end }}
40
-
</select>
41
-
<label for="body" class="dark:text-white">add a description</label>
42
-
<textarea
43
-
name="body"
44
-
id="body"
45
-
rows="6"
46
-
class="w-full resize-y dark:bg-gray-700 dark:text-white dark:border-gray-600"
47
-
placeholder="Describe your change. Markdown is supported."
48
></textarea>
0
49
50
-
<div class="mt-4">
51
-
<label for="patch" class="dark:text-white">paste your patch here</label>
52
-
<textarea
53
-
name="patch"
54
-
id="patch"
55
-
rows="10"
56
-
class="w-full resize-y font-mono dark:bg-gray-700 dark:text-white dark:border-gray-600"
57
-
placeholder="Paste your git diff output here."
58
-
></textarea>
59
-
</div>
60
-
</div>
61
-
<div>
62
-
<button type="submit" class="btn dark:bg-gray-600 dark:hover:bg-gray-500 dark:text-white">create</button>
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
63
</div>
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
64
</div>
65
<div id="pull" class="error dark:text-red-300"></div>
66
</form>
67
{{ end }}
0
0
0
0
···
8
<ul class="list-decimal pl-10 space-y-2 text-gray-700 dark:text-gray-300">
9
<li class="leading-relaxed">Clone this repository.</li>
10
<li class="leading-relaxed">Make your changes in your local repository.</li>
11
+
<li class="leading-relaxed">Grab the diff using <code>git diff</code>.</li>
12
<li class="leading-relaxed">Paste the diff output in the form below.</li>
13
</ul>
14
</p>
···
19
hx-swap="none"
20
>
21
<div class="flex flex-col gap-4">
22
+
<div>
23
+
<label for="title" class="dark:text-white">write a title</label>
24
+
<input type="text" name="title" id="title" class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600" />
25
+
</div>
26
27
+
<div>
28
+
<label for="body" class="dark:text-white">add a description</label>
29
+
<textarea
30
+
name="body"
31
+
id="body"
32
+
rows="6"
33
+
class="w-full resize-y dark:bg-gray-700 dark:text-white dark:border-gray-600"
34
+
placeholder="Describe your change. Markdown is supported."
0
0
0
0
0
0
0
0
0
0
0
0
0
0
35
></textarea>
36
+
</div>
37
38
+
<div>
39
+
<label for="targetBranch" class="dark:text-white">configure branches</label>
40
+
<div class="flex flex-wrap gap-2 items-center">
41
+
<select
42
+
required
43
+
name="targetBranch"
44
+
class="p-1 border border-gray-200 bg-white dark:bg-gray-700 dark:text-white dark:border-gray-600"
45
+
>
46
+
<option disabled selected>target branch</option>
47
+
{{ range .Branches }}
48
+
<option value="{{ .Reference.Name }}" class="py-1">
49
+
{{ .Reference.Name }}
50
+
</option>
51
+
{{ end }}
52
+
</select>
53
+
54
+
{{ if .RepoInfo.Roles.IsPushAllowed }}
55
+
{{ i "move-left" "w-5 h-5" }}
56
+
<select
57
+
name="sourceBranch"
58
+
class="p-1 border border-gray-200 bg-white dark:bg-gray-700 dark:text-white dark:border-gray-600"
59
+
>
60
+
<option disabled selected>source branch</option>
61
+
{{ range .Branches }}
62
+
<option value="{{ .Reference.Name }}" class="py-1">
63
+
{{ .Reference.Name }}
64
+
</option>
65
+
{{ end }}
66
+
</select>
67
+
{{ end }}
68
+
69
</div>
70
+
</div>
71
+
72
+
<div class="mt-4">
73
+
{{ $label := "paste your patch here" }}
74
+
{{ $rows := 10 }}
75
+
{{ if .RepoInfo.Roles.IsPushAllowed }}
76
+
{{ $label = "or paste your patch here" }}
77
+
{{ $rows = 4 }}
78
+
{{ end }}
79
+
80
+
<label for="patch" class="dark:text-white">{{ $label }}</label>
81
+
<textarea
82
+
name="patch"
83
+
id="patch"
84
+
rows="{{$rows}}"
85
+
class="w-full resize-y font-mono dark:bg-gray-700 dark:text-white dark:border-gray-600"
86
+
placeholder="Paste your git diff output here."
87
+
></textarea>
88
+
</div>
89
+
90
+
<div class="flex justify-end items-center gap-2">
91
+
<button type="submit" class="btn">create</button>
92
+
</div>
93
+
94
</div>
95
<div id="pull" class="error dark:text-red-300"></div>
96
</form>
97
{{ end }}
98
+
99
+
{{ define "repoAfter" }}
100
+
<div id="patch-preview" class="error dark:text-red-300"></div>
101
+
{{ end }}
+9
-1
appview/pages/templates/repo/pulls/pull.html
···
39
<span class="select-none before:content-['\00B7']"></span>
40
<time>{{ .Pull.Created | timeFmt }}</time>
41
<span class="select-none before:content-['\00B7']"></span>
42
-
<span>targeting branch
0
43
<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">
44
{{ .Pull.TargetBranch }}
45
</span>
46
</span>
0
0
0
0
0
0
0
47
</span>
48
</div>
49
···
39
<span class="select-none before:content-['\00B7']"></span>
40
<time>{{ .Pull.Created | timeFmt }}</time>
41
<span class="select-none before:content-['\00B7']"></span>
42
+
<span>
43
+
targeting
44
<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">
45
{{ .Pull.TargetBranch }}
46
</span>
47
</span>
48
+
{{ if .Pull.IsSameRepoBranch }}
49
+
<span>from
50
+
<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">
51
+
{{ .Pull.PullSource.Branch }}
52
+
</span>
53
+
</span>
54
+
{{ end }}
55
</span>
56
</div>
57
+8
-1
appview/pages/templates/repo/pulls/pulls.html
···
73
</span>
74
75
<span class="before:content-['·']">
76
-
targeting branch
77
<span class="text-xs rounded bg-gray-100 dark:bg-gray-600 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
78
{{ .TargetBranch }}
79
</span>
80
</span>
0
0
0
0
0
0
0
81
</p>
82
</div>
83
{{ end }}
···
73
</span>
74
75
<span class="before:content-['·']">
76
+
targeting
77
<span class="text-xs rounded bg-gray-100 dark:bg-gray-600 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
78
{{ .TargetBranch }}
79
</span>
80
</span>
81
+
{{ if .IsSameRepoBranch }}
82
+
<span>from
83
+
<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">
84
+
{{ .PullSource.Branch }}
85
+
</span>
86
+
</span>
87
+
{{ end }}
88
</p>
89
</div>
90
{{ end }}
+103
-3
appview/state/pull.go
···
110
LoggedInUser: user,
111
RepoInfo: f.RepoInfo(s, user),
112
DidHandleMap: didHandleMap,
113
-
Pull: *pull,
114
MergeCheck: mergeCheckResponse,
115
})
116
}
···
450
Branches: result.Branches,
451
})
452
case http.MethodPost:
0
453
title := r.FormValue("title")
454
body := r.FormValue("body")
455
targetBranch := r.FormValue("targetBranch")
0
456
patch := r.FormValue("patch")
457
458
-
if title == "" || body == "" || patch == "" || targetBranch == "" {
459
-
s.pages.Notice(w, "pull", "Title, body and patch diff are required.")
0
0
0
0
460
return
461
}
462
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
463
// Validate patch format
464
if !isPatchValid(patch) {
465
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
···
488
Submissions: []*db.PullSubmission{
489
&initialSubmission,
490
},
0
491
})
492
if err != nil {
493
log.Println("failed to create pull request", err)
···
553
return
554
case http.MethodPost:
555
patch := r.FormValue("patch")
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
556
557
if patch == "" {
558
s.pages.Notice(w, "resubmit-error", "Patch is empty.")
···
110
LoggedInUser: user,
111
RepoInfo: f.RepoInfo(s, user),
112
DidHandleMap: didHandleMap,
113
+
Pull: pull,
114
MergeCheck: mergeCheckResponse,
115
})
116
}
···
450
Branches: result.Branches,
451
})
452
case http.MethodPost:
453
+
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
454
title := r.FormValue("title")
455
body := r.FormValue("body")
456
targetBranch := r.FormValue("targetBranch")
457
+
sourceBranch := r.FormValue("sourceBranch")
458
patch := r.FormValue("patch")
459
460
+
if patch == "" {
461
+
if isPushAllowed && sourceBranch == "" {
462
+
s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.")
463
+
return
464
+
}
465
+
s.pages.Notice(w, "pull", "Patch is empty.")
466
return
467
}
468
469
+
if patch != "" && sourceBranch != "" {
470
+
s.pages.Notice(w, "pull", "Cannot select both patch and source branch.")
471
+
return
472
+
}
473
+
474
+
if title == "" || body == "" || targetBranch == "" {
475
+
s.pages.Notice(w, "pull", "Title, body and target branch are required.")
476
+
return
477
+
}
478
+
479
+
// TODO: check if knot has this capability
480
+
var pullSource *db.PullSource
481
+
if sourceBranch != "" && isPushAllowed {
482
+
pullSource = &db.PullSource{
483
+
Branch: sourceBranch,
484
+
}
485
+
// generate a patch using /compare
486
+
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
487
+
if err != nil {
488
+
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
489
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
490
+
return
491
+
}
492
+
493
+
log.Println(targetBranch, sourceBranch)
494
+
495
+
resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)
496
+
switch resp.StatusCode {
497
+
case 404:
498
+
case 400:
499
+
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
500
+
}
501
+
502
+
respBody, err := io.ReadAll(resp.Body)
503
+
if err != nil {
504
+
log.Println("failed to compare across branches")
505
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
506
+
}
507
+
defer resp.Body.Close()
508
+
509
+
var diffTreeResponse types.RepoDiffTreeResponse
510
+
err = json.Unmarshal(respBody, &diffTreeResponse)
511
+
if err != nil {
512
+
log.Println("failed to unmarshal diff tree response", err)
513
+
log.Println(string(respBody))
514
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
515
+
}
516
+
517
+
patch = diffTreeResponse.DiffTree.Patch
518
+
}
519
+
520
+
log.Println(patch)
521
+
522
// Validate patch format
523
if !isPatchValid(patch) {
524
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
···
547
Submissions: []*db.PullSubmission{
548
&initialSubmission,
549
},
550
+
PullSource: pullSource,
551
})
552
if err != nil {
553
log.Println("failed to create pull request", err)
···
613
return
614
case http.MethodPost:
615
patch := r.FormValue("patch")
616
+
617
+
// this pull is a branch based pull
618
+
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
619
+
if pull.IsSameRepoBranch() && isPushAllowed {
620
+
sourceBranch := pull.PullSource.Branch
621
+
targetBranch := pull.TargetBranch
622
+
// extract patch by performing compare
623
+
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
624
+
if err != nil {
625
+
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
626
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
627
+
return
628
+
}
629
+
630
+
log.Println(targetBranch, sourceBranch)
631
+
632
+
resp, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)
633
+
switch resp.StatusCode {
634
+
case 404:
635
+
case 400:
636
+
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
637
+
}
638
+
639
+
respBody, err := io.ReadAll(resp.Body)
640
+
if err != nil {
641
+
log.Println("failed to compare across branches")
642
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
643
+
}
644
+
defer resp.Body.Close()
645
+
646
+
var diffTreeResponse types.RepoDiffTreeResponse
647
+
err = json.Unmarshal(respBody, &diffTreeResponse)
648
+
if err != nil {
649
+
log.Println("failed to unmarshal diff tree response", err)
650
+
log.Println(string(respBody))
651
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
652
+
}
653
+
654
+
patch = diffTreeResponse.DiffTree.Patch
655
+
}
656
657
if patch == "" {
658
s.pages.Notice(w, "resubmit-error", "Patch is empty.")
+15
appview/state/signer.go
···
315
316
return us.client.Do(req)
317
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
315
316
return us.client.Do(req)
317
}
318
+
319
+
func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*http.Response, error) {
320
+
const (
321
+
Method = "GET"
322
+
)
323
+
324
+
endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, rev1, rev2)
325
+
326
+
req, err := us.newRequest(Method, endpoint, nil)
327
+
if err != nil {
328
+
return nil, err
329
+
}
330
+
331
+
return us.client.Do(req)
332
+
}
+1
cmd/gen.go
···
23
shtangled.RepoIssue{},
24
shtangled.Repo{},
25
shtangled.RepoPull{},
0
26
shtangled.RepoPullStatus{},
27
shtangled.RepoPullComment{},
28
); err != nil {
···
23
shtangled.RepoIssue{},
24
shtangled.Repo{},
25
shtangled.RepoPull{},
26
+
shtangled.RepoPull_Source{},
27
shtangled.RepoPullStatus{},
28
shtangled.RepoPullComment{},
29
); err != nil {
+71
-10
knotserver/git/diff.go
···
6
"strings"
7
8
"github.com/bluekeyes/go-gitdiff/gitdiff"
0
9
"github.com/go-git/go-git/v5/plumbing/object"
10
"tangled.sh/tangled.sh/core/types"
11
)
···
46
}
47
48
nd := types.NiceDiff{}
49
-
nd.Commit.This = c.Hash.String()
50
-
51
-
if parent.Hash.IsZero() {
52
-
nd.Commit.Parent = ""
53
-
} else {
54
-
nd.Commit.Parent = parent.Hash.String()
55
-
}
56
-
nd.Commit.Author = c.Author
57
-
nd.Commit.Message = c.Message
58
-
59
for _, d := range diffs {
60
ndiff := types.Diff{}
61
ndiff.Name.New = d.NewName
···
82
}
83
84
nd.Stat.FilesChanged = len(diffs)
0
0
0
0
0
0
0
0
0
85
86
return &nd, nil
87
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
6
"strings"
7
8
"github.com/bluekeyes/go-gitdiff/gitdiff"
9
+
"github.com/go-git/go-git/v5/plumbing"
10
"github.com/go-git/go-git/v5/plumbing/object"
11
"tangled.sh/tangled.sh/core/types"
12
)
···
47
}
48
49
nd := types.NiceDiff{}
0
0
0
0
0
0
0
0
0
0
50
for _, d := range diffs {
51
ndiff := types.Diff{}
52
ndiff.Name.New = d.NewName
···
73
}
74
75
nd.Stat.FilesChanged = len(diffs)
76
+
nd.Commit.This = c.Hash.String()
77
+
78
+
if parent.Hash.IsZero() {
79
+
nd.Commit.Parent = ""
80
+
} else {
81
+
nd.Commit.Parent = parent.Hash.String()
82
+
}
83
+
nd.Commit.Author = c.Author
84
+
nd.Commit.Message = c.Message
85
86
return &nd, nil
87
}
88
+
89
+
func (g *GitRepo) DiffTree(rev1, rev2 string) (*types.DiffTree, error) {
90
+
commit1, err := g.resolveRevision(rev1)
91
+
if err != nil {
92
+
return nil, fmt.Errorf("Invalid revision: %s", rev1)
93
+
}
94
+
95
+
commit2, err := g.resolveRevision(rev2)
96
+
if err != nil {
97
+
return nil, fmt.Errorf("Invalid revision: %s", rev2)
98
+
}
99
+
100
+
log.Println(commit1, commit2)
101
+
102
+
tree1, err := commit1.Tree()
103
+
if err != nil {
104
+
return nil, err
105
+
}
106
+
107
+
tree2, err := commit2.Tree()
108
+
if err != nil {
109
+
return nil, err
110
+
}
111
+
112
+
diff, err := object.DiffTree(tree1, tree2)
113
+
if err != nil {
114
+
return nil, err
115
+
}
116
+
117
+
patch, err := diff.Patch()
118
+
if err != nil {
119
+
return nil, err
120
+
}
121
+
122
+
diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
123
+
if err != nil {
124
+
return nil, err
125
+
}
126
+
127
+
return &types.DiffTree{
128
+
Rev1: commit1.Hash.String(),
129
+
Rev2: commit2.Hash.String(),
130
+
Patch: patch.String(),
131
+
Diff: diffs,
132
+
}, nil
133
+
}
134
+
135
+
func (g *GitRepo) resolveRevision(revStr string) (*object.Commit, error) {
136
+
rev, err := g.r.ResolveRevision(plumbing.Revision(revStr))
137
+
if err != nil {
138
+
return nil, fmt.Errorf("resolving revision %s: %w", revStr, err)
139
+
}
140
+
141
+
commit, err := g.r.CommitObject(*rev)
142
+
if err != nil {
143
+
144
+
return nil, fmt.Errorf("getting commit for %s: %w", revStr, err)
145
+
}
146
+
147
+
return commit, nil
148
+
}
+10
knotserver/git/git.go
···
131
return &g, nil
132
}
133
0
0
0
0
0
0
0
0
0
0
134
func (g *GitRepo) Commits() ([]*object.Commit, error) {
135
ci, err := g.r.Log(&git.LogOptions{From: g.h})
136
if err != nil {
···
131
return &g, nil
132
}
133
134
+
func PlainOpen(path string) (*GitRepo, error) {
135
+
var err error
136
+
g := GitRepo{path: path}
137
+
g.r, err = git.PlainOpen(path)
138
+
if err != nil {
139
+
return nil, fmt.Errorf("opening %s: %w", path, err)
140
+
}
141
+
return &g, nil
142
+
}
143
+
144
func (g *GitRepo) Commits() ([]*object.Commit, error) {
145
ci, err := g.r.Log(&git.LogOptions{From: g.h})
146
if err != nil {
+1
knotserver/handler.go
···
83
r.Get("/", h.RepoIndex)
84
r.Get("/info/refs", h.InfoRefs)
85
r.Post("/git-upload-pack", h.UploadPack)
0
86
87
r.Route("/merge", func(r chi.Router) {
88
r.With(h.VerifySignature)
···
83
r.Get("/", h.RepoIndex)
84
r.Get("/info/refs", h.InfoRefs)
85
r.Post("/git-upload-pack", h.UploadPack)
86
+
r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects
87
88
r.Route("/merge", func(r chi.Router) {
89
r.With(h.VerifySignature)
+30
-1
knotserver/routes.go
···
36
37
capabilities := map[string]any{
38
"pull_requests": map[string]any{
39
-
"patch_submissions": true,
0
0
40
},
41
}
42
···
681
}
682
writeError(w, err.Error(), http.StatusInternalServerError)
683
h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
684
}
685
686
func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
···
36
37
capabilities := map[string]any{
38
"pull_requests": map[string]any{
39
+
"patch_submissions": true,
40
+
"branch_submissions": true,
41
+
"fork_submissions": false,
42
},
43
}
44
···
683
}
684
writeError(w, err.Error(), http.StatusInternalServerError)
685
h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
686
+
}
687
+
688
+
func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
689
+
rev1 := chi.URLParam(r, "rev1")
690
+
rev1, _ = url.PathUnescape(rev1)
691
+
692
+
rev2 := chi.URLParam(r, "rev2")
693
+
rev2, _ = url.PathUnescape(rev2)
694
+
695
+
l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
696
+
697
+
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
698
+
gr, err := git.PlainOpen(path)
699
+
if err != nil {
700
+
notFound(w)
701
+
return
702
+
}
703
+
704
+
difftree, err := gr.DiffTree(rev1, rev2)
705
+
if err != nil {
706
+
l.Error("error comparing revisions", "msg", err.Error())
707
+
writeError(w, "error comparing revisions", http.StatusBadRequest)
708
+
return
709
+
}
710
+
711
+
writeJSON(w, types.RepoDiffTreeResponse{difftree})
712
+
return
713
}
714
715
func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
+24
-5
lexicons/pulls/pull.json
···
9
"key": "tid",
10
"record": {
11
"type": "object",
12
-
"required": ["targetRepo", "targetBranch", "pullId", "title", "patch"],
0
0
0
0
0
0
13
"properties": {
14
"targetRepo": {
15
"type": "string",
···
18
"targetBranch": {
19
"type": "string"
20
},
21
-
"sourceRepo": {
22
-
"type": "string",
23
-
"format": "at-uri"
24
-
},
25
"pullId": {
26
"type": "integer"
27
},
···
37
},
38
"patch": {
39
"type": "string"
0
0
0
0
40
}
0
0
0
0
0
0
0
0
0
0
0
0
0
41
}
42
}
43
}
···
9
"key": "tid",
10
"record": {
11
"type": "object",
12
+
"required": [
13
+
"targetRepo",
14
+
"targetBranch",
15
+
"pullId",
16
+
"title",
17
+
"patch"
18
+
],
19
"properties": {
20
"targetRepo": {
21
"type": "string",
···
24
"targetBranch": {
25
"type": "string"
26
},
0
0
0
0
27
"pullId": {
28
"type": "integer"
29
},
···
39
},
40
"patch": {
41
"type": "string"
42
+
},
43
+
"source": {
44
+
"type": "ref",
45
+
"ref": "#source"
46
}
47
+
}
48
+
}
49
+
},
50
+
"source": {
51
+
"type": "object",
52
+
"required": ["branch"],
53
+
"properties": {
54
+
"branch": {
55
+
"type": "string"
56
+
},
57
+
"repo": {
58
+
"type": "string",
59
+
"format": "at-uri"
60
}
61
}
62
}
+7
types/diff.go
···
38
} `json:"stat"`
39
Diff []Diff `json:"diff"`
40
}
0
0
0
0
0
0
0
···
38
} `json:"stat"`
39
Diff []Diff `json:"diff"`
40
}
41
+
42
+
type DiffTree struct {
43
+
Rev1 string `json:"rev1"`
44
+
Rev2 string `json:"rev2"`
45
+
Patch string `json:"patch"`
46
+
Diff []*gitdiff.File `json:"diff"`
47
+
}
+4
types/repo.go
···
32
Diff *NiceDiff `json:"diff,omitempty"`
33
}
34
0
0
0
0
35
type RepoTreeResponse struct {
36
Ref string `json:"ref,omitempty"`
37
Parent string `json:"parent,omitempty"`
···
32
Diff *NiceDiff `json:"diff,omitempty"`
33
}
34
35
+
type RepoDiffTreeResponse struct {
36
+
DiffTree *DiffTree `json:"difftree,omitempty"`
37
+
}
38
+
39
type RepoTreeResponse struct {
40
Ref string `json:"ref,omitempty"`
41
Parent string `json:"parent,omitempty"`