diff --git a/Cargo.lock b/Cargo.lock index 20f6c9c..bb2439c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,7 +389,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown 0.14.5", + "hashbrown", "stacker", ] @@ -526,19 +526,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -548,34 +535,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -624,7 +583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.5", + "hashbrown", "lock_api", "once_cell", "parking_lot_core", @@ -704,6 +663,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs" version = "1.0.5" @@ -711,10 +679,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", - "redox_users", + "redox_users 0.3.5", "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.5", + "windows-sys 0.48.0", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -1046,13 +1026,16 @@ dependencies = [ "assert2", "bytes", "clap", + "color-eyre", "derive-with", "derive_more", + "directories", "git-conventional", "git-next-core", "git-next-forge-forgejo", "git-next-forge-github", "kxio", + "lazy_static", "lettre", "mockall", "notifica", @@ -1070,6 +1053,7 @@ dependencies = [ "time", "toml", "tracing", + "tracing-error", "tracing-subscriber", "ulid", "warp", @@ -1156,9 +1140,9 @@ dependencies = [ [[package]] name = "gix" -version = "0.64.0" +version = "0.66.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78414d29fcc82329080166077e0f7689f4016551fdb334d787c3d040fe2634f" +checksum = "9048b8d1ae2104f045cb37e5c450fc49d5d8af22609386bfc739c11ba88995eb" dependencies = [ "gix-actor", "gix-archive", @@ -1180,7 +1164,6 @@ dependencies = [ "gix-ignore", "gix-index", "gix-lock", - "gix-macros", "gix-mailmap", "gix-negotiate", "gix-object", @@ -1217,9 +1200,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.31.5" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e454357e34b833cc3a00b6efbbd3dd4d18b24b9fb0c023876ec2645e8aa3f2" +checksum = "fc19e312cd45c4a66cd003f909163dc2f8e1623e30a0c0c6df3776e89b308665" dependencies = [ "bstr", "gix-date", @@ -1231,22 +1214,23 @@ dependencies = [ [[package]] name = "gix-archive" -version = "0.13.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b6bbebdf0223d1d4a69d6027e8b2482daad8eb1a8d3ec97176c7ec58e796d4" +checksum = "9147c08a55c1398b755539e2cdd63ff690ffe4a2e5e5e0780ee6ef2b49b0a60a" dependencies = [ "bstr", "gix-date", "gix-object", "gix-worktree-stream", + "jiff", "thiserror", ] [[package]] name = "gix-attributes" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37ce99c7e81288c28b703641b6d5d119aacc45c1a6b247156e6249afa486257" +checksum = "ebccbf25aa4a973dd352564a9000af69edca90623e8a16dad9cbc03713131311" dependencies = [ "bstr", "gix-glob", @@ -1279,9 +1263,9 @@ dependencies = [ [[package]] name = "gix-command" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d76867867da891cbe32021ad454e8cae90242f6afb06762e4dd0d357afd1d7b" +checksum = "dff2e692b36bbcf09286c70803006ca3fd56551a311de450be317a0ab8ea92e7" dependencies = [ "bstr", "gix-path", @@ -1305,9 +1289,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.38.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f53fd03d1bf09ebcc2c8654f08969439c4556e644ca925f27cf033bc43e658" +checksum = "78e797487e6ca3552491de1131b4f72202f282fb33f198b1c34406d765b42bb0" dependencies = [ "bstr", "gix-config-value", @@ -1326,9 +1310,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b328997d74dd15dc71b2773b162cb4af9a25c424105e4876e6d0686ab41c383e" +checksum = "03f76169faa0dec598eac60f83d7fcdd739ec16596eca8fb144c88973dbe6f8c" dependencies = [ "bitflags 2.5.0", "bstr", @@ -1339,9 +1323,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.24.4" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198588f532e4d1202e04e6c3f50e4d7c060dffc66801c6f53cc246f1d234739e" +checksum = "8ce391d305968782f1ae301c4a3d42c5701df7ff1d8bc03740300f6fd12bce78" dependencies = [ "bstr", "gix-command", @@ -1356,21 +1340,21 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.8.7" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0" +checksum = "35c84b7af01e68daf7a6bb8bb909c1ff5edb3ce4326f1f43063a5a96d3c3c8a5" dependencies = [ "bstr", "itoa", + "jiff", "thiserror", - "time", ] [[package]] name = "gix-diff" -version = "0.44.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996d5c8a305b59709467d80617c9fde48d9d75fd1f4179ea970912630886c9d" +checksum = "92c9afd80fff00f8b38b1c1928442feb4cd6d2232a6ed806b6b193151a3d336c" dependencies = [ "bstr", "gix-command", @@ -1388,9 +1372,9 @@ dependencies = [ [[package]] name = "gix-dir" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c975679aa00dd2d757bfd3ddb232e8a188c0094c3306400575a0813858b1365" +checksum = "0ed3a9076661359a1c5a27c12ad6c3ebe2dd96b8b3c0af6488ab7c128b7bdd98" dependencies = [ "bstr", "gix-discover", @@ -1408,9 +1392,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67662731cec3cb31ba3ed2463809493f76d8e5d6c6d245de8b0560438c13450e" +checksum = "0577366b9567376bc26e815fd74451ebd0e6218814e242f8e5b7072c58d956d2" dependencies = [ "bstr", "dunce", @@ -1436,7 +1420,6 @@ dependencies = [ "gix-hash", "gix-trace", "gix-utils", - "jwalk", "libc", "once_cell", "parking_lot", @@ -1448,9 +1431,9 @@ dependencies = [ [[package]] name = "gix-filter" -version = "0.11.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6547738da28275f4dff4e9f3a0f28509f53f94dd6bd822733c91cb306bca61a" +checksum = "4121790ae140066e5b953becc72e7496278138d19239be2e63b5067b0843119e" dependencies = [ "bstr", "encoding_rs", @@ -1469,9 +1452,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6adf99c27cdf17b1c4d77680c917e0d94d8783d4e1c73d3be0d1d63107163d7a" +checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575" dependencies = [ "fastrand", "gix-features", @@ -1480,9 +1463,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.16.4" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7df15afa265cc8abe92813cd354d522f1ac06b29ec6dfa163ad320575cb447" +checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111" dependencies = [ "bitflags 2.5.0", "bstr", @@ -1507,15 +1490,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" dependencies = [ "gix-hash", - "hashbrown 0.14.5", + "hashbrown", "parking_lot", ] [[package]] name = "gix-ignore" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6afb8f98e314d4e1adc822449389ada863c174b5707cedd327d67b84dba527" +checksum = "e447cd96598460f5906a0f6c75e950a39f98c2705fc755ad2f2020c9e937fab7" dependencies = [ "bstr", "gix-glob", @@ -1526,9 +1509,9 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.33.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9a44eb55bd84bb48f8a44980e951968ced21e171b22d115d1cdcef82a7d73f" +checksum = "0cd4203244444017682176e65fd0180be9298e58ed90bd4a8489a357795ed22d" dependencies = [ "bitflags 2.5.0", "bstr", @@ -1543,7 +1526,7 @@ dependencies = [ "gix-traverse", "gix-utils", "gix-validate", - "hashbrown 0.14.5", + "hashbrown", "itoa", "libc", "memmap2", @@ -1563,22 +1546,11 @@ dependencies = [ "thiserror", ] -[[package]] -name = "gix-macros" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "gix-mailmap" -version = "0.23.5" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6daca6edb6a590c7c0533f3f8e75c54663eb56ce08f46f0891db9fc6f09208" +checksum = "d7d522c8ec2501e1a5b2b4cb54e83cb5d9a52471c9d23b3a1e8dadaf063752f7" dependencies = [ "bstr", "gix-actor", @@ -1588,9 +1560,9 @@ dependencies = [ [[package]] name = "gix-negotiate" -version = "0.13.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec879fb6307bb63519ba89be0024c6f61b4b9d61f1a91fd2ce572d89fe9c224" +checksum = "b4063bf329a191a9e24b6f948a17ccf6698c0380297f5e169cee4f1d2ab9475b" dependencies = [ "bitflags 2.5.0", "gix-commitgraph", @@ -1604,9 +1576,9 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.42.3" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25da2f46b4e7c2fa7b413ce4dffb87f69eaf89c2057e386491f4c55cadbfe386" +checksum = "2f5b801834f1de7640731820c2df6ba88d95480dc4ab166a5882f8ff12b88efa" dependencies = [ "bstr", "gix-actor", @@ -1623,9 +1595,9 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.61.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20d384fe541d93d8a3bb7d5d5ef210780d6df4f50c4e684ccba32665a5e3bc9b" +checksum = "a3158068701c17df54f0ab2adda527f5a6aca38fd5fd80ceb7e3c0a2717ec747" dependencies = [ "arc-swap", "gix-date", @@ -1643,9 +1615,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.51.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0594491fffe55df94ba1c111a6566b7f56b3f8d2e1efc750e77d572f5f5229" +checksum = "3223aa342eee21e1e0e403cad8ae9caf9edca55ef84c347738d10681676fd954" dependencies = [ "clru", "gix-chunk", @@ -1664,9 +1636,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b70486beda0903b6d5b65dfa6e40585098cdf4e6365ca2dff4f74c387354a515" +checksum = "8c43ef4d5fe2fa222c606731c8bdbf4481413ee4ef46d61340ec39e4df4c5e49" dependencies = [ "bstr", "faster-hex", @@ -1676,9 +1648,9 @@ dependencies = [ [[package]] name = "gix-packetline-blocking" -version = "0.17.4" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31d42378a3d284732e4d589979930d0d253360eccf7ec7a80332e5ccb77e14a" +checksum = "b9802304baa798dd6f5ff8008a2b6516d54b74a69ca2d3a2b9e2d6c3b5556b40" dependencies = [ "bstr", "faster-hex", @@ -1688,9 +1660,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.9" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d23d5bbda31344d8abc8de7c075b3cf26e5873feba7c4a15d916bce67382bd9" +checksum = "38d5b8722112fa2fa87135298780bc833b0e9f6c56cc82795d209804b3a03484" dependencies = [ "bstr", "gix-trace", @@ -1701,9 +1673,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d307d1b8f84dc8386c4aa20ce0cf09242033840e15469a3ecba92f10cfb5c046" +checksum = "5d23bf239532b4414d0e63b8ab3a65481881f7237ed9647bb10c1e3cc54c5ceb" dependencies = [ "bitflags 2.5.0", "bstr", @@ -1716,9 +1688,9 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0595d2be4b6d6a71a099e989bdd610882b882da35fb8503d91d6f81aa0936f" +checksum = "74fde865cdb46b30d8dad1293385d9bcf998d3a39cbf41bee67d0dab026fe6b1" dependencies = [ "gix-command", "gix-config-value", @@ -1729,9 +1701,9 @@ dependencies = [ [[package]] name = "gix-protocol" -version = "0.45.2" +version = "0.45.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad8da8e89f24177bd77947092199bb13dcc318bbd73530ba8a05e6d6adaaa9d" +checksum = "cc43a1006f01b5efee22a003928c9eb83dde2f52779ded9d4c0732ad93164e3e" dependencies = [ "bstr", "gix-credentials", @@ -1758,9 +1730,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.45.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636e96a0a5562715153fee098c217110c33a6f8218f08f4687ff99afde159bb5" +checksum = "ae0d8406ebf9aaa91f55a57f053c5a1ad1a39f60fdf0303142b7be7ea44311e5" dependencies = [ "gix-actor", "gix-features", @@ -1779,9 +1751,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.23.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868f8cd2e62555d1f7c78b784bece43ace40dd2a462daf3b588d5416e603f37" +checksum = "ebb005f82341ba67615ffdd9f7742c87787544441c88090878393d0682869ca6" dependencies = [ "bstr", "gix-hash", @@ -1793,9 +1765,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.27.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b13e43c2118c4b0537ddac7d0821ae0dfa90b7b8dbf20c711e153fb749adce" +checksum = "ba4621b219ac0cdb9256883030c3d56a6c64a6deaa829a92da73b9a576825e1e" dependencies = [ "bstr", "gix-date", @@ -1809,9 +1781,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.13.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b030ccaab71af141f537e0225f19b9e74f25fefdba0372246b844491cab43e0" +checksum = "b41e72544b93084ee682ef3d5b31b1ba4d8fa27a017482900e5e044d5b1b3984" dependencies = [ "gix-commitgraph", "gix-date", @@ -1824,9 +1796,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1547d26fa5693a7f34f05b4a3b59a90890972922172653bcb891ab3f09f436df" +checksum = "0fe4d52f30a737bbece5276fab5d3a8b276dc2650df963e293d0673be34e7a5f" dependencies = [ "bitflags 2.5.0", "gix-path", @@ -1836,9 +1808,9 @@ dependencies = [ [[package]] name = "gix-status" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f7b084cb65c3d007ce6bb479755ca13d602ca3cd91c4f08d7e59904de33736" +checksum = "f70d35ba639f0c16a6e4cca81aa374a05f07b23fa36ee8beb72c100d98b4ffea" dependencies = [ "bstr", "filetime", @@ -1859,9 +1831,9 @@ dependencies = [ [[package]] name = "gix-submodule" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2e0f69aa00805e39d39ec80472a7e9da20ed5d73318b27925a2cc198e854fd" +checksum = "529d0af78cc2f372b3218f15eb1e3d1635a21c8937c12e2dd0b6fc80c2ca874b" dependencies = [ "bstr", "gix-config", @@ -1896,9 +1868,9 @@ checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" [[package]] name = "gix-transport" -version = "0.42.2" +version = "0.42.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c02b83763ffe95bcc27ce5821b2b7f843315a009c06f1cd59c9b66c508c058" +checksum = "421dcccab01b41a15d97b226ad97a8f9262295044e34fbd37b10e493b0a6481f" dependencies = [ "base64 0.22.1", "bstr", @@ -1915,9 +1887,9 @@ dependencies = [ [[package]] name = "gix-traverse" -version = "0.39.2" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e499a18c511e71cf4a20413b743b9f5bcf64b3d9e81e9c3c6cd399eae55a8840" +checksum = "030da39af94e4df35472e9318228f36530989327906f38e27807df305fccb780" dependencies = [ "bitflags 2.5.0", "gix-commitgraph", @@ -1932,9 +1904,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.27.4" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2eb9b35bba92ea8f0b5ab406fad3cf6b87f7929aa677ff10aa042c6da621156" +checksum = "fd280c5e84fb22e128ed2a053a0daeacb6379469be6a85e3d518a0636e160c89" dependencies = [ "bstr", "gix-features", @@ -1957,9 +1929,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.8.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" +checksum = "81f2badbb64e57b404593ee26b752c26991910fd0d81fe6f9a71c1a8309b6c86" dependencies = [ "bstr", "thiserror", @@ -1967,9 +1939,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.34.1" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f7326ebe0b9172220694ea69d344c536009a9b98fb0f9de092c440f3efe7a6" +checksum = "c312ad76a3f2ba8e865b360d5cb3aa04660971d16dec6dd0ce717938d903149a" dependencies = [ "bstr", "gix-attributes", @@ -1986,9 +1958,9 @@ dependencies = [ [[package]] name = "gix-worktree-state" -version = "0.11.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ed6205b5f51067a485b11843babcf3304c0799e265a06eb0dde7f69cd85cd8" +checksum = "7b05c4b313fa702c0bacd5068dd3e01671da73b938fade97676859fee286de43" dependencies = [ "bstr", "gix-features", @@ -2006,9 +1978,9 @@ dependencies = [ [[package]] name = "gix-worktree-stream" -version = "0.13.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35d4896249a41856f44571d94d7583b9f3b9cd1a75eaef4f34a4aa2981bed21" +checksum = "68e81b87c1a3ece22a54b682d6fdc37fbb3977132da972cafe5ec07175fddbca" dependencies = [ "gix-attributes", "gix-features", @@ -2066,12 +2038,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -2371,12 +2337,12 @@ dependencies = [ [[package]] name = "imara-diff" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" +checksum = "fc9da1a252bd44cd341657203722352efc9bc0c847d06ea6d2dc1cd1135e0a01" dependencies = [ "ahash", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -2392,7 +2358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -2467,6 +2433,31 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jiff" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2b7379a75544c94b3da32821b0bf41f9062e9970e23b78cc577d0d89676d16" +dependencies = [ + "jiff-tzdb-platform", + "windows-sys 0.52.0", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05fac328b3df1c0f18a3c2ab6cb7e06e4e549f366017d796e3e66b6d6889abe6" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8da387d5feaf355954c2c122c194d6df9c57d865125a67984bb453db5336940" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -2476,16 +2467,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" -dependencies = [ - "crossbeam", - "rayon", -] - [[package]] name = "kqueue" version = "1.0.8" @@ -2537,9 +2518,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lettre" @@ -2581,6 +2562,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -2609,7 +2600,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -2871,15 +2862,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "objc" version = "0.2.7" @@ -2968,6 +2950,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "overload" version = "0.1.1" @@ -3222,26 +3210,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.1.57" @@ -3277,6 +3245,17 @@ dependencies = [ "rust-argon2", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom 0.2.14", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.4" @@ -3972,14 +3951,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", - "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", - "time-macros", ] [[package]] @@ -3988,16 +3963,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 99d465e..4dbda4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,13 +28,17 @@ git-next-forge-github = { path = "crates/forge-github", version = "0.13" } # TUI ratatui = "0.28" +directories = "5.0.1" +lazy_static = "1.5.0" +color-eyre = "0.6.3" # CLI parsing clap = { version = "4.5", features = ["cargo", "derive"] } # logging tracing = "0.1" -tracing-subscriber = "0.3" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-error = "0.2.0" # base64 decoding base64 = "0.22" @@ -46,7 +50,7 @@ hex = "0.4" # git # gix = "0.62" -gix = { version = "0.64", features = [ +gix = { version = "0.66", features = [ "dirwalk", "blocking-http-transport-reqwest-rust-tls", ] } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index afa3cac..2871162 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -12,10 +12,11 @@ keywords = { workspace = true } categories = { workspace = true } [features] -default = ["forgejo", "github"] +# default = ["forgejo", "github"] +default = ["forgejo", "github", "tui"] forgejo = ["git-next-forge-forgejo"] github = ["git-next-forge-github"] -tui = ["ratatui"] +tui = ["ratatui", "directories", "lazy_static"] [dependencies] git-next-core = { workspace = true } @@ -24,6 +25,9 @@ git-next-forge-github = { workspace = true, optional = true } # TUI ratatui = { workspace = true, optional = true } +directories = { workspace = true, optional = true } +lazy_static = { workspace = true, optional = true } +color-eyre = { workspace = true } # CLI parsing clap = { workspace = true } @@ -34,6 +38,7 @@ kxio = { workspace = true } # logging tracing = { workspace = true } tracing-subscriber = { workspace = true } +tracing-error.workspace = true # Conventional Commit check git-conventional = { workspace = true } diff --git a/crates/cli/src/init.rs b/crates/cli/src/init.rs index a13a10a..6b6cbca 100644 --- a/crates/cli/src/init.rs +++ b/crates/cli/src/init.rs @@ -1,5 +1,5 @@ // -use anyhow::{Context, Result}; +use color_eyre::{eyre::Context, Result}; use kxio::fs::FileSystem; pub fn run(fs: &FileSystem) -> Result<()> { diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index f054c2c..b93a3df 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,4 +1,6 @@ // +#![allow(clippy::module_name_repetitions)] + mod alerts; mod file_watcher; mod forge; @@ -17,8 +19,8 @@ use git_next_core::git; use std::path::PathBuf; -use anyhow::Result; use clap::Parser; +use color_eyre::Result; use kxio::{fs, network::Network}; #[derive(Parser, Debug)] diff --git a/crates/cli/src/repo/branch.rs b/crates/cli/src/repo/branch.rs index 3bd4cde..dfe4da4 100644 --- a/crates/cli/src/repo/branch.rs +++ b/crates/cli/src/repo/branch.rs @@ -17,15 +17,13 @@ use tracing::{info, instrument, warn}; // advance next to the next commit towards the head of the dev branch #[instrument(fields(next), skip_all)] pub fn advance_next( - next: &Commit, - main: &Commit, - dev_commit_history: &[Commit], + commit: Option, + force: git_next_core::git::push::Force, repo_details: RepoDetails, repo_config: RepoConfig, open_repository: &dyn OpenRepositoryLike, message_token: MessageToken, ) -> Result { - let (commit, force) = find_next_commit_on_dev(next, main, dev_commit_history); let commit = commit.ok_or_else(|| Error::NextAtDev)?; validate_commit_message(commit.message())?; info!("Advancing next to commit '{}'", commit); diff --git a/crates/cli/src/repo/handlers/advance_main.rs b/crates/cli/src/repo/handlers/advance_main.rs index 8e25008..fcd4a50 100644 --- a/crates/cli/src/repo/handlers/advance_main.rs +++ b/crates/cli/src/repo/handlers/advance_main.rs @@ -5,11 +5,14 @@ use git_next_core::RepoConfigSource; use tracing::warn; -use crate::repo::{ - branch::advance_main, - do_send, - messages::{AdvanceMain, LoadConfigFromRepo, ValidateRepo}, - RepoActor, +use crate::{ + repo::{ + branch::advance_main, + do_send, + messages::{AdvanceMain, LoadConfigFromRepo, ValidateRepo}, + RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -26,13 +29,13 @@ impl Handler for RepoActor { let repo_details = self.repo_details.clone(); let addr = ctx.address(); let message_token = self.message_token; + let commit = msg.unwrap(); - match advance_main( - msg.unwrap(), - &repo_details, - &repo_config, - &**open_repository, - ) { + self.update_tui(RepoUpdate::AdvancingMain { + commit: commit.clone(), + }); + + match advance_main(commit, &repo_details, &repo_config, &**open_repository) { Err(err) => { warn!("advance main: {err}"); } diff --git a/crates/cli/src/repo/handlers/advance_next.rs b/crates/cli/src/repo/handlers/advance_next.rs index 747db94..1d90fcc 100644 --- a/crates/cli/src/repo/handlers/advance_next.rs +++ b/crates/cli/src/repo/handlers/advance_next.rs @@ -3,11 +3,14 @@ use actix::prelude::*; use tracing::warn; -use crate::repo::{ - branch::advance_next, - do_send, - messages::{AdvanceNext, AdvanceNextPayload, ValidateRepo}, - RepoActor, +use crate::{ + repo::{ + branch::{advance_next, find_next_commit_on_dev}, + do_send, + messages::{AdvanceNext, AdvanceNextPayload, ValidateRepo}, + RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -20,6 +23,7 @@ impl Handler for RepoActor { let Some(open_repository) = &self.open_repository else { return; }; + let AdvanceNextPayload { next, main, @@ -29,10 +33,15 @@ impl Handler for RepoActor { let repo_config = repo_config.clone(); let addr = ctx.address(); + let (commit, force) = find_next_commit_on_dev(&next, &main, &dev_commit_history); + self.update_tui(RepoUpdate::AdvancingNext { + commit: commit.clone(), + force: force.clone(), + }); + match advance_next( - &next, - &main, - &dev_commit_history, + commit, + force, repo_details, repo_config, &**open_repository, diff --git a/crates/cli/src/repo/handlers/check_ci_status.rs b/crates/cli/src/repo/handlers/check_ci_status.rs index 122f019..8128998 100644 --- a/crates/cli/src/repo/handlers/check_ci_status.rs +++ b/crates/cli/src/repo/handlers/check_ci_status.rs @@ -3,10 +3,13 @@ use actix::prelude::*; use tracing::{debug, Instrument as _}; -use crate::repo::{ - do_send, - messages::{CheckCIStatus, ReceiveCIStatus}, - RepoActor, +use crate::{ + repo::{ + do_send, + messages::{CheckCIStatus, ReceiveCIStatus}, + RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -14,11 +17,13 @@ impl Handler for RepoActor { fn handle(&mut self, msg: CheckCIStatus, ctx: &mut Self::Context) -> Self::Result { crate::repo::logger(self.log.as_ref(), "start: CheckCIStatus"); + let addr = ctx.address(); let forge = self.forge.duplicate(); let next = msg.unwrap(); let log = self.log.clone(); + self.update_tui(RepoUpdate::CheckingCI); // get the status - pass, fail, pending (all others map to fail, e.g. error) async move { let status = forge.commit_status(&next).await; diff --git a/crates/cli/src/repo/handlers/clone_repo.rs b/crates/cli/src/repo/handlers/clone_repo.rs index a851c69..7917ba4 100644 --- a/crates/cli/src/repo/handlers/clone_repo.rs +++ b/crates/cli/src/repo/handlers/clone_repo.rs @@ -4,10 +4,13 @@ use actix::prelude::*; use git_next_core::git; use tracing::{debug, instrument, warn}; -use crate::repo::{ - do_send, logger, - messages::{CloneRepo, LoadConfigFromRepo, RegisterWebhook}, - RepoActor, +use crate::{ + repo::{ + do_send, logger, + messages::{CloneRepo, LoadConfigFromRepo, RegisterWebhook}, + RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -15,21 +18,26 @@ impl Handler for RepoActor { #[instrument(name = "RepoActor::CloneRepo", skip_all, fields(repo = %self.repo_details))] fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result { logger(self.log.as_ref(), "Handler: CloneRepo: start"); + self.update_tui(RepoUpdate::Opening); debug!("Handler: CloneRepo: start"); match git::repository::open(&*self.repository_factory, &self.repo_details) { Ok(repository) => { logger(self.log.as_ref(), "open okay"); debug!("open okay"); + self.update_tui(RepoUpdate::Opened); self.open_repository.replace(repository); if self.repo_details.repo_config.is_none() { + self.update_tui(RepoUpdate::LoadingConfigFromRepo); do_send(&ctx.address(), LoadConfigFromRepo, self.log.as_ref()); } else { + self.update_tui(RepoUpdate::RegisteringWebhook); do_send(&ctx.address(), RegisterWebhook::new(), self.log.as_ref()); } } Err(err) => { logger(self.log.as_ref(), "open failed"); warn!("Could not open repo: {err:?}"); + self.alert_tui(err.to_string()); } } debug!("Handler: CloneRepo: finish"); diff --git a/crates/cli/src/repo/handlers/load_config_from_repo.rs b/crates/cli/src/repo/handlers/load_config_from_repo.rs index dd3567b..de0da29 100644 --- a/crates/cli/src/repo/handlers/load_config_from_repo.rs +++ b/crates/cli/src/repo/handlers/load_config_from_repo.rs @@ -5,10 +5,13 @@ use git_next_core::git::UserNotification; use tracing::{debug, instrument, Instrument as _}; -use crate::repo::{ - do_send, load, - messages::{LoadConfigFromRepo, ReceiveRepoConfig}, - notify_user, RepoActor, +use crate::{ + repo::{ + do_send, load, + messages::{LoadConfigFromRepo, ReceiveRepoConfig}, + notify_user, RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -16,6 +19,7 @@ impl Handler for RepoActor { #[instrument(name = "Repocrate::repo::LoadConfigFromRepo", skip_all, fields(repo = %self.repo_details))] fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result { debug!("Handler: LoadConfigFromRepo: start"); + self.update_tui(RepoUpdate::LoadingConfigFromRepo); let Some(open_repository) = &self.open_repository else { return; }; diff --git a/crates/cli/src/repo/handlers/receive_ci_status.rs b/crates/cli/src/repo/handlers/receive_ci_status.rs index 6d8e272..1294f67 100644 --- a/crates/cli/src/repo/handlers/receive_ci_status.rs +++ b/crates/cli/src/repo/handlers/receive_ci_status.rs @@ -4,10 +4,13 @@ use actix::prelude::*; use git_next_core::git::{forge::commit::Status, graph, UserNotification}; use tracing::debug; -use crate::repo::{ - delay_send, do_send, logger, - messages::{AdvanceMain, ReceiveCIStatus, ValidateRepo}, - notify_user, RepoActor, +use crate::{ + repo::{ + delay_send, do_send, logger, + messages::{AdvanceMain, ReceiveCIStatus, ValidateRepo}, + notify_user, RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -22,6 +25,11 @@ impl Handler for RepoActor { let repo_alias = self.repo_details.repo_alias.clone(); let message_token = self.message_token; let sleep_duration = self.sleep_duration; + let graph_log = graph::log(&self.repo_details); + self.update_tui_log(graph_log.clone()); + self.update_tui(RepoUpdate::ReceiveCIStatus { + status: status.clone(), + }); debug!(?status, ""); match status { @@ -34,13 +42,14 @@ impl Handler for RepoActor { } Status::Fail => { tracing::warn!("Checks have failed"); + notify_user( self.notify_user_recipient.as_ref(), UserNotification::CICheckFailed { forge_alias, repo_alias, commit: next, - log: graph::log(&self.repo_details), + log: graph_log, }, log.as_ref(), ); diff --git a/crates/cli/src/repo/handlers/receive_repo_config.rs b/crates/cli/src/repo/handlers/receive_repo_config.rs index 12934d8..c496be3 100644 --- a/crates/cli/src/repo/handlers/receive_repo_config.rs +++ b/crates/cli/src/repo/handlers/receive_repo_config.rs @@ -2,10 +2,13 @@ use actix::prelude::*; use tracing::instrument; -use crate::repo::{ - do_send, - messages::{ReceiveRepoConfig, RegisterWebhook}, - RepoActor, +use crate::{ + repo::{ + do_send, + messages::{ReceiveRepoConfig, RegisterWebhook}, + RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { @@ -13,8 +16,11 @@ impl Handler for RepoActor { #[instrument(name = "RepoActor::ReceiveRepoConfig", skip_all, fields(repo = %self.repo_details, branches = ?msg))] fn handle(&mut self, msg: ReceiveRepoConfig, ctx: &mut Self::Context) -> Self::Result { let repo_config = msg.unwrap(); + self.update_tui(RepoUpdate::ReceiveRepoConfig { + repo_config: repo_config.clone(), + }); self.repo_details.repo_config.replace(repo_config); - + self.update_tui_branches(); do_send(&ctx.address(), RegisterWebhook::new(), self.log.as_ref()); } } diff --git a/crates/cli/src/repo/handlers/register_webhook.rs b/crates/cli/src/repo/handlers/register_webhook.rs index 4981918..8e6704f 100644 --- a/crates/cli/src/repo/handlers/register_webhook.rs +++ b/crates/cli/src/repo/handlers/register_webhook.rs @@ -1,12 +1,15 @@ // use actix::prelude::*; -use tracing::{debug, Instrument as _}; +use tracing::{debug, error, Instrument as _}; -use crate::repo::{ - do_send, - messages::{RegisterWebhook, WebhookRegistered}, - notify_user, RepoActor, +use crate::{ + repo::{ + do_send, + messages::{RegisterWebhook, WebhookRegistered}, + notify_user, RepoActor, + }, + server::actor::messages::RepoUpdate, }; use git_next_core::git::UserNotification; @@ -25,11 +28,12 @@ impl Handler for RepoActor { let addr = ctx.address(); let notify_user_recipient = self.notify_user_recipient.clone(); let log = self.log.clone(); + self.update_tui(RepoUpdate::RegisteringWebhook); debug!("registering webhook"); async move { match forge.register_webhook(&repo_listen_url).await { Ok(registered_webhook) => { - debug!(?registered_webhook, ""); + debug!(?registered_webhook, "webhook registered"); do_send( &addr, WebhookRegistered::from(registered_webhook), @@ -37,6 +41,7 @@ impl Handler for RepoActor { ); } Err(err) => { + error!(?err, "failed to register webhook"); notify_user( notify_user_recipient.as_ref(), UserNotification::WebhookRegistration { @@ -52,6 +57,8 @@ impl Handler for RepoActor { .in_current_span() .into_actor(self) .wait(ctx); + } else { + self.alert_tui("already have a webhook id - cant register webhook"); } } } diff --git a/crates/cli/src/repo/handlers/unregister_webhook.rs b/crates/cli/src/repo/handlers/unregister_webhook.rs index 8c748ac..fcc7725 100644 --- a/crates/cli/src/repo/handlers/unregister_webhook.rs +++ b/crates/cli/src/repo/handlers/unregister_webhook.rs @@ -3,13 +3,17 @@ use actix::prelude::*; use tracing::{debug, warn, Instrument as _}; -use crate::repo::{messages::UnRegisterWebhook, RepoActor}; +use crate::{ + repo::{messages::UnRegisterWebhook, RepoActor}, + server::actor::messages::RepoUpdate, +}; impl Handler for RepoActor { type Result = (); fn handle(&mut self, _msg: UnRegisterWebhook, ctx: &mut Self::Context) -> Self::Result { if let Some(webhook_id) = self.webhook_id.take() { + self.update_tui(RepoUpdate::UnregisteringWebhook); let forge = self.forge.duplicate(); debug!("unregistering webhook"); async move { diff --git a/crates/cli/src/repo/handlers/validate_repo.rs b/crates/cli/src/repo/handlers/validate_repo.rs index a693140..dc24684 100644 --- a/crates/cli/src/repo/handlers/validate_repo.rs +++ b/crates/cli/src/repo/handlers/validate_repo.rs @@ -3,10 +3,13 @@ use actix::prelude::*; use tracing::{debug, instrument, Instrument as _}; -use crate::repo::{ - do_send, logger, - messages::{AdvanceNext, AdvanceNextPayload, CheckCIStatus, MessageToken, ValidateRepo}, - notify_user, RepoActor, +use crate::{ + repo::{ + do_send, logger, + messages::{AdvanceNext, AdvanceNextPayload, CheckCIStatus, MessageToken, ValidateRepo}, + notify_user, RepoActor, + }, + server::actor::messages::RepoUpdate, }; use git_next_core::git::validation::positions::{validate, Error, Positions}; @@ -41,14 +44,18 @@ impl Handler for RepoActor { format!("accepted token: {}", self.message_token), ); + self.update_tui(RepoUpdate::ValidateRepo); + // Repository positions let Some(ref open_repository) = self.open_repository else { logger(self.log.as_ref(), "no open repository"); + self.alert_tui("repo not open"); return; }; logger(self.log.as_ref(), "have open repository"); let Some(repo_config) = self.repo_details.repo_config.clone() else { logger(self.log.as_ref(), "no repo config"); + self.alert_tui("no repo config"); return; }; logger(self.log.as_ref(), "have repo config"); @@ -75,10 +82,11 @@ impl Handler for RepoActor { self.log.as_ref(), ); } else { - // do nothing + self.update_tui(RepoUpdate::Okay); } } Err(Error::Retryable(message)) => { + self.alert_tui(format!("[retryable: {message}]")); logger(self.log.as_ref(), message); let addr = ctx.address(); let message_token = self.message_token; @@ -95,12 +103,16 @@ impl Handler for RepoActor { .into_actor(self) .wait(ctx); } - Err(Error::UserIntervention(user_notification)) => notify_user( - self.notify_user_recipient.as_ref(), - user_notification, - self.log.as_ref(), - ), + Err(Error::UserIntervention(user_notification)) => { + self.alert_tui(format!("[USER INTERVENTION: {user_notification}]")); + notify_user( + self.notify_user_recipient.as_ref(), + user_notification, + self.log.as_ref(), + ); + } Err(Error::NonRetryable(message)) => { + self.alert_tui(format!("[Error: {message}]")); logger(self.log.as_ref(), message); } } diff --git a/crates/cli/src/repo/handlers/webhook_notification.rs b/crates/cli/src/repo/handlers/webhook_notification.rs index 930d61e..f5fb83d 100644 --- a/crates/cli/src/repo/handlers/webhook_notification.rs +++ b/crates/cli/src/repo/handlers/webhook_notification.rs @@ -3,10 +3,13 @@ use actix::prelude::*; use tracing::{info, instrument, warn}; -use crate::repo::{ - do_send, logger, - messages::{ValidateRepo, WebhookNotification}, - ActorLog, RepoActor, +use crate::{ + repo::{ + do_send, logger, + messages::{ValidateRepo, WebhookNotification}, + ActorLog, RepoActor, + }, + server::actor::messages::RepoUpdate, }; use git_next_core::{ @@ -52,6 +55,10 @@ impl Handler for RepoActor { return; } Some(Branch::Main) => { + self.update_tui(RepoUpdate::WebhookReceived { + branch: Branch::Main, + push: push.clone(), + }); if handle_push( push, &config.branches().main(), @@ -64,6 +71,10 @@ impl Handler for RepoActor { }; } Some(Branch::Next) => { + self.update_tui(RepoUpdate::WebhookReceived { + branch: Branch::Next, + push: push.clone(), + }); if handle_push( push, &config.branches().next(), @@ -76,6 +87,10 @@ impl Handler for RepoActor { }; } Some(Branch::Dev) => { + self.update_tui(RepoUpdate::WebhookReceived { + branch: Branch::Dev, + push: push.clone(), + }); if handle_push( push, &config.branches().dev(), @@ -135,7 +150,7 @@ fn handle_push( last_commit: &mut Option, log: Option<&ActorLog>, ) -> Result<(), ()> { - logger(log, "message is for dev branch"); + logger(log, format!("message is for {branch} branch")); let commit = Commit::from(push); if last_commit.as_ref() == Some(&commit) { logger(log, format!("not a new commit on {branch}")); diff --git a/crates/cli/src/repo/handlers/webhook_registered.rs b/crates/cli/src/repo/handlers/webhook_registered.rs index a3a5aae..dbb03ff 100644 --- a/crates/cli/src/repo/handlers/webhook_registered.rs +++ b/crates/cli/src/repo/handlers/webhook_registered.rs @@ -2,16 +2,20 @@ use actix::prelude::*; use tracing::instrument; -use crate::repo::{ - do_send, - messages::{ValidateRepo, WebhookRegistered}, - RepoActor, +use crate::{ + repo::{ + do_send, + messages::{ValidateRepo, WebhookRegistered}, + RepoActor, + }, + server::actor::messages::RepoUpdate, }; impl Handler for RepoActor { type Result = (); #[instrument(name = "RepoActor::WebhookRegistered", skip_all, fields(repo = %self.repo_details, webhook_id = %msg.webhook_id()))] fn handle(&mut self, msg: WebhookRegistered, ctx: &mut Self::Context) -> Self::Result { + self.update_tui(RepoUpdate::RegisteredWebhook); self.webhook_id.replace(msg.webhook_id().clone()); self.webhook_auth.replace(msg.webhook_auth().clone()); do_send( diff --git a/crates/cli/src/repo/mod.rs b/crates/cli/src/repo/mod.rs index 7a0c3bc..f680f08 100644 --- a/crates/cli/src/repo/mod.rs +++ b/crates/cli/src/repo/mod.rs @@ -1,7 +1,13 @@ // use actix::prelude::*; -use crate::alerts::messages::NotifyUser; +use crate::{ + alerts::messages::NotifyUser, + server::{ + actor::messages::{RepoUpdate, ServerUpdate}, + ServerActor, + }, +}; use derive_more::Deref; use kxio::network::Network; use std::time::Duration; @@ -11,6 +17,7 @@ use git_next_core::{ git::{ self, repository::{factory::RepositoryFactory, open::OpenRepositoryLike}, + validation::positions::get_commit_histories, UserNotification, }, server::ListenUrl, @@ -59,6 +66,7 @@ pub struct RepoActor { forge: Box, log: Option, notify_user_recipient: Option>, + server_addr: Option>, } impl RepoActor { #[allow(clippy::too_many_arguments)] @@ -71,6 +79,7 @@ impl RepoActor { repository_factory: Box, sleep_duration: std::time::Duration, notify_user_recipient: Option>, + server_addr: Option>, ) -> Self { let message_token = messages::MessageToken::default(); Self { @@ -90,6 +99,58 @@ impl RepoActor { sleep_duration, log: None, notify_user_recipient, + server_addr, + } + } + + fn update_tui_branches(&self) { + #[cfg(feature = "tui")] + { + use crate::server::actor::messages::RepoUpdate; + let Some(repo_config) = &self.repo_details.repo_config else { + return; + }; + let Some(open_repo) = self.open_repository.as_ref() else { + return; + }; + let branches = repo_config.branches().clone(); + let histories = get_commit_histories(&**open_repo, repo_config).ok(); + self.update_tui(RepoUpdate::Branches { + branches, + histories, + }); + } + } + + fn update_tui_log(&self, log: git::graph::Log) { + #[cfg(feature = "tui")] + { + self.update_tui(RepoUpdate::Log { log }); + } + } + + fn alert_tui(&self, alert: impl Into) { + #[cfg(feature = "tui")] + { + self.update_tui(RepoUpdate::Alert { + alert: alert.into(), + }); + } + } + + fn update_tui(&self, repo_update: RepoUpdate) { + #[cfg(feature = "tui")] + { + let Some(server_addr) = &self.server_addr else { + return; + }; + + let update = ServerUpdate::RepoUpdate { + forge_alias: self.repo_details.forge.forge_alias().clone(), + repo_alias: self.repo_details.repo_alias.clone(), + repo_update, + }; + server_addr.do_send(update); } } } diff --git a/crates/cli/src/repo/tests/branch/advance_next.rs b/crates/cli/src/repo/tests/branch/advance_next.rs index cddc886..9425700 100644 --- a/crates/cli/src/repo/tests/branch/advance_next.rs +++ b/crates/cli/src/repo/tests/branch/advance_next.rs @@ -1,6 +1,28 @@ // +use crate::repo::branch::find_next_commit_on_dev; + use super::*; +fn advance_next_sut( + next: &Commit, + main: &Commit, + dev_commit_history: &[Commit], + repo_details: RepoDetails, + repo_config: RepoConfig, + open_repository: &dyn OpenRepositoryLike, + message_token: MessageToken, +) -> branch::Result { + let (commit, force) = find_next_commit_on_dev(next, main, dev_commit_history); + branch::advance_next( + commit, + force, + repo_details, + repo_config, + open_repository, + message_token, + ) +} + mod when_at_dev { // next and dev branches are the same use super::*; @@ -16,7 +38,7 @@ mod when_at_dev { // no on_push defined - so any call to push will cause an error let message_token = given::a_message_token(); let_assert!( - Err(err) = branch::advance_next( + Err(err) = advance_next_sut( &next, main, dev_commit_history, @@ -51,7 +73,7 @@ mod can_advance { // no on_push defined - so any call to push will cause an error let message_token = given::a_message_token(); let_assert!( - Err(err) = branch::advance_next( + Err(err) = advance_next_sut( &next, main, dev_commit_history, @@ -82,7 +104,7 @@ mod can_advance { // no on_push defined - so any call to push will cause an error let message_token = given::a_message_token(); let_assert!( - Err(err) = branch::advance_next( + Err(err) = advance_next_sut( &next, main, dev_commit_history, @@ -122,7 +144,7 @@ mod can_advance { expect::push(&mut open_repository, Err(git::push::Error::Lock)); let message_token = given::a_message_token(); let_assert!( - Err(err) = branch::advance_next( + Err(err) = advance_next_sut( &next, main, dev_commit_history, @@ -154,7 +176,7 @@ mod can_advance { expect::push_ok(&mut open_repository); let message_token = given::a_message_token(); let_assert!( - Ok(mt) = branch::advance_next( + Ok(mt) = advance_next_sut( &next, main, dev_commit_history, diff --git a/crates/cli/src/repo/tests/given.rs b/crates/cli/src/repo/tests/given.rs index e8da64e..c0a6b91 100644 --- a/crates/cli/src/repo/tests/given.rs +++ b/crates/cli/src/repo/tests/given.rs @@ -195,6 +195,7 @@ pub fn a_repo_actor( repository_factory, std::time::Duration::from_nanos(1), None, + None, ) .with_log(actors_log), log, diff --git a/crates/cli/src/server/actor/handlers/mod.rs b/crates/cli/src/server/actor/handlers/mod.rs index 3633000..a5e6358 100644 --- a/crates/cli/src/server/actor/handlers/mod.rs +++ b/crates/cli/src/server/actor/handlers/mod.rs @@ -1,5 +1,6 @@ mod file_updated; mod receive_app_config; mod receive_valid_app_config; +mod server_update; mod shutdown; mod subscribe_updates; diff --git a/crates/cli/src/server/actor/handlers/receive_valid_app_config.rs b/crates/cli/src/server/actor/handlers/receive_valid_app_config.rs index 6380c67..1028cab 100644 --- a/crates/cli/src/server/actor/handlers/receive_valid_app_config.rs +++ b/crates/cli/src/server/actor/handlers/receive_valid_app_config.rs @@ -8,7 +8,7 @@ use crate::{ alerts::messages::UpdateShout, repo::{messages::CloneRepo, RepoActor}, server::actor::{ - messages::{ReceiveValidAppConfig, ValidAppConfig}, + messages::{ReceiveValidAppConfig, ServerUpdate, ValidAppConfig}, ServerActor, }, webhook::{ @@ -21,7 +21,7 @@ use crate::{ impl Handler for ServerActor { type Result = (); - fn handle(&mut self, msg: ReceiveValidAppConfig, _ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: ReceiveValidAppConfig, ctx: &mut Self::Context) -> Self::Result { let ValidAppConfig { app_config, socket_address, @@ -37,6 +37,7 @@ impl Handler for ServerActor { let webhook_router = WebhookRouterActor::default().start(); let listen_url = app_config.listen().url(); let notify_user_recipient = self.alerts.clone().recipient(); + let server_addr = Some(ctx.address()); // Forge Actors for (forge_alias, forge_config) in app_config.forges() { let repo_actors = self @@ -46,6 +47,7 @@ impl Handler for ServerActor { &server_storage, listen_url, ¬ify_user_recipient, + server_addr.clone(), ) .into_iter() .map(start_repo_actor) @@ -69,9 +71,18 @@ impl Handler for ServerActor { WebhookActor::new(socket_address, webhook_router.recipient()).start(); self.webhook_actor_addr.replace(webhook_actor_addr); let shout = app_config.shout().clone(); - self.app_config.replace(app_config); + self.app_config.replace(app_config.clone()); + self.do_send( + ServerUpdate::AppConfigLoaded { + app_config: ValidAppConfig { + app_config, + socket_address, + storage: server_storage, + }, + }, + ctx, + ); self.alerts.do_send(UpdateShout::new(shout)); - self.send_server_updates(); } } diff --git a/crates/cli/src/server/actor/handlers/server_update.rs b/crates/cli/src/server/actor/handlers/server_update.rs new file mode 100644 index 0000000..0d5624d --- /dev/null +++ b/crates/cli/src/server/actor/handlers/server_update.rs @@ -0,0 +1,14 @@ +use actix::Handler; + +// +use crate::server::{actor::messages::ServerUpdate, ServerActor}; + +impl Handler for ServerActor { + type Result = (); + + fn handle(&mut self, msg: ServerUpdate, _ctx: &mut Self::Context) -> Self::Result { + self.subscribers.iter().for_each(move |subscriber| { + subscriber.do_send(msg.clone()); + }); + } +} diff --git a/crates/cli/src/server/actor/messages.rs b/crates/cli/src/server/actor/messages.rs index d361890..c36837b 100644 --- a/crates/cli/src/server/actor/messages.rs +++ b/crates/cli/src/server/actor/messages.rs @@ -1,12 +1,13 @@ +// use actix::{Message, Recipient}; -//- use derive_more::Constructor; use git_next_core::{ - git::graph::Log, + git::{self, forge::commit::Status, graph::Log}, message, server::{AppConfig, Storage}, - ForgeAlias, RepoAlias, RepoBranches, + webhook::{push::Branch, Push}, + ForgeAlias, RepoAlias, RepoBranches, RepoConfig, }; use std::net::SocketAddr; @@ -40,20 +41,54 @@ message!(Shutdown, "Notification to shutdown the server actor"); #[derive(Clone, Debug, PartialEq, Eq, Message)] #[rtype(result = "()")] pub enum ServerUpdate { - /// Status of a repo - UpdateRepoSummary { + /// List of all configured forges and aliases + AppConfigLoaded { app_config: ValidAppConfig }, + + RepoUpdate { forge_alias: ForgeAlias, repo_alias: RepoAlias, + repo_update: RepoUpdate, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RepoUpdate { + Branches { branches: RepoBranches, + histories: Option, + }, + Log { log: Log, }, - /// remove a repo - RemoveRepo { - forge_alias: ForgeAlias, - repo_alias: RepoAlias, + ValidateRepo, + Okay, + Alert { + alert: String, }, - /// test message - Ping, + CheckingCI, + AdvancingNext { + commit: Option, + force: git::push::Force, + }, + AdvancingMain { + commit: git::Commit, + }, + Opening, + LoadingConfigFromRepo, + ReceiveCIStatus { + status: Status, + }, + ReceiveRepoConfig { + repo_config: RepoConfig, + }, + RegisteringWebhook, + UnregisteringWebhook, + WebhookReceived { + branch: Branch, + push: Push, + }, + RegisteredWebhook, + Opened, } message!( diff --git a/crates/cli/src/server/actor/mod.rs b/crates/cli/src/server/actor/mod.rs index d87ebe8..74eefab 100644 --- a/crates/cli/src/server/actor/mod.rs +++ b/crates/cli/src/server/actor/mod.rs @@ -118,6 +118,7 @@ impl ServerActor { server_storage: &Storage, listen_url: &ListenUrl, notify_user_recipient: &Recipient, + server_addr: Option>, ) -> Vec<(ForgeAlias, RepoAlias, RepoActor)> { let span = tracing::info_span!("create_forge_repos", name = %forge_name, config = %forge_config); @@ -125,8 +126,13 @@ impl ServerActor { let _guard = span.enter(); tracing::info!("Creating Forge"); let mut repos = vec![]; - let creator = - self.create_actor(forge_name, forge_config.clone(), server_storage, listen_url); + let creator = self.create_actor( + forge_name, + forge_config.clone(), + server_storage, + listen_url, + server_addr, + ); for (repo_alias, server_repo_config) in forge_config.repos() { let forge_repo = creator(( repo_alias, @@ -148,6 +154,7 @@ impl ServerActor { forge_config: ForgeConfig, server_storage: &Storage, listen_url: &ListenUrl, + server_addr: Option>, ) -> impl Fn( (RepoAlias, &ServerRepoConfig, Recipient), ) -> (ForgeAlias, RepoAlias, RepoActor) { @@ -194,6 +201,7 @@ impl ServerActor { repository_factory.duplicate(), sleep_duration, Some(notify_user_recipient), + server_addr.clone(), ); (forge_name.clone(), repo_alias, actor) } @@ -242,10 +250,4 @@ impl ServerActor { ctx.address().do_send(msg); } } - - fn send_server_updates(&self) { - self.subscribers.iter().for_each(|subscriber| { - subscriber.do_send(ServerUpdate::Ping); - }); - } } diff --git a/crates/cli/src/server/mod.rs b/crates/cli/src/server/mod.rs index 211304c..31ff89c 100644 --- a/crates/cli/src/server/mod.rs +++ b/crates/cli/src/server/mod.rs @@ -17,7 +17,7 @@ pub use actor::ServerActor; use git_next_core::git::RepositoryFactory; -use anyhow::{Context, Result}; +use color_eyre::{eyre::Context, Result}; use kxio::{fs::FileSystem, network::Network}; use tracing::info; @@ -48,7 +48,12 @@ pub fn start( repo: Box, sleep_duration: std::time::Duration, ) -> Result<()> { - if !ui { + if ui { + #[cfg(feature = "tui")] + { + crate::tui::logging::initialize_logging()?; + } + } else { init_logging(); } @@ -59,7 +64,6 @@ pub fn start( info!("Starting Server..."); let server = ServerActor::new(fs.clone(), net.clone(), alerts_addr, repo, sleep_duration).start(); - server.do_send(FileUpdated); info!("Starting File Watcher..."); #[allow(clippy::expect_used)] @@ -75,18 +79,18 @@ pub fn start( let (tx_shutdown, rx_shutdown) = channel::<()>(); let tui_addr = tui::Tui::new(tx_shutdown).start(); - // tui_addr.do_send(tui::Tick); - let _ = tui_addr.send(tui::Tick).await; server.do_send(SubscribeToUpdates::new(tui_addr.clone().recipient())); + server.do_send(FileUpdated); // update file after ui subscription in place loop { let _ = tui_addr.send(tui::Tick).await; if rx_shutdown.try_recv().is_ok() { break; } - // actix_rt::task::yield_now().await; + actix_rt::time::sleep(Duration::from_millis(16)).await; } } } else { + server.do_send(FileUpdated); info!("Server running - Press Ctrl-C to stop..."); let _ = signal::ctrl_c().await; info!("Ctrl-C received, shutting down..."); @@ -95,7 +99,7 @@ pub fn start( // shutdown fw_shutdown.store(true, Ordering::Relaxed); server.do_send(crate::server::actor::messages::Shutdown); - actix_rt::time::sleep(std::time::Duration::from_millis(200)).await; + actix_rt::time::sleep(std::time::Duration::from_millis(10)).await; System::current().stop(); }; diff --git a/crates/cli/src/tui/README.md b/crates/cli/src/tui/README.md new file mode 100644 index 0000000..bafa379 --- /dev/null +++ b/crates/cli/src/tui/README.md @@ -0,0 +1,121 @@ +# TUI Actor + +- Maintains it's own copy of data for holding state +- Is notified of update via actor messages from the Server and Repo Actors +- State is somewhat heirarchical + +## State + +```rust +enum TuiState { + Initial, + Configured { + app_config: ValidAppConfig, + forges: BTreeMap, + }, +} +enum RepoState { + Identified { repo_alias: RepoAlias }, + Configured { repo_alias: RepoAlias, branches: RepoBranches }, + Ready { repo_alias: RepoAlias, branches: RepoBranches, main: Commit, next: Commit, dev: Commit, log: Log }, + Alert { repo_alias: RepoAlias, message: String, branches: RepoBranches, main: Commit, next: Commit, dev: Commit, log: Log }, +} +``` + +## State Transitions: + +### `TuiState` + +```mermaid +stateDiagram-v2 + * --> Initial + Initial --> Configured +``` + +- `message!(Configure, ValidAppConfig, "Initialise UI with valid config");` + +### `RepoState` + +```mermaid +stateDiagram-v2 + * --> Identified + Identified --> Configured + Identified --> Ready + Configured --> Ready + Ready --> Alert + Configured --> Alert + Identified --> Alert + Alert --> Ready +``` + +- `Identified` - from AppConfig where a repo alias is listed, but repo config needs to be loaded from `.git-next.toml` +- `Configured` - as `Identified` but either branches are identified in server config, OR the `.git-next.toml` file has been loaded +- `Ready` - as `Configured` but the branch positions have been validated and do not require user intervention +- `Alert` - as `Ready` but user intervention is required + +## Widget + +Initial mock up of UI. Possibly add some borders and padding as it looks a little squached together. + +``` ++ gh +- jo + + test (main/next/dev) + - tasyn (main/next/dev) + * 12ab32f (dev) added feature X + * bce43b1 (next) added feature Y + * f43e379 (main) added feature Z + - git-next (main/next/dev) DEV NOT AHEAD OF MAIN - rebase onto main + * 239cefd (main/next) fix bug A + * b4c290a (dev) +``` + +Adding a border around open forges: + +``` ++ gh +- jo --------------------------------------------------------------------+ +| + test (main/next/dev) | +| - tasyn (main/next/dev) | +| * 12ab32f (dev) added feature X | +| * bce43b1 (next) added feature Y | +| * f43e379 (main) added feature Z | +| - git-next (main/next/dev) DEV NOT AHEAD OF MAIN - rebase onto main | +| * 239cefd (main/next) fix bug A | +| * b4c290a (dev) | ++------------------------------------------------------------------------+ +``` + +Adding a border around open forges and repos (I like this one the best): + +``` ++ gh +- jo --------------------------------------------------------------------+ +| + test (main/next/dev) | +| - tasyn (main/next/dev) ---------------------------------------------+ | +| | * 12ab32f (dev) added feature X | | +| | * bce43b1 (next) added feature Y | | +| | * f43e379 (main) added feature Z | | +| +--------------------------------------------------------------------+ | +| - git-next (main/next/dev) DEV NOT AHEAD OF MAIN - rebase onto main -+ | +| | * 239cefd (main/next) fix bug A | | +| | * b4c290a (dev) | | +| +--------------------------------------------------------------------+ | ++------------------------------------------------------------------------+ +``` + +## Logging + +- tui-logger to create an optional panel to show the normal server logs + +## Branch Graph + +- tui-nodes ? + +## Scrolling + +- tui-scrollview + +## Tree View + +- tui-tree-widget diff --git a/crates/cli/src/tui/actor/handlers/server_update.rs b/crates/cli/src/tui/actor/handlers/server_update.rs index d5c6bad..d5aa753 100644 --- a/crates/cli/src/tui/actor/handlers/server_update.rs +++ b/crates/cli/src/tui/actor/handlers/server_update.rs @@ -1,27 +1,90 @@ -use std::time::Instant; - +// use actix::Handler; -use crate::{server::actor::messages::ServerUpdate, tui::Tui}; +use crate::{ + server::actor::messages::{RepoUpdate, ServerUpdate}, + tui::{actor::ServerState, Tui}, +}; -// impl Handler for Tui { type Result = (); fn handle(&mut self, msg: ServerUpdate, _ctx: &mut Self::Context) -> Self::Result { + self.state.tap(); match msg { - ServerUpdate::UpdateRepoSummary { + ServerUpdate::AppConfigLoaded { app_config } => { + self.state.mode = ServerState::from(app_config); + } + + ServerUpdate::RepoUpdate { forge_alias, repo_alias, - branches, - log, - } => todo!(), - ServerUpdate::RemoveRepo { - forge_alias, - repo_alias, - } => todo!(), - ServerUpdate::Ping => { - self.last_ping = Instant::now(); + repo_update, + } => { + if let ServerState::Configured { forges } = &mut self.state.mode { + let Some(forge_state) = forges.get_mut(&forge_alias) else { + return; + }; + let Some(repo_state) = forge_state.repos.get_mut(&repo_alias) else { + return; + }; + match repo_update { + RepoUpdate::Branches { + branches, + histories, + } => { + repo_state.update_branches(branches, histories); + } + RepoUpdate::Log { log } => { + repo_state.update_log(log); + } + RepoUpdate::ValidateRepo => repo_state.update_message("polling..."), + RepoUpdate::Okay => { + repo_state.update_message("okay"); + repo_state.clear_alert(); + } + RepoUpdate::Alert { alert } => { + repo_state.alert(alert); + } + RepoUpdate::CheckingCI => { + repo_state.update_message("Checking CI status"); + } + RepoUpdate::AdvancingNext { + commit: _, + force: _, + } => (), + RepoUpdate::AdvancingMain { commit } => { + repo_state.update_message(format!("advancing main to {commit}")); + } + RepoUpdate::Opening => { + repo_state.update_message("opening..."); + } + RepoUpdate::Opened => { + repo_state.update_message("opened"); + } + RepoUpdate::LoadingConfigFromRepo => { + repo_state.update_message("loading config from repo..."); + } + RepoUpdate::ReceiveCIStatus { status } => { + repo_state.update_message(format!("ci status: {status:?}")); + } + RepoUpdate::ReceiveRepoConfig { repo_config: _ } => { + repo_state.update_message("loaded config from repo"); + } + RepoUpdate::RegisteringWebhook => { + repo_state.update_message("registering webhook..."); + } + RepoUpdate::UnregisteringWebhook => { + repo_state.update_message("unregistering webhook..."); + } + RepoUpdate::WebhookReceived { branch, push: _ } => { + repo_state.update_message(format!("webhook update: {branch:?}")); + } + RepoUpdate::RegisteredWebhook => { + repo_state.update_message("registered webhook"); + } + } + } } } } diff --git a/crates/cli/src/tui/actor/handlers/tick.rs b/crates/cli/src/tui/actor/handlers/tick.rs index 215bf1f..44ecb83 100644 --- a/crates/cli/src/tui/actor/handlers/tick.rs +++ b/crates/cli/src/tui/actor/handlers/tick.rs @@ -1,12 +1,5 @@ // -use std::{borrow::BorrowMut, time::Instant}; - -use actix::{ActorContext, Handler}; -use ratatui::{ - crossterm::event::{self, KeyCode, KeyEventKind}, - style::Stylize as _, - widgets::Paragraph, -}; +use actix::Handler; use crate::tui::actor::{messages::Tick, Tui}; @@ -14,44 +7,9 @@ impl Handler for Tui { type Result = std::io::Result<()>; fn handle(&mut self, _msg: Tick, ctx: &mut Self::Context) -> Self::Result { - if let Some(terminal) = self.terminal.borrow_mut() { - terminal.draw(|frame| { - let area = frame.area(); - frame.render_widget( - Paragraph::new(format!( - "(press 'q' to quit) Ping:[{:?}] UI:[{:?}]", - self.last_ping, - Instant::now() - )) - .white() - .on_blue(), - area, - ); - })?; - } else { - eprintln!("No terminal setup"); - } - if event::poll(std::time::Duration::from_millis(16))? { - if let event::Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press { - match key.code { - KeyCode::Char('q') => { - // execute!(stderr(), LeaveAlternateScreen)?; - // disable_raw_mode()?; - ctx.stop(); - if let Err(err) = self.signal_shutdown.send(()) { - tracing::error!(?err, "Failed to signal shutdown"); - } - } - KeyCode::Esc => { - // - } - _ => (), - } - } - } - } - + self.state.tap(); + self.draw()?; + self.handle_input(ctx)?; Ok(()) } } diff --git a/crates/cli/src/tui/actor/messages.rs b/crates/cli/src/tui/actor/messages.rs index 3af3fc1..b47cbc8 100644 --- a/crates/cli/src/tui/actor/messages.rs +++ b/crates/cli/src/tui/actor/messages.rs @@ -1,4 +1,4 @@ // use git_next_core::message; -message!(Tick => std::io::Result<()>, "Start the TUI"); +message!(Tick => std::io::Result<()>, "Update the TUI"); diff --git a/crates/cli/src/tui/actor/mod.rs b/crates/cli/src/tui/actor/mod.rs index b00b4a3..e1fd518 100644 --- a/crates/cli/src/tui/actor/mod.rs +++ b/crates/cli/src/tui/actor/mod.rs @@ -1,17 +1,20 @@ // mod handlers; pub mod messages; +mod model; use std::{ io::{stderr, Stderr}, sync::mpsc::Sender, - time::Instant, }; -use actix::{Actor, Context}; +use actix::{Actor, ActorContext as _, Context}; + +pub use model::*; use ratatui::{ crossterm::{ + event::{self, KeyCode, KeyEventKind}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }, @@ -23,7 +26,7 @@ use ratatui::{ pub struct Tui { terminal: Option>>, signal_shutdown: Sender<()>, - last_ping: Instant, + pub state: State, } impl Actor for Tui { type Context = Context; @@ -58,9 +61,48 @@ impl Tui { Self { terminal: None, signal_shutdown, - last_ping: Instant::now(), + state: State::initial(), } } + + pub const fn state(&self) -> &State { + &self.state + } + + fn draw(&mut self) -> std::io::Result<()> { + let t = self.terminal.take(); + if let Some(mut terminal) = t { + terminal.draw(|frame| { + frame.render_widget(self.state(), frame.area()); + })?; + self.terminal = Some(terminal); + } else { + eprintln!("No terminal setup"); + } + Ok(()) + } + + fn handle_input(&self, ctx: &mut ::Context) -> std::io::Result<()> { + if event::poll(std::time::Duration::from_millis(16))? { + if let event::Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Char('q') => { + ctx.stop(); + if let Err(err) = self.signal_shutdown.send(()) { + tracing::error!(?err, "Failed to signal shutdown"); + } + } + KeyCode::Esc => { + // + } + _ => (), + } + } + } + } + Ok(()) + } } fn init() -> std::io::Result>> { diff --git a/crates/cli/src/tui/actor/model.rs b/crates/cli/src/tui/actor/model.rs new file mode 100644 index 0000000..0d827b7 --- /dev/null +++ b/crates/cli/src/tui/actor/model.rs @@ -0,0 +1,315 @@ +// +use ratatui::{ + layout::Alignment, + prelude::{Buffer, Rect}, + style::Stylize as _, + symbols::border, + text::Line, + widgets::{block::Title, Block, Paragraph, Widget}, +}; + +use git_next_core::{ + git::{self, graph::Log, Commit}, + ForgeAlias, RepoAlias, RepoBranches, +}; + +use std::{collections::BTreeMap, fmt::Display, time::Instant}; + +use crate::{server::actor::messages::ValidAppConfig, tui::components::ConfiguredAppWidget}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct State { + last_update: Instant, + started: Instant, + pub mode: ServerState, +} +impl State { + pub fn initial() -> Self { + Self { + last_update: Instant::now(), + started: Instant::now(), + mode: ServerState::Initial { tick: 0 }, + } + } + + pub fn tap(&mut self) { + self.last_update = Instant::now(); + if let ServerState::Initial { tick } = &mut self.mode { + *tick += 1; + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ServerState { + /// UI has started but has no information on the state of the server + Initial { tick: usize }, // NOTE: for use with throbber-widgets-tui ? + + /// The application configuration has been loaded, individual forges and repos have their own + /// states + Configured { + forges: BTreeMap, + }, +} +impl ServerState { + pub fn update_branches( + &mut self, + forge_alias: &ForgeAlias, + repo_alias: &RepoAlias, + branches: RepoBranches, + ) { + if let Self::Configured { forges } = self { + let Some(forge_state) = forges.get_mut(forge_alias) else { + return; + }; + let Some(repo_state) = forge_state.repos.get_mut(repo_alias) else { + return; + }; + match repo_state { + RepoState::Configured { + branches: state_branches, + .. + } + | RepoState::Ready { + branches: state_branches, + .. + } => *state_branches = branches, + + RepoState::Identified { .. } => (), + } + } + } + + pub fn update_log(&mut self, forge_alias: &ForgeAlias, repo_alias: &RepoAlias, log: Log) { + if let Self::Configured { forges } = self { + let Some(forge_state) = forges.get_mut(forge_alias) else { + return; + }; + let Some(repo_state) = forge_state.repos.get_mut(repo_alias) else { + return; + }; + match repo_state { + RepoState::Ready { log: state_log, .. } => *state_log = log, + + RepoState::Identified { .. } | RepoState::Configured { .. } => (), + } + } + } +} +impl From for ServerState { + fn from(app_config: ValidAppConfig) -> Self { + Self::Configured { + forges: app_config + .app_config + .forges() + .map(|(forge_alias, config)| { + ( + forge_alias, + config + .repos() + .map(|(repo_alias, server_repo_config)| { + (repo_alias, server_repo_config.repo_config()) + }) + .map( + |(repo_alias, option_repo_config)| match option_repo_config { + Some(rc) => ( + repo_alias.clone(), + RepoState::Configured { + repo_alias, + message: "configured".into(), + messages: Vec::new(), + alert: None, + branches: rc.branches().clone(), + histories: None, + }, + ), + None => ( + repo_alias.clone(), + RepoState::Identified { + repo_alias, + message: "identified".into(), + messages: Vec::new(), + alert: None, + }, + ), + }, + ) + .collect::>(), + ) + }) + .map(|(forge_alias, vec_repo_alias_state)| { + let forge_state: ForgeState = ForgeState { + alias: forge_alias.clone(), + view_state: ViewState::default(), + repos: vec_repo_alias_state.into_iter().collect::>(), + }; + (forge_alias, forge_state) + }) + .collect::>(), + } + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum ViewState { + Collapsed, + #[default] + Expanded, +} +impl Display for ViewState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let view_state = match self { + Self::Collapsed => "+", + Self::Expanded => "-", + }; + write!(f, "{view_state}") + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ForgeState { + pub alias: ForgeAlias, + pub view_state: ViewState, + pub repos: BTreeMap, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RepoState { + Identified { + repo_alias: RepoAlias, + message: String, + messages: Vec, + alert: Option, + }, + Configured { + repo_alias: RepoAlias, + message: String, + messages: Vec, + alert: Option, + branches: RepoBranches, + histories: Option, + }, + Ready { + repo_alias: RepoAlias, + message: String, + messages: Vec, + alert: Option, + branches: RepoBranches, + histories: Option, + view_state: ViewState, + main: Commit, + next: Commit, + dev: Commit, + log: Log, + }, +} +impl RepoState { + #[tracing::instrument] + pub fn update_branches( + &mut self, + branches: RepoBranches, + histories: Option, + ) { + match self { + Self::Configured { + branches: state_branches, + histories: state_histories, + .. + } + | Self::Ready { + branches: state_branches, + histories: state_histories, + .. + } => { + *state_branches = branches; + *state_histories = histories; + } + + Self::Identified { .. } => (), + } + } + + #[tracing::instrument] + pub fn update_log(&mut self, log: Log) { + match self { + Self::Ready { log: state_log, .. } => { + *state_log = log; + } + + Self::Identified { .. } | Self::Configured { .. } => (), + } + } + + #[tracing::instrument] + pub fn update_message(&mut self, msg: impl Into + std::fmt::Debug) { + tracing::info!("new tui message"); + let msg: String = msg.into(); + match self { + Self::Identified { + message, messages, .. + } + | Self::Configured { + message, messages, .. + } + | Self::Ready { + message, messages, .. + } => { + messages.push(message.clone()); + *message = msg; + } + } + } + + #[tracing::instrument] + pub fn clear_alert(&mut self) { + match self { + Self::Identified { .. } | Self::Configured { .. } => (), + Self::Ready { alert, .. } => *alert = None, + } + } + + #[tracing::instrument] + pub fn alert(&mut self, msg: impl Into + std::fmt::Debug) { + let msg: String = msg.into(); + tracing::info!(%msg, "new tui alert"); + self.update_message("ALERT"); + match self { + Self::Identified { alert, .. } + | Self::Configured { alert, .. } + | Self::Ready { alert, .. } => *alert = Some(msg), + } + } +} + +impl Widget for &State { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let block = Block::bordered() + .title(Title::from(" Git-Next ".bold()).alignment(Alignment::Center)) + .title( + Title::from(Line::from(vec![ + " [q]uit ".into(), + format!( + "{}s ", + self.last_update.duration_since(self.started).as_secs() + ) + .into(), + ])) + .alignment(Alignment::Center) + .position(ratatui::widgets::block::Position::Bottom), + ) + .border_set(border::THICK); + let interior = block.inner(area); + block.render(area, buf); + match &self.mode { + ServerState::Initial { tick } => Paragraph::new(format!("Loading...{tick}")) + .centered() + .render(interior, buf), + ServerState::Configured { forges } => { + ConfiguredAppWidget { forges }.render(interior, buf); + } + } + } +} diff --git a/crates/cli/src/tui/components/configured_app.rs b/crates/cli/src/tui/components/configured_app.rs new file mode 100644 index 0000000..cc9ad04 --- /dev/null +++ b/crates/cli/src/tui/components/configured_app.rs @@ -0,0 +1,52 @@ +use std::collections::BTreeMap; + +use git_next_core::ForgeAlias; +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + text::Line, + widgets::Widget, +}; + +use crate::tui::actor::{ForgeState, ViewState}; + +use super::forge::ForgeWidget; + +pub struct ConfiguredAppWidget<'a> { + pub forges: &'a BTreeMap, +} +impl<'a> Widget for ConfiguredAppWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let layout_app = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Length(1), Constraint::Fill(1)]) + .split(area); + + Line::from(format!("Forges: ({})", self.forges.keys().len())).render(layout_app[0], buf); + + let layout_forge_list = Layout::default() + .direction(Direction::Vertical) + .constraints( + self.forges + .iter() + .map(|(_alias, state)| match state.view_state { + ViewState::Collapsed => Constraint::Length(1), + ViewState::Expanded => Constraint::Fill(1), + }), + ) + .split(layout_app[1]); + + self.forges + .iter() + .map(|(forge_alias, state)| ForgeWidget { + forge_alias, + repos: &state.repos, + view_state: state.view_state, + }) + .enumerate() + .for_each(|(i, w)| w.render(layout_forge_list[i], buf)); + } +} diff --git a/crates/cli/src/tui/components/forge/collapsed.rs b/crates/cli/src/tui/components/forge/collapsed.rs new file mode 100644 index 0000000..72761e5 --- /dev/null +++ b/crates/cli/src/tui/components/forge/collapsed.rs @@ -0,0 +1,15 @@ +// +use git_next_core::ForgeAlias; +use ratatui::{buffer::Buffer, layout::Rect, text::Text, widgets::Widget}; + +pub struct CollapsedForgeWidget<'a> { + pub forge_alias: &'a ForgeAlias, +} +impl<'a> Widget for CollapsedForgeWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + Text::from(format!("- {}", self.forge_alias)).render(area, buf); + } +} diff --git a/crates/cli/src/tui/components/forge/expanded.rs b/crates/cli/src/tui/components/forge/expanded.rs new file mode 100644 index 0000000..f23fd90 --- /dev/null +++ b/crates/cli/src/tui/components/forge/expanded.rs @@ -0,0 +1,46 @@ +// +use std::collections::BTreeMap; + +use git_next_core::{ForgeAlias, RepoAlias}; +use ratatui::{ + buffer::Buffer, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + widgets::{block::Title, Block, Borders, Widget}, +}; + +use crate::tui::{actor::RepoState, components::repo::RepoWidget}; + +pub struct ExpandedForgeWidget<'a> { + pub forge_alias: &'a ForgeAlias, + pub repos: &'a BTreeMap, +} +impl<'a> Widget for ExpandedForgeWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let block = Block::default() + .title(Title::from(self.forge_alias.to_string()).alignment(Alignment::Left)) + .borders(Borders::ALL); + let layout_forge = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Length(1), Constraint::Fill(1)]) + .split(block.inner(area)); + block.render(area, buf); + + let layout_repo_list = Layout::default() + .direction(Direction::Vertical) + .constraints( + self.repos + .iter() + .map(|(_alias, _state)| Constraint::Fill(1)), + ) + .split(layout_forge[1]); + + self.repos + .values() + .map(|repo_state| RepoWidget { repo_state }) + .enumerate() + .for_each(|(i, w)| w.render(layout_repo_list[i], buf)); + } +} diff --git a/crates/cli/src/tui/components/forge/mod.rs b/crates/cli/src/tui/components/forge/mod.rs new file mode 100644 index 0000000..3851d78 --- /dev/null +++ b/crates/cli/src/tui/components/forge/mod.rs @@ -0,0 +1,34 @@ +// +mod collapsed; +mod expanded; + +use std::collections::BTreeMap; + +use git_next_core::{ForgeAlias, RepoAlias}; +use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; + +use crate::tui::actor::{RepoState, ViewState}; + +pub struct ForgeWidget<'a> { + pub forge_alias: &'a ForgeAlias, + pub repos: &'a BTreeMap, + pub view_state: ViewState, +} +impl<'a> Widget for ForgeWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + match self.view_state { + ViewState::Collapsed => collapsed::CollapsedForgeWidget { + forge_alias: self.forge_alias, + } + .render(area, buf), + ViewState::Expanded => expanded::ExpandedForgeWidget { + forge_alias: self.forge_alias, + repos: self.repos, + } + .render(area, buf), + } + } +} diff --git a/crates/cli/src/tui/components/history.rs b/crates/cli/src/tui/components/history.rs new file mode 100644 index 0000000..097fd1e --- /dev/null +++ b/crates/cli/src/tui/components/history.rs @@ -0,0 +1,17 @@ +use ratatui::{ + text::Line, + widgets::{Paragraph, Widget}, +}; + +// +pub struct CommitLog<'a> { + pub histories: Option<&'a crate::git::commit::Histories>, +} +impl<'a> Widget for CommitLog<'a> { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) + where + Self: Sized, + { + Paragraph::new(Line::from(vec!["todo".into()])).render(area, buf); + } +} diff --git a/crates/cli/src/tui/components/mod.rs b/crates/cli/src/tui/components/mod.rs new file mode 100644 index 0000000..434dd16 --- /dev/null +++ b/crates/cli/src/tui/components/mod.rs @@ -0,0 +1,7 @@ +mod configured_app; +mod forge; +mod history; +mod repo; + +pub use configured_app::ConfiguredAppWidget; +pub use history::CommitLog; diff --git a/crates/cli/src/tui/components/repo/configured.rs b/crates/cli/src/tui/components/repo/configured.rs new file mode 100644 index 0000000..a76188b --- /dev/null +++ b/crates/cli/src/tui/components/repo/configured.rs @@ -0,0 +1,49 @@ +// +use git_next_core::{RepoAlias, RepoBranches}; + +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + text::{Line, Text}, + widgets::Widget, +}; + +pub struct ConfiguredRepoWidget<'a> { + pub repo_alias: &'a RepoAlias, + pub message: &'a str, + pub messages: &'a Vec, + pub alert: Option<&'a String>, + pub branches: &'a RepoBranches, +} +impl<'a> Widget for ConfiguredRepoWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let repo_alias = &self.repo_alias; + let main = self.branches.main(); + let next = self.branches.next(); + let dev = self.branches.dev(); + let message = self.message; + let messages = self.messages; + let alert = self + .alert + .map_or_else(String::new, |alert| format!("[alert: {alert}]")); + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Length(1), Constraint::Fill(1)]) + .split(area); + Text::from(format!( + "c- {repo_alias} {alert} ({main}/{next}/{dev}) [{message}]" + )) + .render(layout[0], buf); + + Text::from( + messages + .iter() + .map(|m| Line::from(format!("- {m}"))) + .collect::>(), + ) + .render(layout[1], buf); + } +} diff --git a/crates/cli/src/tui/components/repo/identified.rs b/crates/cli/src/tui/components/repo/identified.rs new file mode 100644 index 0000000..4864c9f --- /dev/null +++ b/crates/cli/src/tui/components/repo/identified.rs @@ -0,0 +1,33 @@ +// +use git_next_core::RepoAlias; + +use ratatui::{ + buffer::Buffer, + layout::Rect, + text::{Line, Text}, + widgets::{Paragraph, Widget}, +}; + +pub struct IdentifiedRepoWidget<'a> { + pub repo_alias: &'a RepoAlias, + pub message: &'a str, + pub messages: &'a Vec, + pub alert: Option<&'a String>, +} +impl<'a> Widget for IdentifiedRepoWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let repo_alias = self.repo_alias; + let message = self.message; + let alert = self + .alert + .map_or_else(String::new, |alert| format!("[alert: {alert}]")); + Paragraph::new(Text::from(vec![ + Line::from(vec![format!("i- {repo_alias} (loading...) {alert} [{message}]").into()]), + Line::from(self.messages.iter().map(Into::into).collect::>()) + ])) + .render(area, buf); + } +} diff --git a/crates/cli/src/tui/components/repo/mod.rs b/crates/cli/src/tui/components/repo/mod.rs new file mode 100644 index 0000000..1f816c3 --- /dev/null +++ b/crates/cli/src/tui/components/repo/mod.rs @@ -0,0 +1,80 @@ +// +mod configured; +mod identified; +mod ready; + +use configured::ConfiguredRepoWidget; +use identified::IdentifiedRepoWidget; +use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; +use ready::ReadyRepoWidget; + +use crate::tui::actor::RepoState; + +pub struct RepoWidget<'a> { + pub repo_state: &'a RepoState, +} +impl<'a> Widget for RepoWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + match self.repo_state { + RepoState::Identified { + repo_alias, + message, + messages, + alert, + } => { + IdentifiedRepoWidget { + repo_alias, + message, + messages, + alert: alert.as_ref(), + } + .render(area, buf); + } + + RepoState::Configured { + repo_alias, + message, + messages, + alert, + branches, + .. + } => ConfiguredRepoWidget { + repo_alias, + message, + messages, + alert: alert.as_ref(), + branches, + } + .render(area, buf), + + RepoState::Ready { + repo_alias, + message, + messages, + alert, + branches, + histories, + view_state, + main, + next, + dev, + .. + } => ReadyRepoWidget { + repo_alias, + message, + messages, + alert: alert.as_ref(), + branches, + histories: histories.as_ref(), + view_state, + main, + next, + dev, + } + .render(area, buf), + }; + } +} diff --git a/crates/cli/src/tui/components/repo/ready.rs b/crates/cli/src/tui/components/repo/ready.rs new file mode 100644 index 0000000..26548a5 --- /dev/null +++ b/crates/cli/src/tui/components/repo/ready.rs @@ -0,0 +1,82 @@ +// +use git_next_core::{git::Commit, RepoAlias, RepoBranches}; + +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + text::{Line, Text}, + widgets::{Paragraph, Widget}, +}; +use warp::filters::path::param; + +use crate::{ + git, + tui::{actor::ViewState, components::CommitLog}, +}; + +pub struct ReadyRepoWidget<'a> { + pub repo_alias: &'a RepoAlias, + pub message: &'a str, + pub messages: &'a Vec, + pub alert: Option<&'a String>, + pub branches: &'a RepoBranches, + pub histories: Option<&'a git::commit::Histories>, + pub view_state: &'a ViewState, + pub main: &'a Commit, + pub next: &'a Commit, + pub dev: &'a Commit, +} +impl<'a> Widget for ReadyRepoWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Length(1), + Constraint::Fill(1), + Constraint::Fill(1), + ]) + .split(area); + let alert = self + .alert + .map_or_else(String::new, |alert| format!("[alert: {alert}]")); + Paragraph::new(Text::from(vec![ + Line::from(vec![ + "r".into(), + self.view_state.to_string().into(), + " ".into(), + self.repo_alias.to_string().into(), + alert.into(), + " (".into(), + self.branches.main().to_string().into(), + "/".into(), + self.branches.next().to_string().into(), + "/".into(), + self.branches.dev().to_string().into(), + ") [".into(), + self.message.into(), + "]".into(), + ]), + Line::from(vec![ + self.main.to_string().into(), + " , ".into(), + self.next.to_string().into(), + " , ".into(), + self.dev.to_string().into(), + "}".into(), + ]), + ])) + .render(layout[0], buf); + + Paragraph::new(Text::from(vec![Line::from( + self.messages.iter().map(Into::into).collect::>(), + )])) + .render(layout[1], buf); + CommitLog { + histories: self.histories, + } + .render(layout[2], buf); + } +} diff --git a/crates/cli/src/tui/logging.rs b/crates/cli/src/tui/logging.rs new file mode 100644 index 0000000..954a501 --- /dev/null +++ b/crates/cli/src/tui/logging.rs @@ -0,0 +1,86 @@ +// +use std::path::PathBuf; + +use color_eyre::eyre::Result; +use directories::ProjectDirs; +use lazy_static::lazy_static; +use tracing_error::ErrorLayer; +use tracing_subscriber::{self, layer::SubscriberExt, util::SubscriberInitExt, Layer}; + +lazy_static! { + pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase(); + pub static ref DATA_FOLDER: Option = + std::env::var(format!("{}_DATA", PROJECT_NAME.clone())) + .ok() + .map(PathBuf::from); + pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone()); + pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); +} + +fn project_directory() -> Option { + ProjectDirs::from("net", "kemitix", env!("CARGO_PKG_NAME")) +} + +pub fn get_data_dir() -> PathBuf { + let directory = DATA_FOLDER.clone().map_or_else( + || { + project_directory().map_or_else( + || PathBuf::from(".").join(".data"), + |proj_dirs| proj_dirs.data_local_dir().to_path_buf(), + ) + }, + |data_folder| data_folder, + ); + directory +} + +pub fn initialize_logging() -> Result<()> { + let directory = get_data_dir(); + std::fs::create_dir_all(directory.clone())?; + let log_path = directory.join(LOG_FILE.clone()); + let log_file = std::fs::File::create(log_path)?; + std::env::set_var( + "RUST_LOG", + std::env::var("RUST_LOG") + .or_else(|_| std::env::var(LOG_ENV.clone())) + .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), + ); + let file_subscriber = tracing_subscriber::fmt::layer() + .with_file(true) + .with_line_number(true) + .with_writer(log_file) + .with_target(false) + .with_ansi(false) + .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); + tracing_subscriber::registry() + .with(file_subscriber) + .with(ErrorLayer::default()) + .init(); + Ok(()) +} + +/// Similar to the `std::dbg!` macro, but generates `tracing` events rather +/// than printing to stdout. +/// +/// By default, the verbosity level for the generated events is `DEBUG`, but +/// this can be customized. +#[macro_export] +macro_rules! trace_dbg { + (target: $target:expr, level: $level:expr, $ex:expr) => {{ + match $ex { + value => { + tracing::event!(target: $target, $level, ?value, stringify!($ex)); + value + } + } + }}; + (level: $level:expr, $ex:expr) => { + trace_dbg!(target: module_path!(), level: $level, $ex) + }; + (target: $target:expr, $ex:expr) => { + trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex) + }; + ($ex:expr) => { + trace_dbg!(level: tracing::Level::DEBUG, $ex) + }; +} diff --git a/crates/cli/src/tui/mod.rs b/crates/cli/src/tui/mod.rs index 508e8de..b77d3ad 100644 --- a/crates/cli/src/tui/mod.rs +++ b/crates/cli/src/tui/mod.rs @@ -1,5 +1,7 @@ // mod actor; +pub mod components; +pub mod logging; pub use actor::messages::Tick; pub use actor::Tui; diff --git a/crates/core/src/config/server_repo_config.rs b/crates/core/src/config/server_repo_config.rs index 44f4456..83013eb 100644 --- a/crates/core/src/config/server_repo_config.rs +++ b/crates/core/src/config/server_repo_config.rs @@ -47,7 +47,8 @@ impl ServerRepoConfig { } /// Returns a `RepoConfig` from the server configuration if ALL THREE branches were provided - pub(crate) fn repo_config(&self) -> Option { + #[must_use] + pub fn repo_config(&self) -> Option { match (&self.main, &self.next, &self.dev) { (Some(main), Some(next), Some(dev)) => Some(RepoConfig::new( RepoBranches::new(main.to_string(), next.to_string(), dev.to_string()), diff --git a/crates/core/src/config/webhook/push.rs b/crates/core/src/config/webhook/push.rs index 76c515f..6fd051d 100644 --- a/crates/core/src/config/webhook/push.rs +++ b/crates/core/src/config/webhook/push.rs @@ -2,7 +2,7 @@ use crate::config::{BranchName, RepoBranches}; use derive_more::Constructor; -#[derive(Debug, Constructor, derive_with::With)] +#[derive(Clone, Debug, Constructor, PartialEq, Eq, derive_with::With)] pub struct Push { branch: BranchName, sha: String, @@ -34,7 +34,7 @@ impl Push { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Branch { Main, Next, diff --git a/crates/core/src/git/commit.rs b/crates/core/src/git/commit.rs index 99b821d..792100f 100644 --- a/crates/core/src/git/commit.rs +++ b/crates/core/src/git/commit.rs @@ -62,7 +62,7 @@ newtype!( "The commit message for a git commit." ); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Histories { pub main: Vec, pub next: Vec, diff --git a/crates/core/src/git/graph.rs b/crates/core/src/git/graph.rs index 718c0da..5c6c5f7 100644 --- a/crates/core/src/git/graph.rs +++ b/crates/core/src/git/graph.rs @@ -1,5 +1,4 @@ // - use std::borrow::ToOwned; use take_until::TakeUntilExt; diff --git a/crates/core/src/git/push.rs b/crates/core/src/git/push.rs index a791923..25be1b4 100644 --- a/crates/core/src/git/push.rs +++ b/crates/core/src/git/push.rs @@ -1,7 +1,7 @@ // use crate::{git, git::repository::open::OpenRepositoryLike, BranchName}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Force { No, From(git::GitRef), diff --git a/crates/core/src/git/repository/open/mod.rs b/crates/core/src/git/repository/open/mod.rs index 1d8d3ed..74f25b0 100644 --- a/crates/core/src/git/repository/open/mod.rs +++ b/crates/core/src/git/repository/open/mod.rs @@ -94,7 +94,8 @@ pub trait OpenRepositoryLike: std::fmt::Debug + Sync { /// /// # Errors /// - /// Will return `Err` if there are any network connectivity issues with the remote server. + /// Will return `Err` if there are any problems with the branch name being invalid, or any + /// corruption of the git repository. fn commit_log( &self, branch_name: &BranchName, diff --git a/crates/core/src/git/user_notification.rs b/crates/core/src/git/user_notification.rs index 00692f3..828aea3 100644 --- a/crates/core/src/git/user_notification.rs +++ b/crates/core/src/git/user_notification.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + // use crate::{git::Commit, BranchName, ForgeAlias, RepoAlias}; use serde_json::json; @@ -115,3 +117,18 @@ impl UserNotification { } } } +impl Display for UserNotification { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let message = match self { + Self::CICheckFailed { commit, .. } => format!("CI Check Failed [{commit}]"), + Self::RepoConfigLoadFailure { reason, .. } => { + format!("Failed to load repo config: {reason}") + } + Self::WebhookRegistration { reason, .. } => { + format!("Failed to register webhook: {reason}") + } + Self::DevNotBasedOnMain { .. } => "Dev not based on main".to_string(), + }; + write!(f, "{message}") + } +} diff --git a/crates/core/src/git/validation/positions.rs b/crates/core/src/git/validation/positions.rs index 18d81c0..9cfde88 100644 --- a/crates/core/src/git/validation/positions.rs +++ b/crates/core/src/git/validation/positions.rs @@ -129,7 +129,13 @@ fn is_based_on(commits: &[git::commit::Commit], needle: &git::Commit) -> bool { commits.iter().any(|commit| commit == needle) } -fn get_commit_histories( +/// Returns the commit logs for the main, next and dev branches +/// +/// # Errors +/// +/// Will return `Err` if there are any problems with the branch names being invalid, or any +/// corruption of the git repository. +pub fn get_commit_histories( open_repository: &dyn OpenRepositoryLike, repo_config: &RepoConfig, ) -> git::commit::log::Result {