Skip to content

Cli/bsdtar/positional cd#2902

Open
ChanTsune wants to merge 13 commits intomainfrom
cli/bsdtar/positional-cd
Open

Cli/bsdtar/positional cd#2902
ChanTsune wants to merge 13 commits intomainfrom
cli/bsdtar/positional-cd

Conversation

@ChanTsune
Copy link
Copy Markdown
Owner

@ChanTsune ChanTsune commented Apr 3, 2026

Summary by CodeRabbit

  • New Features

    • CLI now preprocesses -C/--cd/--directory forms so directory-change operands can be passed inline in many syntaxes.
  • Bug Fixes

    • Per-argument directory changes are applied correctly for listing/extraction.
    • Creation/append/update no longer change the global process working directory, yielding more consistent archive resolution.
  • Tests

    • Added tests for directory-change encodings and archive-resolution/ordering scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Command-line preprocessing now encodes bsdtar-style -C/--cd/--directory into a NUL-prefixed sentinel token; parsing converts sentinels into ItemSource::ChangeDir; collection and bsdtar commands apply per-operation directory changes instead of a global clap -C option.

Changes

Cohort / File(s) Summary
Argument Preprocessing
cli/src/cli.rs, cli/src/cli/old_style.rs
Added pub(crate) const CD_SENTINEL and encode_bsdtar_cd_args() to encode -C/--cd/--directory forms into sentinel-prefixed positional tokens; re-exported encode_bsdtar_cd_args publicly and CD_SENTINEL crate-visible; updated short-option tests to remove C from clap synch check.
Main Argument Pipeline
cli/src/main.rs
Inserted encode_bsdtar_cd_args() into the pre-parse pipeline after expand_bsdtar_old_style_args and expand_bsdtar_w_option.
ItemSource & Collection
cli/src/command/core.rs
Added ItemSource::ChangeDir(PathBuf) and parsing for sentinel tokens; collect_items_from_sources() applies env::set_current_dir() for ChangeDir entries; filesystem entries record fs_path = cwd.join(path) and archive file markers are resolved to absolute paths; create-entry and fs ops use fs_path.
Bsdtar Command
cli/src/command/bsdtar.rs
Removed clap -C/--cd field and global working_dir handling from create/append/update; create/update no longer change process CWD; extract/list partition file args by CD_SENTINEL, apply per-directive env::set_current_dir(), and use ItemSource→collect flow.
Windows glob & utils
cli/src/utils.rs, cli/src/utils/windows_glob.rs
Added #[allow(unused_imports)] to utils re-exports; added #[cfg_attr(not(windows), allow(dead_code))] to expand_bsdtar_windows_globs to suppress non-Windows warnings (behavior unchanged).
Tests
cli/tests/cli/stdio.rs, cli/tests/cli/stdio/option_cd.rs
Added option_cd test module with tests for -C behavior under experimental stdio (--unstable), covering @archive resolution ordering, multiple/alternating -C uses, -- passthrough, and helpers to create/read test archives.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Args
    participant Preproc as Preprocessor (main.rs)
    participant Parser as Clap Parser (cli.rs)
    participant Collector as Collector (core.rs)
    participant Executor as Bsdtar Cmd (bsdtar.rs)

    CLI->>Preproc: raw args (may include -C / --cd / --directory)
    Preproc->>Preproc: expand_bsdtar_old_style_args(), expand_bsdtar_w_option()
    Preproc->>Preproc: encode_bsdtar_cd_args() -> replace -C forms with "\0CD\0dir"
    Preproc->>Parser: transformed args
    Parser->>Collector: parsed command + args vector
    Collector->>Collector: ItemSource::parse() detects "\0CD\0" -> ChangeDir(dir)
    Collector->>Collector: collect_items_from_sources() applies env::set_current_dir(dir)
    Collector->>Executor: CollectedItems with fs_path-adjusted filesystem entries
    Executor->>Executor: perform extract/list/create using per-operation CWD contexts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble tokens, stitch "\0CD\0" in place,
Hopping through folders without a global chase.
Each sentinel hop sets the tiny stage,
Files find their roots, kept true to their page.
A rabbit's hop — per-file, gentle grace. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Cli/bsdtar/positional cd' is vague and uses non-descriptive formatting that doesn't clearly convey what the change accomplishes. Consider revising to a more descriptive title like 'Support -C directory changes as positional arguments in bsdtar mode' to clarify the main change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cli/bsdtar/positional-cd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
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 refactors the handling of the -C (change directory) option to support positional behavior, aligning it with standard bsdtar usage. It introduces a preprocessing step that encodes directory changes into sentinel-prefixed arguments, which are then processed during item collection to change the working directory dynamically. Feedback highlights a regression in the old-style argument parser where removing 'C' from the list of options with arguments breaks dashless command bundles. Additionally, the extraction logic contains a bug where all directory changes are applied sequentially before any files are processed, failing to maintain the positional relationship between the directory change and specific file patterns.

