1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4 process::{Child, Command, Stdio},
5};
6
7use super::{DiffOptions, ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12#[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#[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#[cfg(not(feature = "lt2019_1"))]
56#[derive(Debug, Clone, Default, PartialEq, Eq)]
57pub struct StreamSpecMode;
58
59#[cfg(not(feature = "lt2019_1"))]
60impl ExclusiveOption for StreamSpecMode {
61 fn inject_args(&self, command: &mut Command) {
62 command.arg("-As");
63 }
64}
65
66#[derive(Debug, Clone, Default)]
80pub struct DepotContent<M = Unselected> {
81 differing_only: bool,
82
83 quiet_mode: bool,
84
85 diff_nontext: bool,
86
87 unified_patch: bool,
88
89 mode: M,
90}
91
92impl<M: ExclusiveOption> ExclusiveOption for DepotContent<M> {
93 fn inject_args(&self, command: &mut Command) {
94 if self.differing_only {
95 command.arg("-Od");
96 }
97
98 if self.quiet_mode {
99 command.arg("-q");
100 }
101
102 if self.diff_nontext {
103 command.arg("-t");
104 }
105
106 if self.unified_patch {
107 command.arg("-u");
108 }
109
110 self.mode.inject_args(command);
111 }
112}
113
114#[cfg_attr(
115 feature = "lt2014_2",
116 doc = "`p4 [g-opts] diff2 [-dflags -Od -q -t -u] file1[rev] file2[rev]`",
117 doc = "",
118 doc = "`p4 [g-opts] diff2 [-dflags -Od -q -t -u] -b branch [[fromfile[rev]] tofile[rev]]`",
119 doc = "",
120 doc = "`p4 [g-opts] diff2 [-dflags -Od -q -t -u] -S stream [-P parent] [[fromfile[rev]] tofile[rev]]`"
121)]
122#[cfg_attr(
123 all(feature = "lt2015_1", not(feature = "lt2014_2")),
124 doc = "`p4 [g-opts] diff2 [-doptions -Od -q -t -u] file1[rev] file2[rev]`",
125 doc = "",
126 doc = "`p4 [g-opts] diff2 [-doptions -Od -q -t -u] -b branch [[fromfile[rev]] tofile[rev]]`",
127 doc = "",
128 doc = "`p4 [g-opts] diff2 [-doptions -Od -q -t -u] -S stream [-P parent] [[fromfile[rev]] tofile[rev]]`"
129)]
130#[cfg_attr(
131 all(feature = "lt2019_1", not(feature = "lt2015_1")),
132 doc = "`p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] file1[rev] file2[rev]`",
133 doc = "",
134 doc = "`p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] -b branch [[fromfile[rev]] tofile[rev]]`",
135 doc = "",
136 doc = "`p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] [-S stream] [-P parent] [[fromfile[rev]] tofile[rev]]`"
137)]
138#[cfg_attr(
139 not(feature = "lt2019_1"),
140 doc = "`p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] file1[rev] file2[rev]`",
141 doc = "",
142 doc = "`p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] -b branch [[fromfile[rev]] tofile[rev]]`",
143 doc = "",
144 doc = "`p4 [g-opts] diff2 [-doptions] [-Od -q -t -u] [-S stream] [-P parent] [[fromfile[rev]] tofile[rev]]`",
145 doc = "",
146 doc = "`p4 [g-opts] diff2 [-doptions] -As streamname1[@change1] streamname2[@change2]`"
147)]
148#[cfg_attr(
153 not(feature = "lt2019_1"),
154 doc = "Also compares two arbitrary stream specs with the -As option."
155)]
156#[cfg_attr(
158 feature = "lt2019_1",
159 doc = "The `M` type parameter tracks the command mode at compile time. The default [`Unselected`] state diffs the two depot paths given as spawn arguments; [`Self::differing_only`], [`Self::quiet_mode`], [`Self::diff_nontext`], and [`Self::unified_patch`] transition to the [`DepotContent`] state, and [`Self::branch`] and [`Self::stream`] transition to the depot content [`BranchMode`] and [`StreamMode`] sub-modes."
160)]
161#[cfg_attr(
162 not(feature = "lt2019_1"),
163 doc = "The `M` type parameter tracks the command mode at compile time. The default [`Unselected`] state diffs the two depot paths given as spawn arguments; [`Self::differing_only`], [`Self::quiet_mode`], [`Self::diff_nontext`], and [`Self::unified_patch`] transition to the [`DepotContent`] state, [`Self::branch`] and [`Self::stream`] transition to the depot content [`BranchMode`] and [`StreamMode`] sub-modes, and [`Self::stream_spec_mode`] transitions to the [`StreamSpecMode`] state."
164)]
165#[derive(Debug, Clone, Default)]
166pub struct Diff2<M = Unselected> {
167 bin: PathBuf,
168
169 global_opts: GlobalOpts,
170
171 diff_opts: Option<DiffOptions>,
172
173 mode: M,
174}
175
176impl Diff2<Unselected> {
177 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
181 Self {
182 bin: bin.into(),
183 global_opts,
184 diff_opts: None,
185 mode: Unselected,
186 }
187 }
188
189 pub fn differing_only(self, v: bool) -> Diff2<DepotContent<Unselected>> {
198 Diff2 {
199 bin: self.bin,
200 global_opts: self.global_opts,
201 diff_opts: self.diff_opts,
202 mode: DepotContent {
203 differing_only: v,
204 quiet_mode: false,
205 diff_nontext: false,
206 unified_patch: false,
207 mode: Unselected,
208 },
209 }
210 }
211
212 pub fn quiet_mode(self, v: bool) -> Diff2<DepotContent<Unselected>> {
222 Diff2 {
223 bin: self.bin,
224 global_opts: self.global_opts,
225 diff_opts: self.diff_opts,
226 mode: DepotContent {
227 differing_only: false,
228 quiet_mode: v,
229 diff_nontext: false,
230 unified_patch: false,
231 mode: Unselected,
232 },
233 }
234 }
235
236 pub fn diff_nontext(self, v: bool) -> Diff2<DepotContent<Unselected>> {
245 Diff2 {
246 bin: self.bin,
247 global_opts: self.global_opts,
248 diff_opts: self.diff_opts,
249 mode: DepotContent {
250 differing_only: false,
251 quiet_mode: false,
252 diff_nontext: v,
253 unified_patch: false,
254 mode: Unselected,
255 },
256 }
257 }
258
259 #[cfg_attr(feature = "lt2017_2", doc = "remain in Perforce syntax.")]
267 #[cfg_attr(
268 all(feature = "lt2024_1", not(feature = "lt2017_2")),
269 doc = "remain in Helix Server syntax."
270 )]
271 #[cfg_attr(
272 all(feature = "lt2024_2", not(feature = "lt2024_1")),
273 doc = "remain in Helix Core Server syntax."
274 )]
275 #[cfg_attr(not(feature = "lt2024_2"), doc = "remain in P4 Server syntax.")]
276 pub fn unified_patch(self, v: bool) -> Diff2<DepotContent<Unselected>> {
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: v,
289 mode: Unselected,
290 },
291 }
292 }
293
294 pub fn branch(self, name: impl Into<String>) -> Diff2<DepotContent<BranchMode>> {
306 Diff2 {
307 bin: self.bin,
308 global_opts: self.global_opts,
309 diff_opts: self.diff_opts,
310 mode: DepotContent {
311 differing_only: false,
312 quiet_mode: false,
313 diff_nontext: false,
314 unified_patch: false,
315 mode: BranchMode {
316 branch: name.into(),
317 },
318 },
319 }
320 }
321
322 pub fn stream(self, name: impl Into<String>) -> Diff2<DepotContent<StreamMode>> {
333 Diff2 {
334 bin: self.bin,
335 global_opts: self.global_opts,
336 diff_opts: self.diff_opts,
337 mode: DepotContent {
338 differing_only: false,
339 quiet_mode: false,
340 diff_nontext: false,
341 unified_patch: false,
342 mode: StreamMode {
343 stream: name.into(),
344 parent: None,
345 },
346 },
347 }
348 }
349
350 #[cfg(not(feature = "lt2019_1"))]
369 pub fn stream_spec_mode(self) -> Diff2<StreamSpecMode> {
370 Diff2 {
371 bin: self.bin,
372 global_opts: self.global_opts,
373 diff_opts: self.diff_opts,
374 mode: StreamSpecMode,
375 }
376 }
377}
378
379impl<M> Diff2<M> {
380 #[cfg_attr(
385 feature = "lt2014_2",
386 doc = "Runs the diff routine with one of a subset of the standard UNIX diff flags. See the Usage Notes below for a listing of these flags."
387 )]
388 #[cfg_attr(
389 all(feature = "lt2015_1", not(feature = "lt2014_2")),
390 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See the Usage Notes below for a listing of these options."
391 )]
392 #[cfg_attr(
393 all(feature = "lt2024_1", not(feature = "lt2015_1")),
394 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See Usage Notes for a listing of these options."
395 )]
396 #[cfg_attr(
397 not(feature = "lt2024_1"),
398 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See Usage notes for a listing of these options."
399 )]
400 pub fn get_diff_options(&self) -> Option<&DiffOptions> {
401 self.diff_opts.as_ref()
402 }
403
404 #[cfg_attr(
409 feature = "lt2014_2",
410 doc = "Runs the diff routine with one of a subset of the standard UNIX diff flags. See the Usage Notes below for a listing of these flags."
411 )]
412 #[cfg_attr(
413 all(feature = "lt2015_1", not(feature = "lt2014_2")),
414 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See the Usage Notes below for a listing of these options."
415 )]
416 #[cfg_attr(
417 all(feature = "lt2024_1", not(feature = "lt2015_1")),
418 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See Usage Notes for a listing of these options."
419 )]
420 #[cfg_attr(
421 not(feature = "lt2024_1"),
422 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See Usage notes for a listing of these options."
423 )]
424 pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
425 self.diff_opts = Some(v.into());
426 self
427 }
428
429 #[cfg_attr(
434 feature = "lt2014_2",
435 doc = "Runs the diff routine with one of a subset of the standard UNIX diff flags. See the Usage Notes below for a listing of these flags."
436 )]
437 #[cfg_attr(
438 all(feature = "lt2015_1", not(feature = "lt2014_2")),
439 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See the Usage Notes below for a listing of these options."
440 )]
441 #[cfg_attr(
442 all(feature = "lt2024_1", not(feature = "lt2015_1")),
443 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See Usage Notes for a listing of these options."
444 )]
445 #[cfg_attr(
446 not(feature = "lt2024_1"),
447 doc = "Runs the diff routine with one of a subset of the standard UNIX diff options. See Usage notes for a listing of these options."
448 )]
449 pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
450 self.diff_opts = Some(v.into());
451 self
452 }
453}
454
455impl<M: ExclusiveOption> Diff2<M> {
456 pub fn get_bin(&self) -> &Path {
458 &self.bin
459 }
460
461 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
463 self.bin = bin.into();
464 self
465 }
466
467 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
469 self.set_bin(bin);
470 self
471 }
472
473 #[cfg_attr(
478 feature = "lt2014_2",
479 doc = "See the [Global Options](GlobalOpts) section."
480 )]
481 #[cfg_attr(
482 all(feature = "lt2015_1", not(feature = "lt2014_2")),
483 doc = "See the [“Global Options”](GlobalOpts) section."
484 )]
485 #[cfg_attr(
486 all(feature = "lt2017_1", not(feature = "lt2015_1")),
487 doc = "See [“Global Options”](GlobalOpts)."
488 )]
489 #[cfg_attr(
490 all(feature = "lt2018_2", not(feature = "lt2017_1")),
491 doc = "See [Global Options](GlobalOpts)."
492 )]
493 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
494 pub fn get_global_opts(&self) -> &GlobalOpts {
495 &self.global_opts
496 }
497
498 #[cfg_attr(
503 feature = "lt2014_2",
504 doc = "See the [Global Options](GlobalOpts) section."
505 )]
506 #[cfg_attr(
507 all(feature = "lt2015_1", not(feature = "lt2014_2")),
508 doc = "See the [“Global Options”](GlobalOpts) section."
509 )]
510 #[cfg_attr(
511 all(feature = "lt2017_1", not(feature = "lt2015_1")),
512 doc = "See [“Global Options”](GlobalOpts)."
513 )]
514 #[cfg_attr(
515 all(feature = "lt2018_2", not(feature = "lt2017_1")),
516 doc = "See [Global Options](GlobalOpts)."
517 )]
518 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
519 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
520 self.global_opts = v;
521 self
522 }
523
524 #[cfg_attr(
529 feature = "lt2014_2",
530 doc = "See the [Global Options](GlobalOpts) section."
531 )]
532 #[cfg_attr(
533 all(feature = "lt2015_1", not(feature = "lt2014_2")),
534 doc = "See the [“Global Options”](GlobalOpts) section."
535 )]
536 #[cfg_attr(
537 all(feature = "lt2017_1", not(feature = "lt2015_1")),
538 doc = "See [“Global Options”](GlobalOpts)."
539 )]
540 #[cfg_attr(
541 all(feature = "lt2018_2", not(feature = "lt2017_1")),
542 doc = "See [Global Options](GlobalOpts)."
543 )]
544 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
545 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
546 self.global_opts = v;
547 self
548 }
549}
550
551impl<M> Diff2<DepotContent<M>> {
552 pub fn get_differing_only(&self) -> bool {
558 self.mode.differing_only
559 }
560
561 pub fn set_differing_only(&mut self, v: bool) -> &mut Self {
567 self.mode.differing_only = v;
568 self
569 }
570
571 pub fn differing_only(mut self, v: bool) -> Self {
577 self.mode.differing_only = v;
578 self
579 }
580
581 pub fn get_quiet_mode(&self) -> bool {
588 self.mode.quiet_mode
589 }
590
591 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
598 self.mode.quiet_mode = v;
599 self
600 }
601
602 pub fn quiet_mode(mut self, v: bool) -> Self {
609 self.mode.quiet_mode = v;
610 self
611 }
612
613 pub fn get_diff_nontext(&self) -> bool {
619 self.mode.diff_nontext
620 }
621
622 pub fn set_diff_nontext(&mut self, v: bool) -> &mut Self {
628 self.mode.diff_nontext = v;
629 self
630 }
631
632 pub fn diff_nontext(mut self, v: bool) -> Self {
638 self.mode.diff_nontext = v;
639 self
640 }
641
642 #[cfg_attr(feature = "lt2017_2", doc = "remain in Perforce syntax.")]
650 #[cfg_attr(
651 all(feature = "lt2024_1", not(feature = "lt2017_2")),
652 doc = "remain in Helix Server syntax."
653 )]
654 #[cfg_attr(
655 all(feature = "lt2024_2", not(feature = "lt2024_1")),
656 doc = "remain in Helix Core Server syntax."
657 )]
658 #[cfg_attr(not(feature = "lt2024_2"), doc = "remain in P4 Server syntax.")]
659 pub fn get_unified_patch(&self) -> bool {
660 self.mode.unified_patch
661 }
662
663 #[cfg_attr(feature = "lt2017_2", doc = "remain in Perforce syntax.")]
671 #[cfg_attr(
672 all(feature = "lt2024_1", not(feature = "lt2017_2")),
673 doc = "remain in Helix Server syntax."
674 )]
675 #[cfg_attr(
676 all(feature = "lt2024_2", not(feature = "lt2024_1")),
677 doc = "remain in Helix Core Server syntax."
678 )]
679 #[cfg_attr(not(feature = "lt2024_2"), doc = "remain in P4 Server syntax.")]
680 pub fn set_unified_patch(&mut self, v: bool) -> &mut Self {
681 self.mode.unified_patch = v;
682 self
683 }
684
685 #[cfg_attr(feature = "lt2017_2", doc = "remain in Perforce syntax.")]
693 #[cfg_attr(
694 all(feature = "lt2024_1", not(feature = "lt2017_2")),
695 doc = "remain in Helix Server syntax."
696 )]
697 #[cfg_attr(
698 all(feature = "lt2024_2", not(feature = "lt2024_1")),
699 doc = "remain in Helix Core Server syntax."
700 )]
701 #[cfg_attr(not(feature = "lt2024_2"), doc = "remain in P4 Server syntax.")]
702 pub fn unified_patch(mut self, v: bool) -> Self {
703 self.mode.unified_patch = v;
704 self
705 }
706}
707
708impl Diff2<DepotContent<Unselected>> {
709 pub fn branch(self, name: impl Into<String>) -> Diff2<DepotContent<BranchMode>> {
721 Diff2 {
722 bin: self.bin,
723 global_opts: self.global_opts,
724 diff_opts: self.diff_opts,
725 mode: DepotContent {
726 differing_only: self.mode.differing_only,
727 quiet_mode: self.mode.quiet_mode,
728 diff_nontext: self.mode.diff_nontext,
729 unified_patch: self.mode.unified_patch,
730 mode: BranchMode {
731 branch: name.into(),
732 },
733 },
734 }
735 }
736
737 pub fn stream(self, name: impl Into<String>) -> Diff2<DepotContent<StreamMode>> {
748 Diff2 {
749 bin: self.bin,
750 global_opts: self.global_opts,
751 diff_opts: self.diff_opts,
752 mode: DepotContent {
753 differing_only: self.mode.differing_only,
754 quiet_mode: self.mode.quiet_mode,
755 diff_nontext: self.mode.diff_nontext,
756 unified_patch: self.mode.unified_patch,
757 mode: StreamMode {
758 stream: name.into(),
759 parent: None,
760 },
761 },
762 }
763 }
764}
765
766impl Diff2<DepotContent<BranchMode>> {
767 pub fn get_branch(&self) -> &str {
775 &self.mode.mode.branch
776 }
777
778 pub fn set_branch(&mut self, v: impl Into<String>) -> &mut Self {
786 self.mode.mode.branch = v.into();
787 self
788 }
789
790 pub fn branch(mut self, v: impl Into<String>) -> Self {
798 self.mode.mode.branch = v.into();
799 self
800 }
801}
802
803impl Diff2<DepotContent<StreamMode>> {
804 pub fn get_stream(&self) -> &str {
812 &self.mode.mode.stream
813 }
814
815 pub fn set_stream(&mut self, v: impl Into<String>) -> &mut Self {
823 self.mode.mode.stream = v.into();
824 self
825 }
826
827 pub fn stream(mut self, v: impl Into<String>) -> Self {
835 self.mode.mode.stream = v.into();
836 self
837 }
838
839 pub fn get_parent(&self) -> Option<&str> {
845 self.mode.mode.parent.as_deref()
846 }
847
848 pub fn set_parent(&mut self, v: impl Into<String>) -> &mut Self {
854 self.mode.mode.parent = Some(v.into());
855 self
856 }
857
858 pub fn parent(mut self, v: impl Into<String>) -> Self {
864 self.mode.mode.parent = Some(v.into());
865 self
866 }
867}
868
869impl<M: ExclusiveOption> SubCommand for Diff2<M> {
870 fn name(&self) -> &str {
871 "diff2"
872 }
873
874 fn inject_local_args(&self, command: &mut Command) {
875 if let Some(diff_opts) = &self.diff_opts {
876 diff_opts.inject_arg(command);
877 }
878
879 self.mode.inject_args(command);
880 }
881
882 fn global_opts(&self) -> Option<&GlobalOpts> {
883 Some(&self.global_opts)
884 }
885}
886
887impl<A, B> ParameterizedSpawn<(A, B)> for Diff2<Unselected>
888where
889 A: AsRef<OsStr>,
890 B: AsRef<OsStr>,
891{
892 type Output = Child;
893 type Error = std::io::Error;
894
895 fn spawn_with(&mut self, (file1, file2): (A, B)) -> Result<Self::Output, Self::Error> {
902 self.setup_command(&self.bin)
903 .arg(file1)
904 .arg(file2)
905 .stdout(Stdio::piped())
906 .stderr(Stdio::piped())
907 .spawn()
908 }
909}
910
911impl<A, B> ParameterizedSpawn<(A, B)> for Diff2<DepotContent<Unselected>>
912where
913 A: AsRef<OsStr>,
914 B: AsRef<OsStr>,
915{
916 type Output = Child;
917 type Error = std::io::Error;
918
919 fn spawn_with(&mut self, (file1, file2): (A, B)) -> Result<Self::Output, Self::Error> {
926 self.setup_command(&self.bin)
927 .arg(file1)
928 .arg(file2)
929 .stdout(Stdio::piped())
930 .stderr(Stdio::piped())
931 .spawn()
932 }
933}
934
935impl ParameterizedSpawn<()> for Diff2<DepotContent<BranchMode>> {
936 type Output = Child;
937 type Error = std::io::Error;
938
939 fn spawn_with(&mut self, _: ()) -> Result<Self::Output, Self::Error> {
944 self.setup_command(&self.bin)
945 .stdout(Stdio::piped())
946 .stderr(Stdio::piped())
947 .spawn()
948 }
949}
950
951impl<I> ParameterizedSpawn<(I,)> for Diff2<DepotContent<BranchMode>>
952where
953 I: AsRef<OsStr>,
954{
955 type Output = Child;
956 type Error = std::io::Error;
957
958 fn spawn_with(&mut self, (tofile,): (I,)) -> Result<Self::Output, Self::Error> {
965 self.setup_command(&self.bin)
966 .arg(tofile)
967 .stdout(Stdio::piped())
968 .stderr(Stdio::piped())
969 .spawn()
970 }
971}
972
973impl<A, B> ParameterizedSpawn<(A, B)> for Diff2<DepotContent<BranchMode>>
974where
975 A: AsRef<OsStr>,
976 B: AsRef<OsStr>,
977{
978 type Output = Child;
979 type Error = std::io::Error;
980
981 fn spawn_with(&mut self, (fromfile, tofile): (A, B)) -> Result<Self::Output, Self::Error> {
986 self.setup_command(&self.bin)
987 .arg(fromfile)
988 .arg(tofile)
989 .stdout(Stdio::piped())
990 .stderr(Stdio::piped())
991 .spawn()
992 }
993}
994
995impl ParameterizedSpawn<()> for Diff2<DepotContent<StreamMode>> {
996 type Output = Child;
997 type Error = std::io::Error;
998
999 fn spawn_with(&mut self, _: ()) -> Result<Self::Output, Self::Error> {
1004 self.setup_command(&self.bin)
1005 .stdout(Stdio::piped())
1006 .stderr(Stdio::piped())
1007 .spawn()
1008 }
1009}
1010
1011impl<I> ParameterizedSpawn<(I,)> for Diff2<DepotContent<StreamMode>>
1012where
1013 I: AsRef<OsStr>,
1014{
1015 type Output = Child;
1016 type Error = std::io::Error;
1017
1018 fn spawn_with(&mut self, (tofile,): (I,)) -> Result<Self::Output, Self::Error> {
1025 self.setup_command(&self.bin)
1026 .arg(tofile)
1027 .stdout(Stdio::piped())
1028 .stderr(Stdio::piped())
1029 .spawn()
1030 }
1031}
1032
1033impl<A, B> ParameterizedSpawn<(A, B)> for Diff2<DepotContent<StreamMode>>
1034where
1035 A: AsRef<OsStr>,
1036 B: AsRef<OsStr>,
1037{
1038 type Output = Child;
1039 type Error = std::io::Error;
1040
1041 fn spawn_with(&mut self, (fromfile, tofile): (A, B)) -> Result<Self::Output, Self::Error> {
1046 self.setup_command(&self.bin)
1047 .arg(fromfile)
1048 .arg(tofile)
1049 .stdout(Stdio::piped())
1050 .stderr(Stdio::piped())
1051 .spawn()
1052 }
1053}
1054
1055#[cfg(not(feature = "lt2019_1"))]
1056impl<A, B> ParameterizedSpawn<(A, B)> for Diff2<StreamSpecMode>
1057where
1058 A: AsRef<OsStr>,
1059 B: AsRef<OsStr>,
1060{
1061 type Output = Child;
1062 type Error = std::io::Error;
1063
1064 fn spawn_with(&mut self, (spec1, spec2): (A, B)) -> Result<Self::Output, Self::Error> {
1073 self.setup_command(&self.bin)
1074 .arg(spec1)
1075 .arg(spec2)
1076 .stdout(Stdio::piped())
1077 .stderr(Stdio::piped())
1078 .spawn()
1079 }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084 use super::*;
1085 use crate::cmd::DiffOptionsBuilder;
1086 use crate::cmd::args_of;
1087
1088 #[test]
1089 fn bin_accessors_update_executable_path() {
1090 let mut command = Diff2::new("p4", GlobalOpts::new());
1091 assert_eq!(command.get_bin(), Path::new("p4"));
1092
1093 command.set_bin("/usr/local/bin/p4");
1094 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
1095
1096 let command = command.bin("p4.exe");
1097 assert_eq!(command.get_bin(), Path::new("p4.exe"));
1098 }
1099
1100 #[test]
1103 fn without_options() {
1104 let diff2 = Diff2::new("p4", GlobalOpts::new());
1105
1106 assert_eq!(args_of(&diff2.setup_command("p4")), ["diff2"]);
1107 }
1108
1109 #[test]
1110 fn depot_content_flags_are_injected() {
1111 let diff2 = Diff2::new("p4", GlobalOpts::new())
1112 .differing_only(true)
1113 .quiet_mode(true)
1114 .diff_nontext(true)
1115 .unified_patch(true);
1116
1117 assert_eq!(
1118 args_of(&diff2.setup_command("p4")),
1119 ["diff2", "-Od", "-q", "-t", "-u"]
1120 );
1121 }
1122
1123 #[test]
1124 fn depot_content_flags_apply_to_branch_mode() {
1125 let diff2 = Diff2::new("p4", GlobalOpts::new())
1126 .quiet_mode(true)
1127 .branch("branch2");
1128
1129 assert!(diff2.get_quiet_mode());
1130 assert_eq!(
1131 args_of(&diff2.setup_command("p4")),
1132 ["diff2", "-q", "-b", "branch2"]
1133 );
1134 }
1135
1136 #[test]
1137 fn depot_content_flags_apply_after_branch_transition() {
1138 let diff2 = Diff2::new("p4", GlobalOpts::new())
1139 .branch("branch2")
1140 .quiet_mode(true);
1141
1142 assert!(diff2.get_quiet_mode());
1143 assert_eq!(
1144 args_of(&diff2.setup_command("p4")),
1145 ["diff2", "-q", "-b", "branch2"]
1146 );
1147 }
1148
1149 #[test]
1150 fn diff_options_are_injected() {
1151 let mut diff2 =
1152 Diff2::new("p4", GlobalOpts::new()).diff_options(DiffOptionsBuilder::unified(None));
1153 diff2.set_diff_options(DiffOptionsBuilder::summary());
1154
1155 assert_eq!(args_of(&diff2.setup_command("p4")), ["diff2", "-ds"]);
1156 }
1157
1158 #[test]
1159 fn branch_mode_injects_branch() {
1160 let diff2 = Diff2::new("p4", GlobalOpts::new()).branch("branch2");
1161
1162 assert_eq!(diff2.get_branch(), "branch2");
1163 assert_eq!(
1164 args_of(&diff2.setup_command("p4")),
1165 ["diff2", "-b", "branch2"]
1166 );
1167 }
1168
1169 #[test]
1170 fn branch_mode_with_files() {
1171 let diff2 = Diff2::new("p4", GlobalOpts::new()).branch("branch2");
1172
1173 let mut command = diff2.setup_command("p4");
1176 command.arg(OsStr::new("//depot/rel1/..."));
1177 command.arg(OsStr::new("//depot/rel2/...#4"));
1178
1179 assert_eq!(
1180 args_of(&command),
1181 [
1182 "diff2",
1183 "-b",
1184 "branch2",
1185 "//depot/rel1/...",
1186 "//depot/rel2/...#4"
1187 ]
1188 );
1189 }
1190
1191 #[test]
1192 fn branch_mode_with_tofile() {
1193 let diff2 = Diff2::new("p4", GlobalOpts::new()).branch("branch2");
1194
1195 let mut command = diff2.setup_command("p4");
1198 command.arg(OsStr::new("//depot/rel2/...#4"));
1199
1200 assert_eq!(
1201 args_of(&command),
1202 ["diff2", "-b", "branch2", "//depot/rel2/...#4"]
1203 );
1204 }
1205
1206 #[test]
1207 fn stream_mode_injects_stream() {
1208 let diff2 = Diff2::new("p4", GlobalOpts::new()).stream("myStream");
1209
1210 assert_eq!(diff2.get_stream(), "myStream");
1211 assert_eq!(
1212 args_of(&diff2.setup_command("p4")),
1213 ["diff2", "-S", "myStream"]
1214 );
1215 }
1216
1217 #[test]
1218 fn stream_mode_with_parent() {
1219 let mut diff2 = Diff2::new("p4", GlobalOpts::new()).stream("myStream");
1220 diff2.set_parent("mainStream");
1221
1222 assert_eq!(diff2.get_parent(), Some("mainStream"));
1223 assert_eq!(
1224 args_of(&diff2.setup_command("p4")),
1225 ["diff2", "-S", "myStream", "-P", "mainStream"]
1226 );
1227 }
1228
1229 #[test]
1230 fn stream_mode_with_tofile() {
1231 let diff2 = Diff2::new("p4", GlobalOpts::new()).stream("myStream");
1232
1233 let mut command = diff2.setup_command("p4");
1236 command.arg(OsStr::new("//depot/rel2/...#4"));
1237
1238 assert_eq!(
1239 args_of(&command),
1240 ["diff2", "-S", "myStream", "//depot/rel2/...#4"]
1241 );
1242 }
1243
1244 #[test]
1245 fn stream_mode_preserves_diff_options() {
1246 let diff2 = Diff2::new("p4", GlobalOpts::new())
1247 .diff_options(DiffOptionsBuilder::summary())
1248 .stream("myStream");
1249
1250 assert_eq!(
1251 args_of(&diff2.setup_command("p4")),
1252 ["diff2", "-ds", "-S", "myStream"]
1253 );
1254 }
1255
1256 #[cfg(not(feature = "lt2019_1"))]
1257 #[test]
1258 fn stream_spec_mode_bare() {
1259 let diff2 = Diff2::new("p4", GlobalOpts::new()).stream_spec_mode();
1260
1261 assert_eq!(args_of(&diff2.setup_command("p4")), ["diff2", "-As"]);
1262 }
1263
1264 #[cfg(not(feature = "lt2019_1"))]
1265 #[test]
1266 fn stream_spec_mode_with_specs() {
1267 let diff2 = Diff2::new("p4", GlobalOpts::new()).stream_spec_mode();
1268
1269 let mut command = diff2.setup_command("p4");
1272 command.arg("myStream@=1");
1273 command.arg("yourStream@2");
1274
1275 assert_eq!(
1276 args_of(&command),
1277 ["diff2", "-As", "myStream@=1", "yourStream@2"]
1278 );
1279 }
1280}