Skip to content

Commit 9de4de4

Browse files
committed
Use clap 3.2 and use only non-deprecated interfaces
clap 3.2 introduces several new interfaces that are backported from the upcoming clap 4.0 branch. Many of these new interfaces deprecate similar/equivalent intefaces. StGit is updated to require clap 3.2 and to use only non-deprecated interfaces. clap::Arg::value_parser() and the corresponding clap::ArgMatches::get_one() interface, which are new in 3.2, have the most impact to StGit. Collectively they provide typed argument values. Within StGit, patch names and patch ranges are the most common argument values to deal with. The patchrange module is updated to take advantage of typed command line arguments by introducing the patchrange::Specification type and associated FromStr impl. These changes should significantly ease the path to clap 4.0.
1 parent be11e5f commit 9de4de4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1141
-1002
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ anyhow = "1.0"
2020
atty = "0.2"
2121
bstr = { version = "0.2", default-features = false, features = ["std"] }
2222
chrono = { version = "0.4", default-features = false, features = ["clock"] }
23-
clap = { version = "3.1", default-features = false, features = ["color", "std", "suggestions", "wrap_help"] }
23+
clap = { version = "3.2", default-features = false, features = ["color", "std", "suggestions", "wrap_help"] }
2424
encoding_rs = "0.8"
2525
git2 = { version = "0.13", default-features = false }
2626
indexmap = "1.8"

src/argset.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
//! [`clap::Arg`] definitions common to several StGit commands.
44
5-
use clap::{Arg, ValueHint};
5+
use clap::Arg;
66

77
lazy_static! {
88
/// The `--branch`/`-b` option for selecting an alternative branch.
@@ -12,7 +12,8 @@ lazy_static! {
1212
.help("Use BRANCH instead of current branch")
1313
.takes_value(true)
1414
.value_name("branch")
15-
.value_hint(ValueHint::Other);
15+
.value_hint(clap::ValueHint::Other)
16+
.value_parser(parse_branch_name);
1617

1718
/// The `--keep/-k` option.
1819
pub(crate) static ref KEEP_ARG: Arg<'static> =
@@ -31,9 +32,36 @@ lazy_static! {
3132
.help("Extra options to pass to \"git diff\"")
3233
.takes_value(true)
3334
.allow_hyphen_values(true)
34-
.multiple_occurrences(true)
35+
.action(clap::ArgAction::Append)
3536
.value_name("options")
36-
.value_hint(ValueHint::Other);
37+
.value_hint(clap::ValueHint::Other);
38+
}
39+
40+
/// For use with `clap::Arg::value_parser()` to ensure a branch name is valid.
41+
pub(crate) fn parse_branch_name(name: &str) -> anyhow::Result<String> {
42+
if git2::Branch::name_is_valid(name)? {
43+
Ok(name.to_string())
44+
} else {
45+
Err(anyhow::anyhow!("invalid branch name `{name}`"))
46+
}
47+
}
48+
49+
/// Get a `&str` from a `clap::ArgMatches` instance for the given `id`.
50+
///
51+
/// This function may be cleaner than calling `ArgMatches::get_one::<String>()` directly
52+
/// since that function returns `Option<&String>` which often needs to be mapped to
53+
/// `Option<&str>`.
54+
pub(crate) fn get_one_str<'a>(matches: &'a clap::ArgMatches, id: &str) -> Option<&'a str> {
55+
matches.get_one::<String>(id).map(|s| s.as_str())
56+
}
57+
58+
/// For use with `clap::Arg::value_parser()` to parse a usize argument.
59+
///
60+
/// This function has a custom error message that is preferable to the messages reported
61+
/// by `str::parse::<usize>()`.
62+
pub(crate) fn parse_usize(s: &str) -> anyhow::Result<usize> {
63+
s.parse::<usize>()
64+
.map_err(|_| anyhow::anyhow!("'{s}' is not a positive integer"))
3765
}
3866

3967
/// Compose aggregate set of git diff options from various sources.
@@ -62,7 +90,7 @@ pub(crate) fn get_diff_opts(
6290
}
6391
}
6492

