1use std::ffi::OsStr;
2use std::path::PathBuf;
3use std::process::{Child, Command, Stdio};
4
5use super::{DiffOptions, ExclusiveOption, SubCommand, Unselected};
6
7use crate::global::GlobalOpts;
8use crate::spawn::ParameterizedSpawn;
9
10#[cfg(not(feature = "lt2016_1"))]
12#[derive(Debug, Clone, Copy)]
13enum Tab {
14 Default,
16 Custom(u32),
18}
19
20pub mod follow {
23 #[derive(Debug, Clone, Copy, Default)]
25 pub struct Branches;
26
27 #[derive(Debug, Clone, Copy, Default)]
29 pub struct Integrations;
30}
31
32impl ExclusiveOption for follow::Branches {
33 fn inject_args(&self, command: &mut Command) {
34 command.arg("-i");
35 }
36}
37
38impl ExclusiveOption for follow::Integrations {
39 fn inject_args(&self, command: &mut Command) {
40 command.arg("-I");
41 }
42}
43
44#[cfg_attr(
45 feature = "lt2014_2",
46 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -d flag] file[revRange] ...`: print file lines along with their revisions."
47)]
48#[cfg_attr(
49 all(feature = "lt2015_1", not(feature = "lt2014_2")),
50 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -d options] file[revRange] ...`: print file lines along with their revisions."
51)]
52#[cfg_attr(
53 all(feature = "lt2015_2", not(feature = "lt2015_1")),
54 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t] [-d options] file[revRange] ...`: print file lines along with their revisions."
55)]
56#[cfg_attr(
57 all(feature = "lt2016_1", not(feature = "lt2015_2")),
58 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -u] [-d options] file[revRange] ...`: print file lines along with their revisions."
59)]
60#[cfg_attr(
61 all(feature = "lt2018_1", not(feature = "lt2016_1")),
62 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -u -T] [-d options] file[revRange] ...`: print file lines along with their revisions."
63)]
64#[cfg_attr(
65 all(feature = "lt2019_1", not(feature = "lt2018_1")),
66 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -u -T] [-d options] File Spec[revSpec]`: print file lines along with their revisions."
67)]
68#[cfg_attr(
69 all(feature = "lt2022_2", not(feature = "lt2019_1")),
70 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -T -u] [-d options] File Spec[revSpec]`: print file lines along with their revisions."
71)]
72#[cfg_attr(
73 not(feature = "lt2022_2"),
74 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -T -u] [-d options] FileSpec[revSpec]`: print file lines along with their revisions."
75)]
76#[derive(Debug, Clone, Default)]
77pub struct Annotate<F = Unselected> {
78 bin: PathBuf,
79
80 global_opts: GlobalOpts,
81
82 all_lines: bool,
83
84 changelist_number: bool,
85
86 diff_options: Option<DiffOptions>,
87
88 follow: F,
89
90 quiet_mode: bool,
91
92 force_binary: bool,
93
94 #[cfg(not(feature = "lt2015_2"))]
95 user_date: bool,
96
97 #[cfg(not(feature = "lt2016_1"))]
98 tab: Option<Tab>,
99}
100
101impl Annotate<Unselected> {
102 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
106 Self {
107 bin: bin.into(),
108 global_opts,
109 ..Default::default()
110 }
111 }
112
113 #[cfg_attr(
120 feature = "lt2017_2",
121 doc = "Perforce includes revisions up to the branch point."
122 )]
123 #[cfg_attr(
124 not(feature = "lt2017_2"),
125 doc = "the output includes revisions up to the branch point."
126 )]
127 pub fn follow_branches(self) -> Annotate<follow::Branches> {
130 Annotate {
131 bin: self.bin,
132 global_opts: self.global_opts,
133 all_lines: self.all_lines,
134 changelist_number: self.changelist_number,
135 diff_options: self.diff_options,
136 follow: follow::Branches,
137 quiet_mode: self.quiet_mode,
138 force_binary: self.force_binary,
139 #[cfg(not(feature = "lt2015_2"))]
140 user_date: self.user_date,
141 #[cfg(not(feature = "lt2016_1"))]
142 tab: self.tab,
143 }
144 }
145
146 #[cfg_attr(
154 feature = "lt2017_2",
155 doc = "If that source was itself the result of an integration, that",
156 doc = "source will be used instead, and so on. The use of the -I option",
157 doc = "implies the -c option. The -I option cannot be combined with -i."
158 )]
159 #[cfg_attr(
160 not(feature = "lt2017_2"),
161 doc = "If that source was itself the result of an integration, that",
162 doc = "source will be used instead. The use of the -I option implies the",
163 doc = "-c option. The -I option cannot be combined with -i."
164 )]
165 pub fn follow_integrations(self) -> Annotate<follow::Integrations> {
166 Annotate {
167 bin: self.bin,
168 global_opts: self.global_opts,
169 all_lines: self.all_lines,
170 changelist_number: self.changelist_number,
171 diff_options: self.diff_options,
172 follow: follow::Integrations,
173 quiet_mode: self.quiet_mode,
174 force_binary: self.force_binary,
175 #[cfg(not(feature = "lt2015_2"))]
176 user_date: self.user_date,
177 #[cfg(not(feature = "lt2016_1"))]
178 tab: self.tab,
179 }
180 }
181}
182
183impl<F: ExclusiveOption> ParameterizedSpawn for Annotate<F> {
184 type Input<'a> = &'a [&'a OsStr];
185 type Output<'a> = Child;
186 type Error = std::io::Error;
187
188 fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
192 self.setup_command(&self.bin)
193 .args(files)
194 .stdout(Stdio::piped())
195 .stderr(Stdio::piped())
196 .spawn()
197 }
198}
199
200impl<F: ExclusiveOption> Annotate<F> {
201 #[cfg_attr(
206 feature = "lt2014_2",
207 doc = "See the [Global Options](GlobalOpts) section."
208 )]
209 #[cfg_attr(
210 all(feature = "lt2015_1", not(feature = "lt2014_2")),
211 doc = "See the [“Global Options”](GlobalOpts) section."
212 )]
213 #[cfg_attr(
214 all(feature = "lt2017_1", not(feature = "lt2015_1")),
215 doc = "See [“Global Options”](GlobalOpts)."
216 )]
217 #[cfg_attr(
218 all(feature = "lt2018_2", not(feature = "lt2017_1")),
219 doc = "See [Global Options](GlobalOpts)."
220 )]
221 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
222 pub fn get_global_opts(&self) -> &GlobalOpts {
223 &self.global_opts
224 }
225
226 #[cfg_attr(
231 feature = "lt2014_2",
232 doc = "See the [Global Options](GlobalOpts) section."
233 )]
234 #[cfg_attr(
235 all(feature = "lt2015_1", not(feature = "lt2014_2")),
236 doc = "See the [“Global Options”](GlobalOpts) section."
237 )]
238 #[cfg_attr(
239 all(feature = "lt2017_1", not(feature = "lt2015_1")),
240 doc = "See [“Global Options”](GlobalOpts)."
241 )]
242 #[cfg_attr(
243 all(feature = "lt2018_2", not(feature = "lt2017_1")),
244 doc = "See [Global Options](GlobalOpts)."
245 )]
246 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
247 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
248 self.global_opts = v;
249 self
250 }
251
252 #[cfg_attr(
257 feature = "lt2014_2",
258 doc = "See the [Global Options](GlobalOpts) section."
259 )]
260 #[cfg_attr(
261 all(feature = "lt2015_1", not(feature = "lt2014_2")),
262 doc = "See the [“Global Options”](GlobalOpts) section."
263 )]
264 #[cfg_attr(
265 all(feature = "lt2017_1", not(feature = "lt2015_1")),
266 doc = "See [“Global Options”](GlobalOpts)."
267 )]
268 #[cfg_attr(
269 all(feature = "lt2018_2", not(feature = "lt2017_1")),
270 doc = "See [Global Options](GlobalOpts)."
271 )]
272 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
273 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
274 self.global_opts = v;
275 self
276 }
277
278 pub fn get_all_lines(&self) -> bool {
286 self.all_lines
287 }
288
289 pub fn set_all_lines(&mut self, v: bool) -> &mut Self {
297 self.all_lines = v;
298 self
299 }
300
301 pub fn all_lines(mut self, v: bool) -> Self {
309 self.all_lines = v;
310 self
311 }
312
313 pub fn get_changelist_number(&self) -> bool {
321 self.changelist_number
322 }
323
324 pub fn set_changelist_number(&mut self, v: bool) -> &mut Self {
332 self.changelist_number = v;
333 self
334 }
335
336 pub fn changelist_number(mut self, v: bool) -> Self {
344 self.changelist_number = v;
345 self
346 }
347
348 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
351 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
352 #[cfg_attr(
355 feature = "lt2014_2",
356 doc = "flags. See the Usage Notes below for a listing of these",
357 doc = "flags."
358 )]
359 #[cfg_attr(
360 all(feature = "lt2015_1", not(feature = "lt2014_2")),
361 doc = "options. See Usage Notes below for a listing of these",
362 doc = "options."
363 )]
364 #[cfg_attr(
365 all(feature = "lt2024_1", not(feature = "lt2015_1")),
366 doc = "options. See Usage Notes for a listing of these options."
367 )]
368 #[cfg_attr(
369 not(feature = "lt2024_1"),
370 doc = "options. See Usage notes for a listing of these options."
371 )]
372 pub fn get_diff_options(&self) -> Option<&DiffOptions> {
373 self.diff_options.as_ref()
374 }
375
376 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
379 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
380 #[cfg_attr(
383 feature = "lt2014_2",
384 doc = "flags. See the Usage Notes below for a listing of these",
385 doc = "flags."
386 )]
387 #[cfg_attr(
388 all(feature = "lt2015_1", not(feature = "lt2014_2")),
389 doc = "options. See Usage Notes below for a listing of these",
390 doc = "options."
391 )]
392 #[cfg_attr(
393 all(feature = "lt2024_1", not(feature = "lt2015_1")),
394 doc = "options. See Usage Notes for a listing of these options."
395 )]
396 #[cfg_attr(
397 not(feature = "lt2024_1"),
398 doc = "options. See Usage notes for a listing of these options."
399 )]
400 pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
401 self.diff_options = Some(v.into());
402 self
403 }
404
405 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
408 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
409 #[cfg_attr(
412 feature = "lt2014_2",
413 doc = "flags. See the Usage Notes below for a listing of these",
414 doc = "flags."
415 )]
416 #[cfg_attr(
417 all(feature = "lt2015_1", not(feature = "lt2014_2")),
418 doc = "options. See Usage Notes below for a listing of these",
419 doc = "options."
420 )]
421 #[cfg_attr(
422 all(feature = "lt2024_1", not(feature = "lt2015_1")),
423 doc = "options. See Usage Notes for a listing of these options."
424 )]
425 #[cfg_attr(
426 not(feature = "lt2024_1"),
427 doc = "options. See Usage notes for a listing of these options."
428 )]
429 pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
430 self.diff_options = Some(v.into());
431 self
432 }
433
434 #[cfg_attr(
439 feature = "lt2017_2",
440 doc = "Quiet mode; suppress the one-line header for each file."
441 )]
442 #[cfg_attr(
443 not(feature = "lt2017_2"),
444 doc = "Quiet mode, which suppresses the one-line header for each file."
445 )]
446 pub fn get_quiet_mode(&self) -> bool {
447 self.quiet_mode
448 }
449
450 #[cfg_attr(
455 feature = "lt2017_2",
456 doc = "Quiet mode; suppress the one-line header for each file."
457 )]
458 #[cfg_attr(
459 not(feature = "lt2017_2"),
460 doc = "Quiet mode, which suppresses the one-line header for each file."
461 )]
462 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
463 self.quiet_mode = v;
464 self
465 }
466
467 #[cfg_attr(
472 feature = "lt2017_2",
473 doc = "Quiet mode; suppress the one-line header for each file."
474 )]
475 #[cfg_attr(
476 not(feature = "lt2017_2"),
477 doc = "Quiet mode, which suppresses the one-line header for each file."
478 )]
479 pub fn quiet_mode(mut self, v: bool) -> Self {
480 self.quiet_mode = v;
481 self
482 }
483
484 pub fn get_force_binary(&self) -> bool {
490 self.force_binary
491 }
492
493 pub fn set_force_binary(&mut self, v: bool) -> &mut Self {
499 self.force_binary = v;
500 self
501 }
502
503 pub fn force_binary(mut self, v: bool) -> Self {
509 self.force_binary = v;
510 self
511 }
512
513 #[cfg(not(feature = "lt2015_2"))]
520 pub fn get_user_date(&self) -> bool {
521 self.user_date
522 }
523
524 #[cfg(not(feature = "lt2015_2"))]
531 pub fn set_user_date(&mut self, v: bool) -> &mut Self {
532 self.user_date = v;
533 self
534 }
535
536 #[cfg(not(feature = "lt2015_2"))]
543 pub fn user_date(mut self, v: bool) -> Self {
544 self.user_date = v;
545 self
546 }
547
548 #[cfg(not(feature = "lt2016_1"))]
557 pub fn get_tab(&self) -> Option<u32> {
558 match self.tab {
559 Some(Tab::Default) => Some(8),
560 Some(Tab::Custom(value)) => Some(value),
561 None => None,
562 }
563 }
564
565 #[cfg(not(feature = "lt2016_1"))]
572 pub fn set_tab(&mut self, v: u32) -> &mut Self {
573 self.tab = Some(Tab::Custom(v));
574 self
575 }
576
577 #[cfg(not(feature = "lt2016_1"))]
584 pub fn tab(mut self, v: u32) -> Self {
585 self.tab = Some(Tab::Custom(v));
586 self
587 }
588
589 #[cfg(not(feature = "lt2016_1"))]
596 pub fn set_default_tab(&mut self) -> &mut Self {
597 self.tab = Some(Tab::Default);
598 self
599 }
600
601 #[cfg(not(feature = "lt2016_1"))]
608 pub fn default_tab(mut self) -> Self {
609 self.tab = Some(Tab::Default);
610 self
611 }
612}
613
614impl<F: ExclusiveOption> SubCommand for Annotate<F> {
615 fn name(&self) -> &str {
616 "annotate"
617 }
618
619 fn inject_local_args(&self, command: &mut Command) {
620 if self.all_lines {
621 command.arg("-a");
622 }
623
624 if self.changelist_number {
625 command.arg("-c");
626 }
627
628 if let Some(diff_options) = &self.diff_options {
629 diff_options.inject_arg(command);
630 }
631
632 self.follow.inject_args(command);
633
634 if self.quiet_mode {
635 command.arg("-q");
636 }
637
638 if self.force_binary {
639 command.arg("-t");
640 }
641
642 #[cfg(not(feature = "lt2015_2"))]
643 {
644 if self.user_date {
645 command.arg("-u");
646 }
647 }
648
649 #[cfg(not(feature = "lt2016_1"))]
650 {
651 if let Some(tab) = self.tab {
652 match tab {
653 Tab::Default => {
654 command.arg("-T");
655 }
656 Tab::Custom(value) => {
657 command.arg(format!("--tab={value}"));
658 }
659 }
660 }
661 }
662 }
663
664 fn global_opts(&self) -> Option<&GlobalOpts> {
665 Some(&self.global_opts)
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672 use crate::cmd::DiffOptionsBuilder;
673 use crate::cmd::args_of;
674
675 #[test]
678 fn without_options() {
679 let annotate = Annotate::new("p4", GlobalOpts::new());
680
681 assert_eq!(args_of(&annotate.setup_command("p4")), ["annotate"]);
682 }
683
684 #[test]
685 fn all_options() {
686 let mut annotate = Annotate::new("p4", GlobalOpts::new());
687 annotate
688 .set_all_lines(true)
689 .set_changelist_number(true)
690 .set_diff_options(DiffOptionsBuilder::raw("Su2"))
691 .set_quiet_mode(true)
692 .set_force_binary(true);
693 #[cfg(not(feature = "lt2015_2"))]
694 annotate.set_user_date(true);
695
696 #[cfg_attr(feature = "lt2016_1", allow(unused_mut))]
699 let mut annotate = annotate.follow_branches();
700
701 #[cfg_attr(feature = "lt2016_1", allow(unused_mut))]
703 let mut expected = vec!["annotate", "-a", "-c", "-dSu2", "-i", "-q", "-t"];
704 #[cfg(not(feature = "lt2015_2"))]
705 expected.push("-u");
706 #[cfg(not(feature = "lt2016_1"))]
707 {
708 annotate.set_default_tab();
709 expected.push("-T");
710 }
711
712 assert_eq!(args_of(&annotate.setup_command("p4")), expected);
713 }
714
715 #[cfg(not(feature = "lt2016_1"))]
716 #[test]
717 fn default_tab_uses_short_flag() {
718 let mut annotate = Annotate::new("p4", GlobalOpts::new());
719 annotate.set_default_tab();
720
721 assert_eq!(annotate.get_tab(), Some(8));
722 assert_eq!(args_of(&annotate.setup_command("p4")), ["annotate", "-T"]);
723 }
724
725 #[cfg(not(feature = "lt2016_1"))]
726 #[test]
727 fn custom_tab_uses_long_flag() {
728 let mut annotate = Annotate::new("p4", GlobalOpts::new());
729 annotate.set_tab(4);
730
731 assert_eq!(annotate.get_tab(), Some(4));
732 assert_eq!(
733 args_of(&annotate.setup_command("p4")),
734 ["annotate", "--tab=4"]
735 );
736 }
737}