Skip to content

feat: add --outdated flag to mise plugins ls#8360

Merged
jdx merged 6 commits intomainfrom
feat/plugins-ls-outdated
Feb 26, 2026
Merged

feat: add --outdated flag to mise plugins ls#8360
jdx merged 6 commits intomainfrom
feat/plugins-ls-outdated

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 26, 2026

Summary

  • Adds --outdated (-o) flag to mise plugins ls that checks remote refs via git ls-remote and only displays plugins where the local SHA differs from the remote
  • Shows a table with plugin name, URL, ref, local SHA, and remote SHA columns
  • Prints "All plugins are up to date" when no plugins are outdated

Closes #8319

Test plan

  • mise run build compiles cleanly
  • mise run lint-fix passes
  • mise plugins ls --outdated works with installed plugins
  • mise plugins ls -o short flag works
  • e2e test added and passing (test_plugins_outdated)

🤖 Generated with Claude Code


Note

Medium Risk
Adds remote git lookups to a common CLI command path, which can introduce latency, network-related failures, and output behavior changes despite being behind a new flag.

Overview
Adds -o/--outdated to mise plugins ls to show only plugins with available updates by comparing local vs remote git SHAs and printing a table of outdated entries (or logging that everything is up to date).

Implements plugin remote SHA fetching via a new Git::remote_sha() helper and a Plugin::remote_sha() hook (implemented for asdf/vfox plugins), runs checks in parallel, and updates generated docs/completions/manpage plus an e2e test covering the new flag.

Written by Cursor Bugbot for commit b61ac6e. This will update automatically on new commits. Configure here.

jdx and others added 2 commits February 26, 2026 11:31
Add a new `--outdated` flag that checks remote refs via `git ls-remote`
and only displays plugins where the local SHA differs from the remote.
Shows a table with plugin name, URL, ref, local SHA, and remote SHA.

Closes #8319

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant enhancement to the mise CLI by providing a mechanism to easily identify outdated plugins. By adding an --outdated flag to the plugins ls command, users can now quickly determine which of their installed plugins have newer versions available in their respective remote repositories. This improves maintainability and ensures users can keep their development environments current with minimal effort.

Highlights

  • New --outdated flag for mise plugins ls: A new --outdated (shorthand -o) flag has been added to the mise plugins ls command. This flag allows users to check for available updates for their installed plugins.
  • Remote SHA comparison: The command now checks remote Git repositories using git ls-remote to compare the local plugin SHA with the remote SHA, identifying plugins where the local version differs from the remote.
  • Outdated plugin display: When outdated plugins are found, they are displayed in a table format, showing the plugin name, URL, ref, local SHA, and remote SHA. If all plugins are up to date, a corresponding message is printed.
  • Core Git and Plugin API extensions: New functionality has been introduced in the git module to fetch remote SHAs, and the Plugin trait and its implementations (for Asdf and Vfox plugins) have been extended to support this remote version checking.
Changelog
  • e2e/plugins/test_plugins_outdated
    • Added a new end-to-end test to validate the functionality of mise plugins ls --outdated and its short form -o.
    • Verified that a freshly installed plugin is not incorrectly reported as outdated.
  • src/cli/plugins/ls.rs
    • Introduced the outdated boolean flag (-o, --outdated) to the PluginsLs command structure.
    • Implemented the core logic for the --outdated flag, which iterates through installed plugins, fetches their local and remote SHAs, and filters for discrepancies.
    • Added a new OutdatedRow struct for formatting the output of outdated plugins into a readable table.
    • Included a conditional message to inform the user if 'All plugins are up to date' when no outdated plugins are found.
  • src/cli/plugins/mod.rs
    • Updated the Plugins command to initialize the outdated field to false when constructing the PluginsLs command.
  • src/git.rs
    • Added a new public method remote_sha to the Git struct, which executes git ls-remote to retrieve the SHA of a specified remote branch.
  • src/plugins/asdf_plugin.rs
    • Implemented the remote_sha method for AsdfPlugin, leveraging the newly added Git::remote_sha to fetch the remote SHA for Asdf-style plugins.
  • src/plugins/mod.rs
    • Extended the Plugin trait with a new remote_sha method, providing a default Ok(None) implementation.
    • Updated the PluginEnum to dispatch calls to the remote_sha method for its underlying plugin types (Asdf, Vfox, VfoxBackend).
  • src/plugins/vfox_plugin.rs
    • Implemented the remote_sha method for VfoxPlugin, utilizing the new Git::remote_sha function to determine the remote SHA for Vfox plugins.