65-
if let Some(values) = matches.values_of("diff-opts") {
93+
if let Some(values) = matches.get_many::<String>("diff-opts") {
6694
for value in values {
6795
for arg in value.split_ascii_whitespace() {
6896
opts.push(String::from(arg))

src/cmd/branch.rs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use clap::{Arg, ArgMatches};
1010
use termcolor::WriteColor;
1111

1212
use crate::{
13+
argset::{self, get_one_str},
1314
print_info_message, print_warning_message,
1415
repo::RepositoryExtended,
1516
stack::{get_branch_name, state_refname_from_branch_name, Stack, StackStateAccess},
@@ -82,12 +83,12 @@ fn make() -> clap::Command<'static> {
8283
Arg::new("new-branch")
8384
.help("New branch name")
8485
.required(true)
85-
.forbid_empty_values(true),
86+
.value_parser(argset::parse_branch_name),
8687
)
8788
.arg(
8889
Arg::new("committish")
8990
.help("Base commit for new branch")
90-
.forbid_empty_values(true),
91+
.value_parser(clap::builder::NonEmptyStringValueParser::new()),
9192
),
9293
)
9394
.subcommand(
@@ -105,7 +106,7 @@ fn make() -> clap::Command<'static> {
105106
.arg(
106107
Arg::new("new-branch")
107108
.help("New branch name")
108-
.forbid_empty_values(true),
109+
.value_parser(argset::parse_branch_name),
109110
),
110111
)
111112
.subcommand(
@@ -121,7 +122,7 @@ fn make() -> clap::Command<'static> {
121122
.multiple_values(true)
122123
.min_values(1)
123124
.max_values(2)
124-
.forbid_empty_values(true),
125+
.value_parser(argset::parse_branch_name),
125126
),
126127
)
127128
.subcommand(
@@ -133,7 +134,7 @@ fn make() -> clap::Command<'static> {
133134
Arg::new("branch")
134135
.help("Branch to protect")
135136
.value_name("branch")
136-
.forbid_empty_values(true),
137+
.value_parser(argset::parse_branch_name),
137138
),
138139
)
139140
.subcommand(
@@ -145,7 +146,7 @@ fn make() -> clap::Command<'static> {
145146
Arg::new("branch")
146147
.help("Branch to unprotect")
147148
.value_name("branch")
148-
.forbid_empty_values(true),
149+
.value_parser(argset::parse_branch_name),
149150
),
150151
)
151152
.subcommand(
@@ -165,7 +166,7 @@ fn make() -> clap::Command<'static> {
165166
.help("Branch to delete")
166167
.value_name("branch")
167168
.required(true)
168-
.forbid_empty_values(true),
169+
.value_parser(argset::parse_branch_name),
169170
)
170171
.arg(
171172
Arg::new("force")
@@ -189,7 +190,7 @@ fn make() -> clap::Command<'static> {
189190
Arg::new("branch")
190191
.help("Branch to clean up")
191192
.value_name("branch")
192-
.forbid_empty_values(true),
193+
.value_parser(argset::parse_branch_name),
193194
)
194195
.arg(
195196
Arg::new("force")
@@ -208,7 +209,7 @@ fn make() -> clap::Command<'static> {
208209
Arg::new("branch-any")
209210
.help("Branch to describe")
210211
.value_name("branch")
211-
.forbid_empty_values(true),
212+
.value_parser(argset::parse_branch_name),
212213
),
213214
)
214215
.arg(
@@ -221,7 +222,7 @@ fn make() -> clap::Command<'static> {
221222
Arg::new("branch-any")
222223
.help("Branch to switch to")
223224
.value_name("branch")
224-
.forbid_empty_values(true),
225+
.value_parser(argset::parse_branch_name),
225226
)
226227
}
227228

