From 4617a60e5593036448320266fc1dc108b2f1634d Mon Sep 17 00:00:00 2001 From: gold-silver-copper <157451540+gold-silver-copper@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:27:52 -0700 Subject: [PATCH 1/4] fix: escape directory names in progress and result lines loop prints directory names and paths from the filesystem (such as the root's basename). They go through meta_core::display, the same helper meta uses everywhere else, so a control character in a directory name cannot reach the terminal. Adds the meta_core dependency; CI clones it from the same branch when one exists. Implements [[tasks/harmony-2749]] --- .github/workflows/auto-format.yml | 18 ++++++++++- .github/workflows/ci.yml | 54 +++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/lib.rs | 19 +++++++---- src/tests.rs | 15 +++++++++ 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/.github/workflows/auto-format.yml b/.github/workflows/auto-format.yml index 4253344..04b861a 100644 --- a/.github/workflows/auto-format.yml +++ b/.github/workflows/auto-format.yml @@ -31,11 +31,27 @@ jobs: # Use PAT to trigger downstream workflows; fallback to GITHUB_TOKEN token: ${{ secrets.PARENT_REPO_PAT || github.token }} + - name: Clone dependencies + shell: bash + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + for repo in meta_core; do + if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then + echo "Cloned ${repo}@${BRANCH}" + elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then + echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + else + echo "::error::Failed to clone ${repo}" + exit 1 + fi + done + - name: Create workspace Cargo.toml run: | cat > Cargo.toml << 'EOF' [workspace] - members = ["loop_lib"] + members = ["loop_lib", "meta_core"] resolver = "2" [workspace.package] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2acee8a..87d664f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,12 +23,28 @@ jobs: with: path: loop_lib + - name: Clone dependencies + shell: bash + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + for repo in meta_core; do + if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then + echo "Cloned ${repo}@${BRANCH}" + elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then + echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + else + echo "::error::Failed to clone ${repo}" + exit 1 + fi + done + - name: Create workspace Cargo.toml shell: bash run: | cat > Cargo.toml << 'EOF' [workspace] - members = ["loop_lib"] + members = ["loop_lib", "meta_core"] resolver = "2" [workspace.package] @@ -59,11 +75,27 @@ jobs: with: path: loop_lib + - name: Clone dependencies + shell: bash + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + for repo in meta_core; do + if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then + echo "Cloned ${repo}@${BRANCH}" + elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then + echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + else + echo "::error::Failed to clone ${repo}" + exit 1 + fi + done + - name: Create workspace Cargo.toml run: | cat > Cargo.toml << 'EOF' [workspace] - members = ["loop_lib"] + members = ["loop_lib", "meta_core"] resolver = "2" [workspace.package] @@ -96,11 +128,27 @@ jobs: with: path: loop_lib + - name: Clone dependencies + shell: bash + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + for repo in meta_core; do + if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then + echo "Cloned ${repo}@${BRANCH}" + elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then + echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + else + echo "::error::Failed to clone ${repo}" + exit 1 + fi + done + - name: Create workspace Cargo.toml run: | cat > Cargo.toml << 'EOF' [workspace] - members = ["loop_lib"] + members = ["loop_lib", "meta_core"] resolver = "2" [workspace.package] diff --git a/Cargo.toml b/Cargo.toml index 825211b..637fa6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ repository.workspace = true description = "Core loop engine library for running commands across directories" [dependencies] +meta_core = { path = "../meta_core" } anyhow = "1.0" rayon = "1.5" serde = { version = "1.0", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index ba7ddda..925899c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use colored::*; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use meta_core::display::{escape, escape_path}; use rayon::prelude::*; use rayon::ThreadPoolBuilder; use serde::{Deserialize, Serialize}; @@ -201,8 +202,12 @@ pub fn add_aliases_to_global_looprc() -> Result<()> { /// How a directory is named in progress and result lines: `.` for the root, /// `./open-source/meta/loop_cli` for a directory under it, so repos that share /// a basename stay distinguishable, and the full path for one outside it. -/// Without a root, the basename. +/// Without a root, the basename. Control characters in it are escaped. fn display_name(dir: &Path, root: Option<&Path>) -> String { + escape(&plain_display_name(dir, root)).into_owned() +} + +fn plain_display_name(dir: &Path, root: Option<&Path>) -> String { let Some(root) = root else { return dir .file_name() @@ -241,7 +246,7 @@ fn result_name(dir: &Path, root: Option<&Path>) -> String { Some(dir.to_path_buf()) }; match base.as_deref().and_then(Path::file_name) { - Some(base) => format!(". ({})", base.to_string_lossy()), + Some(base) => format!(". ({})", escape(&base.to_string_lossy())), None => name, } } @@ -302,10 +307,10 @@ fn failed_result(dir: &Path, command: String, stderr: String) -> CommandResult { fn dry_run_line(dir: &Path, command: &str) -> String { let dir_display = if dir.as_os_str() == "." { env::current_dir() - .map(|cwd| cwd.display().to_string()) + .map(|cwd| escape_path(&cwd)) .unwrap_or_else(|_| ".".to_string()) } else { - dir.display().to_string() + escape_path(dir) }; format!("Would execute in {dir_display}:\n {command}") } @@ -318,7 +323,7 @@ pub fn execute_command_in_directory( extra_env: Option<&HashMap>, ) -> CommandResult { if !dir.exists() { - println!("\nNo directory found for {}", dir.display()); + println!("\nNo directory found for {}", escape_path(dir)); println!( "\x1b[31m\n✗ {}: No directory found. Command: {} (Exit code: {})\x1b[0m", display_name(dir, config.root_dir.as_deref()), @@ -356,7 +361,7 @@ pub fn execute_command_in_directory( } if config.verbose { - println!("Executing in directory: {}", dir.display()); + println!("Executing in directory: {}", escape_path(dir)); } if !config.silent { @@ -806,7 +811,7 @@ fn execute_commands_internal(config: &LoopConfig, commands: &[DirCommand]) -> Re // Without a root the basename is ambiguous, so keep the full path let name = match config.root_dir.as_deref() { Some(root) => result_name(&result.directory, Some(root)), - None => result.directory.display().to_string(), + None => escape_path(&result.directory), }; println!( "\n{} {}: {} (Exit code {}) ", diff --git a/src/tests.rs b/src/tests.rs index 926fe41..df4762b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -819,3 +819,18 @@ fn test_display_name_is_path_from_root() { assert_eq!(display_name(Path::new("./repo"), None), "repo"); assert_eq!(result_name(root, Some(root)), ". (harmony)"); } + +#[test] +fn test_display_names_escape_control_characters() { + let root = Path::new("/ws/x\u{1b}]0;TITLE\u{7}"); + assert_eq!(result_name(root, Some(root)), ". (x\\u{1b}]0;TITLE\\u{7})"); + assert_eq!( + display_name(&root.join("a\u{1b}[2J"), Some(root)), + "./a\\u{1b}[2J" + ); + assert_eq!( + display_name(Path::new("/elsewhere/n\nfake"), Some(root)), + "/elsewhere/n\\nfake" + ); + assert_eq!(display_name(Path::new("b\u{9b}31m"), None), "b\\u{9b}31m"); +} From 2a1ad791c0244bf9ecb4543bc89d51615cc479cd Mon Sep 17 00:00:00 2001 From: gold-silver-copper <157451540+gold-silver-copper@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:27:52 -0700 Subject: [PATCH 2/4] fix: escape the missing-directory error; version the meta_core dependency The meta_core dependency gets a version range so loop_lib can still be published; CI decides between the PR branch and main with ls-remote instead of hiding clone errors. Implements [[tasks/harmony-2749]] --- .github/workflows/auto-format.yml | 15 +++++++---- .github/workflows/ci.yml | 45 ++++++++++++++++++++----------- Cargo.toml | 4 ++- src/lib.rs | 2 +- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/.github/workflows/auto-format.yml b/.github/workflows/auto-format.yml index 04b861a..d93f035 100644 --- a/.github/workflows/auto-format.yml +++ b/.github/workflows/auto-format.yml @@ -37,12 +37,17 @@ jobs: BRANCH: ${{ github.head_ref || github.ref_name }} run: | for repo in meta_core; do - if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then - echo "Cloned ${repo}@${BRANCH}" - elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then - echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + url="https://github.com/gitkb/${repo}.git" + # Exit code 2 means the branch does not exist; anything else is an error + status=0 + git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + if [ "$status" -eq 0 ]; then + git clone --depth 1 -b "$BRANCH" "$url" + elif [ "$status" -eq 2 ]; then + echo "Branch ${BRANCH} not found for ${repo}; using the default branch" + git clone --depth 1 "$url" else - echo "::error::Failed to clone ${repo}" + echo "::error::Failed to look up ${repo}" exit 1 fi done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87d664f..ae8e1d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,17 @@ jobs: BRANCH: ${{ github.head_ref || github.ref_name }} run: | for repo in meta_core; do - if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then - echo "Cloned ${repo}@${BRANCH}" - elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then - echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + url="https://github.com/gitkb/${repo}.git" + # Exit code 2 means the branch does not exist; anything else is an error + status=0 + git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + if [ "$status" -eq 0 ]; then + git clone --depth 1 -b "$BRANCH" "$url" + elif [ "$status" -eq 2 ]; then + echo "Branch ${BRANCH} not found for ${repo}; using the default branch" + git clone --depth 1 "$url" else - echo "::error::Failed to clone ${repo}" + echo "::error::Failed to look up ${repo}" exit 1 fi done @@ -81,12 +86,17 @@ jobs: BRANCH: ${{ github.head_ref || github.ref_name }} run: | for repo in meta_core; do - if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then - echo "Cloned ${repo}@${BRANCH}" - elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then - echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + url="https://github.com/gitkb/${repo}.git" + # Exit code 2 means the branch does not exist; anything else is an error + status=0 + git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + if [ "$status" -eq 0 ]; then + git clone --depth 1 -b "$BRANCH" "$url" + elif [ "$status" -eq 2 ]; then + echo "Branch ${BRANCH} not found for ${repo}; using the default branch" + git clone --depth 1 "$url" else - echo "::error::Failed to clone ${repo}" + echo "::error::Failed to look up ${repo}" exit 1 fi done @@ -134,12 +144,17 @@ jobs: BRANCH: ${{ github.head_ref || github.ref_name }} run: | for repo in meta_core; do - if git clone --depth 1 -b "$BRANCH" "https://github.com/gitkb/${repo}.git" 2>/dev/null; then - echo "Cloned ${repo}@${BRANCH}" - elif git clone --depth 1 "https://github.com/gitkb/${repo}.git"; then - echo "Branch ${BRANCH} not found for ${repo}; falling back to default branch" + url="https://github.com/gitkb/${repo}.git" + # Exit code 2 means the branch does not exist; anything else is an error + status=0 + git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + if [ "$status" -eq 0 ]; then + git clone --depth 1 -b "$BRANCH" "$url" + elif [ "$status" -eq 2 ]; then + echo "Branch ${BRANCH} not found for ${repo}; using the default branch" + git clone --depth 1 "$url" else - echo "::error::Failed to clone ${repo}" + echo "::error::Failed to look up ${repo}" exit 1 fi done diff --git a/Cargo.toml b/Cargo.toml index 637fa6d..2d1f3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,9 @@ repository.workspace = true description = "Core loop engine library for running commands across directories" [dependencies] -meta_core = { path = "../meta_core" } +# Any version: child-repo CI builds meta_core as 0.1.0, releases as the +# workspace version +meta_core = { path = "../meta_core", version = ">=0.1, <1" } anyhow = "1.0" rayon = "1.5" serde = { version = "1.0", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index 925899c..9436067 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -427,7 +427,7 @@ pub fn execute_command_in_directory_capturing( ..failed_result( dir, display_command(argv), - format!("Directory does not exist: {}", dir.display()), + format!("Directory does not exist: {}", escape_path(dir)), ) }; } From 0e76960738b1358cb1694785f1c23ee6a9ee7ff3 Mon Sep 17 00:00:00 2001 From: gold-silver-copper <157451540+gold-silver-copper@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:27:52 -0700 Subject: [PATCH 3/4] fix: accept any 0.x meta_core, as child-repo CI builds it as 0.0.0 Implements [[tasks/harmony-2749]] --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d1f3b7..0afc87e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,9 @@ repository.workspace = true description = "Core loop engine library for running commands across directories" [dependencies] -# Any version: child-repo CI builds meta_core as 0.1.0, releases as the -# workspace version -meta_core = { path = "../meta_core", version = ">=0.1, <1" } +# Any 0.x: child-repo CI builds meta_core as 0.0.0 or 0.1.0, releases as +# the workspace version +meta_core = { path = "../meta_core", version = "<1" } anyhow = "1.0" rayon = "1.5" serde = { version = "1.0", features = ["derive"] } From e0cb6fea1d4c2d534c461ce6707b44f7544ddeaa Mon Sep 17 00:00:00 2001 From: gold-silver-copper <157451540+gold-silver-copper@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:27:52 -0700 Subject: [PATCH 4/4] ci: probe the exact branch ref, not one ending in the same name Implements [[tasks/harmony-2749]] --- .github/workflows/auto-format.yml | 2 +- .github/workflows/ci.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-format.yml b/.github/workflows/auto-format.yml index d93f035..11ce021 100644 --- a/.github/workflows/auto-format.yml +++ b/.github/workflows/auto-format.yml @@ -40,7 +40,7 @@ jobs: url="https://github.com/gitkb/${repo}.git" # Exit code 2 means the branch does not exist; anything else is an error status=0 - git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + git ls-remote --exit-code --heads "$url" "refs/heads/$BRANCH" > /dev/null || status=$? if [ "$status" -eq 0 ]; then git clone --depth 1 -b "$BRANCH" "$url" elif [ "$status" -eq 2 ]; then diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae8e1d5..344d571 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: url="https://github.com/gitkb/${repo}.git" # Exit code 2 means the branch does not exist; anything else is an error status=0 - git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + git ls-remote --exit-code --heads "$url" "refs/heads/$BRANCH" > /dev/null || status=$? if [ "$status" -eq 0 ]; then git clone --depth 1 -b "$BRANCH" "$url" elif [ "$status" -eq 2 ]; then @@ -89,7 +89,7 @@ jobs: url="https://github.com/gitkb/${repo}.git" # Exit code 2 means the branch does not exist; anything else is an error status=0 - git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + git ls-remote --exit-code --heads "$url" "refs/heads/$BRANCH" > /dev/null || status=$? if [ "$status" -eq 0 ]; then git clone --depth 1 -b "$BRANCH" "$url" elif [ "$status" -eq 2 ]; then @@ -147,7 +147,7 @@ jobs: url="https://github.com/gitkb/${repo}.git" # Exit code 2 means the branch does not exist; anything else is an error status=0 - git ls-remote --exit-code --heads "$url" "$BRANCH" > /dev/null || status=$? + git ls-remote --exit-code --heads "$url" "refs/heads/$BRANCH" > /dev/null || status=$? if [ "$status" -eq 0 ]; then git clone --depth 1 -b "$BRANCH" "$url" elif [ "$status" -eq 2 ]; then