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}