Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

gitmirror: introduce gitmirror

Signed-off-by: Seongmin Lee <git@boltless.me>

Seongmin Lee (Jul 1, 2026, 2:21 AM +0900) 8a598a4d ef843de6

+3436 -39
+1318 -24
Cargo.lock
··· 149 149 ] 150 150 151 151 [[package]] 152 + name = "arrayvec" 153 + version = "0.7.7" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" 156 + 157 + [[package]] 152 158 name = "assert-json-diff" 153 159 version = "2.0.2" 154 160 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 311 317 312 318 [[package]] 313 319 name = "bitflags" 320 + version = "1.3.2" 321 + source = "registry+https://github.com/rust-lang/crates.io-index" 322 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 323 + 324 + [[package]] 325 + name = "bitflags" 314 326 version = "2.11.1" 315 327 source = "registry+https://github.com/rust-lang/crates.io-index" 316 328 checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" ··· 331 343 checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 332 344 dependencies = [ 333 345 "generic-array", 346 + ] 347 + 348 + [[package]] 349 + name = "block-buffer" 350 + version = "0.12.1" 351 + source = "registry+https://github.com/rust-lang/crates.io-index" 352 + checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" 353 + dependencies = [ 354 + "hybrid-array", 334 355 ] 335 356 336 357 [[package]] ··· 648 669 ] 649 670 650 671 [[package]] 672 + name = "bstr" 673 + version = "1.12.3" 674 + source = "registry+https://github.com/rust-lang/crates.io-index" 675 + checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" 676 + dependencies = [ 677 + "memchr", 678 + "regex-automata", 679 + "serde_core", 680 + ] 681 + 682 + [[package]] 651 683 name = "bumpalo" 652 684 version = "3.20.3" 653 685 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 667 699 dependencies = [ 668 700 "serde", 669 701 ] 702 + 703 + [[package]] 704 + name = "bytesize" 705 + version = "2.4.0" 706 + source = "registry+https://github.com/rust-lang/crates.io-index" 707 + checksum = "49e78e506b9d7633710dab98996f22f95f3d0f488e8f1aa162830556ed9fc14d" 670 708 671 709 [[package]] 672 710 name = "cast" ··· 826 864 checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" 827 865 828 866 [[package]] 867 + name = "clru" 868 + version = "0.6.3" 869 + source = "registry+https://github.com/rust-lang/crates.io-index" 870 + checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" 871 + dependencies = [ 872 + "hashbrown 0.16.1", 873 + ] 874 + 875 + [[package]] 829 876 name = "cobs" 830 877 version = "0.3.0" 831 878 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 938 985 ] 939 986 940 987 [[package]] 988 + name = "cpufeatures" 989 + version = "0.3.0" 990 + source = "registry+https://github.com/rust-lang/crates.io-index" 991 + checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" 992 + dependencies = [ 993 + "libc", 994 + ] 995 + 996 + [[package]] 941 997 name = "crc32fast" 942 998 version = "1.5.0" 943 999 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1048 1104 ] 1049 1105 1050 1106 [[package]] 1107 + name = "crypto-common" 1108 + version = "0.2.2" 1109 + source = "registry+https://github.com/rust-lang/crates.io-index" 1110 + checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" 1111 + dependencies = [ 1112 + "hybrid-array", 1113 + ] 1114 + 1115 + [[package]] 1051 1116 name = "darling" 1052 1117 version = "0.23.0" 1053 1118 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1146 1211 checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" 1147 1212 1148 1213 [[package]] 1214 + name = "defmt" 1215 + version = "1.1.0" 1216 + source = "registry+https://github.com/rust-lang/crates.io-index" 1217 + checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" 1218 + dependencies = [ 1219 + "bitflags 1.3.2", 1220 + "defmt-macros", 1221 + ] 1222 + 1223 + [[package]] 1224 + name = "defmt-macros" 1225 + version = "1.1.0" 1226 + source = "registry+https://github.com/rust-lang/crates.io-index" 1227 + checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" 1228 + dependencies = [ 1229 + "defmt-parser", 1230 + "proc-macro-error2", 1231 + "proc-macro2", 1232 + "quote", 1233 + "syn", 1234 + ] 1235 + 1236 + [[package]] 1237 + name = "defmt-parser" 1238 + version = "1.0.0" 1239 + source = "registry+https://github.com/rust-lang/crates.io-index" 1240 + checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" 1241 + dependencies = [ 1242 + "thiserror 2.0.18", 1243 + ] 1244 + 1245 + [[package]] 1149 1246 name = "der" 1150 1247 version = "0.7.10" 1151 1248 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1199 1296 source = "registry+https://github.com/rust-lang/crates.io-index" 1200 1297 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 1201 1298 dependencies = [ 1202 - "block-buffer", 1299 + "block-buffer 0.10.4", 1203 1300 "const-oid", 1204 - "crypto-common", 1301 + "crypto-common 0.1.6", 1205 1302 "subtle", 1206 1303 ] 1207 1304 1208 1305 [[package]] 1306 + name = "digest" 1307 + version = "0.11.3" 1308 + source = "registry+https://github.com/rust-lang/crates.io-index" 1309 + checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" 1310 + dependencies = [ 1311 + "block-buffer 0.12.1", 1312 + "crypto-common 0.2.2", 1313 + ] 1314 + 1315 + [[package]] 1209 1316 name = "displaydoc" 1210 1317 version = "0.2.6" 1211 1318 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1223 1330 checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" 1224 1331 1225 1332 [[package]] 1333 + name = "dunce" 1334 + version = "1.0.5" 1335 + source = "registry+https://github.com/rust-lang/crates.io-index" 1336 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 1337 + 1338 + [[package]] 1226 1339 name = "ecdsa" 1227 1340 version = "0.16.9" 1228 1341 source = "registry+https://github.com/rust-lang/crates.io-index" 1229 1342 checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 1230 1343 dependencies = [ 1231 1344 "der", 1232 - "digest", 1345 + "digest 0.10.7", 1233 1346 "elliptic-curve", 1234 1347 "rfc6979", 1235 1348 "signature", ··· 1250 1363 dependencies = [ 1251 1364 "base16ct", 1252 1365 "crypto-bigint", 1253 - "digest", 1366 + "digest 0.10.7", 1254 1367 "ff", 1255 1368 "generic-array", 1256 1369 "group", ··· 1317 1430 checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" 1318 1431 1319 1432 [[package]] 1433 + name = "faster-hex" 1434 + version = "0.10.0" 1435 + source = "registry+https://github.com/rust-lang/crates.io-index" 1436 + checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" 1437 + dependencies = [ 1438 + "heapless 0.8.0", 1439 + "serde", 1440 + ] 1441 + 1442 + [[package]] 1320 1443 name = "fastrand" 1321 1444 version = "2.4.1" 1322 1445 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1330 1453 dependencies = [ 1331 1454 "rand_core 0.6.4", 1332 1455 "subtle", 1456 + ] 1457 + 1458 + [[package]] 1459 + name = "filetime" 1460 + version = "0.2.29" 1461 + source = "registry+https://github.com/rust-lang/crates.io-index" 1462 + checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" 1463 + dependencies = [ 1464 + "cfg-if", 1465 + "libc", 1333 1466 ] 1334 1467 1335 1468 [[package]] ··· 1586 1719 name = "gitmirror" 1587 1720 version = "0.1.0" 1588 1721 dependencies = [ 1722 + "anyhow", 1723 + "clap", 1724 + "gix", 1725 + "line-numbers", 1589 1726 "prost", 1727 + "rustc-hash", 1728 + "tempfile", 1590 1729 "tokio", 1730 + "tokio-stream", 1591 1731 "tonic", 1592 1732 "tonic-prost", 1593 1733 "tonic-prost-build", ··· 1596 1736 ] 1597 1737 1598 1738 [[package]] 1739 + name = "gix" 1740 + version = "0.84.0" 1741 + source = "registry+https://github.com/rust-lang/crates.io-index" 1742 + checksum = "ae54ae0ebd1a5a3c3f8d95dd3b5ca6e63f4fed9bfd585e13801a97d7bde8f9ce" 1743 + dependencies = [ 1744 + "gix-actor", 1745 + "gix-archive", 1746 + "gix-attributes", 1747 + "gix-blame", 1748 + "gix-command", 1749 + "gix-commitgraph", 1750 + "gix-config", 1751 + "gix-credentials", 1752 + "gix-date", 1753 + "gix-diff", 1754 + "gix-dir", 1755 + "gix-discover", 1756 + "gix-error", 1757 + "gix-features", 1758 + "gix-filter", 1759 + "gix-fs", 1760 + "gix-glob", 1761 + "gix-hash", 1762 + "gix-hashtable", 1763 + "gix-ignore", 1764 + "gix-index", 1765 + "gix-lock", 1766 + "gix-mailmap", 1767 + "gix-merge", 1768 + "gix-negotiate", 1769 + "gix-object", 1770 + "gix-odb", 1771 + "gix-pack", 1772 + "gix-path", 1773 + "gix-pathspec", 1774 + "gix-prompt", 1775 + "gix-protocol", 1776 + "gix-ref", 1777 + "gix-refspec", 1778 + "gix-revision", 1779 + "gix-revwalk", 1780 + "gix-sec", 1781 + "gix-shallow", 1782 + "gix-status", 1783 + "gix-submodule", 1784 + "gix-tempfile", 1785 + "gix-trace", 1786 + "gix-traverse", 1787 + "gix-url", 1788 + "gix-utils", 1789 + "gix-validate", 1790 + "gix-worktree", 1791 + "gix-worktree-state", 1792 + "gix-worktree-stream", 1793 + "nonempty", 1794 + "parking_lot", 1795 + "regex", 1796 + "signal-hook", 1797 + "smallvec", 1798 + "thiserror 2.0.18", 1799 + ] 1800 + 1801 + [[package]] 1802 + name = "gix-actor" 1803 + version = "0.41.1" 1804 + source = "registry+https://github.com/rust-lang/crates.io-index" 1805 + checksum = "8bc998b8f746dda8565450d08a63b792ced9165d8c27a1ed3f02799ec6a7820f" 1806 + dependencies = [ 1807 + "bstr", 1808 + "gix-date", 1809 + "gix-error", 1810 + ] 1811 + 1812 + [[package]] 1813 + name = "gix-archive" 1814 + version = "0.33.0" 1815 + source = "registry+https://github.com/rust-lang/crates.io-index" 1816 + checksum = "16909cacc78936ab96f6c3be08379d0a2e88bfa3a7527972d2ed75c7517ef31e" 1817 + dependencies = [ 1818 + "bstr", 1819 + "gix-date", 1820 + "gix-error", 1821 + "gix-object", 1822 + "gix-worktree-stream", 1823 + ] 1824 + 1825 + [[package]] 1826 + name = "gix-attributes" 1827 + version = "0.33.2" 1828 + source = "registry+https://github.com/rust-lang/crates.io-index" 1829 + checksum = "39b40888d0ed415c0744a6cdc61eebf0304c9d26ab726725b718443c322e5ba4" 1830 + dependencies = [ 1831 + "bstr", 1832 + "gix-glob", 1833 + "gix-path", 1834 + "gix-quote", 1835 + "gix-trace", 1836 + "kstring", 1837 + "smallvec", 1838 + "thiserror 2.0.18", 1839 + "unicode-bom", 1840 + ] 1841 + 1842 + [[package]] 1843 + name = "gix-bitmap" 1844 + version = "0.3.2" 1845 + source = "registry+https://github.com/rust-lang/crates.io-index" 1846 + checksum = "52ebef0c26ad305747649e727bbcd56a7b7910754eb7cea88f6dff6f93c51283" 1847 + dependencies = [ 1848 + "gix-error", 1849 + ] 1850 + 1851 + [[package]] 1852 + name = "gix-blame" 1853 + version = "0.14.0" 1854 + source = "registry+https://github.com/rust-lang/crates.io-index" 1855 + checksum = "4d39a0c14af94c2edaa5eefe06d5ef2cdea55316ae9a9321314288e3f55fa4c0" 1856 + dependencies = [ 1857 + "gix-commitgraph", 1858 + "gix-date", 1859 + "gix-diff", 1860 + "gix-error", 1861 + "gix-hash", 1862 + "gix-object", 1863 + "gix-revwalk", 1864 + "gix-trace", 1865 + "gix-traverse", 1866 + "gix-worktree", 1867 + "smallvec", 1868 + "thiserror 2.0.18", 1869 + ] 1870 + 1871 + [[package]] 1872 + name = "gix-chunk" 1873 + version = "0.7.2" 1874 + source = "registry+https://github.com/rust-lang/crates.io-index" 1875 + checksum = "9faee47943b638e58ddd5e275a4906ad3e4b6c8584f1d41bd18ab9032ec52afb" 1876 + dependencies = [ 1877 + "gix-error", 1878 + ] 1879 + 1880 + [[package]] 1881 + name = "gix-command" 1882 + version = "0.9.1" 1883 + source = "registry+https://github.com/rust-lang/crates.io-index" 1884 + checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6" 1885 + dependencies = [ 1886 + "bstr", 1887 + "gix-path", 1888 + "gix-quote", 1889 + "gix-trace", 1890 + "shell-words", 1891 + ] 1892 + 1893 + [[package]] 1894 + name = "gix-commitgraph" 1895 + version = "0.37.1" 1896 + source = "registry+https://github.com/rust-lang/crates.io-index" 1897 + checksum = "7f675d0df484a7f6a47e64bd6f311af489d947c0323b0564f36d14f3d7762abb" 1898 + dependencies = [ 1899 + "bstr", 1900 + "gix-chunk", 1901 + "gix-error", 1902 + "gix-hash", 1903 + "memmap2", 1904 + "nonempty", 1905 + ] 1906 + 1907 + [[package]] 1908 + name = "gix-config" 1909 + version = "0.57.0" 1910 + source = "registry+https://github.com/rust-lang/crates.io-index" 1911 + checksum = "4f2372d4b49ca28431e7d150cab9d25edc1890f0184bd57eb0e917c7799e63de" 1912 + dependencies = [ 1913 + "bstr", 1914 + "gix-config-value", 1915 + "gix-features", 1916 + "gix-glob", 1917 + "gix-path", 1918 + "gix-ref", 1919 + "gix-sec", 1920 + "smallvec", 1921 + "thiserror 2.0.18", 1922 + "unicode-bom", 1923 + ] 1924 + 1925 + [[package]] 1926 + name = "gix-config-value" 1927 + version = "0.18.1" 1928 + source = "registry+https://github.com/rust-lang/crates.io-index" 1929 + checksum = "ed42168329552f6c2e5df09665c104199d45d84bedb53683738a49b57fe1baab" 1930 + dependencies = [ 1931 + "bitflags 2.11.1", 1932 + "bstr", 1933 + "gix-path", 1934 + "libc", 1935 + "thiserror 2.0.18", 1936 + ] 1937 + 1938 + [[package]] 1939 + name = "gix-credentials" 1940 + version = "0.38.1" 1941 + source = "registry+https://github.com/rust-lang/crates.io-index" 1942 + checksum = "f40cd22f0dd71988be12d6e78b1709de2370e1957c5f107ff31e56caeba3745d" 1943 + dependencies = [ 1944 + "bstr", 1945 + "gix-command", 1946 + "gix-config-value", 1947 + "gix-date", 1948 + "gix-path", 1949 + "gix-prompt", 1950 + "gix-sec", 1951 + "gix-trace", 1952 + "gix-url", 1953 + "thiserror 2.0.18", 1954 + ] 1955 + 1956 + [[package]] 1957 + name = "gix-date" 1958 + version = "0.15.5" 1959 + source = "registry+https://github.com/rust-lang/crates.io-index" 1960 + checksum = "3d63f9e28b59ddeb1a1eb9e5cf986a9222b5d484947445edbc20473939cc7fd0" 1961 + dependencies = [ 1962 + "bstr", 1963 + "gix-error", 1964 + "itoa", 1965 + "jiff", 1966 + ] 1967 + 1968 + [[package]] 1969 + name = "gix-diff" 1970 + version = "0.64.0" 1971 + source = "registry+https://github.com/rust-lang/crates.io-index" 1972 + checksum = "3b6d9528f32d94cef2edf39a1ac01fe5a0fc44ddbb18d9e44099936047c3302b" 1973 + dependencies = [ 1974 + "bstr", 1975 + "gix-attributes", 1976 + "gix-command", 1977 + "gix-filter", 1978 + "gix-fs", 1979 + "gix-hash", 1980 + "gix-imara-diff", 1981 + "gix-index", 1982 + "gix-object", 1983 + "gix-path", 1984 + "gix-pathspec", 1985 + "gix-tempfile", 1986 + "gix-trace", 1987 + "gix-traverse", 1988 + "gix-worktree", 1989 + "thiserror 2.0.18", 1990 + ] 1991 + 1992 + [[package]] 1993 + name = "gix-dir" 1994 + version = "0.26.0" 1995 + source = "registry+https://github.com/rust-lang/crates.io-index" 1996 + checksum = "21bb2a53a6fd917ec499ed0bfb5b6887de7a15bd79197dcea7c987938749a9f1" 1997 + dependencies = [ 1998 + "bstr", 1999 + "gix-discover", 2000 + "gix-fs", 2001 + "gix-ignore", 2002 + "gix-index", 2003 + "gix-object", 2004 + "gix-path", 2005 + "gix-pathspec", 2006 + "gix-trace", 2007 + "gix-utils", 2008 + "gix-worktree", 2009 + "thiserror 2.0.18", 2010 + ] 2011 + 2012 + [[package]] 2013 + name = "gix-discover" 2014 + version = "0.52.0" 2015 + source = "registry+https://github.com/rust-lang/crates.io-index" 2016 + checksum = "77bacdd12b7879d2178a80c58c2f319995e4654e1a7a23e3181e5c8a12b824f7" 2017 + dependencies = [ 2018 + "bstr", 2019 + "dunce", 2020 + "gix-fs", 2021 + "gix-path", 2022 + "gix-ref", 2023 + "gix-sec", 2024 + "thiserror 2.0.18", 2025 + ] 2026 + 2027 + [[package]] 2028 + name = "gix-error" 2029 + version = "0.2.4" 2030 + source = "registry+https://github.com/rust-lang/crates.io-index" 2031 + checksum = "e57831e199be480af90dcd7e459abed8a174c09ec9a6e2cc8f7ca6c54598b06b" 2032 + dependencies = [ 2033 + "bstr", 2034 + ] 2035 + 2036 + [[package]] 2037 + name = "gix-features" 2038 + version = "0.48.1" 2039 + source = "registry+https://github.com/rust-lang/crates.io-index" 2040 + checksum = "1849ae154d38bc403185be14fa871e38e3c93ee606875d94e207fdb9fba52dbc" 2041 + dependencies = [ 2042 + "bytes", 2043 + "bytesize", 2044 + "crc32fast", 2045 + "crossbeam-channel", 2046 + "gix-path", 2047 + "gix-trace", 2048 + "gix-utils", 2049 + "libc", 2050 + "once_cell", 2051 + "parking_lot", 2052 + "prodash", 2053 + "thiserror 2.0.18", 2054 + "walkdir", 2055 + "zlib-rs", 2056 + ] 2057 + 2058 + [[package]] 2059 + name = "gix-filter" 2060 + version = "0.31.0" 2061 + source = "registry+https://github.com/rust-lang/crates.io-index" 2062 + checksum = "ecf74b7d16f6694ce4a3049074c41be0c7987105743674f1671807bd6dce09fa" 2063 + dependencies = [ 2064 + "bstr", 2065 + "encoding_rs", 2066 + "gix-attributes", 2067 + "gix-command", 2068 + "gix-hash", 2069 + "gix-object", 2070 + "gix-packetline", 2071 + "gix-path", 2072 + "gix-quote", 2073 + "gix-trace", 2074 + "gix-utils", 2075 + "smallvec", 2076 + "thiserror 2.0.18", 2077 + ] 2078 + 2079 + [[package]] 2080 + name = "gix-fs" 2081 + version = "0.21.2" 2082 + source = "registry+https://github.com/rust-lang/crates.io-index" 2083 + checksum = "6cdff46db8798e47e2f727d84b9379aac5add3dd3d9d0b07bb4d7d5d640771fe" 2084 + dependencies = [ 2085 + "bstr", 2086 + "fastrand", 2087 + "gix-features", 2088 + "gix-path", 2089 + "gix-utils", 2090 + "thiserror 2.0.18", 2091 + ] 2092 + 2093 + [[package]] 2094 + name = "gix-glob" 2095 + version = "0.26.1" 2096 + source = "registry+https://github.com/rust-lang/crates.io-index" 2097 + checksum = "d1fcb8ef5b16bcf874abe9b68d8abb3c0493c876d367ab824151f30a0f3f3756" 2098 + dependencies = [ 2099 + "bitflags 2.11.1", 2100 + "bstr", 2101 + "gix-features", 2102 + "gix-path", 2103 + ] 2104 + 2105 + [[package]] 2106 + name = "gix-hash" 2107 + version = "0.25.1" 2108 + source = "registry+https://github.com/rust-lang/crates.io-index" 2109 + checksum = "cb0926d3819c837750b4e03c7754901e73f68b8c9b690753a6372a1bed4eedce" 2110 + dependencies = [ 2111 + "faster-hex", 2112 + "gix-features", 2113 + "sha1-checked", 2114 + "sha2 0.11.0", 2115 + "thiserror 2.0.18", 2116 + ] 2117 + 2118 + [[package]] 2119 + name = "gix-hashtable" 2120 + version = "0.15.2" 2121 + source = "registry+https://github.com/rust-lang/crates.io-index" 2122 + checksum = "7e261d54091f0d1c729bc83f54548c071bdec60a697de1e58e88bdfd7a99d24e" 2123 + dependencies = [ 2124 + "gix-hash", 2125 + "hashbrown 0.17.1", 2126 + "parking_lot", 2127 + ] 2128 + 2129 + [[package]] 2130 + name = "gix-ignore" 2131 + version = "0.21.1" 2132 + source = "registry+https://github.com/rust-lang/crates.io-index" 2133 + checksum = "d491bab9bf2c9f341dc754f425c31d5d3f63aca615312167b82e1deeaca97d8d" 2134 + dependencies = [ 2135 + "bstr", 2136 + "gix-glob", 2137 + "gix-path", 2138 + "gix-trace", 2139 + "unicode-bom", 2140 + ] 2141 + 2142 + [[package]] 2143 + name = "gix-imara-diff" 2144 + version = "0.2.3" 2145 + source = "registry+https://github.com/rust-lang/crates.io-index" 2146 + checksum = "b305d85504de270ad3525d726a6b69cc59ee7b2269b014387651107ab9f0755b" 2147 + dependencies = [ 2148 + "bstr", 2149 + "hashbrown 0.17.1", 2150 + ] 2151 + 2152 + [[package]] 2153 + name = "gix-index" 2154 + version = "0.52.0" 2155 + source = "registry+https://github.com/rust-lang/crates.io-index" 2156 + checksum = "4e6b28cc592dc753adb58302bb14a64e412ee591a3bec77aa4df87bff74fa80d" 2157 + dependencies = [ 2158 + "bitflags 2.11.1", 2159 + "bstr", 2160 + "filetime", 2161 + "fnv", 2162 + "gix-bitmap", 2163 + "gix-features", 2164 + "gix-fs", 2165 + "gix-hash", 2166 + "gix-lock", 2167 + "gix-object", 2168 + "gix-traverse", 2169 + "gix-utils", 2170 + "gix-validate", 2171 + "hashbrown 0.17.1", 2172 + "itoa", 2173 + "libc", 2174 + "memmap2", 2175 + "rustix", 2176 + "smallvec", 2177 + "thiserror 2.0.18", 2178 + ] 2179 + 2180 + [[package]] 2181 + name = "gix-lock" 2182 + version = "23.0.1" 2183 + source = "registry+https://github.com/rust-lang/crates.io-index" 2184 + checksum = "65c9dedd9e90b0d47624d2ed241d394e09294118364e87b9b7e5f1fe755f3c2c" 2185 + dependencies = [ 2186 + "gix-tempfile", 2187 + "gix-utils", 2188 + "thiserror 2.0.18", 2189 + ] 2190 + 2191 + [[package]] 2192 + name = "gix-mailmap" 2193 + version = "0.33.1" 2194 + source = "registry+https://github.com/rust-lang/crates.io-index" 2195 + checksum = "195fd20808055824531be2fd0d34136d900e5fbca3ffb0a3c07e8beeefb9c828" 2196 + dependencies = [ 2197 + "bstr", 2198 + "gix-actor", 2199 + "gix-date", 2200 + "gix-error", 2201 + ] 2202 + 2203 + [[package]] 2204 + name = "gix-merge" 2205 + version = "0.17.0" 2206 + source = "registry+https://github.com/rust-lang/crates.io-index" 2207 + checksum = "7543e1eceb25fbbd1a29459c794148d8bafc1c77555eb386617a2d99b5371971" 2208 + dependencies = [ 2209 + "bstr", 2210 + "gix-command", 2211 + "gix-diff", 2212 + "gix-filter", 2213 + "gix-fs", 2214 + "gix-hash", 2215 + "gix-imara-diff", 2216 + "gix-index", 2217 + "gix-object", 2218 + "gix-path", 2219 + "gix-quote", 2220 + "gix-revision", 2221 + "gix-revwalk", 2222 + "gix-tempfile", 2223 + "gix-trace", 2224 + "gix-worktree", 2225 + "nonempty", 2226 + "thiserror 2.0.18", 2227 + ] 2228 + 2229 + [[package]] 2230 + name = "gix-negotiate" 2231 + version = "0.32.0" 2232 + source = "registry+https://github.com/rust-lang/crates.io-index" 2233 + checksum = "890c936a215bae25818c076cb881cb2e54d2c66ba947ba58b8dd47cff921bf55" 2234 + dependencies = [ 2235 + "bitflags 2.11.1", 2236 + "gix-commitgraph", 2237 + "gix-date", 2238 + "gix-hash", 2239 + "gix-object", 2240 + "gix-revwalk", 2241 + ] 2242 + 2243 + [[package]] 2244 + name = "gix-object" 2245 + version = "0.61.0" 2246 + source = "registry+https://github.com/rust-lang/crates.io-index" 2247 + checksum = "d5cd857e29429c7213bdef3f5aef83f8cc124774fe8ae0d27b1607d218d6d525" 2248 + dependencies = [ 2249 + "bstr", 2250 + "gix-actor", 2251 + "gix-date", 2252 + "gix-features", 2253 + "gix-hash", 2254 + "gix-hashtable", 2255 + "gix-utils", 2256 + "gix-validate", 2257 + "itoa", 2258 + "smallvec", 2259 + "thiserror 2.0.18", 2260 + ] 2261 + 2262 + [[package]] 2263 + name = "gix-odb" 2264 + version = "0.81.0" 2265 + source = "registry+https://github.com/rust-lang/crates.io-index" 2266 + checksum = "7d004c32858b1556f2d7874405edb3c97dc78fc09beaa87d57bb077ee2858a7d" 2267 + dependencies = [ 2268 + "arc-swap", 2269 + "gix-features", 2270 + "gix-fs", 2271 + "gix-hash", 2272 + "gix-hashtable", 2273 + "gix-object", 2274 + "gix-pack", 2275 + "gix-path", 2276 + "gix-quote", 2277 + "memmap2", 2278 + "parking_lot", 2279 + "tempfile", 2280 + "thiserror 2.0.18", 2281 + ] 2282 + 2283 + [[package]] 2284 + name = "gix-pack" 2285 + version = "0.71.0" 2286 + source = "registry+https://github.com/rust-lang/crates.io-index" 2287 + checksum = "e43626f2a27d1033674ec1a196b845614231e6bbd949d5e21c133045ff56b174" 2288 + dependencies = [ 2289 + "clru", 2290 + "gix-chunk", 2291 + "gix-error", 2292 + "gix-features", 2293 + "gix-hash", 2294 + "gix-hashtable", 2295 + "gix-object", 2296 + "gix-path", 2297 + "memmap2", 2298 + "smallvec", 2299 + "thiserror 2.0.18", 2300 + "uluru", 2301 + ] 2302 + 2303 + [[package]] 2304 + name = "gix-packetline" 2305 + version = "0.21.5" 2306 + source = "registry+https://github.com/rust-lang/crates.io-index" 2307 + checksum = "b217dd0ee0c4021ecf169a4a519b1b4f80d15e3f3765f3dc466223dc0ac891d7" 2308 + dependencies = [ 2309 + "bstr", 2310 + "faster-hex", 2311 + "gix-trace", 2312 + "thiserror 2.0.18", 2313 + ] 2314 + 2315 + [[package]] 2316 + name = "gix-path" 2317 + version = "0.12.1" 2318 + source = "registry+https://github.com/rust-lang/crates.io-index" 2319 + checksum = "afa6ac14cd14939ea94a496ce7460daa6511c09f5b84757e9cfc6f9c8d0f93a6" 2320 + dependencies = [ 2321 + "bstr", 2322 + "gix-trace", 2323 + "gix-validate", 2324 + "thiserror 2.0.18", 2325 + ] 2326 + 2327 + [[package]] 2328 + name = "gix-pathspec" 2329 + version = "0.18.1" 2330 + source = "registry+https://github.com/rust-lang/crates.io-index" 2331 + checksum = "3050783b41ee11511e1e8fb35623df81806194f4030395f14f48ea37c2798c9f" 2332 + dependencies = [ 2333 + "bitflags 2.11.1", 2334 + "bstr", 2335 + "gix-attributes", 2336 + "gix-config-value", 2337 + "gix-glob", 2338 + "gix-path", 2339 + "thiserror 2.0.18", 2340 + ] 2341 + 2342 + [[package]] 2343 + name = "gix-prompt" 2344 + version = "0.15.1" 2345 + source = "registry+https://github.com/rust-lang/crates.io-index" 2346 + checksum = "3ee604d7746080ae7e1023bf47204bcc2c5f307bfbe2306a3c90b1bfd1a2c6d8" 2347 + dependencies = [ 2348 + "gix-command", 2349 + "gix-config-value", 2350 + "parking_lot", 2351 + "rustix", 2352 + "thiserror 2.0.18", 2353 + ] 2354 + 2355 + [[package]] 2356 + name = "gix-protocol" 2357 + version = "0.62.0" 2358 + source = "registry+https://github.com/rust-lang/crates.io-index" 2359 + checksum = "51dea3acb390707ab868f1f9584f18449eb95d869deffae96768e47d303595ee" 2360 + dependencies = [ 2361 + "bstr", 2362 + "gix-date", 2363 + "gix-features", 2364 + "gix-hash", 2365 + "gix-ref", 2366 + "gix-shallow", 2367 + "gix-transport", 2368 + "gix-utils", 2369 + "maybe-async", 2370 + "nonempty", 2371 + "thiserror 2.0.18", 2372 + ] 2373 + 2374 + [[package]] 2375 + name = "gix-quote" 2376 + version = "0.7.2" 2377 + source = "registry+https://github.com/rust-lang/crates.io-index" 2378 + checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef" 2379 + dependencies = [ 2380 + "bstr", 2381 + "gix-error", 2382 + "gix-utils", 2383 + ] 2384 + 2385 + [[package]] 2386 + name = "gix-ref" 2387 + version = "0.64.0" 2388 + source = "registry+https://github.com/rust-lang/crates.io-index" 2389 + checksum = "4c04f64c37eb7e6feb73c7060f8dc6f381cc5de5d53249bfd450bc48a86b2e8b" 2390 + dependencies = [ 2391 + "gix-actor", 2392 + "gix-features", 2393 + "gix-fs", 2394 + "gix-hash", 2395 + "gix-lock", 2396 + "gix-object", 2397 + "gix-path", 2398 + "gix-tempfile", 2399 + "gix-utils", 2400 + "gix-validate", 2401 + "memmap2", 2402 + "thiserror 2.0.18", 2403 + ] 2404 + 2405 + [[package]] 2406 + name = "gix-refspec" 2407 + version = "0.42.0" 2408 + source = "registry+https://github.com/rust-lang/crates.io-index" 2409 + checksum = "b216ae06ec74b5f24ad0142026a997fb0a935b7410eaf9c1616fc3f0e6c5a6d3" 2410 + dependencies = [ 2411 + "bstr", 2412 + "gix-error", 2413 + "gix-glob", 2414 + "gix-hash", 2415 + "gix-revision", 2416 + "gix-validate", 2417 + "smallvec", 2418 + "thiserror 2.0.18", 2419 + ] 2420 + 2421 + [[package]] 2422 + name = "gix-revision" 2423 + version = "0.46.0" 2424 + source = "registry+https://github.com/rust-lang/crates.io-index" 2425 + checksum = "0b47c88884dd3c1a19a39da19d10211fcdea2809aadc86869b6e824a1774340f" 2426 + dependencies = [ 2427 + "bitflags 2.11.1", 2428 + "bstr", 2429 + "gix-commitgraph", 2430 + "gix-date", 2431 + "gix-error", 2432 + "gix-hash", 2433 + "gix-hashtable", 2434 + "gix-object", 2435 + "gix-revwalk", 2436 + "gix-trace", 2437 + "nonempty", 2438 + ] 2439 + 2440 + [[package]] 2441 + name = "gix-revwalk" 2442 + version = "0.32.0" 2443 + source = "registry+https://github.com/rust-lang/crates.io-index" 2444 + checksum = "85f5756abffe0917827aac683b13684ed99875bc398fa1f9b8f479b0681ef9e6" 2445 + dependencies = [ 2446 + "gix-commitgraph", 2447 + "gix-date", 2448 + "gix-error", 2449 + "gix-hash", 2450 + "gix-hashtable", 2451 + "gix-object", 2452 + "smallvec", 2453 + "thiserror 2.0.18", 2454 + ] 2455 + 2456 + [[package]] 2457 + name = "gix-sec" 2458 + version = "0.14.1" 2459 + source = "registry+https://github.com/rust-lang/crates.io-index" 2460 + checksum = "ab8519976e4c7e486270740a5400369f37940779b80bd1377d94cfa1125d01b3" 2461 + dependencies = [ 2462 + "bitflags 2.11.1", 2463 + "gix-path", 2464 + "libc", 2465 + "windows-sys 0.61.2", 2466 + ] 2467 + 2468 + [[package]] 2469 + name = "gix-shallow" 2470 + version = "0.12.1" 2471 + source = "registry+https://github.com/rust-lang/crates.io-index" 2472 + checksum = "a292fc2fe548c5dfa575479d16b445b0ddf1dd2f56f1fec6aed386f82553cd97" 2473 + dependencies = [ 2474 + "bstr", 2475 + "gix-hash", 2476 + "gix-lock", 2477 + "nonempty", 2478 + "thiserror 2.0.18", 2479 + ] 2480 + 2481 + [[package]] 2482 + name = "gix-status" 2483 + version = "0.31.0" 2484 + source = "registry+https://github.com/rust-lang/crates.io-index" 2485 + checksum = "22042e385d28a34275e029d98f4970285045be14b9073658ca897923f2ed8700" 2486 + dependencies = [ 2487 + "bstr", 2488 + "filetime", 2489 + "gix-diff", 2490 + "gix-dir", 2491 + "gix-features", 2492 + "gix-filter", 2493 + "gix-fs", 2494 + "gix-hash", 2495 + "gix-index", 2496 + "gix-object", 2497 + "gix-path", 2498 + "gix-pathspec", 2499 + "gix-worktree", 2500 + "portable-atomic", 2501 + "thiserror 2.0.18", 2502 + ] 2503 + 2504 + [[package]] 2505 + name = "gix-submodule" 2506 + version = "0.31.0" 2507 + source = "registry+https://github.com/rust-lang/crates.io-index" 2508 + checksum = "3059890ef054066c22a94bfc6a3eaba0d806aedcd630a0bc9e5783fd88884781" 2509 + dependencies = [ 2510 + "bstr", 2511 + "gix-config", 2512 + "gix-path", 2513 + "gix-pathspec", 2514 + "gix-refspec", 2515 + "gix-url", 2516 + "thiserror 2.0.18", 2517 + ] 2518 + 2519 + [[package]] 2520 + name = "gix-tempfile" 2521 + version = "23.0.2" 2522 + source = "registry+https://github.com/rust-lang/crates.io-index" 2523 + checksum = "6ef60812443484e67bf84e444cc71b4c78ae62deb822221774a4fa0c57fdb17f" 2524 + dependencies = [ 2525 + "dashmap", 2526 + "gix-fs", 2527 + "libc", 2528 + "parking_lot", 2529 + "signal-hook", 2530 + "signal-hook-registry", 2531 + "tempfile", 2532 + ] 2533 + 2534 + [[package]] 2535 + name = "gix-trace" 2536 + version = "0.1.20" 2537 + source = "registry+https://github.com/rust-lang/crates.io-index" 2538 + checksum = "44dc45eae785c0eb14173e0f152e6e224dcf4d45b6a6999a3aed22af541ad678" 2539 + 2540 + [[package]] 2541 + name = "gix-transport" 2542 + version = "0.57.2" 2543 + source = "registry+https://github.com/rust-lang/crates.io-index" 2544 + checksum = "186874f7ad1fb2f9a2f2aa9c2dabc7f9dd087bef74c1a0eee2b4a9cf0248fcb3" 2545 + dependencies = [ 2546 + "bstr", 2547 + "gix-command", 2548 + "gix-features", 2549 + "gix-packetline", 2550 + "gix-quote", 2551 + "gix-sec", 2552 + "gix-url", 2553 + "thiserror 2.0.18", 2554 + ] 2555 + 2556 + [[package]] 2557 + name = "gix-traverse" 2558 + version = "0.58.0" 2559 + source = "registry+https://github.com/rust-lang/crates.io-index" 2560 + checksum = "e8de590ecc86a3b2870665f2288324fa9f7f8672c7fc2d4e020fdd81cd1f7aed" 2561 + dependencies = [ 2562 + "bitflags 2.11.1", 2563 + "gix-commitgraph", 2564 + "gix-date", 2565 + "gix-hash", 2566 + "gix-hashtable", 2567 + "gix-object", 2568 + "gix-revwalk", 2569 + "smallvec", 2570 + "thiserror 2.0.18", 2571 + ] 2572 + 2573 + [[package]] 2574 + name = "gix-url" 2575 + version = "0.36.1" 2576 + source = "registry+https://github.com/rust-lang/crates.io-index" 2577 + checksum = "65bb01ec69d55e82ccb7a19e264501ead4e6aac38463a8cebfdd81e22bb67ab2" 2578 + dependencies = [ 2579 + "bstr", 2580 + "gix-path", 2581 + "percent-encoding", 2582 + "thiserror 2.0.18", 2583 + ] 2584 + 2585 + [[package]] 2586 + name = "gix-utils" 2587 + version = "0.3.3" 2588 + source = "registry+https://github.com/rust-lang/crates.io-index" 2589 + checksum = "66c50966184123caf580ffa64e28031a878597f1c7fceb8fe19566c38eb1b771" 2590 + dependencies = [ 2591 + "bstr", 2592 + "fastrand", 2593 + "unicode-normalization", 2594 + ] 2595 + 2596 + [[package]] 2597 + name = "gix-validate" 2598 + version = "0.11.2" 2599 + source = "registry+https://github.com/rust-lang/crates.io-index" 2600 + checksum = "7bc6fc771c4063ba7cd2f47b91fb6076251c6a823b64b7fe7b8874b0fe4afae3" 2601 + dependencies = [ 2602 + "bstr", 2603 + ] 2604 + 2605 + [[package]] 2606 + name = "gix-worktree" 2607 + version = "0.53.0" 2608 + source = "registry+https://github.com/rust-lang/crates.io-index" 2609 + checksum = "cef414ed275e8407cd5d53d301e83be19700b0dd3f859d2434417b58f454a2d1" 2610 + dependencies = [ 2611 + "bstr", 2612 + "gix-attributes", 2613 + "gix-fs", 2614 + "gix-glob", 2615 + "gix-hash", 2616 + "gix-ignore", 2617 + "gix-index", 2618 + "gix-object", 2619 + "gix-path", 2620 + "gix-validate", 2621 + ] 2622 + 2623 + [[package]] 2624 + name = "gix-worktree-state" 2625 + version = "0.31.0" 2626 + source = "registry+https://github.com/rust-lang/crates.io-index" 2627 + checksum = "4bffae8b3ca258fdd50370cd51f06deb4c76a3b43db3868bc28dde45ffa77d69" 2628 + dependencies = [ 2629 + "bstr", 2630 + "gix-features", 2631 + "gix-filter", 2632 + "gix-fs", 2633 + "gix-index", 2634 + "gix-object", 2635 + "gix-path", 2636 + "gix-worktree", 2637 + "io-close", 2638 + "thiserror 2.0.18", 2639 + ] 2640 + 2641 + [[package]] 2642 + name = "gix-worktree-stream" 2643 + version = "0.33.0" 2644 + source = "registry+https://github.com/rust-lang/crates.io-index" 2645 + checksum = "d25e9ed30100c63f7590bc581c225e53f731a53e06aa79a245739c07f7dcc557" 2646 + dependencies = [ 2647 + "gix-attributes", 2648 + "gix-error", 2649 + "gix-features", 2650 + "gix-filter", 2651 + "gix-fs", 2652 + "gix-hash", 2653 + "gix-object", 2654 + "gix-path", 2655 + "gix-traverse", 2656 + "parking_lot", 2657 + ] 2658 + 2659 + [[package]] 1599 2660 name = "group" 1600 2661 version = "0.13.0" 1601 2662 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1646 2707 ] 1647 2708 1648 2709 [[package]] 2710 + name = "hash32" 2711 + version = "0.3.1" 2712 + source = "registry+https://github.com/rust-lang/crates.io-index" 2713 + checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 2714 + dependencies = [ 2715 + "byteorder", 2716 + ] 2717 + 2718 + [[package]] 1649 2719 name = "hashbrown" 1650 2720 version = "0.14.5" 1651 2721 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1682 2752 version = "0.17.1" 1683 2753 source = "registry+https://github.com/rust-lang/crates.io-index" 1684 2754 checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" 2755 + dependencies = [ 2756 + "allocator-api2", 2757 + "equivalent", 2758 + "foldhash 0.2.0", 2759 + ] 1685 2760 1686 2761 [[package]] 1687 2762 name = "heapless" ··· 1690 2765 checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" 1691 2766 dependencies = [ 1692 2767 "atomic-polyfill", 1693 - "hash32", 2768 + "hash32 0.2.1", 1694 2769 "rustc_version", 1695 2770 "serde", 1696 2771 "spin 0.9.8", ··· 1698 2773 ] 1699 2774 1700 2775 [[package]] 2776 + name = "heapless" 2777 + version = "0.8.0" 2778 + source = "registry+https://github.com/rust-lang/crates.io-index" 2779 + checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 2780 + dependencies = [ 2781 + "hash32 0.3.1", 2782 + "stable_deref_trait", 2783 + ] 2784 + 2785 + [[package]] 1701 2786 name = "heck" 1702 2787 version = "0.4.1" 1703 2788 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1727 2812 source = "registry+https://github.com/rust-lang/crates.io-index" 1728 2813 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1729 2814 dependencies = [ 1730 - "digest", 2815 + "digest 0.10.7", 1731 2816 ] 1732 2817 1733 2818 [[package]] ··· 1780 2865 version = "1.0.3" 1781 2866 source = "registry+https://github.com/rust-lang/crates.io-index" 1782 2867 checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 2868 + 2869 + [[package]] 2870 + name = "human_format" 2871 + version = "1.2.1" 2872 + source = "registry+https://github.com/rust-lang/crates.io-index" 2873 + checksum = "eaec953f16e5bcf6b8a3cb3aa959b17e5577dbd2693e94554c462c08be22624b" 2874 + 2875 + [[package]] 2876 + name = "hybrid-array" 2877 + version = "0.4.12" 2878 + source = "registry+https://github.com/rust-lang/crates.io-index" 2879 + checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" 2880 + dependencies = [ 2881 + "typenum", 2882 + ] 1783 2883 1784 2884 [[package]] 1785 2885 name = "hyper" ··· 2018 3118 ] 2019 3119 2020 3120 [[package]] 3121 + name = "io-close" 3122 + version = "0.3.7" 3123 + source = "registry+https://github.com/rust-lang/crates.io-index" 3124 + checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" 3125 + dependencies = [ 3126 + "libc", 3127 + "winapi", 3128 + ] 3129 + 3130 + [[package]] 2021 3131 name = "ipld-core" 2022 3132 version = "0.4.3" 2023 3133 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2152 3262 "serde_path_to_error", 2153 3263 "serde_repr", 2154 3264 "serde_with", 2155 - "sha2", 3265 + "sha2 0.10.9", 2156 3266 "syn", 2157 3267 "thiserror 2.0.18", 2158 3268 "unicode-segmentation", 2159 3269 ] 2160 3270 2161 3271 [[package]] 3272 + name = "jiff" 3273 + version = "0.2.29" 3274 + source = "registry+https://github.com/rust-lang/crates.io-index" 3275 + checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" 3276 + dependencies = [ 3277 + "defmt", 3278 + "jiff-static", 3279 + "jiff-tzdb-platform", 3280 + "log", 3281 + "portable-atomic", 3282 + "portable-atomic-util", 3283 + "serde_core", 3284 + "windows-link", 3285 + ] 3286 + 3287 + [[package]] 3288 + name = "jiff-static" 3289 + version = "0.2.29" 3290 + source = "registry+https://github.com/rust-lang/crates.io-index" 3291 + checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" 3292 + dependencies = [ 3293 + "proc-macro2", 3294 + "quote", 3295 + "syn", 3296 + ] 3297 + 3298 + [[package]] 3299 + name = "jiff-tzdb" 3300 + version = "0.1.6" 3301 + source = "registry+https://github.com/rust-lang/crates.io-index" 3302 + checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" 3303 + 3304 + [[package]] 3305 + name = "jiff-tzdb-platform" 3306 + version = "0.1.3" 3307 + source = "registry+https://github.com/rust-lang/crates.io-index" 3308 + checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" 3309 + dependencies = [ 3310 + "jiff-tzdb", 3311 + ] 3312 + 3313 + [[package]] 2162 3314 name = "jobserver" 2163 3315 version = "0.1.34" 2164 3316 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2189 3341 "cfg-if", 2190 3342 "ecdsa", 2191 3343 "elliptic-curve", 2192 - "sha2", 3344 + "sha2 0.10.9", 3345 + ] 3346 + 3347 + [[package]] 3348 + name = "kstring" 3349 + version = "2.0.2" 3350 + source = "registry+https://github.com/rust-lang/crates.io-index" 3351 + checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" 3352 + dependencies = [ 3353 + "static_assertions", 2193 3354 ] 2194 3355 2195 3356 [[package]] ··· 2225 3386 version = "0.2.186" 2226 3387 source = "registry+https://github.com/rust-lang/crates.io-index" 2227 3388 checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" 3389 + 3390 + [[package]] 3391 + name = "line-numbers" 3392 + version = "0.4.0" 3393 + source = "registry+https://github.com/rust-lang/crates.io-index" 3394 + checksum = "24ab6409a2f9ffae158ae823a012a7d7d68386663a14bedc260ca66eacf65c2c" 2228 3395 2229 3396 [[package]] 2230 3397 name = "linux-raw-sys" ··· 2325 3492 version = "0.8.4" 2326 3493 source = "registry+https://github.com/rust-lang/crates.io-index" 2327 3494 checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 3495 + 3496 + [[package]] 3497 + name = "maybe-async" 3498 + version = "0.2.11" 3499 + source = "registry+https://github.com/rust-lang/crates.io-index" 3500 + checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" 3501 + dependencies = [ 3502 + "proc-macro2", 3503 + "quote", 3504 + "syn", 3505 + ] 2328 3506 2329 3507 [[package]] 2330 3508 name = "measure_time" ··· 2490 3668 source = "registry+https://github.com/rust-lang/crates.io-index" 2491 3669 checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" 2492 3670 dependencies = [ 2493 - "bitflags", 3671 + "bitflags 2.11.1", 2494 3672 "cfg-if", 2495 3673 "cfg_aliases", 2496 3674 "libc", ··· 2508 3686 ] 2509 3687 2510 3688 [[package]] 3689 + name = "nonempty" 3690 + version = "0.12.0" 3691 + source = "registry+https://github.com/rust-lang/crates.io-index" 3692 + checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" 3693 + 3694 + [[package]] 2511 3695 name = "nu-ansi-term" 2512 3696 version = "0.50.3" 2513 3697 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2640 3824 "ecdsa", 2641 3825 "elliptic-curve", 2642 3826 "primeorder", 2643 - "sha2", 3827 + "sha2 0.10.9", 2644 3828 ] 2645 3829 2646 3830 [[package]] ··· 2805 3989 checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" 2806 3990 2807 3991 [[package]] 3992 + name = "portable-atomic-util" 3993 + version = "0.2.7" 3994 + source = "registry+https://github.com/rust-lang/crates.io-index" 3995 + checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" 3996 + dependencies = [ 3997 + "portable-atomic", 3998 + ] 3999 + 4000 + [[package]] 2808 4001 name = "postcard" 2809 4002 version = "1.1.3" 2810 4003 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2813 4006 "cobs", 2814 4007 "embedded-io 0.4.0", 2815 4008 "embedded-io 0.6.1", 2816 - "heapless", 4009 + "heapless 0.7.17", 2817 4010 "serde", 2818 4011 ] 2819 4012 ··· 2861 4054 ] 2862 4055 2863 4056 [[package]] 4057 + name = "proc-macro-error-attr2" 4058 + version = "2.0.0" 4059 + source = "registry+https://github.com/rust-lang/crates.io-index" 4060 + checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 4061 + dependencies = [ 4062 + "proc-macro2", 4063 + "quote", 4064 + ] 4065 + 4066 + [[package]] 4067 + name = "proc-macro-error2" 4068 + version = "2.0.1" 4069 + source = "registry+https://github.com/rust-lang/crates.io-index" 4070 + checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 4071 + dependencies = [ 4072 + "proc-macro-error-attr2", 4073 + "proc-macro2", 4074 + "quote", 4075 + "syn", 4076 + ] 4077 + 4078 + [[package]] 2864 4079 name = "proc-macro2" 2865 4080 version = "1.0.106" 2866 4081 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2883 4098 ] 2884 4099 2885 4100 [[package]] 4101 + name = "prodash" 4102 + version = "31.0.0" 4103 + source = "registry+https://github.com/rust-lang/crates.io-index" 4104 + checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" 4105 + dependencies = [ 4106 + "bytesize", 4107 + "human_format", 4108 + "parking_lot", 4109 + ] 4110 + 4111 + [[package]] 2886 4112 name = "prost" 2887 4113 version = "0.14.3" 2888 4114 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3008 4234 source = "registry+https://github.com/rust-lang/crates.io-index" 3009 4235 checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" 3010 4236 dependencies = [ 3011 - "bitflags", 4237 + "bitflags 2.11.1", 3012 4238 "memchr", 3013 4239 "unicase", 3014 4240 ] ··· 3195 4421 source = "registry+https://github.com/rust-lang/crates.io-index" 3196 4422 checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 3197 4423 dependencies = [ 3198 - "bitflags", 4424 + "bitflags 2.11.1", 3199 4425 ] 3200 4426 3201 4427 [[package]] ··· 3352 4578 source = "registry+https://github.com/rust-lang/crates.io-index" 3353 4579 checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 3354 4580 dependencies = [ 3355 - "bitflags", 4581 + "bitflags 2.11.1", 3356 4582 "errno", 3357 4583 "libc", 3358 4584 "linux-raw-sys", ··· 3493 4719 source = "registry+https://github.com/rust-lang/crates.io-index" 3494 4720 checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" 3495 4721 dependencies = [ 3496 - "bitflags", 4722 + "bitflags 2.11.1", 3497 4723 "core-foundation 0.10.1", 3498 4724 "core-foundation-sys", 3499 4725 "libc", ··· 3687 4913 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 3688 4914 dependencies = [ 3689 4915 "cfg-if", 3690 - "cpufeatures", 3691 - "digest", 4916 + "cpufeatures 0.2.17", 4917 + "digest 0.10.7", 4918 + ] 4919 + 4920 + [[package]] 4921 + name = "sha1-checked" 4922 + version = "0.10.0" 4923 + source = "registry+https://github.com/rust-lang/crates.io-index" 4924 + checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" 4925 + dependencies = [ 4926 + "digest 0.10.7", 4927 + "sha1", 3692 4928 ] 3693 4929 3694 4930 [[package]] ··· 3698 4934 checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 3699 4935 dependencies = [ 3700 4936 "cfg-if", 3701 - "cpufeatures", 3702 - "digest", 4937 + "cpufeatures 0.2.17", 4938 + "digest 0.10.7", 4939 + ] 4940 + 4941 + [[package]] 4942 + name = "sha2" 4943 + version = "0.11.0" 4944 + source = "registry+https://github.com/rust-lang/crates.io-index" 4945 + checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" 4946 + dependencies = [ 4947 + "cfg-if", 4948 + "cpufeatures 0.3.0", 4949 + "digest 0.11.3", 3703 4950 ] 3704 4951 3705 4952 [[package]] ··· 3710 4957 dependencies = [ 3711 4958 "lazy_static", 3712 4959 ] 4960 + 4961 + [[package]] 4962 + name = "shell-words" 4963 + version = "1.1.1" 4964 + source = "registry+https://github.com/rust-lang/crates.io-index" 4965 + checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" 3713 4966 3714 4967 [[package]] 3715 4968 name = "shlex" ··· 3735 4988 "tokio-vsock", 3736 4989 "tracing", 3737 4990 "tracing-subscriber", 4991 + ] 4992 + 4993 + [[package]] 4994 + name = "signal-hook" 4995 + version = "0.4.4" 4996 + source = "registry+https://github.com/rust-lang/crates.io-index" 4997 + checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" 4998 + dependencies = [ 4999 + "libc", 5000 + "signal-hook-registry", 3738 5001 ] 3739 5002 3740 5003 [[package]] ··· 3753 5016 source = "registry+https://github.com/rust-lang/crates.io-index" 3754 5017 checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 3755 5018 dependencies = [ 3756 - "digest", 5019 + "digest 0.10.7", 3757 5020 "rand_core 0.6.4", 3758 5021 ] 3759 5022 ··· 3896 5159 source = "registry+https://github.com/rust-lang/crates.io-index" 3897 5160 checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" 3898 5161 dependencies = [ 3899 - "bitflags", 5162 + "bitflags 2.11.1", 3900 5163 "core-foundation 0.9.4", 3901 5164 "system-configuration-sys", 3902 5165 ] ··· 4264 5527 "futures-core", 4265 5528 "pin-project-lite", 4266 5529 "tokio", 5530 + "tokio-util", 4267 5531 ] 4268 5532 4269 5533 [[package]] ··· 4477 5741 checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" 4478 5742 dependencies = [ 4479 5743 "async-compression", 4480 - "bitflags", 5744 + "bitflags 2.11.1", 4481 5745 "bytes", 4482 5746 "futures-core", 4483 5747 "futures-util", ··· 4679 5943 ] 4680 5944 4681 5945 [[package]] 5946 + name = "uluru" 5947 + version = "3.1.0" 5948 + source = "registry+https://github.com/rust-lang/crates.io-index" 5949 + checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" 5950 + dependencies = [ 5951 + "arrayvec", 5952 + ] 5953 + 5954 + [[package]] 4682 5955 name = "unicase" 4683 5956 version = "2.9.0" 4684 5957 source = "registry+https://github.com/rust-lang/crates.io-index" 4685 5958 checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" 4686 5959 4687 5960 [[package]] 5961 + name = "unicode-bom" 5962 + version = "2.0.3" 5963 + source = "registry+https://github.com/rust-lang/crates.io-index" 5964 + checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" 5965 + 5966 + [[package]] 4688 5967 name = "unicode-ident" 4689 5968 version = "1.0.24" 4690 5969 source = "registry+https://github.com/rust-lang/crates.io-index" 4691 5970 checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 5971 + 5972 + [[package]] 5973 + name = "unicode-normalization" 5974 + version = "0.1.25" 5975 + source = "registry+https://github.com/rust-lang/crates.io-index" 5976 + checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" 5977 + dependencies = [ 5978 + "tinyvec", 5979 + ] 4692 5980 4693 5981 [[package]] 4694 5982 name = "unicode-segmentation" ··· 4930 6218 source = "registry+https://github.com/rust-lang/crates.io-index" 4931 6219 checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" 4932 6220 dependencies = [ 4933 - "bitflags", 6221 + "bitflags 2.11.1", 4934 6222 "hashbrown 0.15.5", 4935 6223 "indexmap", 4936 6224 "semver", ··· 5339 6627 checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" 5340 6628 dependencies = [ 5341 6629 "anyhow", 5342 - "bitflags", 6630 + "bitflags 2.11.1", 5343 6631 "indexmap", 5344 6632 "log", 5345 6633 "serde", ··· 5483 6771 "quote", 5484 6772 "syn", 5485 6773 ] 6774 + 6775 + [[package]] 6776 + name = "zlib-rs" 6777 + version = "0.6.4" 6778 + source = "registry+https://github.com/rust-lang/crates.io-index" 6779 + checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" 5486 6780 5487 6781 [[package]] 5488 6782 name = "zmij"
+12
appview/models/pull.go
··· 87 87 Repo *Repo 88 88 } 89 89 90 + func (p *Pull) SourceRepoDid() syntax.DID { 91 + if p.PullSource != nil && p.PullSource.RepoDid != nil { 92 + return *p.PullSource.RepoDid 93 + } 94 + return p.RepoDid 95 + } 96 + 97 + // HEAD returns PR's current HEAD commit ID. 98 + func (p *Pull) HEAD() string { 99 + panic("unimplemented") 100 + } 101 + 90 102 // NOTE: This method does not include patch blob in returned atproto record 91 103 func (p Pull) AsRecord() tangled.RepoPull { 92 104 mentions := make([]string, len(p.Mentions))
+45
appview/pages/pages.go
··· 1427 1427 VouchSkips map[syntax.DID]bool 1428 1428 } 1429 1429 1430 + type PullPageBaseParams struct { 1431 + BaseParams 1432 + Pull *models.Pull 1433 + 1434 + Backlinks []models.RichReferenceLink 1435 + Comments []models.Comment 1436 + Commits []types.Commit // all commits between <target>..<pr/head> 1437 + 1438 + LabelDefs map[string]*models.LabelDefinition 1439 + Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData 1440 + UserReacted map[syntax.ATURI]map[models.ReactionKind]bool 1441 + VouchRelationships map[syntax.DID]*models.VouchRelationship 1442 + VouchSkips map[syntax.DID]bool 1443 + 1444 + // diff, branch-delete-status, merge-check, resubmit-check, pipelines will be lazy-loaded. 1445 + } 1446 + 1447 + // /pulls/123/2/1a2b3c..d4e5f6 1448 + type PullDiffParams struct { 1449 + PullPageBaseParams 1450 + Version int 1451 + BaseCommitId string 1452 + HeadCommitId string 1453 + 1454 + ErrorMsg string 1455 + } 1456 + 1457 + // /pulls/123/1..2/abcdef 1458 + type PullInterdiffParams struct { 1459 + PullPageBaseParams 1460 + Version1 int 1461 + Version2 int 1462 + ChangeId string // optional change-id filter 1463 + 1464 + ErrorMsg string 1465 + } 1466 + 1467 + func (p *Pages) PullDiff(w io.Writer, params PullDiffParams) error { 1468 + panic("unimplemented") 1469 + } 1470 + 1471 + func (p *Pages) PullInterdiff(w io.Writer, params PullInterdiffParams) error { 1472 + panic("unimplemented") 1473 + } 1474 + 1430 1475 func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { 1431 1476 params.Active = "pulls" 1432 1477 return p.executeRepo("repo/pulls/pull", w, params)
+280
appview/pulls/pull2.go
··· 1 + package pulls 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "net/http" 7 + "strconv" 8 + 9 + "github.com/bluesky-social/indigo/atproto/syntax" 10 + "github.com/bluesky-social/indigo/lex/util" 11 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 12 + "github.com/go-chi/chi/v5" 13 + "golang.org/x/sync/errgroup" 14 + "tangled.org/core/api/tangled" 15 + "tangled.org/core/appview/models" 16 + "tangled.org/core/appview/pages" 17 + "tangled.org/core/types" 18 + ) 19 + 20 + // NOTE: parsing object in middleware is bad pattern 21 + // you will have to check if object exist in context "just in case" 22 + // so it's better to make helper function that can read the url pattern instead. 23 + 24 + 25 + // A -- B -- C 26 + // (master) (pr/123/0) 27 + // 28 + // A -- B -- C 29 + // \ (pr/123/0) 30 + // `-- D <- B' <- C' 31 + // (master) (pr/123/1) 32 + 33 + // 1. rebase B<-C to D 34 + // 2. compare tree of C and D 35 + 36 + // PullInterDiff is router for /pulls/{pull}/{version}..{version}/{change} 37 + // 38 + // Examples: 39 + // - /pulls/123/0..2/all 40 + // - /pulls/123/0..2/nrpytyzw 41 + func (s *Pulls) PullInterDiff(w http.ResponseWriter, r *http.Request) { 42 + l := s.logger.With("handler", "PullRound") 43 + ctx := r.Context() 44 + 45 + pull, ok := r.Context().Value("pull").(*models.Pull) 46 + if !ok { 47 + l.Error("failed to get pull") 48 + s.pages.Error500(w) 49 + return 50 + } 51 + 52 + var ( 53 + version1 = 0 54 + version2 = 0 55 + changeId = chi.URLParam(r, "*") 56 + ) 57 + if changeId == "all" { 58 + changeId = "" 59 + } 60 + 61 + // defer render 62 + var params pages.PullInterdiffParams 63 + params.Pull = pull 64 + params.Version1 = version1 65 + params.Version2 = version2 66 + params.ChangeId = changeId 67 + defer s.pages.PullInterdiff(w, params) 68 + 69 + // 1. resolve target branch -> (branch, commit) 70 + xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url} 71 + branch, err := tangled.GitTempGetBranch(ctx, xrpcc, pull.TargetBranch, pull.RepoDid.String()) 72 + if err != nil { 73 + panic("unimplemented") 74 + } 75 + 76 + base := branch.Hash 77 + head1 := "" // pull.Versions[version1].Head 78 + head2 := "" // pull.Versions[version2].Head 79 + 80 + // 1. log commits from base..head1 and base..head2 81 + var commits1, commits2 []types.Commit 82 + g, gctx := errgroup.WithContext(ctx) 83 + g.Go(func() error { 84 + commits1, err = getTempListCommits(gctx, xrpcc, pull.SourceRepoDid(), base, head1) 85 + return err 86 + }) 87 + g.Go(func() error { 88 + commits2, err = getTempListCommits(gctx, xrpcc, pull.SourceRepoDid(), base, head2) 89 + return err 90 + }) 91 + if err := g.Wait(); err != nil { 92 + params.ErrorMsg = "something something" 93 + panic("unimplemented") 94 + } 95 + 96 + if changeId != "" { 97 + // interdiff by change-id 98 + var old, new *types.Commit 99 + for _, commit := range commits1 { 100 + if commit.ChangeId == changeId { 101 + old = &commit 102 + break 103 + } 104 + } 105 + for _, commit := range commits2 { 106 + if commit.ChangeId == changeId { 107 + new = &commit 108 + break 109 + } 110 + } 111 + _, _ = old, new 112 + panic("unimplemented") 113 + } else { 114 + // interdiff of two commit ranges 115 + panic("unimplemented") 116 + } 117 + } 118 + 119 + // PullDiff is router for /pulls/{pull}/{version}/{commit}..{commit} 120 + // 121 + // Examples: 122 + // - /pulls/123/latest 123 + // - /pulls/123/2/head 124 + // - /pulls/123/2/base..head 125 + // - /pulls/123/2/a53ab251e..d8add468c 126 + // - /pulls/123/2/d8add468c 127 + func (s *Pulls) PullDiff(w http.ResponseWriter, r *http.Request) { 128 + l := s.logger.With("handler", "PullRound") 129 + ctx := r.Context() 130 + 131 + pull, ok := r.Context().Value("pull").(*models.Pull) 132 + if !ok { 133 + l.Error("failed to get pull") 134 + s.pages.Error500(w) 135 + return 136 + } 137 + 138 + var err error 139 + 140 + var version int 141 + var versionRaw = chi.URLParam(r, "version") 142 + if versionRaw == "latest" { 143 + version = pull.LastRoundNumber() 144 + } else { 145 + version, err = strconv.Atoi(versionRaw) 146 + if err != nil { 147 + // invalid version number. redirect 148 + http.Redirect(w, r, 149 + fmt.Sprintf("/%s/pulls/%d/latest", pull.Repo.RepoIdentifier(), pull.ID), 150 + http.StatusSeeOther, 151 + ) 152 + return 153 + } 154 + } 155 + 156 + var range_ = chi.URLParam(r, "*") 157 + base, head, err := parseRevRange(range_) 158 + if err != nil { 159 + http.Redirect(w, r, 160 + fmt.Sprintf("/%s/pulls/%d/%s", pull.Repo.RepoIdentifier(), pull.ID, versionRaw), 161 + http.StatusSeeOther, 162 + ) 163 + return 164 + } 165 + 166 + // defer render 167 + var params pages.PullDiffParams 168 + params.Pull = pull 169 + params.Version = version 170 + defer s.pages.PullDiff(w, params) 171 + 172 + // 1. resolve target branch -> (branch, commit) 173 + xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url} 174 + branch, err := tangled.GitTempGetBranch(ctx, xrpcc, pull.TargetBranch, pull.RepoDid.String()) 175 + if err != nil { 176 + l.Warn("Failed to resolve target branch", "branch", pull.TargetBranch, "err", err) 177 + params.ErrorMsg = fmt.Sprintf("Failed to resolve target branch %q", pull.TargetBranch) 178 + return 179 + } 180 + 181 + if base == "base" { 182 + base = branch.Hash 183 + } 184 + if head == "head" { 185 + head = pull.HEAD() 186 + } 187 + 188 + sourceRepoDid := pull.SourceRepoDid() 189 + 190 + // 2. list diverged commits using knotmirror (BASE..HEAD) -> ([]commit) 191 + // - knotmirror needs on-demand fetch implementation for this 192 + commits, err := getTempListCommits(ctx, xrpcc, sourceRepoDid, base, head) 193 + if err != nil { 194 + panic("unimplemented") 195 + } 196 + 197 + // 3. list every commits in UI. They will be lazy-loaded 198 + params.Commits = commits 199 + } 200 + 201 + // htmx fragment. render diff between commits 202 + func (s *Pulls) PullDiffFragment(w http.ResponseWriter, r *http.Request) { 203 + // var ( 204 + // base = r.URL.Query().Get("base") // base commit ID 205 + // head = r.URL.Query().Get("head") // head commit ID 206 + // unified = r.URL.Query().Get("view") == "unified" 207 + // ) 208 + 209 + // 1. get commit object 210 + // 2. get diff between parent..commit (knotmirror), parse that diff 211 + // 3. fetch each file entries (& run syntax highlight) <- skip this part for stage 1. we will do this at stage 2. 212 + // 4. render diff 213 + } 214 + 215 + // htmx fragment. render interdiff between changes 216 + func (s *Pulls) PullInterdiffFragment(w http.ResponseWriter, r *http.Request) { 217 + // var ( 218 + // base1 = r.URL.Query().Get("base1") // base1 commit ID 219 + // base2 = r.URL.Query().Get("base2") // base2 commit ID 220 + // head1 = r.URL.Query().Get("head1") // head1 commit ID 221 + // head2 = r.URL.Query().Get("head2") // head1 commit ID 222 + // unified = r.URL.Query().Get("view") == "unified" 223 + // ) 224 + 225 + // 1. compute interdiff. (knotmirror) 226 + // 2. return rich diff data. (knotmirror) 227 + // 3. load old/new blobs & run syntax highlight 228 + // 4. render diff 229 + } 230 + 231 + // parseRevRange parses <head>..<base> string. 232 + // base and head will default to "base" and "head" when omitted. 233 + func parseRevRange(range_ string) (base string, head string, err error) { 234 + panic("unimplemented") 235 + } 236 + 237 + // parseVersionRange parses <version>..<version> string. 238 + // Each versions will default to "base" and "latest" when omitted. 239 + func parseVersionRange(range_ string) (base string, head string, err error) { 240 + panic("unimplemented") 241 + } 242 + 243 + func getTempListCommits(ctx context.Context, xrpcc util.LexClient, repo syntax.DID, base, head string) ([]types.Commit, error) { 244 + panic("unimplemented") 245 + // raw, err := tangled.GitTempListCommits(ctx, xrpcc, "", 1000, head, repo.String()) 246 + // if err != nil { 247 + // return nil, err 248 + // } 249 + // 250 + // var xrpcResp types.RepoLogResponse 251 + // if err := json.Unmarshal(raw, &xrpcResp); err != nil { 252 + // return nil, fmt.Errorf("failed to decode XRPC response: %w", err) 253 + // } 254 + // 255 + // return xrpcResp.Commits, nil 256 + } 257 + 258 + // htmx fragment. render interdiff between commits 259 + func (s *Pulls) PullInterDiffFragment(w http.ResponseWriter, r *http.Request) { 260 + panic("unimplemented") 261 + } 262 + 263 + // gitmirror 264 + // - git.ListCommitsSinceMergeBase(repo, base, head) 265 + // - git.Diff(repo, base, head, mode) 266 + // - git.Interdiff(repo, 267 + 268 + // for interdiff, we want: from{start,end}, to{start,end} 269 + // 1. squash from.start ~ from.end into one commit 270 + // 2. rebase that commit to to.start.parent() 271 + // 3. diff from_squashed.tree and to.end.tree 272 + 273 + // we want git log BASE..HEAD (only commits in HEAD) diverged=false 274 + // and git diff BASE...HEAD (changes from HEAD since merge-base) absolute=false 275 + 276 + // commands.go:56 picks comparison type: 277 + // - diff BASE..HEAD (COMPARISON_TYPE_ONLY_IN_HEAD) = direct 278 + // - diff BASE...HEAD (COMPARISON_TYPE_INTERSECTION) = merge-base. Server resolves merge-base via g.MergeBase() first (diff.go:43) then diffs. 279 + // we want second one. we should compute merge-base first. 280 + // we can have
+1
buf.yaml
··· 1 1 version: v2 2 2 modules: 3 3 - path: spindle/agentproto 4 + - path: gitmirror/proto 4 5 deps: 5 6 - buf.build/bufbuild/protovalidate
+15
docker-compose.yml
··· 283 283 condition: service_started 284 284 networks: [tngl] 285 285 286 + gitmirror: 287 + build: 288 + context: . 289 + dockerfile: localinfra/gitmirror.Dockerfile 290 + restart: unless-stopped 291 + environment: 292 + RUST_LOG: gitmirror=debug 293 + GITMIRROR_ADDR: 0.0.0.0:9000 294 + GITMIRROR_REPO_BASE: /data/repos 295 + volumes: 296 + - knotmirror-data:/data 297 + - ./localinfra/certs/root.crt:/usr/local/share/ca-certificates/caddy.crt:ro 298 + networks: [tngl] 299 + 286 300 zoekt-webserver: 287 301 build: 288 302 context: https://tangled.org/boltless.me/zoekt.git#tngl ··· 379 393 - spindle.tngl.boltless.dev 380 394 - tngl.boltless.dev 381 395 - mirror.tngl.boltless.dev 396 + - gitmirror.tngl.boltless.dev 382 397 - zoekt.tngl.boltless.dev 383 398 384 399 volumes:
+10 -1
gitmirror/Cargo.toml
··· 6 6 rust-version.workspace = true 7 7 8 8 [dependencies] 9 + anyhow = { workspace = true } 10 + clap = { workspace = true } 9 11 tonic = { workspace = true } 10 12 tonic-prost = { workspace = true } 11 13 prost = { workspace = true } 12 - tokio = { workspace = true, features = ["net"] } 14 + tokio = { workspace = true, features = ["net", "rt-multi-thread", "sync"] } 15 + tokio-stream = { workspace = true, features = ["sync"] } 13 16 tracing = "0.1" 14 17 tracing-subscriber = { version = "0.3", features = ["env-filter"] } 18 + line-numbers = "0.4.0" 19 + rustc-hash = "2.1.2" 20 + gix = { version = "0.84", features = ["parallel", "blob-diff", "merge", "sha1", "sha256", "revision", "tree-editor"] } 15 21 16 22 [build-dependencies] 17 23 tonic-prost-build = { workspace = true } 24 + 25 + [dev-dependencies] 26 + tempfile = "3" 18 27 19 28 [lints] 20 29 workspace = true
+3
gitmirror/README.md
··· 1 + ```sh 2 + buf generate --template gitmirror/buf.gen.yaml --path gitmirror/proto/gitmirror/v1/gitmirror.proto 3 + ```
+728
gitmirror/proto/gen/gitmirror.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.11 4 + // protoc (unknown) 5 + // source: gitmirror/v1/gitmirror.proto 6 + 7 + package gitmirrorv1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type CommitsRequest struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + // repo carries the DID as a string; parsing/validation is an impl detail. 27 + Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` 28 + Options *CommitsOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` 29 + unknownFields protoimpl.UnknownFields 30 + sizeCache protoimpl.SizeCache 31 + } 32 + 33 + func (x *CommitsRequest) Reset() { 34 + *x = CommitsRequest{} 35 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0] 36 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 + ms.StoreMessageInfo(mi) 38 + } 39 + 40 + func (x *CommitsRequest) String() string { 41 + return protoimpl.X.MessageStringOf(x) 42 + } 43 + 44 + func (*CommitsRequest) ProtoMessage() {} 45 + 46 + func (x *CommitsRequest) ProtoReflect() protoreflect.Message { 47 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0] 48 + if x != nil { 49 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 50 + if ms.LoadMessageInfo() == nil { 51 + ms.StoreMessageInfo(mi) 52 + } 53 + return ms 54 + } 55 + return mi.MessageOf(x) 56 + } 57 + 58 + // Deprecated: Use CommitsRequest.ProtoReflect.Descriptor instead. 59 + func (*CommitsRequest) Descriptor() ([]byte, []int) { 60 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{0} 61 + } 62 + 63 + func (x *CommitsRequest) GetRepo() string { 64 + if x != nil { 65 + return x.Repo 66 + } 67 + return "" 68 + } 69 + 70 + func (x *CommitsRequest) GetOptions() *CommitsOptions { 71 + if x != nil { 72 + return x.Options 73 + } 74 + return nil 75 + } 76 + 77 + type CommitsOptions struct { 78 + state protoimpl.MessageState `protogen:"open.v1"` 79 + unknownFields protoimpl.UnknownFields 80 + sizeCache protoimpl.SizeCache 81 + } 82 + 83 + func (x *CommitsOptions) Reset() { 84 + *x = CommitsOptions{} 85 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1] 86 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 87 + ms.StoreMessageInfo(mi) 88 + } 89 + 90 + func (x *CommitsOptions) String() string { 91 + return protoimpl.X.MessageStringOf(x) 92 + } 93 + 94 + func (*CommitsOptions) ProtoMessage() {} 95 + 96 + func (x *CommitsOptions) ProtoReflect() protoreflect.Message { 97 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1] 98 + if x != nil { 99 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 100 + if ms.LoadMessageInfo() == nil { 101 + ms.StoreMessageInfo(mi) 102 + } 103 + return ms 104 + } 105 + return mi.MessageOf(x) 106 + } 107 + 108 + // Deprecated: Use CommitsOptions.ProtoReflect.Descriptor instead. 109 + func (*CommitsOptions) Descriptor() ([]byte, []int) { 110 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{1} 111 + } 112 + 113 + type CommitsResponse struct { 114 + state protoimpl.MessageState `protogen:"open.v1"` 115 + // maps the Go []string return. 116 + Commits []string `protobuf:"bytes,1,rep,name=commits,proto3" json:"commits,omitempty"` 117 + unknownFields protoimpl.UnknownFields 118 + sizeCache protoimpl.SizeCache 119 + } 120 + 121 + func (x *CommitsResponse) Reset() { 122 + *x = CommitsResponse{} 123 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2] 124 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 125 + ms.StoreMessageInfo(mi) 126 + } 127 + 128 + func (x *CommitsResponse) String() string { 129 + return protoimpl.X.MessageStringOf(x) 130 + } 131 + 132 + func (*CommitsResponse) ProtoMessage() {} 133 + 134 + func (x *CommitsResponse) ProtoReflect() protoreflect.Message { 135 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2] 136 + if x != nil { 137 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 138 + if ms.LoadMessageInfo() == nil { 139 + ms.StoreMessageInfo(mi) 140 + } 141 + return ms 142 + } 143 + return mi.MessageOf(x) 144 + } 145 + 146 + // Deprecated: Use CommitsResponse.ProtoReflect.Descriptor instead. 147 + func (*CommitsResponse) Descriptor() ([]byte, []int) { 148 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{2} 149 + } 150 + 151 + func (x *CommitsResponse) GetCommits() []string { 152 + if x != nil { 153 + return x.Commits 154 + } 155 + return nil 156 + } 157 + 158 + type DiffRequest struct { 159 + state protoimpl.MessageState `protogen:"open.v1"` 160 + // repo carries the DID as a string; parsing/validation is an impl detail. 161 + Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` 162 + // base/head are commit-ish ref strings. 163 + Base string `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"` 164 + Head string `protobuf:"bytes,3,opt,name=head,proto3" json:"head,omitempty"` 165 + unknownFields protoimpl.UnknownFields 166 + sizeCache protoimpl.SizeCache 167 + } 168 + 169 + func (x *DiffRequest) Reset() { 170 + *x = DiffRequest{} 171 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3] 172 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 173 + ms.StoreMessageInfo(mi) 174 + } 175 + 176 + func (x *DiffRequest) String() string { 177 + return protoimpl.X.MessageStringOf(x) 178 + } 179 + 180 + func (*DiffRequest) ProtoMessage() {} 181 + 182 + func (x *DiffRequest) ProtoReflect() protoreflect.Message { 183 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3] 184 + if x != nil { 185 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 186 + if ms.LoadMessageInfo() == nil { 187 + ms.StoreMessageInfo(mi) 188 + } 189 + return ms 190 + } 191 + return mi.MessageOf(x) 192 + } 193 + 194 + // Deprecated: Use DiffRequest.ProtoReflect.Descriptor instead. 195 + func (*DiffRequest) Descriptor() ([]byte, []int) { 196 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{3} 197 + } 198 + 199 + func (x *DiffRequest) GetRepo() string { 200 + if x != nil { 201 + return x.Repo 202 + } 203 + return "" 204 + } 205 + 206 + func (x *DiffRequest) GetBase() string { 207 + if x != nil { 208 + return x.Base 209 + } 210 + return "" 211 + } 212 + 213 + func (x *DiffRequest) GetHead() string { 214 + if x != nil { 215 + return x.Head 216 + } 217 + return "" 218 + } 219 + 220 + type InterdiffRequest struct { 221 + state protoimpl.MessageState `protogen:"open.v1"` 222 + // repo carries the DID as a string. 223 + Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` 224 + // Old patch range (from_base..from_head) and new patch range (to_base..to_head). 225 + FromBase string `protobuf:"bytes,2,opt,name=from_base,json=fromBase,proto3" json:"from_base,omitempty"` 226 + FromHead string `protobuf:"bytes,3,opt,name=from_head,json=fromHead,proto3" json:"from_head,omitempty"` 227 + ToBase string `protobuf:"bytes,4,opt,name=to_base,json=toBase,proto3" json:"to_base,omitempty"` 228 + ToHead string `protobuf:"bytes,5,opt,name=to_head,json=toHead,proto3" json:"to_head,omitempty"` 229 + unknownFields protoimpl.UnknownFields 230 + sizeCache protoimpl.SizeCache 231 + } 232 + 233 + func (x *InterdiffRequest) Reset() { 234 + *x = InterdiffRequest{} 235 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4] 236 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 237 + ms.StoreMessageInfo(mi) 238 + } 239 + 240 + func (x *InterdiffRequest) String() string { 241 + return protoimpl.X.MessageStringOf(x) 242 + } 243 + 244 + func (*InterdiffRequest) ProtoMessage() {} 245 + 246 + func (x *InterdiffRequest) ProtoReflect() protoreflect.Message { 247 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4] 248 + if x != nil { 249 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 250 + if ms.LoadMessageInfo() == nil { 251 + ms.StoreMessageInfo(mi) 252 + } 253 + return ms 254 + } 255 + return mi.MessageOf(x) 256 + } 257 + 258 + // Deprecated: Use InterdiffRequest.ProtoReflect.Descriptor instead. 259 + func (*InterdiffRequest) Descriptor() ([]byte, []int) { 260 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{4} 261 + } 262 + 263 + func (x *InterdiffRequest) GetRepo() string { 264 + if x != nil { 265 + return x.Repo 266 + } 267 + return "" 268 + } 269 + 270 + func (x *InterdiffRequest) GetFromBase() string { 271 + if x != nil { 272 + return x.FromBase 273 + } 274 + return "" 275 + } 276 + 277 + func (x *InterdiffRequest) GetFromHead() string { 278 + if x != nil { 279 + return x.FromHead 280 + } 281 + return "" 282 + } 283 + 284 + func (x *InterdiffRequest) GetToBase() string { 285 + if x != nil { 286 + return x.ToBase 287 + } 288 + return "" 289 + } 290 + 291 + func (x *InterdiffRequest) GetToHead() string { 292 + if x != nil { 293 + return x.ToHead 294 + } 295 + return "" 296 + } 297 + 298 + type FileContent struct { 299 + state protoimpl.MessageState `protogen:"open.v1"` 300 + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` 301 + Oid string `protobuf:"bytes,2,opt,name=oid,proto3" json:"oid,omitempty"` 302 + Size uint64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` 303 + IsBinary bool `protobuf:"varint,4,opt,name=is_binary,json=isBinary,proto3" json:"is_binary,omitempty"` 304 + unknownFields protoimpl.UnknownFields 305 + sizeCache protoimpl.SizeCache 306 + } 307 + 308 + func (x *FileContent) Reset() { 309 + *x = FileContent{} 310 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5] 311 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 312 + ms.StoreMessageInfo(mi) 313 + } 314 + 315 + func (x *FileContent) String() string { 316 + return protoimpl.X.MessageStringOf(x) 317 + } 318 + 319 + func (*FileContent) ProtoMessage() {} 320 + 321 + func (x *FileContent) ProtoReflect() protoreflect.Message { 322 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5] 323 + if x != nil { 324 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 325 + if ms.LoadMessageInfo() == nil { 326 + ms.StoreMessageInfo(mi) 327 + } 328 + return ms 329 + } 330 + return mi.MessageOf(x) 331 + } 332 + 333 + // Deprecated: Use FileContent.ProtoReflect.Descriptor instead. 334 + func (*FileContent) Descriptor() ([]byte, []int) { 335 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{5} 336 + } 337 + 338 + func (x *FileContent) GetPath() string { 339 + if x != nil { 340 + return x.Path 341 + } 342 + return "" 343 + } 344 + 345 + func (x *FileContent) GetOid() string { 346 + if x != nil { 347 + return x.Oid 348 + } 349 + return "" 350 + } 351 + 352 + func (x *FileContent) GetSize() uint64 { 353 + if x != nil { 354 + return x.Size 355 + } 356 + return 0 357 + } 358 + 359 + func (x *FileContent) GetIsBinary() bool { 360 + if x != nil { 361 + return x.IsBinary 362 + } 363 + return false 364 + } 365 + 366 + // One aligned line pair; a side is absent (None) when unset. 367 + type LinePair struct { 368 + state protoimpl.MessageState `protogen:"open.v1"` 369 + Lhs *uint32 `protobuf:"varint,1,opt,name=lhs,proto3,oneof" json:"lhs,omitempty"` 370 + Rhs *uint32 `protobuf:"varint,2,opt,name=rhs,proto3,oneof" json:"rhs,omitempty"` 371 + unknownFields protoimpl.UnknownFields 372 + sizeCache protoimpl.SizeCache 373 + } 374 + 375 + func (x *LinePair) Reset() { 376 + *x = LinePair{} 377 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6] 378 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 379 + ms.StoreMessageInfo(mi) 380 + } 381 + 382 + func (x *LinePair) String() string { 383 + return protoimpl.X.MessageStringOf(x) 384 + } 385 + 386 + func (*LinePair) ProtoMessage() {} 387 + 388 + func (x *LinePair) ProtoReflect() protoreflect.Message { 389 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6] 390 + if x != nil { 391 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 392 + if ms.LoadMessageInfo() == nil { 393 + ms.StoreMessageInfo(mi) 394 + } 395 + return ms 396 + } 397 + return mi.MessageOf(x) 398 + } 399 + 400 + // Deprecated: Use LinePair.ProtoReflect.Descriptor instead. 401 + func (*LinePair) Descriptor() ([]byte, []int) { 402 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{6} 403 + } 404 + 405 + func (x *LinePair) GetLhs() uint32 { 406 + if x != nil && x.Lhs != nil { 407 + return *x.Lhs 408 + } 409 + return 0 410 + } 411 + 412 + func (x *LinePair) GetRhs() uint32 { 413 + if x != nil && x.Rhs != nil { 414 + return *x.Rhs 415 + } 416 + return 0 417 + } 418 + 419 + type Hunk struct { 420 + state protoimpl.MessageState `protogen:"open.v1"` 421 + // LineNumber (u32) sets containing novel content per side. 422 + NovelLhs []uint32 `protobuf:"varint,1,rep,packed,name=novel_lhs,json=novelLhs,proto3" json:"novel_lhs,omitempty"` 423 + NovelRhs []uint32 `protobuf:"varint,2,rep,packed,name=novel_rhs,json=novelRhs,proto3" json:"novel_rhs,omitempty"` 424 + Lines []*LinePair `protobuf:"bytes,3,rep,name=lines,proto3" json:"lines,omitempty"` 425 + unknownFields protoimpl.UnknownFields 426 + sizeCache protoimpl.SizeCache 427 + } 428 + 429 + func (x *Hunk) Reset() { 430 + *x = Hunk{} 431 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7] 432 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 433 + ms.StoreMessageInfo(mi) 434 + } 435 + 436 + func (x *Hunk) String() string { 437 + return protoimpl.X.MessageStringOf(x) 438 + } 439 + 440 + func (*Hunk) ProtoMessage() {} 441 + 442 + func (x *Hunk) ProtoReflect() protoreflect.Message { 443 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7] 444 + if x != nil { 445 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 446 + if ms.LoadMessageInfo() == nil { 447 + ms.StoreMessageInfo(mi) 448 + } 449 + return ms 450 + } 451 + return mi.MessageOf(x) 452 + } 453 + 454 + // Deprecated: Use Hunk.ProtoReflect.Descriptor instead. 455 + func (*Hunk) Descriptor() ([]byte, []int) { 456 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{7} 457 + } 458 + 459 + func (x *Hunk) GetNovelLhs() []uint32 { 460 + if x != nil { 461 + return x.NovelLhs 462 + } 463 + return nil 464 + } 465 + 466 + func (x *Hunk) GetNovelRhs() []uint32 { 467 + if x != nil { 468 + return x.NovelRhs 469 + } 470 + return nil 471 + } 472 + 473 + func (x *Hunk) GetLines() []*LinePair { 474 + if x != nil { 475 + return x.Lines 476 + } 477 + return nil 478 + } 479 + 480 + // Number of bytes per side when the files differ (Rust Option<(usize, usize)>). 481 + type ByteChanges struct { 482 + state protoimpl.MessageState `protogen:"open.v1"` 483 + Lhs uint64 `protobuf:"varint,1,opt,name=lhs,proto3" json:"lhs,omitempty"` 484 + Rhs uint64 `protobuf:"varint,2,opt,name=rhs,proto3" json:"rhs,omitempty"` 485 + unknownFields protoimpl.UnknownFields 486 + sizeCache protoimpl.SizeCache 487 + } 488 + 489 + func (x *ByteChanges) Reset() { 490 + *x = ByteChanges{} 491 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8] 492 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 493 + ms.StoreMessageInfo(mi) 494 + } 495 + 496 + func (x *ByteChanges) String() string { 497 + return protoimpl.X.MessageStringOf(x) 498 + } 499 + 500 + func (*ByteChanges) ProtoMessage() {} 501 + 502 + func (x *ByteChanges) ProtoReflect() protoreflect.Message { 503 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8] 504 + if x != nil { 505 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 506 + if ms.LoadMessageInfo() == nil { 507 + ms.StoreMessageInfo(mi) 508 + } 509 + return ms 510 + } 511 + return mi.MessageOf(x) 512 + } 513 + 514 + // Deprecated: Use ByteChanges.ProtoReflect.Descriptor instead. 515 + func (*ByteChanges) Descriptor() ([]byte, []int) { 516 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{8} 517 + } 518 + 519 + func (x *ByteChanges) GetLhs() uint64 { 520 + if x != nil { 521 + return x.Lhs 522 + } 523 + return 0 524 + } 525 + 526 + func (x *ByteChanges) GetRhs() uint64 { 527 + if x != nil { 528 + return x.Rhs 529 + } 530 + return 0 531 + } 532 + 533 + type FileDiff struct { 534 + state protoimpl.MessageState `protogen:"open.v1"` 535 + LhsSrc *FileContent `protobuf:"bytes,1,opt,name=lhs_src,json=lhsSrc,proto3" json:"lhs_src,omitempty"` 536 + RhsSrc *FileContent `protobuf:"bytes,2,opt,name=rhs_src,json=rhsSrc,proto3" json:"rhs_src,omitempty"` 537 + Hunks []*Hunk `protobuf:"bytes,3,rep,name=hunks,proto3" json:"hunks,omitempty"` 538 + HasByteChanges *ByteChanges `protobuf:"bytes,4,opt,name=has_byte_changes,json=hasByteChanges,proto3,oneof" json:"has_byte_changes,omitempty"` 539 + HasSyntacticChanges bool `protobuf:"varint,5,opt,name=has_syntactic_changes,json=hasSyntacticChanges,proto3" json:"has_syntactic_changes,omitempty"` // TODO: lhs_positions / rhs_positions (MatchedPos) — AST, added later. 540 + unknownFields protoimpl.UnknownFields 541 + sizeCache protoimpl.SizeCache 542 + } 543 + 544 + func (x *FileDiff) Reset() { 545 + *x = FileDiff{} 546 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9] 547 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 548 + ms.StoreMessageInfo(mi) 549 + } 550 + 551 + func (x *FileDiff) String() string { 552 + return protoimpl.X.MessageStringOf(x) 553 + } 554 + 555 + func (*FileDiff) ProtoMessage() {} 556 + 557 + func (x *FileDiff) ProtoReflect() protoreflect.Message { 558 + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9] 559 + if x != nil { 560 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 561 + if ms.LoadMessageInfo() == nil { 562 + ms.StoreMessageInfo(mi) 563 + } 564 + return ms 565 + } 566 + return mi.MessageOf(x) 567 + } 568 + 569 + // Deprecated: Use FileDiff.ProtoReflect.Descriptor instead. 570 + func (*FileDiff) Descriptor() ([]byte, []int) { 571 + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{9} 572 + } 573 + 574 + func (x *FileDiff) GetLhsSrc() *FileContent { 575 + if x != nil { 576 + return x.LhsSrc 577 + } 578 + return nil 579 + } 580 + 581 + func (x *FileDiff) GetRhsSrc() *FileContent { 582 + if x != nil { 583 + return x.RhsSrc 584 + } 585 + return nil 586 + } 587 + 588 + func (x *FileDiff) GetHunks() []*Hunk { 589 + if x != nil { 590 + return x.Hunks 591 + } 592 + return nil 593 + } 594 + 595 + func (x *FileDiff) GetHasByteChanges() *ByteChanges { 596 + if x != nil { 597 + return x.HasByteChanges 598 + } 599 + return nil 600 + } 601 + 602 + func (x *FileDiff) GetHasSyntacticChanges() bool { 603 + if x != nil { 604 + return x.HasSyntacticChanges 605 + } 606 + return false 607 + } 608 + 609 + var File_gitmirror_v1_gitmirror_proto protoreflect.FileDescriptor 610 + 611 + const file_gitmirror_v1_gitmirror_proto_rawDesc = "" + 612 + "\n" + 613 + "\x1cgitmirror/v1/gitmirror.proto\x12\fgitmirror.v1\"\\\n" + 614 + "\x0eCommitsRequest\x12\x12\n" + 615 + "\x04repo\x18\x01 \x01(\tR\x04repo\x126\n" + 616 + "\aoptions\x18\x02 \x01(\v2\x1c.gitmirror.v1.CommitsOptionsR\aoptions\"\x10\n" + 617 + "\x0eCommitsOptions\"+\n" + 618 + "\x0fCommitsResponse\x12\x18\n" + 619 + "\acommits\x18\x01 \x03(\tR\acommits\"I\n" + 620 + "\vDiffRequest\x12\x12\n" + 621 + "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x12\n" + 622 + "\x04base\x18\x02 \x01(\tR\x04base\x12\x12\n" + 623 + "\x04head\x18\x03 \x01(\tR\x04head\"\x92\x01\n" + 624 + "\x10InterdiffRequest\x12\x12\n" + 625 + "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x1b\n" + 626 + "\tfrom_base\x18\x02 \x01(\tR\bfromBase\x12\x1b\n" + 627 + "\tfrom_head\x18\x03 \x01(\tR\bfromHead\x12\x17\n" + 628 + "\ato_base\x18\x04 \x01(\tR\x06toBase\x12\x17\n" + 629 + "\ato_head\x18\x05 \x01(\tR\x06toHead\"d\n" + 630 + "\vFileContent\x12\x12\n" + 631 + "\x04path\x18\x01 \x01(\tR\x04path\x12\x10\n" + 632 + "\x03oid\x18\x02 \x01(\tR\x03oid\x12\x12\n" + 633 + "\x04size\x18\x03 \x01(\x04R\x04size\x12\x1b\n" + 634 + "\tis_binary\x18\x04 \x01(\bR\bisBinary\"H\n" + 635 + "\bLinePair\x12\x15\n" + 636 + "\x03lhs\x18\x01 \x01(\rH\x00R\x03lhs\x88\x01\x01\x12\x15\n" + 637 + "\x03rhs\x18\x02 \x01(\rH\x01R\x03rhs\x88\x01\x01B\x06\n" + 638 + "\x04_lhsB\x06\n" + 639 + "\x04_rhs\"n\n" + 640 + "\x04Hunk\x12\x1b\n" + 641 + "\tnovel_lhs\x18\x01 \x03(\rR\bnovelLhs\x12\x1b\n" + 642 + "\tnovel_rhs\x18\x02 \x03(\rR\bnovelRhs\x12,\n" + 643 + "\x05lines\x18\x03 \x03(\v2\x16.gitmirror.v1.LinePairR\x05lines\"1\n" + 644 + "\vByteChanges\x12\x10\n" + 645 + "\x03lhs\x18\x01 \x01(\x04R\x03lhs\x12\x10\n" + 646 + "\x03rhs\x18\x02 \x01(\x04R\x03rhs\"\xaf\x02\n" + 647 + "\bFileDiff\x122\n" + 648 + "\alhs_src\x18\x01 \x01(\v2\x19.gitmirror.v1.FileContentR\x06lhsSrc\x122\n" + 649 + "\arhs_src\x18\x02 \x01(\v2\x19.gitmirror.v1.FileContentR\x06rhsSrc\x12(\n" + 650 + "\x05hunks\x18\x03 \x03(\v2\x12.gitmirror.v1.HunkR\x05hunks\x12H\n" + 651 + "\x10has_byte_changes\x18\x04 \x01(\v2\x19.gitmirror.v1.ByteChangesH\x00R\x0ehasByteChanges\x88\x01\x01\x122\n" + 652 + "\x15has_syntactic_changes\x18\x05 \x01(\bR\x13hasSyntacticChangesB\x13\n" + 653 + "\x11_has_byte_changes2\xde\x01\n" + 654 + "\x10GitMirrorService\x12F\n" + 655 + "\aCommits\x12\x1c.gitmirror.v1.CommitsRequest\x1a\x1d.gitmirror.v1.CommitsResponse\x12;\n" + 656 + "\x04Diff\x12\x19.gitmirror.v1.DiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01\x12E\n" + 657 + "\tInterdiff\x12\x1e.gitmirror.v1.InterdiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01B2Z0tangled.org/core/gitmirror/proto/gen;gitmirrorv1b\x06proto3" 658 + 659 + var ( 660 + file_gitmirror_v1_gitmirror_proto_rawDescOnce sync.Once 661 + file_gitmirror_v1_gitmirror_proto_rawDescData []byte 662 + ) 663 + 664 + func file_gitmirror_v1_gitmirror_proto_rawDescGZIP() []byte { 665 + file_gitmirror_v1_gitmirror_proto_rawDescOnce.Do(func() { 666 + file_gitmirror_v1_gitmirror_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_gitmirror_v1_gitmirror_proto_rawDesc), len(file_gitmirror_v1_gitmirror_proto_rawDesc))) 667 + }) 668 + return file_gitmirror_v1_gitmirror_proto_rawDescData 669 + } 670 + 671 + var file_gitmirror_v1_gitmirror_proto_msgTypes = make([]protoimpl.MessageInfo, 10) 672 + var file_gitmirror_v1_gitmirror_proto_goTypes = []any{ 673 + (*CommitsRequest)(nil), // 0: gitmirror.v1.CommitsRequest 674 + (*CommitsOptions)(nil), // 1: gitmirror.v1.CommitsOptions 675 + (*CommitsResponse)(nil), // 2: gitmirror.v1.CommitsResponse 676 + (*DiffRequest)(nil), // 3: gitmirror.v1.DiffRequest 677 + (*InterdiffRequest)(nil), // 4: gitmirror.v1.InterdiffRequest 678 + (*FileContent)(nil), // 5: gitmirror.v1.FileContent 679 + (*LinePair)(nil), // 6: gitmirror.v1.LinePair 680 + (*Hunk)(nil), // 7: gitmirror.v1.Hunk 681 + (*ByteChanges)(nil), // 8: gitmirror.v1.ByteChanges 682 + (*FileDiff)(nil), // 9: gitmirror.v1.FileDiff 683 + } 684 + var file_gitmirror_v1_gitmirror_proto_depIdxs = []int32{ 685 + 1, // 0: gitmirror.v1.CommitsRequest.options:type_name -> gitmirror.v1.CommitsOptions 686 + 6, // 1: gitmirror.v1.Hunk.lines:type_name -> gitmirror.v1.LinePair 687 + 5, // 2: gitmirror.v1.FileDiff.lhs_src:type_name -> gitmirror.v1.FileContent 688 + 5, // 3: gitmirror.v1.FileDiff.rhs_src:type_name -> gitmirror.v1.FileContent 689 + 7, // 4: gitmirror.v1.FileDiff.hunks:type_name -> gitmirror.v1.Hunk 690 + 8, // 5: gitmirror.v1.FileDiff.has_byte_changes:type_name -> gitmirror.v1.ByteChanges 691 + 0, // 6: gitmirror.v1.GitMirrorService.Commits:input_type -> gitmirror.v1.CommitsRequest 692 + 3, // 7: gitmirror.v1.GitMirrorService.Diff:input_type -> gitmirror.v1.DiffRequest 693 + 4, // 8: gitmirror.v1.GitMirrorService.Interdiff:input_type -> gitmirror.v1.InterdiffRequest 694 + 2, // 9: gitmirror.v1.GitMirrorService.Commits:output_type -> gitmirror.v1.CommitsResponse 695 + 9, // 10: gitmirror.v1.GitMirrorService.Diff:output_type -> gitmirror.v1.FileDiff 696 + 9, // 11: gitmirror.v1.GitMirrorService.Interdiff:output_type -> gitmirror.v1.FileDiff 697 + 9, // [9:12] is the sub-list for method output_type 698 + 6, // [6:9] is the sub-list for method input_type 699 + 6, // [6:6] is the sub-list for extension type_name 700 + 6, // [6:6] is the sub-list for extension extendee 701 + 0, // [0:6] is the sub-list for field type_name 702 + } 703 + 704 + func init() { file_gitmirror_v1_gitmirror_proto_init() } 705 + func file_gitmirror_v1_gitmirror_proto_init() { 706 + if File_gitmirror_v1_gitmirror_proto != nil { 707 + return 708 + } 709 + file_gitmirror_v1_gitmirror_proto_msgTypes[6].OneofWrappers = []any{} 710 + file_gitmirror_v1_gitmirror_proto_msgTypes[9].OneofWrappers = []any{} 711 + type x struct{} 712 + out := protoimpl.TypeBuilder{ 713 + File: protoimpl.DescBuilder{ 714 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 715 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_gitmirror_v1_gitmirror_proto_rawDesc), len(file_gitmirror_v1_gitmirror_proto_rawDesc)), 716 + NumEnums: 0, 717 + NumMessages: 10, 718 + NumExtensions: 0, 719 + NumServices: 1, 720 + }, 721 + GoTypes: file_gitmirror_v1_gitmirror_proto_goTypes, 722 + DependencyIndexes: file_gitmirror_v1_gitmirror_proto_depIdxs, 723 + MessageInfos: file_gitmirror_v1_gitmirror_proto_msgTypes, 724 + }.Build() 725 + File_gitmirror_v1_gitmirror_proto = out.File 726 + file_gitmirror_v1_gitmirror_proto_goTypes = nil 727 + file_gitmirror_v1_gitmirror_proto_depIdxs = nil 728 + }
+204
gitmirror/proto/gen/gitmirror_grpc.pb.go
··· 1 + // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 + // versions: 3 + // - protoc-gen-go-grpc v1.6.0 4 + // - protoc (unknown) 5 + // source: gitmirror/v1/gitmirror.proto 6 + 7 + package gitmirrorv1 8 + 9 + import ( 10 + context "context" 11 + grpc "google.golang.org/grpc" 12 + codes "google.golang.org/grpc/codes" 13 + status "google.golang.org/grpc/status" 14 + ) 15 + 16 + // This is a compile-time assertion to ensure that this generated file 17 + // is compatible with the grpc package it is being compiled against. 18 + // Requires gRPC-Go v1.64.0 or later. 19 + const _ = grpc.SupportPackageIsVersion9 20 + 21 + const ( 22 + GitMirrorService_Commits_FullMethodName = "/gitmirror.v1.GitMirrorService/Commits" 23 + GitMirrorService_Diff_FullMethodName = "/gitmirror.v1.GitMirrorService/Diff" 24 + GitMirrorService_Interdiff_FullMethodName = "/gitmirror.v1.GitMirrorService/Interdiff" 25 + ) 26 + 27 + // GitMirrorServiceClient is the client API for GitMirrorService service. 28 + // 29 + // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 30 + type GitMirrorServiceClient interface { 31 + Commits(ctx context.Context, in *CommitsRequest, opts ...grpc.CallOption) (*CommitsResponse, error) 32 + Diff(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) 33 + Interdiff(ctx context.Context, in *InterdiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) 34 + } 35 + 36 + type gitMirrorServiceClient struct { 37 + cc grpc.ClientConnInterface 38 + } 39 + 40 + func NewGitMirrorServiceClient(cc grpc.ClientConnInterface) GitMirrorServiceClient { 41 + return &gitMirrorServiceClient{cc} 42 + } 43 + 44 + func (c *gitMirrorServiceClient) Commits(ctx context.Context, in *CommitsRequest, opts ...grpc.CallOption) (*CommitsResponse, error) { 45 + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 46 + out := new(CommitsResponse) 47 + err := c.cc.Invoke(ctx, GitMirrorService_Commits_FullMethodName, in, out, cOpts...) 48 + if err != nil { 49 + return nil, err 50 + } 51 + return out, nil 52 + } 53 + 54 + func (c *gitMirrorServiceClient) Diff(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) { 55 + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 56 + stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[0], GitMirrorService_Diff_FullMethodName, cOpts...) 57 + if err != nil { 58 + return nil, err 59 + } 60 + x := &grpc.GenericClientStream[DiffRequest, FileDiff]{ClientStream: stream} 61 + if err := x.ClientStream.SendMsg(in); err != nil { 62 + return nil, err 63 + } 64 + if err := x.ClientStream.CloseSend(); err != nil { 65 + return nil, err 66 + } 67 + return x, nil 68 + } 69 + 70 + // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 71 + type GitMirrorService_DiffClient = grpc.ServerStreamingClient[FileDiff] 72 + 73 + func (c *gitMirrorServiceClient) Interdiff(ctx context.Context, in *InterdiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) { 74 + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 75 + stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[1], GitMirrorService_Interdiff_FullMethodName, cOpts...) 76 + if err != nil { 77 + return nil, err 78 + } 79 + x := &grpc.GenericClientStream[InterdiffRequest, FileDiff]{ClientStream: stream} 80 + if err := x.ClientStream.SendMsg(in); err != nil { 81 + return nil, err 82 + } 83 + if err := x.ClientStream.CloseSend(); err != nil { 84 + return nil, err 85 + } 86 + return x, nil 87 + } 88 + 89 + // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 90 + type GitMirrorService_InterdiffClient = grpc.ServerStreamingClient[FileDiff] 91 + 92 + // GitMirrorServiceServer is the server API for GitMirrorService service. 93 + // All implementations must embed UnimplementedGitMirrorServiceServer 94 + // for forward compatibility. 95 + type GitMirrorServiceServer interface { 96 + Commits(context.Context, *CommitsRequest) (*CommitsResponse, error) 97 + Diff(*DiffRequest, grpc.ServerStreamingServer[FileDiff]) error 98 + Interdiff(*InterdiffRequest, grpc.ServerStreamingServer[FileDiff]) error 99 + mustEmbedUnimplementedGitMirrorServiceServer() 100 + } 101 + 102 + // UnimplementedGitMirrorServiceServer must be embedded to have 103 + // forward compatible implementations. 104 + // 105 + // NOTE: this should be embedded by value instead of pointer to avoid a nil 106 + // pointer dereference when methods are called. 107 + type UnimplementedGitMirrorServiceServer struct{} 108 + 109 + func (UnimplementedGitMirrorServiceServer) Commits(context.Context, *CommitsRequest) (*CommitsResponse, error) { 110 + return nil, status.Error(codes.Unimplemented, "method Commits not implemented") 111 + } 112 + func (UnimplementedGitMirrorServiceServer) Diff(*DiffRequest, grpc.ServerStreamingServer[FileDiff]) error { 113 + return status.Error(codes.Unimplemented, "method Diff not implemented") 114 + } 115 + func (UnimplementedGitMirrorServiceServer) Interdiff(*InterdiffRequest, grpc.ServerStreamingServer[FileDiff]) error { 116 + return status.Error(codes.Unimplemented, "method Interdiff not implemented") 117 + } 118 + func (UnimplementedGitMirrorServiceServer) mustEmbedUnimplementedGitMirrorServiceServer() {} 119 + func (UnimplementedGitMirrorServiceServer) testEmbeddedByValue() {} 120 + 121 + // UnsafeGitMirrorServiceServer may be embedded to opt out of forward compatibility for this service. 122 + // Use of this interface is not recommended, as added methods to GitMirrorServiceServer will 123 + // result in compilation errors. 124 + type UnsafeGitMirrorServiceServer interface { 125 + mustEmbedUnimplementedGitMirrorServiceServer() 126 + } 127 + 128 + func RegisterGitMirrorServiceServer(s grpc.ServiceRegistrar, srv GitMirrorServiceServer) { 129 + // If the following call panics, it indicates UnimplementedGitMirrorServiceServer was 130 + // embedded by pointer and is nil. This will cause panics if an 131 + // unimplemented method is ever invoked, so we test this at initialization 132 + // time to prevent it from happening at runtime later due to I/O. 133 + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 134 + t.testEmbeddedByValue() 135 + } 136 + s.RegisterService(&GitMirrorService_ServiceDesc, srv) 137 + } 138 + 139 + func _GitMirrorService_Commits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 140 + in := new(CommitsRequest) 141 + if err := dec(in); err != nil { 142 + return nil, err 143 + } 144 + if interceptor == nil { 145 + return srv.(GitMirrorServiceServer).Commits(ctx, in) 146 + } 147 + info := &grpc.UnaryServerInfo{ 148 + Server: srv, 149 + FullMethod: GitMirrorService_Commits_FullMethodName, 150 + } 151 + handler := func(ctx context.Context, req interface{}) (interface{}, error) { 152 + return srv.(GitMirrorServiceServer).Commits(ctx, req.(*CommitsRequest)) 153 + } 154 + return interceptor(ctx, in, info, handler) 155 + } 156 + 157 + func _GitMirrorService_Diff_Handler(srv interface{}, stream grpc.ServerStream) error { 158 + m := new(DiffRequest) 159 + if err := stream.RecvMsg(m); err != nil { 160 + return err 161 + } 162 + return srv.(GitMirrorServiceServer).Diff(m, &grpc.GenericServerStream[DiffRequest, FileDiff]{ServerStream: stream}) 163 + } 164 + 165 + // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 166 + type GitMirrorService_DiffServer = grpc.ServerStreamingServer[FileDiff] 167 + 168 + func _GitMirrorService_Interdiff_Handler(srv interface{}, stream grpc.ServerStream) error { 169 + m := new(InterdiffRequest) 170 + if err := stream.RecvMsg(m); err != nil { 171 + return err 172 + } 173 + return srv.(GitMirrorServiceServer).Interdiff(m, &grpc.GenericServerStream[InterdiffRequest, FileDiff]{ServerStream: stream}) 174 + } 175 + 176 + // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 177 + type GitMirrorService_InterdiffServer = grpc.ServerStreamingServer[FileDiff] 178 + 179 + // GitMirrorService_ServiceDesc is the grpc.ServiceDesc for GitMirrorService service. 180 + // It's only intended for direct use with grpc.RegisterService, 181 + // and not to be introspected or modified (even as a copy) 182 + var GitMirrorService_ServiceDesc = grpc.ServiceDesc{ 183 + ServiceName: "gitmirror.v1.GitMirrorService", 184 + HandlerType: (*GitMirrorServiceServer)(nil), 185 + Methods: []grpc.MethodDesc{ 186 + { 187 + MethodName: "Commits", 188 + Handler: _GitMirrorService_Commits_Handler, 189 + }, 190 + }, 191 + Streams: []grpc.StreamDesc{ 192 + { 193 + StreamName: "Diff", 194 + Handler: _GitMirrorService_Diff_Handler, 195 + ServerStreams: true, 196 + }, 197 + { 198 + StreamName: "Interdiff", 199 + Handler: _GitMirrorService_Interdiff_Handler, 200 + ServerStreams: true, 201 + }, 202 + }, 203 + Metadata: "gitmirror/v1/gitmirror.proto", 204 + }
+58
gitmirror/proto/gitmirror/v1/gitmirror.proto
··· 6 6 7 7 service GitMirrorService { 8 8 rpc Commits(CommitsRequest) returns (CommitsResponse); 9 + rpc Diff(DiffRequest) returns (stream FileDiff); 10 + rpc Interdiff(InterdiffRequest) returns (stream FileDiff); 9 11 } 10 12 11 13 message CommitsRequest { ··· 23 25 // maps the Go []string return. 24 26 repeated string commits = 1; 25 27 } 28 + 29 + message DiffRequest { 30 + // repo carries the DID as a string; parsing/validation is an impl detail. 31 + string repo = 1; 32 + // base/head are commit-ish ref strings. 33 + string base = 2; 34 + string head = 3; 35 + } 36 + 37 + message InterdiffRequest { 38 + // repo carries the DID as a string. 39 + string repo = 1; 40 + // Old patch range (from_base..from_head) and new patch range (to_base..to_head). 41 + string from_base = 2; 42 + string from_head = 3; 43 + string to_base = 4; 44 + string to_head = 5; 45 + } 46 + 47 + // Diff and Interdiff stream one FileDiff per changed file. 48 + 49 + message FileContent { 50 + string path = 1; 51 + string oid = 2; 52 + uint64 size = 3; 53 + bool is_binary = 4; 54 + bool is_submodule = 5; 55 + } 56 + 57 + // One aligned line pair; a side is absent (None) when unset. 58 + message LinePair { 59 + optional uint32 lhs = 1; 60 + optional uint32 rhs = 2; 61 + } 62 + 63 + message Hunk { 64 + // LineNumber (u32) sets containing novel content per side. 65 + repeated uint32 novel_lhs = 1; 66 + repeated uint32 novel_rhs = 2; 67 + repeated LinePair lines = 3; 68 + } 69 + 70 + // Number of bytes per side when the files differ (Rust Option<(usize, usize)>). 71 + message ByteChanges { 72 + uint64 lhs = 1; 73 + uint64 rhs = 2; 74 + } 75 + 76 + message FileDiff { 77 + FileContent lhs_src = 1; 78 + FileContent rhs_src = 2; 79 + repeated Hunk hunks = 3; 80 + optional ByteChanges has_byte_changes = 4; 81 + bool has_syntactic_changes = 5; 82 + // TODO: lhs_positions / rhs_positions (MatchedPos) — AST, added later. 83 + }
+572
gitmirror/src/diff.rs
··· 1 + use gix::bstr::ByteSlice as _; 2 + use gix::objs::tree::EntryKind; 3 + use gix::{Commit, ObjectId}; 4 + use line_numbers::{LineNumber, SingleLineSpan}; 5 + use rustc_hash::FxHashSet; 6 + use tracing::info; 7 + 8 + /// Blobs larger than this are treated as binary (reported by size only, not diffed), 9 + /// overriding gix's `core.bigFileThreshold` default of 512 MiB. Scoped to diffing only. 10 + const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; 11 + 12 + #[derive(Debug)] 13 + pub struct FileContent { 14 + pub(crate) path: String, 15 + pub(crate) oid: String, 16 + pub(crate) size: usize, 17 + pub(crate) is_binary: bool, 18 + /// True when this side is a submodule (gitlink); `oid` is then a commit id pointing into 19 + /// the submodule's repository, not a blob in this one. 20 + pub(crate) is_submodule: bool, 21 + } 22 + 23 + /// A hunk represents a series of modified lines that are displayed 24 + /// together. 25 + #[derive(Debug, Clone)] 26 + pub struct Hunk { 27 + /// The LHS line numbers that contain novel content. 28 + pub(crate) novel_lhs: FxHashSet<LineNumber>, 29 + /// The RHS line numbers that contain novel content. 30 + pub(crate) novel_rhs: FxHashSet<LineNumber>, 31 + /// Line pairs that contain modified lines. This does not include 32 + /// padding, so at least one of the two lines has novel content. 33 + pub(crate) lines: Vec<(Option<LineNumber>, Option<LineNumber>)>, 34 + } 35 + 36 + /// A matched token (an atom, a delimiter, or a comment word). 37 + #[derive(PartialEq, Eq, Debug, Clone)] 38 + enum MatchKind { 39 + // TBD 40 + } 41 + 42 + #[derive(Debug, Clone, PartialEq, Eq)] 43 + struct MatchedPos { 44 + kind: MatchKind, 45 + pos: SingleLineSpan, 46 + } 47 + 48 + #[derive(Debug)] 49 + pub struct Diff { 50 + pub(crate) lhs_src: FileContent, 51 + pub(crate) rhs_src: FileContent, 52 + pub(crate) hunks: Vec<Hunk>, 53 + 54 + lhs_positions: Vec<MatchedPos>, 55 + rhs_positions: Vec<MatchedPos>, 56 + 57 + /// If the two files do not have exactly the same bytes, the 58 + /// number of bytes in each file. 59 + pub(crate) has_byte_changes: Option<(usize, usize)>, 60 + pub(crate) has_syntactic_changes: bool, 61 + } 62 + 63 + /// Diff two trees, yielding one [`Diff`] per changed file (line-level [`Hunk`]s included). 64 + /// 65 + /// Returns an iterator of `anyhow::Result<Diff>`: per-file diffing can fail independently, so 66 + /// each item is fallible. The consumer drives it lazily and can stop at any point by dropping 67 + /// the iterator (e.g. when the gRPC client hangs up). 68 + pub fn diff<'repo>( 69 + repo: &'repo gix::Repository, 70 + old_tree: &gix::Tree<'_>, 71 + new_tree: &gix::Tree<'_>, 72 + ) -> anyhow::Result<DiffIter<'repo>> { 73 + let changes = repo.diff_tree_to_tree(Some(old_tree), Some(new_tree), None)?; 74 + let mut cache = repo.diff_resource_cache_for_tree_diff()?; 75 + cache.filter.options.large_file_threshold_bytes = LARGE_FILE_THRESHOLD_BYTES; 76 + Ok(DiffIter { 77 + repo, 78 + changes: changes.into_iter(), 79 + cache, 80 + }) 81 + } 82 + 83 + /// Lazy iterator over per-file [`Diff`]s. Created by [`diff`]. 84 + pub struct DiffIter<'repo> { 85 + repo: &'repo gix::Repository, 86 + changes: std::vec::IntoIter<gix::object::tree::diff::ChangeDetached>, 87 + cache: gix::diff::blob::Platform, 88 + } 89 + 90 + impl Iterator for DiffIter<'_> { 91 + type Item = anyhow::Result<Diff>; 92 + 93 + fn next(&mut self) -> Option<Self::Item> { 94 + loop { 95 + let change = self.changes.next()?; 96 + let (old, new) = change_sides(&change); 97 + // Trees are recursed into by gix and never diffed directly; skip defensively. 98 + if old.is_kind(EntryKind::Tree) || new.is_kind(EntryKind::Tree) { 99 + continue; 100 + } 101 + // Submodules (gitlinks) can't go through the blob pipeline — their commit lives in 102 + // another repo. Emit the commit oids directly for the caller to compare. 103 + if old.is_kind(EntryKind::Commit) || new.is_kind(EntryKind::Commit) { 104 + return Some(Ok(submodule_diff(self.repo, &change, old, new))); 105 + } 106 + let result = build_diff(self.repo, &change, &mut self.cache); 107 + self.cache.clear_resource_cache_keep_allocation(); 108 + return Some(result); 109 + } 110 + } 111 + } 112 + 113 + /// One side (old or new) of a change. 114 + enum Side { 115 + Absent, 116 + Present { oid: ObjectId, kind: EntryKind }, 117 + } 118 + 119 + impl Side { 120 + fn is_kind(&self, wanted: EntryKind) -> bool { 121 + matches!(self, Side::Present { kind, .. } if *kind == wanted) 122 + } 123 + } 124 + 125 + /// Decompose a change into its `(old, new)` sides regardless of variant. 126 + fn change_sides(change: &gix::object::tree::diff::ChangeDetached) -> (Side, Side) { 127 + use gix::object::tree::diff::ChangeDetached as C; 128 + let present = |oid, mode: gix::object::tree::EntryMode| Side::Present { 129 + oid, 130 + kind: mode.kind(), 131 + }; 132 + match change { 133 + C::Addition { id, entry_mode, .. } => (Side::Absent, present(*id, *entry_mode)), 134 + C::Deletion { id, entry_mode, .. } => (present(*id, *entry_mode), Side::Absent), 135 + C::Modification { 136 + previous_id, 137 + previous_entry_mode, 138 + id, 139 + entry_mode, 140 + .. 141 + } => ( 142 + present(*previous_id, *previous_entry_mode), 143 + present(*id, *entry_mode), 144 + ), 145 + C::Rewrite { 146 + source_id, 147 + source_entry_mode, 148 + id, 149 + entry_mode, 150 + .. 151 + } => ( 152 + present(*source_id, *source_entry_mode), 153 + present(*id, *entry_mode), 154 + ), 155 + } 156 + } 157 + 158 + /// Build a [`Diff`] for a submodule (gitlink) change: no hunks, both sides carry their commit 159 + /// oid with `is_submodule` set. The caller compares the oids as submodule pointers. 160 + fn submodule_diff( 161 + repo: &gix::Repository, 162 + change: &gix::object::tree::diff::ChangeDetached, 163 + old: Side, 164 + new: Side, 165 + ) -> Diff { 166 + let path = change.location().to_string(); 167 + let side = |s: Side| FileContent { 168 + path: path.clone(), 169 + oid: match s { 170 + Side::Absent => ObjectId::null(repo.object_hash()).to_string(), 171 + Side::Present { oid, .. } => oid.to_string(), 172 + }, 173 + size: 0, 174 + is_binary: false, 175 + is_submodule: true, 176 + }; 177 + Diff { 178 + lhs_src: side(old), 179 + rhs_src: side(new), 180 + hunks: Vec::new(), 181 + lhs_positions: Vec::new(), 182 + rhs_positions: Vec::new(), 183 + has_byte_changes: None, 184 + has_syntactic_changes: false, 185 + } 186 + } 187 + 188 + /// Build the [`Diff`] for a single changed file. 189 + fn build_diff( 190 + repo: &gix::Repository, 191 + change: &gix::object::tree::diff::ChangeDetached, 192 + cache: &mut gix::diff::blob::Platform, 193 + ) -> anyhow::Result<Diff> { 194 + use gix::diff::blob::platform::prepare_diff::Operation; 195 + use gix::diff::blob::platform::resource::Data; 196 + use gix::diff::blob::{Diff as BlobDiff, InternedInput}; 197 + use gix::prelude::TreeDiffChangeExt as _; 198 + 199 + let change = change.attach(repo, repo); 200 + let blob = change.diff(cache)?; 201 + let out = blob.resource_cache.prepare_diff()?; 202 + 203 + // Per-side file metadata, derived from the prepared resources. 204 + let side = |res: &gix::diff::blob::platform::Resource<'_>| -> FileContent { 205 + let (size, is_binary) = match res.data { 206 + Data::Buffer { buf, .. } => (buf.len(), false), 207 + Data::Binary { size } => (size as usize, true), 208 + Data::Missing => (0, false), 209 + }; 210 + FileContent { 211 + path: res.rela_path.to_string(), 212 + oid: res.id.to_string(), 213 + size, 214 + is_binary, 215 + is_submodule: false, 216 + } 217 + }; 218 + let mut lhs_src = side(&out.old); 219 + let mut rhs_src = side(&out.new); 220 + 221 + let has_byte_changes = if lhs_src.size == rhs_src.size && out.old.id == out.new.id { 222 + None 223 + } else { 224 + Some((lhs_src.size, rhs_src.size)) 225 + }; 226 + 227 + let hunks = match out.operation { 228 + Operation::InternalDiff { algorithm } => { 229 + let input = InternedInput::new(out.old.intern_source(), out.new.intern_source()); 230 + let mut d = BlobDiff::compute(algorithm, &input); 231 + d.postprocess_lines(&input); 232 + d.hunks() 233 + .map(|h| { 234 + let before: Vec<LineNumber> = h.before.clone().map(LineNumber).collect(); 235 + let after: Vec<LineNumber> = h.after.clone().map(LineNumber).collect(); 236 + let n = before.len().max(after.len()); 237 + let lines = (0..n) 238 + .map(|i| (before.get(i).copied(), after.get(i).copied())) 239 + .collect(); 240 + Hunk { 241 + novel_lhs: before.iter().copied().collect(), 242 + novel_rhs: after.iter().copied().collect(), 243 + lines, 244 + } 245 + }) 246 + .collect() 247 + } 248 + Operation::SourceOrDestinationIsBinary => { 249 + lhs_src.is_binary = true; 250 + rhs_src.is_binary = true; 251 + Vec::new() 252 + } 253 + Operation::ExternalCommand { .. } => unreachable!("we disabled that"), 254 + }; 255 + 256 + Ok(Diff { 257 + lhs_src, 258 + rhs_src, 259 + hunks, 260 + lhs_positions: Vec::new(), 261 + rhs_positions: Vec::new(), 262 + has_byte_changes, 263 + has_syntactic_changes: false, 264 + }) 265 + } 266 + 267 + /// Compute the two trees whose diff is the *interdiff* between an old patch 268 + /// version (`from_base..from_head`) and a new one (`to_base..to_head`). 269 + /// 270 + /// We rebase the old patch onto the new base so both versions share `to_base`, 271 + /// then the caller diffs the returned trees (step 4): 272 + /// 273 + /// ```text 274 + /// from_base -- from_head (old patch) 275 + /// \ 276 + /// `-- to_base -- to_head (new patch) 277 + /// ``` 278 + /// 279 + /// 1. Rebase `from` onto `to_base` via a 3-way tree merge. 280 + /// Replay the patch diff `from_base.tree -> from_head.tree` on top of `to_base.tree`. 281 + /// 2. Return `(rebased_from_tree, to_head.tree)`, both rooted at `to_base`. 282 + /// 283 + /// `from_base` / `to_base` default to the respective head commit's first parent. 284 + /// 285 + /// `repo` is borrowed so the returned trees can reference it; resolving a 286 + /// `repo_did` to an on-disk repository is out of scope here. 287 + pub fn prepare_interdiff<'repo>( 288 + repo: &'repo gix::Repository, 289 + from: (Option<ObjectId>, ObjectId), 290 + to: (Option<ObjectId>, ObjectId), 291 + ) -> anyhow::Result<(gix::Tree<'repo>, gix::Tree<'repo>)> { 292 + let (from_base_id, from_head_id) = from; 293 + let (to_base_id, to_head_id) = to; 294 + 295 + let from_head = repo.find_commit(from_head_id)?; 296 + let to_head = repo.find_commit(to_head_id)?; 297 + 298 + let from_base_id = from_base_id.unwrap_or_else(|| resolve_base(&from_head).unwrap()); 299 + let to_base_id = to_base_id.unwrap_or_else(|| resolve_base(&to_head).unwrap()); 300 + 301 + let from_base = repo.find_commit(from_base_id)?; 302 + let to_base = repo.find_commit(to_base_id)?; 303 + 304 + // Endpoint trees for the 3-way merge. 305 + let from_base_tree = from_base.tree_id()?.detach(); 306 + let from_head_tree = from_head.tree_id()?.detach(); 307 + let to_base_tree = to_base.tree_id()?.detach(); 308 + 309 + // Rebase = replay (from_base_tree -> from_head_tree) onto to_base_tree: 310 + // ancestor = from_base_tree, ours = to_base_tree, theirs = from_head_tree. 311 + let options = repo.tree_merge_options()?; 312 + let labels = gix::merge::blob::builtin_driver::text::Labels { 313 + ancestor: Some("from.base".as_bytes().as_bstr()), 314 + current: Some("to.base".as_bytes().as_bstr()), 315 + other: Some("from.head".as_bytes().as_bstr()), 316 + }; 317 + let mut outcome = repo.merge_trees( 318 + from_base_tree, 319 + to_base_tree, 320 + from_head_tree, 321 + labels, 322 + options, 323 + )?; 324 + 325 + // Interdiff tolerates conflicts: the merged tree carries conflict markers 326 + // (blob driver keeps them); surface them rather than failing. 327 + if !outcome.conflicts.is_empty() { 328 + info!( 329 + conflicts = outcome.conflicts.len(), 330 + "interdiff rebase produced conflicts; tree contains conflict markers" 331 + ); 332 + } 333 + 334 + let rebased_id = outcome.tree.write()?.detach(); 335 + let rebased_tree = repo.find_tree(rebased_id)?; 336 + let to_head_tree = to_head.tree()?; 337 + Ok((rebased_tree, to_head_tree)) 338 + } 339 + 340 + fn resolve_base(commit: &Commit) -> anyhow::Result<ObjectId> { 341 + commit 342 + .parent_ids() 343 + .next() 344 + .map(|id| id.detach()) 345 + .ok_or_else(|| anyhow::anyhow!("commit {0} has no parent to use as base", commit.id)) 346 + } 347 + 348 + #[cfg(test)] 349 + mod tests { 350 + use super::*; 351 + 352 + /// A fresh bare repo in a tempdir. The `TempDir` is returned so it outlives the repo. 353 + fn repo() -> (tempfile::TempDir, gix::Repository) { 354 + let dir = tempfile::tempdir().unwrap(); 355 + let repo = gix::init_bare(dir.path()).unwrap(); 356 + (dir, repo) 357 + } 358 + 359 + /// Build a tree from `(path, content)` pairs and return its oid. 360 + fn tree(repo: &gix::Repository, files: &[(&str, &[u8])]) -> ObjectId { 361 + let empty = repo.empty_tree().id; 362 + let mut editor = repo.edit_tree(empty).unwrap(); 363 + for (path, content) in files { 364 + let blob = repo.write_blob(content).unwrap().detach(); 365 + editor.upsert(*path, EntryKind::Blob, blob).unwrap(); 366 + } 367 + editor.write().unwrap().detach() 368 + } 369 + 370 + /// Build a tree with a single submodule (gitlink) entry at `path` pointing at `commit_oid`. 371 + fn tree_with_gitlink(repo: &gix::Repository, path: &str, commit_oid: ObjectId) -> ObjectId { 372 + let empty = repo.empty_tree().id; 373 + let mut editor = repo.edit_tree(empty).unwrap(); 374 + editor.upsert(path, EntryKind::Commit, commit_oid).unwrap(); 375 + editor.write().unwrap().detach() 376 + } 377 + 378 + /// Commit `tree_oid` with the given parents. Uses an explicit signature because a fresh 379 + /// `init_bare` repo has no user config (config-reading `commit` would fail AuthorMissing). 380 + fn commit(repo: &gix::Repository, tree_oid: ObjectId, parents: &[ObjectId]) -> ObjectId { 381 + let sig = gix::actor::SignatureRef { 382 + name: b"t".as_bstr(), 383 + email: b"t@t".as_bstr(), 384 + time: "0 +0000", 385 + }; 386 + repo.new_commit_as(sig, sig, "msg", tree_oid, parents.iter().copied()) 387 + .unwrap() 388 + .id 389 + } 390 + 391 + /// Collect all diffs between two tree oids. 392 + fn diffs(repo: &gix::Repository, old: ObjectId, new: ObjectId) -> Vec<Diff> { 393 + let old = repo.find_tree(old).unwrap(); 394 + let new = repo.find_tree(new).unwrap(); 395 + diff(repo, &old, &new) 396 + .unwrap() 397 + .collect::<anyhow::Result<Vec<_>>>() 398 + .unwrap() 399 + } 400 + 401 + #[test] 402 + fn modification() { 403 + let (_dir, repo) = repo(); 404 + let old = tree(&repo, &[("a", b"x\n")]); 405 + let new = tree(&repo, &[("a", b"y\n")]); 406 + 407 + let got = diffs(&repo, old, new); 408 + assert_eq!(got.len(), 1); 409 + let d = &got[0]; 410 + assert_eq!(d.lhs_src.path, "a"); 411 + assert_eq!(d.rhs_src.path, "a"); 412 + assert!(!d.lhs_src.is_binary && !d.rhs_src.is_binary); 413 + assert_eq!(d.has_byte_changes, Some((2, 2))); 414 + assert_eq!(d.hunks.len(), 1); 415 + let h = &d.hunks[0]; 416 + assert!(h.novel_lhs.contains(&LineNumber(0))); 417 + assert!(h.novel_rhs.contains(&LineNumber(0))); 418 + } 419 + 420 + #[test] 421 + fn addition() { 422 + let (_dir, repo) = repo(); 423 + let old = tree(&repo, &[]); 424 + let new = tree(&repo, &[("a", b"hello\n")]); 425 + 426 + let got = diffs(&repo, old, new); 427 + assert_eq!(got.len(), 1); 428 + let d = &got[0]; 429 + assert_eq!(d.lhs_src.size, 0); // old side missing 430 + assert_eq!(d.rhs_src.size, 6); 431 + assert_eq!(d.rhs_src.path, "a"); 432 + assert!(d.has_byte_changes.is_some()); 433 + } 434 + 435 + #[test] 436 + fn deletion() { 437 + let (_dir, repo) = repo(); 438 + let old = tree(&repo, &[("a", b"hello\n")]); 439 + let new = tree(&repo, &[]); 440 + 441 + let got = diffs(&repo, old, new); 442 + assert_eq!(got.len(), 1); 443 + let d = &got[0]; 444 + assert_eq!(d.lhs_src.size, 6); 445 + assert_eq!(d.rhs_src.size, 0); // new side missing 446 + assert_eq!(d.lhs_src.path, "a"); 447 + } 448 + 449 + #[test] 450 + fn binary() { 451 + let (_dir, repo) = repo(); 452 + let old = tree(&repo, &[("a", b"\x00\x01bin\n")]); 453 + let new = tree(&repo, &[("a", b"\x00\x01BIN\n")]); 454 + 455 + let got = diffs(&repo, old, new); 456 + assert_eq!(got.len(), 1); 457 + let d = &got[0]; 458 + assert!(d.lhs_src.is_binary && d.rhs_src.is_binary); 459 + assert!(d.hunks.is_empty()); 460 + } 461 + 462 + #[test] 463 + fn multiple_files_and_laziness() { 464 + let (_dir, repo) = repo(); 465 + let old = tree(&repo, &[("a", b"1\n"), ("b", b"1\n")]); 466 + let new = tree(&repo, &[("a", b"2\n"), ("b", b"2\n")]); 467 + 468 + let old_t = repo.find_tree(old).unwrap(); 469 + let new_t = repo.find_tree(new).unwrap(); 470 + 471 + // Lazy: pulling one item yields exactly one without consuming the rest. 472 + let mut it = diff(&repo, &old_t, &new_t).unwrap(); 473 + assert!(it.next().is_some()); 474 + 475 + let all = diffs(&repo, old, new); 476 + assert_eq!(all.len(), 2); 477 + let mut paths: Vec<_> = all.iter().map(|d| d.rhs_src.path.clone()).collect(); 478 + paths.sort(); 479 + assert_eq!(paths, vec!["a".to_string(), "b".to_string()]); 480 + } 481 + 482 + #[test] 483 + fn directory_addition_yields_inner_blobs() { 484 + let (_dir, repo) = repo(); 485 + let old = tree(&repo, &[]); 486 + // A whole new directory: gix emits a Tree change for `dir/` plus blob changes for the 487 + // files inside. We must skip the tree and yield only the blobs (no error). 488 + let new = tree(&repo, &[("dir/a", b"x\n"), ("dir/b", b"y\n")]); 489 + 490 + let got = diffs(&repo, old, new); 491 + let mut paths: Vec<_> = got.iter().map(|d| d.rhs_src.path.clone()).collect(); 492 + paths.sort(); 493 + assert_eq!(paths, vec!["dir/a".to_string(), "dir/b".to_string()]); 494 + } 495 + 496 + #[test] 497 + fn interdiff_equal_patches_is_empty() { 498 + let (_dir, repo) = repo(); 499 + // Shared base content B; patch P turns a's "1\n" into "2\n". 500 + let base = commit(&repo, tree(&repo, &[("a", b"1\n")]), &[]); 501 + let patched = tree(&repo, &[("a", b"2\n")]); 502 + // `from` and `to` both apply the same patch P on top of the same base. 503 + let from_head = commit(&repo, patched, &[base]); 504 + let to_head = commit(&repo, patched, &[base]); 505 + 506 + let (rebased, to_head_tree) = 507 + prepare_interdiff(&repo, (Some(base), from_head), (Some(base), to_head)).unwrap(); 508 + 509 + // Same patch ⇒ interdiff (diff of rebased vs to_head) is empty. 510 + let got = diff(&repo, &rebased, &to_head_tree) 511 + .unwrap() 512 + .collect::<anyhow::Result<Vec<_>>>() 513 + .unwrap(); 514 + assert!(got.is_empty(), "expected empty interdiff, got {got:?}"); 515 + } 516 + 517 + #[test] 518 + fn interdiff_divergent_shows_delta() { 519 + let (_dir, repo) = repo(); 520 + let base = commit(&repo, tree(&repo, &[("a", b"1\n")]), &[]); 521 + // `from` applies patch P (a: 1->2). 522 + let from_head = commit(&repo, tree(&repo, &[("a", b"2\n")]), &[base]); 523 + // `to` applies P plus a new file `c`. 524 + let to_head = commit(&repo, tree(&repo, &[("a", b"2\n"), ("c", b"new\n")]), &[base]); 525 + 526 + let (rebased, to_head_tree) = 527 + prepare_interdiff(&repo, (Some(base), from_head), (Some(base), to_head)).unwrap(); 528 + 529 + let got = diff(&repo, &rebased, &to_head_tree) 530 + .unwrap() 531 + .collect::<anyhow::Result<Vec<_>>>() 532 + .unwrap(); 533 + assert_eq!(got.len(), 1); 534 + assert_eq!(got[0].rhs_src.path, "c"); 535 + } 536 + 537 + #[test] 538 + fn submodule_modification() { 539 + let (_dir, repo) = repo(); 540 + // Two distinct commit oids for the gitlink to point at (need not be real submodules). 541 + let a = commit(&repo, tree(&repo, &[("x", b"1\n")]), &[]); 542 + let b = commit(&repo, tree(&repo, &[("x", b"2\n")]), &[]); 543 + let old = tree_with_gitlink(&repo, "sub", a); 544 + let new = tree_with_gitlink(&repo, "sub", b); 545 + 546 + let got = diffs(&repo, old, new); 547 + assert_eq!(got.len(), 1); 548 + let d = &got[0]; 549 + assert!(d.lhs_src.is_submodule && d.rhs_src.is_submodule); 550 + assert!(d.hunks.is_empty()); 551 + assert_eq!(d.lhs_src.path, "sub"); 552 + assert_eq!(d.lhs_src.oid, a.to_string()); 553 + assert_eq!(d.rhs_src.oid, b.to_string()); 554 + } 555 + 556 + #[test] 557 + fn submodule_addition() { 558 + let (_dir, repo) = repo(); 559 + let b = commit(&repo, tree(&repo, &[("x", b"1\n")]), &[]); 560 + let old = tree(&repo, &[]); 561 + let new = tree_with_gitlink(&repo, "sub", b); 562 + 563 + let got = diffs(&repo, old, new); 564 + assert_eq!(got.len(), 1); 565 + let d = &got[0]; 566 + assert!(d.rhs_src.is_submodule); 567 + assert!(d.hunks.is_empty()); 568 + assert_eq!(d.rhs_src.oid, b.to_string()); 569 + // Absent old side reported as the null oid. 570 + assert_eq!(d.lhs_src.oid, ObjectId::null(repo.object_hash()).to_string()); 571 + } 572 + }
+150 -13
gitmirror/src/main.rs
··· 1 - use std::env; 2 1 use std::net::SocketAddr; 3 2 3 + use clap::Parser; 4 4 use tonic::transport::Server; 5 5 use tonic::{Request, Response, Status}; 6 6 use tracing::info; 7 7 8 + mod diff; 9 + 8 10 pub mod gitmirror { 9 11 pub mod v1 { 10 12 tonic::include_proto!("gitmirror.v1"); 11 13 } 12 14 } 13 15 16 + use std::path::PathBuf; 17 + use std::pin::Pin; 18 + 19 + use gix::bstr::ByteSlice as _; 20 + use tokio::sync::mpsc; 21 + use tokio_stream::Stream; 22 + use tokio_stream::wrappers::ReceiverStream; 23 + 14 24 use gitmirror::v1::git_mirror_service_server::{GitMirrorService, GitMirrorServiceServer}; 15 - use gitmirror::v1::{CommitsRequest, CommitsResponse}; 25 + use gitmirror::v1::{ 26 + ByteChanges, CommitsRequest, CommitsResponse, DiffRequest, FileContent, FileDiff, Hunk, 27 + InterdiffRequest, LinePair, 28 + }; 29 + 30 + type FileDiffStream = Pin<Box<dyn Stream<Item = Result<FileDiff, Status>> + Send>>; 31 + 32 + #[derive(Parser)] 33 + #[command(name = "gitmirror", about = "Git mirror gRPC service")] 34 + struct Args { 35 + /// Address to bind the gRPC server to. 36 + #[arg(long, env = "GITMIRROR_ADDR", default_value = "127.0.0.1:9000")] 37 + addr: SocketAddr, 16 38 17 - #[derive(Default)] 18 - struct GitMirror {} 39 + /// Base directory holding bare mirror repos, one per DID (<base>/<did>). 40 + #[arg(long, env = "GITMIRROR_REPO_BASE", default_value = "repos")] 41 + repo_base: PathBuf, 42 + } 43 + 44 + struct GitMirror { 45 + repo_base: PathBuf, 46 + } 47 + 48 + impl GitMirror { 49 + fn open_repo(&self, did: &str) -> Result<gix::Repository, Status> { 50 + if !did.starts_with("did:") { 51 + return Err(Status::invalid_argument("repo must be a DID")); 52 + } 53 + gix::open(self.repo_base.join(did)) 54 + .map_err(|e| Status::not_found(format!("repo not found: {e}"))) 55 + } 56 + } 19 57 20 58 #[tonic::async_trait] 21 59 impl GitMirrorService for GitMirror { ··· 25 63 ) -> Result<Response<CommitsResponse>, Status> { 26 64 Err(Status::unimplemented("not implemented")) 27 65 } 66 + 67 + type DiffStream = FileDiffStream; 68 + 69 + async fn diff( 70 + &self, 71 + request: Request<DiffRequest>, 72 + ) -> Result<Response<Self::DiffStream>, Status> { 73 + let req = request.into_inner(); 74 + let repo = self.open_repo(&req.repo)?; 75 + // Resolve refs up-front so bad input surfaces as the RPC status, then move the 76 + // owned repo + oids into a blocking task for the (synchronous) diff loop. 77 + let old_oid = resolve_tree_oid(&repo, &req.base)?; 78 + let new_oid = resolve_tree_oid(&repo, &req.head)?; 79 + let safe = repo.into_sync(); 80 + 81 + let (tx, rx) = mpsc::channel::<Result<FileDiff, Status>>(16); 82 + tokio::task::spawn_blocking(move || { 83 + let repo = safe.to_thread_local(); 84 + let run = || -> anyhow::Result<()> { 85 + let old = repo.find_tree(old_oid)?; 86 + let new = repo.find_tree(new_oid)?; 87 + for item in diff::diff(&repo, &old, &new)? { 88 + let proto = to_proto(item?); 89 + if tx.blocking_send(Ok(proto)).is_err() { 90 + break; // client hung up 91 + } 92 + } 93 + Ok(()) 94 + }; 95 + if let Err(e) = run() { 96 + let _ = tx.blocking_send(Err(Status::internal(e.to_string()))); 97 + } 98 + }); 99 + 100 + Ok(Response::new(Box::pin(ReceiverStream::new(rx)))) 101 + } 102 + 103 + type InterdiffStream = FileDiffStream; 104 + 105 + async fn interdiff( 106 + &self, 107 + _request: Request<InterdiffRequest>, 108 + ) -> Result<Response<Self::InterdiffStream>, Status> { 109 + Err(Status::unimplemented("not implemented")) 110 + } 111 + } 112 + 113 + /// Resolve a commit-ish ref string (oid, short oid, branch/tag) to its tree oid. 114 + fn resolve_tree_oid(repo: &gix::Repository, spec: &str) -> Result<gix::ObjectId, Status> { 115 + let tree = repo 116 + .rev_parse_single(spec.as_bytes().as_bstr()) 117 + .map_err(|e| Status::invalid_argument(format!("bad rev '{spec}': {e}")))? 118 + .object() 119 + .map_err(|e| Status::internal(e.to_string()))? 120 + .peel_to_tree() 121 + .map_err(|e| Status::internal(e.to_string()))?; 122 + Ok(tree.id) 123 + } 124 + 125 + fn to_proto(d: diff::Diff) -> FileDiff { 126 + FileDiff { 127 + lhs_src: Some(file_content(d.lhs_src)), 128 + rhs_src: Some(file_content(d.rhs_src)), 129 + hunks: d.hunks.into_iter().map(hunk).collect(), 130 + has_byte_changes: d.has_byte_changes.map(|(lhs, rhs)| ByteChanges { 131 + lhs: lhs as u64, 132 + rhs: rhs as u64, 133 + }), 134 + has_syntactic_changes: d.has_syntactic_changes, 135 + } 136 + } 137 + 138 + fn file_content(f: diff::FileContent) -> FileContent { 139 + FileContent { 140 + path: f.path, 141 + oid: f.oid, 142 + size: f.size as u64, 143 + is_binary: f.is_binary, 144 + is_submodule: f.is_submodule, 145 + } 146 + } 147 + 148 + fn hunk(h: diff::Hunk) -> Hunk { 149 + // Sort the novel line sets for deterministic output. 150 + let sorted = |set: rustc_hash::FxHashSet<line_numbers::LineNumber>| -> Vec<u32> { 151 + let mut v: Vec<u32> = set.into_iter().map(|n| n.0).collect(); 152 + v.sort_unstable(); 153 + v 154 + }; 155 + Hunk { 156 + novel_lhs: sorted(h.novel_lhs), 157 + novel_rhs: sorted(h.novel_rhs), 158 + lines: h 159 + .lines 160 + .into_iter() 161 + .map(|(lhs, rhs)| LinePair { 162 + lhs: lhs.map(|n| n.0), 163 + rhs: rhs.map(|n| n.0), 164 + }) 165 + .collect(), 166 + } 28 167 } 29 168 30 169 #[tokio::main] 31 170 async fn main() -> Result<(), Box<dyn std::error::Error>> { 32 171 tracing_subscriber::fmt() 33 172 .with_env_filter( 34 - tracing_subscriber::EnvFilter::try_from_default_env() 35 - .unwrap_or_else(|_| "info".into()), 173 + tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()), 36 174 ) 37 175 .init(); 38 176 39 - let addr: SocketAddr = env::var("GITMIRROR_ADDR") 40 - .unwrap_or_else(|_| "127.0.0.1:9000".to_string()) 41 - .parse()?; 42 - 43 - let service = GitMirror::default(); 44 - info!(%addr, "gitmirror gRPC server listening"); 177 + let args = Args::parse(); 178 + let service = GitMirror { 179 + repo_base: args.repo_base, 180 + }; 181 + info!(addr = %args.addr, "gitmirror gRPC server listening"); 45 182 46 183 Server::builder() 47 184 .add_service(GitMirrorServiceServer::new(service)) 48 - .serve(addr) 185 + .serve(args.addr) 49 186 .await?; 50 187 51 188 Ok(())
+1 -1
go.mod
··· 81 81 golang.org/x/sync v0.20.0 82 82 golang.org/x/sys v0.45.0 83 83 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 84 + google.golang.org/grpc v1.80.0 84 85 google.golang.org/protobuf v1.36.11 85 86 gopkg.in/yaml.v3 v3.0.1 86 87 ) ··· 310 311 golang.org/x/tools v0.44.0 // indirect 311 312 google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect 312 313 google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect 313 - google.golang.org/grpc v1.80.0 // indirect 314 314 gopkg.in/fsnotify.v1 v1.4.7 // indirect 315 315 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 316 316 gopkg.in/warnings.v0 v0.1.2 // indirect
+10
localinfra/Caddyfile
··· 53 53 reverse_proxy knotmirror:7000 54 54 } 55 55 56 + gitmirror.tngl.boltless.dev { 57 + tls internal 58 + # gitmirror (tonic) serves h2c; Caddy defaults to HTTP/1.1 to the backend. 59 + reverse_proxy gitmirror:9000 { 60 + transport http { 61 + versions h2c 2 62 + } 63 + } 64 + } 65 + 56 66 # zoekt (internal service) 57 67 zoekt.tngl.boltless.dev { 58 68 tls internal
+29
localinfra/gitmirror.Dockerfile
··· 1 + # Development only. Not for production use. 2 + 3 + FROM rust:1.96-bookworm AS build 4 + 5 + RUN apt-get update && apt-get install -y --no-install-recommends protobuf-compiler \ 6 + && rm -rf /var/lib/apt/lists/* 7 + 8 + WORKDIR /src 9 + COPY . . 10 + 11 + # Cache mounts on the cargo registry + target dir. The binary is copied out within the same 12 + # RUN because the target cache mount is unmounted afterwards (a later COPY --from could not 13 + # see it). 14 + RUN --mount=type=cache,target=/usr/local/cargo/registry \ 15 + --mount=type=cache,target=/src/target \ 16 + cargo build --release -p gitmirror \ 17 + && cp /src/target/release/gitmirror /usr/local/bin/gitmirror 18 + 19 + FROM debian:bookworm-slim 20 + 21 + RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates tini \ 22 + && rm -rf /var/lib/apt/lists/* 23 + 24 + COPY --from=build /usr/local/bin/gitmirror /usr/local/bin/gitmirror 25 + 26 + EXPOSE 9000 27 + 28 + ENTRYPOINT ["/usr/bin/tini", "--"] 29 + CMD ["sh", "-c", "if [ -f /usr/local/share/ca-certificates/caddy.crt ]; then update-ca-certificates; fi && exec /usr/local/bin/gitmirror"]