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#[derive(Debug, Clone, Default)]
30pub struct DetailDiffMode {
31 diff_options: Option<DiffOptions>,
32
33 #[cfg(not(feature = "lt2017_2"))]
34 display_added_text_content: bool,
35}
36
37impl ExclusiveOption for DetailDiffMode {
38 fn inject_args(&self, command: &mut Command) {
39 #[cfg(not(feature = "lt2017_2"))]
40 if self.display_added_text_content {
41 command.arg("-a");
42 }
43
44 if let Some(diff_options) = &self.diff_options {
45 diff_options.inject_arg(command);
46 }
47 }
48}
49
50#[cfg_attr(
51 feature = "lt2014_2",
52 doc = "`p4 [g-opts] describe [ -dflags -s -S -f -O ] changelist...`"
53)]
54#[cfg_attr(
55 all(feature = "lt2015_1", not(feature = "lt2014_2")),
56 doc = "`p4 [g-opts] describe [ -doptions -s -S -f -O ] changelist...`"
57)]
58#[cfg_attr(
59 all(feature = "lt2015_2", not(feature = "lt2015_1")),
60 doc = "`p4 [g-opts] describe [-doptions] [-s -S -f -O] changelist …`"
61)]
62#[cfg_attr(
63 all(feature = "lt2016_1", not(feature = "lt2015_2")),
64 doc = "`p4 [g-opts] describe [-doptions] [-s -S -f -O -I] changelist …`"
65)]
66#[cfg_attr(
67 all(feature = "lt2017_1", not(feature = "lt2016_1")),
68 doc = "`p4 [g-opts] describe [-doptions] [-s -S -f -O -I] changelist ...`"
69)]
70#[cfg_attr(
71 all(feature = "lt2017_2", not(feature = "lt2017_1")),
72 doc = "`p4 [g-opts] describe [-doptions] [-f -I -m -O -s -S] changelist ...`"
73)]
74#[cfg_attr(
75 not(feature = "lt2017_2"),
76 doc = "`p4 [g-opts] describe [-doptions] [-a -f -I -m -O -s -S] changelist ...`"
77)]
78#[cfg_attr(
80 feature = "lt2017_2",
81 doc = "Provides information about changelists and the changelists' files."
82)]
83#[cfg_attr(
84 all(feature = "lt2019_1", not(feature = "lt2017_2")),
85 doc = "Provides information about changelists and files in the changelists."
86)]
87#[cfg_attr(
88 not(feature = "lt2019_1"),
89 doc = "Provides information about changelists, as well as files in the",
90 doc = "changelists, and the path of the open stream, if a stream is open."
91)]
92#[cfg_attr(
93 all(feature = "lt2018_1", not(feature = "lt2017_2")),
94 doc = "",
95 doc = "# Note",
96 doc = "",
97 doc = "If the depot is of type `graph`, displays a commit description. See",
98 doc = "the command-line help for `p4 help-graph describe`."
99)]
100#[derive(Debug, Clone, Default)]
107pub struct Describe<M = Unselected> {
108 bin: PathBuf,
109
110 global_opts: GlobalOpts,
111
112 force_restricted: bool,
113
114 #[cfg(not(feature = "lt2015_2"))]
115 use_identity: bool,
116
117 #[cfg(not(feature = "lt2017_1"))]
118 limit: Option<u64>,
119
120 use_original_number: bool,
121
122 include_shelved_files: bool,
123
124 mode: M,
125}
126
127impl Describe<Unselected> {
128 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
132 Self {
133 bin: bin.into(),
134 global_opts,
135 force_restricted: false,
136 #[cfg(not(feature = "lt2015_2"))]
137 use_identity: false,
138 #[cfg(not(feature = "lt2017_1"))]
139 limit: None,
140 use_original_number: false,
141 include_shelved_files: false,
142 mode: Unselected,
143 }
144 }
145
146 #[cfg_attr(feature = "lt2017_1", doc = "the files' diffs.")]
152 #[cfg_attr(not(feature = "lt2017_1"), doc = "the diffs of the files.")]
153 pub fn short_summary_output(self) -> Describe<ShortSummaryMode> {
156 Describe {
157 bin: self.bin,
158 global_opts: self.global_opts,
159 force_restricted: self.force_restricted,
160 #[cfg(not(feature = "lt2015_2"))]
161 use_identity: self.use_identity,
162 #[cfg(not(feature = "lt2017_1"))]
163 limit: self.limit,
164 use_original_number: self.use_original_number,
165 include_shelved_files: self.include_shelved_files,
166 mode: ShortSummaryMode,
167 }
168 }
169
170 #[cfg(not(feature = "lt2017_2"))]
182 pub fn display_added_text_content(self, v: bool) -> Describe<DetailDiffMode> {
183 Describe {
184 bin: self.bin,
185 global_opts: self.global_opts,
186 force_restricted: self.force_restricted,
187 #[cfg(not(feature = "lt2015_2"))]
188 use_identity: self.use_identity,
189 #[cfg(not(feature = "lt2017_1"))]
190 limit: self.limit,
191 use_original_number: self.use_original_number,
192 include_shelved_files: self.include_shelved_files,
193 mode: DetailDiffMode {
194 diff_options: None,
195 display_added_text_content: v,
196 },
197 }
198 }
199
200 #[cfg_attr(feature = "lt2014_2", doc = "-dflags")]
203 #[cfg_attr(not(feature = "lt2014_2"), doc = "-doptions")]
204 #[cfg_attr(
207 feature = "lt2014_2",
208 doc = "flags. See the Usage Notes below for a flag listing."
209 )]
210 #[cfg_attr(
211 all(feature = "lt2015_1", not(feature = "lt2014_2")),
212 doc = "options. See the Usage Notes below for a option listing."
213 )]
214 #[cfg_attr(
215 all(feature = "lt2024_1", not(feature = "lt2015_1")),
216 doc = "options. See Usage Notes for an option listing."
217 )]
218 #[cfg_attr(
219 not(feature = "lt2024_1"),
220 doc = "options. See Usage notes for an option listing."
221 )]
222 pub fn diff_options(self, v: impl Into<DiffOptions>) -> Describe<DetailDiffMode> {
226 Describe {
227 bin: self.bin,
228 global_opts: self.global_opts,
229 force_restricted: self.force_restricted,
230 #[cfg(not(feature = "lt2015_2"))]
231 use_identity: self.use_identity,
232 #[cfg(not(feature = "lt2017_1"))]
233 limit: self.limit,
234 use_original_number: self.use_original_number,
235 include_shelved_files: self.include_shelved_files,
236 mode: DetailDiffMode {
237 diff_options: Some(v.into()),
238 #[cfg(not(feature = "lt2017_2"))]
239 display_added_text_content: false,
240 },
241 }
242 }
243}
244
245impl<M: ExclusiveOption> ParameterizedSpawn for Describe<M> {
246 type Input<'a> = &'a [&'a OsStr];
247 type Output<'a> = Child;
248 type Error = std::io::Error;
249
250 fn spawn_with<'a>(
254 &mut self,
255 changelists: Self::Input<'a>,
256 ) -> Result<Self::Output<'a>, Self::Error> {
257 self.setup_command(&self.bin)
258 .args(changelists)
259 .stdout(Stdio::piped())
260 .stderr(Stdio::piped())
261 .spawn()
262 }
263}
264
265impl<M: ExclusiveOption> Describe<M> {
266 #[cfg_attr(
271 feature = "lt2014_2",
272 doc = "See the [Global Options](GlobalOpts) section."
273 )]
274 #[cfg_attr(
275 all(feature = "lt2015_1", not(feature = "lt2014_2")),
276 doc = "See the [“Global Options”](GlobalOpts) section."
277 )]
278 #[cfg_attr(
279 all(feature = "lt2017_1", not(feature = "lt2015_1")),
280 doc = "See [“Global Options”](GlobalOpts)."
281 )]
282 #[cfg_attr(
283 all(feature = "lt2018_2", not(feature = "lt2017_1")),
284 doc = "See [Global Options](GlobalOpts)."
285 )]
286 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
287 pub fn get_global_opts(&self) -> &GlobalOpts {
288 &self.global_opts
289 }
290
291 #[cfg_attr(
296 feature = "lt2014_2",
297 doc = "See the [Global Options](GlobalOpts) section."
298 )]
299 #[cfg_attr(
300 all(feature = "lt2015_1", not(feature = "lt2014_2")),
301 doc = "See the [“Global Options”](GlobalOpts) section."
302 )]
303 #[cfg_attr(
304 all(feature = "lt2017_1", not(feature = "lt2015_1")),
305 doc = "See [“Global Options”](GlobalOpts)."
306 )]
307 #[cfg_attr(
308 all(feature = "lt2018_2", not(feature = "lt2017_1")),
309 doc = "See [Global Options](GlobalOpts)."
310 )]
311 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
312 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
313 self.global_opts = v;
314 self
315 }
316
317 #[cfg_attr(
322 feature = "lt2014_2",
323 doc = "See the [Global Options](GlobalOpts) section."
324 )]
325 #[cfg_attr(
326 all(feature = "lt2015_1", not(feature = "lt2014_2")),
327 doc = "See the [“Global Options”](GlobalOpts) section."
328 )]
329 #[cfg_attr(
330 all(feature = "lt2017_1", not(feature = "lt2015_1")),
331 doc = "See [“Global Options”](GlobalOpts)."
332 )]
333 #[cfg_attr(
334 all(feature = "lt2018_2", not(feature = "lt2017_1")),
335 doc = "See [Global Options](GlobalOpts)."
336 )]
337 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
338 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
339 self.global_opts = v;
340 self
341 }
342
343 #[cfg_attr(feature = "lt2014_2", doc = "flag requires `admin` permission.")]
349 #[cfg_attr(not(feature = "lt2014_2"), doc = "option requires `admin` permission.")]
350 pub fn get_force_restricted(&self) -> bool {
351 self.force_restricted
352 }
353
354 #[cfg_attr(feature = "lt2014_2", doc = "flag requires `admin` permission.")]
360 #[cfg_attr(not(feature = "lt2014_2"), doc = "option requires `admin` permission.")]
361 pub fn set_force_restricted(&mut self, v: bool) -> &mut Self {
362 self.force_restricted = v;
363 self
364 }
365
366 #[cfg_attr(feature = "lt2014_2", doc = "flag requires `admin` permission.")]
372 #[cfg_attr(not(feature = "lt2014_2"), doc = "option requires `admin` permission.")]
373 pub fn force_restricted(mut self, v: bool) -> Self {
374 self.force_restricted = v;
375 self
376 }
377
378 #[cfg_attr(feature = "lt2017_1", doc = "Identity field of a changelist.")]
384 #[cfg_attr(not(feature = "lt2017_1"), doc = "`Identity` field of a changelist.")]
385 #[cfg(not(feature = "lt2015_2"))]
386 pub fn get_use_identity(&self) -> bool {
387 self.use_identity
388 }
389
390 #[cfg_attr(feature = "lt2017_1", doc = "Identity field of a changelist.")]
396 #[cfg_attr(not(feature = "lt2017_1"), doc = "`Identity` field of a changelist.")]
397 #[cfg(not(feature = "lt2015_2"))]
398 pub fn set_use_identity(&mut self, v: bool) -> &mut Self {
399 self.use_identity = v;
400 self
401 }
402
403 #[cfg_attr(feature = "lt2017_1", doc = "Identity field of a changelist.")]
409 #[cfg_attr(not(feature = "lt2017_1"), doc = "`Identity` field of a changelist.")]
410 #[cfg(not(feature = "lt2015_2"))]
411 pub fn use_identity(mut self, v: bool) -> Self {
412 self.use_identity = v;
413 self
414 }
415
416 #[cfg(not(feature = "lt2017_1"))]
425 pub fn get_limit(&self) -> Option<u64> {
426 self.limit
427 }
428
429 #[cfg(not(feature = "lt2017_1"))]
438 pub fn set_limit(&mut self, v: u64) -> &mut Self {
439 self.limit = Some(v);
440 self
441 }
442
443 #[cfg(not(feature = "lt2017_1"))]
452 pub fn limit(mut self, v: u64) -> Self {
453 self.limit = Some(v);
454 self
455 }
456
457 pub fn get_use_original_number(&self) -> bool {
465 self.use_original_number
466 }
467
468 pub fn set_use_original_number(&mut self, v: bool) -> &mut Self {
476 self.use_original_number = v;
477 self
478 }
479
480 pub fn use_original_number(mut self, v: bool) -> Self {
488 self.use_original_number = v;
489 self
490 }
491
492 #[cfg_attr(
497 feature = "lt2017_1",
498 doc = "Display files shelved for the specified changelist, including",
499 doc = "diffs of those files against their previous depot revision."
500 )]
501 #[cfg_attr(
502 all(feature = "lt2020_2", not(feature = "lt2017_1")),
503 doc = "Display the names of files shelved for the specified changelist,",
504 doc = "including the diff of each file against its previous depot",
505 doc = "revision."
506 )]
507 #[cfg_attr(
508 not(feature = "lt2020_2"),
509 doc = "Lists files that are shelved for the pending changelist and",
510 doc = "displays diffs of the files against their previous revision."
511 )]
512 pub fn get_include_shelved_files(&self) -> bool {
513 self.include_shelved_files
514 }
515
516 #[cfg_attr(
521 feature = "lt2017_1",
522 doc = "Display files shelved for the specified changelist, including",
523 doc = "diffs of those files against their previous depot revision."
524 )]
525 #[cfg_attr(
526 all(feature = "lt2020_2", not(feature = "lt2017_1")),
527 doc = "Display the names of files shelved for the specified changelist,",
528 doc = "including the diff of each file against its previous depot",
529 doc = "revision."
530 )]
531 #[cfg_attr(
532 not(feature = "lt2020_2"),
533 doc = "Lists files that are shelved for the pending changelist and",
534 doc = "displays diffs of the files against their previous revision."
535 )]
536 pub fn set_include_shelved_files(&mut self, v: bool) -> &mut Self {
537 self.include_shelved_files = v;
538 self
539 }
540
541 #[cfg_attr(
546 feature = "lt2017_1",
547 doc = "Display files shelved for the specified changelist, including",
548 doc = "diffs of those files against their previous depot revision."
549 )]
550 #[cfg_attr(
551 all(feature = "lt2020_2", not(feature = "lt2017_1")),
552 doc = "Display the names of files shelved for the specified changelist,",
553 doc = "including the diff of each file against its previous depot",
554 doc = "revision."
555 )]
556 #[cfg_attr(
557 not(feature = "lt2020_2"),
558 doc = "Lists files that are shelved for the pending changelist and",
559 doc = "displays diffs of the files against their previous revision."
560 )]
561 pub fn include_shelved_files(mut self, v: bool) -> Self {
562 self.include_shelved_files = v;
563 self
564 }
565}
566
567impl Describe<DetailDiffMode> {
568 #[cfg_attr(feature = "lt2014_2", doc = "-dflags")]
571 #[cfg_attr(not(feature = "lt2014_2"), doc = "-doptions")]
572 #[cfg_attr(
575 feature = "lt2014_2",
576 doc = "flags. See the Usage Notes below for a flag listing."
577 )]
578 #[cfg_attr(
579 all(feature = "lt2015_1", not(feature = "lt2014_2")),
580 doc = "options. See the Usage Notes below for a option listing."
581 )]
582 #[cfg_attr(
583 all(feature = "lt2024_1", not(feature = "lt2015_1")),
584 doc = "options. See Usage Notes for an option listing."
585 )]
586 #[cfg_attr(
587 not(feature = "lt2024_1"),
588 doc = "options. See Usage notes for an option listing."
589 )]
590 pub fn get_diff_options(&self) -> Option<&DiffOptions> {
591 self.mode.diff_options.as_ref()
592 }
593
594 #[cfg_attr(feature = "lt2014_2", doc = "-dflags")]
597 #[cfg_attr(not(feature = "lt2014_2"), doc = "-doptions")]
598 #[cfg_attr(
601 feature = "lt2014_2",
602 doc = "flags. See the Usage Notes below for a flag listing."
603 )]
604 #[cfg_attr(
605 all(feature = "lt2015_1", not(feature = "lt2014_2")),
606 doc = "options. See the Usage Notes below for a option listing."
607 )]
608 #[cfg_attr(
609 all(feature = "lt2024_1", not(feature = "lt2015_1")),
610 doc = "options. See Usage Notes for an option listing."
611 )]
612 #[cfg_attr(
613 not(feature = "lt2024_1"),
614 doc = "options. See Usage notes for an option listing."
615 )]
616 pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
617 self.mode.diff_options = Some(v.into());
618 self
619 }
620
621 #[cfg(not(feature = "lt2017_2"))]
630 pub fn get_display_added_text_content(&self) -> bool {
631 self.mode.display_added_text_content
632 }
633
634 #[cfg(not(feature = "lt2017_2"))]
643 pub fn set_display_added_text_content(&mut self, v: bool) -> &mut Self {
644 self.mode.display_added_text_content = v;
645 self
646 }
647}
648
649impl<M: ExclusiveOption> SubCommand for Describe<M> {
650 fn name(&self) -> &str {
651 "describe"
652 }
653
654 fn inject_local_args(&self, command: &mut Command) {
655 self.mode.inject_args(command);
656
657 if self.force_restricted {
658 command.arg("-f");
659 }
660
661 #[cfg(not(feature = "lt2015_2"))]
662 if self.use_identity {
663 command.arg("-I");
664 }
665
666 #[cfg(not(feature = "lt2017_1"))]
667 if let Some(max) = self.limit {
668 command.arg("-m").arg(max.to_string());
669 }
670
671 if self.use_original_number {
672 command.arg("-O");
673 }
674
675 if self.include_shelved_files {
676 command.arg("-S");
677 }
678 }
679
680 fn global_opts(&self) -> Option<&GlobalOpts> {
681 Some(&self.global_opts)
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688 use crate::cmd::DiffOptionsBuilder;
689 use crate::cmd::args_of;
690
691 #[test]
694 fn without_options() {
695 let describe = Describe::new("p4", GlobalOpts::new());
696
697 assert_eq!(args_of(&describe.setup_command("p4")), ["describe"]);
698 }
699
700 #[test]
701 fn unselected_with_generic_options() {
702 let mut describe = Describe::new("p4", GlobalOpts::new());
703 describe.set_force_restricted(true);
704 #[cfg(not(feature = "lt2015_2"))]
705 describe.set_use_identity(true);
706 #[cfg(not(feature = "lt2017_1"))]
707 describe.set_limit(2);
708 describe
709 .set_use_original_number(true)
710 .set_include_shelved_files(true);
711
712 let mut expected = vec!["describe"];
713 expected.push("-f");
714 #[cfg(not(feature = "lt2015_2"))]
715 expected.push("-I");
716 #[cfg(not(feature = "lt2017_1"))]
717 expected.extend(["-m", "2"]);
718 expected.extend(["-O", "-S"]);
719
720 assert_eq!(args_of(&describe.setup_command("p4")), expected);
721 }
722
723 #[test]
724 fn short_summary_mode() {
725 let describe = Describe::new("p4", GlobalOpts::new())
726 .force_restricted(true)
727 .short_summary_output();
728
729 assert_eq!(
730 args_of(&describe.setup_command("p4")),
731 ["describe", "-s", "-f"]
732 );
733 }
734
735 #[test]
736 fn mode_transition_preserves_generic_options() {
737 let mut describe = Describe::new("p4", GlobalOpts::new());
738 describe.set_force_restricted(true);
739 #[cfg(not(feature = "lt2017_1"))]
740 describe.set_limit(3);
741
742 let describe = describe.short_summary_output().include_shelved_files(true);
743
744 let mut expected = vec!["describe", "-s", "-f"];
745 #[cfg(not(feature = "lt2017_1"))]
746 expected.extend(["-m", "3"]);
747 expected.push("-S");
748
749 assert_eq!(args_of(&describe.setup_command("p4")), expected);
750 }
751
752 #[test]
753 fn detail_diff_mode_via_diff_options() {
754 let describe =
755 Describe::new("p4", GlobalOpts::new()).diff_options(DiffOptionsBuilder::unified(None));
756
757 assert!(describe.get_diff_options().is_some());
758 assert_eq!(args_of(&describe.setup_command("p4")), ["describe", "-du"]);
759 }
760
761 #[test]
762 fn detail_diff_mode_set_diff_options() {
763 let mut describe =
764 Describe::new("p4", GlobalOpts::new()).diff_options(DiffOptionsBuilder::unified(None));
765 describe.set_diff_options(DiffOptionsBuilder::summary());
766
767 assert_eq!(
768 describe.get_diff_options(),
769 Some(&DiffOptions::from(DiffOptionsBuilder::summary()))
770 );
771 assert_eq!(args_of(&describe.setup_command("p4")), ["describe", "-ds"]);
772 }
773
774 #[cfg(not(feature = "lt2017_2"))]
775 #[test]
776 fn detail_diff_mode_via_display_added_text_content() {
777 let describe = Describe::new("p4", GlobalOpts::new()).display_added_text_content(true);
778
779 assert!(describe.get_display_added_text_content());
780 assert_eq!(describe.get_diff_options(), None);
781 assert_eq!(args_of(&describe.setup_command("p4")), ["describe", "-a"]);
782 }
783
784 #[cfg(not(feature = "lt2017_2"))]
785 #[test]
786 fn detail_diff_mode_combines_a_and_d() {
787 let mut describe = Describe::new("p4", GlobalOpts::new()).display_added_text_content(true);
788 describe.set_diff_options(DiffOptionsBuilder::unified(None));
789
790 assert_eq!(
791 args_of(&describe.setup_command("p4")),
792 ["describe", "-a", "-du"]
793 );
794 }
795
796 #[cfg(not(feature = "lt2017_1"))]
797 #[test]
798 fn limit_is_injected_as_separate_args() {
799 let mut describe = Describe::new("p4", GlobalOpts::new());
800 describe.set_limit(5);
801
802 assert_eq!(
803 args_of(&describe.setup_command("p4")),
804 ["describe", "-m", "5"]
805 );
806 }
807}