Comment thread cli/src/cli/old_style.rs Outdated
/// compression level via `Option<Option<XzLevel>>`.)
/// Kept in sync with `BsdtarCommand` clap definition via unit test.
const SHORT_OPTIONS_WITH_ARG: &[char] = &['b', 'C', 'f', 'I', 's', 'T', 'W', 'X'];
const SHORT_OPTIONS_WITH_ARG: &[char] = &['b', 'f', 'I', 's', 'T', 'W', 'X'];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Removing 'C' from SHORT_OPTIONS_WITH_ARG breaks old-style (dashless) bundles where -C is not the last option. For example, in pna compat bsdtar cCf dir archive.pna, the expand_bsdtar_old_style_args function will no longer recognize that C takes an argument, leading to incorrect argument association. It would produce -c -C -f archive.pna dir, and then encode_bsdtar_cd_args would incorrectly treat -f as the directory for -C instead of dir.

Suggested change
const SHORT_OPTIONS_WITH_ARG: &[char] = &['b', 'f', 'I', 's', 'T', 'W', 'X'];
const SHORT_OPTIONS_WITH_ARG: &[char] = &['b', 'C', 'f', 'I', 's', 'T', 'W', 'X'];

Comment thread cli/src/command/bsdtar.rs
Comment on lines +1121 to +1132
let (cd_dirs, file_patterns): (Vec<_>, Vec<_>) = files.into_iter().partition_map(|f| {
if let Some(dir) = f.strip_prefix(crate::cli::CD_SENTINEL) {
itertools::Either::Left(PathBuf::from(dir))
} else {
itertools::Either::Right(f)
}
});
for dir in &cd_dirs {
env::set_current_dir(dir)
.with_context(|| format!("changing directory to {}", dir.display()))?;
}
let files = file_patterns;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The extraction logic for -C is not positional. By partitioning all directory changes and applying them sequentially before processing any file patterns, the implementation effectively applies the final directory state to all files. In bsdtar, -C should only affect the files that follow it on the command line. For example, -xf archive.pna -C dir1 file1 -C dir2 file2 should extract file1 into dir1 and file2 into dir2, but this implementation will extract both into dir2 (or dir1/dir2 if the paths are relative).

@github-actions github-actions Bot added the cli This issue is about cli application label Apr 3, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/command/bsdtar.rs (1)

1508-1542: ⚠️ Potential issue | 🟠 Major

Reject @archive operands in update mode instead of dropping them.

The old collect_items_from_paths path here never produced archive markers, so @source.pna/@- would fail as unsupported input. This new flow parses them and then silently filters them out, which turns pna compat bsdtar -u -f target.pna @source.pna`` into a successful no-op.

💡 Suggested fix
     files = utils::expand_bsdtar_windows_globs(files)?;
     let sources = ItemSource::parse_many(&files);
     validate_no_duplicate_stdin(&sources)?;
@@
     };
     let mut resolver = HardlinkResolver::new(collect_options.follow_links);
     let collected = collect_items_from_sources(sources, &collect_options, &mut resolver)?;