Activity
  • The pull request includes a detailed summary of changes and a test plan.
  • The author has confirmed that mise run build compiles cleanly.
  • The author has confirmed that mise run lint-fix passes.
  • The author has verified that mise plugins ls --outdated works with installed plugins.
  • The author has verified that the short flag mise plugins ls -o works.
  • An e2e test (test_plugins_outdated) was added and is passing.
  • The changes were generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@greptile-apps
Copy link

greptile-apps bot commented Feb 26, 2026

Greptile Summary

Added --outdated flag to mise plugins ls that compares local plugin SHAs with remote refs via git ls-remote and displays a formatted table of outdated plugins

  • Implemented remote_sha() method in git utilities and plugin trait
  • Added proper error handling with warnings for failed git operations
  • Includes e2e test coverage for basic functionality
  • One critical bug: unsafe string slicing at line 113 could panic if remote SHA is unexpectedly short

Confidence Score: 3/5

  • Generally safe with good error handling, but contains one unsafe string slicing bug
  • The implementation is well-structured with proper error handling throughout, but the unsafe string slicing at line 113 in src/cli/plugins/ls.rs could cause a runtime panic. This needs to be fixed before merging to prevent potential crashes.
  • Pay close attention to src/cli/plugins/ls.rs line 113 - the unsafe string slicing needs to be fixed

Important Files Changed

Filename Overview
src/cli/plugins/ls.rs Added --outdated flag with table output; contains unsafe string slicing that could panic
src/git.rs Added remote_sha() method using git ls-remote; implementation is clean and safe
e2e/plugins/test_plugins_outdated Basic e2e test covering fresh install and flag variations; could test more edge cases

Sequence Diagram

sequenceDiagram
    participant User
    participant PluginsLs
    participant Plugin
    participant Git
    participant Remote

    User->>PluginsLs: mise plugins ls --outdated
    PluginsLs->>PluginsLs: Filter installed plugins
    loop For each installed plugin
        PluginsLs->>Plugin: current_sha_short()
        Plugin->>Git: current_sha_short()
        Git-->>Plugin: local SHA (7 chars)
        Plugin-->>PluginsLs: local SHA
        
        PluginsLs->>Plugin: remote_sha()
        Plugin->>Git: current_branch()
        Git-->>Plugin: branch name
        Plugin->>Git: remote_sha(branch)
        Git->>Remote: git ls-remote origin <branch>
        Remote-->>Git: remote SHA (40 chars)
        Git-->>Plugin: remote SHA
        Plugin-->>PluginsLs: remote SHA
        
        alt Remote SHA starts with local SHA
            PluginsLs->>PluginsLs: Filter out (up to date)
        else Different SHAs
            PluginsLs->>Plugin: get_remote_url()
            Plugin-->>PluginsLs: URL
            PluginsLs->>Plugin: current_abbrev_ref()
            Plugin-->>PluginsLs: ref
            PluginsLs->>PluginsLs: Add to outdated list
        end
    end
    
    alt No outdated plugins
        PluginsLs->>User: "All plugins are up to date"
    else Has outdated plugins
        PluginsLs->>User: Display table with plugin info
    end
Loading

Fix All in Claude Code

Last reviewed commit: d50cf53

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

