๐ (native): Fix GraalVM runtime failures in JGit and SSHD, widen IT coverage
Changes
8 files changed, +1278 -26
MODIFY
README.md
+17 -2
@@ -82,11 +82,26 @@
82
82
```shell script
83
83
./mvnw quarkus:dev # dev mode; PostgreSQL + Keycloak via Dev Services (Docker/Podman required)
84
84
./mvnw test # JVM tests
85
-./mvnw verify -Dnative # native build + integration smoke tests (HTTP health, SSH banner)
85
+./mvnw verify -Dnative # native build + integration tests (HTTP, Git protocol, SSH)
86
86
```
87
87
88
88
Native build uses a container build automatically when no local GraalVM is present
89
89
(`-Dquarkus.native.container-build=true` to force). Binary: `target/git-shark-1.0-SNAPSHOT-runner`.
90
+Native-specific fixes: JGit's NLS classes (`JGitText`, `HttpServerText`) and its config enums are
91
+registered in `META-INF/native-image/reflect-config.json`, and BouncyCastle is registered at build
92
+time via `quarkus.security.security-providers=BC` (Apache SSHD's runtime provider registration
93
+doesn't work in native images).
94
+
95
+Integration tests (Failsafe, `*IT`) run against the packaged app โ JVM jar or native binary alike:
96
+
97
+- `SmokeIT` โ health endpoint, landing page, explore page, OpenAPI document, SSH banner
98
+- `GitHttpIT` โ anonymous JGit clone of the seeded `alice/demo` repo over smart HTTP, anonymous
99
+ push rejection (refs unchanged), repository overview page, raw file serving
100
+- `SshHandshakeIT` โ full SSH handshake (KEX/host key/cipher via Apache SSHD + BouncyCastle),
101
+ asserting clean `publickey`-auth rejection for an unregistered key
102
+
103
+ITs seed demo data (`GITSHARK_DEV_SEED_DATA=true`) and isolate runtime data under
104
+`target/it-data` (see Failsafe `systemPropertyVariables` in `pom.xml`).
90
105
91
106
### Dev mode seed data
92
107
@@ -105,5 +120,5 @@
105
120
106
121
## CI
107
122
108
-`.github/workflows/ci.yml`: JVM tests on every push/PR; native build + native smoke tests
123
+`.github/workflows/ci.yml`: JVM tests on every push/PR; native build + integration tests
109
124
gate main-branch builds.
MODIFY
pom.xml
+5 -0
@@ -191,6 +191,11 @@
191
191
</native.image.path>
192
192
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
193
193
<maven.home>${maven.home}</maven.home>
194
+ <!-- Integration tests launch the packaged app (prod profile): seed alice/demo
195
+ so transport tests have a repository, and keep runtime data out of ./data -->
196
+ <quarkus.test.env.GITSHARK_DEV_SEED_DATA>true</quarkus.test.env.GITSHARK_DEV_SEED_DATA>
197
+ <quarkus.test.env.GITSHARK_STORAGE_ROOT>${project.build.directory}/it-data/repositories</quarkus.test.env.GITSHARK_STORAGE_ROOT>
198
+ <quarkus.test.env.GITSHARK_SSH_HOST_KEY>${project.build.directory}/it-data/ssh/host-key</quarkus.test.env.GITSHARK_SSH_HOST_KEY>
194
199
</systemPropertyVariables>
195
200
</configuration>
196
201
</plugin>
MODIFY
src/main/resources/META-INF/native-image/de.workaround/git-shark/reflect-config.json
+1033 -22
@@ -3,68 +3,1079 @@
3
3
"name": "java.security.KeyPairGenerator",
4
4
"queryAllDeclaredMethods": true,
5
5
"methods": [
6
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
7
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
8
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
6
+ {
7
+ "name": "getInstance",
8
+ "parameterTypes": [
9
+ "java.lang.String",
10
+ "java.lang.String"
11
+ ]
12
+ },
13
+ {
14
+ "name": "getInstance",
15
+ "parameterTypes": [
16
+ "java.lang.String",
17
+ "java.security.Provider"
18
+ ]
19
+ },
20
+ {
21
+ "name": "getInstance",
22
+ "parameterTypes": [
23
+ "java.lang.String"
24
+ ]
25
+ }
9
26
]
10
27
},
11
28
{
12
29
"name": "java.security.KeyFactory",
13
30
"queryAllDeclaredMethods": true,
14
31
"methods": [
15
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
16
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
17
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
32
+ {
33
+ "name": "getInstance",
34
+ "parameterTypes": [
35
+ "java.lang.String",
36
+ "java.lang.String"
37
+ ]
38
+ },
39
+ {
40
+ "name": "getInstance",
41
+ "parameterTypes": [
42
+ "java.lang.String",
43
+ "java.security.Provider"
44
+ ]
45
+ },
46
+ {
47
+ "name": "getInstance",
48
+ "parameterTypes": [
49
+ "java.lang.String"
50
+ ]
51
+ }
18
52
]
19
53
},
20
54
{
21
55
"name": "javax.crypto.KeyAgreement",
22
56
"queryAllDeclaredMethods": true,
23
57
"methods": [
24
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
25
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
26
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
58
+ {
59
+ "name": "getInstance",
60
+ "parameterTypes": [
61
+ "java.lang.String",
62
+ "java.lang.String"
63
+ ]
64
+ },
65
+ {
66
+ "name": "getInstance",
67
+ "parameterTypes": [
68
+ "java.lang.String",
69
+ "java.security.Provider"
70
+ ]
71
+ },
72
+ {
73
+ "name": "getInstance",
74
+ "parameterTypes": [
75
+ "java.lang.String"
76
+ ]
77
+ }
27
78
]
28
79
},
29
80
{
30
81
"name": "java.security.Signature",
31
82
"queryAllDeclaredMethods": true,
32
83
"methods": [
33
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
34
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
35
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
84
+ {
85
+ "name": "getInstance",
86
+ "parameterTypes": [
87
+ "java.lang.String",
88
+ "java.lang.String"
89
+ ]
90
+ },
91
+ {
92
+ "name": "getInstance",
93
+ "parameterTypes": [
94
+ "java.lang.String",
95
+ "java.security.Provider"
96
+ ]
97
+ },
98
+ {
99
+ "name": "getInstance",
100
+ "parameterTypes": [
101
+ "java.lang.String"
102
+ ]
103
+ }
36
104
]
37
105
},
38
106
{
39
107
"name": "javax.crypto.Cipher",
40
108
"queryAllDeclaredMethods": true,
41
109
"methods": [
42
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
43
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
44
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
110
+ {
111
+ "name": "getInstance",
112
+ "parameterTypes": [
113
+ "java.lang.String",
114
+ "java.lang.String"
115
+ ]
116
+ },
117
+ {
118
+ "name": "getInstance",
119
+ "parameterTypes": [
120
+ "java.lang.String",
121
+ "java.security.Provider"
122
+ ]
123
+ },
124
+ {
125
+ "name": "getInstance",
126
+ "parameterTypes": [
127
+ "java.lang.String"
128
+ ]
129
+ }
45
130
]
46
131
},
47
132
{
48
133
"name": "java.security.MessageDigest",
49
134
"queryAllDeclaredMethods": true,
50
135
"methods": [
51
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
52
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
53
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
136
+ {
137
+ "name": "getInstance",
138
+ "parameterTypes": [
139
+ "java.lang.String",
140
+ "java.lang.String"
141
+ ]
142
+ },
143
+ {
144
+ "name": "getInstance",
145
+ "parameterTypes": [
146
+ "java.lang.String",
147
+ "java.security.Provider"
148
+ ]
149
+ },
150
+ {
151
+ "name": "getInstance",
152
+ "parameterTypes": [
153
+ "java.lang.String"
154
+ ]
155
+ }
54
156
]
55
157
},
56
158
{
57
159
"name": "javax.crypto.Mac",
58
160
"queryAllDeclaredMethods": true,
59
161
"methods": [
60
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.lang.String"]},
61
- {"name": "getInstance", "parameterTypes": ["java.lang.String", "java.security.Provider"]},
62
- {"name": "getInstance", "parameterTypes": ["java.lang.String"]}
162
+ {
163
+ "name": "getInstance",
164
+ "parameterTypes": [
165
+ "java.lang.String",
166
+ "java.lang.String"
167
+ ]
168
+ },
169
+ {
170
+ "name": "getInstance",
171
+ "parameterTypes": [
172
+ "java.lang.String",
173
+ "java.security.Provider"
174
+ ]
175
+ },
176
+ {
177
+ "name": "getInstance",
178
+ "parameterTypes": [
179
+ "java.lang.String"
180
+ ]
181
+ }
63
182
]
64
183
},
65
184
{
66
185
"name": "org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory",
67
186
"allDeclaredConstructors": true,
68
187
"queryAllDeclaredConstructors": true
188
+ },
189
+ {
190
+ "name": "org.eclipse.jgit.internal.JGitText",
191
+ "allDeclaredConstructors": true,
192
+ "allDeclaredFields": true
193
+ },
194
+ {
195
+ "name": "org.eclipse.jgit.http.server.HttpServerText",
196
+ "allDeclaredConstructors": true,
197
+ "allDeclaredFields": true
198
+ },
199
+ {
200
+ "name": "org.eclipse.jgit.api.CheckoutCommand$Stage",
201
+ "methods": [
202
+ {
203
+ "name": "values",
204
+ "parameterTypes": []
205
+ }
206
+ ]
207
+ },
208
+ {
209
+ "name": "org.eclipse.jgit.api.CheckoutResult$Status",
210
+ "methods": [
211
+ {
212
+ "name": "values",
213
+ "parameterTypes": []
214
+ }
215
+ ]
216
+ },
217
+ {
218
+ "name": "org.eclipse.jgit.api.CherryPickResult$CherryPickStatus",
219
+ "methods": [
220
+ {
221
+ "name": "values",
222
+ "parameterTypes": []
223
+ }
224
+ ]
225
+ },
226
+ {
227
+ "name": "org.eclipse.jgit.api.CloneCommand$FETCH_TYPE",
228
+ "methods": [
229
+ {
230
+ "name": "values",
231
+ "parameterTypes": []
232
+ }
233
+ ]
234
+ },
235
+ {
236
+ "name": "org.eclipse.jgit.api.CreateBranchCommand$SetupUpstreamMode",
237
+ "methods": [
238
+ {
239
+ "name": "values",
240
+ "parameterTypes": []
241
+ }
242
+ ]
243
+ },
244
+ {
245
+ "name": "org.eclipse.jgit.api.ListBranchCommand$ListMode",
246
+ "methods": [
247
+ {
248
+ "name": "values",
249
+ "parameterTypes": []
250
+ }
251
+ ]
252
+ },
253
+ {
254
+ "name": "org.eclipse.jgit.api.MergeCommand$ConflictStyle",
255
+ "methods": [
256
+ {
257
+ "name": "values",
258
+ "parameterTypes": []
259
+ }
260
+ ]
261
+ },
262
+ {
263
+ "name": "org.eclipse.jgit.api.MergeCommand$FastForwardMode",
264
+ "methods": [
265
+ {
266
+ "name": "values",
267
+ "parameterTypes": []
268
+ }
269
+ ]
270
+ },
271
+ {
272
+ "name": "org.eclipse.jgit.api.MergeCommand$FastForwardMode$Merge",
273
+ "methods": [
274
+ {
275
+ "name": "values",
276
+ "parameterTypes": []
277
+ }
278
+ ]
279
+ },
280
+ {
281
+ "name": "org.eclipse.jgit.api.MergeResult$MergeStatus",
282
+ "methods": [
283
+ {
284
+ "name": "values",
285
+ "parameterTypes": []
286
+ }
287
+ ]
288
+ },
289
+ {
290
+ "name": "org.eclipse.jgit.api.RebaseCommand$Operation",
291
+ "methods": [
292
+ {
293
+ "name": "values",
294
+ "parameterTypes": []
295
+ }
296
+ ]
297
+ },
298
+ {
299
+ "name": "org.eclipse.jgit.api.RebaseResult$Status",
300
+ "methods": [
301
+ {
302
+ "name": "values",
303
+ "parameterTypes": []
304
+ }
305
+ ]
306
+ },
307
+ {
308
+ "name": "org.eclipse.jgit.api.RemoteSetUrlCommand$UriType",
309
+ "methods": [
310
+ {
311
+ "name": "values",
312
+ "parameterTypes": []
313
+ }
314
+ ]
315
+ },
316
+ {
317
+ "name": "org.eclipse.jgit.api.ResetCommand$ResetType",
318
+ "methods": [
319
+ {
320
+ "name": "values",
321
+ "parameterTypes": []
322
+ }
323
+ ]
324
+ },
325
+ {
326
+ "name": "org.eclipse.jgit.api.SubmoduleDeinitCommand$SubmoduleDeinitStatus",
327
+ "methods": [
328
+ {
329
+ "name": "values",
330
+ "parameterTypes": []
331
+ }
332
+ ]
333
+ },
334
+ {
335
+ "name": "org.eclipse.jgit.api.VerifySignatureCommand$VerifyMode",
336
+ "methods": [
337
+ {
338
+ "name": "values",
339
+ "parameterTypes": []
340
+ }
341
+ ]
342
+ },
343
+ {
344
+ "name": "org.eclipse.jgit.attributes.Attribute$State",
345
+ "methods": [
346
+ {
347
+ "name": "values",
348
+ "parameterTypes": []
349
+ }
350
+ ]
351
+ },
352
+ {
353
+ "name": "org.eclipse.jgit.diff.DiffAlgorithm$SupportedAlgorithm",
354
+ "methods": [
355
+ {
356
+ "name": "values",
357
+ "parameterTypes": []
358
+ }
359
+ ]
360
+ },
361
+ {
362
+ "name": "org.eclipse.jgit.diff.DiffConfig$RenameDetectionType",
363
+ "methods": [
364
+ {
365
+ "name": "values",
366
+ "parameterTypes": []
367
+ }
368
+ ]
369
+ },
370
+ {
371
+ "name": "org.eclipse.jgit.diff.DiffDriver",
372
+ "methods": [
373
+ {
374
+ "name": "values",
375
+ "parameterTypes": []
376
+ }
377
+ ]
378
+ },
379
+ {
380
+ "name": "org.eclipse.jgit.diff.DiffEntry$ChangeType",
381
+ "methods": [
382
+ {
383
+ "name": "values",
384
+ "parameterTypes": []
385
+ }
386
+ ]
387
+ },
388
+ {
389
+ "name": "org.eclipse.jgit.diff.DiffEntry$Side",
390
+ "methods": [
391
+ {
392
+ "name": "values",
393
+ "parameterTypes": []
394
+ }
395
+ ]
396
+ },
397
+ {
398
+ "name": "org.eclipse.jgit.diff.Edit$Type",
399
+ "methods": [
400
+ {
401
+ "name": "values",
402
+ "parameterTypes": []
403
+ }
404
+ ]
405
+ },
406
+ {
407
+ "name": "org.eclipse.jgit.dircache.DirCache$DirCacheVersion",
408
+ "methods": [
409
+ {
410
+ "name": "values",
411
+ "parameterTypes": []
412
+ }
413
+ ]
414
+ },
415
+ {
416
+ "name": "org.eclipse.jgit.errors.CorruptPackIndexException$ErrorType",
417
+ "methods": [
418
+ {
419
+ "name": "values",
420
+ "parameterTypes": []
421
+ }
422
+ ]
423
+ },
424
+ {
425
+ "name": "org.eclipse.jgit.errors.NoMergeBaseException$MergeBaseFailureReason",
426
+ "methods": [
427
+ {
428
+ "name": "values",
429
+ "parameterTypes": []
430
+ }
431
+ ]
432
+ },
433
+ {
434
+ "name": "org.eclipse.jgit.ignore.IgnoreNode$MatchResult",
435
+ "methods": [
436
+ {
437
+ "name": "values",
438
+ "parameterTypes": []
439
+ }
440
+ ]
441
+ },
442
+ {
443
+ "name": "org.eclipse.jgit.ignore.internal.Strings$PatternState",
444
+ "methods": [
445
+ {
446
+ "name": "values",
447
+ "parameterTypes": []
448
+ }
449
+ ]
450
+ },
451
+ {
452
+ "name": "org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool",
453
+ "methods": [
454
+ {
455
+ "name": "values",
456
+ "parameterTypes": []
457
+ }
458
+ ]
459
+ },
460
+ {
461
+ "name": "org.eclipse.jgit.internal.diffmergetool.CommandLineMergeTool",
462
+ "methods": [
463
+ {
464
+ "name": "values",
465
+ "parameterTypes": []
466
+ }
467
+ ]
468
+ },
469
+ {
470
+ "name": "org.eclipse.jgit.internal.diffmergetool.FileElement$Type",
471
+ "methods": [
472
+ {
473
+ "name": "values",
474
+ "parameterTypes": []
475
+ }
476
+ ]
477
+ },
478
+ {
479
+ "name": "org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase$PackSource",
480
+ "methods": [
481
+ {
482
+ "name": "values",
483
+ "parameterTypes": []
484
+ }
485
+ ]
486
+ },
487
+ {
488
+ "name": "org.eclipse.jgit.internal.storage.file.FileObjectDatabase$InsertLooseObjectResult",
489
+ "methods": [
490
+ {
491
+ "name": "values",
492
+ "parameterTypes": []
493
+ }
494
+ ]
495
+ },
496
+ {
497
+ "name": "org.eclipse.jgit.internal.storage.pack.PackExt",
498
+ "methods": [
499
+ {
500
+ "name": "values",
501
+ "parameterTypes": []
502
+ }
503
+ ]
504
+ },
505
+ {
506
+ "name": "org.eclipse.jgit.internal.storage.pack.PackWriter$PackingPhase",
507
+ "methods": [
508
+ {
509
+ "name": "values",
510
+ "parameterTypes": []
511
+ }
512
+ ]
513
+ },
514
+ {
515
+ "name": "org.eclipse.jgit.internal.util.ShutdownHook",
516
+ "methods": [
517
+ {
518
+ "name": "values",
519
+ "parameterTypes": []
520
+ }
521
+ ]
522
+ },
523
+ {
524
+ "name": "org.eclipse.jgit.lib.BranchConfig$BranchRebaseMode",
525
+ "methods": [
526
+ {
527
+ "name": "values",
528
+ "parameterTypes": []
529
+ }
530
+ ]
531
+ },
532
+ {
533
+ "name": "org.eclipse.jgit.lib.CommitConfig$CleanupMode",
534
+ "methods": [
535
+ {
536
+ "name": "values",
537
+ "parameterTypes": []
538
+ }
539
+ ]
540
+ },
541
+ {
542
+ "name": "org.eclipse.jgit.lib.CoreConfig$AutoCRLF",
543
+ "methods": [
544
+ {
545
+ "name": "values",
546
+ "parameterTypes": []
547
+ }
548
+ ]
549
+ },
550
+ {
551
+ "name": "org.eclipse.jgit.lib.CoreConfig$CheckStat",
552
+ "methods": [
553
+ {
554
+ "name": "values",
555
+ "parameterTypes": []
556
+ }
557
+ ]
558
+ },
559
+ {
560
+ "name": "org.eclipse.jgit.lib.CoreConfig$EOL",
561
+ "methods": [
562
+ {
563
+ "name": "values",
564
+ "parameterTypes": []
565
+ }
566
+ ]
567
+ },
568
+ {
569
+ "name": "org.eclipse.jgit.lib.CoreConfig$EolStreamType",
570
+ "methods": [
571
+ {
572
+ "name": "values",
573
+ "parameterTypes": []
574
+ }
575
+ ]
576
+ },
577
+ {
578
+ "name": "org.eclipse.jgit.lib.CoreConfig$HideDotFiles",
579
+ "methods": [
580
+ {
581
+ "name": "values",
582
+ "parameterTypes": []
583
+ }
584
+ ]
585
+ },
586
+ {
587
+ "name": "org.eclipse.jgit.lib.CoreConfig$LogRefUpdates",
588
+ "methods": [
589
+ {
590
+ "name": "values",
591
+ "parameterTypes": []
592
+ }
593
+ ]
594
+ },
595
+ {
596
+ "name": "org.eclipse.jgit.lib.CoreConfig$SymLinks",
597
+ "methods": [
598
+ {
599
+ "name": "values",
600
+ "parameterTypes": []
601
+ }
602
+ ]
603
+ },
604
+ {
605
+ "name": "org.eclipse.jgit.lib.CoreConfig$TrustLooseRefStat",
606
+ "methods": [
607
+ {
608
+ "name": "values",
609
+ "parameterTypes": []
610
+ }
611
+ ]
612
+ },
613
+ {
614
+ "name": "org.eclipse.jgit.lib.CoreConfig$TrustPackedRefsStat",
615
+ "methods": [
616
+ {
617
+ "name": "values",
618
+ "parameterTypes": []
619
+ }
620
+ ]
621
+ },
622
+ {
623
+ "name": "org.eclipse.jgit.lib.CoreConfig$TrustStat",
624
+ "methods": [
625
+ {
626
+ "name": "values",
627
+ "parameterTypes": []
628
+ }
629
+ ]
630
+ },
631
+ {
632
+ "name": "org.eclipse.jgit.lib.GpgConfig$GpgFormat",
633
+ "methods": [
634
+ {
635
+ "name": "values",
636
+ "parameterTypes": []
637
+ }
638
+ ]
639
+ },
640
+ {
641
+ "name": "org.eclipse.jgit.lib.IndexDiff$StageState",
642
+ "methods": [
643
+ {
644
+ "name": "values",
645
+ "parameterTypes": []
646
+ }
647
+ ]
648
+ },
649
+ {
650
+ "name": "org.eclipse.jgit.lib.ObjectChecker$ErrorType",
651
+ "methods": [
652
+ {
653
+ "name": "values",
654
+ "parameterTypes": []
655
+ }
656
+ ]
657
+ },
658
+ {
659
+ "name": "org.eclipse.jgit.lib.RebaseTodoLine$Action",
660
+ "methods": [
661
+ {
662
+ "name": "values",
663
+ "parameterTypes": []
664
+ }
665
+ ]
666
+ },
667
+ {
668
+ "name": "org.eclipse.jgit.lib.Ref$Storage",
669
+ "methods": [
670
+ {
671
+ "name": "values",
672
+ "parameterTypes": []
673
+ }
674
+ ]
675
+ },
676
+ {
677
+ "name": "org.eclipse.jgit.lib.RefUpdate$Result",
678
+ "methods": [
679
+ {
680
+ "name": "values",
681
+ "parameterTypes": []
682
+ }
683
+ ]
684
+ },
685
+ {
686
+ "name": "org.eclipse.jgit.lib.RepositoryState",
687
+ "methods": [
688
+ {
689
+ "name": "values",
690
+ "parameterTypes": []
691
+ }
692
+ ]
693
+ },
694
+ {
695
+ "name": "org.eclipse.jgit.lib.SignatureVerifier$TrustLevel",
696
+ "methods": [
697
+ {
698
+ "name": "values",
699
+ "parameterTypes": []
700
+ }
701
+ ]
702
+ },
703
+ {
704
+ "name": "org.eclipse.jgit.lib.SubmoduleConfig$FetchRecurseSubmodulesMode",
705
+ "methods": [
706
+ {
707
+ "name": "values",
708
+ "parameterTypes": []
709
+ }
710
+ ]
711
+ },
712
+ {
713
+ "name": "org.eclipse.jgit.lib.internal.BooleanTriState",
714
+ "methods": [
715
+ {
716
+ "name": "values",
717
+ "parameterTypes": []
718
+ }
719
+ ]
720
+ },
721
+ {
722
+ "name": "org.eclipse.jgit.merge.ContentMergeStrategy",
723
+ "methods": [
724
+ {
725
+ "name": "values",
726
+ "parameterTypes": []
727
+ }
728
+ ]
729
+ },
730
+ {
731
+ "name": "org.eclipse.jgit.merge.MergeChunk$ConflictState",
732
+ "methods": [
733
+ {
734
+ "name": "values",
735
+ "parameterTypes": []
736
+ }
737
+ ]
738
+ },
739
+ {
740
+ "name": "org.eclipse.jgit.merge.ResolveMerger$MergeFailureReason",
741
+ "methods": [
742
+ {
743
+ "name": "values",
744
+ "parameterTypes": []
745
+ }
746
+ ]
747
+ },
748
+ {
749
+ "name": "org.eclipse.jgit.patch.BinaryHunk$Type",
750
+ "methods": [
751
+ {
752
+ "name": "values",
753
+ "parameterTypes": []
754
+ }
755
+ ]
756
+ },
757
+ {
758
+ "name": "org.eclipse.jgit.patch.FileHeader$PatchType",
759
+ "methods": [
760
+ {
761
+ "name": "values",
762
+ "parameterTypes": []
763
+ }
764
+ ]
765
+ },
766
+ {
767
+ "name": "org.eclipse.jgit.patch.FormatError$Severity",
768
+ "methods": [
769
+ {
770
+ "name": "values",
771
+ "parameterTypes": []
772
+ }
773
+ ]
774
+ },
775
+ {
776
+ "name": "org.eclipse.jgit.revwalk.RevSort",
777
+ "methods": [
778
+ {
779
+ "name": "values",
780
+ "parameterTypes": []
781
+ }
782
+ ]
783
+ },
784
+ {
785
+ "name": "org.eclipse.jgit.revwalk.RevWalk$GetMergedIntoStrategy",
786
+ "methods": [
787
+ {
788
+ "name": "values",
789
+ "parameterTypes": []
790
+ }
791
+ ]
792
+ },
793
+ {
794
+ "name": "org.eclipse.jgit.submodule.SubmoduleStatusType",
795
+ "methods": [
796
+ {
797
+ "name": "values",
798
+ "parameterTypes": []
799
+ }
800
+ ]
801
+ },
802
+ {
803
+ "name": "org.eclipse.jgit.submodule.SubmoduleWalk$IgnoreSubmoduleMode",
804
+ "methods": [
805
+ {
806
+ "name": "values",
807
+ "parameterTypes": []
808
+ }
809
+ ]
810
+ },
811
+ {
812
+ "name": "org.eclipse.jgit.transport.GitProtocolConstants$MultiAck",
813
+ "methods": [
814
+ {
815
+ "name": "values",
816
+ "parameterTypes": []
817
+ }
818
+ ]
819
+ },
820
+ {
821
+ "name": "org.eclipse.jgit.transport.HttpAuthMethod$Type",
822
+ "methods": [
823
+ {
824
+ "name": "values",
825
+ "parameterTypes": []
826
+ }
827
+ ]
828
+ },
829
+ {
830
+ "name": "org.eclipse.jgit.transport.HttpConfig$HttpRedirectMode",
831
+ "methods": [
832
+ {
833
+ "name": "values",
834
+ "parameterTypes": []
835
+ }
836
+ ]
837
+ },
838
+ {
839
+ "name": "org.eclipse.jgit.transport.NetRC$State",
840
+ "methods": [
841
+ {
842
+ "name": "values",
843
+ "parameterTypes": []
844
+ }
845
+ ]
846
+ },
847
+ {
848
+ "name": "org.eclipse.jgit.transport.PackParser$Source",
849
+ "methods": [
850
+ {
851
+ "name": "values",
852
+ "parameterTypes": []
853
+ }
854
+ ]
855
+ },
856
+ {
857
+ "name": "org.eclipse.jgit.transport.PacketLineIn$AckNackResult",
858
+ "methods": [
859
+ {
860
+ "name": "values",
861
+ "parameterTypes": []
862
+ }
863
+ ]
864
+ },
865
+ {
866
+ "name": "org.eclipse.jgit.transport.PushCertificate$NonceStatus",
867
+ "methods": [
868
+ {
869
+ "name": "values",
870
+ "parameterTypes": []
871
+ }
872
+ ]
873
+ },
874
+ {
875
+ "name": "org.eclipse.jgit.transport.PushConfig$PushDefault",
876
+ "methods": [
877
+ {
878
+ "name": "values",
879
+ "parameterTypes": []
880
+ }
881
+ ]
882
+ },
883
+ {
884
+ "name": "org.eclipse.jgit.transport.PushConfig$PushRecurseSubmodulesMode",
885
+ "methods": [
886
+ {
887
+ "name": "values",
888
+ "parameterTypes": []
889
+ }
890
+ ]
891
+ },
892
+ {
893
+ "name": "org.eclipse.jgit.transport.ReceiveCommand$Result",
894
+ "methods": [
895
+ {
896
+ "name": "values",
897
+ "parameterTypes": []
898
+ }
899
+ ]
900
+ },
901
+ {
902
+ "name": "org.eclipse.jgit.transport.ReceiveCommand$Type",
903
+ "methods": [
904
+ {
905
+ "name": "values",
906
+ "parameterTypes": []
907
+ }
908
+ ]
909
+ },
910
+ {
911
+ "name": "org.eclipse.jgit.transport.RefSpec$WildcardMode",
912
+ "methods": [
913
+ {
914
+ "name": "values",
915
+ "parameterTypes": []
916
+ }
917
+ ]
918
+ },
919
+ {
920
+ "name": "org.eclipse.jgit.transport.RemoteRefUpdate$Status",
921
+ "methods": [
922
+ {
923
+ "name": "values",
924
+ "parameterTypes": []
925
+ }
926
+ ]
927
+ },
928
+ {
929
+ "name": "org.eclipse.jgit.transport.TagOpt",
930
+ "methods": [
931
+ {
932
+ "name": "values",
933
+ "parameterTypes": []
934
+ }
935
+ ]
936
+ },
937
+ {
938
+ "name": "org.eclipse.jgit.transport.TransferConfig$FsckMode",
939
+ "methods": [
940
+ {
941
+ "name": "values",
942
+ "parameterTypes": []
943
+ }
944
+ ]
945
+ },
946
+ {
947
+ "name": "org.eclipse.jgit.transport.TransferConfig$ProtocolVersion",
948
+ "methods": [
949
+ {
950
+ "name": "values",
951
+ "parameterTypes": []
952
+ }
953
+ ]
954
+ },
955
+ {
956
+ "name": "org.eclipse.jgit.transport.Transport$Operation",
957
+ "methods": [
958
+ {
959
+ "name": "values",
960
+ "parameterTypes": []
961
+ }
962
+ ]
963
+ },
964
+ {
965
+ "name": "org.eclipse.jgit.transport.TransportHttp$AcceptEncoding",
966
+ "methods": [
967
+ {
968
+ "name": "values",
969
+ "parameterTypes": []
970
+ }
971
+ ]
972
+ },
973
+ {
974
+ "name": "org.eclipse.jgit.transport.TransportProtocol$URIishField",
975
+ "methods": [
976
+ {
977
+ "name": "values",
978
+ "parameterTypes": []
979
+ }
980
+ ]
981
+ },
982
+ {
983
+ "name": "org.eclipse.jgit.transport.UploadPack$Option",
984
+ "methods": [
985
+ {
986
+ "name": "values",
987
+ "parameterTypes": []
988
+ }
989
+ ]
990
+ },
991
+ {
992
+ "name": "org.eclipse.jgit.transport.UploadPack$RequestPolicy",
993
+ "methods": [
994
+ {
995
+ "name": "values",
996
+ "parameterTypes": []
997
+ }
998
+ ]
999
+ },
1000
+ {
1001
+ "name": "org.eclipse.jgit.treewalk.TreeWalk$OperationType",
1002
+ "methods": [
1003
+ {
1004
+ "name": "values",
1005
+ "parameterTypes": []
1006
+ }
1007
+ ]
1008
+ },
1009
+ {
1010
+ "name": "org.eclipse.jgit.treewalk.WorkingTreeIterator$MetadataDiff",
1011
+ "methods": [
1012
+ {
1013
+ "name": "values",
1014
+ "parameterTypes": []
1015
+ }
1016
+ ]
1017
+ },
1018
+ {
1019
+ "name": "org.eclipse.jgit.util.FS_POSIX$AtomicFileCreation",
1020
+ "methods": [
1021
+ {
1022
+ "name": "values",
1023
+ "parameterTypes": []
1024
+ }
1025
+ ]
1026
+ },
1027
+ {
1028
+ "name": "org.eclipse.jgit.util.GitDateFormatter$Format",
1029
+ "methods": [
1030
+ {
1031
+ "name": "values",
1032
+ "parameterTypes": []
1033
+ }
1034
+ ]
1035
+ },
1036
+ {
1037
+ "name": "org.eclipse.jgit.util.GitDateParser$ParseableSimpleDateFormat",
1038
+ "methods": [
1039
+ {
1040
+ "name": "values",
1041
+ "parameterTypes": []
1042
+ }
1043
+ ]
1044
+ },
1045
+ {
1046
+ "name": "org.eclipse.jgit.util.GitTimeParser$ParseableSimpleDateFormat",
1047
+ "methods": [
1048
+ {
1049
+ "name": "values",
1050
+ "parameterTypes": []
1051
+ }
1052
+ ]
1053
+ },
1054
+ {
1055
+ "name": "org.eclipse.jgit.util.ProcessResult$Status",
1056
+ "methods": [
1057
+ {
1058
+ "name": "values",
1059
+ "parameterTypes": []
1060
+ }
1061
+ ]
1062
+ },
1063
+ {
1064
+ "name": "org.eclipse.jgit.util.io.AutoLFInputStream$StreamFlag",
1065
+ "methods": [
1066
+ {
1067
+ "name": "values",
1068
+ "parameterTypes": []
1069
+ }
1070
+ ]
1071
+ },
1072
+ {
1073
+ "name": "org.eclipse.jgit.util.sha1.SHA1$Sha1Implementation",
1074
+ "methods": [
1075
+ {
1076
+ "name": "values",
1077
+ "parameterTypes": []
1078
+ }
1079
+ ]
69
1080
}
70
-]
1081
+]
MODIFY
src/main/resources/META-INF/native-image/de.workaround/git-shark/resource-config.json
+2 -1
@@ -2,7 +2,8 @@
2
2
"resources": {
3
3
"includes": [
4
4
{"pattern": "org/eclipse/jgit/internal/JGitText\\.properties"},
5
- {"pattern": ".*JGitText.*\\.properties"}
5
+ {"pattern": ".*JGitText.*\\.properties"},
6
+ {"pattern": "org/eclipse/jgit/.*\\.properties"}
6
7
]
7
8
},
8
9
"bundles": [
MODIFY
src/main/resources/application.properties
+3 -0
@@ -54,6 +54,9 @@
54
54
55
55
# Native image: JGit holds static Random/lazy state that must not be build-time initialized
56
56
quarkus.native.additional-build-args=--initialize-at-run-time=org.eclipse.jgit,--initialize-at-run-time=org.apache.sshd.common.random,--initialize-at-run-time=org.apache.sshd.common.util.security.bouncycastle.BouncyCastleEncryptedPrivateKeyInfoDecryptor
57
+# Register BouncyCastle at build time: SSHD's runtime registrar (Security.addProvider via reflection)
58
+# does not work in a native image, leaving host-key generation without the BC provider.
59
+quarkus.security.security-providers=BC
57
60
58
61
# Git repository storage
59
62
gitshark.storage.root=${GITSHARK_STORAGE_ROOT:data/repositories}
MODIFY
src/test/java/de/workaround/SmokeIT.java
+29 -1
@@ -8,7 +8,8 @@
8
8
9
9
/**
10
10
* Boots the packaged application (JVM jar or native binary) and verifies the service is alive:
11
- * HTTP health endpoint up and the embedded SSH server accepting connections.
11
+ * HTTP health endpoint up, public pages (Qute templates) rendering, the OpenAPI document
12
+ * served, and the embedded SSH server accepting connections.
12
13
*/
13
14
@QuarkusIntegrationTest
14
15
class SmokeIT
@@ -24,6 +25,33 @@
24
25
}
25
26
26
27
@Test
28
+ void landingPageRenders()
29
+ {
30
+ given()
31
+ .when().get("/")
32
+ .then()
33
+ .statusCode(200);
34
+ }
35
+
36
+ @Test
37
+ void explorePageRenders()
38
+ {
39
+ given()
40
+ .when().get("/explore")
41
+ .then()
42
+ .statusCode(200);
43
+ }
44
+
45
+ @Test
46
+ void openApiDocumentIsServed()
47
+ {
48
+ given()
49
+ .when().get("/q/openapi")
50
+ .then()
51
+ .statusCode(200);
52
+ }
53
+
54
+ @Test
27
55
void sshPortPresentsSshBanner() throws Exception
28
56
{
29
57
int port = Integer.getInteger("gitshark.ssh.port", 2222);
ADD
src/test/java/de/workaround/http/GitHttpIT.java
+107 -0
@@ -0,0 +1,107 @@
1
+package de.workaround.http;
2
+
3
+import java.nio.file.Files;
4
+import java.nio.file.Path;
5
+
6
+import org.eclipse.jgit.api.Git;
7
+import org.eclipse.jgit.api.errors.TransportException;
8
+import org.eclipse.jgit.transport.RefSpec;
9
+import org.junit.jupiter.api.Test;
10
+
11
+import io.quarkus.test.junit.QuarkusIntegrationTest;
12
+
13
+import static io.restassured.RestAssured.given;
14
+import static org.hamcrest.CoreMatchers.containsString;
15
+import static org.junit.jupiter.api.Assertions.assertFalse;
16
+import static org.junit.jupiter.api.Assertions.assertThrows;
17
+import static org.junit.jupiter.api.Assertions.assertTrue;
18
+
19
+/**
20
+ * Exercises the git smart-HTTP transport and repository browsing against the packaged
21
+ * application (JVM jar or native binary). The seeded alice/demo repository (enabled via
22
+ * GITSHARK_DEV_SEED_DATA in the failsafe config) provides the data; JGit acts as a real
23
+ * client, so this covers the servlet, UploadPack, JGit object I/O and the DB lookup path
24
+ * in the native image.
25
+ */
26
+@QuarkusIntegrationTest
27
+class GitHttpIT
28
+{
29
+ private static String cloneUrl()
30
+ {
31
+ int port = Integer.getInteger("quarkus.http.test-port", 8081);
32
+ return "http://localhost:" + port + "/git/alice/demo.git";
33
+ }
34
+
35
+ @Test
36
+ void anonymousCloneOfSeededPublicRepositorySucceeds() throws Exception
37
+ {
38
+ Path target = Files.createTempDirectory("it-clone");
39
+ try (Git clone = Git.cloneRepository().setURI(cloneUrl()).setDirectory(target.toFile()).call())
40
+ {
41
+ assertTrue(Files.exists(target.resolve("README.md")), "clone must contain the seeded README");
42
+ assertTrue(Files.readString(target.resolve("README.md")).contains("demo"));
43
+ }
44
+ }
45
+
46
+ @Test
47
+ void anonymousPushIsRejectedAndDoesNotAlterTheRepository() throws Exception
48
+ {
49
+ Path work = Files.createTempDirectory("it-push");
50
+ try (Git git = Git.cloneRepository().setURI(cloneUrl()).setDirectory(work.toFile()).call())
51
+ {
52
+ Files.writeString(work.resolve("evil.txt"), "anonymous write\n");
53
+ git.add().addFilepattern(".").call();
54
+ git.commit().setMessage("anonymous push attempt").setSign(false)
55
+ .setAuthor("t", "t@example.com").setCommitter("t", "t@example.com").call();
56
+ assertThrows(TransportException.class,
57
+ () -> git.push().setRefSpecs(new RefSpec("HEAD:refs/heads/main")).call());
58
+ }
59
+
60
+ Path recheck = Files.createTempDirectory("it-recheck");
61
+ try (Git clone = Git.cloneRepository().setURI(cloneUrl()).setDirectory(recheck.toFile()).call())
62
+ {
63
+ assertFalse(Files.exists(recheck.resolve("evil.txt")), "rejected push must not change refs");
64
+ }
65
+ }
66
+
67
+ @Test
68
+ void repositoryOverviewPageRenders()
69
+ {
70
+ given()
71
+ .when().get("/repos/alice/demo")
72
+ .then()
73
+ .statusCode(200)
74
+ .body(containsString("demo"));
75
+ }
76
+
77
+ @Test
78
+ void rawFileIsServedFromTheSeededRepository()
79
+ {
80
+ given()
81
+ .when().get("/repos/alice/demo/raw/main/README.md")
82
+ .then()
83
+ .statusCode(200)
84
+ .body(containsString("demo"));
85
+ }
86
+
87
+ @Test
88
+ void blobPageRendersSeededFileWithHighlightMarkup()
89
+ {
90
+ given()
91
+ .when().get("/repos/alice/demo/tree/main/README.md")
92
+ .then()
93
+ .statusCode(200)
94
+ .body(containsString("demo"))
95
+ .body(containsString("<code"));
96
+ }
97
+
98
+ @Test
99
+ void vendoredHighlightAssetIsServed()
100
+ {
101
+ given()
102
+ .when().get("/highlight.min.js")
103
+ .then()
104
+ .statusCode(200);
105
+ }
106
+
107
+}
ADD
src/test/java/de/workaround/ssh/SshHandshakeIT.java
+82 -0
@@ -0,0 +1,82 @@
1
+package de.workaround.ssh;
2
+
3
+import java.nio.file.Files;
4
+import java.nio.file.Path;
5
+import java.security.KeyPair;
6
+import java.security.KeyPairGenerator;
7
+import java.util.List;
8
+
9
+import org.eclipse.jgit.api.Git;
10
+import org.eclipse.jgit.api.errors.TransportException;
11
+import org.eclipse.jgit.transport.SshTransport;
12
+import org.eclipse.jgit.transport.sshd.JGitKeyCache;
13
+import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
14
+import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
15
+import org.eclipse.jgit.transport.sshd.SshdSessionFactoryBuilder;
16
+import org.junit.jupiter.api.Test;
17
+
18
+import io.quarkus.test.junit.QuarkusIntegrationTest;
19
+
20
+import static org.junit.jupiter.api.Assertions.assertNotNull;
21
+import static org.junit.jupiter.api.Assertions.assertThrows;
22
+import static org.junit.jupiter.api.Assertions.assertTrue;
23
+
24
+/**
25
+ * Drives a complete SSH handshake (key exchange, host key signature, cipher negotiation)
26
+ * against the packaged application and expects a clean publickey-auth rejection for an
27
+ * unregistered key. KEX and signatures are the classic native-image failure zone for
28
+ * Apache SSHD + BouncyCastle, so a crypto regression fails this test with a negotiation
29
+ * error instead of the expected auth failure.
30
+ */
31
+@QuarkusIntegrationTest
32
+class SshHandshakeIT
33
+{
34
+ @Test
35
+ void fullHandshakeCompletesAndUnregisteredKeyIsRejectedAtAuth() throws Exception
36
+ {
37
+ int port = Integer.getInteger("gitshark.ssh.port", 2222);
38
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
39
+ generator.initialize(2048);
40
+ KeyPair unregistered = generator.generateKeyPair();
41
+
42
+ Path home = Files.createTempDirectory("it-ssh-home");
43
+ Files.createDirectories(home.resolve(".ssh"));
44
+ try (SshdSessionFactory factory = new SshdSessionFactoryBuilder()
45
+ .setPreferredAuthentications("publickey")
46
+ .setHomeDirectory(home.toFile())
47
+ .setSshDirectory(home.resolve(".ssh").toFile())
48
+ .setDefaultKeysProvider(dir -> List.of(unregistered))
49
+ .setServerKeyDatabase((ignoredHome, ignoredSsh) -> new ServerKeyDatabase()
50
+ {
51
+ @Override
52
+ public List<java.security.PublicKey> lookup(String connectAddress,
53
+ java.net.InetSocketAddress remoteAddress, Configuration config)
54
+ {
55
+ return List.of();
56
+ }
57
+
58
+ @Override
59
+ public boolean accept(String connectAddress, java.net.InetSocketAddress remoteAddress,
60
+ java.security.PublicKey serverKey, Configuration config,
61
+ org.eclipse.jgit.transport.CredentialsProvider provider)
62
+ {
63
+ return true;
64
+ }
65
+ })
66
+ .build(new JGitKeyCache()))
67
+ {
68
+ Path target = Files.createTempDirectory("it-ssh-clone");
69
+ TransportException e = assertThrows(TransportException.class, () -> Git.cloneRepository()
70
+ .setURI("ssh://git@localhost:" + port + "/alice/demo.git")
71
+ .setDirectory(target.toFile())
72
+ .setTransportConfigCallback(t -> ((SshTransport) t).setSshSessionFactory(factory))
73
+ .call());
74
+
75
+ assertNotNull(e.getMessage());
76
+ String message = e.getMessage().toLowerCase();
77
+ assertTrue(message.contains("auth") || message.contains("log in") || message.contains("refused"),
78
+ "handshake must reach the auth stage (crypto negotiation intact), but was: " + e.getMessage());
79
+ }
80
+ }
81
+
82
+}