Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions .github/workflows/perf-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ name: perf-pr
# person who can still do something about it.
#
# Nothing here writes to refs/notes/tak. A pull request's measurements are
# recorded locally, used for the comparison, and discarded with the runner. Only
# main contributes to the history — a branch's numbers are not the trunk's, and
# a series that mixes them cannot be read.
# base and pull-request measurements are recorded locally, used for the comparison,
# and discarded with the runner. Only main contributes to the persistent history —
# a branch's numbers are not the trunk's, and a series that mixes them cannot be read.

on:
pull_request:
Expand Down Expand Up @@ -74,6 +74,38 @@ jobs:
}
valgrind --version

- name: Find the base commit
id: base
env:
# The generic context resolved to main for a reopened stacked PR even
# though the event and PR both named the stack parent. Read the
# authoritative pull-request payload directly.
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
git fetch --quiet origin "$BASE_REF"
# The merge base, not the branch tip: comparing against a moving
# target attributes other people's commits to this pull request.
base=$(git merge-base "origin/$BASE_REF" HEAD)
echo "sha=$base" >> "$GITHUB_OUTPUT"
echo "comparing against $base on $BASE_REF"

# Stack-parent commits are intentionally absent from the persistent tak history. Measure
# the exact merge base in a linked worktree so the comparison below has a same-runner
# baseline instead of passing with "nothing was compared". Git notes are shared between
# linked worktrees, but both measurements remain local to this disposable runner.
- name: Measure the base commit
env:
BASE_SHA: ${{ steps.base.outputs.sha }}
MISE_TRUSTED_CONFIG_PATHS: /tmp/usage-perf-base
run: |
base_worktree=/tmp/usage-perf-base
git worktree add --detach "$base_worktree" "$BASE_SHA"
trap 'git worktree remove --force "$base_worktree"' EXIT
(
cd "$base_worktree"
mise run perf:record
)

# `--record` writes a git note locally and nothing more. There is no
# `tak push` in this workflow and there should never be one.
- name: Measure this pull request
Expand All @@ -87,18 +119,6 @@ jobs:
- name: Measure the shadow against clap, argh and bpaf
run: ./tasks/perf-shadow.sh /tmp/shadow-report.md || true

- name: Find the base commit
id: base
env:
BASE_REF: ${{ github.base_ref }}
run: |
git fetch --quiet origin "$BASE_REF"
# The merge base, not the branch tip: comparing against a moving
# target attributes other people's commits to this pull request.
base=$(git merge-base "origin/$BASE_REF" HEAD)
echo "sha=$base" >> "$GITHUB_OUTPUT"
echo "comparing against $base on $BASE_REF"

# Deliberately not `continue-on-error`. That would hide a compare that
# died for an unrelated reason behind a green check. The exit code is
# captured, the comment is posted either way, and a later step re-raises
Expand Down
15 changes: 8 additions & 7 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1319,13 +1319,14 @@ above are where it lands.
obvious first user. It is now portable through KDL, the typed derive,
usage-lib and generated Go help. clap#4589 asks for prose under a heading — Deno wants
per-section doc links — which is the same node with one more field.
- [ ] **Deprecation and stability metadata on flags and commands** (clap#3321) —
`deprecated`, with warn/remove versions, already exists in the
config-prop vocabulary; the same on flags and commands would flow into
help, docs and completions from one declaration. Explicit full-name
deprecated env aliases (clap#5447) fit the same slot — full names, so
they stay greppable — and clap#5925's ordered fallback across several
env names is the same declaration read in order.
- [x] **Deprecation and stability metadata on flags and commands** (clap#3321) —
`deprecated`, with warn/remove versions, is portable through KDL, typed
Rust, generated Go, help, docs and completions from one declaration.
- [ ] **Ordered environment fallbacks and deprecated aliases** — explicit
full-name deprecated env aliases (clap#5447) stay greppable, while
clap#5925's fallback across several env names preserves declaration
order. This is parser behavior rather than command/flag presentation, so
it remains separate from the deprecation metadata above.
- [ ] **A group as an enum in the derive** (clap#2621, 102 votes — tied for
clap's most-requested) — mutually exclusive flags declared as enum
variants, lowering to the `group`/`conflicts` vocabulary the spec already
Expand Down
55 changes: 50 additions & 5 deletions argv/src/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,12 @@ fn subcommands<'a>(meta: &'a CommandMeta<'a>, token: &str) -> Vec<Candidate<'a>>
if name.starts_with(token) {
out.push(Candidate {
value: (*name).to_string(),
description: sub.about.map(::std::borrow::Cow::Borrowed),
description: deprecated_description(
sub.about,
sub.deprecated,
sub.deprecated_warn_at,
sub.deprecated_remove_at,
),
});
}
}
Expand All @@ -1158,7 +1163,14 @@ fn long_flags<'a>(spec: &'a Spec<'a>, position: &Position<'_>, token: &str) -> V
if meta.is_some_and(|m| m.hide) {
continue;
}
let description = meta.and_then(|m| m.help);
let description = meta.and_then(|m| {
deprecated_description(
m.help,
m.deprecated,
m.deprecated_warn_at,
m.deprecated_remove_at,
)
});
for long in flag.longs {
if meta.is_some_and(|m| m.hidden_longs.contains(long)) {
continue;
Expand All @@ -1167,7 +1179,7 @@ fn long_flags<'a>(spec: &'a Spec<'a>, position: &Position<'_>, token: &str) -> V
if value.starts_with(token) {
out.push(Candidate {
value,
description: description.map(::std::borrow::Cow::Borrowed),
description: description.clone(),
});
}
}
Expand All @@ -1181,7 +1193,7 @@ fn long_flags<'a>(spec: &'a Spec<'a>, position: &Position<'_>, token: &str) -> V
if value.starts_with(token) {
out.push(Candidate {
value,
description: description.map(::std::borrow::Cow::Borrowed),
description: description.clone(),
});
}
}
Expand Down Expand Up @@ -1213,14 +1225,47 @@ fn short_flags<'a>(spec: &'a Spec<'a>, position: &Position<'_>, token: &str) ->
if asked_about {
out.push(Candidate {
value: format!("-{}", short as char),
description: meta.and_then(|m| m.help).map(::std::borrow::Cow::Borrowed),
description: meta.and_then(|m| {
deprecated_description(
m.help,
m.deprecated,
m.deprecated_warn_at,
m.deprecated_remove_at,
)
}),
});
}
}
}
out
}

fn deprecated_description<'a>(
base: Option<&'a str>,
message: Option<&'a str>,
warn_at: Option<&'a str>,
remove_at: Option<&'a str>,
) -> Option<std::borrow::Cow<'a, str>> {
if message.is_none() && warn_at.is_none() && remove_at.is_none() {
return base.map(std::borrow::Cow::Borrowed);
}
let mut parts = Vec::new();
if let Some(message) = message {
parts.push(message.to_string());
}
if let Some(at) = warn_at {
parts.push(format!("warns at {at}"));
}
if let Some(at) = remove_at {
parts.push(format!("removed at {at}"));
}
let label = format!("[deprecated: {}]", parts.join("; "));
Some(std::borrow::Cow::Owned(match base {
Some(base) if !base.is_empty() => format!("{base} {label}"),
_ => label,
}))
}

/// What a positional accepts here — its choices, or the separator that has to come first.
///
/// An argument declared `double_dash = "required"` is not fillable until a `--` has been
Expand Down
Loading
Loading