Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI completions #265

Merged
merged 56 commits into from
Jan 29, 2025
Merged

Add CLI completions #265

merged 56 commits into from
Jan 29, 2025

Conversation

iesahin
Copy link
Owner

@iesahin iesahin commented Jan 15, 2025

  • Upgraded and set version to 0.6.14-alpha.1
  • Added completions to xvc-test-helper
  • Added completions for xvc
  • Bump version
  • Removed test_aliases and added test_completions
  • Add xvc completions page to ref
  • Add aliases to xvc file
  • add aliases to top commands
  • Bump version
  • Testing dynamic completions
  • bump version
  • add list references and list branches functions
  • add gix errors
  • make git module public
  • Add dynamic completions ✅ and cleaned up duplicate code
  • add cli handling errors
  • changes for dynamic completions
  • moved all completion related functions to completions
  • Bump version
  • Adding completers

Description by Callstackai

This PR introduces CLI completions for various commands, enhances error handling, and updates dependencies to version 0.6.14-alpha.8.

Diagrams of code changes
sequenceDiagram
    participant User
    participant Shell
    participant XvcCLI
    participant Completer
    participant Store
    participant Git

    User->>Shell: Type partial command
    Shell->>XvcCLI: Request completions
    
    alt Command Completion
        XvcCLI->>Completer: Get command completions
        Completer-->>XvcCLI: Return available commands/options
    else Dynamic Path Completion
        XvcCLI->>Store: Load store for completion
        Store-->>XvcCLI: Return stored paths
        XvcCLI->>Completer: Filter paths by prefix
        Completer-->>XvcCLI: Return matching paths
    else Git Reference Completion
        XvcCLI->>Git: List references/branches
        Git-->>XvcCLI: Return git refs
        XvcCLI->>Completer: Filter by prefix
        Completer-->>XvcCLI: Return matching refs
    end

    XvcCLI-->>Shell: Return completion candidates
    Shell-->>User: Show completion suggestions
Loading
Files Changed
FileSummary
CHANGELOG.mdUpdated changelog for version 0.6.14-alpha.8 with new features and improvements.
book/src/SUMMARY.mdAdded a reference to the new completions page.
book/src/ref/xvc-completions.mdCreated a new documentation page for xvc completions.
core/src/error.rsAdded new error types for Gix repository handling.
core/src/util/completer.rsImplemented completion helpers for Git references and branches.
lib/src/main.rsAdded handling for shell completions in the main entry point.
lib/tests/test_completions.rsAdded tests for the new completion functionality.

This PR includes files in programming languages that we currently do not support. We have not reviewed files with the extensions .md, .toml, .zsh. See list of supported languages.

Copy link

netlify bot commented Jan 15, 2025

Deploy Preview for xvc canceled.

Name Link
🔨 Latest commit b58cda1
🔍 Latest deploy log https://app.netlify.com/sites/xvc/deploys/6799f048337f5b0009338c15

Copy link

@callstackai callstackai bot left a comment

Choose a reason for hiding this comment

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

Key Issues

The PR review highlights the need for proper error handling to prevent silent failures, particularly with the use of unwrap() and unwrap_or_else, which can lead to panics or data corruption. Additionally, converting OsStr to string using to_string_lossy() may result in data loss with invalid Unicode sequences, and the code should implement functionality for setting a default pipeline instead of returning a Todo error.

Comment on lines +312 to +321
ref_platform.all().map(|all| {
all.for_each(|reference| {
if let Ok(reference) = reference {
if let Some((_, name)) = reference.name().category_and_short_name() {
refs.push(name.to_string());
}
}
});
Ok(refs)
})?
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
The map closure silently ignores errors from reference and category_and_short_name(). This could hide important errors and lead to incomplete results. The closure should properly handle and propagate these errors using the ? operator.

