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, PartialEq, Eq)]
48pub struct StreamSelected {
49 stream: String,
50
51 parent: Option<String>,
52}
53
54#[derive(Debug, Clone, Default)]
66pub struct StdoutMode<S = Unselected> {
67 stream_opts: S,
68}
69
70impl ExclusiveOption for GeneralMode {
75 fn inject_args(&self, command: &mut Command) {
76 if self.force {
77 command.arg("-f");
78 }
79 }
80}
81
82impl ExclusiveOption for DeleteMode {
83 fn inject_args(&self, command: &mut Command) {
84 command.arg("-d");
85 if self.force {
86 command.arg("-f");
87 }
88 }
89}
90
91impl ExclusiveOption for StdinMode {
92 fn inject_args(&self, command: &mut Command) {
93 command.arg("-i");
94 if self.force {
95 command.arg("-f");
96 }
97 }
98}
99
100impl ExclusiveOption for StdoutMode<Unselected> {
101 fn inject_args(&self, command: &mut Command) {
102 command.arg("-o");
103 }
104}
105
106impl ExclusiveOption for StdoutMode<StreamSelected> {
107 fn inject_args(&self, command: &mut Command) {
108 command.arg("-S").arg(&self.stream_opts.stream);
109 if let Some(parent) = &self.stream_opts.parent {
110 command.arg("-P").arg(parent);
111 }
112 command.arg("-o");
113 }
114}
115
116#[derive(Debug, Clone, Default)]
149pub struct Branch<M = Unselected> {
150 bin: PathBuf,
151
152 global_opts: GlobalOpts,
153
154 mode: M,
155}
156
157impl Branch<Unselected> {
162 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
168 Self {
169 bin: bin.into(),
170 global_opts,
171 ..Default::default()
172 }
173 }
174
175 pub fn force(self, v: bool) -> Branch<GeneralMode> {
179 Branch {
180 bin: self.bin,
181 global_opts: self.global_opts,
182 mode: GeneralMode { force: v },
183 }
184 }
185
186 pub fn delete(self) -> Branch<DeleteMode> {
188 Branch {
189 bin: self.bin,
190 global_opts: self.global_opts,
191 mode: DeleteMode { force: false },
192 }
193 }
194
195 pub fn stdin(self) -> Branch<StdinMode> {
197 Branch {
198 bin: self.bin,
199 global_opts: self.global_opts,
200 mode: StdinMode { force: false },
201 }
202 }
203
204 pub fn stdout(self) -> Branch<StdoutMode> {
211 Branch {
212 bin: self.bin,
213 global_opts: self.global_opts,
214 mode: StdoutMode::default(),
215 }
216 }
217}
218
219impl<M> Branch<M> {
224 pub fn get_bin(&self) -> &Path {
226 &self.bin
227 }
228
229 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
231 self.bin = bin.into();
232 self
233 }
234
235 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
237 self.set_bin(bin);
238 self
239 }
240
241 #[cfg_attr(
246 feature = "lt2014_2",
247 doc = "See the [Global Options](GlobalOpts) section."
248 )]
249 #[cfg_attr(
250 all(feature = "lt2015_1", not(feature = "lt2014_2")),
251 doc = "See the [“Global Options”](GlobalOpts) section."
252 )]
253 #[cfg_attr(
254 all(feature = "lt2017_1", not(feature = "lt2015_1")),
255 doc = "See [“Global Options”](GlobalOpts)."
256 )]
257 #[cfg_attr(
258 all(feature = "lt2018_2", not(feature = "lt2017_1")),
259 doc = "See [Global Options](GlobalOpts)."
260 )]
261 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
262 pub fn get_global_opts(&self) -> &GlobalOpts {
263 &self.global_opts
264 }
265
266 #[cfg_attr(
271 feature = "lt2014_2",
272 doc = "See the [Global Options](GlobalOpts) section."
273 )]
274 #[cfg_attr(
275 all(feature = "lt2015_1", not(feature = "lt2014_2")),
276 doc = "See the [“Global Options”](GlobalOpts) section."
277 )]
278 #[cfg_attr(
279 all(feature = "lt2017_1", not(feature = "lt2015_1")),
280 doc = "See [“Global Options”](GlobalOpts)."
281 )]
282 #[cfg_attr(
283 all(feature = "lt2018_2", not(feature = "lt2017_1")),
284 doc = "See [Global Options](GlobalOpts)."
285 )]
286 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
287 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
288 self.global_opts = v;
289 self
290 }
291
292 #[cfg_attr(
297 feature = "lt2014_2",
298 doc = "See the [Global Options](GlobalOpts) section."
299 )]
300 #[cfg_attr(
301 all(feature = "lt2015_1", not(feature = "lt2014_2")),
302 doc = "See the [“Global Options”](GlobalOpts) section."
303 )]
304 #[cfg_attr(
305 all(feature = "lt2017_1", not(feature = "lt2015_1")),
306 doc = "See [“Global Options”](GlobalOpts)."
307 )]
308 #[cfg_attr(
309 all(feature = "lt2018_2", not(feature = "lt2017_1")),
310 doc = "See [Global Options](GlobalOpts)."
311 )]
312 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
313 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
314 self.global_opts = v;
315 self
316 }
317}
318
319impl Branch<GeneralMode> {
324 pub fn get_force(&self) -> bool {
333 self.mode.force
334 }
335
336 pub fn set_force(&mut self, v: bool) -> &mut Self {
345 self.mode.force = v;
346 self
347 }
348
349 pub fn force(mut self, v: bool) -> Self {
358 self.mode.force = v;
359 self
360 }
361
362 pub fn delete(self) -> Branch<DeleteMode> {
364 Branch {
365 bin: self.bin,
366 global_opts: self.global_opts,
367 mode: DeleteMode {
368 force: self.mode.force,
369 },
370 }
371 }
372
373 pub fn stdin(self) -> Branch<StdinMode> {
375 Branch {
376 bin: self.bin,
377 global_opts: self.global_opts,
378 mode: StdinMode {
379 force: self.mode.force,
380 },
381 }
382 }
383}
384
385impl Branch<DeleteMode> {
390 pub fn get_force(&self) -> bool {
397 self.mode.force
398 }
399
400 pub fn set_force(&mut self, v: bool) -> &mut Self {
407 self.mode.force = v;
408 self
409 }
410
411 pub fn force(mut self, v: bool) -> Self {
418 self.mode.force = v;
419 self
420 }
421}
422
423impl Branch<StdinMode> {
428 pub fn get_force(&self) -> bool {
435 self.mode.force
436 }
437
438 pub fn set_force(&mut self, v: bool) -> &mut Self {
445 self.mode.force = v;
446 self
447 }
448
449 pub fn force(mut self, v: bool) -> Self {
456 self.mode.force = v;
457 self
458 }
459}
460
461impl Branch<StdoutMode<Unselected>> {
466 pub fn stream(self, v: impl Into<String>) -> Branch<StdoutMode<StreamSelected>> {
477 Branch {
478 bin: self.bin,
479 global_opts: self.global_opts,
480 mode: StdoutMode {
481 stream_opts: StreamSelected {
482 stream: v.into(),
483 parent: None,
484 },
485 },
486 }
487 }
488}
489
490impl Branch<StdoutMode<StreamSelected>> {
495 pub fn get_stream(&self) -> &str {
501 &self.mode.stream_opts.stream
502 }
503
504 pub fn set_stream(&mut self, v: impl Into<String>) -> &mut Self {
510 self.mode.stream_opts.stream = v.into();
511 self
512 }
513
514 pub fn stream(mut self, v: impl Into<String>) -> Self {
521 self.mode.stream_opts.stream = v.into();
522 self
523 }
524
525 pub fn get_parent(&self) -> Option<&String> {
533 self.mode.stream_opts.parent.as_ref()
534 }
535
536 pub fn set_parent(&mut self, v: impl Into<String>) -> &mut Self {
544 self.mode.stream_opts.parent = Some(v.into());
545 self
546 }
547
548 pub fn parent(mut self, v: impl Into<String>) -> Self {
556 self.mode.stream_opts.parent = Some(v.into());
557 self
558 }
559}
560
561impl<M: ExclusiveOption> SubCommand for Branch<M> {
566 fn name(&self) -> &str {
567 "branch"
568 }
569
570 fn inject_local_args(&self, command: &mut Command) {
571 self.mode.inject_args(command);
572 }
573
574 fn global_opts(&self) -> Option<&GlobalOpts> {
575 Some(&self.global_opts)
576 }
577}
578
579impl ParameterizedSpawn<()> for Branch<GeneralMode> {
585 type Output = Child;
586 type Error = std::io::Error;
587
588 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
591 self.setup_command(&self.bin)
592 .stdout(Stdio::piped())
593 .stderr(Stdio::piped())
594 .spawn()
595 }
596}
597
598impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<GeneralMode> {
599 type Output = Child;
600 type Error = std::io::Error;
601
602 fn spawn_with(&mut self, (branchspec,): (I,)) -> Result<Self::Output, Self::Error> {
606 self.setup_command(&self.bin)
607 .arg(branchspec)
608 .stdout(Stdio::piped())
609 .stderr(Stdio::piped())
610 .spawn()
611 }
612}
613
614impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<DeleteMode> {
616 type Output = Child;
617 type Error = std::io::Error;
618
619 fn spawn_with(&mut self, (branchspec,): (I,)) -> Result<Self::Output, Self::Error> {
623 self.setup_command(&self.bin)
624 .arg(branchspec)
625 .stdout(Stdio::piped())
626 .stderr(Stdio::piped())
627 .spawn()
628 }
629}
630
631impl ParameterizedSpawn<()> for Branch<StdinMode> {
633 type Output = Child;
634 type Error = std::io::Error;
635
636 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
640 self.setup_command(&self.bin)
641 .stdin(Stdio::piped())
642 .stdout(Stdio::piped())
643 .stderr(Stdio::piped())
644 .spawn()
645 }
646}
647
648impl ParameterizedSpawn<(Stdio,)> for Branch<StdinMode> {
649 type Output = Child;
650 type Error = std::io::Error;
651
652 fn spawn_with(&mut self, (stdio,): (Stdio,)) -> Result<Self::Output, Self::Error> {
661 self.setup_command(&self.bin)
662 .stdin(stdio)
663 .stdout(Stdio::piped())
664 .stderr(Stdio::piped())
665 .spawn()
666 }
667}
668
669impl<S, I> ParameterizedSpawn<(I,)> for Branch<StdoutMode<S>>
671where
672 StdoutMode<S>: ExclusiveOption,
673 I: AsRef<OsStr>,
674{
675 type Output = Child;
676 type Error = std::io::Error;
677
678 fn spawn_with(&mut self, (placeholder,): (I,)) -> Result<Self::Output, Self::Error> {
683 self.setup_command(&self.bin)
684 .arg(placeholder)
685 .stdout(Stdio::piped())
686 .stderr(Stdio::piped())
687 .spawn()
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use super::*;
694 use crate::cmd::args_of;
695
696 #[test]
697 fn bin_accessors_update_executable_path() {
698 let mut command = Branch::new("p4", GlobalOpts::new());
699 assert_eq!(command.get_bin(), Path::new("p4"));
700
701 command.set_bin("/usr/local/bin/p4");
702 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
703
704 let command = command.bin("p4.exe");
705 assert_eq!(command.get_bin(), Path::new("p4.exe"));
706 }
707
708 #[test]
717 fn general_form_without_force() {
718 let branch = Branch::new("p4", GlobalOpts::new()).force(false);
719 assert_eq!(args_of(&branch.setup_command("p4")), ["branch"]);
720 }
721
722 #[test]
723 fn general_form_with_force() {
724 let branch = Branch::new("p4", GlobalOpts::new()).force(true);
725 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-f"]);
726 }
727
728 #[test]
729 fn delete_form_from_unselected() {
730 let branch = Branch::new("p4", GlobalOpts::new()).delete();
731 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-d"]);
732 }
733
734 #[test]
735 fn delete_form_preserves_force_from_general() {
736 let branch = Branch::new("p4", GlobalOpts::new()).force(true).delete();
737 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-d", "-f"]);
738 }
739
740 #[test]
741 fn stdin_form_from_unselected() {
742 let branch = Branch::new("p4", GlobalOpts::new()).stdin();
743 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-i"]);
744 }
745
746 #[test]
747 fn stdin_form_preserves_force_from_general() {
748 let branch = Branch::new("p4", GlobalOpts::new()).force(true).stdin();
749 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-i", "-f"]);
750 }
751
752 #[test]
753 fn stdout_form_from_unselected() {
754 let branch = Branch::new("p4", GlobalOpts::new()).stdout();
755 assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-o"]);
756 }
757
758 #[test]
759 fn stdout_with_stream() {
760 let branch = Branch::new("p4", GlobalOpts::new())
761 .stdout()
762 .stream("//depotstream1/5dev1");
763 assert_eq!(
764 args_of(&branch.setup_command("p4")),
765 ["branch", "-S", "//depotstream1/5dev1", "-o"]
766 );
767 }
768
769 #[test]
770 fn stdout_with_stream_and_parent() {
771 let branch = Branch::new("p4", GlobalOpts::new())
772 .stdout()
773 .stream("//stream1/5dev1")
774 .parent("//stream1/main5");
775 assert_eq!(
776 args_of(&branch.setup_command("p4")),
777 [
778 "branch",
779 "-S",
780 "//stream1/5dev1",
781 "-P",
782 "//stream1/main5",
783 "-o"
784 ]
785 );
786 }
787
788 #[test]
789 fn renaming_stream_preserves_parent() {
790 let branch = Branch::new("p4", GlobalOpts::new())
791 .stdout()
792 .stream("//stream1/5dev1")
793 .parent("//stream1/main5")
794 .stream("//stream2/5dev2");
795 assert_eq!(
796 args_of(&branch.setup_command("p4")),
797 [
798 "branch",
799 "-S",
800 "//stream2/5dev2",
801 "-P",
802 "//stream1/main5",
803 "-o"
804 ]
805 );
806 }
807
808 #[test]
809 fn force_can_be_set_inside_general_delete_and_stdin() {
810 let general = Branch::new("p4", GlobalOpts::new())
811 .force(false)
812 .force(true);
813 assert_eq!(args_of(&general.setup_command("p4")), ["branch", "-f"]);
814
815 let delete = Branch::new("p4", GlobalOpts::new()).delete().force(true);
816 assert_eq!(args_of(&delete.setup_command("p4")), ["branch", "-d", "-f"]);
817
818 let stdin = Branch::new("p4", GlobalOpts::new()).stdin().force(true);
819 assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i", "-f"]);
820 }
821
822 #[test]
823 fn force_can_be_unset() {
824 let branch = Branch::new("p4", GlobalOpts::new())
825 .force(true)
826 .force(false);
827 assert_eq!(args_of(&branch.setup_command("p4")), ["branch"]);
828 }
829
830 #[test]
831 fn setters_update_the_same_command() {
832 let mut branch = Branch::new("p4", GlobalOpts::new()).force(false);
833 branch.set_force(true);
834 assert!(branch.get_force());
835
836 let mut stdout = Branch::new("p4", GlobalOpts::new())
837 .stdout()
838 .stream("//depot/main");
839 stdout.set_parent("//depot/dev");
840 assert_eq!(stdout.get_stream(), "//depot/main");
841 assert_eq!(stdout.get_parent(), Some(&String::from("//depot/dev")));
842 }
843
844 #[test]
845 fn global_opts_are_injected_once() {
846 let branch = Branch::new("p4", GlobalOpts::new().port("localhost:1666")).force(false);
847 assert_eq!(
848 args_of(&branch.setup_command("p4")),
849 ["-p", "localhost:1666", "branch"]
850 );
851 }
852
853 #[test]
855 fn spawn_argument_counts_per_form() {
856 let plain = Branch::new("p4", GlobalOpts::new()).force(false);
858 assert_eq!(args_of(&plain.setup_command("p4")), ["branch"]);
859 let mut with_bs = plain.setup_command("p4");
860 with_bs.arg("mybranch");
861 assert_eq!(args_of(&with_bs), ["branch", "mybranch"]);
862
863 let delete = Branch::new("p4", GlobalOpts::new()).force(true).delete();
865 let mut delete_bs = delete.setup_command("p4");
866 delete_bs.arg("oldbranch");
867 assert_eq!(args_of(&delete_bs), ["branch", "-d", "-f", "oldbranch"]);
868
869 let stdin = Branch::new("p4", GlobalOpts::new()).stdin();
871 assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i"]);
872
873 let stdout = Branch::new("p4", GlobalOpts::new()).stdout();
875 let mut stdout_ph = stdout.setup_command("p4");
876 stdout_ph.arg("placeholder1");
877 assert_eq!(args_of(&stdout_ph), ["branch", "-o", "placeholder1"]);
878
879 let stream = Branch::new("p4", GlobalOpts::new())
881 .stdout()
882 .stream("//depotstream1/5dev1")
883 .parent("//stream1/main5");
884 let mut stream_ph = stream.setup_command("p4");
885 stream_ph.arg("placeholder2");
886 assert_eq!(
887 args_of(&stream_ph),
888 [
889 "branch",
890 "-S",
891 "//depotstream1/5dev1",
892 "-P",
893 "//stream1/main5",
894 "-o",
895 "placeholder2"
896 ]
897 );
898 }
899}