@@ -243,11 +244,11 @@ fn run(matches: &ArgMatches) -> Result<()> {
243244
} else {
244245
let current_branch = repo.get_branch(None)?;
245246
let current_branchname = get_branch_name(&current_branch)?;
246-
if let Some(target_branchname) = matches.value_of("branch-any") {
247+
if let Some(target_branchname) = get_one_str(matches, "branch-any") {
247248
if target_branchname == current_branchname {
248249
Err(anyhow!("{target_branchname} is already the current branch"))
249250
} else {
250-
if !matches.is_present("merge") {
251+
if !matches.contains_id("merge") {
251252
repo.check_worktree_clean()?;
252253
}
253254
let target_branch = repo.get_branch(Some(target_branchname))?;
@@ -422,12 +423,12 @@ fn list(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
422423
}
423424

424425
fn create(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
425-
let new_branchname = matches.value_of("new-branch").expect("required argument");
426+
let new_branchname = get_one_str(matches, "new-branch").expect("required argument");
426427

427428
repo.check_repository_state()?;
428429
repo.check_conflicts()?;
429430

430-
let parent_branch = if let Some(committish) = matches.value_of("committish") {
431+
let parent_branch = if let Some(committish) = get_one_str(matches, "committish") {
431432
repo.check_worktree_clean()?;
432433
let mut parent_branch = None;
433434
if let Ok(local_parent_branch) = repo.find_branch(committish, git2::BranchType::Local) {
@@ -458,7 +459,7 @@ fn create(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
458459

459460
let target_commit = if let Some(parent_branch) = parent_branch.as_ref() {
460461
parent_branch.get().peel_to_commit()?
461-
} else if let Some(committish) = matches.value_of("committish") {
462+
} else if let Some(committish) = get_one_str(matches, "committish") {
462463
crate::revspec::parse_stgit_revision(&repo, Some(committish), None)?.peel_to_commit()?
463464
} else {
464465
repo.head()?.peel_to_commit()?
@@ -515,7 +516,7 @@ fn clone(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
515516
let current_branch = repo.get_branch(None)?;
516517
let current_branchname = get_branch_name(&current_branch)?;
517518

518-
let new_branchname = if let Some(new_branchname) = matches.value_of("new-branch") {
519+
let new_branchname = if let Some(new_branchname) = get_one_str(matches, "new-branch") {
519520
new_branchname.to_string()
520521
} else {
521522
let suffix = chrono::Local::now().format("%Y%m%d-%H%M%S");
@@ -560,7 +561,11 @@ fn clone(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
560561
}
561562

562563
fn rename(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
563-
let names: Vec<_> = matches.values_of("branch-any").unwrap().collect();
564+
let names: Vec<_> = matches
565+
.get_many::<String>("branch-any")
566+
.unwrap()
567+
.map(|s| s.as_str())
568+
.collect();
564569
let current_branchname;
565570
let (old_branchname, new_branchname) = if names.len() == 2 {
566571
repo.get_branch(Some(names[0]))?;
@@ -596,19 +601,19 @@ fn rename(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
596601
}
597602

598603
fn protect(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
599-
let stack = Stack::from_branch(&repo, matches.value_of("branch"))?;
604+
let stack = Stack::from_branch(&repo, get_one_str(matches, "branch"))?;
600605
let mut config = repo.config()?;
601606
stack.set_protected(&mut config, true)
602607
}
603608

604609
fn unprotect(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
605-
let stack = Stack::from_branch(&repo, matches.value_of("branch"))?;
610+
let stack = Stack::from_branch(&repo, get_one_str(matches, "branch"))?;
606611
let mut config = repo.config()?;
607612
stack.set_protected(&mut config, false)
608613
}
609614

610615
fn delete(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
611-
let target_branchname = matches.value_of("branch-any").expect("required argument");
616+
let target_branchname = get_one_str(matches, "branch-any").expect("required argument");
612617
let mut target_branch = repo.get_branch(Some(target_branchname))?;
613618
let current_branch = repo.get_branch(None).ok();
614619
let current_branchname = current_branch.and_then(|branch| get_branch_name(&branch).ok());
@@ -621,7 +626,7 @@ fn delete(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
621626
if let Ok(stack) = Stack::from_branch(&repo, Some(target_branchname)) {
622627
if stack.is_protected(&config) {
623628
return Err(anyhow!("Delete not permitted: this branch is protected"));
624-
} else if !matches.is_present("force") && stack.all_patches().count() > 0 {
629+
} else if !matches.contains_id("force") && stack.all_patches().count() > 0 {
625630
return Err(anyhow!(
626631
"Delete not permitted: the series still contains patches (override with --force)"
627632
));
@@ -634,11 +639,11 @@ fn delete(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
634639
}
635640

636641
fn cleanup(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
637-
let stack = Stack::from_branch(&repo, matches.value_of("branch"))?;
642+
let stack = Stack::from_branch(&repo, get_one_str(matches, "branch"))?;
638643
let config = repo.config()?;
639644
if stack.is_protected(&config) {
640645
return Err(anyhow!("Clean up not permitted: this branch is protected"));
641-
} else if !matches.is_present("force") && stack.all_patches().count() > 0 {
646+
} else if !matches.contains_id("force") && stack.all_patches().count() > 0 {
642647
return Err(anyhow!(
643648
"Clean up not permitted: the series still contains patches (override with --force)"
644649
));
@@ -648,8 +653,8 @@ fn cleanup(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
648653
}
649654

650655
fn describe(repo: git2::Repository, matches: &ArgMatches) -> Result<()> {
651-
let branch = repo.get_branch(matches.value_of("branch-any"))?;
652-
let description = matches.value_of("description").expect("required argument");
656+
let branch = repo.get_branch(get_one_str(matches, "branch-any"))?;
657+
let description = get_one_str(matches, "description").expect("required argument");
653658
let branchname = get_branch_name(&branch)?;
654659
let mut config = repo.config()?;
655660
set_description(&mut config, &branchname, description)

src/cmd/clean.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ fn run(matches: &ArgMatches) -> Result<()> {
5555
repo.check_repository_state()?;
5656

5757
let (clean_applied, clean_unapplied) = match (
58-
matches.is_present("applied"),
59-
matches.is_present("unapplied"),
58+
matches.contains_id("applied"),
59+
matches.contains_id("unapplied"),
6060
) {
6161
(false, false) => (true, true),
6262
opts => opts,

src/cmd/commit.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fn make() -> clap::Command<'static> {
5252
.help("Patches to commit")
5353
.value_name("patch")
5454
.multiple_values(true)
55-
.forbid_empty_values(true)
55+
.value_parser(clap::value_parser!(patchrange::Specification))
5656
.conflicts_with_all(&["all", "number"]),
5757
)
5858
.arg(
@@ -61,10 +61,7 @@ fn make() -> clap::Command<'static> {
6161
.short('n')
6262
.help("Commit the specified number of applied patches")
6363
.value_name("number")
64-
.validator(|s| {
65-
s.parse::<usize>()
66-
.map_err(|_| format!("'{s}' is not an integer"))
67-
})
64+
.value_parser(crate::argset::parse_usize)
6865
.conflicts_with("all"),
6966
)
7067
.arg(
@@ -84,10 +81,11 @@ fn run(matches: &ArgMatches) -> Result<()> {
8481
let repo = git2::Repository::open_from_env()?;
8582
let stack = Stack::from_branch(&repo, None)?;
8683

87-
let patches: Vec<PatchName> = if let Some(patchranges) = matches.values_of("patchranges") {
84+
let range_specs = matches.get_many::<patchrange::Specification>("patchranges");
85+
let patches: Vec<PatchName> = if let Some(range_specs) = range_specs {
8886
let applied_and_unapplied = stack.applied_and_unapplied().collect::<Vec<&PatchName>>();
89-
let mut requested_patches = patchrange::parse(
90-
patchranges,
87+
let mut requested_patches = patchrange::patches_from_specs(
88+
range_specs,
9189
&stack,
9290
patchrange::Allow::VisibleWithAppliedBoundary,
9391
)?;
@@ -98,11 +96,7 @@ fn run(matches: &ArgMatches) -> Result<()> {
9896
.unwrap()
9997
});
10098
requested_patches
101-
} else if let Some(number) = matches.value_of("number").map(|num_str| {
102-
num_str
103-
.parse::<usize>()
104-
.expect("validator previously parsed this")
105-
}) {
99+
} else if let Some(number) = matches.get_one::<usize>("number").copied() {
106100
if number == 0 {
107101
return Ok(());
108102
} else if number > stack.applied().len() {
@@ -114,13 +108,13 @@ fn run(matches: &ArgMatches) -> Result<()> {
114108
}
115109
} else if stack.applied().is_empty() {
116110
return Err(Error::NoAppliedPatches.into());
117-
} else if matches.is_present("all") {
111+
} else if matches.contains_id("all") {
118112
stack.applied().to_vec()
119113
} else {
120114
vec![stack.applied()[0].clone()]
121115
};
122116

123-
if !matches.is_present("allow-empty") {
117+
if !matches.contains_id("allow-empty") {
124118
let mut empty_patches: Vec<&PatchName> = Vec::new();
125119
for pn in &patches {
126120
let patch_commit = stack.get_patch_commit(pn);

src/cmd/completion/bash.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use anyhow::Result;
66

7-
use std::format as f;
7+
use std::{format as f, path::PathBuf};
88

99
use super::shstream::ShStream;
1010

@@ -18,7 +18,7 @@ pub(super) fn command() -> clap::Command<'static> {
1818
.help("Output completion script to <path>")
1919
.value_name("path")
2020
.value_hint(clap::ValueHint::FilePath)
21-
.allow_invalid_utf8(true),
21+
.value_parser(clap::value_parser!(PathBuf)),
2222
)
2323
}
2424

@@ -142,7 +142,7 @@ fn write_command_func(script: &mut ShStream, fname: &str, command: &clap::Comman
142142
let mut pos_index = 0;
143143
for arg in command.get_positionals() {
144144
script.ensure_blank_line();
145-
if arg.is_multiple_occurrences_set()
145+
if matches!(arg.get_action(), clap::ArgAction::Append)
146146
|| (arg.is_multiple_values_set() && arg.get_value_delimiter() == Some(' '))
147147
{
148148
if let Some(num_vals) = arg.get_num_vals() {
@@ -195,7 +195,7 @@ fn write_command_func(script: &mut ShStream, fname: &str, command: &clap::Comman
195195
fn insert_compreply(script: &mut ShStream, arg: &clap::Arg) {
196196
assert!(arg.is_takes_value_set());
197197

198-
if let Some(possible_values) = arg.get_possible_values() {
198+
if let Some(possible_values) = arg.get_value_parser().possible_values() {
199199
let mut possible = ShStream::new();
200200
for pv in possible_values {
201201
possible.word(pv.get_name());

0 commit comments

Comments
 (0)