Suggested change
ref_platform.all().map(|all| {
all.for_each(|reference| {
if let Ok(reference) = reference {
if let Some((_, name)) = reference.name().category_and_short_name() {
refs.push(name.to_string());
}
}
});
Ok(refs)
})?
ref_platform.all().map(|all| {
all.try_for_each(|reference| -> Result<()> {
let reference = reference?;
if let Some((_, name)) = reference.name().category_and_short_name() {
refs.push(name.to_string());
}
Ok(())
})?;
Ok(refs)
})?

/// Return completions for all Git branches starting with `current` in the current directory
/// Used in `--to-branch` option
pub fn git_branch_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
let current = current.to_string_lossy();
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
Converting OsStr to string using to_string_lossy() could lead to data loss or unexpected behavior with invalid Unicode sequences. Consider handling non-Unicode paths explicitly.

Suggested change
let current = current.to_string_lossy();
let current = match current.to_str() {
Some(s) => s.to_string(),
None => return Vec::new(), // or handle invalid Unicode explicitly
};

@@ -194,17 +205,16 @@ pub fn cmd_pipeline<R: BufRead>(
// This should already be filled from the conf if not given
let pipeline_name = command.pipeline_name.unwrap();
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
Using unwrap() on pipeline_name can cause a panic if the value is None. Even though there's a comment suggesting it should be filled from config, it's better to handle the None case explicitly with proper error handling.

Suggested change
let pipeline_name = command.pipeline_name.unwrap();
let pipeline_name = command.pipeline_name.ok_or_else(|| anyhow::anyhow!("Pipeline name is required but not provided"))?

Copy link

@callstackai callstackai bot left a comment

Choose a reason for hiding this comment

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

Key Issues

The PR review highlights critical issues including potential data loss from improper OsStr to str conversion, unhandled feature flags leading to compile-time or runtime errors, and insufficient error handling in file system operations that could cause program crashes or partial initialization.

/// Return all step names starting with `prefix`
pub fn step_name_completer(prefix: &OsStr) -> Vec<CompletionCandidate> {
// This must be safe as we don't allow Non-UTF-8 strings for storage identifiers
let prefix = prefix.to_str().unwrap_or("");
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
Using unwrap_or("") on OsStr to str conversion could lead to data loss or incorrect behavior if the prefix contains non-UTF8 characters. Since this is used for name matching, it could cause valid matches to be missed.

Suggested change
let prefix = prefix.to_str().unwrap_or("");
let prefix = prefix.to_str().unwrap_or_else(|| {
log::warn!("Non-UTF8 prefix received: {:?}", prefix);
""
});

Comment on lines +118 to +134
match self {
XvcStorage::Local(lr) => lr.send(output, xvc_root, paths, force),
XvcStorage::Generic(gr) => gr.send(output, xvc_root, paths, force),
XvcStorage::Rsync(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "s3")]
XvcStorage::S3(s3r) => s3r.send(output, xvc_root, paths, force),
#[cfg(feature = "minio")]
XvcStorage::Minio(mr) => mr.send(output, xvc_root, paths, force),
#[cfg(feature = "r2")]
XvcStorage::R2(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "gcs")]
XvcStorage::Gcs(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "wasabi")]
XvcStorage::Wasabi(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "digital-ocean")]
XvcStorage::DigitalOcean(r) => r.send(output, xvc_root, paths, force),
}
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
The code doesn't handle the case where a storage type is used but its feature flag is not enabled. This could lead to compile-time errors or runtime issues if the code tries to use a storage type that isn't available.

