Skip to main content

perforce_cli/cmd/
diff2.rs

1use std::{
2    ffi::OsStr,
3    path::PathBuf,
4    process::{Child, Command, Stdio},
5};
6
7use super::{DiffOptions, ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::{ParameterizedSpawn, SpawnExt, SpawnExt1, SpawnExt2};
11
12/// The `-b branch` sub-mode of the [`DepotContent`] state: diff files in
13/// two branched codelines through a branch mapping.
14///
15/// Entered with [`Diff2::branch`].
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct BranchMode {
18    branch: String,
19}
20
21impl ExclusiveOption for BranchMode {
22    fn inject_args(&self, command: &mut Command) {
23        command.arg("-b").arg(&self.branch);
24    }
25}
26
27/// The `-S stream` sub-mode of the [`DepotContent`] state: diff a stream
28/// with its parent.
29///
30/// Entered with [`Diff2::stream`].
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct StreamMode {
33    stream: String,
34
35    parent: Option<String>,
36}
37
38impl ExclusiveOption for StreamMode {
39    fn inject_args(&self, command: &mut Command) {
40        command.arg("-S").arg(&self.stream);
41
42        if let Some(parent) = &self.parent {
43            command.arg("-P").arg(parent);
44        }
45    }
46}
47
48/// Stream spec mode of `p4 diff2` (`-As`): diff two arbitrary stream specs
49/// against each other.
50///
51/// Entered with [`Diff2::stream_spec_mode`]; the two stream specs to compare
52/// are passed as the arguments of the spawned command. As the `-As` form
53/// accepts only `-doptions` besides g-opts, this mode offers neither the
54/// depot content options nor a sub-mode.
55#[derive(Debug, Clone, Default, PartialEq, Eq)]
56pub struct StreamSpecMode;
57
58impl ExclusiveOption for StreamSpecMode {
59    fn inject_args(&self, command: &mut Command) {
60        command.arg("-As");
61    }
62}
63
64/// File arguments of the [`DepotContent`] state's [`BranchMode`] and
65/// [`StreamMode`] sub-modes.
66///
67/// The prototype `[[fromfile[rev]] tofile[rev]]` allows either no file
68/// arguments ([`Diff2Parameters::None`]), the target side only
69/// ([`Diff2Parameters::To`]), or both sides ([`Diff2Parameters::Full`]).
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Diff2Parameters<'a> {
72    /// Diff `fromfile[rev]` against `tofile[rev]`.
73    Full { from: &'a OsStr, to: &'a OsStr },
74    /// Diff the whole branch or stream, limited to the target side
75    /// `tofile[rev]`.
76    To(&'a OsStr),
77    /// Diff the whole branch or stream without limiting files.
78    None,
79}
80
81/// The depot content modes of `p4 diff2`: the default form comparing two
82/// depot paths, or the `-b branch` / `-S stream` sub-modes.
83///
84/// Entered with [`Diff2::differing_only`], [`Diff2::quiet_mode`],
85/// [`Diff2::diff_nontext`], [`Diff2::unified_patch`], [`Diff2::branch`], or
86/// [`Diff2::stream`].
87///
88/// The `M` type parameter tracks the sub-mode at compile time. The default
89/// [`Unselected`] state diffs the file pair given as spawn arguments;
90/// [`Diff2::branch`] transitions to the [`BranchMode`] sub-mode, and
91/// [`Diff2::stream`] transitions to the [`StreamMode`] sub-mode. The
92/// `[-Od -q -t -u]` options are shared by all sub-modes and therefore
93/// stored here.
94#[derive(Debug, Clone, Default)]
95pub struct DepotContent<M = Unselected> {
96    differing_only: bool,
97
98    quiet_mode: bool,
99
100    diff_nontext: bool,
101
102    unified_patch: bool,
103
104    mode: M,
105}
106
107impl<M: ExclusiveOption> ExclusiveOption for DepotContent<M> {
108    fn inject_args(&self, command: &mut Command) {
109        if self.differing_only {
110            command.arg("-Od");
111        }
112
113        if self.quiet_mode {
114            command.arg("-q");
115        }
116
117        if self.diff_nontext {
118            command.arg("-t");
119        }
120
121        if self.unified_patch {
122            command.arg("-u");
123        }
124
125        self.mode.inject_args(command);
126    }
127}
128
129/// `p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] file1[rev] file2[rev]`
130///
131/// `p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] -b branch [[fromfile[rev]] tofile[rev]]`
132///
133/// `p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] [-S stream] [-P parent] [[fromfile[rev]] tofile[rev]]`
134///
135/// `p4 [g-opts] diff2 [-doptions] -As streamname1[@change1] streamname2[@change2]`
136///
137/// Diff utility for comparing the content at two depot paths. (For
138/// comparing workspace content to depot content, see `p4 diff`.) Also
139/// compares two arbitrary stream specs with the -As option.
140///
141/// The `M` type parameter tracks the command mode at compile time. The
142/// default [`Unselected`] state diffs the two depot paths given as spawn
143/// arguments; [`Self::differing_only`], [`Self::quiet_mode`],
144/// [`Self::diff_nontext`], and [`Self::unified_patch`] transition to the
145/// [`DepotContent`] state, [`Self::branch`] and [`Self::stream`] transition
146/// to the depot content [`BranchMode`] and [`StreamMode`] sub-modes, and
147/// [`Self::stream_spec_mode`] transitions to the [`StreamSpecMode`] state.
148#[derive(Debug, Clone, Default)]
149pub struct Diff2<M = Unselected> {
150    bin: PathBuf,
151
152    global_opts: GlobalOpts,
153
154    diff_opts: Option<DiffOptions>,
155
156    mode: M,
157}
158
159impl Diff2<Unselected> {
160    /// Creates a new `p4 diff2` command.
161    ///
162    /// `bin` is the path to the Perforce command-line executable.
163    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
164        Self {
165            bin: bin.into(),
166            global_opts,
167            diff_opts: None,
168            mode: Unselected,
169        }
170    }
171
172    /// # Description
173    ///
174    /// -Od
175    ///
176    /// Limit output to only those files that differ.
177    ///
178    /// Transitions this command to the [`DepotContent`] state with `-Od`
179    /// set according to `v`.
180    pub fn differing_only(self, v: bool) -> Diff2<DepotContent<Unselected>> {
181        Diff2 {
182            bin: self.bin,
183            global_opts: self.global_opts,
184            diff_opts: self.diff_opts,
185            mode: DepotContent {
186                differing_only: v,
187                quiet_mode: false,
188                diff_nontext: false,
189                unified_patch: false,
190                mode: Unselected,
191            },
192        }
193    }
194
195    /// # Description
196    ///
197    /// -q
198    ///
199    /// Quiet diff. Display only the header; if `file1` and `file2` are
200    /// identical, display only `file1 - no differing files` as the output.
201    ///
202    /// Transitions this command to the [`DepotContent`] state with `-q` set
203    /// according to `v`.
204    pub fn quiet_mode(self, v: bool) -> Diff2<DepotContent<Unselected>> {
205        Diff2 {
206            bin: self.bin,
207            global_opts: self.global_opts,
208            diff_opts: self.diff_opts,
209            mode: DepotContent {
210                differing_only: false,
211                quiet_mode: v,
212                diff_nontext: false,
213                unified_patch: false,
214                mode: Unselected,
215            },
216        }
217    }
218
219    /// # Description
220    ///
221    /// -t
222    ///
223    /// Diff the file revisions even if the file(s) are not of type `text`.
224    ///
225    /// Transitions this command to the [`DepotContent`] state with `-t` set
226    /// according to `v`.
227    pub fn diff_nontext(self, v: bool) -> Diff2<DepotContent<Unselected>> {
228        Diff2 {
229            bin: self.bin,
230            global_opts: self.global_opts,
231            diff_opts: self.diff_opts,
232            mode: DepotContent {
233                differing_only: false,
234                quiet_mode: false,
235                diff_nontext: v,
236                unified_patch: false,
237                mode: Unselected,
238            },
239        }
240    }
241
242    /// # Description
243    ///
244    /// -u
245    ///
246    /// Generate unified output format, showing added and deleted lines with
247    /// sufficient context for compatibility with the `patch(1)` utility.
248    /// Only those files that differ are included. File names and dates
249    /// remain in P4 Server syntax.
250    ///
251    /// Transitions this command to the [`DepotContent`] state with `-u` set
252    /// according to `v`.
253    pub fn unified_patch(self, v: bool) -> Diff2<DepotContent<Unselected>> {
254        Diff2 {
255            bin: self.bin,
256            global_opts: self.global_opts,
257            diff_opts: self.diff_opts,
258            mode: DepotContent {
259                differing_only: false,
260                quiet_mode: false,
261                diff_nontext: false,
262                unified_patch: v,
263                mode: Unselected,
264            },
265        }
266    }
267
268    /// # Description
269    ///
270    /// -b branch
271    ///
272    /// Use a branch mapping to diff files in two branched codelines. The
273    /// files that are compared can be limited by file patterns in either
274    /// the `from` or `to` file specifications.
275    ///
276    /// Transitions this command to the [`DepotContent`] state with the
277    /// [`BranchMode`] sub-mode selected and the branch mapping set to
278    /// `name`.
279    pub fn branch(self, name: impl Into<String>) -> Diff2<DepotContent<BranchMode>> {
280        Diff2 {
281            bin: self.bin,
282            global_opts: self.global_opts,
283            diff_opts: self.diff_opts,
284            mode: DepotContent {
285                differing_only: false,
286                quiet_mode: false,
287                diff_nontext: false,
288                unified_patch: false,
289                mode: BranchMode {
290                    branch: name.into(),
291                },
292            },
293        }
294    }
295
296    /// # Description
297    ///
298    /// -S stream
299    ///
300    /// Diff a stream with its parent. To diff the stream with a stream
301    /// other than its configured parent, specify [`Diff2::parent`] or
302    /// [`Diff2::set_parent`].
303    ///
304    /// Transitions this command to the [`DepotContent`] state with the
305    /// [`StreamMode`] sub-mode selected and the stream set to `name`.
306    pub fn stream(self, name: impl Into<String>) -> Diff2<DepotContent<StreamMode>> {
307        Diff2 {
308            bin: self.bin,
309            global_opts: self.global_opts,
310            diff_opts: self.diff_opts,
311            mode: DepotContent {
312                differing_only: false,
313                quiet_mode: false,
314                diff_nontext: false,
315                unified_patch: false,
316                mode: StreamMode {
317                    stream: name.into(),
318                    parent: None,
319                },
320            },
321        }
322    }
323
324    /// # Description
325    ///
326    /// -As
327    ///
328    /// Allows two arbitrary stream specs to be diffed against each other.
329    /// Can be used with a streamname, or with a streamname at a specific
330    /// changelist number.
331    ///
332    /// Although this option requires the user have at least the list access
333    /// to the stream path, it ignores any other entry in the protections
334    /// table, including any minus sign (`-`) that would otherwise block the
335    /// operation.
336    ///
337    /// Transitions this command to the [`StreamSpecMode`] state. The two
338    /// stream specs to compare are passed as the arguments of the spawned
339    /// command ([`ParameterizedSpawn::spawn_with`]). As the `-As` form
340    /// accepts only `-doptions` besides g-opts, this transition is
341    /// unavailable once the command has entered the [`DepotContent`] state.
342    pub fn stream_spec_mode(self) -> Diff2<StreamSpecMode> {
343        Diff2 {
344            bin: self.bin,
345            global_opts: self.global_opts,
346            diff_opts: self.diff_opts,
347            mode: StreamSpecMode,
348        }
349    }
350}
351
352impl<M> Diff2<M> {
353    /// # Description
354    ///
355    /// -doptions
356    ///
357    /// Pass options to the underlying diff routine (see Usage notes for
358    /// details).
359    pub fn get_diff_options(&self) -> Option<&DiffOptions> {
360        self.diff_opts.as_ref()
361    }
362
363    /// # Description
364    ///
365    /// -doptions
366    ///
367    /// Pass options to the underlying diff routine (see Usage notes for
368    /// details).
369    pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
370        self.diff_opts = Some(v.into());
371        self
372    }
373
374    /// # Description
375    ///
376    /// -doptions
377    ///
378    /// Pass options to the underlying diff routine (see Usage notes for
379    /// details).
380    pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
381        self.diff_opts = Some(v.into());
382        self
383    }
384}
385
386impl<M: ExclusiveOption> Diff2<M> {
387    /// # Description
388    ///
389    /// g-opts
390    ///
391    /// See [Global options](GlobalOpts).
392    pub fn get_global_opts(&self) -> &GlobalOpts {
393        &self.global_opts
394    }
395
396    /// # Description
397    ///
398    /// g-opts
399    ///
400    /// See [Global options](GlobalOpts).
401    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
402        self.global_opts = v;
403        self
404    }
405
406    /// # Description
407    ///
408    /// g-opts
409    ///
410    /// See [Global options](GlobalOpts).
411    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
412        self.global_opts = v;
413        self
414    }
415}
416
417impl<M> Diff2<DepotContent<M>> {
418    /// # Description
419    ///
420    /// -Od
421    ///
422    /// Limit output to only those files that differ.
423    pub fn get_differing_only(&self) -> bool {
424        self.mode.differing_only
425    }
426
427    /// # Description
428    ///
429    /// -Od
430    ///
431    /// Limit output to only those files that differ.
432    pub fn set_differing_only(&mut self, v: bool) -> &mut Self {
433        self.mode.differing_only = v;
434        self
435    }
436
437    /// # Description
438    ///
439    /// -Od
440    ///
441    /// Limit output to only those files that differ.
442    pub fn differing_only(mut self, v: bool) -> Self {
443        self.mode.differing_only = v;
444        self
445    }
446
447    /// # Description
448    ///
449    /// -q
450    ///
451    /// Quiet diff. Display only the header; if `file1` and `file2` are
452    /// identical, display only `file1 - no differing files` as the output.
453    pub fn get_quiet_mode(&self) -> bool {
454        self.mode.quiet_mode
455    }
456
457    /// # Description
458    ///
459    /// -q
460    ///
461    /// Quiet diff. Display only the header; if `file1` and `file2` are
462    /// identical, display only `file1 - no differing files` as the output.
463    pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
464        self.mode.quiet_mode = v;
465        self
466    }
467
468    /// # Description
469    ///
470    /// -q
471    ///
472    /// Quiet diff. Display only the header; if `file1` and `file2` are
473    /// identical, display only `file1 - no differing files` as the output.
474    pub fn quiet_mode(mut self, v: bool) -> Self {
475        self.mode.quiet_mode = v;
476        self
477    }
478
479    /// # Description
480    ///
481    /// -t
482    ///
483    /// Diff the file revisions even if the file(s) are not of type `text`.
484    pub fn get_diff_nontext(&self) -> bool {
485        self.mode.diff_nontext
486    }
487
488    /// # Description
489    ///
490    /// -t
491    ///
492    /// Diff the file revisions even if the file(s) are not of type `text`.
493    pub fn set_diff_nontext(&mut self, v: bool) -> &mut Self {
494        self.mode.diff_nontext = v;
495        self
496    }
497
498    /// # Description
499    ///
500    /// -t
501    ///
502    /// Diff the file revisions even if the file(s) are not of type `text`.
503    pub fn diff_nontext(mut self, v: bool) -> Self {
504        self.mode.diff_nontext = v;
505        self
506    }
507
508    /// # Description
509    ///
510    /// -u
511    ///
512    /// Generate unified output format, showing added and deleted lines with
513    /// sufficient context for compatibility with the `patch(1)` utility.
514    /// Only those files that differ are included. File names and dates
515    /// remain in P4 Server syntax.
516    pub fn get_unified_patch(&self) -> bool {
517        self.mode.unified_patch
518    }
519
520    /// # Description
521    ///
522    /// -u
523    ///
524    /// Generate unified output format, showing added and deleted lines with
525    /// sufficient context for compatibility with the `patch(1)` utility.
526    /// Only those files that differ are included. File names and dates
527    /// remain in P4 Server syntax.
528    pub fn set_unified_patch(&mut self, v: bool) -> &mut Self {
529        self.mode.unified_patch = v;
530        self
531    }
532
533    /// # Description
534    ///
535    /// -u
536    ///
537    /// Generate unified output format, showing added and deleted lines with
538    /// sufficient context for compatibility with the `patch(1)` utility.
539    /// Only those files that differ are included. File names and dates
540    /// remain in P4 Server syntax.
541    pub fn unified_patch(mut self, v: bool) -> Self {
542        self.mode.unified_patch = v;
543        self
544    }
545}
546
547impl Diff2<DepotContent<Unselected>> {
548    /// # Description
549    ///
550    /// -b branch
551    ///
552    /// Use a branch mapping to diff files in two branched codelines. The
553    /// files that are compared can be limited by file patterns in either
554    /// the `from` or `to` file specifications.
555    ///
556    /// Transitions this command to the [`DepotContent`] state with the
557    /// [`BranchMode`] sub-mode selected and the branch mapping set to
558    /// `name`.
559    pub fn branch(self, name: impl Into<String>) -> Diff2<DepotContent<BranchMode>> {
560        Diff2 {
561            bin: self.bin,
562            global_opts: self.global_opts,
563            diff_opts: self.diff_opts,
564            mode: DepotContent {
565                differing_only: self.mode.differing_only,
566                quiet_mode: self.mode.quiet_mode,
567                diff_nontext: self.mode.diff_nontext,
568                unified_patch: self.mode.unified_patch,
569                mode: BranchMode {
570                    branch: name.into(),
571                },
572            },
573        }
574    }
575
576    /// # Description
577    ///
578    /// -S stream
579    ///
580    /// Diff a stream with its parent. To diff the stream with a stream
581    /// other than its configured parent, specify [`Diff2::parent`] or
582    /// [`Diff2::set_parent`].
583    ///
584    /// Transitions this command to the [`DepotContent`] state with the
585    /// [`StreamMode`] sub-mode selected and the stream set to `name`.
586    pub fn stream(self, name: impl Into<String>) -> Diff2<DepotContent<StreamMode>> {
587        Diff2 {
588            bin: self.bin,
589            global_opts: self.global_opts,
590            diff_opts: self.diff_opts,
591            mode: DepotContent {
592                differing_only: self.mode.differing_only,
593                quiet_mode: self.mode.quiet_mode,
594                diff_nontext: self.mode.diff_nontext,
595                unified_patch: self.mode.unified_patch,
596                mode: StreamMode {
597                    stream: name.into(),
598                    parent: None,
599                },
600            },
601        }
602    }
603}
604
605impl Diff2<DepotContent<BranchMode>> {
606    /// # Description
607    ///
608    /// -b branch
609    ///
610    /// Use a branch mapping to diff files in two branched codelines. The
611    /// files that are compared can be limited by file patterns in either
612    /// the `from` or `to` file specifications.
613    pub fn get_branch(&self) -> &str {
614        &self.mode.mode.branch
615    }
616
617    /// # Description
618    ///
619    /// -b branch
620    ///
621    /// Use a branch mapping to diff files in two branched codelines. The
622    /// files that are compared can be limited by file patterns in either
623    /// the `from` or `to` file specifications.
624    pub fn set_branch(&mut self, v: impl Into<String>) -> &mut Self {
625        self.mode.mode.branch = v.into();
626        self
627    }
628
629    /// # Description
630    ///
631    /// -b branch
632    ///
633    /// Use a branch mapping to diff files in two branched codelines. The
634    /// files that are compared can be limited by file patterns in either
635    /// the `from` or `to` file specifications.
636    pub fn branch(mut self, v: impl Into<String>) -> Self {
637        self.mode.mode.branch = v.into();
638        self
639    }
640}
641
642impl Diff2<DepotContent<StreamMode>> {
643    /// # Description
644    ///
645    /// -S stream
646    ///
647    /// Diff a stream with its parent. To diff the stream with a stream
648    /// other than its configured parent, specify [`Diff2::parent`] or
649    /// [`Diff2::set_parent`].
650    pub fn get_stream(&self) -> &str {
651        &self.mode.mode.stream
652    }
653
654    /// # Description
655    ///
656    /// -S stream
657    ///
658    /// Diff a stream with its parent. To diff the stream with a stream
659    /// other than its configured parent, specify [`Diff2::parent`] or
660    /// [`Diff2::set_parent`].
661    pub fn set_stream(&mut self, v: impl Into<String>) -> &mut Self {
662        self.mode.mode.stream = v.into();
663        self
664    }
665
666    /// # Description
667    ///
668    /// -S stream
669    ///
670    /// Diff a stream with its parent. To diff the stream with a stream
671    /// other than its configured parent, specify [`Diff2::parent`] or
672    /// [`Diff2::set_parent`].
673    pub fn stream(mut self, v: impl Into<String>) -> Self {
674        self.mode.mode.stream = v.into();
675        self
676    }
677
678    /// # Description
679    ///
680    /// -P parent
681    ///
682    /// Diff the stream with a stream other than its configured parent.
683    pub fn get_parent(&self) -> Option<&str> {
684        self.mode.mode.parent.as_deref()
685    }
686
687    /// # Description
688    ///
689    /// -P parent
690    ///
691    /// Diff the stream with a stream other than its configured parent.
692    pub fn set_parent(&mut self, v: impl Into<String>) -> &mut Self {
693        self.mode.mode.parent = Some(v.into());
694        self
695    }
696
697    /// # Description
698    ///
699    /// -P parent
700    ///
701    /// Diff the stream with a stream other than its configured parent.
702    pub fn parent(mut self, v: impl Into<String>) -> Self {
703        self.mode.mode.parent = Some(v.into());
704        self
705    }
706}
707
708impl<M: ExclusiveOption> SubCommand for Diff2<M> {
709    fn name(&self) -> &str {
710        "diff2"
711    }
712
713    fn inject_local_args(&self, command: &mut Command) {
714        if let Some(diff_opts) = &self.diff_opts {
715            diff_opts.inject_arg(command);
716        }
717
718        self.mode.inject_args(command);
719    }
720
721    fn global_opts(&self) -> Option<&GlobalOpts> {
722        Some(&self.global_opts)
723    }
724}
725
726impl ParameterizedSpawn for Diff2<Unselected> {
727    type Input<'a> = (&'a OsStr, &'a OsStr);
728    type Output<'a> = Child;
729    type Error = std::io::Error;
730
731    /// Spawns `p4 diff2` for the given pair of file arguments as a child
732    /// process with piped standard output and error streams; use the
733    /// returned [`Child`] handle to wait for it or interact with it.
734    ///
735    /// Each file argument is a file name, optionally with a revision
736    /// specifier (for example `file#2` or `file@34`).
737    fn spawn_with<'a>(
738        &mut self,
739        (file1, file2): Self::Input<'a>,
740    ) -> Result<Self::Output<'a>, Self::Error> {
741        self.setup_command(&self.bin)
742            .arg(file1)
743            .arg(file2)
744            .stdout(Stdio::piped())
745            .stderr(Stdio::piped())
746            .spawn()
747    }
748}
749
750impl ParameterizedSpawn for Diff2<DepotContent<Unselected>> {
751    type Input<'a> = (&'a OsStr, &'a OsStr);
752    type Output<'a> = Child;
753    type Error = std::io::Error;
754
755    /// Spawns `p4 diff2` for the given pair of file arguments as a child
756    /// process with piped standard output and error streams; use the
757    /// returned [`Child`] handle to wait for it or interact with it.
758    ///
759    /// Each file argument is a file name, optionally with a revision
760    /// specifier (for example `file#2` or `file@34`).
761    fn spawn_with<'a>(
762        &mut self,
763        (file1, file2): Self::Input<'a>,
764    ) -> Result<Self::Output<'a>, Self::Error> {
765        self.setup_command(&self.bin)
766            .arg(file1)
767            .arg(file2)
768            .stdout(Stdio::piped())
769            .stderr(Stdio::piped())
770            .spawn()
771    }
772}
773
774impl ParameterizedSpawn for Diff2<DepotContent<BranchMode>> {
775    type Input<'a> = Diff2Parameters<'a>;
776    type Output<'a> = Child;
777    type Error = std::io::Error;
778
779    /// Spawns `p4 diff2 -b branch` as a child process with piped standard
780    /// output and error streams; use the returned [`Child`] handle to wait
781    /// for it or interact with it.
782    ///
783    /// [`Diff2Parameters::Full`] limits the files compared to
784    /// `fromfile[rev]` and `tofile[rev]`, [`Diff2Parameters::To`] limits
785    /// the target side only, and [`Diff2Parameters::None`] diffs the whole
786    /// branch mapping.
787    fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
788        let mut command = self.setup_command(&self.bin);
789
790        match files {
791            Diff2Parameters::Full { from, to } => {
792                command.arg(from);
793                command.arg(to);
794            }
795            Diff2Parameters::To(tofile) => {
796                command.arg(tofile);
797            }
798            Diff2Parameters::None => {}
799        }
800
801        command
802            .stdout(Stdio::piped())
803            .stderr(Stdio::piped())
804            .spawn()
805    }
806}
807
808impl SpawnExt for Diff2<DepotContent<BranchMode>> {
809    /// Spawns `p4 diff2 -b branch` without file arguments as a child
810    /// process with piped standard output and error streams; the whole
811    /// branch mapping is diffed. Use the returned [`Child`] handle to wait
812    /// for it or interact with it.
813    fn spawn<'a>(&mut self) -> Result<Self::Output<'a>, Self::Error> {
814        self.spawn_with(Diff2Parameters::None)
815    }
816}
817
818impl<'f> SpawnExt1<&'f OsStr> for Diff2<DepotContent<BranchMode>> {
819    /// Spawns `p4 diff2 -b branch tofile[rev]` as a child process with
820    /// piped standard output and error streams; the branch mapping is
821    /// diffed with the target side limited to the given file. Use the
822    /// returned [`Child`] handle to wait for it or interact with it.
823    fn spawn<'a>(&mut self, tofile: &'f OsStr) -> Result<Self::Output<'a>, Self::Error> {
824        self.spawn_with(Diff2Parameters::To(tofile))
825    }
826}
827
828impl<'f> SpawnExt2<&'f OsStr, &'f OsStr> for Diff2<DepotContent<BranchMode>> {
829    /// Spawns `p4 diff2 -b branch fromfile[rev] tofile[rev]` as a child
830    /// process with piped standard output and error streams; the branch
831    /// mapping is diffed between the given files. Use the returned
832    /// [`Child`] handle to wait for it or interact with it.
833    fn spawn<'a>(
834        &mut self,
835        fromfile: &'f OsStr,
836        tofile: &'f OsStr,
837    ) -> Result<Self::Output<'a>, Self::Error> {
838        self.spawn_with(Diff2Parameters::Full {
839            from: fromfile,
840            to: tofile,
841        })
842    }
843}
844
845impl ParameterizedSpawn for Diff2<DepotContent<StreamMode>> {
846    type Input<'a> = Diff2Parameters<'a>;
847    type Output<'a> = Child;
848    type Error = std::io::Error;
849
850    /// Spawns `p4 diff2 -S stream` as a child process with piped standard
851    /// output and error streams; use the returned [`Child`] handle to wait
852    /// for it or interact with it.
853    ///
854    /// [`Diff2Parameters::Full`] limits the files compared to
855    /// `fromfile[rev]` and `tofile[rev]`, [`Diff2Parameters::To`] limits
856    /// the target side only, and [`Diff2Parameters::None`] diffs the whole
857    /// stream with its parent.
858    fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
859        let mut command = self.setup_command(&self.bin);
860
861        match files {
862            Diff2Parameters::Full { from, to } => {
863                command.arg(from);
864                command.arg(to);
865            }
866            Diff2Parameters::To(tofile) => {
867                command.arg(tofile);
868            }
869            Diff2Parameters::None => {}
870        }
871
872        command
873            .stdout(Stdio::piped())
874            .stderr(Stdio::piped())
875            .spawn()
876    }
877}
878
879impl SpawnExt for Diff2<DepotContent<StreamMode>> {
880    /// Spawns `p4 diff2 -S stream` without file arguments as a child
881    /// process with piped standard output and error streams; the whole
882    /// stream is diffed with its parent. Use the returned [`Child`] handle
883    /// to wait for it or interact with it.
884    fn spawn<'a>(&mut self) -> Result<Self::Output<'a>, Self::Error> {
885        self.spawn_with(Diff2Parameters::None)
886    }
887}
888
889impl<'f> SpawnExt1<&'f OsStr> for Diff2<DepotContent<StreamMode>> {
890    /// Spawns `p4 diff2 -S stream tofile[rev]` as a child process with
891    /// piped standard output and error streams; the stream is diffed with
892    /// its parent with the target side limited to the given file. Use the
893    /// returned [`Child`] handle to wait for it or interact with it.
894    fn spawn<'a>(&mut self, tofile: &'f OsStr) -> Result<Self::Output<'a>, Self::Error> {
895        self.spawn_with(Diff2Parameters::To(tofile))
896    }
897}
898
899impl<'f> SpawnExt2<&'f OsStr, &'f OsStr> for Diff2<DepotContent<StreamMode>> {
900    /// Spawns `p4 diff2 -S stream fromfile[rev] tofile[rev]` as a child
901    /// process with piped standard output and error streams; the stream is
902    /// diffed with its parent between the given files. Use the returned
903    /// [`Child`] handle to wait for it or interact with it.
904    fn spawn<'a>(
905        &mut self,
906        fromfile: &'f OsStr,
907        tofile: &'f OsStr,
908    ) -> Result<Self::Output<'a>, Self::Error> {
909        self.spawn_with(Diff2Parameters::Full {
910            from: fromfile,
911            to: tofile,
912        })
913    }
914}
915
916impl ParameterizedSpawn for Diff2<StreamSpecMode> {
917    type Input<'a> = (&'a str, &'a str);
918    type Output<'a> = Child;
919    type Error = std::io::Error;
920
921    /// Spawns `p4 diff2 -As` for the given pair of stream specs as a child
922    /// process with piped standard output and error streams; use the
923    /// returned [`Child`] handle to wait for it or interact with it.
924    ///
925    /// Each stream spec is a streamname, optionally at a specific changelist
926    /// number: `@head` selects the head version, `@change` the version at a
927    /// specific change, and `@=change` the shelved version at a specific
928    /// change.
929    fn spawn_with<'a>(
930        &mut self,
931        (spec1, spec2): Self::Input<'a>,
932    ) -> Result<Self::Output<'a>, Self::Error> {
933        self.setup_command(&self.bin)
934            .arg(spec1)
935            .arg(spec2)
936            .stdout(Stdio::piped())
937            .stderr(Stdio::piped())
938            .spawn()
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use super::*;
945    use crate::cmd::DiffOptionsBuilder;
946    use crate::cmd::args_of;
947
948    /// Dry-run checks of the assembled `p4 diff2` command line; no process
949    /// is spawned.
950    #[test]
951    fn without_options() {
952        let diff2 = Diff2::new("p4", GlobalOpts::new());
953
954        assert_eq!(args_of(&diff2.setup_command("p4")), ["diff2"]);
955    }
956
957    #[test]
958    fn depot_content_flags_are_injected() {
959        let diff2 = Diff2::new("p4", GlobalOpts::new())
960            .differing_only(true)
961            .quiet_mode(true)
962            .diff_nontext(true)
963            .unified_patch(true);
964
965        assert_eq!(
966            args_of(&diff2.setup_command("p4")),
967            ["diff2", "-Od", "-q", "-t", "-u"]
968        );
969    }
970
971    #[test]
972    fn depot_content_flags_apply_to_branch_mode() {
973        let diff2 = Diff2::new("p4", GlobalOpts::new())
974            .quiet_mode(true)
975            .branch("branch2");
976
977        assert!(diff2.get_quiet_mode());
978        assert_eq!(
979            args_of(&diff2.setup_command("p4")),
980            ["diff2", "-q", "-b", "branch2"]
981        );
982    }
983
984    #[test]
985    fn depot_content_flags_apply_after_branch_transition() {
986        let diff2 = Diff2::new("p4", GlobalOpts::new())
987            .branch("branch2")
988            .quiet_mode(true);
989
990        assert!(diff2.get_quiet_mode());
991        assert_eq!(
992            args_of(&diff2.setup_command("p4")),
993            ["diff2", "-q", "-b", "branch2"]
994        );
995    }
996
997    #[test]
998    fn diff_options_are_injected() {
999        let mut diff2 =
1000            Diff2::new("p4", GlobalOpts::new()).diff_options(DiffOptionsBuilder::unified(None));
1001        diff2.set_diff_options(DiffOptionsBuilder::summary());
1002
1003        assert_eq!(args_of(&diff2.setup_command("p4")), ["diff2", "-ds"]);
1004    }
1005
1006    #[test]
1007    fn branch_mode_injects_branch() {
1008        let diff2 = Diff2::new("p4", GlobalOpts::new()).branch("branch2");
1009
1010        assert_eq!(diff2.get_branch(), "branch2");
1011        assert_eq!(
1012            args_of(&diff2.setup_command("p4")),
1013            ["diff2", "-b", "branch2"]
1014        );
1015    }
1016
1017    #[test]
1018    fn branch_mode_with_files() {
1019        let diff2 = Diff2::new("p4", GlobalOpts::new()).branch("branch2");
1020
1021        // Mirrors `spawn_with`, which appends the file arguments after the
1022        // assembled command.
1023        let mut command = diff2.setup_command("p4");
1024        command.arg(OsStr::new("//depot/rel1/..."));
1025        command.arg(OsStr::new("//depot/rel2/...#4"));
1026
1027        assert_eq!(
1028            args_of(&command),
1029            [
1030                "diff2",
1031                "-b",
1032                "branch2",
1033                "//depot/rel1/...",
1034                "//depot/rel2/...#4"
1035            ]
1036        );
1037    }
1038
1039    #[test]
1040    fn branch_mode_with_tofile() {
1041        let diff2 = Diff2::new("p4", GlobalOpts::new()).branch("branch2");
1042
1043        // Mirrors `spawn_with` for `Diff2Parameters::To`, which appends the
1044        // target file after the assembled command.
1045        let mut command = diff2.setup_command("p4");
1046        command.arg(OsStr::new("//depot/rel2/...#4"));
1047
1048        assert_eq!(
1049            args_of(&command),
1050            ["diff2", "-b", "branch2", "//depot/rel2/...#4"]
1051        );
1052    }
1053
1054    #[test]
1055    fn stream_mode_injects_stream() {
1056        let diff2 = Diff2::new("p4", GlobalOpts::new()).stream("myStream");
1057
1058        assert_eq!(diff2.get_stream(), "myStream");
1059        assert_eq!(
1060            args_of(&diff2.setup_command("p4")),
1061            ["diff2", "-S", "myStream"]
1062        );
1063    }
1064
1065    #[test]
1066    fn stream_mode_with_parent() {
1067        let mut diff2 = Diff2::new("p4", GlobalOpts::new()).stream("myStream");
1068        diff2.set_parent("mainStream");
1069
1070        assert_eq!(diff2.get_parent(), Some("mainStream"));
1071        assert_eq!(
1072            args_of(&diff2.setup_command("p4")),
1073            ["diff2", "-S", "myStream", "-P", "mainStream"]
1074        );
1075    }
1076
1077    #[test]
1078    fn stream_mode_with_tofile() {
1079        let diff2 = Diff2::new("p4", GlobalOpts::new()).stream("myStream");
1080
1081        // Mirrors `spawn_with` for `Diff2Parameters::To`, which appends the
1082        // target file after the assembled command.
1083        let mut command = diff2.setup_command("p4");
1084        command.arg(OsStr::new("//depot/rel2/...#4"));
1085
1086        assert_eq!(
1087            args_of(&command),
1088            ["diff2", "-S", "myStream", "//depot/rel2/...#4"]
1089        );
1090    }
1091
1092    #[test]
1093    fn stream_mode_preserves_diff_options() {
1094        let diff2 = Diff2::new("p4", GlobalOpts::new())
1095            .diff_options(DiffOptionsBuilder::summary())
1096            .stream("myStream");
1097
1098        assert_eq!(
1099            args_of(&diff2.setup_command("p4")),
1100            ["diff2", "-ds", "-S", "myStream"]
1101        );
1102    }
1103
1104    #[test]
1105    fn stream_spec_mode_bare() {
1106        let diff2 = Diff2::new("p4", GlobalOpts::new()).stream_spec_mode();
1107
1108        assert_eq!(args_of(&diff2.setup_command("p4")), ["diff2", "-As"]);
1109    }
1110
1111    #[test]
1112    fn stream_spec_mode_with_specs() {
1113        let diff2 = Diff2::new("p4", GlobalOpts::new()).stream_spec_mode();
1114
1115        // Mirrors `spawn_with`, which appends the two stream specs after
1116        // the assembled command.
1117        let mut command = diff2.setup_command("p4");
1118        command.arg("myStream@=1");
1119        command.arg("yourStream@2");
1120
1121        assert_eq!(
1122            args_of(&command),
1123            ["diff2", "-As", "myStream@=1", "yourStream@2"]
1124        );
1125    }
1126}