Skip to main content

perforce_cli/cmd/
branch.rs

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// ---------------------------------------------------------------------------
13// Mode structs
14// ---------------------------------------------------------------------------
15
16/// The general form: `p4 branch [-f] branchspec`.
17///
18/// Create or edit a branch mapping. The `-f` flag permits administrators to
19/// change the modification date of the branch mapping.
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub struct GeneralMode {
22    force: bool,
23}
24
25/// The delete form: `p4 branch -d [-f] branchspec`.
26///
27/// Delete the named branch mapping. The `-f` flag permits P4 Server
28/// administrators to delete branches they don't own.
29#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct DeleteMode {
31    force: bool,
32}
33
34/// The stdin form: `p4 branch -i [-f]`.
35///
36/// Read the branch mapping from standard input without invoking an editor.
37#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct StdinMode {
39    force: bool,
40}
41
42/// The stdout form state in which `-S stream` has been supplied.
43///
44/// Carries the stream name and unlocks the `-P parent` option, which
45/// requires `-S` and evaluates the stream as if it were a child of the
46/// given parent.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct StreamSelected {
49    stream: String,
50
51    parent: Option<String>,
52}
53
54/// The stdout form: `p4 branch [-S stream [-P parent]] -o placeholder`.
55///
56/// Write the branch mapping to standard output without invoking an editor.
57/// The optional `-S stream` displays the mapping generated for the specified
58/// stream; `-P parent` requires `-S` and evaluates the stream as if it were
59/// a child of the given parent.
60///
61/// The `S` type parameter tracks whether `-S` has been supplied: the
62/// [`Unselected`] state carries no stream, and supplying a stream with
63/// `Branch::stream` transitions to the [`StreamSelected`] state, which
64/// unlocks the `-P` option.
65#[derive(Debug, Clone, Default)]
66pub struct StdoutMode<S = Unselected> {
67    stream_opts: S,
68}
69
70// ---------------------------------------------------------------------------
71// ExclusiveOption implementations
72// ---------------------------------------------------------------------------
73
74impl 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// ---------------------------------------------------------------------------
117// Command struct
118// ---------------------------------------------------------------------------
119
120/// Type-safe builder for `p4 branch`.
121///
122/// Create or edit a branch mapping and its view.
123///
124/// # Syntax
125///
126/// ```text
127/// p4 [g-opts] branch [-f] branchspec
128/// p4 [g-opts] branch -d [-f] branchspec
129/// p4 [g-opts] branch -i [-f]
130/// p4 [g-opts] branch [-S stream] -o placeholderForStreamBranchSpecName
131/// p4 [g-opts] branch [-S stream -P parent] -o placeholderForStreamBranchSpecName
132/// ```
133///
134/// The `M` type parameter tracks the command form at compile time, starting
135/// as [`Unselected`] from [`Branch::new`]. Enter a form with one of
136/// [`Self::force`], [`Self::delete`], [`Self::stdin`], or [`Self::stdout`]:
137/// - [`GeneralMode`] (entered via [`Self::force`]): create or edit a branch
138///   mapping; can transition to [`DeleteMode`] or [`StdinMode`] preserving
139///   the `-f` flag.
140/// - [`DeleteMode`] (`-d`, entered via [`Self::delete`]): delete a branch
141///   mapping.
142/// - [`StdinMode`] (`-i`, entered via [`Self::stdin`]): read the branch
143///   mapping from standard input.
144/// - [`StdoutMode`] (`-o`, entered via [`Self::stdout`]): write the branch
145///   mapping to standard output; `Branch::stream` supplies `-S` and
146///   transitions to the [`StreamSelected`] state, which unlocks
147///   `Branch::parent` (`-P` requires `-S`).
148#[derive(Debug, Clone, Default)]
149pub struct Branch<M = Unselected> {
150    bin: PathBuf,
151
152    global_opts: GlobalOpts,
153
154    mode: M,
155}
156
157// ---------------------------------------------------------------------------
158// Constructor
159// ---------------------------------------------------------------------------
160
161impl Branch<Unselected> {
162    /// Creates a new `p4 branch` command with no form selected.
163    ///
164    /// `bin` is the path to the Perforce command-line executable. Use
165    /// [`Self::force`], [`Self::delete`], [`Self::stdin`], or
166    /// [`Self::stdout`] to select a form before spawning.
167    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    /// Enters the general form: `p4 branch [-f] branchspec`.
176    ///
177    /// `v` sets the initial state of the `-f` flag.
178    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    /// Enters the delete form: `p4 branch -d [-f] branchspec`.
187    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    /// Enters the stdin form: `p4 branch -i [-f]`.
196    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    /// Enters the stdout form: `p4 branch [-S stream [-P parent]] -o
205    /// placeholder`.
206    ///
207    /// Use `Branch::stream` on the entered form to supply `-S`; that
208    /// transitions to the [`StreamSelected`] state and unlocks
209    /// `Branch::parent` (`-P` requires `-S`).
210    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
219// ---------------------------------------------------------------------------
220// Shared accessors
221// ---------------------------------------------------------------------------
222
223impl<M> Branch<M> {
224    /// Get the Perforce command path.
225    pub fn get_bin(&self) -> &Path {
226        &self.bin
227    }
228
229    /// Set the Perforce command path.
230    pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
231        self.bin = bin.into();
232        self
233    }
234
235    /// Set the Perforce command path.
236    pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
237        self.set_bin(bin);
238        self
239    }
240
241    /// # Description
242    ///
243    /// g-opts
244    ///
245    #[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    /// # Description
267    ///
268    /// g-opts
269    ///
270    #[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    /// # Description
293    ///
294    /// g-opts
295    ///
296    #[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
319// ---------------------------------------------------------------------------
320// GeneralMode: force accessor + transitions
321// ---------------------------------------------------------------------------
322
323impl Branch<GeneralMode> {
324    /// # Description
325    ///
326    /// -f
327    ///
328    /// Force option. Combined with `-d`, permits P4 Server administrators to
329    /// delete branches they don't own. Also permits administrators to change
330    /// the modification date of the branch mapping (the `Update` field is
331    /// writable when using the `-f` option).
332    pub fn get_force(&self) -> bool {
333        self.mode.force
334    }
335
336    /// # Description
337    ///
338    /// -f
339    ///
340    /// Force option. Combined with `-d`, permits P4 Server administrators to
341    /// delete branches they don't own. Also permits administrators to change
342    /// the modification date of the branch mapping (the `Update` field is
343    /// writable when using the `-f` option).
344    pub fn set_force(&mut self, v: bool) -> &mut Self {
345        self.mode.force = v;
346        self
347    }
348
349    /// # Description
350    ///
351    /// -f
352    ///
353    /// Force option. Combined with `-d`, permits P4 Server administrators to
354    /// delete branches they don't own. Also permits administrators to change
355    /// the modification date of the branch mapping (the `Update` field is
356    /// writable when using the `-f` option).
357    pub fn force(mut self, v: bool) -> Self {
358        self.mode.force = v;
359        self
360    }
361
362    /// Transitions to the delete form (`-d`), preserving the `-f` flag.
363    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    /// Transitions to the stdin form (`-i`), preserving the `-f` flag.
374    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
385// ---------------------------------------------------------------------------
386// DeleteMode: force accessor
387// ---------------------------------------------------------------------------
388
389impl Branch<DeleteMode> {
390    /// # Description
391    ///
392    /// -f
393    ///
394    /// Force option. Permits P4 Server administrators to delete branches they
395    /// don't own.
396    pub fn get_force(&self) -> bool {
397        self.mode.force
398    }
399
400    /// # Description
401    ///
402    /// -f
403    ///
404    /// Force option. Permits P4 Server administrators to delete branches they
405    /// don't own.
406    pub fn set_force(&mut self, v: bool) -> &mut Self {
407        self.mode.force = v;
408        self
409    }
410
411    /// # Description
412    ///
413    /// -f
414    ///
415    /// Force option. Permits P4 Server administrators to delete branches they
416    /// don't own.
417    pub fn force(mut self, v: bool) -> Self {
418        self.mode.force = v;
419        self
420    }
421}
422
423// ---------------------------------------------------------------------------
424// StdinMode: force accessor
425// ---------------------------------------------------------------------------
426
427impl Branch<StdinMode> {
428    /// # Description
429    ///
430    /// -f
431    ///
432    /// Force option. Permits administrators to change the modification date of
433    /// the branch mapping.
434    pub fn get_force(&self) -> bool {
435        self.mode.force
436    }
437
438    /// # Description
439    ///
440    /// -f
441    ///
442    /// Force option. Permits administrators to change the modification date of
443    /// the branch mapping.
444    pub fn set_force(&mut self, v: bool) -> &mut Self {
445        self.mode.force = v;
446        self
447    }
448
449    /// # Description
450    ///
451    /// -f
452    ///
453    /// Force option. Permits administrators to change the modification date of
454    /// the branch mapping.
455    pub fn force(mut self, v: bool) -> Self {
456        self.mode.force = v;
457        self
458    }
459}
460
461// ---------------------------------------------------------------------------
462// StdoutMode<Unselected>: -S transition
463// ---------------------------------------------------------------------------
464
465impl Branch<StdoutMode<Unselected>> {
466    /// # Description
467    ///
468    /// -S stream
469    ///
470    /// Display the mapping generated for the specified stream. This option
471    /// enables you to see how change is propagated between the stream and its
472    /// parent.
473    ///
474    /// Transitions this command to the [`StreamSelected`] state, which
475    /// unlocks the `-P` option.
476    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
490// ---------------------------------------------------------------------------
491// StdoutMode<StreamSelected>: stream and parent accessors
492// ---------------------------------------------------------------------------
493
494impl Branch<StdoutMode<StreamSelected>> {
495    /// # Description
496    ///
497    /// -S stream
498    ///
499    /// The stream whose generated mapping is displayed.
500    pub fn get_stream(&self) -> &str {
501        &self.mode.stream_opts.stream
502    }
503
504    /// # Description
505    ///
506    /// -S stream
507    ///
508    /// The stream whose generated mapping is displayed.
509    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    /// # Description
515    ///
516    /// -S stream
517    ///
518    /// The stream whose generated mapping is displayed. Preserves any `-P`
519    /// parent already set.
520    pub fn stream(mut self, v: impl Into<String>) -> Self {
521        self.mode.stream_opts.stream = v.into();
522        self
523    }
524
525    /// # Description
526    ///
527    /// -P parent
528    ///
529    /// For a specified stream, display the mapping that is generated by
530    /// hypothetically treating the stream as if it were a child of the
531    /// specified parent.
532    pub fn get_parent(&self) -> Option<&String> {
533        self.mode.stream_opts.parent.as_ref()
534    }
535
536    /// # Description
537    ///
538    /// -P parent
539    ///
540    /// For a specified stream, display the mapping that is generated by
541    /// hypothetically treating the stream as if it were a child of the
542    /// specified parent.
543    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    /// # Description
549    ///
550    /// -P parent
551    ///
552    /// For a specified stream, display the mapping that is generated by
553    /// hypothetically treating the stream as if it were a child of the
554    /// specified parent.
555    pub fn parent(mut self, v: impl Into<String>) -> Self {
556        self.mode.stream_opts.parent = Some(v.into());
557        self
558    }
559}
560
561// ---------------------------------------------------------------------------
562// SubCommand
563// ---------------------------------------------------------------------------
564
565impl<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
579// ---------------------------------------------------------------------------
580// Spawning
581// ---------------------------------------------------------------------------
582
583// General form: 0 args (open editor) or 1 branchspec.
584impl ParameterizedSpawn<()> for Branch<GeneralMode> {
585    type Output = Child;
586    type Error = std::io::Error;
587
588    /// Spawns `p4 branch` (the general form) without a branchspec argument,
589    /// opening the editor to create a new branch mapping.
590    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    /// Spawns `p4 branch` with the given `branchspec` argument as a child
603    /// process with piped standard output and error streams; use the
604    /// returned [`Child`] handle to wait for it or interact with it.
605    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
614// Delete form: exactly 1 branchspec.
615impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<DeleteMode> {
616    type Output = Child;
617    type Error = std::io::Error;
618
619    /// Spawns `p4 branch -d` with the given `branchspec` argument as a child
620    /// process with piped standard output and error streams; use the
621    /// returned [`Child`] handle to wait for it or interact with it.
622    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
631// Stdin form: no positional argument; stdin is piped.
632impl ParameterizedSpawn<()> for Branch<StdinMode> {
633    type Output = Child;
634    type Error = std::io::Error;
635
636    /// Spawns `p4 branch -i` with a piped standard input stream (and piped
637    /// standard output and error streams); write the branch mapping
638    /// specification to the returned [`Child`]'s stdin handle.
639    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    /// Spawns `p4 branch -i` with the given standard input configuration and
653    /// piped standard output and error streams; use the returned [`Child`]
654    /// handle to wait for it or interact with it.
655    ///
656    /// Pass `Stdio::piped()` to obtain a writable `child.stdin` handle and
657    /// feed the branch mapping specification into it; pass `Stdio::inherit()`
658    /// to read the specification from the spawning process's own standard
659    /// input.
660    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
669// Stdout form: exactly 1 placeholder.
670impl<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    /// Spawns `p4 branch [-S stream [-P parent]] -o` with the given placeholder
679    /// argument as a child process with piped standard output and error
680    /// streams; use the returned [`Child`] handle to wait for it or interact
681    /// with it.
682    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    // Compile-time rejection: Branch<Unselected> has no spawn impl.
709    // Compile-time rejection: Branch<StdoutMode> has no `force()` method
710    // (stdout form does not accept -f).
711    // Compile-time rejection: Branch<StdoutMode> (no -S yet) has no
712    // `parent()`/`set_parent()`/`get_parent()` method (-P requires -S).
713
714    /// Dry-run checks of the assembled `p4 branch` command line; no process
715    /// is spawned.
716    #[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    // Spawn argument counts per form.
854    #[test]
855    fn spawn_argument_counts_per_form() {
856        // General: 0 or 1 branchspec.
857        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        // Delete: exactly 1 branchspec.
864        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        // Stdin: no positional; stdin is configured separately.
870        let stdin = Branch::new("p4", GlobalOpts::new()).stdin();
871        assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i"]);
872
873        // Stdout: exactly 1 placeholder.
874        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        // Stdout with stream and parent: exactly 1 placeholder.
880        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}