1use std::{
2 path::{Path, PathBuf},
3 process::{Child, Command, Stdio},
4};
5
6use super::{ExclusiveOption, SubCommand, Unselected};
7
8use crate::global::GlobalOpts;
9use crate::spawn::ParameterizedSpawn;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum Filter {
23 CaseSensitiveMode(String),
26
27 InCaseSensitiveMode(String),
30}
31
32impl ExclusiveOption for Filter {
33 fn inject_args(&self, command: &mut Command) {
34 match self {
35 Filter::CaseSensitiveMode(name) => {
36 command.arg("-e").arg(name);
37 }
38 Filter::InCaseSensitiveMode(name) => {
39 command.arg("-E").arg(name);
40 }
41 }
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum User {
56 Named {
59 name: String,
60
61 #[cfg(not(feature = "lt2024_2"))]
62 case_insensitive: bool,
63 },
64
65 #[cfg(not(feature = "lt2016_1"))]
68 Me,
69}
70
71impl ExclusiveOption for User {
72 fn inject_args(&self, command: &mut Command) {
73 match self {
74 User::Named {
75 name,
76 #[cfg(not(feature = "lt2024_2"))]
77 case_insensitive,
78 } => {
79 command.arg("-u").arg(name);
80 #[cfg(not(feature = "lt2024_2"))]
81 if *case_insensitive {
82 command.arg("--user-case-insensitive");
83 }
84 }
85 #[cfg(not(feature = "lt2016_1"))]
86 User::Me => {
87 command.arg("--me");
88 }
89 }
90 }
91}
92
93#[cfg_attr(
98 feature = "lt2016_1",
99 doc = "`p4 [g-opts] branches [-t] [-u user] [[-e|-E]filter -mmax]`"
100)]
101#[cfg_attr(
102 all(feature = "lt2024_2", not(feature = "lt2016_1")),
103 doc = "`p4 [g-opts] branches [[-e | -E]filter] [-mmax][-t] [-u user | --me]`"
104)]
105#[cfg_attr(
106 not(feature = "lt2024_2"),
107 doc = "`p4 [g-opts] branches [[-e | -E] nameFilter] [-m max][-t] [-u user [--user-case-insensitive] | --me]`"
108)]
109#[cfg_attr(feature = "lt2016_1", doc = "[`User`] (entered via [`Self::user`]).")]
122#[cfg_attr(
123 not(feature = "lt2016_1"),
124 doc = "[`User`] (entered via [`Self::user`] or [`Self::filter_me`])."
125)]
126#[derive(Debug, Clone, Default)]
130pub struct Branches<F = Unselected, U = Unselected> {
131 bin: PathBuf,
132
133 global_opts: GlobalOpts,
134
135 limit: Option<u64>,
136
137 time: bool,
138
139 user_filter: U,
140
141 name_filter: F,
142}
143
144impl Branches<Unselected, Unselected> {
149 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
153 Self {
154 bin: bin.into(),
155 global_opts,
156 ..Default::default()
157 }
158 }
159}
160
161impl<F, U> Branches<F, U> {
166 pub fn get_bin(&self) -> &Path {
168 &self.bin
169 }
170
171 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
173 self.bin = bin.into();
174 self
175 }
176
177 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
179 self.set_bin(bin);
180 self
181 }
182
183 #[cfg_attr(
188 feature = "lt2014_2",
189 doc = "See the [Global Options](GlobalOpts) section."
190 )]
191 #[cfg_attr(
192 all(feature = "lt2015_1", not(feature = "lt2014_2")),
193 doc = "See the [“Global Options”](GlobalOpts) section."
194 )]
195 #[cfg_attr(
196 all(feature = "lt2017_1", not(feature = "lt2015_1")),
197 doc = "See [“Global Options”](GlobalOpts)."
198 )]
199 #[cfg_attr(
200 all(feature = "lt2018_2", not(feature = "lt2017_1")),
201 doc = "See [Global Options](GlobalOpts)."
202 )]
203 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
204 pub fn get_global_opts(&self) -> &GlobalOpts {
205 &self.global_opts
206 }
207
208 #[cfg_attr(
213 feature = "lt2014_2",
214 doc = "See the [Global Options](GlobalOpts) section."
215 )]
216 #[cfg_attr(
217 all(feature = "lt2015_1", not(feature = "lt2014_2")),
218 doc = "See the [“Global Options”](GlobalOpts) section."
219 )]
220 #[cfg_attr(
221 all(feature = "lt2017_1", not(feature = "lt2015_1")),
222 doc = "See [“Global Options”](GlobalOpts)."
223 )]
224 #[cfg_attr(
225 all(feature = "lt2018_2", not(feature = "lt2017_1")),
226 doc = "See [Global Options](GlobalOpts)."
227 )]
228 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
229 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
230 self.global_opts = v;
231 self
232 }
233
234 #[cfg_attr(
239 feature = "lt2014_2",
240 doc = "See the [Global Options](GlobalOpts) section."
241 )]
242 #[cfg_attr(
243 all(feature = "lt2015_1", not(feature = "lt2014_2")),
244 doc = "See the [“Global Options”](GlobalOpts) section."
245 )]
246 #[cfg_attr(
247 all(feature = "lt2017_1", not(feature = "lt2015_1")),
248 doc = "See [“Global Options”](GlobalOpts)."
249 )]
250 #[cfg_attr(
251 all(feature = "lt2018_2", not(feature = "lt2017_1")),
252 doc = "See [Global Options](GlobalOpts)."
253 )]
254 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
255 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
256 self.global_opts = v;
257 self
258 }
259
260 pub fn get_limit(&self) -> Option<u64> {
266 self.limit
267 }
268
269 pub fn set_limit(&mut self, v: u64) -> &mut Self {
275 self.limit = Some(v);
276 self
277 }
278
279 pub fn limit(mut self, v: u64) -> Self {
285 self.limit = Some(v);
286 self
287 }
288
289 pub fn get_display_time(&self) -> bool {
295 self.time
296 }
297
298 pub fn set_display_time(&mut self, v: bool) -> &mut Self {
304 self.time = v;
305 self
306 }
307
308 pub fn display_time(mut self, v: bool) -> Self {
314 self.time = v;
315 self
316 }
317}
318
319impl<U> Branches<Unselected, U> {
324 #[cfg_attr(feature = "lt2024_2", doc = "-e filter / -E filter")]
327 #[cfg_attr(not(feature = "lt2024_2"), doc = "-e nameFilter / -E nameFilter")]
328 #[cfg_attr(
337 not(feature = "lt2024_2"),
338 doc = "",
339 doc = "For example:",
340 doc = "",
341 doc = "`p4 branches -e \"svr-dev-rel*\"`"
342 )]
343 pub fn filter(self, v: impl Into<String>, case_insensitive: bool) -> Branches<Filter, U> {
346 Branches {
347 bin: self.bin,
348 global_opts: self.global_opts,
349 limit: self.limit,
350 time: self.time,
351 user_filter: self.user_filter,
352 name_filter: if case_insensitive {
353 Filter::InCaseSensitiveMode(v.into())
354 } else {
355 Filter::CaseSensitiveMode(v.into())
356 },
357 }
358 }
359}
360
361impl<F> Branches<F, Unselected> {
366 #[cfg_attr(
371 feature = "lt2024_2",
372 doc = "`v` is the owner user name or search pattern."
373 )]
374 #[cfg_attr(
375 not(feature = "lt2024_2"),
376 doc = "`v` is the owner user name or search pattern; `case_insensitive` selects the `--user-case-insensitive` suboption."
377 )]
378 #[cfg_attr(feature = "lt2024_2", doc = "List only branches owned by `user`.")]
380 #[cfg_attr(
381 not(feature = "lt2024_2"),
382 doc = "List branch specs owned by the specified user.",
383 doc = "",
384 doc = "This can include `*` wildcards to form a search pattern. If wildcards are used, enclose the search pattern in double quotes (`\"`).",
385 doc = "",
386 doc = "For example:",
387 doc = "",
388 doc = "`p4 branches -u \"*bert\"`",
389 doc = "",
390 doc = "You can also add the `--user-case-insensitive` suboption to indicate that the `user` value is a case-insensitive search pattern, even for a case-sensitive server."
391 )]
392 pub fn user(self, v: impl Into<String>, case_insensitive: bool) -> Branches<F, User> {
395 let _ = case_insensitive;
396 Branches {
397 bin: self.bin,
398 global_opts: self.global_opts,
399 limit: self.limit,
400 time: self.time,
401 name_filter: self.name_filter,
402 user_filter: User::Named {
403 name: v.into(),
404 #[cfg(not(feature = "lt2024_2"))]
405 case_insensitive,
406 },
407 }
408 }
409
410 #[cfg(not(feature = "lt2016_1"))]
418 pub fn filter_me(self) -> Branches<F, User> {
419 Branches {
420 bin: self.bin,
421 global_opts: self.global_opts,
422 limit: self.limit,
423 time: self.time,
424 name_filter: self.name_filter,
425 user_filter: User::Me,
426 }
427 }
428}
429
430impl<F: ExclusiveOption, U: ExclusiveOption> SubCommand for Branches<F, U> {
435 fn name(&self) -> &str {
436 "branches"
437 }
438
439 fn inject_local_args(&self, command: &mut Command) {
440 self.name_filter.inject_args(command);
441 if let Some(max) = self.limit {
442 command.arg("-m").arg(max.to_string());
443 }
444 if self.time {
445 command.arg("-t");
446 }
447 self.user_filter.inject_args(command);
448 }
449
450 fn global_opts(&self) -> Option<&GlobalOpts> {
451 Some(&self.global_opts)
452 }
453}
454
455impl<F: ExclusiveOption, U: ExclusiveOption> ParameterizedSpawn<()> for Branches<F, U> {
460 type Output = Child;
461 type Error = std::io::Error;
462
463 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
467 self.setup_command(&self.bin)
468 .stdout(Stdio::piped())
469 .stderr(Stdio::piped())
470 .spawn()
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use crate::cmd::args_of;
478
479 #[test]
480 fn bin_accessors_update_executable_path() {
481 let mut command = Branches::new("p4", GlobalOpts::new());
482 assert_eq!(command.get_bin(), Path::new("p4"));
483
484 command.set_bin("/usr/local/bin/p4");
485 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
486
487 let command = command.bin("p4.exe");
488 assert_eq!(command.get_bin(), Path::new("p4.exe"));
489 }
490
491 #[test]
505 fn without_options() {
506 let branches = Branches::new("p4", GlobalOpts::new());
507 assert_eq!(args_of(&branches.setup_command("p4")), ["branches"]);
508 }
509
510 #[test]
511 fn limit() {
512 let branches = Branches::new("p4", GlobalOpts::new()).limit(5);
513 assert_eq!(
514 args_of(&branches.setup_command("p4")),
515 ["branches", "-m", "5"]
516 );
517 }
518
519 #[test]
520 fn display_time() {
521 let branches = Branches::new("p4", GlobalOpts::new()).display_time(true);
522 assert_eq!(args_of(&branches.setup_command("p4")), ["branches", "-t"]);
523 }
524
525 #[test]
526 fn display_time_can_be_unset() {
527 let branches = Branches::new("p4", GlobalOpts::new()).display_time(false);
528 assert_eq!(args_of(&branches.setup_command("p4")), ["branches"]);
529 }
530
531 #[test]
532 fn filter_case_sensitive() {
533 let branches = Branches::new("p4", GlobalOpts::new()).filter("svr-dev-rel*", false);
534 assert_eq!(
535 args_of(&branches.setup_command("p4")),
536 ["branches", "-e", "svr-dev-rel*"]
537 );
538 }
539
540 #[test]
541 fn filter_case_insensitive() {
542 let branches = Branches::new("p4", GlobalOpts::new()).filter("SVR-DEV-REL*", true);
543 assert_eq!(
544 args_of(&branches.setup_command("p4")),
545 ["branches", "-E", "SVR-DEV-REL*"]
546 );
547 }
548
549 #[test]
550 fn user() {
551 let branches = Branches::new("p4", GlobalOpts::new()).user("*bert", false);
552 assert_eq!(
553 args_of(&branches.setup_command("p4")),
554 ["branches", "-u", "*bert"]
555 );
556 }
557
558 #[cfg(not(feature = "lt2016_1"))]
559 #[test]
560 fn filter_me() {
561 let branches = Branches::new("p4", GlobalOpts::new()).filter_me();
562 assert_eq!(args_of(&branches.setup_command("p4")), ["branches", "--me"]);
563 }
564
565 #[cfg(not(feature = "lt2024_2"))]
566 #[test]
567 fn user_case_insensitive() {
568 let branches = Branches::new("p4", GlobalOpts::new()).user("*bert", true);
569 assert_eq!(
570 args_of(&branches.setup_command("p4")),
571 ["branches", "-u", "*bert", "--user-case-insensitive"]
572 );
573 }
574
575 #[test]
576 fn filter_transitions_preserve_owner_filter() {
577 let branches = Branches::new("p4", GlobalOpts::new())
578 .user("*bert", false)
579 .filter("svr-dev-rel*", false);
580 assert_eq!(
581 args_of(&branches.setup_command("p4")),
582 ["branches", "-e", "svr-dev-rel*", "-u", "*bert"]
583 );
584
585 #[cfg(not(feature = "lt2016_1"))]
586 {
587 let branches = Branches::new("p4", GlobalOpts::new())
588 .filter("SVR*", true)
589 .filter_me();
590 assert_eq!(
591 args_of(&branches.setup_command("p4")),
592 ["branches", "-E", "SVR*", "--me"]
593 );
594 }
595 }
596
597 #[test]
598 fn setters_update_limit_and_display_time() {
599 let mut branches = Branches::new("p4", GlobalOpts::new()).filter("svr-dev*", false);
600 assert_eq!(branches.get_limit(), None);
601 branches.set_limit(10);
602 assert_eq!(branches.get_limit(), Some(10));
603 assert!(!branches.get_display_time());
604 branches.set_display_time(true);
605 assert!(branches.get_display_time());
606 }
607
608 #[test]
609 fn all_options_order() {
610 let branches = Branches::new("p4", GlobalOpts::new())
611 .filter("svr-dev-rel*", false)
612 .limit(5)
613 .display_time(true)
614 .user("*bert", false);
615 assert_eq!(
616 args_of(&branches.setup_command("p4")),
617 [
618 "branches",
619 "-e",
620 "svr-dev-rel*",
621 "-m",
622 "5",
623 "-t",
624 "-u",
625 "*bert"
626 ]
627 );
628 }
629
630 #[test]
631 fn global_opts_are_injected_once() {
632 let branches = Branches::new("p4", GlobalOpts::new().port("localhost:1666")).limit(1);
633 assert_eq!(
634 args_of(&branches.setup_command("p4")),
635 ["-p", "localhost:1666", "branches", "-m", "1"]
636 );
637 }
638}