diff options
Diffstat (limited to 'src/bin/ngit/sub_commands/init.rs')
| -rw-r--r-- | src/bin/ngit/sub_commands/init.rs | 321 |
1 files changed, 175 insertions, 146 deletions
diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs index 1b577ed..75306d1 100644 --- a/src/bin/ngit/sub_commands/init.rs +++ b/src/bin/ngit/sub_commands/init.rs | |||
| @@ -274,8 +274,7 @@ fn apply_grasp_infrastructure( | |||
| 274 | public_key: &PublicKey, | 274 | public_key: &PublicKey, |
| 275 | identifier: &str, | 275 | identifier: &str, |
| 276 | ) -> Result<()> { | 276 | ) -> Result<()> { |
| 277 | let mut grasp_relay_insert_idx = 0; | 277 | for (grasp_relay_insert_idx, grasp_server) in grasp_servers.iter().enumerate() { |
| 278 | for grasp_server in grasp_servers { | ||
| 279 | // Always add grasp-derived clone URL | 278 | // Always add grasp-derived clone URL |
| 280 | let clone_url = format_grasp_server_url_as_clone_url(grasp_server, public_key, identifier)?; | 279 | let clone_url = format_grasp_server_url_as_clone_url(grasp_server, public_key, identifier)?; |
| 281 | 280 | ||
| @@ -312,7 +311,6 @@ fn apply_grasp_infrastructure( | |||
| 312 | if !relays.contains(&relay_url) { | 311 | if !relays.contains(&relay_url) { |
| 313 | relays.insert(grasp_relay_insert_idx, relay_url); | 312 | relays.insert(grasp_relay_insert_idx, relay_url); |
| 314 | } | 313 | } |
| 315 | grasp_relay_insert_idx += 1; | ||
| 316 | } | 314 | } |
| 317 | Ok(()) | 315 | Ok(()) |
| 318 | } | 316 | } |
| @@ -1713,15 +1711,90 @@ struct DeferredServerFinish { | |||
| 1713 | message: String, | 1711 | message: String, |
| 1714 | } | 1712 | } |
| 1715 | 1713 | ||
| 1716 | /// Coordinates the delayed reveal of per-server detail bars. | ||
| 1717 | /// Bars that finish before the expand timer fires store their final | ||
| 1718 | /// style+message here. The timer applies them all at reveal time so | ||
| 1719 | /// every bar — completed or still waiting — appears in the expanded view. | ||
| 1720 | struct ServerRevealState { | 1714 | struct ServerRevealState { |
| 1721 | revealed: AtomicBool, | 1715 | revealed: AtomicBool, |
| 1722 | deferred: Mutex<Vec<DeferredServerFinish>>, | 1716 | deferred: Mutex<Vec<DeferredServerFinish>>, |
| 1723 | } | 1717 | } |
| 1724 | 1718 | ||
| 1719 | struct PollContext { | ||
| 1720 | timeout_secs: u64, | ||
| 1721 | total: u64, | ||
| 1722 | ready_count: Arc<AtomicU64>, | ||
| 1723 | spinner_pb: ProgressBar, | ||
| 1724 | reveal_state: Arc<ServerRevealState>, | ||
| 1725 | } | ||
| 1726 | |||
| 1727 | fn create_server_bars( | ||
| 1728 | clone_urls: &[String], | ||
| 1729 | detail_multi: &MultiProgress, | ||
| 1730 | ) -> Vec<ProgressBar> { | ||
| 1731 | let waiting_style = ProgressStyle::with_template(" {spinner} {msg}") | ||
| 1732 | .unwrap() | ||
| 1733 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"); | ||
| 1734 | clone_urls | ||
| 1735 | .iter() | ||
| 1736 | .map(|url| { | ||
| 1737 | let name = url | ||
| 1738 | .trim_start_matches("https://") | ||
| 1739 | .trim_start_matches("http://") | ||
| 1740 | .to_string(); | ||
| 1741 | detail_multi.add( | ||
| 1742 | ProgressBar::new_spinner() | ||
| 1743 | .with_style(waiting_style.clone()) | ||
| 1744 | .with_message( | ||
| 1745 | console::style(format!("{name} - waiting")) | ||
| 1746 | .for_stderr() | ||
| 1747 | .dim() | ||
| 1748 | .to_string(), | ||
| 1749 | ), | ||
| 1750 | ) | ||
| 1751 | }) | ||
| 1752 | .collect() | ||
| 1753 | } | ||
| 1754 | |||
| 1755 | fn spawn_expand_timer( | ||
| 1756 | expand_delay_ms: u64, | ||
| 1757 | spinner_pb: ProgressBar, | ||
| 1758 | detail_multi: MultiProgress, | ||
| 1759 | heading_bar: ProgressBar, | ||
| 1760 | reveal_state: Arc<ServerRevealState>, | ||
| 1761 | server_bars: Vec<ProgressBar>, | ||
| 1762 | ) -> tokio::task::JoinHandle<()> { | ||
| 1763 | tokio::spawn(async move { | ||
| 1764 | tokio::time::sleep(Duration::from_millis(expand_delay_ms)).await; | ||
| 1765 | spinner_pb.finish_and_clear(); | ||
| 1766 | detail_multi.set_draw_target(ProgressDrawTarget::stderr()); | ||
| 1767 | heading_bar.finish_with_message("waiting for servers to create bare git repo..."); | ||
| 1768 | let mut deferred = reveal_state.deferred.lock().unwrap(); | ||
| 1769 | reveal_state.revealed.store(true, Ordering::Release); | ||
| 1770 | for df in deferred.drain(..) { | ||
| 1771 | df.bar.set_style(df.style); | ||
| 1772 | df.bar.finish_with_message(df.message); | ||
| 1773 | } | ||
| 1774 | for bar in &server_bars { | ||
| 1775 | if !bar.is_finished() { | ||
| 1776 | bar.enable_steady_tick(Duration::from_millis(100)); | ||
| 1777 | } | ||
| 1778 | } | ||
| 1779 | }) | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | fn finalize_spinner( | ||
| 1783 | all_ready: bool, | ||
| 1784 | spinner_pb: &ProgressBar, | ||
| 1785 | final_ready: u64, | ||
| 1786 | total: u64, | ||
| 1787 | ) { | ||
| 1788 | if all_ready { | ||
| 1789 | spinner_pb.finish_and_clear(); | ||
| 1790 | } else { | ||
| 1791 | spinner_pb.set_style(ProgressStyle::with_template("{msg}").unwrap()); | ||
| 1792 | spinner_pb.finish_with_message(format!( | ||
| 1793 | "timed out waiting for servers to create bare git repo ({final_ready}/{total} - complete), proceeding anyway" | ||
| 1794 | )); | ||
| 1795 | } | ||
| 1796 | } | ||
| 1797 | |||
| 1725 | fn finish_server_bar( | 1798 | fn finish_server_bar( |
| 1726 | bar: &ProgressBar, | 1799 | bar: &ProgressBar, |
| 1727 | style: ProgressStyle, | 1800 | style: ProgressStyle, |
| @@ -1745,6 +1818,78 @@ fn finish_server_bar( | |||
| 1745 | } | 1818 | } |
| 1746 | } | 1819 | } |
| 1747 | 1820 | ||
| 1821 | async fn poll_single_server( | ||
| 1822 | url: String, | ||
| 1823 | git_repo_path: std::path::PathBuf, | ||
| 1824 | bar: ProgressBar, | ||
| 1825 | ctx: Arc<PollContext>, | ||
| 1826 | ) -> bool { | ||
| 1827 | let poll_interval = Duration::from_millis(500); | ||
| 1828 | let deadline = tokio::time::Instant::now() + Duration::from_secs(ctx.timeout_secs); | ||
| 1829 | let mut ready = false; | ||
| 1830 | loop { | ||
| 1831 | let is_ready = tokio::task::spawn_blocking({ | ||
| 1832 | let url = url.clone(); | ||
| 1833 | let path = git_repo_path.clone(); | ||
| 1834 | move || check_git_server_ready(&path, &url) | ||
| 1835 | }) | ||
| 1836 | .await | ||
| 1837 | .unwrap_or(false); | ||
| 1838 | |||
| 1839 | if is_ready { | ||
| 1840 | ready = true; | ||
| 1841 | break; | ||
| 1842 | } | ||
| 1843 | |||
| 1844 | if tokio::time::Instant::now() >= deadline { | ||
| 1845 | break; | ||
| 1846 | } | ||
| 1847 | |||
| 1848 | tokio::time::sleep(poll_interval).await; | ||
| 1849 | } | ||
| 1850 | |||
| 1851 | let count = if ready { | ||
| 1852 | ctx.ready_count.fetch_add(1, Ordering::Relaxed) + 1 | ||
| 1853 | } else { | ||
| 1854 | ctx.ready_count.load(Ordering::Relaxed) | ||
| 1855 | }; | ||
| 1856 | |||
| 1857 | ctx.spinner_pb.set_message(format!( | ||
| 1858 | "waiting for servers to create bare git repo... ({count}/{total} - complete)", | ||
| 1859 | total = ctx.total | ||
| 1860 | )); | ||
| 1861 | |||
| 1862 | let name = url | ||
| 1863 | .trim_start_matches("https://") | ||
| 1864 | .trim_start_matches("http://") | ||
| 1865 | .to_string(); | ||
| 1866 | if ready { | ||
| 1867 | let style = ProgressStyle::with_template(&format!( | ||
| 1868 | " {} {{msg}}", | ||
| 1869 | console::style("✔").for_stderr().green() | ||
| 1870 | )) | ||
| 1871 | .unwrap(); | ||
| 1872 | let msg = console::style(format!("{name} - ready")) | ||
| 1873 | .for_stderr() | ||
| 1874 | .green() | ||
| 1875 | .to_string(); | ||
| 1876 | finish_server_bar(&bar, style, msg, &ctx.reveal_state); | ||
| 1877 | } else { | ||
| 1878 | let style = ProgressStyle::with_template(&format!( | ||
| 1879 | " {} {{msg}}", | ||
| 1880 | console::style("✘").for_stderr().red() | ||
| 1881 | )) | ||
| 1882 | .unwrap(); | ||
| 1883 | let msg = console::style(format!("{name} - timeout")) | ||
| 1884 | .for_stderr() | ||
| 1885 | .red() | ||
| 1886 | .to_string(); | ||
| 1887 | finish_server_bar(&bar, style, msg, &ctx.reveal_state); | ||
| 1888 | } | ||
| 1889 | |||
| 1890 | ready | ||
| 1891 | } | ||
| 1892 | |||
| 1748 | /// Poll grasp servers in parallel until all are ready or timeout is reached. | 1893 | /// Poll grasp servers in parallel until all are ready or timeout is reached. |
| 1749 | /// | 1894 | /// |
| 1750 | /// Shows a concise spinner with `x/y - complete` progress. After 5s without | 1895 | /// Shows a concise spinner with `x/y - complete` progress. After 5s without |
| @@ -1799,166 +1944,50 @@ async fn wait_for_grasp_servers( | |||
| 1799 | deferred: Mutex::new(Vec::new()), | 1944 | deferred: Mutex::new(Vec::new()), |
| 1800 | }); | 1945 | }); |
| 1801 | 1946 | ||
| 1802 | // Per-server spinner bars (added to hidden detail_multi) | 1947 | let server_bars = create_server_bars(&clone_urls, &detail_multi); |
| 1803 | let waiting_style = ProgressStyle::with_template(" {spinner} {msg}") | ||
| 1804 | .unwrap() | ||
| 1805 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"); | ||
| 1806 | let server_bars: Vec<ProgressBar> = clone_urls | ||
| 1807 | .iter() | ||
| 1808 | .map(|url| { | ||
| 1809 | let name = url | ||
| 1810 | .trim_start_matches("https://") | ||
| 1811 | .trim_start_matches("http://") | ||
| 1812 | .to_string(); | ||
| 1813 | detail_multi.add( | ||
| 1814 | ProgressBar::new_spinner() | ||
| 1815 | .with_style(waiting_style.clone()) | ||
| 1816 | .with_message( | ||
| 1817 | console::style(format!("{name} - waiting")) | ||
| 1818 | .for_stderr() | ||
| 1819 | .dim() | ||
| 1820 | .to_string(), | ||
| 1821 | ), | ||
| 1822 | ) | ||
| 1823 | }) | ||
| 1824 | .collect(); | ||
| 1825 | 1948 | ||
| 1826 | // Background timer: after expand_delay_ms reveal the detail view and | 1949 | let timer_handle = spawn_expand_timer( |
| 1827 | // flush any bars that already finished (the BarRevealState pattern). | 1950 | expand_delay_ms, |
| 1828 | let detail_multi_for_timer = detail_multi.clone(); | 1951 | spinner_pb.clone(), |
| 1829 | let spinner_for_timer = spinner_pb.clone(); | 1952 | detail_multi.clone(), |
| 1830 | let reveal_state_for_timer = reveal_state.clone(); | 1953 | heading_bar, |
| 1831 | let server_bars_for_timer = server_bars.clone(); | 1954 | reveal_state.clone(), |
| 1832 | let heading_bar_for_timer = heading_bar.clone(); | 1955 | server_bars.clone(), |
| 1833 | let timer_handle = tokio::spawn(async move { | 1956 | ); |
| 1834 | tokio::time::sleep(Duration::from_millis(expand_delay_ms)).await; | ||
| 1835 | spinner_for_timer.finish_and_clear(); | ||
| 1836 | detail_multi_for_timer.set_draw_target(ProgressDrawTarget::stderr()); | ||
| 1837 | // Show the heading in the expanded view. | ||
| 1838 | heading_bar_for_timer.finish_with_message("waiting for servers to create bare git repo..."); | ||
| 1839 | // Lock deferred list, mark revealed, and flush bars that already | ||
| 1840 | // finished. Must hold the lock across the revealed.store so that | ||
| 1841 | // finish_server_bar cannot push after the drain. | ||
| 1842 | let mut deferred = reveal_state_for_timer.deferred.lock().unwrap(); | ||
| 1843 | reveal_state_for_timer | ||
| 1844 | .revealed | ||
| 1845 | .store(true, Ordering::Release); | ||
| 1846 | for df in deferred.drain(..) { | ||
| 1847 | df.bar.set_style(df.style); | ||
| 1848 | df.bar.finish_with_message(df.message); | ||
| 1849 | } | ||
| 1850 | // Kick still-waiting bars into drawing by enabling their tick. | ||
| 1851 | for bar in &server_bars_for_timer { | ||
| 1852 | if !bar.is_finished() { | ||
| 1853 | bar.enable_steady_tick(Duration::from_millis(100)); | ||
| 1854 | } | ||
| 1855 | } | ||
| 1856 | }); | ||
| 1857 | 1957 | ||
| 1858 | // Poll each server in parallel | 1958 | // Poll each server in parallel |
| 1859 | let git_repo_path = git_repo.get_path()?.to_path_buf(); | 1959 | let git_repo_path = git_repo.get_path()?.to_path_buf(); |
| 1960 | let poll_ctx = Arc::new(PollContext { | ||
| 1961 | timeout_secs, | ||
| 1962 | total, | ||
| 1963 | ready_count: ready_count.clone(), | ||
| 1964 | spinner_pb: spinner_pb.clone(), | ||
| 1965 | reveal_state: reveal_state.clone(), | ||
| 1966 | }); | ||
| 1860 | let futures: Vec<_> = clone_urls | 1967 | let futures: Vec<_> = clone_urls |
| 1861 | .iter() | 1968 | .iter() |
| 1862 | .enumerate() | 1969 | .enumerate() |
| 1863 | .map(|(i, url)| { | 1970 | .map(|(i, url)| { |
| 1864 | let url = url.clone(); | 1971 | poll_single_server( |
| 1865 | let ready_count = ready_count.clone(); | 1972 | url.clone(), |
| 1866 | let spinner_pb = spinner_pb.clone(); | 1973 | git_repo_path.clone(), |
| 1867 | let bar = server_bars[i].clone(); | 1974 | server_bars[i].clone(), |
| 1868 | let git_repo_path = git_repo_path.clone(); | 1975 | poll_ctx.clone(), |
| 1869 | let reveal_state = reveal_state.clone(); | 1976 | ) |
| 1870 | async move { | ||
| 1871 | let poll_interval = Duration::from_millis(500); | ||
| 1872 | let deadline = tokio::time::Instant::now() + Duration::from_secs(timeout_secs); | ||
| 1873 | let mut ready = false; | ||
| 1874 | loop { | ||
| 1875 | let is_ready = tokio::task::spawn_blocking({ | ||
| 1876 | let url = url.clone(); | ||
| 1877 | let path = git_repo_path.clone(); | ||
| 1878 | move || check_git_server_ready(&path, &url) | ||
| 1879 | }) | ||
| 1880 | .await | ||
| 1881 | .unwrap_or(false); | ||
| 1882 | |||
| 1883 | if is_ready { | ||
| 1884 | ready = true; | ||
| 1885 | break; | ||
| 1886 | } | ||
| 1887 | |||
| 1888 | if tokio::time::Instant::now() >= deadline { | ||
| 1889 | break; | ||
| 1890 | } | ||
| 1891 | |||
| 1892 | tokio::time::sleep(poll_interval).await; | ||
| 1893 | } | ||
| 1894 | |||
| 1895 | let count = if ready { | ||
| 1896 | ready_count.fetch_add(1, Ordering::Relaxed) + 1 | ||
| 1897 | } else { | ||
| 1898 | ready_count.load(Ordering::Relaxed) | ||
| 1899 | }; | ||
| 1900 | |||
| 1901 | // Update spinner message | ||
| 1902 | spinner_pb.set_message(format!( | ||
| 1903 | "waiting for servers to create bare git repo... ({count}/{total} - complete)" | ||
| 1904 | )); | ||
| 1905 | |||
| 1906 | // Finish per-server bar (deferred if detail not yet visible) | ||
| 1907 | let name = url | ||
| 1908 | .trim_start_matches("https://") | ||
| 1909 | .trim_start_matches("http://") | ||
| 1910 | .to_string(); | ||
| 1911 | if ready { | ||
| 1912 | let style = ProgressStyle::with_template(&format!( | ||
| 1913 | " {} {{msg}}", | ||
| 1914 | console::style("✔").for_stderr().green() | ||
| 1915 | )) | ||
| 1916 | .unwrap(); | ||
| 1917 | let msg = console::style(format!("{name} - ready")) | ||
| 1918 | .for_stderr() | ||
| 1919 | .green() | ||
| 1920 | .to_string(); | ||
| 1921 | finish_server_bar(&bar, style, msg, &reveal_state); | ||
| 1922 | } else { | ||
| 1923 | let style = ProgressStyle::with_template(&format!( | ||
| 1924 | " {} {{msg}}", | ||
| 1925 | console::style("✘").for_stderr().red() | ||
| 1926 | )) | ||
| 1927 | .unwrap(); | ||
| 1928 | let msg = console::style(format!("{name} - timeout")) | ||
| 1929 | .for_stderr() | ||
| 1930 | .red() | ||
| 1931 | .to_string(); | ||
| 1932 | finish_server_bar(&bar, style, msg, &reveal_state); | ||
| 1933 | } | ||
| 1934 | |||
| 1935 | ready | ||
| 1936 | } | ||
| 1937 | }) | 1977 | }) |
| 1938 | .collect(); | 1978 | .collect(); |
| 1939 | 1979 | ||
| 1940 | let results = join_all(futures).await; | 1980 | let results = join_all(futures).await; |
| 1941 | let final_ready = ready_count.load(Ordering::Relaxed); | 1981 | let final_ready = ready_count.load(Ordering::Relaxed); |
| 1942 | 1982 | ||
| 1943 | // Cancel the expand timer if it hasn't fired yet. | ||
| 1944 | timer_handle.abort(); | 1983 | timer_handle.abort(); |
| 1945 | 1984 | ||
| 1946 | // If detail view was revealed, clear the detail bars. | ||
| 1947 | if reveal_state.revealed.load(Ordering::Acquire) { | 1985 | if reveal_state.revealed.load(Ordering::Acquire) { |
| 1948 | let _ = detail_multi.clear(); | 1986 | let _ = detail_multi.clear(); |
| 1949 | } | 1987 | } |
| 1950 | 1988 | ||
| 1951 | let all_ready = results.iter().all(|&r| r); | 1989 | let all_ready = results.iter().all(|&r| r); |
| 1952 | if all_ready { | 1990 | finalize_spinner(all_ready, &spinner_pb, final_ready, total); |
| 1953 | // Success — erase the spinner line entirely, leave nothing behind. | ||
| 1954 | spinner_pb.finish_and_clear(); | ||
| 1955 | } else { | ||
| 1956 | // Partial timeout — leave a message so the user knows we proceeded. | ||
| 1957 | spinner_pb.set_style(ProgressStyle::with_template("{msg}").unwrap()); | ||
| 1958 | spinner_pb.finish_with_message(format!( | ||
| 1959 | "timed out waiting for servers to create bare git repo ({final_ready}/{total} - complete), proceeding anyway" | ||
| 1960 | )); | ||
| 1961 | } | ||
| 1962 | 1991 | ||
| 1963 | Ok(()) | 1992 | Ok(()) |
| 1964 | } | 1993 | } |