url: remote_url.unwrap_or_default(),
ref_: abbrev_ref.unwrap_or_default(),
local: local_sha,
remote: remote_sha[..7].to_string(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe string slicing could panic if remote_sha is less than 7 characters

Suggested change
remote: remote_sha[..7].to_string(),
remote: remote_sha.chars().take(7).collect(),

Fix in Claude Code

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new --outdated flag to mise plugins ls for checking outdated plugins. The implementation correctly uses git ls-remote to compare local and remote SHAs. The changes are well-structured across the plugin system.

My review includes two main points:

  1. The e2e test for this new feature could be improved by adding a case that verifies the detection of an outdated plugin.
  2. The implementation for checking outdated plugins performs blocking I/O sequentially in an async context. I've suggested a refactoring to parallelize these checks using tokio::task::spawn_blocking for better performance and to avoid blocking the async runtime.

Overall, this is a great feature addition. Addressing these points will make it more robust and performant.

Comment on lines +82 to +123
if self.outdated {
let data = plugins
.into_iter()
.filter_map(|(name, p)| {
if !p.is_installed() {
return None;
}
let local_sha = p.current_sha_short().unwrap_or_else(|e| {
warn!("{name}: {e:?}");
None
})?;
let remote_sha = p.remote_sha().unwrap_or_else(|e| {
warn!("{name}: {e:?}");
None
})?;
if remote_sha.starts_with(&local_sha) {
return None;
}
let remote_url = p.get_remote_url().unwrap_or_else(|e| {
warn!("{name}: {e:?}");
None
});
let abbrev_ref = p.current_abbrev_ref().unwrap_or_else(|e| {
warn!("{name}: {e:?}");
None
});
Some(OutdatedRow {
plugin: name,
url: remote_url.unwrap_or_default(),
ref_: abbrev_ref.unwrap_or_default(),
local: local_sha,
remote: remote_sha[..7].to_string(),
})
})
.collect::<Vec<_>>();
if data.is_empty() {
info!("All plugins are up to date");
} else {
let mut table = Table::new(data);
table::default_style(&mut table, false);
miseprintln!("{table}");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation for checking outdated plugins is sequential and uses blocking I/O within an async function. This can be inefficient, especially with many plugins, and it blocks the Tokio runtime worker thread. To improve performance and follow async best practices, these checks should be parallelized and blocking calls should be moved to a blocking thread pool using tokio::task::spawn_blocking.

        if self.outdated {
            let tasks: Vec<_> = plugins
                .into_iter()
                .map(|(name, p)| {
                    tokio::spawn(async move {
                        if !p.is_installed() {
                            return None;
                        }
                        let p_task = p.clone();
                        let name_task = name.clone();
                        tokio::task::spawn_blocking(move || {
                            let local_sha = p_task.current_sha_short().unwrap_or_else(|e| {
                                warn!("{}: {e:?}", name_task);
                                None
                            })?;
                            let remote_sha = p_task.remote_sha().unwrap_or_else(|e| {
                                warn!("{}: {e:?}", name_task);
                                None
                            })?;
                            if remote_sha.starts_with(&local_sha) {
                                return None;
                            }
                            let remote_url = p_task.get_remote_url().unwrap_or_else(|e| {
                                warn!("{}: {e:?}", name_task);
                                None
                            });
                            let abbrev_ref = p_task.current_abbrev_ref().unwrap_or_else(|e| {
                                warn!("{}: {e:?}", name_task);
                                None
                            });
                            Some(OutdatedRow {
                                plugin: name_task,
                                url: remote_url.unwrap_or_default(),
                                ref_: abbrev_ref.unwrap_or_default(),
                                local: local_sha,
                                remote: remote_sha[..7].to_string(),
                            })
                        })
                        .await
                        .ok()
                        .flatten()
                    })
                })
                .collect();

            let data = futures::future::join_all(tasks)
                .await
                .into_iter()
                .filter_map(|r| r.ok().flatten())
                .collect::<Vec<_>>();

            if data.is_empty() {
                info!("All plugins are up to date");
            } else {
                let mut table = Table::new(data);
                table::default_style(&mut table, false);
                miseprintln!("{table}");
            }

Comment on lines +1 to +12
#!/usr/bin/env bash

mise plugin install tiny https://github.com/mise-plugins/rtx-tiny.git

# freshly installed plugin should not appear as outdated
assert_not_contains "mise plugins ls --outdated" "tiny"

# verify --outdated flag works without error
assert_succeed "mise plugins ls --outdated"

# verify -o short flag works
assert_succeed "mise plugins ls -o"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test only covers the case where no plugins are outdated. It would be more robust to also test the case where a plugin is outdated. This could be done by installing a plugin, then using git reset in the plugin's directory to move it to an older commit, and then asserting that mise plugins ls --outdated lists the plugin.

#!/usr/bin/env bash

# setup
mise plugin install tiny https://github.com/mise-plugins/rtx-tiny.git

# freshly installed plugin should not appear as outdated
assert_not_contains "mise plugins ls --outdated" "tiny"

# verify --outdated flag works without error
assert_succeed "mise plugins ls --outdated"

# verify -o short flag works
assert_succeed "mise plugins ls -o"

# verify outdated detection works
(cd "$MISE_DATA_DIR/plugins/tiny" && git reset --hard HEAD~1)
assert_contains "mise plugins ls --outdated" "tiny"

autofix-ci bot and others added 4 commits February 26, 2026 11:38
- Reorder struct fields so short flags are alphabetical (-a, -c, -o, -u)
  fixing the test_subcommands_are_sorted test
- Use .chars().take(7).collect() instead of [..7] to avoid potential panic
- Add outdated detection test case using git reset HEAD~1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use parallel::parallel with spawn_blocking to check all plugins
concurrently instead of sequentially, improving performance when
many plugins are installed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.21 x -- echo 27.6 ± 0.4 27.0 31.9 1.00
mise x -- echo 28.1 ± 0.9 27.0 35.3 1.02 ± 0.04

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.21 env 27.7 ± 0.5 26.6 30.1 1.00
mise env 28.0 ± 1.4 26.8 56.9 1.01 ± 0.05

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.21 hook-env 28.1 ± 0.5 27.1 30.7 1.01 ± 0.03
mise hook-env 27.8 ± 0.7 27.0 39.2 1.00

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.21 ls 25.9 ± 0.3 25.4 27.2 1.00 ± 0.01
mise ls 25.9 ± 0.3 25.3 27.6 1.00

xtasks/test/perf

Command mise-2026.2.21 mise Variance
install (cached) 169ms 167ms +1%
ls (cached) 95ms 94ms +1%
bin-paths (cached) 101ms 100ms +1%
task-ls (cached) 836ms 840ms +0%

@jdx jdx changed the title feat(completions): add --outdated flag to mise plugins ls feat: add --outdated flag to mise plugins ls Feb 26, 2026
@jdx jdx merged commit c7316db into main Feb 26, 2026
37 checks passed
@jdx jdx deleted the feat/plugins-ls-outdated branch February 26, 2026 13:01
mise-en-dev added a commit that referenced this pull request Feb 27, 2026
### 🚀 Features

- add `--outdated` flag to `mise plugins ls` by @jdx in
[#8360](#8360)

### 🐛 Bug Fixes

- **(github)** resolve rename_exe search dir for archives with bin/
subdirectory by @jdx in [#8358](#8358)
- **(install)** skip tools=true env directives during backend
installation by @jdx in [#8356](#8356)
- **(ruby)** resolve correct Windows checksums in lockfile by @jdx in
[#8357](#8357)

### 📦 Registry

- switch terradozer backend to github fork by @chenrui333 in
[#8365](#8365)

### Chore

- **(release)** fix duplicated version prefix in release title by @jdx
in [#8359](#8359)

### New Contributors

- @chenrui333 made their first contribution in
[#8365](#8365)

## 📦 Aqua Registry Updates

#### New Packages (1)

- [`huseyinbabal/taws`](https://github.com/huseyinbabal/taws)

#### Updated Packages (2)

- [`block/goose`](https://github.com/block/goose)
- [`pre-commit/pre-commit`](https://github.com/pre-commit/pre-commit)
risu729 pushed a commit to risu729/mise that referenced this pull request Feb 27, 2026
## Summary
- Adds `--outdated` (`-o`) flag to `mise plugins ls` that checks remote
refs via `git ls-remote` and only displays plugins where the local SHA
differs from the remote
- Shows a table with plugin name, URL, ref, local SHA, and remote SHA
columns
- Prints "All plugins are up to date" when no plugins are outdated

Closes jdx#8319

## Test plan
- [x] `mise run build` compiles cleanly
- [x] `mise run lint-fix` passes
- [x] `mise plugins ls --outdated` works with installed plugins
- [x] `mise plugins ls -o` short flag works
- [x] e2e test added and passing (`test_plugins_outdated`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds remote git lookups to a common CLI command path, which can
introduce latency, network-related failures, and output behavior changes
despite being behind a new flag.
> 
> **Overview**
> Adds `-o/--outdated` to `mise plugins ls` to show only plugins with
available updates by comparing local vs remote git SHAs and printing a
table of outdated entries (or logging that everything is up to date).
> 
> Implements plugin remote SHA fetching via a new `Git::remote_sha()`
helper and a `Plugin::remote_sha()` hook (implemented for asdf/vfox
plugins), runs checks in parallel, and updates generated
docs/completions/manpage plus an e2e test covering the new flag.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b61ac6e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
risu729 pushed a commit to risu729/mise that referenced this pull request Feb 27, 2026
### 🚀 Features

- add `--outdated` flag to `mise plugins ls` by @jdx in
[jdx#8360](jdx#8360)

### 🐛 Bug Fixes

- **(github)** resolve rename_exe search dir for archives with bin/
subdirectory by @jdx in [jdx#8358](jdx#8358)
- **(install)** skip tools=true env directives during backend
installation by @jdx in [jdx#8356](jdx#8356)
- **(ruby)** resolve correct Windows checksums in lockfile by @jdx in
[jdx#8357](jdx#8357)

### 📦 Registry

- switch terradozer backend to github fork by @chenrui333 in
[jdx#8365](jdx#8365)

### Chore

- **(release)** fix duplicated version prefix in release title by @jdx
in [jdx#8359](jdx#8359)

### New Contributors

- @chenrui333 made their first contribution in
[jdx#8365](jdx#8365)

## 📦 Aqua Registry Updates

#### New Packages (1)

- [`huseyinbabal/taws`](https://github.com/huseyinbabal/taws)

#### Updated Packages (2)

- [`block/goose`](https://github.com/block/goose)
- [`pre-commit/pre-commit`](https://github.com/pre-commit/pre-commit)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant