1use std::ffi::OsStr;
2use std::path::{Path, PathBuf};
3use std::process::{Child, Command, Stdio};
4
5use super::{ExclusiveOption, SubCommand};
6
7use crate::global::GlobalOpts;
8use crate::spawn::ParameterizedSpawn;
9
10#[derive(Debug, Clone, Copy, Default)]
15pub struct SafeArchive {
16 skip_head_revisions: bool,
17
18 #[cfg(not(feature = "lt2019_1"))]
19 include_lazy_copies: bool,
20}
21
22impl ExclusiveOption for SafeArchive {
23 fn inject_args(&self, command: &mut Command) {
24 if self.skip_head_revisions {
25 command.arg("-h");
26 }
27
28 #[cfg(not(feature = "lt2019_1"))]
29 if self.include_lazy_copies {
30 command.arg("-z");
31 }
32 }
33}
34
35#[derive(Debug, Clone, Copy, Default)]
40pub struct PhysicalPurge;
41
42impl ExclusiveOption for PhysicalPurge {
43 fn inject_args(&self, command: &mut Command) {
44 command.arg("-p");
45 }
46}
47
48#[cfg_attr(
49 feature = "lt2015_1",
50 doc = "`p4 [g-opts] archive [-n -h -p -q -t] -D depot file[revRange] ...`: archive obsolete revisions to an archive depot."
51)]
52#[cfg_attr(
53 all(feature = "lt2018_1", not(feature = "lt2015_1")),
54 doc = "`p4 [g-opts] archive [-h -n -p -q -t] -D depot file[revRange] ...`: archive obsolete revisions to an archive depot."
55)]
56#[cfg_attr(
57 all(feature = "lt2019_1", not(feature = "lt2018_1")),
58 doc = "`p4 [g-opts] archive [-h -n -p -q -t] -D depot File Spec[revSpec]`: archive obsolete revisions to an archive depot."
59)]
60#[cfg_attr(
61 not(feature = "lt2019_1"),
62 doc = "`p4 [g-opts] archive [-n -h -p -q -t -z] -D depot File Spec[revSpec]`: archive obsolete revisions to an archive depot."
63)]
64#[derive(Debug, Clone, Default)]
68pub struct Archive<S = SafeArchive> {
69 bin: PathBuf,
70
71 global_opts: GlobalOpts,
72
73 preview: bool,
74
75 quiet_mode: bool,
76
77 archive_delta: bool,
78
79 depot: Option<String>,
80
81 archive_mode: S,
82}
83
84impl<S: ExclusiveOption> SubCommand for Archive<S> {
85 fn name(&self) -> &str {
86 "archive"
87 }
88
89 fn inject_local_args(&self, command: &mut Command) {
90 self.archive_mode.inject_args(command);
91
92 if self.preview {
93 command.arg("-n");
94 }
95
96 if self.quiet_mode {
97 command.arg("-q");
98 }
99
100 if self.archive_delta {
101 command.arg("-t");
102 }
103
104 if let Some(depot) = self.depot.as_ref() {
105 command.arg("-D").arg(depot);
106 }
107 }
108
109 fn global_opts(&self) -> Option<&GlobalOpts> {
110 Some(&self.global_opts)
111 }
112}
113
114impl Archive<SafeArchive> {
115 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
119 Self {
120 bin: bin.into(),
121 global_opts,
122 ..Default::default()
123 }
124 }
125
126 #[cfg_attr(
131 feature = "lt2019_1",
132 doc = "Purge any archives of the specified files named in the archive depot.",
133 doc = "(The action for affected revisions is set to `purge` on completion. File",
134 doc = "contents are no longer accessible from `p4 restore`.)"
135 )]
136 #[cfg_attr(
137 all(feature = "lt2019_2", not(feature = "lt2019_1")),
138 doc = "Purge any archives of the specified files named in the archive depot.",
139 doc = "The action for affected revisions is set to `purge` on completion.",
140 doc = "",
141 doc = "**Warning:** File contents are no longer accessible from `p4 restore`."
142 )]
143 #[cfg_attr(
144 all(feature = "lt2024_1", not(feature = "lt2019_2")),
145 doc = "Purge any archives of the specified files named in the archive depot.",
146 doc = "The action for affected revisions is set to `purge` on completion.",
147 doc = "",
148 doc = "**Warning:** File contents are no longer accessible from `p4 restore`.",
149 doc = "",
150 doc = "**Tip:** If you want to retain the metadata of purged files, do one of the",
151 doc = "following:",
152 doc = "",
153 doc = "- use the `-p` option of `p4 obliterate` (recommended)",
154 doc = "- perform the two-step sequence of `p4 archive` followed by",
155 doc = " `p4 archive -p` (time-consuming)"
156 )]
157 #[cfg_attr(
158 all(feature = "lt2024_2", not(feature = "lt2024_1")),
159 doc = "For the specified files in the archive depot, remove the physical file",
160 doc = "revisions but retain the revision history with the latest action being set",
161 doc = "to `purge`.",
162 doc = "",
163 doc = "**Warning:** File contents are no longer accessible from `p4 restore`.",
164 doc = "",
165 doc = "**Tip:** If you want to retain the metadata of purged files, do one of the",
166 doc = "following:",
167 doc = "",
168 doc = "- use the `-p` option of `p4 obliterate` (recommended)",
169 doc = "- perform the two-step sequence of `p4 archive` followed by",
170 doc = " `p4 archive -p` (time-consuming)"
171 )]
172 #[cfg_attr(
173 not(feature = "lt2024_2"),
174 doc = "For the specified files in the archive depot, remove the physical file",
175 doc = "revisions but retain the revision history with the latest action being set",
176 doc = "to `purge`. File contents are no longer accessible from `p4 restore`.",
177 doc = "",
178 doc = "If you want to retain the metadata of purged files, do one of the following:",
179 doc = "",
180 doc = "- use the `-p` option of `p4 obliterate` (recommended)",
181 doc = "- perform the two-step sequence of `p4 archive` followed by",
182 doc = " `p4 archive -p` (time-consuming)"
183 )]
184 pub fn purge_physical_files(self) -> Archive<PhysicalPurge> {
189 Archive {
190 bin: self.bin,
191 global_opts: self.global_opts,
192 preview: self.preview,
193 quiet_mode: self.quiet_mode,
194 archive_delta: self.archive_delta,
195 depot: self.depot,
196 archive_mode: PhysicalPurge,
197 }
198 }
199}
200
201impl<M, S, I> ParameterizedSpawn<(S,)> for Archive<M>
202where
203 Archive<M>: SubCommand,
204 S: IntoIterator<Item = I>,
205 I: AsRef<OsStr>,
206{
207 type Output = Child;
208 type Error = std::io::Error;
209
210 fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
214 self.setup_command(&self.bin)
215 .args(files)
216 .stdout(Stdio::piped())
217 .stderr(Stdio::piped())
218 .spawn()
219 }
220}
221
222impl<M> Archive<M>
223where
224 Archive<M>: SubCommand,
225{
226 pub fn get_bin(&self) -> &Path {
228 &self.bin
229 }
230
231 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
233 self.bin = bin.into();
234 self
235 }
236
237 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
239 self.set_bin(bin);
240 self
241 }
242
243 #[cfg_attr(
248 feature = "lt2014_2",
249 doc = "See the [Global Options](GlobalOpts) section."
250 )]
251 #[cfg_attr(
252 all(feature = "lt2015_1", not(feature = "lt2014_2")),
253 doc = "See the [“Global Options”](GlobalOpts) section."
254 )]
255 #[cfg_attr(
256 all(feature = "lt2017_1", not(feature = "lt2015_1")),
257 doc = "See [“Global Options”](GlobalOpts)."
258 )]
259 #[cfg_attr(
260 all(feature = "lt2018_2", not(feature = "lt2017_1")),
261 doc = "See [Global Options](GlobalOpts)."
262 )]
263 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
264 pub fn get_global_opts(&self) -> &GlobalOpts {
265 &self.global_opts
266 }
267
268 #[cfg_attr(
273 feature = "lt2014_2",
274 doc = "See the [Global Options](GlobalOpts) section."
275 )]
276 #[cfg_attr(
277 all(feature = "lt2015_1", not(feature = "lt2014_2")),
278 doc = "See the [“Global Options”](GlobalOpts) section."
279 )]
280 #[cfg_attr(
281 all(feature = "lt2017_1", not(feature = "lt2015_1")),
282 doc = "See [“Global Options”](GlobalOpts)."
283 )]
284 #[cfg_attr(
285 all(feature = "lt2018_2", not(feature = "lt2017_1")),
286 doc = "See [Global Options](GlobalOpts)."
287 )]
288 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
289 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
290 self.global_opts = v;
291 self
292 }
293
294 #[cfg_attr(
299 feature = "lt2014_2",
300 doc = "See the [Global Options](GlobalOpts) section."
301 )]
302 #[cfg_attr(
303 all(feature = "lt2015_1", not(feature = "lt2014_2")),
304 doc = "See the [“Global Options”](GlobalOpts) section."
305 )]
306 #[cfg_attr(
307 all(feature = "lt2017_1", not(feature = "lt2015_1")),
308 doc = "See [“Global Options”](GlobalOpts)."
309 )]
310 #[cfg_attr(
311 all(feature = "lt2018_2", not(feature = "lt2017_1")),
312 doc = "See [Global Options](GlobalOpts)."
313 )]
314 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
315 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
316 self.global_opts = v;
317 self
318 }
319
320 pub fn get_depot(&self) -> Option<&String> {
326 self.depot.as_ref()
327 }
328
329 pub fn set_depot(&mut self, v: impl Into<String>) -> &mut Self {
335 self.depot = Some(v.into());
336 self
337 }
338
339 pub fn depot(mut self, v: impl Into<String>) -> Self {
345 self.depot = Some(v.into());
346 self
347 }
348
349 #[cfg_attr(
354 feature = "lt2018_1",
355 doc = "Do not archive revisions; report on which revisions would have been archived."
356 )]
357 #[cfg_attr(
358 not(feature = "lt2018_1"),
359 doc = "Do not archive revisions. Instead, report on which revisions would have been",
360 doc = "archived."
361 )]
362 pub fn get_preview(&self) -> bool {
363 self.preview
364 }
365
366 #[cfg_attr(
371 feature = "lt2018_1",
372 doc = "Do not archive revisions; report on which revisions would have been archived."
373 )]
374 #[cfg_attr(
375 not(feature = "lt2018_1"),
376 doc = "Do not archive revisions. Instead, report on which revisions would have been",
377 doc = "archived."
378 )]
379 pub fn set_preview(&mut self, v: bool) -> &mut Self {
380 self.preview = v;
381 self
382 }
383
384 #[cfg_attr(
389 feature = "lt2018_1",
390 doc = "Do not archive revisions; report on which revisions would have been archived."
391 )]
392 #[cfg_attr(
393 not(feature = "lt2018_1"),
394 doc = "Do not archive revisions. Instead, report on which revisions would have been",
395 doc = "archived."
396 )]
397 pub fn preview(mut self, v: bool) -> Self {
398 self.preview = v;
399 self
400 }
401
402 #[cfg_attr(
407 feature = "lt2018_1",
408 doc = "Quiet mode; suppress messages about skipped revisions."
409 )]
410 #[cfg_attr(
411 not(feature = "lt2018_1"),
412 doc = "Quiet mode, which suppresses messages about skipped revisions."
413 )]
414 pub fn get_quiet_mode(&self) -> bool {
415 self.quiet_mode
416 }
417
418 #[cfg_attr(
423 feature = "lt2018_1",
424 doc = "Quiet mode; suppress messages about skipped revisions."
425 )]
426 #[cfg_attr(
427 not(feature = "lt2018_1"),
428 doc = "Quiet mode, which suppresses messages about skipped revisions."
429 )]
430 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
431 self.quiet_mode = v;
432 self
433 }
434
435 #[cfg_attr(
440 feature = "lt2018_1",
441 doc = "Quiet mode; suppress messages about skipped revisions."
442 )]
443 #[cfg_attr(
444 not(feature = "lt2018_1"),
445 doc = "Quiet mode, which suppresses messages about skipped revisions."
446 )]
447 pub fn quiet_mode(mut self, v: bool) -> Self {
448 self.quiet_mode = v;
449 self
450 }
451
452 pub fn get_archive_delta(&self) -> bool {
459 self.archive_delta
460 }
461
462 pub fn set_archive_delta(&mut self, v: bool) -> &mut Self {
469 self.archive_delta = v;
470 self
471 }
472
473 pub fn archive_delta(mut self, v: bool) -> Self {
480 self.archive_delta = v;
481 self
482 }
483}
484
485impl Archive<SafeArchive> {
486 pub fn get_skip_head_revisions(&self) -> bool {
492 self.archive_mode.skip_head_revisions
493 }
494
495 pub fn set_skip_head_revisions(&mut self, v: bool) -> &mut Self {
501 self.archive_mode.skip_head_revisions = v;
502 self
503 }
504
505 pub fn skip_head_revisions(mut self, v: bool) -> Self {
511 self.archive_mode.skip_head_revisions = v;
512 self
513 }
514
515 #[cfg_attr(
520 all(feature = "lt2021_1", not(feature = "lt2019_1")),
521 doc = "Can reduce the use of disk space because it includes in the archive any files",
522 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
523 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
524 doc = "met. Unless all copies are archived, the original file remains in the depot."
525 )]
526 #[cfg_attr(
527 not(feature = "lt2021_1"),
528 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
529 doc = "with `-z`."
530 )]
531 #[cfg(not(feature = "lt2019_1"))]
532 pub fn get_include_lazy_copies(&self) -> bool {
533 self.archive_mode.include_lazy_copies
534 }
535
536 #[cfg_attr(
541 all(feature = "lt2021_1", not(feature = "lt2019_1")),
542 doc = "Can reduce the use of disk space because it includes in the archive any files",
543 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
544 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
545 doc = "met. Unless all copies are archived, the original file remains in the depot."
546 )]
547 #[cfg_attr(
548 not(feature = "lt2021_1"),
549 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
550 doc = "with `-z`."
551 )]
552 #[cfg(not(feature = "lt2019_1"))]
553 pub fn set_include_lazy_copies(&mut self, v: bool) -> &mut Self {
554 self.archive_mode.include_lazy_copies = v;
555 self
556 }
557
558 #[cfg_attr(
563 all(feature = "lt2021_1", not(feature = "lt2019_1")),
564 doc = "Can reduce the use of disk space because it includes in the archive any files",
565 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
566 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
567 doc = "met. Unless all copies are archived, the original file remains in the depot."
568 )]
569 #[cfg_attr(
570 not(feature = "lt2021_1"),
571 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
572 doc = "with `-z`."
573 )]
574 #[cfg(not(feature = "lt2019_1"))]
575 pub fn include_lazy_copies(mut self, v: bool) -> Self {
576 self.archive_mode.include_lazy_copies = v;
577 self
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use crate::cmd::args_of;
585
586 #[test]
587 fn bin_accessors_update_executable_path() {
588 let mut command = Archive::new("p4", GlobalOpts::new());
589 assert_eq!(command.get_bin(), Path::new("p4"));
590
591 command.set_bin("/usr/local/bin/p4");
592 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
593
594 let command = command.bin("p4.exe");
595 assert_eq!(command.get_bin(), Path::new("p4.exe"));
596 }
597
598 #[test]
601 fn without_options() {
602 let archive = Archive::new("p4", GlobalOpts::new());
603
604 assert_eq!(args_of(&archive.setup_command("p4")), ["archive"]);
605 }
606
607 #[test]
608 fn all_safe_archive_options() {
609 let mut archive = Archive::new("p4", GlobalOpts::new());
610 archive
611 .set_skip_head_revisions(true)
612 .set_preview(true)
613 .set_quiet_mode(true)
614 .set_archive_delta(true)
615 .set_depot("archives");
616 #[cfg(not(feature = "lt2019_1"))]
617 archive.set_include_lazy_copies(true);
618
619 #[cfg_attr(feature = "lt2019_1", allow(unused_mut))]
620 let mut expected = vec!["archive", "-h"];
621 #[cfg(not(feature = "lt2019_1"))]
622 expected.push("-z");
623 expected.extend(["-n", "-q", "-t", "-D", "archives"]);
624
625 assert_eq!(args_of(&archive.setup_command("p4")), expected);
626 }
627
628 #[test]
629 fn physical_purge_mode() {
630 let mut archive = Archive::new("p4", GlobalOpts::new());
631 archive
632 .set_preview(true)
633 .set_quiet_mode(true)
634 .set_archive_delta(true)
635 .set_depot("archives");
636
637 let archive = archive.purge_physical_files();
638
639 assert_eq!(
640 args_of(&archive.setup_command("p4")),
641 ["archive", "-p", "-n", "-q", "-t", "-D", "archives"]
642 );
643 }
644
645 #[test]
648 fn archive_selection_flags_not_injected_after_purge() {
649 let mut archive = Archive::new("p4", GlobalOpts::new());
650 archive.set_skip_head_revisions(true);
651 #[cfg(not(feature = "lt2019_1"))]
652 archive.set_include_lazy_copies(true);
653
654 let archive = archive.purge_physical_files();
655
656 assert_eq!(args_of(&archive.setup_command("p4")), ["archive", "-p"]);
657 }
658
659 #[cfg(not(feature = "lt2019_1"))]
660 #[test]
661 fn lazy_copies_flag() {
662 let archive = Archive::new("p4", GlobalOpts::new())
663 .depot("archives")
664 .include_lazy_copies(true);
665
666 assert!(archive.get_include_lazy_copies());
667 assert_eq!(
668 args_of(&archive.setup_command("p4")),
669 ["archive", "-z", "-D", "archives"]
670 );
671 }
672
673 #[test]
674 fn builder_style_with_str_and_global_opts() {
675 let archive = Archive::new("p4", GlobalOpts::new().port("localhost:1666"))
676 .depot("archives")
677 .preview(true);
678
679 assert_eq!(archive.get_depot(), Some(&"archives".to_string()));
680 assert_eq!(
681 args_of(&archive.setup_command("p4")),
682 ["-p", "localhost:1666", "archive", "-n", "-D", "archives"]
683 );
684 }
685}