From 17ff88902ba089285a8d635c4bda05dbaea94131 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Sep 2025 18:19:41 +0200 Subject: [PATCH 1/7] Avoid initializing nested Git repository Previously a Git repository was initialized if a Cargo workspace was detected. However, it's also possible for users to initialize rustlings in an existing Git repository that doesn't contain a Cargo workspace. In that case, it's still undesirable to initialize a nested Git repository for rustlings. We therefore search all ancestors of the current working directory for `.git` or `.jj` directories to determine if rustlings is being initialized in an existing Git repository. --- src/init.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/init.rs b/src/init.rs index 971b0e51e2..2987907e82 100644 --- a/src/init.rs +++ b/src/init.rs @@ -29,6 +29,21 @@ pub fn init() -> Result<()> { bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR); } + let is_inside_vcs_repository = 'detect_repo: { + let Ok(mut dir) = std::env::current_dir() else { + break 'detect_repo false; + }; + loop { + if dir.join(".git").exists() || dir.join(".jj").exists() { + break 'detect_repo true; + } + match dir.parent() { + Some(parent) => dir = parent.into(), + None => break 'detect_repo false, + } + } + }; + let locate_project_output = Command::new("cargo") .arg("locate-project") .arg("-q") @@ -59,7 +74,7 @@ pub fn init() -> Result<()> { } let mut stdout = io::stdout().lock(); - let mut init_git = true; + let mut init_git = !is_inside_vcs_repository; if locate_project_output.status.success() { if Path::new("exercises").exists() && Path::new("solutions").exists() { From d87a3b6ca53016ed51da6ea94a1291a596a40db4 Mon Sep 17 00:00:00 2001 From: Gabriel Feceoru Date: Sat, 14 Mar 2026 16:26:22 +0100 Subject: [PATCH 2/7] Fix u16 mul overflow with big term width When running rustlings in Rover IDE, term width could have a value of 2480 which causes u16 mul overflow. --- src/term.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/term.rs b/src/term.rs index 3d149b33e8..b661dfa20f 100644 --- a/src/term.rs +++ b/src/term.rs @@ -216,7 +216,9 @@ pub fn progress_bar<'a>( stdout.write_all(PREFIX)?; let width = term_width - WRAPPER_WIDTH; - let filled = (width * progress) / total; + // Use u32 to prevent the intermediate multiplication from overflowing u16 + let filled = (width as u32 * progress as u32) / total as u32; + let filled = filled as u16; stdout.queue(SetForegroundColor(Color::Green))?; for _ in 0..filled { From 611d62951f74040a750589bd42f84dfaccead8cb Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 14 Mar 2026 16:57:50 +0100 Subject: [PATCH 3/7] Update deps --- Cargo.lock | 68 +++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c690e819a3..c0f0147a6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -19,15 +19,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -78,9 +78,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -112,15 +112,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "crossterm" @@ -196,9 +196,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ "bitflags 2.11.0", "inotify-sys", @@ -306,9 +306,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "linux-raw-sys" @@ -384,9 +384,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -438,18 +438,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "redox_syscall" @@ -627,9 +627,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.3+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "indexmap", "serde_core", @@ -885,9 +885,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "wit-bindgen" From 0ffeb1440294c0ec63b488fb7a1ac7ff1510c2fa Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 14 Mar 2026 17:10:11 +0100 Subject: [PATCH 4/7] Avoid unneeded computation on full progress bar --- src/term.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/term.rs b/src/term.rs index b661dfa20f..96b8745a95 100644 --- a/src/term.rs +++ b/src/term.rs @@ -227,14 +227,13 @@ pub fn progress_bar<'a>( if filled < width { stdout.write_all(b">")?; - } - let width_minus_filled = width - filled; - if width_minus_filled > 1 { - let red_part_width = width_minus_filled - 1; - stdout.queue(SetForegroundColor(Color::Red))?; - for _ in 0..red_part_width { - stdout.write_all(b"-")?; + let width_minus_filled = width - filled; + if width_minus_filled > 1 { + stdout.queue(SetForegroundColor(Color::Red))?; + for _ in 1..width_minus_filled { + stdout.write_all(b"-")?; + } } } From 337f6b152132438aa22c739aa79dcb17d9c9e239 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 14 Mar 2026 17:18:39 +0100 Subject: [PATCH 5/7] Apply pedantic Clippy lints --- src/app_state.rs | 2 +- src/info_file.rs | 2 +- src/main.rs | 4 ++-- src/term.rs | 6 +++--- src/watch/state.rs | 2 +- src/watch/terminal_event.rs | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app_state.rs b/src/app_state.rs index a566e14bdd..ad63c6de8b 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -433,7 +433,7 @@ impl AppState { .is_err() { break; - }; + } let success = exercise.run_exercise(None, &slf.cmd_runner); let progress = match success { diff --git a/src/info_file.rs b/src/info_file.rs index 13206fd86b..54a21a5c02 100644 --- a/src/info_file.rs +++ b/src/info_file.rs @@ -23,7 +23,7 @@ pub struct ExerciseInfo { #[serde(default)] pub skip_check_unsolved: bool, } -#[inline(always)] +#[inline] const fn default_true() -> bool { true } diff --git a/src/main.rs b/src/main.rs index b541b68ff9..c39e862927 100644 --- a/src/main.rs +++ b/src/main.rs @@ -171,9 +171,9 @@ fn main() -> Result { stdout.write_all(b"\n")?; return Ok(ExitCode::FAILURE); - } else { - app_state.render_final_message(&mut stdout)?; } + + app_state.render_final_message(&mut stdout)?; } Some(Subcommands::Reset { name }) => { app_state.set_current_exercise_by_name(&name)?; diff --git a/src/term.rs b/src/term.rs index 96b8745a95..65e4c5112f 100644 --- a/src/term.rs +++ b/src/term.rs @@ -197,15 +197,15 @@ pub fn progress_bar<'a>( total: u16, term_width: u16, ) -> io::Result<()> { - debug_assert!(total <= 999); - debug_assert!(progress <= total); - const PREFIX: &[u8] = b"Progress: ["; const PREFIX_WIDTH: u16 = PREFIX.len() as u16; const POSTFIX_WIDTH: u16 = "] xxx/xxx".len() as u16; const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH; const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4; + debug_assert!(total <= 999); + debug_assert!(progress <= total); + if term_width < MIN_LINE_WIDTH { writer.write_ascii(b"Progress: ")?; // Integers are in ASCII. diff --git a/src/watch/state.rs b/src/watch/state.rs index a92dd2d6d7..44cbd43985 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -60,7 +60,7 @@ impl<'a> WatchState<'a> { watch_event_sender, terminal_event_unpause_receiver, manual_run, - ) + ); }) .context("Failed to spawn a thread to handle terminal events")?; diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs index 2400a3df54..439e47300a 100644 --- a/src/watch/terminal_event.rs +++ b/src/watch/terminal_event.rs @@ -47,7 +47,7 @@ pub fn terminal_event_handler( // Pause input until quitting the confirmation prompt. if unpause_receiver.recv().is_err() { return; - }; + } continue; } @@ -64,7 +64,7 @@ pub fn terminal_event_handler( return; } } - Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue, + Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => (), Err(e) => break WatchEvent::TerminalEventErr(e), } }; From ceb98475e2a3a3c83df4c381a2da14afe3f8a2e8 Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 14 Mar 2026 17:36:18 +0100 Subject: [PATCH 6/7] Avoid unneeded castings --- src/app_state.rs | 10 +++++----- src/list/state.rs | 2 +- src/term.rs | 11 +++++------ src/watch/state.rs | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/app_state.rs b/src/app_state.rs index ad63c6de8b..5722e607eb 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -52,8 +52,8 @@ pub enum CheckProgress { pub struct AppState { current_exercise_ind: usize, exercises: Vec, - // Caches the number of done exercises to avoid iterating over all exercises every time. - n_done: u16, + // Cache the number of done exercises to avoid iterating over all exercises every time. + n_done: u32, final_message: &'static str, state_file: File, // Preallocated buffer for reading and writing the state file. @@ -191,13 +191,13 @@ impl AppState { } #[inline] - pub fn n_done(&self) -> u16 { + pub fn n_done(&self) -> u32 { self.n_done } #[inline] - pub fn n_pending(&self) -> u16 { - self.exercises.len() as u16 - self.n_done + pub fn n_pending(&self) -> u32 { + self.exercises.len() as u32 - self.n_done } #[inline] diff --git a/src/list/state.rs b/src/list/state.rs index 55ccb4c96b..58aa496167 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -229,7 +229,7 @@ impl<'a> ListState<'a> { progress_bar( &mut MaxLenWriter::new(stdout, self.term_width as usize), self.app_state.n_done(), - self.app_state.exercises().len() as u16, + self.app_state.exercises().len() as u32, self.term_width, )?; next_ln(stdout)?; diff --git a/src/term.rs b/src/term.rs index 65e4c5112f..8cab50055f 100644 --- a/src/term.rs +++ b/src/term.rs @@ -193,8 +193,8 @@ impl Drop for ProgressCounter<'_, '_> { pub fn progress_bar<'a>( writer: &mut impl CountedWrite<'a>, - progress: u16, - total: u16, + progress: u32, + total: u32, term_width: u16, ) -> io::Result<()> { const PREFIX: &[u8] = b"Progress: ["; @@ -215,10 +215,9 @@ pub fn progress_bar<'a>( let stdout = writer.stdout(); stdout.write_all(PREFIX)?; - let width = term_width - WRAPPER_WIDTH; - // Use u32 to prevent the intermediate multiplication from overflowing u16 - let filled = (width as u32 * progress as u32) / total as u32; - let filled = filled as u16; + // Use u32 to prevent the intermediate multiplication from overflowing + let width = u32::from(term_width - WRAPPER_WIDTH); + let filled = (width * progress) / total; stdout.queue(SetForegroundColor(Color::Green))?; for _ in 0..filled { diff --git a/src/watch/state.rs b/src/watch/state.rs index 44cbd43985..f93ea0cf90 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -245,7 +245,7 @@ impl<'a> WatchState<'a> { progress_bar( stdout, self.app_state.n_done(), - self.app_state.exercises().len() as u16, + self.app_state.exercises().len() as u32, self.term_width, )?; From a28b9eda84db5d1810f77225aee9d95ae960da2b Mon Sep 17 00:00:00 2001 From: mo8it Date: Sat, 14 Mar 2026 18:22:27 +0100 Subject: [PATCH 7/7] Delay inside_vcs_repo check until Git initialization --- src/init.rs | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/init.rs b/src/init.rs index 2987907e82..f043bd48f2 100644 --- a/src/init.rs +++ b/src/init.rs @@ -5,7 +5,7 @@ use crossterm::{ }; use serde::Deserialize; use std::{ - env::set_current_dir, + env::{current_dir, set_current_dir}, fs::{self, create_dir}, io::{self, Write}, path::Path, @@ -29,21 +29,6 @@ pub fn init() -> Result<()> { bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR); } - let is_inside_vcs_repository = 'detect_repo: { - let Ok(mut dir) = std::env::current_dir() else { - break 'detect_repo false; - }; - loop { - if dir.join(".git").exists() || dir.join(".jj").exists() { - break 'detect_repo true; - } - match dir.parent() { - Some(parent) => dir = parent.into(), - None => break 'detect_repo false, - } - } - }; - let locate_project_output = Command::new("cargo") .arg("locate-project") .arg("-q") @@ -74,7 +59,7 @@ pub fn init() -> Result<()> { } let mut stdout = io::stdout().lock(); - let mut init_git = !is_inside_vcs_repository; + let mut init_git = true; if locate_project_output.status.success() { if Path::new("exercises").exists() && Path::new("solutions").exists() { @@ -184,14 +169,27 @@ pub fn init() -> Result<()> { fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON) .context("Failed to create the file `rustlings/.vscode/extensions.json`")?; - if init_git { - // Ignore any Git error because Git initialization is not required. - let _ = Command::new("git") - .arg("init") - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); + if init_git && let Ok(dir) = current_dir() { + let mut dir = dir.as_path(); + + loop { + if dir.join(".git").exists() || dir.join(".jj").exists() { + break; + } + + if let Some(parent) = dir.parent() { + dir = parent; + } else { + // Ignore any Git error because Git initialization is not required. + let _ = Command::new("git") + .arg("init") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + break; + } + } } stdout.queue(SetForegroundColor(Color::Green))?;