+170
-133
Cargo.lock
+170
-133
Cargo.lock
···
701
701
"libc",
702
702
"option-ext",
703
703
"redox_users",
704
-
"windows-sys 0.61.0",
704
+
"windows-sys 0.60.2",
705
705
]
706
706
707
707
[[package]]
···
821
821
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
822
822
dependencies = [
823
823
"libc",
824
-
"windows-sys 0.61.0",
824
+
"windows-sys 0.60.2",
825
825
]
826
826
827
827
[[package]]
···
881
881
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
882
882
dependencies = [
883
883
"crc32fast",
884
-
"libz-rs-sys",
885
884
"miniz_oxide",
886
885
]
887
886
···
895
896
version = "0.1.5"
896
897
source = "registry+https://github.com/rust-lang/crates.io-index"
897
898
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
899
+
900
+
[[package]]
901
+
name = "foldhash"
902
+
version = "0.2.0"
903
+
source = "registry+https://github.com/rust-lang/crates.io-index"
904
+
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
898
905
899
906
[[package]]
900
907
name = "foreign-types"
···
1027
1022
1028
1023
[[package]]
1029
1024
name = "gix"
1030
-
version = "0.73.0"
1025
+
version = "0.74.0"
1031
1026
source = "registry+https://github.com/rust-lang/crates.io-index"
1032
-
checksum = "514c29cc879bdc0286b0cbc205585a49b252809eb86c69df4ce4f855ee75f635"
1027
+
checksum = "d3fd3d8b30e15352c955ae99f3497a7ee5e346a6fa2662fc9301001691c4fb7e"
1033
1028
dependencies = [
1034
1029
"gix-actor",
1035
1030
"gix-archive",
1036
1031
"gix-attributes",
1032
+
"gix-blame",
1037
1033
"gix-command",
1038
1034
"gix-commitgraph",
1039
1035
"gix-config",
···
1078
1072
"gix-worktree",
1079
1073
"gix-worktree-state",
1080
1074
"gix-worktree-stream",
1081
-
"once_cell",
1082
1075
"parking_lot",
1083
1076
"regex",
1084
1077
"signal-hook",
···
1087
1082
1088
1083
[[package]]
1089
1084
name = "gix-actor"
1090
-
version = "0.35.4"
1085
+
version = "0.35.5"
1091
1086
source = "registry+https://github.com/rust-lang/crates.io-index"
1092
-
checksum = "2d36dcf9efe32b51b12dfa33cedff8414926124e760a32f9e7a6b5580d280967"
1087
+
checksum = "575bb2dafa4e56721c0f7e8b2da647452e6c10c69b6157b118388a132cccb892"
1093
1088
dependencies = [
1094
1089
"bstr",
1095
1090
"gix-date",
···
1101
1096
1102
1097
[[package]]
1103
1098
name = "gix-archive"
1104
-
version = "0.22.0"
1099
+
version = "0.23.0"
1105
1100
source = "registry+https://github.com/rust-lang/crates.io-index"
1106
-
checksum = "7be088a0e1b30abe15572ffafb3409172a3d88148e13959734f24f52112a19d6"
1101
+
checksum = "015d177dcb7c2d9efc71ab9163cfa9cc83aa3c2409a2cd57d723a3c3cfbcb4c5"
1107
1102
dependencies = [
1108
1103
"bstr",
1109
1104
"gix-date",
···
1115
1110
1116
1111
[[package]]
1117
1112
name = "gix-attributes"
1118
-
version = "0.27.0"
1113
+
version = "0.28.0"
1119
1114
source = "registry+https://github.com/rust-lang/crates.io-index"
1120
-
checksum = "45442188216d08a5959af195f659cb1f244a50d7d2d0c3873633b1cd7135f638"
1115
+
checksum = "42bbbbe6e01d238f7cf1ce7f604d742e91639006f2ac1685b008cfa55903206d"
1121
1116
dependencies = [
1122
1117
"bstr",
1123
1118
"gix-glob",
···
1132
1127
1133
1128
[[package]]
1134
1129
name = "gix-bitmap"
1135
-
version = "0.2.14"
1130
+
version = "0.2.15"
1136
1131
source = "registry+https://github.com/rust-lang/crates.io-index"
1137
-
checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540"
1132
+
checksum = "5e150161b8a75b5860521cb876b506879a3376d3adc857ec7a9d35e7c6a5e531"
1138
1133
dependencies = [
1139
1134
"thiserror",
1140
1135
]
1141
1136
1142
1137
[[package]]
1143
-
name = "gix-chunk"
1144
-
version = "0.4.11"
1138
+
name = "gix-blame"
1139
+
version = "0.4.0"
1145
1140
source = "registry+https://github.com/rust-lang/crates.io-index"
1146
-
checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f"
1141
+
checksum = "260df64cea7bf3ab6db00e8f8cd8f1f85513d69c19fadd714422a39b8e8a8617"
1142
+
dependencies = [
1143
+
"gix-commitgraph",
1144
+
"gix-date",
1145
+
"gix-diff",
1146
+
"gix-hash",
1147
+
"gix-object",
1148
+
"gix-revwalk",
1149
+
"gix-trace",
1150
+
"gix-traverse",
1151
+
"gix-worktree",
1152
+
"smallvec",
1153
+
"thiserror",
1154
+
]
1155
+
1156
+
[[package]]
1157
+
name = "gix-chunk"
1158
+
version = "0.4.12"
1159
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1160
+
checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb"
1147
1161
dependencies = [
1148
1162
"thiserror",
1149
1163
]
1150
1164
1151
1165
[[package]]
1152
1166
name = "gix-command"
1153
-
version = "0.6.2"
1167
+
version = "0.6.3"
1154
1168
source = "registry+https://github.com/rust-lang/crates.io-index"
1155
-
checksum = "6b31b65ca48a352ae86312b27a514a0c661935f96b481ac8b4371f65815eb196"
1169
+
checksum = "095c8367c9dc4872a7706fbc39c7f34271b88b541120a4365ff0e36366f66e62"
1156
1170
dependencies = [
1157
1171
"bstr",
1158
1172
"gix-path",
···
1182
1158
1183
1159
[[package]]
1184
1160
name = "gix-commitgraph"
1185
-
version = "0.29.0"
1161
+
version = "0.30.0"
1186
1162
source = "registry+https://github.com/rust-lang/crates.io-index"
1187
-
checksum = "6bb23121e952f43a5b07e3e80890336cb847297467a410475036242732980d06"
1163
+
checksum = "5b7c0f17a94a27283afce5c1c5d7b1778cacc0a70bb8affc0e38611f06ffcb2b"
1188
1164
dependencies = [
1189
1165
"bstr",
1190
1166
"gix-chunk",
···
1195
1171
1196
1172
[[package]]
1197
1173
name = "gix-config"
1198
-
version = "0.46.0"
1174
+
version = "0.47.0"
1199
1175
source = "registry+https://github.com/rust-lang/crates.io-index"
1200
-
checksum = "5dfb898c5b695fd4acfc3c0ab638525a65545d47706064dcf7b5ead6cdb136c0"
1176
+
checksum = "76acbe8307019b17411a3c50e181e4dd97d78a840dead884a4f2dd35a8d38f6a"
1201
1177
dependencies = [
1202
1178
"bstr",
1203
1179
"gix-config-value",
···
1207
1183
"gix-ref",
1208
1184
"gix-sec",
1209
1185
"memchr",
1210
-
"once_cell",
1211
1186
"smallvec",
1212
1187
"thiserror",
1213
1188
"unicode-bom",
···
1215
1192
1216
1193
[[package]]
1217
1194
name = "gix-config-value"
1218
-
version = "0.15.1"
1195
+
version = "0.15.2"
1219
1196
source = "registry+https://github.com/rust-lang/crates.io-index"
1220
-
checksum = "9f012703eb67e263c6c1fc96649fec47694dd3e5d2a91abfc65e4a6a6dc85309"
1197
+
checksum = "7d837b93598b240624ed9b30f3a7629dc6088db60a025c90a628116a16d26719"
1221
1198
dependencies = [
1222
1199
"bitflags",
1223
1200
"bstr",
···
1228
1205
1229
1206
[[package]]
1230
1207
name = "gix-credentials"
1231
-
version = "0.30.0"
1208
+
version = "0.31.0"
1232
1209
source = "registry+https://github.com/rust-lang/crates.io-index"
1233
-
checksum = "0039dd3ac606dd80b16353a41b61fc237ca5cb8b612f67a9f880adfad4be4e05"
1210
+
checksum = "86a332d35e543005d61a7d517ccc32812476309e71aac02aef52525a501f1cae"
1234
1211
dependencies = [
1235
1212
"bstr",
1236
1213
"gix-command",
···
1246
1223
1247
1224
[[package]]
1248
1225
name = "gix-date"
1249
-
version = "0.10.5"
1226
+
version = "0.10.6"
1250
1227
source = "registry+https://github.com/rust-lang/crates.io-index"
1251
-
checksum = "996b6b90bafb287330af92b274c3e64309dc78359221d8612d11cd10c8b9fe1c"
1228
+
checksum = "36911ec97d0343048c6ecbcd2dcbcc6a9adf8272845417593915d1f8514b44ee"
1252
1229
dependencies = [
1253
1230
"bstr",
1254
1231
"itoa",
···
1259
1236
1260
1237
[[package]]
1261
1238
name = "gix-diff"
1262
-
version = "0.53.0"
1239
+
version = "0.54.0"
1263
1240
source = "registry+https://github.com/rust-lang/crates.io-index"
1264
-
checksum = "de854852010d44a317f30c92d67a983e691c9478c8a3fb4117c1f48626bcdea8"
1241
+
checksum = "b9844abf11a6dc5ab1d9e8670e17d35afbfebbebe216522454883140745bc68b"
1265
1242
dependencies = [
1266
1243
"bstr",
1267
1244
"gix-attributes",
···
1283
1260
1284
1261
[[package]]
1285
1262
name = "gix-dir"
1286
-
version = "0.15.0"
1263
+
version = "0.16.0"
1287
1264
source = "registry+https://github.com/rust-lang/crates.io-index"
1288
-
checksum = "dad34e4f373f94902df1ba1d2a1df3a1b29eacd15e316ac5972d842e31422dd7"
1265
+
checksum = "f99fb4dcba076453d791949bf3af977c5678a1cbd76740ec2cfe37e29431daf3"
1289
1266
dependencies = [
1290
1267
"bstr",
1291
1268
"gix-discover",
···
1303
1280
1304
1281
[[package]]
1305
1282
name = "gix-discover"
1306
-
version = "0.41.0"
1283
+
version = "0.42.0"
1307
1284
source = "registry+https://github.com/rust-lang/crates.io-index"
1308
-
checksum = "ffb180c91ca1a2cf53e828bb63d8d8f8fa7526f49b83b33d7f46cbeb5d79d30a"
1285
+
checksum = "9d24547153810634636471af88338240e6ab0831308cd41eb6ebfffea77811c6"
1309
1286
dependencies = [
1310
1287
"bstr",
1311
1288
"dunce",
···
1319
1296
1320
1297
[[package]]
1321
1298
name = "gix-features"
1322
-
version = "0.43.1"
1299
+
version = "0.44.0"
1323
1300
source = "registry+https://github.com/rust-lang/crates.io-index"
1324
-
checksum = "cd1543cd9b8abcbcebaa1a666a5c168ee2cda4dea50d3961ee0e6d1c42f81e5b"
1301
+
checksum = "4ff90017694214e1445b1baab8ca299e548cdf42c9d4588c02017c352d32652b"
1325
1302
dependencies = [
1326
1303
"bytes",
1327
1304
"bytesize",
1328
1305
"crc32fast",
1329
1306
"crossbeam-channel",
1330
-
"flate2",
1331
1307
"gix-path",
1332
1308
"gix-trace",
1333
1309
"gix-utils",
1334
1310
"libc",
1311
+
"libz-rs-sys",
1335
1312
"once_cell",
1336
1313
"parking_lot",
1337
1314
"prodash",
···
1341
1318
1342
1319
[[package]]
1343
1320
name = "gix-filter"
1344
-
version = "0.20.0"
1321
+
version = "0.21.0"
1345
1322
source = "registry+https://github.com/rust-lang/crates.io-index"
1346
-
checksum = "aa6571a3927e7ab10f64279a088e0dae08e8da05547771796d7389bbe28ad9ff"
1323
+
checksum = "1d1253452c9808da01eaaf9b1c4929b9982efec29ef0a668b3326b8046d9b8fb"
1347
1324
dependencies = [
1348
1325
"bstr",
1349
1326
"encoding_rs",
···
1362
1339
1363
1340
[[package]]
1364
1341
name = "gix-fs"
1365
-
version = "0.16.1"
1342
+
version = "0.17.0"
1366
1343
source = "registry+https://github.com/rust-lang/crates.io-index"
1367
-
checksum = "9a4d90307d064fa7230e0f87b03231be28f8ba63b913fc15346f489519d0c304"
1344
+
checksum = "3f1ecd896258cdc5ccd94d18386d17906b8de265ad2ecf68e3bea6b007f6a28f"
1368
1345
dependencies = [
1369
1346
"bstr",
1370
1347
"fastrand",
···
1376
1353
1377
1354
[[package]]
1378
1355
name = "gix-glob"
1379
-
version = "0.21.0"
1356
+
version = "0.22.0"
1380
1357
source = "registry+https://github.com/rust-lang/crates.io-index"
1381
-
checksum = "b947db8366823e7a750c254f6bb29e27e17f27e457bf336ba79b32423db62cd5"
1358
+
checksum = "56089c688b74d628bdd7f38c9cadf08d39948c783decc48474cbfdf341ba68c4"
1382
1359
dependencies = [
1383
1360
"bitflags",
1384
1361
"bstr",
···
1388
1365
1389
1366
[[package]]
1390
1367
name = "gix-hash"
1391
-
version = "0.19.0"
1368
+
version = "0.20.0"
1392
1369
source = "registry+https://github.com/rust-lang/crates.io-index"
1393
-
checksum = "251fad79796a731a2a7664d9ea95ee29a9e99474de2769e152238d4fdb69d50e"
1370
+
checksum = "0732910f9595d1600a6355f37241c1960a851a7250df0e04ad0dad85a20cbf73"
1394
1371
dependencies = [
1395
1372
"faster-hex",
1396
1373
"gix-features",
···
1400
1377
1401
1378
[[package]]
1402
1379
name = "gix-hashtable"
1403
-
version = "0.9.0"
1380
+
version = "0.10.0"
1404
1381
source = "registry+https://github.com/rust-lang/crates.io-index"
1405
-
checksum = "c35300b54896153e55d53f4180460931ccd69b7e8d2f6b9d6401122cdedc4f07"
1382
+
checksum = "a27d4a3ea9640da504a2657fef3419c517fd71f1767ad8935298bcc805edd195"
1406
1383
dependencies = [
1407
1384
"gix-hash",
1408
-
"hashbrown 0.15.5",
1385
+
"hashbrown 0.16.0",
1409
1386
"parking_lot",
1410
1387
]
1411
1388
1412
1389
[[package]]
1413
1390
name = "gix-ignore"
1414
-
version = "0.16.0"
1391
+
version = "0.17.0"
1415
1392
source = "registry+https://github.com/rust-lang/crates.io-index"
1416
-
checksum = "564d6fddf46e2c981f571b23d6ad40cb08bddcaf6fc7458b1d49727ad23c2870"
1393
+
checksum = "cd53028c6adea0b41e0ae35499542f164115c555b6c15b80778e4c22b148a3d0"
1417
1394
dependencies = [
1418
1395
"bstr",
1419
1396
"gix-glob",
···
1424
1401
1425
1402
[[package]]
1426
1403
name = "gix-index"
1427
-
version = "0.41.0"
1404
+
version = "0.42.0"
1428
1405
source = "registry+https://github.com/rust-lang/crates.io-index"
1429
-
checksum = "2af39fde3ce4ce11371d9ce826f2936ec347318f2d1972fe98c2e7134e267e25"
1406
+
checksum = "91c389a09b9575b6e856bf2199bd717f5bd5e37333c24575a0cf9d08c41d75d6"
1430
1407
dependencies = [
1431
1408
"bitflags",
1432
1409
"bstr",
···
1441
1418
"gix-traverse",
1442
1419
"gix-utils",
1443
1420
"gix-validate",
1444
-
"hashbrown 0.15.5",
1421
+
"hashbrown 0.16.0",
1445
1422
"itoa",
1446
1423
"libc",
1447
1424
"memmap2",
···
1452
1429
1453
1430
[[package]]
1454
1431
name = "gix-lock"
1455
-
version = "18.0.0"
1432
+
version = "19.0.0"
1456
1433
source = "registry+https://github.com/rust-lang/crates.io-index"
1457
-
checksum = "b9fa71da90365668a621e184eb5b979904471af1b3b09b943a84bc50e8ad42ed"
1434
+
checksum = "729d7857429a66023bc0c29d60fa21d0d6ae8862f33c1937ba89e0f74dd5c67f"
1458
1435
dependencies = [
1459
1436
"gix-tempfile",
1460
1437
"gix-utils",
···
1463
1440
1464
1441
[[package]]
1465
1442
name = "gix-mailmap"
1466
-
version = "0.27.2"
1443
+
version = "0.27.3"
1467
1444
source = "registry+https://github.com/rust-lang/crates.io-index"
1468
-
checksum = "9a8982e1874a2034d7dd481bcdd6a05579ba444bcda748511eb0f8e50eb10487"
1445
+
checksum = "ef3b9794fa2545ab583e7fcd952900cdd8673fe51e57ee2a85b6728abb6bcb22"
1469
1446
dependencies = [
1470
1447
"bstr",
1471
1448
"gix-actor",
···
1475
1452
1476
1453
[[package]]
1477
1454
name = "gix-negotiate"
1478
-
version = "0.21.0"
1455
+
version = "0.22.0"
1479
1456
source = "registry+https://github.com/rust-lang/crates.io-index"
1480
-
checksum = "1d58d4c9118885233be971e0d7a589f5cfb1a8bd6cb6e2ecfb0fc6b1b293c83b"
1457
+
checksum = "89e16c96e052467d64c8f75a703b78976b33b034b9ff1f1d0c056c584319b0b8"
1481
1458
dependencies = [
1482
1459
"bitflags",
1483
1460
"gix-commitgraph",
···
1491
1468
1492
1469
[[package]]
1493
1470
name = "gix-object"
1494
-
version = "0.50.2"
1471
+
version = "0.51.0"
1495
1472
source = "registry+https://github.com/rust-lang/crates.io-index"
1496
-
checksum = "d69ce108ab67b65fbd4fb7e1331502429d78baeb2eee10008bdef55765397c07"
1473
+
checksum = "f4d23c75c112f03724792155ff847f12df6396e2528a5f2ed26eca2cf9a11896"
1497
1474
dependencies = [
1498
1475
"bstr",
1499
1476
"gix-actor",
···
1512
1489
1513
1490
[[package]]
1514
1491
name = "gix-odb"
1515
-
version = "0.70.0"
1492
+
version = "0.71.0"
1516
1493
source = "registry+https://github.com/rust-lang/crates.io-index"
1517
-
checksum = "9c9d7af10fda9df0bb4f7f9bd507963560b3c66cb15a5b825caf752e0eb109ac"
1494
+
checksum = "8c5f6d4ba857982cd2225de0a5d8b61d3b63a314395327f8a15943977203da08"
1518
1495
dependencies = [
1519
1496
"arc-swap",
1520
1497
"gix-date",
···
1533
1510
1534
1511
[[package]]
1535
1512
name = "gix-pack"
1536
-
version = "0.60.0"
1513
+
version = "0.61.0"
1537
1514
source = "registry+https://github.com/rust-lang/crates.io-index"
1538
-
checksum = "d8571df89bfca5abb49c3e3372393f7af7e6f8b8dbe2b96303593cef5b263019"
1515
+
checksum = "b3afaaa2205a477f5c59a5459bad9205e6de438d218d2af6f0092e19cec03a60"
1539
1516
dependencies = [
1540
1517
"clru",
1541
1518
"gix-chunk",
···
1552
1529
1553
1530
[[package]]
1554
1531
name = "gix-packetline"
1555
-
version = "0.19.1"
1532
+
version = "0.19.2"
1556
1533
source = "registry+https://github.com/rust-lang/crates.io-index"
1557
-
checksum = "2592fbd36249a2fea11056f7055cc376301ef38d903d157de41998335bbf1f93"
1534
+
checksum = "cd57d692b4625c2ece7634ba5d71939bbfe07bcc70eac92e10875faaf83f5316"
1558
1535
dependencies = [
1559
1536
"bstr",
1560
1537
"faster-hex",
···
1564
1541
1565
1542
[[package]]
1566
1543
name = "gix-packetline-blocking"
1567
-
version = "0.19.1"
1544
+
version = "0.19.2"
1568
1545
source = "registry+https://github.com/rust-lang/crates.io-index"
1569
-
checksum = "fc4e706f328cd494cc8f932172e123a72b9a4711b0db5e411681432a89bd4c94"
1546
+
checksum = "d10476ed5b3c9e04113e16dbee1fa7a4690279f257a74691e85372c63588c4fd"
1570
1547
dependencies = [
1571
1548
"bstr",
1572
1549
"faster-hex",
···
1576
1553
1577
1554
[[package]]
1578
1555
name = "gix-path"
1579
-
version = "0.10.20"
1556
+
version = "0.10.21"
1580
1557
source = "registry+https://github.com/rust-lang/crates.io-index"
1581
-
checksum = "06d37034a4c67bbdda76f7bcd037b2f7bc0fba0c09a6662b19697a5716e7b2fd"
1558
+
checksum = "0416b41cd00ff292af9b94b0660880c44bd2ed66828ddca9a2b333535cbb71b8"
1582
1559
dependencies = [
1583
1560
"bstr",
1584
1561
"gix-trace",
1585
1562
"gix-validate",
1586
1563
"home",
1587
-
"once_cell",
1588
1564
"thiserror",
1589
1565
]
1590
1566
1591
1567
[[package]]
1592
1568
name = "gix-pathspec"
1593
-
version = "0.12.0"
1569
+
version = "0.13.0"
1594
1570
source = "registry+https://github.com/rust-lang/crates.io-index"
1595
-
checksum = "daedead611c9bd1f3640dc90a9012b45f790201788af4d659f28d94071da7fba"
1571
+
checksum = "d05e28457dca7c65a2dbe118869aab922a5bd382b7bb10cff5354f366845c128"
1596
1572
dependencies = [
1597
1573
"bitflags",
1598
1574
"bstr",
···
1604
1582
1605
1583
[[package]]
1606
1584
name = "gix-prompt"
1607
-
version = "0.11.1"
1585
+
version = "0.11.2"
1608
1586
source = "registry+https://github.com/rust-lang/crates.io-index"
1609
-
checksum = "6ffa1a7a34c81710aaa666a428c142b6c5d640492fcd41267db0740d923c7906"
1587
+
checksum = "868e6516dfa16fdcbc5f8c935167d085f2ae65ccd4c9476a4319579d12a69d8d"
1610
1588
dependencies = [
1611
1589
"gix-command",
1612
1590
"gix-config-value",
···
1617
1595
1618
1596
[[package]]
1619
1597
name = "gix-protocol"
1620
-
version = "0.51.0"
1598
+
version = "0.52.0"
1621
1599
source = "registry+https://github.com/rust-lang/crates.io-index"
1622
-
checksum = "12b4b807c47ffcf7c1e5b8119585368a56449f3493da93b931e1d4239364e922"
1600
+
checksum = "681901ef2f057fd1d856941f43d9f8b84a30df61f46182c0f26297b1a16f1f7f"
1623
1601
dependencies = [
1624
1602
"bstr",
1625
1603
"gix-date",
···
1636
1614
1637
1615
[[package]]
1638
1616
name = "gix-quote"
1639
-
version = "0.6.0"
1617
+
version = "0.6.1"
1640
1618
source = "registry+https://github.com/rust-lang/crates.io-index"
1641
-
checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd"
1619
+
checksum = "e912ec04b7b1566a85ad486db0cab6b9955e3e32bcd3c3a734542ab3af084c5b"
1642
1620
dependencies = [
1643
1621
"bstr",
1644
1622
"gix-utils",
···
1647
1625
1648
1626
[[package]]
1649
1627
name = "gix-ref"
1650
-
version = "0.53.1"
1628
+
version = "0.54.0"
1651
1629
source = "registry+https://github.com/rust-lang/crates.io-index"
1652
-
checksum = "b966f578079a42f4a51413b17bce476544cca1cf605753466669082f94721758"
1630
+
checksum = "b3e8559e2f6eb30785f13a689764d571d9aaf2e60603829e7504b97cae5aaaad"
1653
1631
dependencies = [
1654
1632
"gix-actor",
1655
1633
"gix-features",
···
1668
1646
1669
1647
[[package]]
1670
1648
name = "gix-refspec"
1671
-
version = "0.31.0"
1649
+
version = "0.32.0"
1672
1650
source = "registry+https://github.com/rust-lang/crates.io-index"
1673
-
checksum = "7d29cae1ae31108826e7156a5e60bffacab405f4413f5bc0375e19772cce0055"
1651
+
checksum = "93147960f77695ba89b72019b789679278dd4dad6a0f9a4a5bf2fd07aba56912"
1674
1652
dependencies = [
1675
1653
"bstr",
1676
1654
"gix-hash",
···
1682
1660
1683
1661
[[package]]
1684
1662
name = "gix-revision"
1685
-
version = "0.35.0"
1663
+
version = "0.36.0"
1686
1664
source = "registry+https://github.com/rust-lang/crates.io-index"
1687
-
checksum = "f651f2b1742f760bb8161d6743229206e962b73d9c33c41f4e4aefa6586cbd3d"
1665
+
checksum = "235c2338044e1e901e98cc44d669574592394212803d3956b6ac025e88963512"
1688
1666
dependencies = [
1689
1667
"bitflags",
1690
1668
"bstr",
···
1700
1678
1701
1679
[[package]]
1702
1680
name = "gix-revwalk"
1703
-
version = "0.21.0"
1681
+
version = "0.22.0"
1704
1682
source = "registry+https://github.com/rust-lang/crates.io-index"
1705
-
checksum = "06e74f91709729e099af6721bd0fa7d62f243f2005085152301ca5cdd86ec02c"
1683
+
checksum = "02e2de4f91d712b1f6873477f769225fe430ffce2af8c7c85721c3ff955783b3"
1706
1684
dependencies = [
1707
1685
"gix-commitgraph",
1708
1686
"gix-date",
···
1715
1693
1716
1694
[[package]]
1717
1695
name = "gix-sec"
1718
-
version = "0.12.0"
1696
+
version = "0.12.1"
1719
1697
source = "registry+https://github.com/rust-lang/crates.io-index"
1720
-
checksum = "09f7053ed7c66633b56c57bc6ed3377be3166eaf3dc2df9f1c5ec446df6fdf2c"
1698
+
checksum = "52e45952d0f94824e6ae7e4388b4cf9691cf50ca2ac544e3540858305a316ce7"
1721
1699
dependencies = [
1722
1700
"bitflags",
1723
1701
"gix-path",
1724
1702
"libc",
1725
-
"windows-sys 0.59.0",
1703
+
"windows-sys 0.61.2",
1726
1704
]
1727
1705
1728
1706
[[package]]
1729
1707
name = "gix-shallow"
1730
-
version = "0.5.0"
1708
+
version = "0.6.0"
1731
1709
source = "registry+https://github.com/rust-lang/crates.io-index"
1732
-
checksum = "d936745103243ae4c510f19e0760ce73fb0f08096588fdbe0f0d7fb7ce8944b7"
1710
+
checksum = "e2374692db1ee1ffa0eddcb9e86ec218f7c4cdceda800ebc5a9fdf73a8c08223"
1733
1711
dependencies = [
1734
1712
"bstr",
1735
1713
"gix-hash",
···
1739
1717
1740
1718
[[package]]
1741
1719
name = "gix-status"
1742
-
version = "0.20.0"
1720
+
version = "0.21.0"
1743
1721
source = "registry+https://github.com/rust-lang/crates.io-index"
1744
-
checksum = "2a4afff9b34eeececa8bdc32b42fb318434b6b1391d9f8d45fe455af08dc2d35"
1722
+
checksum = "84e01b3762e781e3d4800dfcd25140878d704b018cf792f4a0c55a1298cfdba1"
1745
1723
dependencies = [
1746
1724
"bstr",
1747
1725
"filetime",
···
1762
1740
1763
1741
[[package]]
1764
1742
name = "gix-submodule"
1765
-
version = "0.20.0"
1743
+
version = "0.21.0"
1766
1744
source = "registry+https://github.com/rust-lang/crates.io-index"
1767
-
checksum = "657cc5dd43cbc7a14d9c5aaf02cfbe9c2a15d077cded3f304adb30ef78852d3e"
1745
+
checksum = "9bacc06333b50abc4fc06204622c2dd92850de2066bb5d421ac776d2bef7ae55"
1768
1746
dependencies = [
1769
1747
"bstr",
1770
1748
"gix-config",
···
1777
1755
1778
1756
[[package]]
1779
1757
name = "gix-tempfile"
1780
-
version = "18.0.0"
1758
+
version = "19.0.0"
1781
1759
source = "registry+https://github.com/rust-lang/crates.io-index"
1782
-
checksum = "666c0041bcdedf5fa05e9bef663c897debab24b7dc1741605742412d1d47da57"
1760
+
checksum = "0c413fdfca2dea768903b7ca73e46b0a8c32e4b61662c68af127bcfb7c2f7c20"
1783
1761
dependencies = [
1784
1762
"dashmap",
1785
1763
"gix-fs",
1786
1764
"libc",
1787
-
"once_cell",
1788
1765
"parking_lot",
1789
1766
"signal-hook",
1790
1767
"signal-hook-registry",
···
1792
1771
1793
1772
[[package]]
1794
1773
name = "gix-trace"
1795
-
version = "0.1.13"
1774
+
version = "0.1.14"
1796
1775
source = "registry+https://github.com/rust-lang/crates.io-index"
1797
-
checksum = "e2ccaf54b0b1743a695b482ca0ab9d7603744d8d10b2e5d1a332fef337bee658"
1776
+
checksum = "d35bf1c8c1a883bdbb17ed1fef49be8c751a50dc3a28a2deb4234d826eddb4bb"
1798
1777
1799
1778
[[package]]
1800
1779
name = "gix-transport"
1801
-
version = "0.48.0"
1780
+
version = "0.49.0"
1802
1781
source = "registry+https://github.com/rust-lang/crates.io-index"
1803
-
checksum = "12f7cc0179fc89d53c54e1f9ce51229494864ab4bf136132d69db1b011741ca3"
1782
+
checksum = "bd65513315c16ec52f479858b15fc89057e5ff6342cad1ea5573789ca4448f25"
1804
1783
dependencies = [
1805
1784
"bstr",
1806
1785
"gix-command",
···
1814
1793
1815
1794
[[package]]
1816
1795
name = "gix-traverse"
1817
-
version = "0.47.0"
1796
+
version = "0.48.0"
1818
1797
source = "registry+https://github.com/rust-lang/crates.io-index"
1819
-
checksum = "c7cdc82509d792ba0ad815f86f6b469c7afe10f94362e96c4494525a6601bdd5"
1798
+
checksum = "412126bade03a34f5d4125fd64878852718575b3b360eaae3b29970cb555e2a2"
1820
1799
dependencies = [
1821
1800
"bitflags",
1822
1801
"gix-commitgraph",
···
1831
1810
1832
1811
[[package]]
1833
1812
name = "gix-url"
1834
-
version = "0.32.0"
1813
+
version = "0.33.0"
1835
1814
source = "registry+https://github.com/rust-lang/crates.io-index"
1836
-
checksum = "1b76a9d266254ad287ffd44467cd88e7868799b08f4d52e02d942b93e514d16f"
1815
+
checksum = "788faa4421a720200c532e4377b17214963583cef7d43f0741a299f81e6b634e"
1837
1816
dependencies = [
1838
1817
"bstr",
1839
1818
"gix-features",
···
1845
1824
1846
1825
[[package]]
1847
1826
name = "gix-utils"
1848
-
version = "0.3.0"
1827
+
version = "0.3.1"
1849
1828
source = "registry+https://github.com/rust-lang/crates.io-index"
1850
-
checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5"
1829
+
checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5"
1851
1830
dependencies = [
1852
1831
"bstr",
1853
1832
"fastrand",
···
1856
1835
1857
1836
[[package]]
1858
1837
name = "gix-validate"
1859
-
version = "0.10.0"
1838
+
version = "0.10.1"
1860
1839
source = "registry+https://github.com/rust-lang/crates.io-index"
1861
-
checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d"
1840
+
checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4"
1862
1841
dependencies = [
1863
1842
"bstr",
1864
1843
"thiserror",
···
1866
1845
1867
1846
[[package]]
1868
1847
name = "gix-worktree"
1869
-
version = "0.42.0"
1848
+
version = "0.43.0"
1870
1849
source = "registry+https://github.com/rust-lang/crates.io-index"
1871
-
checksum = "55f625ac9126c19bef06dbc6d2703cdd7987e21e35b497bb265ac37d383877b1"
1850
+
checksum = "c5a40efab3eef5485365fee7b5df60732110bc3521af5e48c5cd1c153c48dae0"
1872
1851
dependencies = [
1873
1852
"bstr",
1874
1853
"gix-attributes",
···
1885
1864
1886
1865
[[package]]
1887
1866
name = "gix-worktree-state"
1888
-
version = "0.20.0"
1867
+
version = "0.21.0"
1889
1868
source = "registry+https://github.com/rust-lang/crates.io-index"
1890
-
checksum = "06ba9b17cbacc02b25801197b20100f7f9bd621db1e7fce9d3c8ab3175207bf8"
1869
+
checksum = "046efd191ff842cc22ddce61a4e8cea75ef7e3c659772de0838b2ad74b0016ef"
1891
1870
dependencies = [
1892
1871
"bstr",
1893
1872
"gix-features",
···
1905
1884
1906
1885
[[package]]
1907
1886
name = "gix-worktree-stream"
1908
-
version = "0.22.0"
1887
+
version = "0.23.0"
1909
1888
source = "registry+https://github.com/rust-lang/crates.io-index"
1910
-
checksum = "f56a737cefbcd90b573cb5393d636f6dc5e0d08a8086356d8c4fcc623b49a0e8"
1889
+
checksum = "a629188d528f5ed8abe023cdbdc4d51ef19223552cd7e2808733f96163fbf79d"
1911
1890
dependencies = [
1912
1891
"gix-attributes",
1913
1892
"gix-features",
···
1978
1957
source = "registry+https://github.com/rust-lang/crates.io-index"
1979
1958
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
1980
1959
dependencies = [
1981
-
"allocator-api2",
1982
-
"equivalent",
1983
-
"foldhash",
1960
+
"foldhash 0.1.5",
1984
1961
]
1985
1962
1986
1963
[[package]]
···
1986
1967
version = "0.16.0"
1987
1968
source = "registry+https://github.com/rust-lang/crates.io-index"
1988
1969
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
1970
+
dependencies = [
1971
+
"allocator-api2",
1972
+
"equivalent",
1973
+
"foldhash 0.2.0",
1974
+
]
1989
1975
1990
1976
[[package]]
1991
1977
name = "heapless"
···
2529
2505
"http-body-util",
2530
2506
"hyper-util",
2531
2507
"identity",
2508
+
"lexicon",
2532
2509
"oauth",
2533
2510
"reqwest",
2534
2511
"rustc-hash",
···
2565
2540
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
2566
2541
dependencies = [
2567
2542
"spin",
2543
+
]
2544
+
2545
+
[[package]]
2546
+
name = "lexicon"
2547
+
version = "0.0.0"
2548
+
dependencies = [
2549
+
"data-encoding",
2550
+
"gix-hash",
2551
+
"identity",
2552
+
"serde",
2553
+
"thiserror",
2554
+
"time",
2568
2555
]
2569
2556
2570
2557
[[package]]
···
3380
3343
"errno",
3381
3344
"libc",
3382
3345
"linux-raw-sys",
3383
-
"windows-sys 0.61.0",
3346
+
"windows-sys 0.60.2",
3384
3347
]
3385
3348
3386
3349
[[package]]
···
3443
3406
source = "registry+https://github.com/rust-lang/crates.io-index"
3444
3407
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
3445
3408
dependencies = [
3446
-
"windows-sys 0.61.0",
3409
+
"windows-sys 0.61.2",
3447
3410
]
3448
3411
3449
3412
[[package]]
···
3905
3868
"getrandom 0.3.4",
3906
3869
"once_cell",
3907
3870
"rustix",
3908
-
"windows-sys 0.61.0",
3871
+
"windows-sys 0.60.2",
3909
3872
]
3910
3873
3911
3874
[[package]]
···
4006
3969
"signal-hook-registry",
4007
3970
"socket2 0.6.0",
4008
3971
"tokio-macros",
4009
-
"windows-sys 0.61.0",
3972
+
"windows-sys 0.61.2",
4010
3973
]
4011
3974
4012
3975
[[package]]
···
4527
4490
source = "registry+https://github.com/rust-lang/crates.io-index"
4528
4491
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
4529
4492
dependencies = [
4530
-
"windows-sys 0.61.0",
4493
+
"windows-sys 0.60.2",
4531
4494
]
4532
4495
4533
4496
[[package]]
···
4544
4507
4545
4508
[[package]]
4546
4509
name = "windows-link"
4547
-
version = "0.2.0"
4510
+
version = "0.2.1"
4548
4511
source = "registry+https://github.com/rust-lang/crates.io-index"
4549
-
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
4512
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4550
4513
4551
4514
[[package]]
4552
4515
name = "windows-registry"
···
4615
4578
4616
4579
[[package]]
4617
4580
name = "windows-sys"
4618
-
version = "0.61.0"
4581
+
version = "0.61.2"
4619
4582
source = "registry+https://github.com/rust-lang/crates.io-index"
4620
-
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
4583
+
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
4621
4584
dependencies = [
4622
-
"windows-link 0.2.0",
4585
+
"windows-link 0.2.1",
4623
4586
]
4624
4587
4625
4588
[[package]]
+5
-1
Cargo.toml
+5
-1
Cargo.toml
···
4
4
"crates/credential_helper",
5
5
"crates/identity",
6
6
"crates/knot",
7
+
"crates/lexicon",
7
8
"crates/oauth",
8
9
"crates/serve_git",
9
10
"crates/xrpc"
···
21
20
22
21
[workspace.dependencies]
23
22
identity = { path = "crates/identity" }
23
+
lexicon = { path = "crates/lexicon" }
24
24
oauth = { path = "crates/oauth" }
25
25
serve_git = { path = "crates/serve_git"}
26
26
xrpc = { path = "crates/xrpc" }
27
27
28
28
anyhow = "1.0.100"
29
29
axum = "0.8.4"
30
-
gix = { version = "0.73.0", features = ["max-performance"] }
30
+
data-encoding = "2.9.0"
31
+
gix = { version = "0.74.0", features = ["max-performance"] }
31
32
reqwest = { version = "0.12.23", features = ["json"] }
32
33
serde = { version = "1.0.226", features = ["derive"] }
33
34
serde_json = "1.0.145"
34
35
thiserror = "2.0.16"
36
+
time = { version = "0.3.43", features = ["formatting", "macros", "parsing", "serde"] }
35
37
tracing = "0.1.41"
36
38
url = { version = "2.5.7", features = ["serde"] }
37
39
+3
-2
crates/knot/Cargo.toml
+3
-2
crates/knot/Cargo.toml
···
10
10
11
11
[dependencies]
12
12
identity.workspace = true
13
+
lexicon.workspace = true
13
14
oauth.workspace = true
14
15
serve_git.workspace = true
15
16
xrpc.workspace = true
···
28
27
axum-extra = { version = "0.10.1", features = ["async-read-body"] }
29
28
bytes = "1.10.1"
30
29
clap = { version = "4.5.47", features = ["derive", "env", "string"] }
31
-
data-encoding = "2.9.0"
30
+
data-encoding.workspace = true
32
31
futures-util = "0.3.31"
33
32
hyper-util = { version = "0.1.17", features = ["client"] }
34
33
rustc-hash = "2.1.1"
35
-
time = { version = "0.3.43", features = ["formatting", "macros", "parsing", "serde"] }
34
+
time.workspace = true
36
35
tokio = { version = "1.47.1", features = ["io-util", "macros", "net", "process", "signal", "rt-multi-thread"] }
37
36
tokio-stream = { version = "0.1.17", features = ["time"] }
38
37
tokio-tungstenite = "0.28.0"
-17
crates/knot/src/convert.rs
-17
crates/knot/src/convert.rs
···
1
-
//!
2
-
//! Utility conversions.
3
-
//!
4
-
use gix::date::Time;
5
-
use time::OffsetDateTime;
6
-
use time::UtcOffset;
7
-
use time::error::ComponentRange;
8
-
9
-
/// Convert a [`gix::date::Time`] to a [`time::OffsetDateTime`].
10
-
///
11
-
#[allow(unused)]
12
-
pub fn time_to_offsetdatetime(time: &Time) -> Result<OffsetDateTime, ComponentRange> {
13
-
let odt = OffsetDateTime::from_unix_timestamp(time.seconds)?
14
-
.to_offset(UtcOffset::from_whole_seconds(time.offset)?);
15
-
16
-
Ok(odt)
17
-
}
-6
crates/knot/src/lib.rs
-6
crates/knot/src/lib.rs
+6
-4
crates/knot/src/main.rs
+6
-4
crates/knot/src/main.rs
···
10
10
rt::TokioExecutor,
11
11
};
12
12
use identity::Resolver;
13
-
use knot::{model::Knot, repospec::RepoSpec};
13
+
use knot::model::Knot;
14
+
use lexicon::extra::repopath::RepoPath;
14
15
use reqwest::{ClientBuilder, StatusCode};
15
16
use serde::Deserialize;
16
17
use std::{
···
54
53
55
54
#[derive(Deserialize)]
56
55
struct Repo {
57
-
#[serde(with = "knot::repospec::query_param")]
58
-
repo: RepoSpec,
56
+
#[serde(with = "lexicon::extra::repopath::string")]
57
+
repo: RepoPath,
59
58
}
60
59
61
60
fn extract_request_id<B>(request: &Request<B>) -> Option<&str> {
···
164
163
else => break,
165
164
}
166
165
}
166
+
tracing::info!("/events websocket closed");
167
167
})
168
168
},
169
169
),
···
189
187
}
190
188
191
189
if let Ok(Query(Repo { repo })) = Query::try_from_uri(uri) {
192
-
span.record("repo", format!("{}/{}", repo.owner, repo.name));
190
+
span.record("repo", repo.to_string());
193
191
}
194
192
195
193
span
+77
-139
crates/knot/src/model.rs
+77
-139
crates/knot/src/model.rs
···
1
+
pub mod convert;
2
+
pub mod errors;
3
+
mod knot_state;
4
+
pub mod nicediff;
5
+
1
6
use crate::{
2
-
convert::time_to_offsetdatetime,
3
7
model::errors::{HeadDetached, PathNotFound, RefNotFound, RepoEmpty, RepoError, RepoNotFound},
4
8
public::{
5
9
git::{Error, GitAuthorization, NotFound},
6
10
xrpc::XrpcError,
7
11
},
8
-
repospec::RepoSpec,
9
12
types::sh::tangled::repo::{
10
-
BlobEncoding, BlobParams, BlobResponse, Branch, BranchesParams, BranchesResponse,
11
-
DefaultBranchResponse, Diff, DiffParams, DiffResponse, GetDefaultBranchParams, JsonBlob,
12
-
LogParams, LogResponse, Readme, Reference, Tag, TagParams, TagsResponse, TreeEntry,
13
-
TreeParams, TreeResponse,
13
+
blob::BlobResponse, branches::BranchesOutput, diff::DiffResponse, log::LogOutput,
14
+
tags::TagsOutput,
14
15
},
15
16
};
16
17
use axum::{
17
18
extract::{FromRef, FromRequestParts},
18
19
http::request::Parts,
19
20
};
21
+
use convert::convert_entry;
20
22
use gix::{Commit, ObjectId, Repository, bstr::ByteSlice as _, open::Options};
21
23
use identity::{Did, Resolver};
24
+
use lexicon::{
25
+
extra::repopath::RepoPath,
26
+
sh::tangled::repo::{blob, branches, diff, get_default_branch, log, refs, tags, tree},
27
+
};
22
28
use oauth::public_key::PublicKey;
23
29
use reqwest::ClientBuilder;
24
30
use serve_git::state::GitServiceState;
···
56
50
b"readme.asciidoc",
57
51
b"index.rst",
58
52
];
59
-
60
-
pub mod errors;
61
-
mod gitoxide;
62
-
mod knot_state;
63
53
64
54
#[derive(Clone)]
65
55
pub struct Knot {
···
153
151
pub trait RepositoryManager {
154
152
type Error;
155
153
156
-
fn resolve_repo_path(&self, repo: &RepoSpec) -> PathBuf;
154
+
fn resolve_repo_path(&self, repo: &RepoPath) -> PathBuf;
157
155
158
-
fn open(&self, repo: &RepoSpec) -> Result<Repository, Self::Error>;
156
+
fn open(&self, repo: &RepoPath) -> Result<Repository, Self::Error>;
159
157
160
158
/// Determine whether the specified account has permission to push to the
161
159
/// specified repository.
162
-
fn can_push(&self, repo: &RepoSpec, did: &Did) -> impl Future<Output = bool>;
160
+
fn can_push(&self, repo: &RepoPath, did: &Did) -> impl Future<Output = bool>;
163
161
}
164
162
165
163
impl RepositoryManager for Knot {
166
164
type Error = Box<gix::open::Error>;
167
165
168
-
fn resolve_repo_path(&self, repo: &RepoSpec) -> PathBuf {
166
+
fn resolve_repo_path(&self, repo: &RepoPath) -> PathBuf {
169
167
let mut path = self.inner.repository_base.clone();
170
168
path.push(repo.owner());
171
169
path.push(repo.name());
172
170
path
173
171
}
174
172
175
-
fn open(&self, repo: &RepoSpec) -> Result<Repository, Self::Error> {
173
+
fn open(&self, repo: &RepoPath) -> Result<Repository, Self::Error> {
176
174
let path = self.resolve_repo_path(repo);
177
175
let repository = Options::default()
178
176
.strict_config(true)
···
186
184
}
187
185
188
186
// Currently defers to the real knotserver's internal "/push-allowed" route.
189
-
async fn can_push(&self, repo: &RepoSpec, did: &Did) -> bool {
187
+
async fn can_push(&self, repo: &RepoPath, did: &Did) -> bool {
190
188
use reqwest::StatusCode;
191
189
192
190
let url = format!("http://localhost:5444/push-allowed?user={did}&repo={repo}");
···
223
221
224
222
pub async fn get_default_branch(
225
223
&self,
226
-
params: &GetDefaultBranchParams,
227
-
) -> Result<DefaultBranchResponse, XrpcError> {
224
+
params: &get_default_branch::Input,
225
+
) -> Result<get_default_branch::Output, XrpcError> {
228
226
tokio::task::block_in_place(|| {
229
227
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
230
228
···
237
235
.shorten()
238
236
.to_string();
239
237
240
-
let hash = head.id().map(|id| id.into());
238
+
let hash = head.id().map(|id| id.detach().into());
241
239
let when = head
242
-
.peel_to_commit_in_place()
240
+
.peel_to_commit()
243
241
.ok()
244
242
.and_then(|commit| {
245
243
commit
···
247
245
.ok()
248
246
.and_then(|committer| committer.time().ok())
249
247
})
250
-
.and_then(|time| time_to_offsetdatetime(&time).ok());
248
+
.and_then(|time| convert::time_to_offsetdatetime(&time).ok());
251
249
252
-
Ok(DefaultBranchResponse { name, hash, when })
250
+
Ok(get_default_branch::Output { name, hash, when })
253
251
})
254
252
}
255
253
256
-
pub async fn branches(&self, params: &BranchesParams) -> Result<BranchesResponse, XrpcError> {
254
+
pub async fn branches(&self, params: &branches::Input) -> Result<BranchesOutput, XrpcError> {
255
+
use crate::types::sh::tangled::repo::branches::Branch;
257
256
tokio::task::block_in_place(|| {
258
257
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
259
258
···
268
265
.to_string();
269
266
270
267
let mut branches = Vec::new();
271
-
for branch in repo
272
-
.references()?
273
-
.local_branches()?
274
-
.skip(params.cursor)
275
-
.take(params.limit.into())
268
+
for branch in repo.references()?.local_branches()?
269
+
// .skip(params.cursor)
270
+
// .take(params.limit.into())
276
271
{
277
272
let Ok(branch) = branch.inspect_err(|error| tracing::error!(?error)) else {
278
273
continue;
···
289
288
290
289
let is_default = name == default_name;
291
290
branches.push(Branch {
292
-
reference: Reference {
291
+
reference: refs::Reference {
293
292
name,
294
293
hash: commit.id.into(),
295
294
},
296
-
commit: commit.try_into()?,
295
+
commit: convert::try_convert_commit(commit)?,
297
296
is_default,
298
297
});
299
298
}
300
299
301
-
Ok(BranchesResponse { branches })
300
+
Ok(BranchesOutput { branches })
302
301
})
303
302
}
304
303
305
-
pub async fn log(&self, params: &LogParams) -> Result<LogResponse, XrpcError> {
304
+
pub async fn log(&self, params: &log::Input) -> Result<LogOutput, XrpcError> {
306
305
tokio::task::block_in_place(|| {
307
306
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
308
307
309
-
let commit_graph = repo.commit_graph_if_enabled().unwrap();
308
+
let commit_graph = repo.commit_graph_if_enabled()?;
310
309
let total = match &commit_graph {
311
310
Some(cg) => cg
312
311
.num_commits()
···
335
334
match commit {
336
335
Ok(commit) => {
337
336
let commit = repo.find_commit(commit.id()).map_err(RepoError)?;
338
-
commits.push(commit.try_into().map_err(RepoError)?);
337
+
commits.push(convert::try_convert_commit(commit).map_err(RepoError)?);
339
338
}
340
339
Err(error) => {
341
340
tracing::error!(?error);
···
344
343
}
345
344
}
346
345
347
-
Ok(LogResponse {
346
+
Ok(LogOutput {
348
347
commits,
349
348
log: true,
350
349
total,
···
354
353
})
355
354
}
356
355
357
-
pub async fn tags(&self, params: &TagParams) -> Result<TagsResponse, XrpcError> {
358
-
// @TODO Implement cursor.
356
+
pub async fn tags(&self, params: &tags::Input) -> Result<TagsOutput, XrpcError> {
357
+
use crate::types::sh::tangled::repo::tags::{Tag, TagsOutput};
358
+
359
359
tokio::task::block_in_place(|| {
360
360
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
361
361
···
380
378
)
381
379
});
382
380
383
-
tags.truncate(params.limit);
384
-
385
-
Ok(TagsResponse { tags })
381
+
Ok(TagsOutput { tags })
386
382
})
387
383
}
388
384
389
-
pub async fn tree(&self, params: &TreeParams) -> Result<TreeResponse, XrpcError> {
385
+
pub async fn tree(&self, params: &tree::Input) -> Result<tree::Output, XrpcError> {
390
386
tokio::task::block_in_place(|| {
391
387
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
392
388
let tip = Self::resolve_rev(&repo, params.rev.as_deref())?;
···
404
404
.ok_or(PathNotFound(subpath.to_string_lossy()))?;
405
405
406
406
if !entry.mode().is_tree() {
407
-
return Ok(TreeResponse {
407
+
return Ok(tree::Output {
408
408
files: vec![],
409
409
dotdot,
410
410
parent: params.path.clone(),
···
418
418
parent = Some(subpath.to_path_buf());
419
419
}
420
420
421
-
let mut files: Vec<TreeEntry> = vec![];
421
+
let mut files: Vec<tree::TreeEntry> = vec![];
422
422
let mut readme = None;
423
423
for entry in tree.iter() {
424
424
let Ok(entry) = entry else {
···
431
431
{
432
432
let mut file = repo.find_blob(entry.id())?;
433
433
if let Ok(contents) = String::from_utf8(file.take_data()) {
434
-
readme.replace(Readme {
434
+
readme.replace(tree::Readme {
435
435
contents,
436
436
filename: entry.filename().to_string(),
437
437
});
438
438
}
439
439
}
440
440
441
-
files.push(entry.into());
441
+
files.push(convert_entry(entry));
442
442
}
443
443
444
444
let files: Vec<_> = tree
445
445
.iter()
446
446
.filter_map(|entry| {
447
447
let entry = entry.ok()?;
448
-
let file: TreeEntry = entry.try_into().ok()?;
448
+
let file = convert::convert_entry(entry);
449
449
Some(file)
450
450
})
451
451
.collect();
452
452
453
-
Ok(TreeResponse {
453
+
Ok(tree::Output {
454
454
files,
455
455
dotdot,
456
456
parent,
···
460
460
})
461
461
}
462
462
463
-
pub async fn blob(&self, params: &BlobParams) -> Result<BlobResponse, XrpcError> {
463
+
pub async fn blob(&self, params: &blob::Input) -> Result<BlobResponse, XrpcError> {
464
+
use lexicon::sh::tangled::repo::blob::{Encoding, Output};
464
465
tokio::task::block_in_place(|| {
465
466
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
466
467
let tip = Self::resolve_rev(&repo, params.rev.as_deref())?;
···
482
481
483
482
let size = data.len();
484
483
let (content, is_binary, encoding) = match String::from_utf8(data) {
485
-
Ok(content) => (content, false, BlobEncoding::Utf8),
484
+
Ok(content) => (content, false, blob::Encoding::Utf8),
486
485
Err(error) => (
487
486
data_encoding::BASE64.encode(&error.into_bytes()),
488
487
true,
489
-
BlobEncoding::Base64,
488
+
Encoding::Base64,
490
489
),
491
490
};
492
491
493
-
Ok(BlobResponse::Json(JsonBlob {
492
+
Ok(BlobResponse::Json(Output {
493
+
rev: params.rev.as_deref().unwrap_or_default().to_owned(),
494
+
path: params.path.to_owned(),
494
495
content,
495
496
encoding,
497
+
size,
496
498
is_binary,
497
499
mime_type: "".to_string(),
498
-
path: params.path.to_owned(),
499
-
rev: params.rev.as_deref().unwrap_or_default().to_owned(),
500
-
size,
500
+
last_commit: None,
501
501
}))
502
502
})
503
503
}
504
504
505
-
pub async fn diff(&self, params: &DiffParams) -> Result<DiffResponse, XrpcError> {
506
-
{
507
-
tokio::task::block_in_place(|| {
508
-
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
509
-
510
-
let revision = match ObjectId::from_str(¶ms.rev) {
511
-
Ok(id) => repo.find_commit(id).map_err(RefNotFound)?,
512
-
Err(_) => {
513
-
// Assume the refspec is a branch or tag.
514
-
let mut reference =
515
-
repo.find_reference(¶ms.rev).map_err(RefNotFound)?;
516
-
reference.peel_to_commit().map_err(RefNotFound)?
517
-
}
518
-
};
519
-
520
-
// let parent_tree = match revision.parent_ids().next() {
521
-
// Some(id) => repository.find_commit(id)?.tree()?,
522
-
// None => repository.empty_tree(),
523
-
// };
524
-
525
-
// let mut cache = repository
526
-
// .diff_resource_cache(
527
-
// gix::diff::blob::pipeline::Mode::ToGit,
528
-
// WorktreeRoots::default(),
529
-
// )
530
-
// .unwrap();
531
-
532
-
// let this_tree = revision.tree()?;
533
-
// this_tree
534
-
// .changes()?
535
-
// .for_each_to_obtain_tree(&parent_tree, |change| {
536
-
// tracing::debug!(?change);
537
-
// if change.entry_mode().is_blob() {
538
-
// println!("{}:", change.location().to_string());
539
-
// let mut line_diff = change.diff(&mut cache).unwrap();
540
-
// line_diff
541
-
// .resource_cache
542
-
// .options
543
-
// .algorithm
544
-
// .replace(Algorithm::Histogram);
545
-
546
-
// let out = line_diff.lines(|_| Ok::<_, Er>(()) )?;
547
-
548
-
// let input = out.interned_input();
549
-
// let diff = gix::diff::blob::diff(
550
-
// Algorithm::Histogram,
551
-
// &input,
552
-
// UnifiedDiff::new(
553
-
// &input,
554
-
// String::new(),
555
-
// gix::diff::blob::unified_diff::NewlineSeparator::AfterHeaderAndWhenNeeded("\n"),
556
-
// ContextSize::symmetrical(3),
557
-
// ),
558
-
// ).unwrap()
559
-
// .finish();
560
-
561
-
// println!("{diff}");
562
-
// }
563
-
// Ok::<_, Er>(Action::Continue)
564
-
// })?;
565
-
566
-
let diff = Diff {
567
-
commit: revision.try_into()?,
568
-
stat: Default::default(),
569
-
deltas: vec![],
570
-
};
571
-
572
-
let response = DiffResponse {
573
-
rev: params.rev.to_owned(),
574
-
diff,
575
-
};
576
-
577
-
Ok(response)
578
-
})
579
-
}
505
+
pub async fn diff(&self, params: diff::Input) -> Result<DiffResponse, XrpcError> {
506
+
tokio::task::block_in_place(|| {
507
+
let repo = self.open(¶ms.repo).map_err(RepoNotFound)?;
508
+
let this_commit = Self::resolve_rev(&repo, Some(¶ms.rev))?;
509
+
let diff = nicediff::unified_diff_from_parent(this_commit).unwrap();
510
+
let response = DiffResponse {
511
+
rev: params.rev,
512
+
diff,
513
+
};
514
+
Ok(response)
515
+
})
580
516
}
581
517
}
582
518
···
538
600
parts: &mut Parts,
539
601
) -> Result<tokio::process::Command, Self::Rejection> {
540
602
use axum::extract::Path;
541
-
let Path(repospec) = Path::<RepoSpec>::from_request_parts(parts, &()).await?;
542
-
let repo = self.open(&repospec).map_err(NotFound)?;
603
+
let Path(repopath) = Path::<RepoPath>::from_request_parts(parts, &()).await?;
604
+
let repo = self.open(&repopath).map_err(NotFound)?;
543
605
let command = serve_git::commands::upload_pack_info_refs("/usr/bin/git", repo.path());
544
606
Ok(command)
545
607
}
···
549
611
parts: &mut Parts,
550
612
) -> Result<tokio::process::Command, Self::Rejection> {
551
613
use axum::extract::Path;
552
-
let Path(repospec) = Path::<RepoSpec>::from_request_parts(parts, &()).await?;
553
-
let repo = self.open(&repospec).map_err(NotFound)?;
614
+
let Path(repopath) = Path::<RepoPath>::from_request_parts(parts, &()).await?;
615
+
let repo = self.open(&repopath).map_err(NotFound)?;
554
616
let command = serve_git::commands::upload_pack("/usr/bin/git", repo.path());
555
617
Ok(command)
556
618
}
···
568
630
}
569
631
570
632
use axum::extract::Path;
571
-
let Path(repospec) = Path::<RepoSpec>::from_request_parts(parts, &()).await?;
572
-
if !self.can_push(&repospec, &auth.iss).await {
633
+
let Path(repopath) = Path::<RepoPath>::from_request_parts(parts, &()).await?;
634
+
if !self.can_push(&repopath, &auth.iss).await {
573
635
tracing::error!(did = %auth.iss, "push denied");
574
636
return Err(Error::forbidden(
575
637
self,
···
580
642
))?;
581
643
}
582
644
583
-
let repo = self.open(&repospec).map_err(NotFound)?;
645
+
let repo = self.open(&repopath).map_err(NotFound)?;
584
646
let command = serve_git::commands::receive_pack_info_refs("/usr/bin/git", repo.path());
585
647
Ok(command)
586
648
}
···
598
660
}
599
661
600
662
use axum::extract::Path;
601
-
let Path(repospec) = Path::<RepoSpec>::from_request_parts(parts, &()).await?;
602
-
if !self.can_push(&repospec, &auth.iss).await {
663
+
let Path(repopath) = Path::<RepoPath>::from_request_parts(parts, &()).await?;
664
+
if !self.can_push(&repopath, &auth.iss).await {
603
665
tracing::error!(did = %auth.iss, "push denied");
604
666
return Err(Error::forbidden(
605
667
self,
···
610
672
))?;
611
673
}
612
674
613
-
let repo = self.open(&repospec).map_err(NotFound)?;
675
+
let repo = self.open(&repopath).map_err(NotFound)?;
614
676
let command = serve_git::commands::receive_pack("/usr/bin/git", repo.path());
615
677
Ok(command)
616
678
}
+157
crates/knot/src/model/convert.rs
+157
crates/knot/src/model/convert.rs
···
1
+
use crate::{public::xrpc::XrpcError, types::sh::tangled::repo::tags};
2
+
use data_encoding::BASE64URL;
3
+
use gix::bstr::ByteSlice;
4
+
use lexicon::sh::tangled::repo::{refs, tree};
5
+
use reqwest::StatusCode;
6
+
use std::{borrow::Cow, collections::HashMap};
7
+
use time::{OffsetDateTime, error::ComponentRange};
8
+
9
+
#[derive(Debug, thiserror::Error)]
10
+
pub enum ConversionError {
11
+
#[error("Failed to decode object: {0}")]
12
+
Decode(#[from] gix::objs::decode::Error),
13
+
#[error("Failed to parse git time: {0}")]
14
+
TimeParse(#[from] gix::date::parse::Error),
15
+
#[error("Failed to convert git time: {0}")]
16
+
TimeConversion(#[from] time::error::ComponentRange),
17
+
}
18
+
19
+
impl From<ConversionError> for XrpcError {
20
+
fn from(value: ConversionError) -> Self {
21
+
Self {
22
+
status: StatusCode::INTERNAL_SERVER_ERROR,
23
+
error: "RepositoryError".into(),
24
+
message: value.to_string().into(),
25
+
}
26
+
}
27
+
}
28
+
29
+
/// Convert a git timestamp to an [`OffsetDateTime`].
30
+
pub fn time_to_offsetdatetime(time: &gix::date::Time) -> Result<OffsetDateTime, ComponentRange> {
31
+
use time::UtcOffset;
32
+
33
+
let odt = OffsetDateTime::from_unix_timestamp(time.seconds)?
34
+
.to_offset(UtcOffset::from_whole_seconds(time.offset)?);
35
+
36
+
Ok(odt)
37
+
}
38
+
39
+
pub fn try_convert_commit(value: gix::Commit<'_>) -> Result<refs::Commit, ConversionError> {
40
+
let id = value.id().detach();
41
+
let decoded = value.decode()?;
42
+
let mut merge_tag = String::default();
43
+
let mut extra_headers = HashMap::default();
44
+
for (key, value) in decoded.extra_headers {
45
+
match key.as_bytes() {
46
+
b"mergetag" => merge_tag = value.to_string(),
47
+
b"gpgsig" => {}
48
+
_ => {
49
+
extra_headers.insert(key.to_string(), BASE64URL.encode(value.as_bytes()));
50
+
}
51
+
}
52
+
}
53
+
54
+
Ok(refs::Commit {
55
+
hash: id.into(),
56
+
author: try_convert_signature(decoded.author)?,
57
+
committer: try_convert_signature(decoded.committer)?,
58
+
merge_tag,
59
+
pgp_signature: value.signature()?.map(|(sig, _)| sig.to_string()),
60
+
message: decoded.message.to_string(),
61
+
tree_hash: value.tree_id()?.detach().into(),
62
+
parent_hashes: value.parent_ids().map(|id| id.detach().into()).collect(),
63
+
// @TODO Review encoding parameter
64
+
encoding: Cow::Borrowed("UTF-8"),
65
+
extra_headers,
66
+
})
67
+
}
68
+
69
+
pub fn convert_entry(value: gix::object::tree::EntryRef<'_, '_>) -> tree::TreeEntry {
70
+
use gix::objs::tree::EntryKind;
71
+
72
+
let name = value.filename().to_string();
73
+
let mode = value.mode();
74
+
75
+
let is_file = mode.is_blob();
76
+
let is_subtree = mode.is_tree();
77
+
78
+
let mode_string = match mode.kind() {
79
+
EntryKind::Tree => "drwxr-xr-x",
80
+
EntryKind::Blob => "-rw-r--r--",
81
+
EntryKind::BlobExecutable => "-rwxr-xr-x",
82
+
EntryKind::Link => "----------",
83
+
EntryKind::Commit => "----------",
84
+
};
85
+
86
+
tree::TreeEntry {
87
+
name,
88
+
mode: Cow::Borrowed(mode_string),
89
+
is_file,
90
+
is_subtree,
91
+
..Default::default()
92
+
}
93
+
}
94
+
95
+
pub fn convert_reference(value: &gix::Reference<'_>) -> refs::Reference {
96
+
refs::Reference {
97
+
name: value.name().shorten().to_string(),
98
+
hash: value.id().detach().into(),
99
+
}
100
+
}
101
+
102
+
pub fn try_convert_signature(
103
+
signature: gix::actor::SignatureRef<'_>,
104
+
) -> Result<refs::Signature, ConversionError> {
105
+
let signature = signature.trim();
106
+
Ok(refs::Signature {
107
+
name: signature.name.to_string(),
108
+
email: signature.email.to_string(),
109
+
when: time_to_offsetdatetime(&signature.time()?)?,
110
+
})
111
+
}
112
+
113
+
impl TryFrom<gix::Tag<'_>> for tags::TagAnnotation {
114
+
type Error = gix::objs::decode::Error;
115
+
116
+
fn try_from(value: gix::Tag<'_>) -> Result<Self, Self::Error> {
117
+
use gix::object::Kind;
118
+
119
+
let hash = value.id.into();
120
+
let decoded = value.decode()?;
121
+
122
+
// cf. <https://github.com/git/git/blob/7014b55638da979331baf8dc31c4e1d697cf2d67/object.h#L97>
123
+
let target_type = match decoded.target_kind {
124
+
Kind::Commit => 1,
125
+
Kind::Tree => 2,
126
+
Kind::Blob => 3,
127
+
Kind::Tag => 4,
128
+
};
129
+
130
+
Ok(tags::TagAnnotation {
131
+
hash,
132
+
name: decoded.name.to_string(),
133
+
tagger: decoded
134
+
.tagger
135
+
.and_then(|tagger| try_convert_signature(tagger).ok()),
136
+
message: decoded.message.to_string(),
137
+
pgp_signature: decoded.pgp_signature.map(ToString::to_string),
138
+
target_type,
139
+
target: decoded.target().into(),
140
+
})
141
+
}
142
+
}
143
+
144
+
impl TryFrom<gix::Reference<'_>> for tags::Tag {
145
+
type Error = gix::objs::decode::Error;
146
+
147
+
fn try_from(mut value: gix::Reference<'_>) -> Result<Self, Self::Error> {
148
+
let r#ref: refs::Reference = convert_reference(&value);
149
+
let annotation = value
150
+
.peel_to_tag()
151
+
.ok()
152
+
.map(TryFrom::try_from)
153
+
.transpose()?;
154
+
155
+
Ok(tags::Tag { r#ref, annotation })
156
+
}
157
+
}
-160
crates/knot/src/model/gitoxide.rs
-160
crates/knot/src/model/gitoxide.rs
···
1
-
//!
2
-
//! [`TryFrom`] impls for converting Gitoxide objects into Tangled objects.
3
-
//!
4
-
use crate::{
5
-
public::xrpc::XrpcError,
6
-
types::sh::tangled::repo::{Commit, Reference, Signature, Tag, TagAnnotation, TreeEntry},
7
-
};
8
-
9
-
use data_encoding::BASE64URL;
10
-
use gix::bstr::ByteSlice;
11
-
use reqwest::StatusCode;
12
-
use std::{borrow::Cow, collections::HashMap};
13
-
14
-
#[derive(Debug, thiserror::Error)]
15
-
pub enum ConversionError {
16
-
#[error("{0}")]
17
-
Decode(#[from] gix::objs::decode::Error),
18
-
#[error("{0}")]
19
-
TimeParse(#[from] gix::date::parse::Error),
20
-
#[error("{0}")]
21
-
TimeConversion(#[from] time::error::ComponentRange),
22
-
}
23
-
24
-
impl From<ConversionError> for XrpcError {
25
-
fn from(value: ConversionError) -> Self {
26
-
Self {
27
-
status: StatusCode::INTERNAL_SERVER_ERROR,
28
-
error: "RepositoryError".into(),
29
-
message: value.to_string().into(),
30
-
}
31
-
}
32
-
}
33
-
34
-
impl TryFrom<gix::actor::SignatureRef<'_>> for Signature {
35
-
type Error = ConversionError;
36
-
37
-
fn try_from(value: gix::actor::SignatureRef<'_>) -> Result<Self, Self::Error> {
38
-
let signature = value.trim();
39
-
Ok(Self {
40
-
name: signature.name.to_string(),
41
-
email: signature.email.to_string(),
42
-
when: crate::convert::time_to_offsetdatetime(&signature.time()?)?,
43
-
})
44
-
}
45
-
}
46
-
47
-
impl TryFrom<gix::Commit<'_>> for Commit {
48
-
type Error = ConversionError;
49
-
50
-
fn try_from(value: gix::Commit<'_>) -> Result<Self, Self::Error> {
51
-
let id = value.id();
52
-
let decoded = value.decode()?;
53
-
let signature = value.signature()?.map(|(sig, _)| sig.to_string());
54
-
let mut merge_tag = String::default();
55
-
let mut extra_headers = HashMap::default();
56
-
for (key, value) in decoded.extra_headers {
57
-
match key.as_bytes() {
58
-
b"mergetag" => merge_tag = value.to_string(),
59
-
b"gpgsig" => {}
60
-
_ => {
61
-
extra_headers.insert(key.to_string(), BASE64URL.encode(value.as_bytes()));
62
-
}
63
-
}
64
-
}
65
-
66
-
Ok(Self {
67
-
hash: id.into(),
68
-
author: decoded.author.try_into()?,
69
-
committer: decoded.committer.try_into()?,
70
-
merge_tag,
71
-
signature,
72
-
message: decoded.message.to_string(),
73
-
tree_hash: value.tree_id()?.into(),
74
-
parent_hashes: value.parent_ids().map(|id| id.into()).collect(),
75
-
encoding: Cow::Borrowed("UTF-8"),
76
-
extra_headers,
77
-
})
78
-
}
79
-
}
80
-
81
-
impl From<gix::Reference<'_>> for Reference {
82
-
fn from(value: gix::Reference<'_>) -> Self {
83
-
Self {
84
-
name: value.name().shorten().to_string(),
85
-
hash: value.id().into(),
86
-
}
87
-
}
88
-
}
89
-
90
-
impl TryFrom<gix::Tag<'_>> for TagAnnotation {
91
-
type Error = gix::objs::decode::Error;
92
-
93
-
fn try_from(value: gix::Tag<'_>) -> Result<Self, Self::Error> {
94
-
use gix::object::Kind;
95
-
96
-
let hash = value.id.into();
97
-
let decoded = value.decode()?;
98
-
99
-
// cf. <https://github.com/git/git/blob/7014b55638da979331baf8dc31c4e1d697cf2d67/object.h#L97>
100
-
let target_type = match decoded.target_kind {
101
-
Kind::Commit => 1,
102
-
Kind::Tree => 2,
103
-
Kind::Blob => 3,
104
-
Kind::Tag => 4,
105
-
};
106
-
107
-
Ok(Self {
108
-
hash,
109
-
name: decoded.name.to_string(),
110
-
tagger: decoded.tagger.and_then(|tagger| tagger.try_into().ok()),
111
-
message: decoded.message.to_string(),
112
-
signature: decoded.pgp_signature.map(ToString::to_string),
113
-
target_type,
114
-
target: decoded.target().into(),
115
-
})
116
-
}
117
-
}
118
-
119
-
impl TryFrom<gix::Reference<'_>> for Tag {
120
-
type Error = gix::objs::decode::Error;
121
-
122
-
fn try_from(mut value: gix::Reference<'_>) -> Result<Self, Self::Error> {
123
-
let r#ref: Reference = value.clone().into();
124
-
let annotation = value
125
-
.peel_to_tag()
126
-
.ok()
127
-
.map(TryFrom::try_from)
128
-
.transpose()?;
129
-
130
-
Ok(Self { r#ref, annotation })
131
-
}
132
-
}
133
-
134
-
impl From<gix::object::tree::EntryRef<'_, '_>> for TreeEntry {
135
-
fn from(value: gix::object::tree::EntryRef<'_, '_>) -> Self {
136
-
use gix::objs::tree::EntryKind;
137
-
138
-
let name = value.filename().to_string();
139
-
let mode = value.mode();
140
-
141
-
let is_file = mode.is_blob();
142
-
let is_subtree = mode.is_tree();
143
-
144
-
let mode_string = match mode.kind() {
145
-
EntryKind::Tree => "drwxr-xr-x",
146
-
EntryKind::Blob => "-rw-r--r--",
147
-
EntryKind::BlobExecutable => "-rwxr-xr-x",
148
-
EntryKind::Link => "----------",
149
-
EntryKind::Commit => "----------",
150
-
};
151
-
152
-
Self {
153
-
name,
154
-
mode: Cow::Borrowed(mode_string),
155
-
is_file,
156
-
is_subtree,
157
-
..Default::default()
158
-
}
159
-
}
160
-
}
+2
-14
crates/knot/src/model/knot_state.rs
+2
-14
crates/knot/src/model/knot_state.rs
···
1
1
use identity::Did;
2
2
use identity::Resolver;
3
3
use oauth::public_key::PublicKey;
4
-
use serde::Deserialize;
5
4
use std::collections::HashMap;
6
5
use std::path::PathBuf;
7
6
use std::sync::Mutex;
···
51
52
}
52
53
53
54
pub async fn fetch_public_keys(&self, did: &Did) -> anyhow::Result<Vec<PublicKey>> {
55
+
use lexicon::sh::tangled::PublicKey as LexiconPublicKey;
54
56
use url::Url;
55
-
56
-
#[allow(unused)]
57
-
#[derive(Debug, Deserialize)]
58
-
#[serde(rename = "sh.tangled.publicKey", rename_all = "camelCase")]
59
-
pub struct ShTangledPublicKey {
60
-
#[serde(rename = "$type")]
61
-
pub r#type: String,
62
-
pub name: String,
63
-
pub key: String,
64
-
#[serde(alias = "created")]
65
-
pub created_at: String,
66
-
}
67
57
68
58
fn list_records_url(mut pds: Url, collection: &str, repo: &Did) -> Url {
69
59
pds.set_path("/xrpc/com.atproto.repo.listRecords");
···
71
83
.ok_or(anyhow::anyhow!("DID document does not declare a pds"))?
72
84
.service_endpoint;
73
85
74
-
let response: crate::atproto::Response<ShTangledPublicKey> = self
86
+
let response: crate::atproto::Response<LexiconPublicKey> = self
75
87
.public_http
76
88
.get(list_records_url(pds.clone(), "sh.tangled.publicKey", did))
77
89
.send()
+258
crates/knot/src/model/nicediff.rs
+258
crates/knot/src/model/nicediff.rs
···
1
+
use std::borrow::Cow;
2
+
3
+
use crate::types::sh::tangled::repo::diff::{
4
+
Commit, Diff, Line, Name, NiceDiff, Stat, TextFragment,
5
+
};
6
+
use gix::{
7
+
Repository,
8
+
diff::blob::{
9
+
Algorithm, UnifiedDiff,
10
+
pipeline::{Mode, WorktreeRoots},
11
+
unified_diff::{ConsumeHunk, ContextSize, DiffLineKind},
12
+
},
13
+
object::tree::diff::Action,
14
+
};
15
+
16
+
#[derive(Debug, thiserror::Error)]
17
+
enum Error {
18
+
#[error("{0}")]
19
+
Io(#[from] std::io::Error),
20
+
#[error("{0}")]
21
+
Platform(#[from] gix::diff::blob::platform::set_resource::Error),
22
+
#[error("{0}")]
23
+
Diff(#[from] gix::object::blob::diff::lines::Error<SubError>),
24
+
}
25
+
26
+
#[derive(Debug, thiserror::Error)]
27
+
enum SubError {}
28
+
29
+
#[derive(Default)]
30
+
struct DiffHunkConsumer {
31
+
hunks: Vec<TextFragment>,
32
+
}
33
+
34
+
impl DiffHunkConsumer {
35
+
pub fn with_capacity(capacity: usize) -> Self {
36
+
Self {
37
+
hunks: Vec::with_capacity(capacity),
38
+
}
39
+
}
40
+
}
41
+
42
+
impl ConsumeHunk for DiffHunkConsumer {
43
+
type Out = Vec<TextFragment>;
44
+
45
+
fn consume_hunk(
46
+
&mut self,
47
+
header: gix::diff::blob::unified_diff::HunkHeader,
48
+
raw_lines: &[(gix::diff::blob::unified_diff::DiffLineKind, &[u8])],
49
+
) -> std::io::Result<()> {
50
+
let mut lines_added = 0;
51
+
let mut lines_deleted = 0;
52
+
let mut leading_context = 0;
53
+
let mut trailing_context = 0;
54
+
let lines = raw_lines
55
+
.iter()
56
+
.map(|(kind, data)| {
57
+
let line = String::from_utf8_lossy(data).to_string();
58
+
match kind {
59
+
DiffLineKind::Context => {
60
+
*if lines_added == 0 && lines_deleted == 0 {
61
+
&mut leading_context
62
+
} else {
63
+
&mut trailing_context
64
+
} += 1;
65
+
Line::Context { line }
66
+
}
67
+
DiffLineKind::Remove => {
68
+
lines_deleted += 1;
69
+
Line::Addition { line }
70
+
}
71
+
DiffLineKind::Add => {
72
+
lines_added += 1;
73
+
Line::Deletion { line }
74
+
}
75
+
}
76
+
})
77
+
.collect();
78
+
79
+
let hunk = TextFragment {
80
+
comment: "".into(),
81
+
old_position: header.after_hunk_start,
82
+
old_lines: header.after_hunk_len,
83
+
new_position: header.before_hunk_start,
84
+
new_lines: header.before_hunk_len,
85
+
lines,
86
+
lines_added,
87
+
lines_deleted,
88
+
leading_context,
89
+
trailing_context,
90
+
};
91
+
92
+
self.hunks.push(hunk);
93
+
Ok(())
94
+
}
95
+
96
+
fn finish(self) -> Self::Out {
97
+
self.hunks
98
+
}
99
+
}
100
+
101
+
/// Produce a unified diff from the first parent of the specified commit.
102
+
///
103
+
pub fn unified_diff_from_parent<'r>(commit: gix::Commit<'r>) -> anyhow::Result<NiceDiff> {
104
+
let current_tree = commit.tree()?;
105
+
let parent_tree = match commit.parent_ids().next() {
106
+
Some(id) => commit.repo.find_commit(id)?.tree()?,
107
+
// If the commit does not have a parent, compute the diff against an empty tree.
108
+
None => commit.repo.empty_tree(),
109
+
};
110
+
111
+
let (stat, deltas) = unified_diff(&commit.repo, ¤t_tree, &parent_tree)?;
112
+
let decoded = commit.decode()?;
113
+
let diff = NiceDiff {
114
+
commit: Commit {
115
+
message: decoded.message.to_string(),
116
+
author: super::convert::try_convert_signature(decoded.author)?,
117
+
this: commit.id.into(),
118
+
parent: commit.parent_ids().next().map(|id| id.detach().into()),
119
+
pgp_signature: commit
120
+
.signature()?
121
+
.map(|(sig, _)| Cow::Owned(sig.to_string()))
122
+
.unwrap_or(Cow::Borrowed("")),
123
+
committer: super::convert::try_convert_signature(decoded.committer)?,
124
+
tree: current_tree.id.into(),
125
+
change_id: decoded
126
+
.extra_headers()
127
+
.find("change-id")
128
+
.map(|v| Cow::Owned(v.to_string()))
129
+
.unwrap_or(Cow::Borrowed("")),
130
+
},
131
+
stat,
132
+
deltas,
133
+
};
134
+
135
+
Ok(diff)
136
+
}
137
+
138
+
/// Produce a unified diff between two trees.
139
+
///
140
+
pub fn unified_diff<'r>(
141
+
repo: &'r Repository,
142
+
this_tree: &gix::Tree<'r>,
143
+
parent_tree: &gix::Tree<'r>,
144
+
) -> anyhow::Result<(Stat, Vec<Diff>)> {
145
+
let mut cache = repo.diff_resource_cache(Mode::ToGit, WorktreeRoots::default())?;
146
+
147
+
// let mut diffs: BTreeMap<_, Vec<DiffHunk>> = BTreeMap::default();
148
+
let mut deltas: Vec<Diff> = Default::default();
149
+
let mut insertions = 0;
150
+
let mut deletions = 0;
151
+
152
+
let context = 3;
153
+
parent_tree
154
+
.changes()?
155
+
.for_each_to_obtain_tree(&this_tree, |change| {
156
+
use gix::object::tree::diff::Change;
157
+
let mut delta = match change {
158
+
Change::Addition { location, .. } => Diff {
159
+
name: Name {
160
+
old: location.to_string(),
161
+
new: location.to_string(),
162
+
},
163
+
text_fragments: vec![],
164
+
is_binary: false,
165
+
is_new: true,
166
+
is_delete: false,
167
+
is_copy: false,
168
+
is_rename: false,
169
+
},
170
+
Change::Deletion { location, .. } => Diff {
171
+
name: Name {
172
+
old: location.to_string(),
173
+
new: location.to_string(),
174
+
},
175
+
text_fragments: vec![],
176
+
is_binary: false,
177
+
is_new: false,
178
+
is_delete: true,
179
+
is_copy: false,
180
+
is_rename: false,
181
+
},
182
+
Change::Modification { location, .. } => Diff {
183
+
name: Name {
184
+
old: location.to_string(),
185
+
new: location.to_string(),
186
+
},
187
+
text_fragments: vec![],
188
+
is_binary: false,
189
+
is_new: false,
190
+
is_delete: false,
191
+
is_copy: false,
192
+
is_rename: false,
193
+
},
194
+
Change::Rewrite {
195
+
source_location,
196
+
location,
197
+
copy,
198
+
..
199
+
} => Diff {
200
+
name: Name {
201
+
old: source_location.to_string(),
202
+
new: location.to_string(),
203
+
},
204
+
text_fragments: vec![],
205
+
is_binary: false,
206
+
is_new: false,
207
+
is_delete: false,
208
+
is_copy: copy,
209
+
is_rename: !copy,
210
+
},
211
+
};
212
+
213
+
if change.entry_mode().is_blob() {
214
+
let mut line_count = 0;
215
+
let mut platform = change.diff(&mut cache)?;
216
+
let outcome = platform.lines(|_| {
217
+
line_count += 1;
218
+
Ok::<_, SubError>(())
219
+
})?;
220
+
221
+
if matches!(
222
+
outcome.operation,
223
+
gix::diff::blob::platform::prepare_diff::Operation::SourceOrDestinationIsBinary
224
+
) {
225
+
delta.is_binary = true;
226
+
deltas.push(delta);
227
+
return Ok(Action::Continue);
228
+
}
229
+
230
+
let input = outcome.interned_input();
231
+
let hunks = gix::diff::blob::diff(
232
+
Algorithm::Histogram,
233
+
&input,
234
+
UnifiedDiff::new(
235
+
&input,
236
+
DiffHunkConsumer::with_capacity(line_count),
237
+
ContextSize::symmetrical(context),
238
+
),
239
+
)?;
240
+
241
+
tracing::debug!(prealloc = %line_count, outcome = %hunks.len());
242
+
insertions += hunks.iter().map(|hunk| hunk.lines_added).sum::<u32>();
243
+
deletions += hunks.iter().map(|hunk| hunk.lines_deleted).sum::<u32>();
244
+
delta.text_fragments = hunks;
245
+
deltas.push(delta);
246
+
}
247
+
Ok::<_, Error>(Action::Continue)
248
+
})?;
249
+
250
+
let stat = Stat {
251
+
files_changed: deltas.len() as u32,
252
+
insertions,
253
+
deletions,
254
+
};
255
+
256
+
deltas.sort_by_cached_key(|d| d.name.old.clone());
257
+
Ok((stat, deltas))
258
+
}
+12
-18
crates/knot/src/objectid.rs
crates/lexicon/src/extra/objectid.rs
+12
-18
crates/knot/src/objectid.rs
crates/lexicon/src/extra/objectid.rs
···
6
6
#[doc(hidden)]
7
7
pub struct Array;
8
8
9
+
/// An object ID that can serialized as a hex-string or an array of integers.
10
+
///
11
+
/// This only exists because knotserver uses both representations.
12
+
///
9
13
#[derive(Clone, PartialEq, Eq)]
10
14
pub struct ObjectId<E = Hex> {
11
-
inner: gix::ObjectId,
15
+
inner: gix_hash::ObjectId,
12
16
enc: PhantomData<E>,
13
17
}
14
18
15
19
impl<E> ObjectId<E> {
16
-
pub fn id(&self) -> gix::ObjectId {
20
+
pub fn id(&self) -> gix_hash::ObjectId {
17
21
self.inner
18
22
}
19
23
}
···
26
22
#[inline]
27
23
fn default() -> Self {
28
24
Self {
29
-
inner: gix::ObjectId::null(gix::hash::Kind::Sha1),
25
+
inner: gix_hash::ObjectId::null(gix_hash::Kind::Sha1),
30
26
enc: PhantomData,
31
27
}
32
28
}
···
38
34
}
39
35
}
40
36
41
-
impl<E> From<gix::ObjectId> for ObjectId<E> {
37
+
impl<E> From<gix_hash::ObjectId> for ObjectId<E> {
42
38
#[inline]
43
-
fn from(value: gix::ObjectId) -> Self {
39
+
fn from(value: gix_hash::ObjectId) -> Self {
44
40
Self {
45
41
inner: value,
46
42
enc: PhantomData,
···
48
44
}
49
45
}
50
46
51
-
impl<E> From<ObjectId<E>> for gix::ObjectId {
47
+
impl<E> From<ObjectId<E>> for gix_hash::ObjectId {
52
48
#[inline]
53
49
fn from(value: ObjectId<E>) -> Self {
54
50
value.inner
55
-
}
56
-
}
57
-
58
-
impl<E> From<gix::Id<'_>> for ObjectId<E> {
59
-
#[inline]
60
-
fn from(value: gix::Id<'_>) -> Self {
61
-
Self {
62
-
inner: value.detach(),
63
-
enc: PhantomData,
64
-
}
65
51
}
66
52
}
67
53
···
82
88
}
83
89
84
90
impl FromStr for ObjectId<Hex> {
85
-
type Err = gix::hash::decode::Error;
91
+
type Err = gix_hash::decode::Error;
86
92
87
93
fn from_str(s: &str) -> Result<Self, Self::Err> {
88
-
let id = gix::ObjectId::from_hex(s.as_bytes())?;
94
+
let id = gix_hash::ObjectId::from_hex(s.as_bytes())?;
89
95
Ok(Self {
90
96
inner: id,
91
97
enc: PhantomData,
+4
-64
crates/knot/src/public.rs
+4
-64
crates/knot/src/public.rs
···
1
-
use crate::model::Knot;
2
-
use axum::{
3
-
Router,
4
-
extract::{WebSocketUpgrade, ws::WebSocket},
5
-
};
6
-
use std::time::Duration;
7
-
use tokio::time::Instant;
8
-
9
1
pub mod git;
10
2
pub mod xrpc;
11
3
12
-
pub fn router() -> Router<Knot> {
13
-
Router::new()
4
+
use crate::model::Knot;
5
+
6
+
pub fn router() -> axum::Router<Knot> {
7
+
axum::Router::new()
14
8
.without_v07_checks()
15
-
// .route("/events", axum::routing::get(events_handler))
16
9
.nest("/xrpc", xrpc::router())
17
10
.nest("/{owner}/{name}", serve_git::router())
18
-
}
19
-
20
-
pub async fn events_handler(ws: WebSocketUpgrade) -> axum::response::Response {
21
-
ws.on_upgrade(handle_events_socket)
22
-
}
23
-
24
-
pub async fn handle_events_socket(mut socket: WebSocket) {
25
-
use axum::extract::ws::Message;
26
-
27
-
let start = Instant::now();
28
-
let mut timeout = tokio::time::interval(Duration::from_secs(45));
29
-
30
-
// The first tick of an interval always resolves immediately.
31
-
timeout.tick().await;
32
-
33
-
tracing::trace!(?socket, "new websocket connection");
34
-
loop {
35
-
let message = tokio::select! {
36
-
now = timeout.tick() => {
37
-
let diff = now.saturating_duration_since(start);
38
-
let payload = format!("{}", diff.as_millis());
39
-
if let Err(error) = socket.send(Message::Ping(payload.into())).await {
40
-
tracing::error!(?error);
41
-
break;
42
-
}
43
-
continue;
44
-
}
45
-
Some(message) = socket.recv() => match message {
46
-
Ok(message) => message,
47
-
Err(error) => {
48
-
tracing::error!(?error);
49
-
break;
50
-
}
51
-
}
52
-
};
53
-
54
-
match message {
55
-
Message::Pong(payload) => match std::str::from_utf8(&payload) {
56
-
Ok(payload) => tracing::debug!(%payload, "received pong"),
57
-
Err(error) => {
58
-
tracing::error!(?error, ?payload, "received non-utf8 payload in pong")
59
-
}
60
-
},
61
-
Message::Close(_) => {
62
-
tracing::debug!("client closed connection");
63
-
break;
64
-
}
65
-
Message::Text(payload) => {
66
-
tracing::info!(payload = payload.as_str(), "received message from client");
67
-
}
68
-
_ => {} //
69
-
}
70
-
}
71
11
}
-47
crates/knot/src/public/git/helpers.rs
-47
crates/knot/src/public/git/helpers.rs
···
1
-
use std::ffi::OsStr;
2
-
3
-
trait SetEnv {
4
-
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
5
-
where
6
-
K: AsRef<OsStr>,
7
-
V: AsRef<OsStr>;
8
-
}
9
-
10
-
macro_rules! impl_set_env {
11
-
($ty:ty) => {
12
-
impl SetEnv for $ty {
13
-
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
14
-
where
15
-
K: AsRef<OsStr>,
16
-
V: AsRef<OsStr>,
17
-
{
18
-
Self::env(self, key, val)
19
-
}
20
-
}
21
-
};
22
-
}
23
-
24
-
impl_set_env!(std::process::Command);
25
-
impl_set_env!(tokio::process::Command);
26
-
27
-
pub trait SetOptionEnv {
28
-
/// Inserts or updates an environment variable mapping if, and only if,
29
-
/// `val` is [`Some`].
30
-
fn option_env<K, V>(&mut self, key: K, val: Option<V>) -> &mut Self
31
-
where
32
-
K: AsRef<OsStr>,
33
-
V: AsRef<OsStr>;
34
-
}
35
-
36
-
impl<T: SetEnv> SetOptionEnv for T {
37
-
fn option_env<K, V>(&mut self, key: K, val: Option<V>) -> &mut Self
38
-
where
39
-
K: AsRef<OsStr>,
40
-
V: AsRef<OsStr>,
41
-
{
42
-
if let Some(val) = val {
43
-
self.env(key, val);
44
-
}
45
-
self
46
-
}
47
-
}
+4
-1
crates/knot/src/public/xrpc.rs
+4
-1
crates/knot/src/public/xrpc.rs
···
14
14
.merge(sh::tangled::knot::version())
15
15
.merge(sh::tangled::repo::blob())
16
16
.merge(sh::tangled::repo::branches())
17
-
// .merge(sh::tangled::repo::diff())
17
+
.merge(sh::tangled::repo::diff())
18
18
.merge(sh::tangled::repo::get_default_branch())
19
19
.merge(sh::tangled::repo::languages())
20
20
.merge(sh::tangled::repo::log())
···
115
115
ise!(std::io::Error);
116
116
ise!(gix::diff::blob::platform::set_resource::Error);
117
117
ise!(gix::diff::options::init::Error);
118
+
ise!(gix::repository::diff_resource_cache::Error);
118
119
ise!(gix::object::commit::Error);
120
+
ise!(gix::objs::decode::Error);
119
121
ise!(gix::object::find::existing::Error);
120
122
ise!(gix::object::find::existing::with_conversion::Error);
121
123
ise!(gix::object::tree::diff::for_each::Error);
···
125
123
ise!(gix::reference::iter::init::Error);
126
124
ise!(gix::reference::head_tree::Error);
127
125
ise!(gix::reference::find::existing::Error, StatusCode::NOT_FOUND);
126
+
ise!(gix::repository::commit_graph_if_enabled::Error);
+7
-4
crates/knot/src/public/xrpc/sh/tangled.rs
+7
-4
crates/knot/src/public/xrpc/sh/tangled.rs
···
1
-
use crate::{model::Knot, types::sh::tangled::Owner};
1
+
use crate::model::Knot;
2
2
use axum::{
3
3
Json,
4
4
extract::{FromRef, State},
5
5
};
6
+
use lexicon::sh::tangled::OwnerOutput;
6
7
7
8
pub mod knot;
8
9
pub mod repo;
9
10
10
11
#[xrpc::query("sh.tangled.owner")]
11
-
#[tracing::instrument]
12
-
pub async fn owner(State(knot): State<Knot>) -> Json<Owner>
12
+
pub async fn owner(State(knot): State<Knot>) -> Json<OwnerOutput>
13
13
where
14
14
Knot: FromRef<S>,
15
15
{
16
-
Owner::new(knot.owner().clone()).into()
16
+
OwnerOutput {
17
+
owner: knot.owner().clone(),
18
+
}
19
+
.into()
17
20
}
+4
-9
crates/knot/src/public/xrpc/sh/tangled/knot.rs
+4
-9
crates/knot/src/public/xrpc/sh/tangled/knot.rs
···
1
1
use axum::Json;
2
-
3
-
/// Response type for 'sh.tangled.knot.version'.
4
-
#[derive(Debug, serde::Serialize)]
5
-
pub struct Version {
6
-
pub version: &'static str,
7
-
}
2
+
use lexicon::sh::tangled::knot::VersionOutput;
8
3
9
4
#[xrpc::query("sh.tangled.knot.version")]
10
-
pub async fn version() -> Json<Version> {
11
-
Json(Version {
12
-
version: concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
5
+
pub async fn version() -> Json<VersionOutput> {
6
+
Json(VersionOutput {
7
+
version: concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")).into(),
13
8
})
14
9
}
+34
-22
crates/knot/src/public/xrpc/sh/tangled/repo.rs
+34
-22
crates/knot/src/public/xrpc/sh/tangled/repo.rs
···
1
1
use crate::{
2
2
model::{Knot, errors::InvalidRequest},
3
3
public::xrpc::XrpcError,
4
-
types::sh::tangled::repo::*,
4
+
types::sh::tangled::repo::{
5
+
branches::BranchesOutput, diff::DiffResponse, log::LogOutput, tags::TagsOutput,
6
+
},
5
7
};
6
8
use axum::{
7
9
Json,
8
10
extract::{FromRef, Query, State},
9
11
response::{IntoResponse, Response},
10
12
};
13
+
use lexicon::sh::tangled::repo::{
14
+
blob, branches, diff, get_default_branch, languages, log, tags, tree,
15
+
};
11
16
12
17
type Result<T> = std::result::Result<Json<T>, XrpcError>;
13
18
14
19
#[xrpc::query("sh.tangled.repo.getDefaultBranch")]
15
-
#[tracing::instrument]
20
+
#[tracing::instrument(skip(knot))]
16
21
pub async fn get_default_branch(
17
22
State(knot): State<Knot>,
18
-
Query(params): Query<GetDefaultBranchParams>,
19
-
) -> Result<DefaultBranchResponse>
23
+
Query(params): Query<get_default_branch::Input>,
24
+
) -> Result<get_default_branch::Output>
20
25
where
21
26
Knot: FromRef<S>,
22
27
{
···
29
24
}
30
25
31
26
#[xrpc::query("sh.tangled.repo.branches")]
32
-
#[tracing::instrument]
27
+
#[tracing::instrument(skip(knot))]
33
28
pub async fn branches(
34
29
State(knot): State<Knot>,
35
-
Query(params): Query<BranchesParams>,
36
-
) -> Result<BranchesResponse>
30
+
Query(params): Query<branches::Input>,
31
+
) -> Result<BranchesOutput>
37
32
where
38
33
Knot: FromRef<S>,
39
34
{
···
49
44
}
50
45
51
46
#[xrpc::query("sh.tangled.repo.log")]
52
-
#[tracing::instrument]
53
-
pub async fn log(State(knot): State<Knot>, Query(params): Query<LogParams>) -> Result<LogResponse>
47
+
#[tracing::instrument(skip(knot))]
48
+
pub async fn log(State(knot): State<Knot>, Query(params): Query<log::Input>) -> Result<LogOutput>
54
49
where
55
50
Knot: FromRef<S>,
56
51
{
···
66
61
}
67
62
68
63
#[xrpc::query("sh.tangled.repo.tags")]
69
-
#[tracing::instrument]
70
-
pub async fn tags(State(knot): State<Knot>, Query(params): Query<TagParams>) -> Result<TagsResponse>
64
+
#[tracing::instrument(skip(knot))]
65
+
pub async fn tags(State(knot): State<Knot>, Query(params): Query<tags::Input>) -> Result<TagsOutput>
71
66
where
72
67
Knot: FromRef<S>,
73
68
{
···
75
70
}
76
71
77
72
#[xrpc::query("sh.tangled.repo.tree")]
78
-
#[tracing::instrument]
73
+
#[tracing::instrument(skip(knot))]
79
74
pub async fn tree(
80
75
State(knot): State<Knot>,
81
-
Query(params): Query<TreeParams>,
82
-
) -> Result<TreeResponse>
76
+
Query(params): Query<tree::Input>,
77
+
) -> Result<tree::Output>
83
78
where
84
79
Knot: FromRef<S>,
85
80
{
···
87
82
}
88
83
89
84
#[xrpc::query("sh.tangled.repo.blob")]
90
-
#[tracing::instrument]
85
+
#[tracing::instrument(skip(knot))]
91
86
pub async fn blob(
92
87
State(knot): State<Knot>,
93
-
Query(params): Query<BlobParams>,
88
+
Query(params): Query<blob::Input>,
94
89
) -> std::result::Result<Response, XrpcError>
95
90
where
96
91
Knot: FromRef<S>,
97
92
{
93
+
use crate::types::sh::tangled::repo::blob::BlobResponse;
98
94
match knot.blob(¶ms).await? {
99
95
BlobResponse::Json(blob) => Ok(Json(blob).into_response()),
100
96
BlobResponse::Raw(blob) => Ok(blob.into_response()),
···
103
97
}
104
98
105
99
#[xrpc::query("sh.tangled.repo.languages")]
106
-
#[tracing::instrument]
107
-
pub async fn languages() -> Json<Languages> {
100
+
#[tracing::instrument(skip(_knot))]
101
+
pub async fn languages(
102
+
State(_knot): State<Knot>,
103
+
Query(params): Query<languages::Input>,
104
+
) -> Json<languages::Output>
105
+
where
106
+
Knot: FromRef<S>,
107
+
{
108
108
Default::default()
109
109
}
110
110
111
111
#[xrpc::query("sh.tangled.repo.diff")]
112
-
#[tracing::instrument]
113
-
pub async fn diff(
112
+
#[tracing::instrument(skip(knot))]
113
+
pub async fn diff<S>(
114
114
State(knot): State<Knot>,
115
-
Query(params): Query<DiffParams>,
115
+
Query(params): Query<diff::Input>,
116
116
) -> Result<DiffResponse>
117
117
where
118
118
Knot: FromRef<S>,
119
119
{
120
-
Ok(knot.diff(¶ms).await?.into())
120
+
Ok(knot.diff(params).await?.into())
121
121
}
-250
crates/knot/src/repospec.rs
-250
crates/knot/src/repospec.rs
···
1
-
use serde::{Deserialize, Serialize};
2
-
use std::str::FromStr;
3
-
4
-
/// Repository path specifier.
5
-
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
6
-
#[serde(try_from = "UnvalidatedRepoSpec")]
7
-
pub struct RepoSpec {
8
-
/// DID of the repository owner
9
-
pub owner: Box<str>,
10
-
11
-
/// Repository name
12
-
pub name: Box<str>,
13
-
}
14
-
15
-
#[derive(Deserialize)]
16
-
struct UnvalidatedRepoSpec {
17
-
pub owner: Box<str>,
18
-
pub name: Box<str>,
19
-
}
20
-
21
-
impl TryFrom<UnvalidatedRepoSpec> for RepoSpec {
22
-
type Error = RepoSpecError;
23
-
24
-
#[inline]
25
-
fn try_from(
26
-
UnvalidatedRepoSpec { owner, name }: UnvalidatedRepoSpec,
27
-
) -> Result<Self, Self::Error> {
28
-
Self::from_parts(owner, name)
29
-
}
30
-
}
31
-
32
-
impl RepoSpec {
33
-
pub fn from_parts(
34
-
owner: impl Into<Box<str>>,
35
-
name: impl Into<Box<str>>,
36
-
) -> Result<Self, RepoSpecError> {
37
-
let owner = owner.into();
38
-
let name = name.into();
39
-
40
-
validate(owner.as_ref())?;
41
-
validate(name.as_ref())?;
42
-
43
-
Ok(Self { owner, name })
44
-
}
45
-
46
-
pub const fn owner(&self) -> &str {
47
-
&self.owner
48
-
}
49
-
50
-
pub const fn name(&self) -> &str {
51
-
&self.name
52
-
}
53
-
}
54
-
55
-
impl std::fmt::Display for RepoSpec {
56
-
#[inline]
57
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58
-
write!(f, "{}/{}", self.owner, self.name)
59
-
}
60
-
}
61
-
62
-
impl FromStr for RepoSpec {
63
-
type Err = RepoSpecError;
64
-
65
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
66
-
let Some((owner, name)) = s.split_once('/') else {
67
-
return Err(RepoSpecError::Format);
68
-
};
69
-
70
-
validate(owner)?;
71
-
validate(name)?;
72
-
73
-
Ok(Self {
74
-
owner: owner.into(),
75
-
name: name.into(),
76
-
})
77
-
}
78
-
}
79
-
80
-
pub mod query_param {
81
-
//! Serialize/Deserialize a `RepoSpec` from a string.
82
-
//!
83
-
//! # Example
84
-
//!
85
-
//! ```rust,no_run
86
-
//! # use knot::repospec::RepoSpec;
87
-
//! #[derive(serde::Deserialize)]
88
-
//! struct Params {
89
-
//! #[serde(with = "knot::repospec::query_param")]
90
-
//! repo: RepoSpec,
91
-
//! }
92
-
//! ```
93
-
//!
94
-
use super::RepoSpec;
95
-
use serde::{Deserialize, Deserializer, Serializer};
96
-
use std::{borrow::Cow, str::FromStr};
97
-
98
-
pub fn deserialize<'de, D>(deserializer: D) -> Result<RepoSpec, D::Error>
99
-
where
100
-
D: Deserializer<'de>,
101
-
{
102
-
let s = <Cow<str>>::deserialize(deserializer)?;
103
-
let repo = RepoSpec::from_str(&s).map_err(serde::de::Error::custom)?;
104
-
Ok(repo)
105
-
}
106
-
107
-
#[allow(unused)]
108
-
pub fn serialize<S>(v: &RepoSpec, serializer: S) -> Result<S::Ok, S::Error>
109
-
where
110
-
S: Serializer,
111
-
{
112
-
serializer.serialize_str(&format!("{}/{}", v.owner(), v.name()))
113
-
}
114
-
}
115
-
116
-
fn validate(name: &str) -> Result<(), RepoSpecError> {
117
-
static FORBIDDEN_SUBSTRINGS: &[&str] = &[".."];
118
-
static FORBIDDEN_PREFIXES: &[&str] = &[".", "-"];
119
-
static ACCEPTED_SYMBOLS: &[char] = &['.', '-', '_', ':'];
120
-
121
-
if name.is_empty() {
122
-
return Err(RepoSpecError::Empty);
123
-
}
124
-
125
-
for value in name.chars() {
126
-
if !(value.is_ascii_alphanumeric() || ACCEPTED_SYMBOLS.contains(&value)) {
127
-
return Err(RepoSpecError::Character { value });
128
-
}
129
-
}
130
-
131
-
for substring in FORBIDDEN_SUBSTRINGS {
132
-
if name.contains(substring) {
133
-
return Err(RepoSpecError::Substring { substring });
134
-
}
135
-
}
136
-
137
-
for prefix in FORBIDDEN_PREFIXES {
138
-
if name.starts_with(prefix) {
139
-
return Err(RepoSpecError::Prefix { prefix });
140
-
}
141
-
}
142
-
143
-
Ok(())
144
-
}
145
-
146
-
#[derive(Debug, thiserror::Error)]
147
-
pub enum RepoSpecError {
148
-
#[error("expected a repository specifier in form '{{owner}}/{{name}}'")]
149
-
Format,
150
-
#[error("repository name cannot be empty")]
151
-
Empty,
152
-
#[error("invalid character in repository name: '{value}'")]
153
-
Character { value: char },
154
-
#[error("invalid sub-string in repository name: '{substring}'")]
155
-
Substring { substring: &'static str },
156
-
#[error("invalid prefix for respository name: '{prefix}'")]
157
-
Prefix { prefix: &'static str },
158
-
}
159
-
160
-
#[cfg(test)]
161
-
mod tests {
162
-
use axum::{
163
-
Json, Router,
164
-
body::Body,
165
-
extract::{Path, Query},
166
-
http::{Request, Response},
167
-
routing,
168
-
};
169
-
use http_body_util::BodyExt as _;
170
-
use serde::Deserialize;
171
-
use tower::ServiceExt as _;
172
-
173
-
use super::RepoSpec;
174
-
175
-
const SAMPLE_OWNER: &str = "did:plc:65gha4t3avpfpzmvpbwovss7";
176
-
const SAMPLE_NAME: &str = "gordian";
177
-
178
-
async fn check_response(res: Response<Body>) {
179
-
let body = res.into_body().collect().await.unwrap();
180
-
let repo: RepoSpec = serde_json::from_slice(&body.to_bytes()).unwrap();
181
-
182
-
assert_eq!(repo.owner.as_ref(), SAMPLE_OWNER);
183
-
assert_eq!(repo.name.as_ref(), SAMPLE_NAME);
184
-
}
185
-
186
-
#[tokio::test]
187
-
async fn extract_from_query() {
188
-
#[derive(Deserialize)]
189
-
struct Params {
190
-
#[serde(with = "super::query_param")]
191
-
repo: RepoSpec,
192
-
}
193
-
194
-
let res = Router::new()
195
-
.route(
196
-
"/something",
197
-
routing::get(async |Query(Params { repo }): Query<Params>| Json(repo)),
198
-
)
199
-
.oneshot(
200
-
Request::builder()
201
-
.uri(format!("/something?repo={SAMPLE_OWNER}/{SAMPLE_NAME}"))
202
-
.body(Body::empty())
203
-
.unwrap(),
204
-
)
205
-
.await
206
-
.unwrap();
207
-
208
-
check_response(res).await;
209
-
}
210
-
211
-
#[tokio::test]
212
-
async fn extract_from_path() {
213
-
let res = Router::new()
214
-
.route(
215
-
"/{owner}/{name}",
216
-
routing::get(async |Path(repo): Path<RepoSpec>| Json(repo)),
217
-
)
218
-
.oneshot(
219
-
Request::builder()
220
-
.uri(format!("/{SAMPLE_OWNER}/{SAMPLE_NAME}"))
221
-
.body(Body::empty())
222
-
.unwrap(),
223
-
)
224
-
.await
225
-
.unwrap();
226
-
227
-
check_response(res).await;
228
-
}
229
-
230
-
#[tokio::test]
231
-
async fn extract_from_path_backwards() {
232
-
// Ensure we are extracting placeholders by name not position.
233
-
234
-
let res = Router::new()
235
-
.route(
236
-
"/{name}/{owner}",
237
-
routing::get(async |Path(repo): Path<RepoSpec>| Json(repo)),
238
-
)
239
-
.oneshot(
240
-
Request::builder()
241
-
.uri(format!("/{SAMPLE_NAME}/{SAMPLE_OWNER}"))
242
-
.body(Body::empty())
243
-
.unwrap(),
244
-
)
245
-
.await
246
-
.unwrap();
247
-
248
-
check_response(res).await;
249
-
}
250
-
}
-23
crates/knot/src/revspec.rs
-23
crates/knot/src/revspec.rs
···
1
-
use axum::{
2
-
extract::{FromRequestParts, Query, rejection::QueryRejection},
3
-
http::request::Parts,
4
-
};
5
-
use serde::Deserialize;
6
-
7
-
#[derive(Debug, Deserialize)]
8
-
pub struct RevSpec {
9
-
pub r#ref: String,
10
-
}
11
-
12
-
impl<S: Send + Sync> FromRequestParts<S> for RevSpec {
13
-
type Rejection = QueryRejection;
14
-
15
-
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
16
-
let Query(revspec) = Query::from_request_parts(parts, state).await?;
17
-
Ok(revspec)
18
-
}
19
-
}
20
-
21
-
impl RevSpec {
22
-
//
23
-
}
+176
-343
crates/knot/src/types.rs
+176
-343
crates/knot/src/types.rs
···
1
1
pub mod sh {
2
2
pub mod tangled {
3
-
use identity::Did;
4
-
use serde::{Deserialize, Serialize};
5
-
use time::OffsetDateTime;
6
-
7
-
/// Response for `sh.tangled.owner`.
8
-
#[derive(Debug, Serialize)]
9
-
pub struct Owner {
10
-
pub owner: Did,
11
-
}
12
-
13
-
impl Owner {
14
-
pub fn new(owner: Did) -> Self {
15
-
Self { owner }
16
-
}
17
-
}
18
-
19
-
/// Lexicon definition `sh.tangled.repo`
20
-
#[derive(Debug, Deserialize)]
21
-
#[serde(rename_all = "camelCase")]
22
-
pub struct Repo {
23
-
#[serde(rename = "$type")]
24
-
pub typ: String,
25
-
pub knot: String,
26
-
pub name: String,
27
-
#[serde(default)]
28
-
pub labels: Vec<String>,
29
-
#[serde(with = "time::serde::rfc3339")]
30
-
pub created_at: OffsetDateTime,
31
-
}
32
-
33
3
pub mod repo {
34
-
use crate::{ObjectId, objectid::Array, repospec::RepoSpec};
35
-
use serde::{Deserialize, Serialize};
36
-
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
37
-
use time::OffsetDateTime;
4
+
pub mod blob {
5
+
use lexicon::sh::tangled::repo::blob;
38
6
39
-
#[derive(Debug, Deserialize)]
40
-
pub struct GetDefaultBranchParams {
41
-
#[serde(with = "crate::repospec::query_param")]
42
-
pub repo: RepoSpec,
7
+
#[derive(Debug)]
8
+
pub enum BlobResponse {
9
+
Json(blob::Output),
10
+
Raw(Vec<u8>),
11
+
}
43
12
}
44
13
45
-
/// Response type for the `sh.tangled.repo.getDefaultBranch` query.
46
-
#[derive(Debug, Default, Serialize)]
47
-
#[serde(rename_all = "camelCase")]
48
-
pub struct DefaultBranchResponse {
49
-
/// Short-name of the default branch.
50
-
pub name: String,
14
+
pub mod branches {
15
+
use lexicon::sh::tangled::repo::refs;
16
+
use serde::Serialize;
51
17
52
-
/// ID of the most recent commit on the default branch
53
-
//
54
-
// @NOTE Official knotserver always returns an empty string.
55
-
#[serde(skip_serializing_if = "Option::is_none")]
56
-
pub hash: Option<ObjectId>,
18
+
/// Output of `sh.tangled.repo.branches` query.
19
+
#[derive(Debug, Default, Serialize)]
20
+
#[serde(rename_all = "camelCase")]
21
+
pub struct BranchesOutput {
22
+
#[serde(skip_serializing_if = "Vec::is_empty")]
23
+
pub branches: Vec<Branch>,
24
+
}
57
25
58
-
/// Timestamp of the most recent commit on the default branch
59
-
//
60
-
// @NOTE Official knotserver always returns the unix epoch.
61
-
#[serde(
62
-
with = "time::serde::rfc3339::option",
63
-
skip_serializing_if = "Option::is_none"
64
-
)]
65
-
pub when: Option<OffsetDateTime>,
26
+
#[derive(Debug, Serialize)]
27
+
#[serde(rename_all = "camelCase")]
28
+
pub struct Branch {
29
+
pub reference: refs::Reference,
30
+
pub commit: refs::Commit,
31
+
#[serde(rename = "is_deafult")]
32
+
pub is_default: bool,
33
+
}
66
34
}
67
35
68
-
#[derive(Debug, Default, Serialize)]
69
-
#[serde(rename_all = "camelCase")]
70
-
pub struct Reference {
71
-
/// Short-name of the reference.
72
-
pub name: String,
36
+
pub mod diff {
37
+
use lexicon::{extra::objectid::ObjectId, sh::tangled::repo::refs};
38
+
use serde::Serialize;
39
+
use std::borrow::Cow;
73
40
74
-
// @TODO What is this for?
75
-
pub hash: ObjectId,
41
+
#[derive(Debug, Serialize)]
42
+
pub struct DiffResponse {
43
+
#[serde(rename = "ref")]
44
+
pub rev: Box<str>,
45
+
pub diff: NiceDiff,
46
+
}
47
+
48
+
/// Unified diff replicating Tangled's `NiceDiff` structure.
49
+
///
50
+
/// See: <https://tangled.org/@tangled.org/core/blob/master/types/diff.go#L44>
51
+
#[derive(Debug, Serialize)]
52
+
pub struct NiceDiff {
53
+
pub commit: Commit,
54
+
pub stat: Stat,
55
+
#[serde(rename = "diff")]
56
+
pub deltas: Vec<Diff>,
57
+
}
58
+
59
+
#[derive(Debug, Serialize)]
60
+
pub struct Commit {
61
+
pub message: String,
62
+
pub author: refs::Signature,
63
+
pub this: ObjectId,
64
+
#[serde(skip_serializing_if = "Option::is_none")]
65
+
pub parent: Option<ObjectId>,
66
+
#[serde(default)]
67
+
pub pgp_signature: Cow<'static, str>,
68
+
pub committer: refs::Signature,
69
+
pub tree: ObjectId,
70
+
#[serde(default)]
71
+
pub change_id: Cow<'static, str>,
72
+
}
73
+
74
+
#[derive(Debug, Default, Serialize)]
75
+
pub struct Stat {
76
+
pub files_changed: u32,
77
+
pub insertions: u32,
78
+
pub deletions: u32,
79
+
}
80
+
81
+
#[derive(Debug, Default, Serialize)]
82
+
pub struct Diff {
83
+
pub name: Name,
84
+
pub text_fragments: Vec<TextFragment>,
85
+
pub is_binary: bool,
86
+
pub is_new: bool,
87
+
pub is_delete: bool,
88
+
pub is_copy: bool,
89
+
pub is_rename: bool,
90
+
}
91
+
92
+
#[derive(Debug, Default, Serialize)]
93
+
pub struct Name {
94
+
pub old: String,
95
+
pub new: String,
96
+
}
97
+
98
+
#[derive(Debug, Default, Serialize)]
99
+
#[serde(rename_all = "PascalCase")]
100
+
pub struct TextFragment {
101
+
#[serde(skip_serializing_if = "str::is_empty")]
102
+
pub comment: Cow<'static, str>,
103
+
pub old_position: u32,
104
+
pub old_lines: u32,
105
+
pub new_position: u32,
106
+
pub new_lines: u32,
107
+
pub lines_added: u32,
108
+
pub lines_deleted: u32,
109
+
pub leading_context: u32,
110
+
pub trailing_context: u32,
111
+
#[serde(skip_serializing_if = "Vec::is_empty")]
112
+
pub lines: Vec<Line>,
113
+
}
114
+
115
+
#[derive(Debug)]
116
+
pub enum Line {
117
+
Context { line: String },
118
+
Addition { line: String },
119
+
Deletion { line: String },
120
+
}
121
+
122
+
// Manual impl because this enum is tagged with an integer.
123
+
impl Serialize for Line {
124
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
125
+
where
126
+
S: serde::Serializer,
127
+
{
128
+
#[derive(Serialize)]
129
+
#[serde(rename_all = "PascalCase")]
130
+
struct IntTagged<'a> {
131
+
op: u8,
132
+
line: &'a str,
133
+
}
134
+
135
+
match self {
136
+
Self::Context { line } => IntTagged { op: 0, line },
137
+
Self::Addition { line } => IntTagged { op: 1, line },
138
+
Self::Deletion { line } => IntTagged { op: 2, line },
139
+
}
140
+
.serialize(serializer)
141
+
}
142
+
}
76
143
}
77
144
78
-
/// Git commit signature (ie. the author or committer).
79
-
#[derive(Debug, Serialize)]
80
-
#[serde(rename_all = "PascalCase")]
81
-
pub struct Signature {
82
-
pub name: String,
83
-
pub email: String,
84
-
#[serde(with = "time::serde::rfc3339")]
85
-
pub when: OffsetDateTime,
145
+
pub mod log {
146
+
use lexicon::sh::tangled::repo::refs;
147
+
use serde::Serialize;
148
+
149
+
#[derive(Debug, Serialize)]
150
+
pub struct LogOutput {
151
+
pub commits: Vec<refs::Commit>,
152
+
pub log: bool,
153
+
pub total: usize,
154
+
pub page: usize,
155
+
pub per_page: u16,
156
+
}
86
157
}
87
158
88
-
#[derive(Debug, Serialize)]
89
-
#[serde(rename_all = "PascalCase")]
90
-
pub struct Commit {
91
-
pub hash: ObjectId<Array>,
92
-
pub author: Signature,
93
-
pub committer: Signature,
94
-
pub merge_tag: String,
95
-
#[serde(rename = "PGPSignature")]
96
-
pub signature: Option<String>,
97
-
pub message: String,
98
-
pub tree_hash: ObjectId<Array>,
99
-
pub parent_hashes: Vec<ObjectId<Array>>,
100
-
pub encoding: Cow<'static, str>,
101
-
/// Non-standard object headers. Values are always base64 encoded.
102
-
pub extra_headers: HashMap<String, String>,
103
-
}
159
+
pub mod tags {
160
+
use lexicon::{
161
+
extra::objectid::{Array, ObjectId},
162
+
sh::tangled::repo::refs,
163
+
};
164
+
use serde::Serialize;
104
165
105
-
#[derive(Debug, Serialize)]
106
-
#[serde(rename_all = "camelCase")]
107
-
pub struct Branch {
108
-
pub reference: Reference,
109
-
pub commit: Commit,
110
-
#[serde(rename = "is_deafult")]
111
-
pub is_default: bool,
112
-
}
166
+
/// Output of `sh.tangled.repo.tags` query.
167
+
///
168
+
/// This is not defined in the lexicon, but models what knotserver currently
169
+
/// produces.
170
+
#[derive(Debug, Serialize)]
171
+
pub struct TagsOutput {
172
+
pub tags: Vec<Tag>,
173
+
}
113
174
114
-
#[derive(Debug, Deserialize)]
115
-
pub struct BranchesParams {
116
-
#[serde(with = "crate::repospec::query_param")]
117
-
pub repo: RepoSpec,
118
-
#[serde(default = "branches_limit_default")]
119
-
pub limit: u16,
120
-
#[serde(default)]
121
-
pub cursor: usize,
122
-
}
175
+
#[derive(Debug, Serialize)]
176
+
#[serde(rename_all = "PascalCase")]
177
+
pub struct TagAnnotation {
178
+
pub hash: ObjectId<Array>,
179
+
pub name: String,
180
+
pub tagger: Option<refs::Signature>,
181
+
pub message: String,
182
+
#[serde(rename = "PGPSignature")]
183
+
pub pgp_signature: Option<String>,
184
+
pub target_type: i32,
185
+
pub target: ObjectId<Array>,
186
+
}
123
187
124
-
const fn branches_limit_default() -> u16 {
125
-
50
126
-
}
127
-
128
-
#[derive(Debug, Default, Serialize)]
129
-
#[serde(rename_all = "camelCase")]
130
-
pub struct BranchesResponse {
131
-
#[serde(skip_serializing_if = "Vec::is_empty")]
132
-
pub branches: Vec<Branch>,
133
-
}
134
-
135
-
#[derive(Debug, serde::Serialize)]
136
-
pub struct LogResponse {
137
-
pub commits: Vec<Commit>,
138
-
pub log: bool,
139
-
pub total: usize,
140
-
pub page: usize,
141
-
pub per_page: u16,
142
-
}
143
-
144
-
#[derive(Debug, Deserialize)]
145
-
pub struct LogParams {
146
-
#[serde(with = "crate::repospec::query_param")]
147
-
pub repo: RepoSpec,
148
-
#[serde(rename = "ref")]
149
-
pub rev: Option<String>,
150
-
// @NOTE The lexicon states `cursor` is a string containing a pagination
151
-
// cursor of a commit id. AppView actually gives us an integer for
152
-
// the number of commits to skip.
153
-
#[serde(default)]
154
-
pub cursor: usize,
155
-
#[serde(default = "log_limit_default")]
156
-
pub limit: u16,
157
-
}
158
-
159
-
const fn log_limit_default() -> u16 {
160
-
50
161
-
}
162
-
163
-
#[derive(Debug, Deserialize)]
164
-
pub struct TagParams {
165
-
#[serde(with = "crate::repospec::query_param")]
166
-
pub repo: RepoSpec,
167
-
#[serde(default)]
168
-
pub cursor: usize,
169
-
#[serde(default = "tag_limit_default")]
170
-
pub limit: usize,
171
-
}
172
-
173
-
const fn tag_limit_default() -> usize {
174
-
50
175
-
}
176
-
177
-
#[derive(Debug, Serialize)]
178
-
#[serde(rename_all = "PascalCase")]
179
-
pub struct TagAnnotation {
180
-
pub hash: ObjectId<Array>,
181
-
pub name: String,
182
-
pub tagger: Option<Signature>,
183
-
pub message: String,
184
-
#[serde(rename = "PGPSignature")]
185
-
pub signature: Option<String>,
186
-
pub target_type: i32,
187
-
pub target: ObjectId<Array>,
188
-
}
189
-
190
-
#[derive(Debug, Serialize)]
191
-
pub struct Tag {
192
-
#[serde(flatten)]
193
-
pub r#ref: Reference,
194
-
#[serde(rename = "tag", skip_serializing_if = "Option::is_none")]
195
-
pub annotation: Option<TagAnnotation>,
196
-
}
197
-
198
-
#[derive(Debug, Serialize)]
199
-
pub struct TagsResponse {
200
-
pub tags: Vec<Tag>,
201
-
}
202
-
203
-
#[derive(Debug, Default, Serialize)]
204
-
pub struct TreeEntry {
205
-
pub name: String,
206
-
pub mode: Cow<'static, str>,
207
-
pub size: usize,
208
-
pub is_file: bool,
209
-
pub is_subtree: bool,
210
-
}
211
-
212
-
#[derive(Debug, Deserialize)]
213
-
pub struct TreeParams {
214
-
#[serde(with = "crate::repospec::query_param")]
215
-
pub repo: RepoSpec,
216
-
#[serde(rename = "ref")]
217
-
pub rev: Option<String>,
218
-
pub path: Option<PathBuf>,
219
-
}
220
-
221
-
#[derive(Debug, Serialize)]
222
-
pub struct Readme {
223
-
pub contents: String,
224
-
pub filename: String,
225
-
}
226
-
227
-
/// Return type for `sh.tangled.repo.tree`.
228
-
#[derive(Debug, Serialize)]
229
-
pub struct TreeResponse {
230
-
pub files: Vec<TreeEntry>,
231
-
#[serde(skip_serializing_if = "Option::is_none")]
232
-
pub dotdot: Option<PathBuf>,
233
-
#[serde(skip_serializing_if = "Option::is_none")]
234
-
pub parent: Option<PathBuf>,
235
-
#[serde(rename = "ref")]
236
-
pub rev: String,
237
-
#[serde(skip_serializing_if = "Option::is_none")]
238
-
pub readme: Option<Readme>,
239
-
}
240
-
241
-
#[derive(Debug, Deserialize)]
242
-
pub struct BlobParams {
243
-
#[serde(with = "crate::repospec::query_param")]
244
-
pub repo: RepoSpec,
245
-
#[serde(rename = "ref")]
246
-
pub rev: Option<String>,
247
-
pub path: PathBuf,
248
-
#[serde(default)]
249
-
pub raw: bool,
250
-
}
251
-
252
-
#[derive(Debug, Serialize)]
253
-
pub enum BlobEncoding {
254
-
#[serde(rename = "utf-8")]
255
-
Utf8,
256
-
#[serde(rename = "base64")]
257
-
Base64,
258
-
}
259
-
260
-
#[derive(Debug, Serialize)]
261
-
#[serde(rename_all = "camelCase")]
262
-
pub struct JsonBlob {
263
-
pub content: String,
264
-
pub encoding: BlobEncoding,
265
-
pub is_binary: bool,
266
-
pub mime_type: String,
267
-
pub path: PathBuf,
268
-
#[serde(rename = "ref")]
269
-
pub rev: String,
270
-
pub size: usize,
271
-
}
272
-
273
-
#[derive(Debug)]
274
-
pub enum BlobResponse {
275
-
Json(JsonBlob),
276
-
Raw(Vec<u8>),
277
-
}
278
-
279
-
#[derive(Debug, Default, Serialize)]
280
-
#[serde(rename_all = "PascalCase")]
281
-
pub struct DiffLine {
282
-
pub op: u8,
283
-
pub line: String,
284
-
}
285
-
286
-
#[derive(Debug, Default, Serialize)]
287
-
#[serde(rename_all = "PascalCase")]
288
-
pub struct DiffHunk {
289
-
// comment: String,
290
-
pub old_position: u32,
291
-
pub old_lines: u32,
292
-
pub new_position: u32,
293
-
pub new_lines: u32,
294
-
pub lines_added: u32,
295
-
pub lines_deleted: u32,
296
-
pub leading_context: u32,
297
-
pub trailing_context: u32,
298
-
pub lines: Vec<DiffLine>,
299
-
}
300
-
301
-
#[derive(Debug, Default, Serialize)]
302
-
pub struct Delta {
303
-
pub name: DeltaName,
304
-
#[serde(rename = "text_fragments")]
305
-
pub hunks: Vec<DiffHunk>,
306
-
pub is_binary: bool,
307
-
pub is_new: bool,
308
-
pub is_delete: bool,
309
-
pub is_copy: bool,
310
-
pub is_rename: bool,
311
-
}
312
-
313
-
#[derive(Debug, Default, Serialize)]
314
-
pub struct DiffStat {
315
-
pub files_changed: usize,
316
-
pub insertions: usize,
317
-
pub deletions: usize,
318
-
}
319
-
320
-
#[derive(Debug, Default, Serialize)]
321
-
pub struct DeltaName {
322
-
pub old: String,
323
-
pub new: String,
324
-
}
325
-
326
-
#[derive(Debug, Serialize)]
327
-
pub struct Diff {
328
-
pub commit: Commit,
329
-
pub stat: DiffStat,
330
-
#[serde(rename = "diff")]
331
-
pub deltas: Vec<Delta>,
332
-
}
333
-
334
-
#[derive(Debug, Deserialize)]
335
-
pub struct DiffParams {
336
-
#[serde(with = "crate::repospec::query_param")]
337
-
pub repo: RepoSpec,
338
-
#[serde(rename = "ref")]
339
-
pub rev: String,
340
-
}
341
-
342
-
#[derive(Debug, Serialize)]
343
-
pub struct DiffResponse {
344
-
#[serde(rename = "ref")]
345
-
pub rev: String,
346
-
pub diff: Diff,
347
-
}
348
-
349
-
#[derive(Debug, Default, Serialize)]
350
-
#[serde(rename_all = "camelCase")]
351
-
pub struct Languages {
352
-
#[serde(skip_serializing_if = "Vec::is_empty")]
353
-
pub languages: Vec<()>,
354
-
}
355
-
356
-
// Mutations
357
-
358
-
#[derive(Debug, Deserialize)]
359
-
#[serde(rename_all = "camelCase")]
360
-
pub struct CreateParams {
361
-
pub rkey: String,
188
+
#[derive(Debug, Serialize)]
189
+
pub struct Tag {
190
+
#[serde(flatten)]
191
+
pub r#ref: refs::Reference,
192
+
#[serde(rename = "tag", skip_serializing_if = "Option::is_none")]
193
+
pub annotation: Option<TagAnnotation>,
194
+
}
362
195
}
363
196
}
364
197
}
+18
crates/lexicon/Cargo.toml
+18
crates/lexicon/Cargo.toml
···
1
+
[package]
2
+
name = "lexicon"
3
+
version.workspace = true
4
+
authors.workspace = true
5
+
repository.workspace = true
6
+
license.workspace = true
7
+
edition.workspace = true
8
+
publish.workspace = true
9
+
10
+
[dependencies]
11
+
identity.workspace = true
12
+
13
+
data-encoding.workspace = true
14
+
serde.workspace = true
15
+
thiserror.workspace = true
16
+
time.workspace = true
17
+
18
+
gix-hash = "^0.20.0"
+157
crates/lexicon/src/extra/repopath.rs
+157
crates/lexicon/src/extra/repopath.rs
···
1
+
use serde::{Deserialize, Serialize};
2
+
use std::str::FromStr;
3
+
4
+
/// Repository path specifier.
5
+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
6
+
#[serde(try_from = "UnvalidatedRepoPath")]
7
+
pub struct RepoPath {
8
+
/// Repository owner
9
+
pub owner: Box<str>,
10
+
11
+
/// Repository name
12
+
pub name: Box<str>,
13
+
}
14
+
15
+
#[derive(Deserialize)]
16
+
struct UnvalidatedRepoPath {
17
+
owner: Box<str>,
18
+
name: Box<str>,
19
+
}
20
+
21
+
impl TryFrom<UnvalidatedRepoPath> for RepoPath {
22
+
type Error = RepoPathError;
23
+
24
+
#[inline]
25
+
fn try_from(
26
+
UnvalidatedRepoPath { owner, name }: UnvalidatedRepoPath,
27
+
) -> Result<Self, Self::Error> {
28
+
Self::from_parts(owner, name)
29
+
}
30
+
}
31
+
32
+
impl RepoPath {
33
+
pub fn from_parts(
34
+
owner: impl Into<Box<str>>,
35
+
name: impl Into<Box<str>>,
36
+
) -> Result<Self, RepoPathError> {
37
+
fn inner(owner: Box<str>, name: Box<str>) -> Result<RepoPath, RepoPathError> {
38
+
validate(owner.as_ref())?;
39
+
validate(name.as_ref())?;
40
+
Ok(RepoPath { owner, name })
41
+
}
42
+
inner(owner.into(), name.into())
43
+
}
44
+
45
+
pub const fn owner(&self) -> &str {
46
+
&self.owner
47
+
}
48
+
49
+
pub const fn name(&self) -> &str {
50
+
&self.name
51
+
}
52
+
}
53
+
54
+
impl std::fmt::Display for RepoPath {
55
+
#[inline]
56
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57
+
write!(f, "{}/{}", self.owner, self.name)
58
+
}
59
+
}
60
+
61
+
impl FromStr for RepoPath {
62
+
type Err = RepoPathError;
63
+
64
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
65
+
let Some((owner, name)) = s.split_once('/') else {
66
+
return Err(RepoPathError::Format);
67
+
};
68
+
69
+
validate(owner)?;
70
+
validate(name)?;
71
+
72
+
Ok(Self {
73
+
owner: owner.into(),
74
+
name: name.into(),
75
+
})
76
+
}
77
+
}
78
+
79
+
pub mod string {
80
+
//! Serialize/Deserialize a `RepoSpec` from a string.
81
+
//!
82
+
//! # Example
83
+
//!
84
+
//! ```rust,no_run
85
+
//! # use lexicon::extra::repopath::RepoPath;
86
+
//! #[derive(serde::Deserialize)]
87
+
//! struct Params {
88
+
//! #[serde(with = "lexicon::extra::repopath::string")]
89
+
//! repo: RepoPath,
90
+
//! }
91
+
//! ```
92
+
//!
93
+
use super::RepoPath;
94
+
use serde::{Deserialize, Deserializer, Serializer};
95
+
use std::{borrow::Cow, str::FromStr};
96
+
97
+
pub fn deserialize<'de, D>(deserializer: D) -> Result<RepoPath, D::Error>
98
+
where
99
+
D: Deserializer<'de>,
100
+
{
101
+
let s = <Cow<str>>::deserialize(deserializer)?;
102
+
let repo = RepoPath::from_str(&s).map_err(serde::de::Error::custom)?;
103
+
Ok(repo)
104
+
}
105
+
106
+
#[allow(unused)]
107
+
pub fn serialize<S>(v: &RepoPath, serializer: S) -> Result<S::Ok, S::Error>
108
+
where
109
+
S: Serializer,
110
+
{
111
+
serializer.serialize_str(&format!("{}/{}", v.owner(), v.name()))
112
+
}
113
+
}
114
+
115
+
fn validate(name: &str) -> Result<(), RepoPathError> {
116
+
static FORBIDDEN_SUBSTRINGS: &[&str] = &[".."];
117
+
static FORBIDDEN_PREFIXES: &[&str] = &[".", "-"];
118
+
static ACCEPTED_SYMBOLS: &[char] = &['.', '-', '_', ':'];
119
+
120
+
if name.is_empty() {
121
+
return Err(RepoPathError::Empty);
122
+
}
123
+
124
+
for value in name.chars() {
125
+
if !(value.is_ascii_alphanumeric() || ACCEPTED_SYMBOLS.contains(&value)) {
126
+
return Err(RepoPathError::Character { value });
127
+
}
128
+
}
129
+
130
+
for substring in FORBIDDEN_SUBSTRINGS {
131
+
if name.contains(substring) {
132
+
return Err(RepoPathError::Substring { substring });
133
+
}
134
+
}
135
+
136
+
for prefix in FORBIDDEN_PREFIXES {
137
+
if name.starts_with(prefix) {
138
+
return Err(RepoPathError::Prefix { prefix });
139
+
}
140
+
}
141
+
142
+
Ok(())
143
+
}
144
+
145
+
#[derive(Debug, thiserror::Error)]
146
+
pub enum RepoPathError {
147
+
#[error("expected a repository specifier in form '{{owner}}/{{name}}'")]
148
+
Format,
149
+
#[error("repository name cannot be empty")]
150
+
Empty,
151
+
#[error("invalid character in repository name: '{value}'")]
152
+
Character { value: char },
153
+
#[error("invalid sub-string in repository name: '{substring}'")]
154
+
Substring { substring: &'static str },
155
+
#[error("invalid prefix for respository name: '{prefix}'")]
156
+
Prefix { prefix: &'static str },
157
+
}
+11
crates/lexicon/src/lib.rs
+11
crates/lexicon/src/lib.rs
···
1
+
//!
2
+
//! Artisinal, hand-written Lexicon definitions.
3
+
//!
4
+
//! When you're too lazy to setup codegen, not lazy enough to not write them
5
+
//! all out manually.
6
+
//!
7
+
pub mod sh;
8
+
9
+
/// Types which aren't part of the lexicon, but are used for serializing
10
+
/// and deserializing.
11
+
pub mod extra;
+1
crates/lexicon/src/sh.rs
+1
crates/lexicon/src/sh.rs
···
1
+
pub mod tangled;
+32
crates/lexicon/src/sh/tangled.rs
+32
crates/lexicon/src/sh/tangled.rs
···
1
+
pub mod knot;
2
+
pub mod repo;
3
+
pub mod spindle;
4
+
5
+
use identity::Did;
6
+
use serde::{Deserialize, Serialize};
7
+
use time::OffsetDateTime;
8
+
9
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/owner.json>
10
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
11
+
#[serde(rename = "sh.tangled.owner")]
12
+
pub struct OwnerOutput {
13
+
pub owner: Did,
14
+
}
15
+
16
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/publicKey.json>
17
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
18
+
#[serde(rename = "sh.tangled.publicKey", rename_all = "camelCase")]
19
+
pub struct PublicKey {
20
+
#[serde(rename = "$type")]
21
+
pub r#type: String,
22
+
23
+
/// Public key contents
24
+
pub key: String,
25
+
26
+
/// Human-readable name for this key
27
+
pub name: String,
28
+
29
+
/// Key upload timestamp
30
+
#[serde(alias = "created", with = "time::serde::rfc3339")]
31
+
pub created_at: OffsetDateTime,
32
+
}
+36
crates/lexicon/src/sh/tangled/knot.rs
+36
crates/lexicon/src/sh/tangled/knot.rs
···
1
+
use identity::Did;
2
+
use serde::{Deserialize, Serialize};
3
+
use std::borrow::Cow;
4
+
use time::OffsetDateTime;
5
+
6
+
/// Output of `sh.tangled.knot.version` query.
7
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
8
+
#[serde(rename_all = "camelCase")]
9
+
pub struct VersionOutput {
10
+
pub version: Cow<'static, str>,
11
+
}
12
+
13
+
/// `sh.tangled.knot` record.
14
+
///
15
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/knot/knot.json>
16
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
17
+
#[serde(rename = "sh.tangled.knot", rename_all = "camelCase")]
18
+
pub struct Knot {
19
+
#[serde(with = "time::serde::rfc3339")]
20
+
pub created_at: OffsetDateTime,
21
+
}
22
+
23
+
/// `sh.tangled.knot.member` record.
24
+
///
25
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/knot/member.json>
26
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
27
+
#[serde(rename = "sh.tangled.knot.member", rename_all = "camelCase")]
28
+
pub struct Member {
29
+
pub subject: Did,
30
+
31
+
/// Domain that this member now belongs to
32
+
pub domain: String,
33
+
34
+
#[serde(with = "time::serde::rfc3339")]
35
+
pub created_at: OffsetDateTime,
36
+
}
+89
crates/lexicon/src/sh/tangled/repo.rs
+89
crates/lexicon/src/sh/tangled/repo.rs
···
1
+
use serde::{Deserialize, Serialize};
2
+
use time::OffsetDateTime;
3
+
4
+
/// `sh.tangled.repo` record.
5
+
///
6
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/repo.json>
7
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
8
+
#[serde(rename = "sh.tangled.repo", rename_all = "camelCase")]
9
+
pub struct Repo {
10
+
#[serde(rename = "$type")]
11
+
pub r#type: String,
12
+
13
+
/// Name of the repo
14
+
pub name: String,
15
+
16
+
/// Knot where the repo was created
17
+
pub knot: String,
18
+
19
+
/// CI runner to send jobs to and receive results from
20
+
#[serde(skip_serializing_if = "Option::is_none")]
21
+
pub spindle: Option<String>,
22
+
23
+
#[serde(skip_serializing_if = "Option::is_none")]
24
+
pub description: Option<String>,
25
+
26
+
/// Source of the repo
27
+
#[serde(skip_serializing_if = "Option::is_none")]
28
+
pub source: Option<String>,
29
+
30
+
/// List of labels that this repo subscribes to
31
+
// @NOTE These should be at-uris.
32
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
33
+
pub labels: Vec<String>,
34
+
35
+
#[serde(with = "time::serde::rfc3339")]
36
+
pub created_at: OffsetDateTime,
37
+
}
38
+
39
+
pub mod blob;
40
+
pub mod branches;
41
+
pub mod diff;
42
+
pub mod get_default_branch;
43
+
pub mod languages;
44
+
pub mod log;
45
+
pub mod tags;
46
+
pub mod tree;
47
+
48
+
/// Lexicon sub-types.
49
+
pub mod refs {
50
+
use crate::extra::objectid::{Array, ObjectId};
51
+
use serde::Serialize;
52
+
use std::{borrow::Cow, collections::HashMap};
53
+
use time::OffsetDateTime;
54
+
55
+
#[derive(Debug, Default, Serialize)]
56
+
#[serde(rename_all = "camelCase")]
57
+
pub struct Reference {
58
+
/// Short-name of the reference.
59
+
pub name: String,
60
+
pub hash: ObjectId,
61
+
}
62
+
63
+
/// Git commit signature (ie. the author or committer).
64
+
#[derive(Debug, Serialize)]
65
+
#[serde(rename_all = "PascalCase")]
66
+
pub struct Signature {
67
+
pub name: String,
68
+
pub email: String,
69
+
#[serde(with = "time::serde::rfc3339")]
70
+
pub when: OffsetDateTime,
71
+
}
72
+
73
+
#[derive(Debug, Serialize)]
74
+
#[serde(rename_all = "PascalCase")]
75
+
pub struct Commit {
76
+
pub hash: ObjectId<Array>,
77
+
pub author: Signature,
78
+
pub committer: Signature,
79
+
pub merge_tag: String,
80
+
#[serde(rename = "PGPSignature")]
81
+
pub pgp_signature: Option<String>,
82
+
pub message: String,
83
+
pub tree_hash: ObjectId<Array>,
84
+
pub parent_hashes: Vec<ObjectId<Array>>,
85
+
pub encoding: Cow<'static, str>,
86
+
/// Non-standard object headers. Values are always base64 encoded.
87
+
pub extra_headers: HashMap<String, String>,
88
+
}
89
+
}
+85
crates/lexicon/src/sh/tangled/repo/blob.rs
+85
crates/lexicon/src/sh/tangled/repo/blob.rs
···
1
+
//!
2
+
//! <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/blob.json>
3
+
//!
4
+
use crate::extra::{
5
+
objectid::{Hex, ObjectId},
6
+
repopath::RepoPath,
7
+
};
8
+
use serde::{Deserialize, Serialize};
9
+
use std::path::PathBuf;
10
+
use time::OffsetDateTime;
11
+
12
+
use super::refs;
13
+
14
+
#[derive(Debug, Deserialize)]
15
+
pub struct Input {
16
+
/// Repository identifier in format `did:plc:.../repoName`.
17
+
#[serde(with = "crate::extra::repopath::string")]
18
+
pub repo: RepoPath,
19
+
20
+
/// Git reference (branch, tag, or commit SHA).
21
+
#[serde(rename = "ref")]
22
+
pub rev: Option<String>,
23
+
24
+
/// Path to the file within the repository.
25
+
pub path: PathBuf,
26
+
27
+
/// Return raw file content instead of JSON response.
28
+
#[serde(default)]
29
+
pub raw: bool,
30
+
}
31
+
32
+
#[derive(Debug, Serialize)]
33
+
#[serde(rename_all = "camelCase")]
34
+
pub struct Output {
35
+
/// The git reference used.
36
+
#[serde(rename = "ref")]
37
+
pub rev: String,
38
+
39
+
/// This file path.
40
+
pub path: PathBuf,
41
+
42
+
/// File content (base64 encoded for binary files).
43
+
pub content: String,
44
+
45
+
/// Content encoding.
46
+
pub encoding: Encoding,
47
+
48
+
/// File size in bytes.
49
+
pub size: usize,
50
+
51
+
/// Whether the file is binary.
52
+
pub is_binary: bool,
53
+
54
+
/// MIME type of the file.
55
+
pub mime_type: String,
56
+
57
+
pub last_commit: Option<LastCommit>,
58
+
}
59
+
60
+
#[derive(Debug, Serialize)]
61
+
pub enum Encoding {
62
+
#[serde(rename = "utf-8")]
63
+
Utf8,
64
+
#[serde(rename = "base64")]
65
+
Base64,
66
+
}
67
+
68
+
#[derive(Debug, Serialize)]
69
+
#[serde(rename_all = "camelCase")]
70
+
pub struct LastCommit {
71
+
/// Commit hash.
72
+
pub hash: ObjectId<Hex>,
73
+
74
+
/// Short commit hash.
75
+
pub short_hash: Option<Box<str>>,
76
+
77
+
/// Commit message.
78
+
pub message: Box<str>,
79
+
80
+
pub author: Option<refs::Signature>,
81
+
82
+
/// Commit timestamp.
83
+
#[serde(with = "time::serde::rfc3339")]
84
+
pub when: OffsetDateTime,
85
+
}
+28
crates/lexicon/src/sh/tangled/repo/branches.rs
+28
crates/lexicon/src/sh/tangled/repo/branches.rs
···
1
+
//!
2
+
//! <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/branches.json>
3
+
//!
4
+
use crate::extra::repopath::RepoPath;
5
+
use serde::Deserialize;
6
+
7
+
/// Parameters for the `sh.tangled.repo.branches` query.
8
+
///
9
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/branches.json>
10
+
#[derive(Debug, Deserialize)]
11
+
pub struct Input {
12
+
/// Repository identifier in the format `did:plc:.../repoName`
13
+
#[serde(with = "crate::extra::repopath::string")]
14
+
pub repo: RepoPath,
15
+
16
+
/// Maximum number of branches to return.
17
+
///
18
+
/// (1..=100); Default: 50
19
+
#[serde(default = "branches_limit_default")]
20
+
pub limit: u16,
21
+
22
+
/// Pagination cursor
23
+
pub cursor: Option<String>,
24
+
}
25
+
26
+
const fn branches_limit_default() -> u16 {
27
+
50
28
+
}
+16
crates/lexicon/src/sh/tangled/repo/diff.rs
+16
crates/lexicon/src/sh/tangled/repo/diff.rs
···
1
+
//!
2
+
//! <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/diff.json>
3
+
//!
4
+
use crate::extra::repopath::RepoPath;
5
+
use serde::Deserialize;
6
+
7
+
#[derive(Debug, Deserialize)]
8
+
pub struct Input {
9
+
/// Repository identifier in the format `did:plc:.../repoName`
10
+
#[serde(with = "crate::extra::repopath::string")]
11
+
pub repo: RepoPath,
12
+
13
+
/// Git reference (branch, tag, or commit SHA)
14
+
#[serde(rename = "ref")]
15
+
pub rev: Box<str>,
16
+
}
+65
crates/lexicon/src/sh/tangled/repo/get_default_branch.rs
+65
crates/lexicon/src/sh/tangled/repo/get_default_branch.rs
···
1
+
use crate::extra::{
2
+
objectid::{Hex, ObjectId},
3
+
repopath::RepoPath,
4
+
};
5
+
use serde::{Deserialize, Serialize};
6
+
use time::OffsetDateTime;
7
+
8
+
use super::refs;
9
+
10
+
#[derive(Debug, Deserialize)]
11
+
pub struct Input {
12
+
/// Repository identifier in the format `did:plc:.../repoName`
13
+
#[serde(with = "crate::extra::repopath::string")]
14
+
pub repo: RepoPath,
15
+
}
16
+
17
+
/// Response type for the `sh.tangled.repo.getDefaultBranch` query.
18
+
//
19
+
// @NOTE This completely different to what the lexicon describes,
20
+
// but it is what knotserver outputs.
21
+
#[derive(Debug, Serialize)]
22
+
#[serde(rename_all = "camelCase")]
23
+
pub struct Output {
24
+
/// Short-name of the default branch.
25
+
pub name: String,
26
+
27
+
/// ID of the most recent commit on the default branch
28
+
//
29
+
// @NOTE Official knotserver always returns an empty string.
30
+
#[serde(skip_serializing_if = "Option::is_none")]
31
+
pub hash: Option<ObjectId>,
32
+
33
+
/// Timestamp of the most recent commit on the default branch
34
+
//
35
+
// @NOTE Official knotserver always returns the unix epoch.
36
+
#[serde(
37
+
with = "time::serde::rfc3339::option",
38
+
skip_serializing_if = "Option::is_none"
39
+
)]
40
+
pub when: Option<OffsetDateTime>,
41
+
}
42
+
43
+
/// Defined response type for the `sh.tangled.repo.getDefaultBranch` query.
44
+
//
45
+
#[derive(Debug, Serialize)]
46
+
#[serde(rename_all = "camelCase")]
47
+
pub struct LexiOutput {
48
+
/// Default branch name.
49
+
pub name: Box<str>,
50
+
51
+
/// Latest commit hash on default branch.
52
+
pub hash: ObjectId<Hex>,
53
+
54
+
/// Short commit hash.
55
+
pub short_hash: Box<str>,
56
+
57
+
/// Timestamp of the latest commit.
58
+
#[serde(with = "time::serde::rfc3339")]
59
+
pub when: OffsetDateTime,
60
+
61
+
/// Latest commit message.
62
+
pub message: Option<Box<str>>,
63
+
64
+
pub author: Option<refs::Signature>,
65
+
}
+57
crates/lexicon/src/sh/tangled/repo/languages.rs
+57
crates/lexicon/src/sh/tangled/repo/languages.rs
···
1
+
//!
2
+
//! <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/languages.json>
3
+
//!
4
+
use crate::extra::repopath::RepoPath;
5
+
use serde::{Deserialize, Serialize};
6
+
7
+
#[derive(Debug, Deserialize)]
8
+
pub struct Input {
9
+
/// Respository identifier in format `did:plc:.../repoName`
10
+
#[serde(with = "crate::extra::repopath::string")]
11
+
pub repo: RepoPath,
12
+
13
+
/// Git reference (branch, tag, or commit SHA)
14
+
#[serde(rename = "ref")]
15
+
pub rev: String,
16
+
}
17
+
18
+
#[derive(Debug, Default, Serialize)]
19
+
#[serde(rename_all = "camelCase")]
20
+
pub struct Output {
21
+
/// The git reference used
22
+
#[serde(rename = "ref")]
23
+
pub rev: String,
24
+
25
+
#[serde(skip_serializing_if = "Vec::is_empty")]
26
+
pub languages: Vec<Language>,
27
+
28
+
/// Total size of all analyzed files in bytes
29
+
#[serde(skip_serializing_if = "Option::is_none")]
30
+
pub total_size: Option<u64>,
31
+
32
+
/// Total number of files analyzed
33
+
#[serde(skip_serializing_if = "Option::is_none")]
34
+
pub total_files: Option<u64>,
35
+
}
36
+
37
+
#[derive(Debug, Serialize)]
38
+
#[serde(rename_all = "camelCase")]
39
+
pub struct Language {
40
+
/// Programming language name
41
+
pub name: String,
42
+
43
+
/// Total size of files in this language (bytes)
44
+
pub size: u64,
45
+
46
+
/// Percentage of total codebase 0..=100
47
+
pub percentage: u8,
48
+
49
+
/// Number of files in this language
50
+
pub file_count: u64,
51
+
52
+
/// Hex color code for this language
53
+
pub color: String,
54
+
55
+
/// File extensions associated with this language
56
+
pub extensions: Vec<String>,
57
+
}
+38
crates/lexicon/src/sh/tangled/repo/log.rs
+38
crates/lexicon/src/sh/tangled/repo/log.rs
···
1
+
//!
2
+
//! <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/log.json>
3
+
//!
4
+
use crate::extra::repopath::RepoPath;
5
+
use serde::Deserialize;
6
+
use std::path::PathBuf;
7
+
8
+
/// Parameters for the `sh.tangled.repo.log` query.
9
+
#[derive(Debug, Deserialize)]
10
+
pub struct Input {
11
+
/// Repository identifier in the format `did:plc:.../repoName`
12
+
#[serde(with = "crate::extra::repopath::string")]
13
+
pub repo: RepoPath,
14
+
15
+
/// Git reference (branch, tag, or commit SHA)
16
+
#[serde(rename = "ref")]
17
+
pub rev: Option<String>,
18
+
19
+
/// Path to filter commits by
20
+
pub path: Option<PathBuf>,
21
+
22
+
/// Maximum number of commits to return.
23
+
///
24
+
/// (1..=100); Default: 50
25
+
#[serde(default = "log_limit_default")]
26
+
pub limit: u16,
27
+
28
+
/// Pagination cursor (commit SHA)
29
+
//
30
+
// @NOTE AppView actually gives us an integer for the number of
31
+
// commits to skip.
32
+
#[serde(default)]
33
+
pub cursor: usize,
34
+
}
35
+
36
+
const fn log_limit_default() -> u16 {
37
+
50
38
+
}
+89
crates/lexicon/src/sh/tangled/repo/tree.rs
+89
crates/lexicon/src/sh/tangled/repo/tree.rs
···
1
+
//!
2
+
//! <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/tree.json>
3
+
//!
4
+
use std::{borrow::Cow, path::PathBuf};
5
+
6
+
use crate::extra::{objectid::ObjectId, repopath::RepoPath};
7
+
use serde::{Deserialize, Serialize};
8
+
use time::OffsetDateTime;
9
+
10
+
/// Parameters for the `sh.tangled.repo.tree` query.
11
+
///
12
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/repo/tree.json>
13
+
#[derive(Debug, Deserialize)]
14
+
pub struct Input {
15
+
/// Repository identifier in the format `did:plc:.../repoName`
16
+
#[serde(with = "crate::extra::repopath::string")]
17
+
pub repo: RepoPath,
18
+
19
+
/// Git reference (branch, tag, or commit SHA)
20
+
#[serde(rename = "ref")]
21
+
pub rev: Option<String>,
22
+
23
+
/// Path within the repository tree
24
+
pub path: Option<PathBuf>,
25
+
}
26
+
27
+
/// Output of `sh.tangled.repo.tree` query.
28
+
#[derive(Debug, Serialize)]
29
+
pub struct Output {
30
+
/// The git reference used
31
+
#[serde(rename = "ref")]
32
+
pub rev: String,
33
+
34
+
/// The parent path in the tree
35
+
#[serde(skip_serializing_if = "Option::is_none")]
36
+
pub parent: Option<PathBuf>,
37
+
38
+
/// Parent directory path
39
+
#[serde(skip_serializing_if = "Option::is_none")]
40
+
pub dotdot: Option<PathBuf>,
41
+
42
+
/// Readme for this file tree
43
+
#[serde(skip_serializing_if = "Option::is_none")]
44
+
pub readme: Option<Readme>,
45
+
46
+
pub files: Vec<TreeEntry>,
47
+
}
48
+
49
+
#[derive(Debug, Default, Serialize)]
50
+
pub struct TreeEntry {
51
+
/// Relative file or directory name
52
+
pub name: String,
53
+
54
+
/// File mode
55
+
pub mode: Cow<'static, str>,
56
+
57
+
/// File size in bytes
58
+
pub size: usize,
59
+
60
+
/// Whether this entry is a file
61
+
pub is_file: bool,
62
+
63
+
/// Whether this entry is a directory/subtree
64
+
pub is_subtree: bool,
65
+
66
+
pub last_commit: Option<LastCommit>,
67
+
}
68
+
69
+
#[derive(Debug, Serialize)]
70
+
pub struct Readme {
71
+
/// Contents of the readme file
72
+
pub filename: String,
73
+
74
+
/// Name of the readme file
75
+
pub contents: String,
76
+
}
77
+
78
+
#[derive(Debug, Serialize)]
79
+
pub struct LastCommit {
80
+
/// Commit hash
81
+
pub hash: ObjectId,
82
+
83
+
/// Commit message
84
+
pub message: String,
85
+
86
+
/// Commit timestamp
87
+
#[serde(with = "time::serde::rfc3339")]
88
+
pub when: OffsetDateTime,
89
+
}
+28
crates/lexicon/src/sh/tangled/spindle.rs
+28
crates/lexicon/src/sh/tangled/spindle.rs
···
1
+
use identity::Did;
2
+
use serde::{Deserialize, Serialize};
3
+
use time::OffsetDateTime;
4
+
5
+
/// `sh.tangled.spindle` record.
6
+
///
7
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/spindle/spindle.json>
8
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
9
+
#[serde(rename = "sh.tangled.spindle", rename_all = "camelCase")]
10
+
pub struct Spindle {
11
+
#[serde(with = "time::serde::rfc3339")]
12
+
pub created_at: OffsetDateTime,
13
+
}
14
+
15
+
/// `sh.tangled.spindle.member` record.
16
+
///
17
+
/// <https://tangled.org/@tangled.org/core/blob/master/lexicons/spindle/member.json>
18
+
#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
19
+
#[serde(rename = "sh.tangled.spindle.member", rename_all = "camelCase")]
20
+
pub struct Member {
21
+
pub subject: Did,
22
+
23
+
/// Domain that this member now belongs to
24
+
pub domain: String,
25
+
26
+
#[serde(with = "time::serde::rfc3339")]
27
+
pub created_at: OffsetDateTime,
28
+
}