-    let target_items = collected
-        .into_iter()
-        .filter_map(|item| match item {
-            crate::command::core::CollectedItem::Filesystem(entry) => Some(entry),
-            crate::command::core::CollectedItem::ArchiveMarker(_) => None,
-        })
-        .collect();
+    let mut target_items = Vec::new();
+    for item in collected {
+        match item {
+            crate::command::core::CollectedItem::Filesystem(entry) => target_items.push(entry),
+            crate::command::core::CollectedItem::ArchiveMarker(source) => {
+                return Err(io::Error::new(
+                    io::ErrorKind::InvalidInput,
+                    format!("update mode does not support archive inclusion source {source}"),
+                )
+                .into());
+            }
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/command/bsdtar.rs` around lines 1508 - 1542, The code currently
parses archive-style operands (e.g., `@source.pna`, `@-`) and ends up filtering out
CollectedItem::ArchiveMarker in the target_items collection, causing update mode
to silently no-op; instead detect and reject archive operands when running in
update mode. After ItemSource::parse_many(&files) (or immediately after
collect_items_from_sources), check for archive operands /
CollectedItem::ArchiveMarker and if args.update (update mode) is set return an
error rather than dropping them; reference the ItemSource/CollectedItem enum,
collect_items_from_sources, and the target_items filtering code and return a
clear user-facing error when ArchiveMarker is encountered in update mode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/cli/old_style.rs`:
- Around line 9-13: SHORT_OPTIONS_WITH_ARG currently omits 'C', causing
old-style expansion to treat a non-terminal C bundle (e.g. "cCf" or "cCv") as
separate flags and mis-assign operands; add 'C' back into the const
SHORT_OPTIONS_WITH_ARG (so old-style expansion will consume the next token as
C's operand) and add a regression test that asserts parsing/expansion for a
non-terminal C bundle like "cCf" or "cCv" yields the expected sequence (e.g. -c
-C <dir> -f <archive>) to prevent future regressions.

In `@cli/src/command/core.rs`:
- Around line 591-609: The current ItemSource::ChangeDir branch can yield zero
collected items and thus allow create-mode to proceed with no real inputs; after
the input-parsing/collection loop (the code handling ItemSource variants that
produces results and is consumed by run_create_archive), add a post-parse
validation that ensures at least one actual source was produced (i.e., not only
ChangeDir entries). Implement this by checking either that results is non-empty
or that the original ItemSource list contains at least one entry not matching
ItemSource::ChangeDir, and if the check fails return the same create-mode error
used by run_create_archive (an io::Error or the existing validation error) with
a clear message like “create requires at least one input path or `@archive`
source.” Ensure you update the same function that builds results so callers such
as run_create_archive never receive an empty actionable input set.

---

Outside diff comments:
In `@cli/src/command/bsdtar.rs`:
- Around line 1508-1542: The code currently parses archive-style operands (e.g.,
`@source.pna`, `@-`) and ends up filtering out CollectedItem::ArchiveMarker in the
target_items collection, causing update mode to silently no-op; instead detect
and reject archive operands when running in update mode. After
ItemSource::parse_many(&files) (or immediately after
collect_items_from_sources), check for archive operands /
CollectedItem::ArchiveMarker and if args.update (update mode) is set return an
error rather than dropping them; reference the ItemSource/CollectedItem enum,
collect_items_from_sources, and the target_items filtering code and return a
clear user-facing error when ArchiveMarker is encountered in update mode.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5fcf870c-8bc6-4429-b993-06537504dfd6

📥 Commits

Reviewing files that changed from the base of the PR and between ee32892 and 6171dec.

📒 Files selected for processing (7)
  • cli/src/cli.rs
  • cli/src/cli/old_style.rs
  • cli/src/command/bsdtar.rs
  • cli/src/command/core.rs
  • cli/src/main.rs
  • cli/tests/cli/stdio.rs
  • cli/tests/cli/stdio/option_cd.rs

Comment thread cli/src/cli/old_style.rs Outdated
Comment thread cli/src/command/core.rs
Comment on lines +591 to 609
ItemSource::ChangeDir(dir) => {
env::set_current_dir(&dir)
.map_err(|e| io::Error::new(e.kind(), format!("{}: {}", dir.display(), e)))?;
}
ItemSource::Filesystem(path) => {
let items = collect_items_with_state(&path, options, hardlink_resolver)?;
results.extend(items.into_iter().map(CollectedItem::Filesystem));
}
ItemSource::Archive(archive_source) => {
results.push(CollectedItem::ArchiveMarker(archive_source));
ItemSource::Archive(ArchiveSource::File(path)) => {
let abs = if path.is_relative() {
env::current_dir()?.join(&path)
} else {
path
};
results.push(CollectedItem::ArchiveMarker(ArchiveSource::File(abs)));
}
ItemSource::Archive(source @ ArchiveSource::Stdin) => {
results.push(CollectedItem::ArchiveMarker(source));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ChangeDir-only inputs now bypass the create-mode input check.

This branch can turn a non-empty source list into zero collected items. run_create_archive still validates inputs before parsing the sentinel stream, so pna compat bsdtar -c -f out.pna -C dir now slips past the existing “requires at least one input path or @archive source” check and reaches archive creation with no actionable inputs. Callers need a post-parse check that at least one source is not ChangeDir.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/command/core.rs` around lines 591 - 609, The current
ItemSource::ChangeDir branch can yield zero collected items and thus allow
create-mode to proceed with no real inputs; after the input-parsing/collection
loop (the code handling ItemSource variants that produces results and is
consumed by run_create_archive), add a post-parse validation that ensures at
least one actual source was produced (i.e., not only ChangeDir entries).
Implement this by checking either that results is non-empty or that the original
ItemSource list contains at least one entry not matching ItemSource::ChangeDir,
and if the check fails return the same create-mode error used by
run_create_archive (an io::Error or the existing validation error) with a clear
message like “create requires at least one input path or `@archive` source.”
Ensure you update the same function that builds results so callers such as
run_create_archive never receive an empty actionable input set.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/cli/old_style.rs`:
- Around line 203-207: The guard that rejects concatenated `-C` values beginning
with `-` is too strict: in the preprocessing branch that matches
`bytes.strip_prefix(b"-C")` you should allow `rest` values that start with `-`
(e.g. `-C-foo`) while still rejecting empty arguments; remove the
`!rest.starts_with(b"-")` check so the `if let Some(rest) =
bytes.strip_prefix(b"-C") && !rest.is_empty()` branch accepts `-C-...` forms and
continues to pass `rest` to the existing downstream handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9913c217-9301-4d85-a269-bf3dc41e8550

📥 Commits

Reviewing files that changed from the base of the PR and between 6171dec and d5477ab.

📒 Files selected for processing (1)
  • cli/src/cli/old_style.rs

Comment thread cli/src/cli/old_style.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cli/src/command/core.rs (1)

591-620: ⚠️ Potential issue | 🟠 Major

ChangeDir can still erase the entire actionable input set.

A source list like -C dir now becomes Ok(vec![]). Create-mode validation still happens before sentinel decoding, so this path can proceed with no real inputs unless you add a post-parse check here or in the create-mode caller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/command/core.rs` around lines 591 - 620, The ChangeDir branch
(ItemSource::ChangeDir) can leave the parsed sources producing no actionable
inputs (results empty), which lets create-mode continue without inputs; after
the loop that builds results (or immediately after handling
ItemSource::ChangeDir) add a validation that errors when running in create-mode
with an empty results set (or move/create the create-mode validation to run
after sentinel decoding so it sees the post-ChangeDir results). Reference
ItemSource::ChangeDir, the results Vec and the create-mode caller (or sentinel
decoding location) and ensure the code returns an Err when create-mode is
requested but results.is_empty().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/command/bsdtar.rs`:
- Around line 1505-1506: ItemSource::parse_many now produces ArchiveMarker
entries for `@foo/`@-, but later code's filter_map silently drops those and can
turn a supplied operand into a no-op (possibly leaving target_items empty); in
update mode you must reject ArchiveMarker operands instead of filtering them
out. After let sources = ItemSource::parse_many(&files); (and before the
filter_map that builds target_items) scan sources for any ArchiveMarker and
return an error when running in update mode (the same guard should be applied
around the code paths noted at lines ~1532-1539), so the code explicitly fails
on `@archive/`@- rather than silently ignoring them. Ensure the check uses the
same error reporting used elsewhere and references the ArchiveMarker variant
from ItemSource.

---

Duplicate comments:
In `@cli/src/command/core.rs`:
- Around line 591-620: The ChangeDir branch (ItemSource::ChangeDir) can leave
the parsed sources producing no actionable inputs (results empty), which lets
create-mode continue without inputs; after the loop that builds results (or
immediately after handling ItemSource::ChangeDir) add a validation that errors
when running in create-mode with an empty results set (or move/create the
create-mode validation to run after sentinel decoding so it sees the
post-ChangeDir results). Reference ItemSource::ChangeDir, the results Vec and
the create-mode caller (or sentinel decoding location) and ensure the code
returns an Err when create-mode is requested but results.is_empty().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a52e86f3-4db1-4601-912c-e1a8452a8888

📥 Commits

Reviewing files that changed from the base of the PR and between d5477ab and cc81d1d.

📒 Files selected for processing (2)
  • cli/src/command/bsdtar.rs
  • cli/src/command/core.rs

Comment thread cli/src/command/bsdtar.rs Outdated
Comment on lines +1505 to +1506
let sources = ItemSource::parse_many(&files);
validate_no_duplicate_stdin(&sources)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject @archive operands in update mode instead of dropping them.

ItemSource::parse_many now makes @foo / @- special here, but the later filter_map throws every ArchiveMarker away. That turns a user-supplied operand into a silent no-op and can leave target_items empty with a successful exit.

💡 Suggested guard
 let sources = ItemSource::parse_many(&files);
-validate_no_duplicate_stdin(&sources)?;
+if sources.iter().any(|s| matches!(s, ItemSource::Archive(_))) {
+    anyhow::bail!("update mode does not support `@archive` sources");
+}

Also applies to: 1532-1539

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/command/bsdtar.rs` around lines 1505 - 1506, ItemSource::parse_many
now produces ArchiveMarker entries for `@foo/`@-, but later code's filter_map
silently drops those and can turn a supplied operand into a no-op (possibly
leaving target_items empty); in update mode you must reject ArchiveMarker
operands instead of filtering them out. After let sources =
ItemSource::parse_many(&files); (and before the filter_map that builds
target_items) scan sources for any ArchiveMarker and return an error when
running in update mode (the same guard should be applied around the code paths
noted at lines ~1532-1539), so the code explicitly fails on `@archive/`@- rather
than silently ignoring them. Ensure the check uses the same error reporting used
elsewhere and references the ArchiveMarker variant from ItemSource.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
cli/src/cli/old_style.rs (1)

203-207: ⚠️ Potential issue | 🟡 Minor

Allow concatenated -C values that begin with -.

Line 206 still rejects -C-foo, which is a valid short-option-with-argument spelling and can now leak to clap unencoded.

💡 Proposed fix
-        if let Some(rest) = bytes.strip_prefix(b"-C")
-            && !rest.is_empty()
-            && !rest.starts_with(b"-")
+        if let Some(rest) = bytes.strip_prefix(b"-C")
+            && !rest.is_empty()
         {
             let mut sentinel = OsString::from(CD_SENTINEL);
             sentinel.push(unsafe { std::ffi::OsStr::from_encoded_bytes_unchecked(rest) });
             result.push(sentinel);
             continue;
         }
+    #[test]
+    fn encode_cd_concatenated_dash_prefixed_value() {
+        let args = s(&["pna", "compat", "bsdtar", "-C-foo", "file"]);
+        let result = encode_bsdtar_cd_args(args);
+        assert_eq!(result, s(&["pna", "compat", "bsdtar", "\0CD\0-foo", "file"]));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/cli/old_style.rs` around lines 203 - 207, The condition that rejects
concatenated `-C` arguments starting with `-` is too strict; in the `if let
Some(rest) = bytes.strip_prefix(b"-C") && !rest.is_empty() &&
!rest.starts_with(b"-")` check remove the `!rest.starts_with(b"-")` clause so
that `rest` (the concatenated value from `bytes.strip_prefix`) is accepted even
when it begins with `-`; leave the `strip_prefix` and `!rest.is_empty()` checks
intact and adjust any downstream handling expecting `rest` accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cli/src/cli/old_style.rs`:
- Around line 203-207: The condition that rejects concatenated `-C` arguments
starting with `-` is too strict; in the `if let Some(rest) =
bytes.strip_prefix(b"-C") && !rest.is_empty() && !rest.starts_with(b"-")` check
remove the `!rest.starts_with(b"-")` clause so that `rest` (the concatenated
value from `bytes.strip_prefix`) is accepted even when it begins with `-`; leave
the `strip_prefix` and `!rest.is_empty()` checks intact and adjust any
downstream handling expecting `rest` accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff8ca54f-82a0-4a95-94d7-161137446a8e

📥 Commits

Reviewing files that changed from the base of the PR and between cc81d1d and 39257f8.

📒 Files selected for processing (4)
  • cli/src/cli/old_style.rs
  • cli/src/command/core.rs
  • cli/src/utils.rs
  • cli/src/utils/windows_glob.rs
✅ Files skipped from review due to trivial changes (3)
  • cli/src/utils.rs
  • cli/src/utils/windows_glob.rs
  • cli/src/command/core.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
cli/src/cli/old_style.rs (1)

207-210: ⚠️ Potential issue | 🟡 Minor

Allow concatenated -C values beginning with -.

Line 209 blocks valid forms like -C-foo, so they won’t be sentinel-encoded and can fail later now that -C is preprocessing-only. Please remove that guard and add a regression test for -C-foo.

💡 Proposed fix
-        if let Some(rest) = bytes.strip_prefix(b"-C")
-            && !rest.is_empty()
-            && !rest.starts_with(b"-")
+        if let Some(rest) = bytes.strip_prefix(b"-C")
+            && !rest.is_empty()
         {
             result.push(make_cd_sentinel(unsafe {
                 std::ffi::OsStr::from_encoded_bytes_unchecked(rest)
             }));
             continue;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/cli/old_style.rs` around lines 207 - 210, The conditional in the
bytes parsing branch that checks rest.starts_with(b"-") incorrectly rejects
valid concatenated -C values like "-C-foo"; update the if condition that
currently reads if let Some(rest) = bytes.strip_prefix(b"-C") &&
!rest.is_empty() && !rest.starts_with(b"-") { to drop the
!rest.starts_with(b"-") guard so any non-empty rest is accepted, keep the
rest.empty() check, and add a regression test exercising the parser with
"-C-foo" to ensure it is sentinel-encoded/preprocessed as expected (refer to the
bytes.strip_prefix(b"-C") handling and the rest variable in old_style.rs).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cli/src/cli/old_style.rs`:
- Around line 207-210: The conditional in the bytes parsing branch that checks
rest.starts_with(b"-") incorrectly rejects valid concatenated -C values like
"-C-foo"; update the if condition that currently reads if let Some(rest) =
bytes.strip_prefix(b"-C") && !rest.is_empty() && !rest.starts_with(b"-") { to
drop the !rest.starts_with(b"-") guard so any non-empty rest is accepted, keep
the rest.empty() check, and add a regression test exercising the parser with
"-C-foo" to ensure it is sentinel-encoded/preprocessed as expected (refer to the
bytes.strip_prefix(b"-C") handling and the rest variable in old_style.rs).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23a117ff-6d3f-4d86-993b-0c9921ce51b1

📥 Commits

Reviewing files that changed from the base of the PR and between 39257f8 and 429f936.

📒 Files selected for processing (2)
  • cli/src/cli/old_style.rs
  • cli/src/command/core.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/src/command/core.rs

ChanTsune added 13 commits April 5, 2026 12:00
Encode -C dir as \0CD\0dir sentinel in the pre-processing pipeline, then
consume it positionally via ItemSource::ChangeDir in collect_items_from_sources.

- Remove working_dir: Option<PathBuf> from BsdtarCommand
- create/append: ChangeDir handled inline during item collection
- update: switch from collect_items_from_paths to collect_items_from_sources
- extract/list: strip sentinel values and apply as global chdir
- Fix collapsible-if clippy lint in write_from_path
Without 'C' in SHORT_OPTIONS_WITH_ARG, old-style bundles like cCf would
misparse: -C would not consume its directory argument during old-style
expansion, causing subsequent flags to shift. 'C' is now handled the
same way as 'W' — listed in SHORT_OPTIONS_WITH_ARG for old-style
expansion but excluded from the clap sync assertion since it is consumed
by encode_bsdtar_cd_args preprocessing.
Previously, expand_bsdtar_windows_globs ran before ItemSource parsing,
which meant glob patterns were expanded relative to the original cwd
rather than the directory set by a preceding -C. This caused all
windows_glob tests to fail on Windows CI.

Now glob expansion happens per-item inside collect_items_from_sources,
after any ChangeDir has taken effect, matching bsdtar's behavior where
glob expansion occurs relative to the most recent -C directory.
- Use #[cfg(windows)] to skip expand_bsdtar_windows_globs on non-Windows,
  avoiding to_string_lossy round-trip that could corrupt non-UTF-8 paths
- Use format!("{e:#}") instead of e.to_string() to preserve full error chain
- Add pipeline composition test for cCf old-style expansion
- Extract make_cd_sentinel helper in encode_bsdtar_cd_args (5 sites → 1)
- Unify #[cfg(windows)] / #[cfg(not(windows))] into shared loop
- Add doc comments to CollectedEntry path/fs_path fields
…ard in update mode

- Create mode: check for real input paths after filtering -C sentinels,
  so `pna compat bsdtar -cf out.pna -C dir` correctly errors instead of
  creating an empty archive
- Update mode: use parse_many_no_archives to treat @ as filesystem paths,
  preventing silent discard of @archive operands that update cannot handle
- Add ItemSource::parse_with and parse_many_no_archives for modes that
  should not recognize @archive syntax
…xtract/update/append

Replace opaque boolean in ItemSource::parse_with with ParseMode enum
for clarity at call sites. Add CollectedEntry::new() constructor to
centralize the fs_path = cwd.join(path) invariant. Add integration
tests verifying -C works correctly in extract, update, and append
modes.
Replace local create_test_archive and get_archive_entry_names with
the shared implementations from utils/archive.
@ChanTsune ChanTsune force-pushed the cli/bsdtar/positional-cd branch from f82a5dc to 74c987b Compare April 5, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli This issue is about cli application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant