Skip to content

fix(mkdir): don't report an existing directory as created in --verbose - #19017

Merged
fdncred merged 1 commit into
nushell:mainfrom
Developer1010x:mkdir-verbose-existing-directory
Sep 13, 2026
Merged

fdncred merged 1 commit into
nushell:mainfrom
Developer1010x:mkdir-verbose-existing-directory

Conversation

@Developer1010x

@Developer1010x Developer1010x commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Description

mkdir --verbose reports created: true for directories that already existed.

mkdir builds its uutils config with recursive: true (the IS_RECURSIVE
constant), and in that mode uu_mkdir returns Ok(()) for a path that is
already there rather than raising "File exists" — that check is guarded by
!config.recursive. The verbose branch treated every non-error as a creation,
so an existing directory came back as created: true.

This records whether the path existed before the call and reports that instead:

> mkdir foo
> mkdir -v foo
╭───┬──────────────┬─────────┬───────╮
│ # │     path     │ created │ error │
├───┼──────────────┼─────────┼───────┤
│ 0 │ /tmp/xxxx/foo│ false   │       │
╰───┴──────────────┴─────────┴───────╯

Previously that same row said created: true.

Scope: this touches only the created column. Whether mkdir should fail on
an existing directory is the open question in #19003, and I have deliberately
not answered it here — that one is a behavior change worth deciding separately,
since mkdir in Nushell is always mkdir -p and POSIX has -p suppress the
error. The created field is wrong under any answer to it, because nothing was
created either way.

User-facing changes (Release notes)

mkdir --verbose no longer reports existing directories as newly created

Fixed mkdir --verbose reporting created: true for directories that already
existed. Such rows now report created: false. Directories that are actually
created are unaffected.

Tests + Formatting

Two tests in crates/nu-command/tests/commands/umkdir.rs:

  • verbose_does_not_report_existing_directory_as_created
  • verbose_reports_newly_created_directory_as_created — the control, so the
    first cannot pass by inverting the field

I checked these are not vacuous: reverting the one-line change makes the first
fail while the control still passes. The other 15 existing umkdir tests pass
unchanged.

cargo fmt --all -- --check and cargo clippy --all-targets are clean.

Additional notes

Related to #19003 — this fixes one part of that report and deliberately leaves
the exit-code question open. See my comment there for the trade-offs.

`mkdir` passes `recursive: true` to uutils, which makes it succeed silently
when the path is already there. The verbose output did not account for that and
reported `created: true` for every path it did not fail on, including ones that
already existed before the call.

Record whether the path existed before calling `mkdir` and report that instead.

This does not change the exit code or the error behavior discussed in nushell#19003 —
whether `mkdir` should fail on an existing directory is a separate decision. The
`created` column is wrong under any answer to it, since nothing was created.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@fdncred
fdncred merged commit b6e6562 into nushell:main Sep 13, 2026
15 checks passed
@fdncred

fdncred commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Thanks

@github-actions github-actions Bot added this to the v0.116.0 milestone Sep 13, 2026
fdncred pushed a commit that referenced this pull request Sep 14, 2026
…19023)

## Description

`help mkdir` shows this for the `--verbose` example:

| path | created | error |
| --- | --- | --- |
| foo/bar | true | |
| foo2 | true | |

`mkdir` never produces that. Every argument goes through
`nu_path::expand_path_with(..)` before it reaches the output, so the
reported
paths are absolute:

```
> mkdir -v foo/bar foo2 | get 0.path
/tmp/.tmpHDMTuH/mkdir_verbose_absolute/foo/bar
```

Since the value depends on the working directory, it cannot be written
down as
a literal at all — so this drops the declared `result`, the way the
first
example in the same command already does, and says in the description
that the
paths are absolute.

Why it went unnoticed: `umkdir.rs` has no `test_examples()`, unlike the
commands
in `crates/nu-command/src/path/`, so the declared result was never
compared
against what the command actually returns.

## User-facing changes (Release notes)

n/a — documentation only. The `help mkdir` example no longer shows
output that
the command cannot produce.

## Tests + Formatting

Added `verbose_reports_the_expanded_path`, which asserts the reported
path
equals the expanded path rather than the argument as typed. It is not
vacuous:
pointing it at the value the example used to claim produces

```
assertion `left == right` failed
  left: "foo/bar"
 right: "/tmp/.tmpHDMTuH/mkdir_verbose_absolute/foo/bar"
```