Suggested change
match self {
XvcStorage::Local(lr) => lr.send(output, xvc_root, paths, force),
XvcStorage::Generic(gr) => gr.send(output, xvc_root, paths, force),
XvcStorage::Rsync(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "s3")]
XvcStorage::S3(s3r) => s3r.send(output, xvc_root, paths, force),
#[cfg(feature = "minio")]
XvcStorage::Minio(mr) => mr.send(output, xvc_root, paths, force),
#[cfg(feature = "r2")]
XvcStorage::R2(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "gcs")]
XvcStorage::Gcs(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "wasabi")]
XvcStorage::Wasabi(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "digital-ocean")]
XvcStorage::DigitalOcean(r) => r.send(output, xvc_root, paths, force),
}
match self {
XvcStorage::Local(lr) => lr.send(output, xvc_root, paths, force),
XvcStorage::Generic(gr) => gr.send(output, xvc_root, paths, force),
XvcStorage::Rsync(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "s3")]
XvcStorage::S3(s3r) => s3r.send(output, xvc_root, paths, force),
#[cfg(feature = "minio")]
XvcStorage::Minio(mr) => mr.send(output, xvc_root, paths, force),
#[cfg(feature = "r2")]
XvcStorage::R2(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "gcs")]
XvcStorage::Gcs(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "wasabi")]
XvcStorage::Wasabi(r) => r.send(output, xvc_root, paths, force),
#[cfg(feature = "digital-ocean")]
XvcStorage::DigitalOcean(r) => r.send(output, xvc_root, paths, force),
_ => Err(anyhow::anyhow!("Storage type not available. Enable the corresponding feature flag."))

Comment on lines +265 to +267
let abs_path = PathBuf::from(path)
.canonicalize()
.expect("Cannot canonicalize the path. Possible symlink loop.");
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
Using expect() on canonicalize() will cause the program to panic if there's a symlink loop or permission issues. This should be handled gracefully with proper error propagation.

Suggested change
let abs_path = PathBuf::from(path)
.canonicalize()
.expect("Cannot canonicalize the path. Possible symlink loop.");
let abs_path = PathBuf::from(path)
.canonicalize()
.map_err(|e| Error::PathCanonicalizeError { path: path.into(), source: e })?

Comment on lines 90 to 91
// FIXME: This doesn't consider current dir to filter the elements
let filtered = xvc_path_store.filter(|_, xp| xp.starts_with_str(&prefix));
Copy link

Choose a reason for hiding this comment

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

🐛 Possible Bug
The code collects all paths that start with the prefix without considering the current directory, which could lead to performance issues with large repositories and return irrelevant results. The FIXME comment acknowledges this, but the current implementation could cause confusion for users.

Suggested change
// FIXME: This doesn't consider current dir to filter the elements
let filtered = xvc_path_store.filter(|_, xp| xp.starts_with_str(&prefix));
let current_dir_str = current_dir.to_str().ok_or(Error::NonUtf8Path)?;
let filtered = xvc_path_store.filter(|_, xp| {
xp.starts_with_str(current_dir_str) && xp.starts_with_str(prefix)
});

Copy link

codecov bot commented Jan 29, 2025

Codecov Report

Attention: Patch coverage is 32.30769% with 264 lines in your changes missing coverage. Please review.

Project coverage is 71.21%. Comparing base (7022cc7) to head (b58cda1).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
core/src/util/completer.rs 0.00% 64 Missing ⚠️
storage/src/storage/common_ops.rs 41.23% 57 Missing ⚠️
storage/src/storage/mod.rs 0.00% 54 Missing ⚠️
core/src/util/git.rs 0.00% 36 Missing ⚠️
pipeline/src/pipeline/util.rs 0.00% 36 Missing ⚠️
test_helper/src/main.rs 0.00% 9 Missing ⚠️
lib/src/main.rs 80.00% 2 Missing ⚠️
pipeline/src/pipeline/deps/mod.rs 0.00% 2 Missing ⚠️
file/src/list/mod.rs 0.00% 1 Missing ⚠️
lib/src/init/mod.rs 0.00% 1 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #265      +/-   ##
==========================================
- Coverage   71.80%   71.21%   -0.59%     
==========================================
  Files         123      125       +2     
  Lines       14172    14313     +141     
==========================================
+ Hits        10176    10193      +17     
- Misses       3996     4120     +124     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@iesahin iesahin merged commit 9c1c6bb into main Jan 29, 2025
7 checks passed
@iesahin iesahin deleted the clap-complete-16608 branch January 29, 2025 09:13
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