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 Branches {
396 bin: self.bin,
397 global_opts: self.global_opts,
398 limit: self.limit,
399 time: self.time,
400 name_filter: self.name_filter,
401 user_filter: User::Named {
402 name: v.into(),
403 #[cfg(not(feature = "lt2024_2"))]
404 case_insensitive,
405 },
406 }
407 }
408
409 #[cfg(not(feature = "lt2016_1"))]
417 pub fn filter_me(self) -> Branches<F, User> {
418 Branches {
419 bin: self.bin,
420 global_opts: self.global_opts,
421 limit: self.limit,
422 time: self.time,
423 name_filter: self.name_filter,
424 user_filter: User::Me,
425 }
426 }
427}
428
429impl<F: ExclusiveOption, U: ExclusiveOption> SubCommand for Branches<F, U> {
434 fn name(&self) -> &str {
435 "branches"
436 }
437
438 fn inject_local_args(&self, command: &mut Command) {
439 self.name_filter.inject_args(command);
440 if let Some(max) = self.limit {
441 command.arg("-m").arg(max.to_string());
442 }
443 if self.time {
444 command.arg("-t");
445 }
446 self.user_filter.inject_args(command);
447 }
448
449 fn global_opts(&self) -> Option<&GlobalOpts> {
450 Some(&self.global_opts)
451 }
452}
453
454impl<F: ExclusiveOption, U: ExclusiveOption> ParameterizedSpawn<()> for Branches<F, U> {
459 type Output = Child;
460 type Error = std::io::Error;
461
462 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
466 self.setup_command(&self.bin)
467 .stdout(Stdio::piped())
468 .stderr(Stdio::piped())
469 .spawn()
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use crate::cmd::args_of;
477
478 #[test]
479 fn bin_accessors_update_executable_path() {
480 let mut command = Branches::new("p4", GlobalOpts::new());
481 assert_eq!(command.get_bin(), Path::new("p4"));
482
483 command.set_bin("/usr/local/bin/p4");
484 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
485
486 let command = command.bin("p4.exe");
487 assert_eq!(command.get_bin(), Path::new("p4.exe"));
488 }
489
490 #[test]
504 fn without_options() {
505 let branches = Branches::new("p4", GlobalOpts::new());
506 assert_eq!(args_of(&branches.setup_command("p4")), ["branches"]);
507 }
508
509 #[test]
510 fn limit() {
511 let branches = Branches::new("p4", GlobalOpts::new()).limit(5);
512 assert_eq!(
513 args_of(&branches.setup_command("p4")),
514 ["branches", "-m", "5"]
515 );
516 }
517
518 #[test]
519 fn display_time() {
520 let branches = Branches::new("p4", GlobalOpts::new()).display_time(true);
521 assert_eq!(args_of(&branches.setup_command("p4")), ["branches", "-t"]);
522 }
523
524 #[test]
525 fn display_time_can_be_unset() {
526 let branches = Branches::new("p4", GlobalOpts::new()).display_time(false);
527 assert_eq!(args_of(&branches.setup_command("p4")), ["branches"]);
528 }
529
530 #[test]
531 fn filter_case_sensitive() {
532 let branches = Branches::new("p4", GlobalOpts::new()).filter("svr-dev-rel*", false);
533 assert_eq!(
534 args_of(&branches.setup_command("p4")),
535 ["branches", "-e", "svr-dev-rel*"]
536 );
537 }
538
539 #[test]
540 fn filter_case_insensitive() {
541 let branches = Branches::new("p4", GlobalOpts::new()).filter("SVR-DEV-REL*", true);
542 assert_eq!(
543 args_of(&branches.setup_command("p4")),
544 ["branches", "-E", "SVR-DEV-REL*"]
545 );
546 }
547
548 #[test]
549 fn user() {
550 let branches = Branches::new("p4", GlobalOpts::new()).user("*bert", false);
551 assert_eq!(
552 args_of(&branches.setup_command("p4")),
553 ["branches", "-u", "*bert"]
554 );
555 }
556
557 #[cfg(not(feature = "lt2016_1"))]
558 #[test]
559 fn filter_me() {
560 let branches = Branches::new("p4", GlobalOpts::new()).filter_me();
561 assert_eq!(args_of(&branches.setup_command("p4")), ["branches", "--me"]);
562 }
563
564 #[cfg(not(feature = "lt2024_2"))]
565 #[test]
566 fn user_case_insensitive() {
567 let branches = Branches::new("p4", GlobalOpts::new()).user("*bert", true);
568 assert_eq!(
569 args_of(&branches.setup_command("p4")),
570 ["branches", "-u", "*bert", "--user-case-insensitive"]
571 );
572 }
573
574 #[test]
575 fn filter_transitions_preserve_owner_filter() {
576 let branches = Branches::new("p4", GlobalOpts::new())
577 .user("*bert", false)
578 .filter("svr-dev-rel*", false);
579 assert_eq!(
580 args_of(&branches.setup_command("p4")),
581 ["branches", "-e", "svr-dev-rel*", "-u", "*bert"]
582 );
583
584 #[cfg(not(feature = "lt2016_1"))]
585 {
586 let branches = Branches::new("p4", GlobalOpts::new())
587 .filter("SVR*", true)
588 .filter_me();
589 assert_eq!(
590 args_of(&branches.setup_command("p4")),
591 ["branches", "-E", "SVR*", "--me"]
592 );
593 }
594 }
595
596 #[test]
597 fn setters_update_limit_and_display_time() {
598 let mut branches = Branches::new("p4", GlobalOpts::new()).filter("svr-dev*", false);
599 assert_eq!(branches.get_limit(), None);
600 branches.set_limit(10);
601 assert_eq!(branches.get_limit(), Some(10));
602 assert!(!branches.get_display_time());
603 branches.set_display_time(true);
604 assert!(branches.get_display_time());
605 }
606
607 #[test]
608 fn all_options_order() {
609 let branches = Branches::new("p4", GlobalOpts::new())
610 .filter("svr-dev-rel*", false)
611 .limit(5)
612 .display_time(true)
613 .user("*bert", false);
614 assert_eq!(
615 args_of(&branches.setup_command("p4")),
616 [
617 "branches",
618 "-e",
619 "svr-dev-rel*",
620 "-m",
621 "5",
622 "-t",
623 "-u",
624 "*bert"
625 ]
626 );
627 }
628
629 #[test]
630 fn global_opts_are_injected_once() {
631 let branches = Branches::new("p4", GlobalOpts::new().port("localhost:1666")).limit(1);
632 assert_eq!(
633 args_of(&branches.setup_command("p4")),
634 ["-p", "localhost:1666", "branches", "-m", "1"]
635 );
636 }
637}