+80
patchutil/patchutil.go
+80
patchutil/patchutil.go
···
316
316
317
317
return nd
318
318
}
319
+
320
+
func extractTrailersFromCommitMessage(commitMsg string) (string, []string) {
321
+
lines := strings.Split(strings.TrimSpace(commitMsg), "\n")
322
+
if len(lines) == 0 {
323
+
return "", []string{}
324
+
}
325
+
326
+
trailerStart := len(lines)
327
+
trailers := []string{}
328
+
329
+
// Loop over commit message lines in reverse order to find trailers
330
+
for i := len(lines) - 1; i >= 0; i-- {
331
+
line := strings.TrimSpace(lines[i])
332
+
if line == "" {
333
+
continue
334
+
}
335
+
336
+
// Check if line looks like a trailer (has a colon)
337
+
if colonIndex := strings.Index(line, ":"); colonIndex <= 0 {
338
+
// Not a trailer, stop scanning
339
+
break
340
+
}
341
+
342
+
trailers = append([]string{line}, trailers...)
343
+
trailerStart = i
344
+
}
345
+
346
+
// Extract message body (everything before trailers)
347
+
messageBody := ""
348
+
if trailerStart > 0 {
349
+
bodyLines := lines[:trailerStart]
350
+
for len(bodyLines) > 0 && strings.TrimSpace(bodyLines[len(bodyLines)-1]) == "" {
351
+
bodyLines = bodyLines[:len(bodyLines)-1]
352
+
}
353
+
messageBody = strings.Join(bodyLines, "\n")
354
+
}
355
+
356
+
return messageBody, trailers
357
+
}
358
+
359
+
func AddCommitMessageTrailers(patch string, trailers []string) string {
360
+
re := regexp.MustCompile(`(?s)\n(Subject: )(.*?)(---\n|\ndiff --git )`)
361
+
362
+
allMatches := re.FindAllStringSubmatch(patch, -1)
363
+
if len(allMatches) == 0 {
364
+
return patch
365
+
}
366
+
367
+
result := patch
368
+
for _, matches := range allMatches {
369
+
if len(matches) < 4 {
370
+
continue
371
+
}
372
+
373
+
commitMsg := matches[2]
374
+
messageBody, existingTrailers := extractTrailersFromCommitMessage(commitMsg)
375
+
376
+
allTrailers := append(existingTrailers, trailers...)
377
+
378
+
var newCommitMsg string
379
+
if messageBody != "" {
380
+
newCommitMsg = messageBody
381
+
if len(allTrailers) > 0 {
382
+
newCommitMsg += "\n\n"
383
+
newCommitMsg += strings.Join(allTrailers, "\n")
384
+
newCommitMsg += "\n"
385
+
}
386
+
} else {
387
+
if len(allTrailers) > 0 {
388
+
newCommitMsg = "\n" + strings.Join(allTrailers, "\n") + "\n"
389
+
} else {
390
+
newCommitMsg = ""
391
+
}
392
+
}
393
+
394
+
result = strings.Replace(result, matches[0], "\n"+matches[1]+newCommitMsg+matches[3], 1)
395
+
}
396
+
397
+
return result
398
+
}
+279
patchutil/patchutil_test.go
+279
patchutil/patchutil_test.go
···
322
322
})
323
323
}
324
324
}
325
+
326
+
func TestAddCommmitMessageTrailers(t *testing.T) {
327
+
tests := []struct {
328
+
name string
329
+
input string
330
+
trailers []string
331
+
expected string
332
+
}{
333
+
{
334
+
name: "Single patch with existing trailers",
335
+
input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
336
+
From: Author <author@example.com>
337
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
338
+
Subject: [PATCH] Example patch
339
+
340
+
more message body
341
+
342
+
Signed-off-by: Author <author@example.com>
343
+
Reviewed-by: Reviewer <reviewer@example.com>
344
+
---
345
+
file.txt | 2 +-
346
+
1 file changed, 1 insertions(+), 1 deletions(-)
347
+
348
+
diff --git a/file.txt b/file.txt
349
+
index 123456..789012 100644
350
+
--- a/file.txt
351
+
+++ b/file.txt
352
+
@@ -1 +1 @@
353
+
-old content
354
+
+new content
355
+
--
356
+
2.48.1`,
357
+
trailers: []string{`Pull-Id: Author <author@example.com>`, `Merge-Request: 123`},
358
+
expected: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
359
+
From: Author <author@example.com>
360
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
361
+
Subject: [PATCH] Example patch
362
+
363
+
more message body
364
+
365
+
Signed-off-by: Author <author@example.com>
366
+
Reviewed-by: Reviewer <reviewer@example.com>
367
+
Pull-Id: Author <author@example.com>
368
+
Merge-Request: 123
369
+
---
370
+
file.txt | 2 +-
371
+
1 file changed, 1 insertions(+), 1 deletions(-)
372
+
373
+
diff --git a/file.txt b/file.txt
374
+
index 123456..789012 100644
375
+
--- a/file.txt
376
+
+++ b/file.txt
377
+
@@ -1 +1 @@
378
+
-old content
379
+
+new content
380
+
--
381
+
2.48.1`,
382
+
},
383
+
{
384
+
name: "Single patch with existing trailers and no stat",
385
+
input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
386
+
From: Author <author@example.com>
387
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
388
+
Subject: [PATCH] Example patch
389
+
390
+
Signed-off-by: Author <author@example.com>
391
+
Reviewed-by: Reviewer <reviewer@example.com>
392
+
393
+
diff --git a/file.txt b/file.txt
394
+
index 123456..789012 100644
395
+
--- a/file.txt
396
+
+++ b/file.txt
397
+
@@ -1 +1 @@
398
+
-old content
399
+
+new content
400
+
--
401
+
2.48.1`,
402
+
trailers: []string{`Pull-Id: Author <author@example.com>`, `Merge-Request: 123`},
403
+
expected: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
404
+
From: Author <author@example.com>
405
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
406
+
Subject: [PATCH] Example patch
407
+
408
+
Signed-off-by: Author <author@example.com>
409
+
Reviewed-by: Reviewer <reviewer@example.com>
410
+
Pull-Id: Author <author@example.com>
411
+
Merge-Request: 123
412
+
413
+
diff --git a/file.txt b/file.txt
414
+
index 123456..789012 100644
415
+
--- a/file.txt
416
+
+++ b/file.txt
417
+
@@ -1 +1 @@
418
+
-old content
419
+
+new content
420
+
--
421
+
2.48.1`,
422
+
},
423
+
{
424
+
name: "Multiple patches with existing trailers",
425
+
input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
426
+
From: Author <author@example.com>
427
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
428
+
Subject: [PATCH 1/2] First patch
429
+
430
+
more message body
431
+
432
+
---
433
+
file.txt | 2 +-
434
+
1 file changed, 1 insertions(+), 1 deletions(-)
435
+
436
+
diff --git a/file1.txt b/file1.txt
437
+
index 123456..789012 100644
438
+
--- a/file1.txt
439
+
+++ b/file1.txt
440
+
@@ -1 +1 @@
441
+
-old content
442
+
+new content
443
+
--
444
+
2.48.1
445
+
From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001
446
+
From: Author <author@example.com>
447
+
Date: Wed, 16 Apr 2025 11:03:11 +0300
448
+
Subject: [PATCH 2/2] Second patch
449
+
450
+
more message body
451
+
452
+
Signed-off-by: Author <author@example.com>
453
+
Reviewed-by: Reviewer <reviewer@example.com>
454
+
---
455
+
file.txt | 2 +-
456
+
1 file changed, 1 insertions(+), 1 deletions(-)
457
+
458
+
diff --git a/file2.txt b/file2.txt
459
+
index abcdef..ghijkl 100644
460
+
--- a/file2.txt
461
+
+++ b/file2.txt
462
+
@@ -1 +1 @@
463
+
-foo bar
464
+
+baz qux
465
+
--
466
+
2.48.1`,
467
+
trailers: []string{`Pull-Id: Author <author@example.com>`, `Merge-Request: 123`},
468
+
expected: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
469
+
From: Author <author@example.com>
470
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
471
+
Subject: [PATCH 1/2] First patch
472
+
473
+
more message body
474
+
475
+
Pull-Id: Author <author@example.com>
476
+
Merge-Request: 123
477
+
---
478
+
file.txt | 2 +-
479
+
1 file changed, 1 insertions(+), 1 deletions(-)
480
+
481
+
diff --git a/file1.txt b/file1.txt
482
+
index 123456..789012 100644
483
+
--- a/file1.txt
484
+
+++ b/file1.txt
485
+
@@ -1 +1 @@
486
+
-old content
487
+
+new content
488
+
--
489
+
2.48.1
490
+
From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001
491
+
From: Author <author@example.com>
492
+
Date: Wed, 16 Apr 2025 11:03:11 +0300
493
+
Subject: [PATCH 2/2] Second patch
494
+
495
+
more message body
496
+
497
+
Signed-off-by: Author <author@example.com>
498
+
Reviewed-by: Reviewer <reviewer@example.com>
499
+
Pull-Id: Author <author@example.com>
500
+
Merge-Request: 123
501
+
---
502
+
file.txt | 2 +-
503
+
1 file changed, 1 insertions(+), 1 deletions(-)
504
+
505
+
diff --git a/file2.txt b/file2.txt
506
+
index abcdef..ghijkl 100644
507
+
--- a/file2.txt
508
+
+++ b/file2.txt
509
+
@@ -1 +1 @@
510
+
-foo bar
511
+
+baz qux
512
+
--
513
+
2.48.1`,
514
+
},
515
+
{
516
+
name: "Multiple patches with existing trailers and no stat",
517
+
input: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
518
+
From: Author <author@example.com>
519
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
520
+
Subject: [PATCH 1/2] First patch
521
+
522
+
with long message body
523
+
524
+
Signed-off-by: Author <author@example.com>
525
+
526
+
diff --git a/file1.txt b/file1.txt
527
+
index 123456..789012 100644
528
+
--- a/file1.txt
529
+
+++ b/file1.txt
530
+
@@ -1 +1 @@
531
+
-old content
532
+
+new content
533
+
--
534
+
2.48.1
535
+
From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001
536
+
From: Author <author@example.com>
537
+
Date: Wed, 16 Apr 2025 11:03:11 +0300
538
+
Subject: [PATCH 2/2] Second patch
539
+
540
+
Signed-off-by: Author <author@example.com>
541
+
Reviewed-by: Reviewer <reviewer@example.com>
542
+
543
+
diff --git a/file2.txt b/file2.txt
544
+
index abcdef..ghijkl 100644
545
+
--- a/file2.txt
546
+
+++ b/file2.txt
547
+
@@ -1 +1 @@
548
+
-foo bar
549
+
+baz qux
550
+
--
551
+
2.48.1`,
552
+
trailers: []string{`Pull-Id: Author <author@example.com>`, `Merge-Request: 123`},
553
+
expected: `From 3c5035488318164b81f60fe3adcd6c9199d76331 Mon Sep 17 00:00:00 2001
554
+
From: Author <author@example.com>
555
+
Date: Wed, 16 Apr 2025 11:01:00 +0300
556
+
Subject: [PATCH 1/2] First patch
557
+
558
+
with long message body
559
+
560
+
Signed-off-by: Author <author@example.com>
561
+
Pull-Id: Author <author@example.com>
562
+
Merge-Request: 123
563
+
564
+
diff --git a/file1.txt b/file1.txt
565
+
index 123456..789012 100644
566
+
--- a/file1.txt
567
+
+++ b/file1.txt
568
+
@@ -1 +1 @@
569
+
-old content
570
+
+new content
571
+
--
572
+
2.48.1
573
+
From a9529f3b3a653329a5268f0f4067225480207e3c Mon Sep 17 00:00:00 2001
574
+
From: Author <author@example.com>
575
+
Date: Wed, 16 Apr 2025 11:03:11 +0300
576
+
Subject: [PATCH 2/2] Second patch
577
+
578
+
Signed-off-by: Author <author@example.com>
579
+
Reviewed-by: Reviewer <reviewer@example.com>
580
+
Pull-Id: Author <author@example.com>
581
+
Merge-Request: 123
582
+
583
+
diff --git a/file2.txt b/file2.txt
584
+
index abcdef..ghijkl 100644
585
+
--- a/file2.txt
586
+
+++ b/file2.txt
587
+
@@ -1 +1 @@
588
+
-foo bar
589
+
+baz qux
590
+
--
591
+
2.48.1`,
592
+
},
593
+
}
594
+
595
+
for _, tt := range tests {
596
+
t.Run(tt.name, func(t *testing.T) {
597
+
result := AddCommitMessageTrailers(tt.input, tt.trailers)
598
+
if result != tt.expected {
599
+
t.Errorf("Got:\n========\n%v\n========\nExpected:\n========\n%v\n========\n", result, tt.expected)
600
+
}
601
+
})
602
+
}
603
+
}
+11
-1
appview/pulls/pulls.go
+11
-1
appview/pulls/pulls.go
···
1915
1915
return
1916
1916
}
1917
1917
1918
+
actor := s.oauth.GetUser(r) // no need to check for nil as this is an authenticated request
1919
+
1920
+
trailers := []string{
1921
+
fmt.Sprintf("Pull-id: %s", pull.PullAt()),
1922
+
fmt.Sprintf("Merged-by: %s", actor.Did),
1923
+
}
1924
+
1925
+
pullBody := pull.Body + "\n\n" + strings.Join(trailers, "\n")
1926
+
patch = patchutil.AddCommitMessageTrailers(patch, trailers)
1927
+
1918
1928
// Merge the pull request
1919
-
resp, err := ksClient.Merge([]byte(patch), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address)
1929
+
resp, err := ksClient.Merge([]byte(patch), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pullBody, ident.Handle.String(), email.Address)
1920
1930
if err != nil {
1921
1931
log.Printf("failed to merge pull request: %s", err)
1922
1932
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")