feat(update): add overall progress bar with per-task spinners#11
Conversation
When projects have nested paths (e.g. arcs-sdk and arcs-sdk/modules/x), concurrent clones can create parent directories before the parent project itself is cloned, causing git clone to fail. Now automatically falls back to init+fetch when clone fails and the directory exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 59 individual spinners with a clean progress UI: - Top-level bar showing [##---] pos/total updating... - Per-task spinners below the bar (cloning/fetching/checking out) - Spinners auto-clear on success, persist on failure - Overall bar clears when all tasks complete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates east-cli update output to use a cleaner progress UI: a single overall progress bar with up to 8 concurrent per-project status spinners, reducing noisy multi-line output in large workspaces.
Changes:
- Add a top-level overall progress bar showing
{pos}/{len}update completion. - Insert per-project spinner progress indicators below the overall bar and clear them on success.
- Add a clone fallback path that attempts
init_and_fetchwhen a concurrent operation creates the destination directory mid-clone.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let pb = mp.insert_after(&overall, ProgressBar::new_spinner()); | ||
| pb.set_style(spinner_style.clone()); |
There was a problem hiding this comment.
Per-task ProgressBar::new_spinner() never calls tick() or enable_steady_tick(...), so the spinner glyph will stay static (and may not render as intended) until some other redraw happens. Consider enabling a steady tick for these spinners (bounded by MAX_CONCURRENT_GIT), or explicitly ticking on state updates so the UI matches the intended “spinner” behavior.
| let clone_result = Git::clone(url, &project_path, revision.as_deref()).await; | ||
| if clone_result.is_err() && project_path.exists() { | ||
| pb.set_message(format!("{project_name}: initializing (fallback)...")); | ||
| Git::init_and_fetch(url, &project_path, revision.as_deref()).await | ||
| } else { | ||
| clone_result | ||
| } |
There was a problem hiding this comment.
The clone fallback triggers on any clone error as long as project_path.exists() afterward. Since git clone often creates the destination directory even when failing (e.g., auth/network failures), this can replace the original error with an init_and_fetch failure (e.g., remote origin already exists) and make debugging harder. Restrict the fallback to the specific “destination exists / not empty” failure mode (e.g., by inspecting the error’s stderr), or re-check that the destination is still a non-repo directory before falling back.
When a project has uncommitted changes, east update now skips the checkout step and warns the user instead of failing. The --force flag overrides this safety check. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When project paths are nested (e.g. arcs-sdk contains arcs-sdk/modules/xxx), automatically add child project paths to the parent's .git/info/exclude. This prevents child directories from showing as untracked in the parent repo's git status, which is especially useful when migrating from git submodules to east manifest management. The managed block is clearly marked and idempotent — re-running east update will update the block without duplicating entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Display the relative path alongside the project name so users can quickly locate the affected project directory. Before: cat: skipped checkout (uncommitted changes, ...) After: cat (arcs-sdk/modules/cAT): skipped checkout (uncommitted changes, ...) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow specifying project names with --force to selectively override dirty checkout protection: east update --force # force all projects east update --force cat arcs-sdk # force only named projects Unknown project names are rejected with a clear error message. Add Git::force_checkout() for checkout -f. Add 4 integration tests covering dirty skip, per-project force, global force, and unknown project validation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Windows git checkout converts \n to \r\n, causing exact string comparison to fail. Use contains() for cross-platform compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only fallback to init+fetch when git clone fails specifically because the destination already exists and is not a git repo. Other failures (auth, network, etc.) now propagate the original error directly, making debugging easier. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without steady tick, spinner glyphs stay static. Enable 100ms tick interval so the animation renders correctly during git operations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add spinners to MultiProgress only after acquiring semaphore permit, preventing empty spinner lines for queued tasks - Match both "already exists" and "File exists" in clone fallback to handle different git error message formats across platforms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
[######----] 32/59 updating...Before
After
Test plan
🤖 Generated with Claude Code