1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4 process::{Child, Command, Stdio},
5};
6
7use super::{ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub struct GeneralMode {
22 force: bool,
23}
24
25#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct DeleteMode {
31 force: bool,
32}
33
34#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct StdinMode {
39 force: bool,
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct StdoutMode {
50 stream: Option<String>,
51 parent: Option<String>,
52}
53
54impl ExclusiveOption for GeneralMode {
59 fn inject_args(&self, command: &mut Command) {
60 if self.force {
61 command.arg("-f");
62 }
63 }
64}
65
66impl ExclusiveOption for DeleteMode {
67 fn inject_args(&self, command: &mut Command) {
68 command.arg("-d");
69 if self.force {
70 command.arg("-f");
71 }
72 }
73}
74
75impl ExclusiveOption for StdinMode {
76 fn inject_args(&self, command: &mut Command) {
77 command.arg("-i");
78 if self.force {
79 command.arg("-f");
80 }
81 }
82}
83
84impl ExclusiveOption for StdoutMode {
85 fn inject_args(&self, command: &mut Command) {
86 if let Some(stream) = &self.stream {
87 command.arg("-S").arg(stream);
88 }
89 if let Some(parent) = &self.parent {
90 command.arg("-P").arg(parent);
91 }
92 command.arg("-o");
93 }
94}
95
96#[derive(Debug, Clone, Default)]
128pub struct Branch<M = Unselected> {
129 bin: PathBuf,
130
131 global_opts: GlobalOpts,
132
133 mode: M,
134}
135
136impl Branch<Unselected> {
141 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
147 Self {
148 bin: bin.into(),
149 global_opts,
150 ..Default::default()
151 }
152 }
153
154 pub fn force(self, v: bool) -> Branch<GeneralMode> {
158 Branch {
159 bin: self.bin,
160 global_opts: self.global_opts,
161 mode: GeneralMode { force: v },
162 }
163 }
164
165 pub fn delete(self) -> Branch<DeleteMode> {
167 Branch {
168 bin: self.bin,
169 global_opts: self.global_opts,
170 mode: DeleteMode { force: false },
171 }
172 }
173
174 pub fn stdin(self) -> Branch<StdinMode> {
176 Branch {
177 bin: self.bin,
178 global_opts: self.global_opts,
179 mode: StdinMode { force: false },
180 }
181 }
182
183 pub fn stdout(self) -> Branch<StdoutMode> {
189 Branch {
190 bin: self.bin,
191 global_opts: self.global_opts,
192 mode: StdoutMode::default(),
193 }
194 }
195}
196
197impl<M> Branch<M> {
202 pub fn get_bin(&self) -> &Path {
204 &self.bin
205 }
206
207 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
209 self.bin = bin.into();
210 self
211 }
212
213 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
215 self.set_bin(bin);
216 self
217 }
218
219 #[cfg_attr(
224 feature = "lt2014_2",
225 doc = "See the [Global Options](GlobalOpts) section."
226 )]
227 #[cfg_attr(
228 all(feature = "lt2015_1", not(feature = "lt2014_2")),
229 doc = "See the [“Global Options”](GlobalOpts) section."
230 )]
231 #[cfg_attr(
232 all(feature = "lt2017_1", not(feature = "lt2015_1")),
233 doc = "See [“Global Options”](GlobalOpts)."
234 )]
235 #[cfg_attr(
236 all(feature = "lt2018_2", not(feature = "lt2017_1")),
237 doc = "See [Global Options](GlobalOpts)."
238 )]
239 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
240 pub fn get_global_opts(&self) -> &GlobalOpts {
241 &self.global_opts
242 }
243
244 #[cfg_attr(
249 feature = "lt2014_2",
250 doc = "See the [Global Options](GlobalOpts) section."
251 )]
252 #[cfg_attr(
253 all(feature = "lt2015_1", not(feature = "lt2014_2")),
254 doc = "See the [“Global Options”](GlobalOpts) section."
255 )]
256 #[cfg_attr(
257 all(feature = "lt2017_1", not(feature = "lt2015_1")),
258 doc = "See [“Global Options”](GlobalOpts)."
259 )]
260 #[cfg_attr(
261 all(feature = "lt2018_2", not(feature = "lt2017_1")),
262 doc = "See [Global Options](GlobalOpts)."
263 )]
264 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
265 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
266 self.global_opts = v;
267 self
268 }
269
270 #[cfg_attr(
275 feature = "lt2014_2",
276 doc = "See the [Global Options](GlobalOpts) section."
277 )]
278 #[cfg_attr(
279 all(feature = "lt2015_1", not(feature = "lt2014_2")),
280 doc = "See the [“Global Options”](GlobalOpts) section."
281 )]
282 #[cfg_attr(
283 all(feature = "lt2017_1", not(feature = "lt2015_1")),
284 doc = "See [“Global Options”](GlobalOpts)."
285 )]
286 #[cfg_attr(
287 all(feature = "lt2018_2", not(feature = "lt2017_1")),
288 doc = "See [Global Options](GlobalOpts)."
289 )]
290 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
291 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
292 self.global_opts = v;
293 self
294 }
295}
296
297impl Branch<GeneralMode> {
302 pub fn get_force(&self) -> bool {
311 self.mode.force
312 }
313
314 pub fn set_force(&mut self, v: bool) -> &mut Self {
323 self.mode.force = v;
324 self
325 }
326
327 pub fn force(mut self, v: bool) -> Self {
336 self.mode.force = v;
337 self
338 }
339
340 pub fn delete(self) -> Branch<DeleteMode> {
342 Branch {
343 bin: self.bin,
344 global_opts: self.global_opts,
345 mode: DeleteMode {
346 force: self.mode.force,
347 },
348 }
349 }
350
351 pub fn stdin(self) -> Branch<StdinMode> {
353 Branch {
354 bin: self.bin,
355 global_opts: self.global_opts,
356 mode: StdinMode {
357 force: self.mode.force,
358 },
359 }
360 }
361}
362
363impl Branch<DeleteMode> {
368 pub fn get_force(&self) -> bool {
375 self.mode.force
376 }
377
378 pub fn set_force(&mut self, v: bool) -> &mut Self {
385 self.mode.force = v;
386 self
387 }
388
389 pub fn force(mut self, v: bool) -> Self {
396 self.mode.force = v;
397 self
398 }
399}
400
401impl Branch<StdinMode> {
406 pub fn get_force(&self) -> bool {
413 self.mode.force
414 }
415
416 pub fn set_force(&mut self, v: bool) -> &mut Self {
423 self.mode.force = v;
424 self
425 }
426
427 pub fn force(mut self, v: bool) -> Self {
434 self.mode.force = v;
435 self
436 }
437}
438
439impl Branch<StdoutMode> {
444 pub fn get_stream(&self) -> Option<&String> {
452 self.mode.stream.as_ref()
453 }
454
455 pub fn set_stream(&mut self, v: impl Into<String>) -> &mut Self {
463 self.mode.stream = Some(v.into());
464 self
465 }
466
467 pub fn stream(mut self, v: impl Into<String>) -> Self {
475 self.mode.stream = Some(v.into());
476 self
477 }
478
479 pub fn get_parent(&self) -> Option<&String> {
487 self.mode.parent.as_ref()
488 }
489
490 pub fn set_parent(&mut self, v: impl Into<String>) -> &mut Self {
498 self.mode.parent = Some(v.into());
499 self
500 }
501
502 pub fn parent(mut self, v: impl Into<String>) -> Self {
510 self.mode.parent = Some(v.into());
511 self
512 }
513}
514
515impl<M: ExclusiveOption> SubCommand for Branch<M> {
520 fn name(&self) -> &str {
521 "branch"
522 }
523
524 fn inject_local_args(&self, command: &mut Command) {
525 self.mode.inject_args(command);
526 }
527
528 fn global_opts(&self) -> Option<&GlobalOpts> {
529 Some(&self.global_opts)
530 }
531}
532
533impl ParameterizedSpawn<()> for Branch<GeneralMode> {
539 type Output = Child;
540 type Error = std::io::Error;
541
542 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
545 self.setup_command(&self.bin)
546 .stdout(Stdio::piped())
547 .stderr(Stdio::piped())
548 .spawn()
549 }
550}
551
552impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<GeneralMode> {
553 type Output = Child;
554 type Error = std::io::Error;
555
556 fn spawn_with(&mut self, (branchspec,): (I,)) -> Result<Self::Output, Self::Error> {
560 self.setup_command(&self.bin)
561 .arg(branchspec)
562 .stdout(Stdio::piped())
563 .stderr(Stdio::piped())
564 .spawn()
565 }
566}
567
568impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<DeleteMode> {
570 type Output = Child;
571 type Error = std::io::Error;
572
573 fn spawn_with(&mut self, (branchspec,): (I,)) -> Result<Self::Output, Self::Error> {
577 self.setup_command(&self.bin)
578 .arg(branchspec)
579 .stdout(Stdio::piped())
580 .stderr(Stdio::piped())
581 .spawn()
582 }
583}
584
585impl ParameterizedSpawn<()> for Branch<StdinMode> {
587 type Output = Child;
588 type Error = std::io::Error;
589
590 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
594 self.setup_command(&self.bin)
595 .stdin(Stdio::piped())
596 .stdout(Stdio::piped())
597 .stderr(Stdio::piped())
598 .spawn()
599 }
600}
601
602impl ParameterizedSpawn<(Stdio,)> for Branch<StdinMode> {
603 type Output = Child;
604 type Error = std::io::Error;
605
606 fn spawn_with(&mut self, (stdio,): (Stdio,)) -> Result<Self::Output, Self::Error> {
615 self.setup_command(&self.bin)
616 .stdin(stdio)
617 .stdout(Stdio::piped())
618 .stderr(Stdio::piped())
619 .spawn()
620 }
621}
622
623impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<StdoutMode> {
625 type Output = Child;
626 type Error = std::io::Error;
627
628 fn spawn_with(&mut self, (placeholder,): (I,)) -> Result<Self::Output, Self::Error> {
633 self.setup_command(&self.bin)
634 .arg(placeholder)
635 .stdout(Stdio::piped())
636 .stderr(Stdio::piped())
637 .spawn()
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use crate::cmd::args_of;
645
646 #[test]
647 fn bin_accessors_update_executable_path() {
648 let mut command = Branch::new("p4", GlobalOpts::new());
649 assert_eq!(command.get_bin(), Path::new("p4"));
650
651 command.set_bin("/usr/local/bin/p4");
652 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
653
654 let command = command.bin("p4.exe");
655 assert_eq!(command.get_bin(), Path::new("p4.exe"));
656 }
657
658 #[test]
665 fn general_form_without_force() {
666 let branch = Branch::new("p4", GlobalOpts::new()).force(false);
667 assert_eq!(args_of(&branch.setup_command("p4")), ["branch"]);
668 }
669
670 #[test]
671 fn general_form_with_force() {
672 let branch = Branch::new("p4", GlobalOpts::new()).force(true);
673 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-f"]);
674 }
675
676 #[test]
677 fn delete_form_from_unselected() {
678 let branch = Branch::new("p4", GlobalOpts::new()).delete();
679 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-d"]);
680 }
681
682 #[test]
683 fn delete_form_preserves_force_from_general() {
684 let branch = Branch::new("p4", GlobalOpts::new()).force(true).delete();
685 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-d", "-f"]);
686 }
687
688 #[test]
689 fn stdin_form_from_unselected() {
690 let branch = Branch::new("p4", GlobalOpts::new()).stdin();
691 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-i"]);
692 }
693
694 #[test]
695 fn stdin_form_preserves_force_from_general() {
696 let branch = Branch::new("p4", GlobalOpts::new()).force(true).stdin();
697 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-i", "-f"]);
698 }
699
700 #[test]
701 fn stdout_form_from_unselected() {
702 let branch = Branch::new("p4", GlobalOpts::new()).stdout();
703 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-o"]);
704 }
705
706 #[test]
707 fn stdout_with_stream() {
708 let branch = Branch::new("p4", GlobalOpts::new())
709 .stdout()
710 .stream("//depotstream1/5dev1");
711 assert_eq!(
712 args_of(&branch.setup_command("p4")),
713 ["branch", "-S", "//depotstream1/5dev1", "-o"]
714 );
715 }
716
717 #[test]
718 fn stdout_with_stream_and_parent() {
719 let branch = Branch::new("p4", GlobalOpts::new())
720 .stdout()
721 .stream("//stream1/5dev1")
722 .parent("//stream1/main5");
723 assert_eq!(
724 args_of(&branch.setup_command("p4")),
725 [
726 "branch",
727 "-S",
728 "//stream1/5dev1",
729 "-P",
730 "//stream1/main5",
731 "-o"
732 ]
733 );
734 }
735
736 #[test]
737 fn force_can_be_set_inside_general_delete_and_stdin() {
738 let general = Branch::new("p4", GlobalOpts::new())
739 .force(false)
740 .force(true);
741 assert_eq!(args_of(&general.setup_command("p4")), ["branch", "-f"]);
742
743 let delete = Branch::new("p4", GlobalOpts::new()).delete().force(true);
744 assert_eq!(args_of(&delete.setup_command("p4")), ["branch", "-d", "-f"]);
745
746 let stdin = Branch::new("p4", GlobalOpts::new()).stdin().force(true);
747 assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i", "-f"]);
748 }
749
750 #[test]
751 fn force_can_be_unset() {
752 let branch = Branch::new("p4", GlobalOpts::new())
753 .force(true)
754 .force(false);
755 assert_eq!(args_of(&branch.setup_command("p4")), ["branch"]);
756 }
757
758 #[test]
759 fn setters_update_the_same_command() {
760 let mut branch = Branch::new("p4", GlobalOpts::new()).force(false);
761 branch.set_force(true);
762 assert!(branch.get_force());
763
764 let mut stdout = Branch::new("p4", GlobalOpts::new()).stdout();
765 stdout.set_stream("//depot/main");
766 stdout.set_parent("//depot/dev");
767 assert_eq!(stdout.get_stream(), Some(&String::from("//depot/main")));
768 assert_eq!(stdout.get_parent(), Some(&String::from("//depot/dev")));
769 }
770
771 #[test]
772 fn global_opts_are_injected_once() {
773 let branch = Branch::new("p4", GlobalOpts::new().port("localhost:1666")).force(false);
774 assert_eq!(
775 args_of(&branch.setup_command("p4")),
776 ["-p", "localhost:1666", "branch"]
777 );
778 }
779
780 #[test]
782 fn spawn_argument_counts_per_form() {
783 let plain = Branch::new("p4", GlobalOpts::new()).force(false);
785 assert_eq!(args_of(&plain.setup_command("p4")), ["branch"]);
786 let mut with_bs = plain.setup_command("p4");
787 with_bs.arg("mybranch");
788 assert_eq!(args_of(&with_bs), ["branch", "mybranch"]);
789
790 let delete = Branch::new("p4", GlobalOpts::new()).force(true).delete();
792 let mut delete_bs = delete.setup_command("p4");
793 delete_bs.arg("oldbranch");
794 assert_eq!(args_of(&delete_bs), ["branch", "-d", "-f", "oldbranch"]);
795
796 let stdin = Branch::new("p4", GlobalOpts::new()).stdin();
798 assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i"]);
799
800 let stdout = Branch::new("p4", GlobalOpts::new()).stdout();
802 let mut stdout_ph = stdout.setup_command("p4");
803 stdout_ph.arg("placeholder1");
804 assert_eq!(args_of(&stdout_ph), ["branch", "-o", "placeholder1"]);
805
806 let stream = Branch::new("p4", GlobalOpts::new())
808 .stdout()
809 .stream("//depotstream1/5dev1")
810 .parent("//stream1/main5");
811 let mut stream_ph = stream.setup_command("p4");
812 stream_ph.arg("placeholder2");
813 assert_eq!(
814 args_of(&stream_ph),
815 [
816 "branch",
817 "-S",
818 "//depotstream1/5dev1",
819 "-P",
820 "//stream1/main5",
821 "-o",
822 "placeholder2"
823 ]
824 );
825 }
826}