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;
11
12#[derive(Debug, Clone, Copy, Default)]
17pub struct ShortSummaryMode;
18
19impl ExclusiveOption for ShortSummaryMode {
20 fn inject_args(&self, command: &mut Command) {
21 command.arg("-s");
22 }
23}
24
25#[cfg_attr(
26 feature = "lt2017_2",
27 doc = "Detail diff output of `p4 describe` (`-d`).",
28 doc = "",
29 doc = "Entered with [`Describe::diff_options`]."
30)]
31#[cfg_attr(
32 not(feature = "lt2017_2"),
33 doc = "Detail diff output of `p4 describe` (`-a` and `-d`).",
34 doc = "",
35 doc = "Entered with [`Describe::display_added_text_content`] or",
36 doc = "[`Describe::diff_options`]."
37)]
38#[derive(Debug, Clone, Default)]
39pub struct DetailDiffMode {
40 diff_options: Option<DiffOptions>,
41
42 #[cfg(not(feature = "lt2017_2"))]
43 display_added_text_content: bool,
44}
45
46impl ExclusiveOption for DetailDiffMode {
47 fn inject_args(&self, command: &mut Command) {
48 #[cfg(not(feature = "lt2017_2"))]
49 if self.display_added_text_content {
50 command.arg("-a");
51 }
52
53 if let Some(diff_options) = &self.diff_options {
54 diff_options.inject_arg(command);
55 }
56 }
57}
58
59#[cfg_attr(
60 feature = "lt2014_2",
61 doc = "`p4 [g-opts] describe [ -dflags -s -S -f -O ] changelist...`"
62)]
63#[cfg_attr(
64 all(feature = "lt2015_1", not(feature = "lt2014_2")),
65 doc = "`p4 [g-opts] describe [ -doptions -s -S -f -O ] changelist...`"
66)]
67#[cfg_attr(
68 all(feature = "lt2015_2", not(feature = "lt2015_1")),
69 doc = "`p4 [g-opts] describe [-doptions] [-s -S -f -O] changelist …`"
70)]
71#[cfg_attr(
72 all(feature = "lt2016_1", not(feature = "lt2015_2")),
73 doc = "`p4 [g-opts] describe [-doptions] [-s -S -f -O -I] changelist …`"
74)]
75#[cfg_attr(
76 all(feature = "lt2017_1", not(feature = "lt2016_1")),
77 doc = "`p4 [g-opts] describe [-doptions] [-s -S -f -O -I] changelist ...`"
78)]
79#[cfg_attr(
80 all(feature = "lt2017_2", not(feature = "lt2017_1")),
81 doc = "`p4 [g-opts] describe [-doptions] [-f -I -m -O -s -S] changelist ...`"
82)]
83#[cfg_attr(
84 not(feature = "lt2017_2"),
85 doc = "`p4 [g-opts] describe [-doptions] [-a -f -I -m -O -s -S] changelist ...`"
86)]
87#[cfg_attr(
89 feature = "lt2017_2",
90 doc = "Provides information about changelists and the changelists' files."
91)]
92#[cfg_attr(
93 all(feature = "lt2019_1", not(feature = "lt2017_2")),
94 doc = "Provides information about changelists and files in the changelists."
95)]
96#[cfg_attr(
97 not(feature = "lt2019_1"),
98 doc = "Provides information about changelists, as well as files in the",
99 doc = "changelists, and the path of the open stream, if a stream is open."
100)]
101#[cfg_attr(
102 all(feature = "lt2018_1", not(feature = "lt2017_2")),
103 doc = "",
104 doc = "# Note",
105 doc = "",
106 doc = "If the depot is of type `graph`, displays a commit description. See",
107 doc = "the command-line help for `p4 help-graph describe`."
108)]
109#[cfg_attr(
113 feature = "lt2017_2",
114 doc = "`-d`; [`Self::short_summary_output`] transitions to the [`ShortSummaryMode`] state, while [`Self::diff_options`] transitions to the [`DetailDiffMode`] state."
115)]
116#[cfg_attr(
117 not(feature = "lt2017_2"),
118 doc = "`-a`/`-d`; [`Self::short_summary_output`] transitions to the [`ShortSummaryMode`] state, while [`Self::display_added_text_content`] and [`Self::diff_options`] transition to the [`DetailDiffMode`] state."
119)]
120#[derive(Debug, Clone, Default)]
121pub struct Describe<M = Unselected> {
122 bin: PathBuf,
123
124 global_opts: GlobalOpts,
125
126 force_restricted: bool,
127
128 #[cfg(not(feature = "lt2015_2"))]
129 use_identity: bool,
130
131 #[cfg(not(feature = "lt2017_1"))]
132 limit: Option<u64>,
133
134 use_original_number: bool,
135
136 include_shelved_files: bool,
137
138 mode: M,
139}
140
141impl Describe<Unselected> {
142 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
146 Self {
147 bin: bin.into(),
148 global_opts,
149 force_restricted: false,
150 #[cfg(not(feature = "lt2015_2"))]
151 use_identity: false,
152 #[cfg(not(feature = "lt2017_1"))]
153 limit: None,
154 use_original_number: false,
155 include_shelved_files: false,
156 mode: Unselected,
157 }
158 }
159
160 #[cfg_attr(feature = "lt2017_1", doc = "the files' diffs.")]
166 #[cfg_attr(not(feature = "lt2017_1"), doc = "the diffs of the files.")]
167 pub fn short_summary_output(self) -> Describe<ShortSummaryMode> {
170 Describe {
171 bin: self.bin,
172 global_opts: self.global_opts,
173 force_restricted: self.force_restricted,
174 #[cfg(not(feature = "lt2015_2"))]
175 use_identity: self.use_identity,
176 #[cfg(not(feature = "lt2017_1"))]
177 limit: self.limit,
178 use_original_number: self.use_original_number,
179 include_shelved_files: self.include_shelved_files,
180 mode: ShortSummaryMode,
181 }
182 }
183
184 #[cfg(not(feature = "lt2017_2"))]
196 pub fn display_added_text_content(self, v: bool) -> Describe<DetailDiffMode> {
197 Describe {
198 bin: self.bin,
199 global_opts: self.global_opts,
200 force_restricted: self.force_restricted,
201 #[cfg(not(feature = "lt2015_2"))]
202 use_identity: self.use_identity,
203 #[cfg(not(feature = "lt2017_1"))]
204 limit: self.limit,
205 use_original_number: self.use_original_number,
206 include_shelved_files: self.include_shelved_files,
207 mode: DetailDiffMode {
208 diff_options: None,
209 display_added_text_content: v,
210 },
211 }
212 }
213
214 #[cfg_attr(feature = "lt2014_2", doc = "-dflags")]
217 #[cfg_attr(not(feature = "lt2014_2"), doc = "-doptions")]
218 #[cfg_attr(
221 feature = "lt2014_2",
222 doc = "flags. See the Usage Notes below for a flag listing."
223 )]
224 #[cfg_attr(
225 all(feature = "lt2015_1", not(feature = "lt2014_2")),
226 doc = "options. See the Usage Notes below for a option listing."
227 )]
228 #[cfg_attr(
229 all(feature = "lt2024_1", not(feature = "lt2015_1")),
230 doc = "options. See Usage Notes for an option listing."
231 )]
232 #[cfg_attr(
233 not(feature = "lt2024_1"),
234 doc = "options. See Usage notes for an option listing."
235 )]
236 pub fn diff_options(self, v: impl Into<DiffOptions>) -> Describe<DetailDiffMode> {
240 Describe {
241 bin: self.bin,
242 global_opts: self.global_opts,
243 force_restricted: self.force_restricted,
244 #[cfg(not(feature = "lt2015_2"))]
245 use_identity: self.use_identity,
246 #[cfg(not(feature = "lt2017_1"))]
247 limit: self.limit,
248 use_original_number: self.use_original_number,
249 include_shelved_files: self.include_shelved_files,
250 mode: DetailDiffMode {
251 diff_options: Some(v.into()),
252 #[cfg(not(feature = "lt2017_2"))]
253 display_added_text_content: false,
254 },
255 }
256 }
257}
258
259impl<M: ExclusiveOption, S, I> ParameterizedSpawn<(S,)> for Describe<M>
260where
261 S: IntoIterator<Item = I>,
262 I: AsRef<OsStr>,
263{
264 type Output = Child;
265 type Error = std::io::Error;
266
267 fn spawn_with(&mut self, (changelists,): (S,)) -> Result<Self::Output, Self::Error> {
271 self.setup_command(&self.bin)
272 .args(changelists)
273 .stdout(Stdio::piped())
274 .stderr(Stdio::piped())
275 .spawn()
276 }
277}
278
279impl<M: ExclusiveOption> Describe<M> {
280 #[cfg_attr(
285 feature = "lt2014_2",
286 doc = "See the [Global Options](GlobalOpts) section."
287 )]
288 #[cfg_attr(
289 all(feature = "lt2015_1", not(feature = "lt2014_2")),
290 doc = "See the [“Global Options”](GlobalOpts) section."
291 )]
292 #[cfg_attr(
293 all(feature = "lt2017_1", not(feature = "lt2015_1")),
294 doc = "See [“Global Options”](GlobalOpts)."
295 )]
296 #[cfg_attr(
297 all(feature = "lt2018_2", not(feature = "lt2017_1")),
298 doc = "See [Global Options](GlobalOpts)."
299 )]
300 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
301 pub fn get_global_opts(&self) -> &GlobalOpts {
302 &self.global_opts
303 }
304
305 #[cfg_attr(
310 feature = "lt2014_2",
311 doc = "See the [Global Options](GlobalOpts) section."
312 )]
313 #[cfg_attr(
314 all(feature = "lt2015_1", not(feature = "lt2014_2")),
315 doc = "See the [“Global Options”](GlobalOpts) section."
316 )]
317 #[cfg_attr(
318 all(feature = "lt2017_1", not(feature = "lt2015_1")),
319 doc = "See [“Global Options”](GlobalOpts)."
320 )]
321 #[cfg_attr(
322 all(feature = "lt2018_2", not(feature = "lt2017_1")),
323 doc = "See [Global Options](GlobalOpts)."
324 )]
325 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
326 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
327 self.global_opts = v;
328 self
329 }
330
331 #[cfg_attr(
336 feature = "lt2014_2",
337 doc = "See the [Global Options](GlobalOpts) section."
338 )]
339 #[cfg_attr(
340 all(feature = "lt2015_1", not(feature = "lt2014_2")),
341 doc = "See the [“Global Options”](GlobalOpts) section."
342 )]
343 #[cfg_attr(
344 all(feature = "lt2017_1", not(feature = "lt2015_1")),
345 doc = "See [“Global Options”](GlobalOpts)."
346 )]
347 #[cfg_attr(
348 all(feature = "lt2018_2", not(feature = "lt2017_1")),
349 doc = "See [Global Options](GlobalOpts)."
350 )]
351 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
352 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
353 self.global_opts = v;
354 self
355 }
356
357 #[cfg_attr(feature = "lt2014_2", doc = "flag requires `admin` permission.")]
363 #[cfg_attr(not(feature = "lt2014_2"), doc = "option requires `admin` permission.")]
364 pub fn get_force_restricted(&self) -> bool {
365 self.force_restricted
366 }
367
368 #[cfg_attr(feature = "lt2014_2", doc = "flag requires `admin` permission.")]
374 #[cfg_attr(not(feature = "lt2014_2"), doc = "option requires `admin` permission.")]
375 pub fn set_force_restricted(&mut self, v: bool) -> &mut Self {
376 self.force_restricted = v;
377 self
378 }
379
380 #[cfg_attr(feature = "lt2014_2", doc = "flag requires `admin` permission.")]
386 #[cfg_attr(not(feature = "lt2014_2"), doc = "option requires `admin` permission.")]
387 pub fn force_restricted(mut self, v: bool) -> Self {
388 self.force_restricted = v;
389 self
390 }
391
392 #[cfg_attr(feature = "lt2017_1", doc = "Identity field of a changelist.")]
398 #[cfg_attr(not(feature = "lt2017_1"), doc = "`Identity` field of a changelist.")]
399 #[cfg(not(feature = "lt2015_2"))]
400 pub fn get_use_identity(&self) -> bool {
401 self.use_identity
402 }
403
404 #[cfg_attr(feature = "lt2017_1", doc = "Identity field of a changelist.")]
410 #[cfg_attr(not(feature = "lt2017_1"), doc = "`Identity` field of a changelist.")]
411 #[cfg(not(feature = "lt2015_2"))]
412 pub fn set_use_identity(&mut self, v: bool) -> &mut Self {
413 self.use_identity = v;
414 self
415 }
416
417 #[cfg_attr(feature = "lt2017_1", doc = "Identity field of a changelist.")]
423 #[cfg_attr(not(feature = "lt2017_1"), doc = "`Identity` field of a changelist.")]
424 #[cfg(not(feature = "lt2015_2"))]
425 pub fn use_identity(mut self, v: bool) -> Self {
426 self.use_identity = v;
427 self
428 }
429
430 #[cfg(not(feature = "lt2017_1"))]
439 pub fn get_limit(&self) -> Option<u64> {
440 self.limit
441 }
442
443 #[cfg(not(feature = "lt2017_1"))]
452 pub fn set_limit(&mut self, v: u64) -> &mut Self {
453 self.limit = Some(v);
454 self
455 }
456
457 #[cfg(not(feature = "lt2017_1"))]
466 pub fn limit(mut self, v: u64) -> Self {
467 self.limit = Some(v);
468 self
469 }
470
471 pub fn get_use_original_number(&self) -> bool {
479 self.use_original_number
480 }
481
482 pub fn set_use_original_number(&mut self, v: bool) -> &mut Self {
490 self.use_original_number = v;
491 self
492 }
493
494 pub fn use_original_number(mut self, v: bool) -> Self {
502 self.use_original_number = v;
503 self
504 }
505
506 #[cfg_attr(
511 feature = "lt2017_1",
512 doc = "Display files shelved for the specified changelist, including",
513 doc = "diffs of those files against their previous depot revision."
514 )]
515 #[cfg_attr(
516 all(feature = "lt2020_2", not(feature = "lt2017_1")),
517 doc = "Display the names of files shelved for the specified changelist,",
518 doc = "including the diff of each file against its previous depot",
519 doc = "revision."
520 )]
521 #[cfg_attr(
522 not(feature = "lt2020_2"),
523 doc = "Lists files that are shelved for the pending changelist and",
524 doc = "displays diffs of the files against their previous revision."
525 )]
526 pub fn get_include_shelved_files(&self) -> bool {
527 self.include_shelved_files
528 }
529
530 #[cfg_attr(
535 feature = "lt2017_1",
536 doc = "Display files shelved for the specified changelist, including",
537 doc = "diffs of those files against their previous depot revision."
538 )]
539 #[cfg_attr(
540 all(feature = "lt2020_2", not(feature = "lt2017_1")),
541 doc = "Display the names of files shelved for the specified changelist,",
542 doc = "including the diff of each file against its previous depot",
543 doc = "revision."
544 )]
545 #[cfg_attr(
546 not(feature = "lt2020_2"),
547 doc = "Lists files that are shelved for the pending changelist and",
548 doc = "displays diffs of the files against their previous revision."
549 )]
550 pub fn set_include_shelved_files(&mut self, v: bool) -> &mut Self {
551 self.include_shelved_files = v;
552 self
553 }
554
555 #[cfg_attr(
560 feature = "lt2017_1",
561 doc = "Display files shelved for the specified changelist, including",
562 doc = "diffs of those files against their previous depot revision."
563 )]
564 #[cfg_attr(
565 all(feature = "lt2020_2", not(feature = "lt2017_1")),
566 doc = "Display the names of files shelved for the specified changelist,",
567 doc = "including the diff of each file against its previous depot",
568 doc = "revision."
569 )]
570 #[cfg_attr(
571 not(feature = "lt2020_2"),
572 doc = "Lists files that are shelved for the pending changelist and",
573 doc = "displays diffs of the files against their previous revision."
574 )]
575 pub fn include_shelved_files(mut self, v: bool) -> Self {
576 self.include_shelved_files = v;
577 self
578 }
579}
580
581impl Describe<DetailDiffMode> {
582 #[cfg_attr(feature = "lt2014_2", doc = "-dflags")]
585 #[cfg_attr(not(feature = "lt2014_2"), doc = "-doptions")]
586 #[cfg_attr(
589 feature = "lt2014_2",
590 doc = "flags. See the Usage Notes below for a flag listing."
591 )]
592 #[cfg_attr(
593 all(feature = "lt2015_1", not(feature = "lt2014_2")),
594 doc = "options. See the Usage Notes below for a option listing."
595 )]
596 #[cfg_attr(
597 all(feature = "lt2024_1", not(feature = "lt2015_1")),
598 doc = "options. See Usage Notes for an option listing."
599 )]
600 #[cfg_attr(
601 not(feature = "lt2024_1"),
602 doc = "options. See Usage notes for an option listing."
603 )]
604 pub fn get_diff_options(&self) -> Option<&DiffOptions> {
605 self.mode.diff_options.as_ref()
606 }
607
608 #[cfg_attr(feature = "lt2014_2", doc = "-dflags")]
611 #[cfg_attr(not(feature = "lt2014_2"), doc = "-doptions")]
612 #[cfg_attr(
615 feature = "lt2014_2",
616 doc = "flags. See the Usage Notes below for a flag listing."
617 )]
618 #[cfg_attr(
619 all(feature = "lt2015_1", not(feature = "lt2014_2")),
620 doc = "options. See the Usage Notes below for a option listing."
621 )]
622 #[cfg_attr(
623 all(feature = "lt2024_1", not(feature = "lt2015_1")),
624 doc = "options. See Usage Notes for an option listing."
625 )]
626 #[cfg_attr(
627 not(feature = "lt2024_1"),
628 doc = "options. See Usage notes for an option listing."
629 )]
630 pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
631 self.mode.diff_options = Some(v.into());
632 self
633 }
634
635 #[cfg(not(feature = "lt2017_2"))]
644 pub fn get_display_added_text_content(&self) -> bool {
645 self.mode.display_added_text_content
646 }
647
648 #[cfg(not(feature = "lt2017_2"))]
657 pub fn set_display_added_text_content(&mut self, v: bool) -> &mut Self {
658 self.mode.display_added_text_content = v;
659 self
660 }
661}
662
663impl<M: ExclusiveOption> SubCommand for Describe<M> {
664 fn name(&self) -> &str {
665 "describe"
666 }
667
668 fn inject_local_args(&self, command: &mut Command) {
669 self.mode.inject_args(command);
670
671 if self.force_restricted {
672 command.arg("-f");
673 }
674
675 #[cfg(not(feature = "lt2015_2"))]
676 if self.use_identity {
677 command.arg("-I");
678 }
679
680 #[cfg(not(feature = "lt2017_1"))]
681 if let Some(max) = self.limit {
682 command.arg("-m").arg(max.to_string());
683 }
684
685 if self.use_original_number {
686 command.arg("-O");
687 }
688
689 if self.include_shelved_files {
690 command.arg("-S");
691 }
692 }
693
694 fn global_opts(&self) -> Option<&GlobalOpts> {
695 Some(&self.global_opts)
696 }
697}
698
699#[cfg(test)]
700mod tests {
701 use super::*;
702 use crate::cmd::DiffOptionsBuilder;
703 use crate::cmd::args_of;
704
705 #[test]
708 fn without_options() {
709 let describe = Describe::new("p4", GlobalOpts::new());
710
711 assert_eq!(args_of(&describe.setup_command("p4")), ["describe"]);
712 }
713
714 #[test]
715 fn unselected_with_generic_options() {
716 let mut describe = Describe::new("p4", GlobalOpts::new());
717 describe.set_force_restricted(true);
718 #[cfg(not(feature = "lt2015_2"))]
719 describe.set_use_identity(true);
720 #[cfg(not(feature = "lt2017_1"))]
721 describe.set_limit(2);
722 describe
723 .set_use_original_number(true)
724 .set_include_shelved_files(true);
725
726 let mut expected = vec!["describe"];
727 expected.push("-f");
728 #[cfg(not(feature = "lt2015_2"))]
729 expected.push("-I");
730 #[cfg(not(feature = "lt2017_1"))]
731 expected.extend(["-m", "2"]);
732 expected.extend(["-O", "-S"]);
733
734 assert_eq!(args_of(&describe.setup_command("p4")), expected);
735 }
736
737 #[test]
738 fn short_summary_mode() {
739 let describe = Describe::new("p4", GlobalOpts::new())
740 .force_restricted(true)
741 .short_summary_output();
742
743 assert_eq!(
744 args_of(&describe.setup_command("p4")),
745 ["describe", "-s", "-f"]
746 );
747 }
748
749 #[test]
750 fn mode_transition_preserves_generic_options() {
751 let mut describe = Describe::new("p4", GlobalOpts::new());
752 describe.set_force_restricted(true);
753 #[cfg(not(feature = "lt2017_1"))]
754 describe.set_limit(3);
755
756 let describe = describe.short_summary_output().include_shelved_files(true);
757
758 let mut expected = vec!["describe", "-s", "-f"];
759 #[cfg(not(feature = "lt2017_1"))]
760 expected.extend(["-m", "3"]);
761 expected.push("-S");
762
763 assert_eq!(args_of(&describe.setup_command("p4")), expected);
764 }
765
766 #[test]
767 fn detail_diff_mode_via_diff_options() {
768 let describe =
769 Describe::new("p4", GlobalOpts::new()).diff_options(DiffOptionsBuilder::unified(None));
770
771 assert!(describe.get_diff_options().is_some());
772 assert_eq!(args_of(&describe.setup_command("p4")), ["describe", "-du"]);
773 }
774
775 #[test]
776 fn detail_diff_mode_set_diff_options() {
777 let mut describe =
778 Describe::new("p4", GlobalOpts::new()).diff_options(DiffOptionsBuilder::unified(None));
779 describe.set_diff_options(DiffOptionsBuilder::summary());
780
781 assert_eq!(
782 describe.get_diff_options(),
783 Some(&DiffOptions::from(DiffOptionsBuilder::summary()))
784 );
785 assert_eq!(args_of(&describe.setup_command("p4")), ["describe", "-ds"]);
786 }
787
788 #[cfg(not(feature = "lt2017_2"))]
789 #[test]
790 fn detail_diff_mode_via_display_added_text_content() {
791 let describe = Describe::new("p4", GlobalOpts::new()).display_added_text_content(true);
792
793 assert!(describe.get_display_added_text_content());
794 assert_eq!(describe.get_diff_options(), None);
795 assert_eq!(args_of(&describe.setup_command("p4")), ["describe", "-a"]);
796 }
797
798 #[cfg(not(feature = "lt2017_2"))]
799 #[test]
800 fn detail_diff_mode_combines_a_and_d() {
801 let mut describe = Describe::new("p4", GlobalOpts::new()).display_added_text_content(true);
802 describe.set_diff_options(DiffOptionsBuilder::unified(None));
803
804 assert_eq!(
805 args_of(&describe.setup_command("p4")),
806 ["describe", "-a", "-du"]
807 );
808 }
809
810 #[cfg(not(feature = "lt2017_1"))]
811 #[test]
812 fn limit_is_injected_as_separate_args() {
813 let mut describe = Describe::new("p4", GlobalOpts::new());
814 describe.set_limit(5);
815
816 assert_eq!(
817 args_of(&describe.setup_command("p4")),
818 ["describe", "-m", "5"]
819 );
820 }
821}