which is the bug this PR fixes. All 18 `umkdir` tests pass;
`cargo fmt --all -- --check` and `cargo clippy --all-targets` are clean.

I did not add `test_examples()` for this command: its examples create
directories as a side effect, which is presumably why it has none.

## Additional notes

Follow-up to #19017, same command, unrelated bug.

Co-authored-by: Prajwal Narayana <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
fdncred pushed a commit that referenced this pull request Sep 14, 2026
…9024)

## Description

`view source` renders a rest parameter without a space after the colon,
unlike
every other parameter kind:

```nu
> def test [a?:any --b:int ...rest:string] { echo 'test' }; view source test
def test [ a?: any --b: int ...rest:string] { echo 'test' }
```

`a?: any` and `--b: int` are spaced; `...rest:string` is not. In
`render_def`
the branches are:

| kind | format |
| --- | --- |
| required positional | `"{}: {} "` |
| optional positional | `"{}?: {} "` |
| flag with an argument | `": {arg}"` |
| rest | `"...{}:{}"` |

The rest branch is the only one missing the space. The command's own
examples
already show `...rest: string`, in two of them, so the documented form
was the
right one and the code disagreed with it.

Why it was not caught: `view_source.rs` declares eight example results
and had
no test running them. Adding `test_examples` for `ViewSource` is what
surfaced
this, and it also caught a second, smaller mismatch — the module example
claimed

```
" export-env { $env.FOO_ENV = 'BAZ' }"
```

while `view source` returns the source between the module's braces
verbatim,
so the real value has the trailing space too. That one is the example
being
wrong rather than the code, so the example is corrected.

## User-facing changes (Release notes)

`view source` now renders a rest parameter as `...rest: string` rather
than
`...rest:string`, matching how it already renders every other parameter.

## Tests + Formatting

Added `test_examples` for `ViewSource`, which runs all eight declared
example
results. It is not vacuous — it is what found both mismatches, failing
first on

```
expected: "def --env --wrapped foo [ ...rest: string] { print $rest }"
got:      "def --env --wrapped foo [ ...rest:string] { print $rest }"
```

and then on the module example. Both are green now.

The full `nu-command` lib suite (815 tests) and the existing
`view_source`
integration tests pass. I checked nothing asserted the old spelling: the
only
other `...name:type` in the tree is input source in an unrelated parser
test,
not an assertion on `view source` output. `cargo fmt --all -- --check`
and
`cargo clippy --all-targets` are clean.

## Additional notes

Third small `nu-command` fix from me after #19017 and #19023, same
theme: a
declared example result that nothing was checking.

Co-authored-by: Prajwal Narayana <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
@fdncred fdncred added notes:fixes Noted in "Bug fixes" section notes:ready Indicates Ready for Release notes labels Sep 14, 2026
fdncred pushed a commit that referenced this pull request Sep 16, 2026
…#19021)

# Description

Follow-up to #19003. The default `mkdir` behavior (idempotent,
`-p`-like) stays exactly as-is — #19006 tried making it error by default
and was reverted in #19008, since people rely on `mkdir foo` to just
ensure a directory exists (and `^mkdir -p` doesn't error either). Per
@fdncred's suggestion in the thread, this instead adds an **opt-in**
flag.

With `--fail-if-exists`, `mkdir` errors on a target that already exists
(message `<path>: File exists`, matching coreutils), while still
creating any non-existing targets passed in the same call. Without the
flag, nothing changes.

The `--verbose` `created: true` half of the issue was already fixed in
#19017, so this covers the remaining piece.

# User-Facing Changes

New opt-in switch: `mkdir --fail-if-exists <dir>` (short: none) errors
if the directory already exists instead of silently succeeding. Default
`mkdir` is unchanged.

# Tests + Formatting

Added `commands::umkdir` tests: errors on an existing dir with the flag,
still creates new dirs with the flag, and `-v --fail-if-exists` reports
`created: false`. Existing default-behavior tests (idempotent mkdir,
intermediary dirs) still pass. `cargo fmt --all --check`, `cargo clippy
-p nu-command`, and the `nu-command` test suite are clean.

# After Submitting

Nothing needed (no doc/`std` changes).

---
_Disclosure: I use AI assistance for code review and for drafting text
(English isn't my first language); I review and verify every change
myself before sending it._

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

notes:fixes Noted in "Bug fixes" section notes:ready Indicates Ready for Release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants