1use std::ffi::OsStr;
2use std::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> ParameterizedSpawn for Archive<M>
202where
203 Archive<M>: SubCommand,
204{
205 type Input<'a> = &'a [&'a OsStr];
206 type Output<'a> = Child;
207 type Error = std::io::Error;
208
209 fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
213 self.setup_command(&self.bin)
214 .args(files)
215 .stdout(Stdio::piped())
216 .stderr(Stdio::piped())
217 .spawn()
218 }
219}
220
221impl<M> Archive<M>
222where
223 Archive<M>: SubCommand,
224{
225 #[cfg_attr(
230 feature = "lt2014_2",
231 doc = "See the [Global Options](GlobalOpts) section."
232 )]
233 #[cfg_attr(
234 all(feature = "lt2015_1", not(feature = "lt2014_2")),
235 doc = "See the [“Global Options”](GlobalOpts) section."
236 )]
237 #[cfg_attr(
238 all(feature = "lt2017_1", not(feature = "lt2015_1")),
239 doc = "See [“Global Options”](GlobalOpts)."
240 )]
241 #[cfg_attr(
242 all(feature = "lt2018_2", not(feature = "lt2017_1")),
243 doc = "See [Global Options](GlobalOpts)."
244 )]
245 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
246 pub fn get_global_opts(&self) -> &GlobalOpts {
247 &self.global_opts
248 }
249
250 #[cfg_attr(
255 feature = "lt2014_2",
256 doc = "See the [Global Options](GlobalOpts) section."
257 )]
258 #[cfg_attr(
259 all(feature = "lt2015_1", not(feature = "lt2014_2")),
260 doc = "See the [“Global Options”](GlobalOpts) section."
261 )]
262 #[cfg_attr(
263 all(feature = "lt2017_1", not(feature = "lt2015_1")),
264 doc = "See [“Global Options”](GlobalOpts)."
265 )]
266 #[cfg_attr(
267 all(feature = "lt2018_2", not(feature = "lt2017_1")),
268 doc = "See [Global Options](GlobalOpts)."
269 )]
270 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
271 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
272 self.global_opts = v;
273 self
274 }
275
276 #[cfg_attr(
281 feature = "lt2014_2",
282 doc = "See the [Global Options](GlobalOpts) section."
283 )]
284 #[cfg_attr(
285 all(feature = "lt2015_1", not(feature = "lt2014_2")),
286 doc = "See the [“Global Options”](GlobalOpts) section."
287 )]
288 #[cfg_attr(
289 all(feature = "lt2017_1", not(feature = "lt2015_1")),
290 doc = "See [“Global Options”](GlobalOpts)."
291 )]
292 #[cfg_attr(
293 all(feature = "lt2018_2", not(feature = "lt2017_1")),
294 doc = "See [Global Options](GlobalOpts)."
295 )]
296 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
297 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
298 self.global_opts = v;
299 self
300 }
301
302 pub fn get_depot(&self) -> Option<&String> {
308 self.depot.as_ref()
309 }
310
311 pub fn set_depot(&mut self, v: impl Into<String>) -> &mut Self {
317 self.depot = Some(v.into());
318 self
319 }
320
321 pub fn depot(mut self, v: impl Into<String>) -> Self {
327 self.depot = Some(v.into());
328 self
329 }
330
331 #[cfg_attr(
336 feature = "lt2018_1",
337 doc = "Do not archive revisions; report on which revisions would have been archived."
338 )]
339 #[cfg_attr(
340 not(feature = "lt2018_1"),
341 doc = "Do not archive revisions. Instead, report on which revisions would have been",
342 doc = "archived."
343 )]
344 pub fn get_preview(&self) -> bool {
345 self.preview
346 }
347
348 #[cfg_attr(
353 feature = "lt2018_1",
354 doc = "Do not archive revisions; report on which revisions would have been archived."
355 )]
356 #[cfg_attr(
357 not(feature = "lt2018_1"),
358 doc = "Do not archive revisions. Instead, report on which revisions would have been",
359 doc = "archived."
360 )]
361 pub fn set_preview(&mut self, v: bool) -> &mut Self {
362 self.preview = v;
363 self
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 preview(mut self, v: bool) -> Self {
380 self.preview = v;
381 self
382 }
383
384 #[cfg_attr(
389 feature = "lt2018_1",
390 doc = "Quiet mode; suppress messages about skipped revisions."
391 )]
392 #[cfg_attr(
393 not(feature = "lt2018_1"),
394 doc = "Quiet mode, which suppresses messages about skipped revisions."
395 )]
396 pub fn get_quiet_mode(&self) -> bool {
397 self.quiet_mode
398 }
399
400 #[cfg_attr(
405 feature = "lt2018_1",
406 doc = "Quiet mode; suppress messages about skipped revisions."
407 )]
408 #[cfg_attr(
409 not(feature = "lt2018_1"),
410 doc = "Quiet mode, which suppresses messages about skipped revisions."
411 )]
412 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
413 self.quiet_mode = v;
414 self
415 }
416
417 #[cfg_attr(
422 feature = "lt2018_1",
423 doc = "Quiet mode; suppress messages about skipped revisions."
424 )]
425 #[cfg_attr(
426 not(feature = "lt2018_1"),
427 doc = "Quiet mode, which suppresses messages about skipped revisions."
428 )]
429 pub fn quiet_mode(mut self, v: bool) -> Self {
430 self.quiet_mode = v;
431 self
432 }
433
434 pub fn get_archive_delta(&self) -> bool {
441 self.archive_delta
442 }
443
444 pub fn set_archive_delta(&mut self, v: bool) -> &mut Self {
451 self.archive_delta = v;
452 self
453 }
454
455 pub fn archive_delta(mut self, v: bool) -> Self {
462 self.archive_delta = v;
463 self
464 }
465}
466
467impl Archive<SafeArchive> {
468 pub fn get_skip_head_revisions(&self) -> bool {
474 self.archive_mode.skip_head_revisions
475 }
476
477 pub fn set_skip_head_revisions(&mut self, v: bool) -> &mut Self {
483 self.archive_mode.skip_head_revisions = v;
484 self
485 }
486
487 pub fn skip_head_revisions(mut self, v: bool) -> Self {
493 self.archive_mode.skip_head_revisions = v;
494 self
495 }
496
497 #[cfg_attr(
502 all(feature = "lt2021_1", not(feature = "lt2019_1")),
503 doc = "Can reduce the use of disk space because it includes in the archive any files",
504 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
505 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
506 doc = "met. Unless all copies are archived, the original file remains in the depot."
507 )]
508 #[cfg_attr(
509 not(feature = "lt2021_1"),
510 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
511 doc = "with `-z`."
512 )]
513 #[cfg(not(feature = "lt2019_1"))]
514 pub fn get_include_lazy_copies(&self) -> bool {
515 self.archive_mode.include_lazy_copies
516 }
517
518 #[cfg_attr(
523 all(feature = "lt2021_1", not(feature = "lt2019_1")),
524 doc = "Can reduce the use of disk space because it includes in the archive any files",
525 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
526 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
527 doc = "met. Unless all copies are archived, the original file remains in the depot."
528 )]
529 #[cfg_attr(
530 not(feature = "lt2021_1"),
531 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
532 doc = "with `-z`."
533 )]
534 #[cfg(not(feature = "lt2019_1"))]
535 pub fn set_include_lazy_copies(&mut self, v: bool) -> &mut Self {
536 self.archive_mode.include_lazy_copies = v;
537 self
538 }
539
540 #[cfg_attr(
545 all(feature = "lt2021_1", not(feature = "lt2019_1")),
546 doc = "Can reduce the use of disk space because it includes in the archive any files",
547 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
548 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
549 doc = "met. Unless all copies are archived, the original file remains in the depot."
550 )]
551 #[cfg_attr(
552 not(feature = "lt2021_1"),
553 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
554 doc = "with `-z`."
555 )]
556 #[cfg(not(feature = "lt2019_1"))]
557 pub fn include_lazy_copies(mut self, v: bool) -> Self {
558 self.archive_mode.include_lazy_copies = v;
559 self
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use crate::cmd::args_of;
567
568 #[test]
571 fn without_options() {
572 let archive = Archive::new("p4", GlobalOpts::new());
573
574 assert_eq!(args_of(&archive.setup_command("p4")), ["archive"]);
575 }
576
577 #[test]
578 fn all_safe_archive_options() {
579 let mut archive = Archive::new("p4", GlobalOpts::new());
580 archive
581 .set_skip_head_revisions(true)
582 .set_preview(true)
583 .set_quiet_mode(true)
584 .set_archive_delta(true)
585 .set_depot("archives");
586 #[cfg(not(feature = "lt2019_1"))]
587 archive.set_include_lazy_copies(true);
588
589 #[cfg_attr(feature = "lt2019_1", allow(unused_mut))]
590 let mut expected = vec!["archive", "-h"];
591 #[cfg(not(feature = "lt2019_1"))]
592 expected.push("-z");
593 expected.extend(["-n", "-q", "-t", "-D", "archives"]);
594
595 assert_eq!(args_of(&archive.setup_command("p4")), expected);
596 }
597
598 #[test]
599 fn physical_purge_mode() {
600 let mut archive = Archive::new("p4", GlobalOpts::new());
601 archive
602 .set_preview(true)
603 .set_quiet_mode(true)
604 .set_archive_delta(true)
605 .set_depot("archives");
606
607 let archive = archive.purge_physical_files();
608
609 assert_eq!(
610 args_of(&archive.setup_command("p4")),
611 ["archive", "-p", "-n", "-q", "-t", "-D", "archives"]
612 );
613 }
614
615 #[test]
618 fn archive_selection_flags_not_injected_after_purge() {
619 let mut archive = Archive::new("p4", GlobalOpts::new());
620 archive.set_skip_head_revisions(true);
621 #[cfg(not(feature = "lt2019_1"))]
622 archive.set_include_lazy_copies(true);
623
624 let archive = archive.purge_physical_files();
625
626 assert_eq!(args_of(&archive.setup_command("p4")), ["archive", "-p"]);
627 }
628
629 #[cfg(not(feature = "lt2019_1"))]
630 #[test]
631 fn lazy_copies_flag() {
632 let archive = Archive::new("p4", GlobalOpts::new())
633 .depot("archives")
634 .include_lazy_copies(true);
635
636 assert!(archive.get_include_lazy_copies());
637 assert_eq!(
638 args_of(&archive.setup_command("p4")),
639 ["archive", "-z", "-D", "archives"]
640 );
641 }
642
643 #[test]
644 fn builder_style_with_str_and_global_opts() {
645 let archive = Archive::new("p4", GlobalOpts::new().port("localhost:1666"))
646 .depot("archives")
647 .preview(true);
648
649 assert_eq!(archive.get_depot(), Some(&"archives".to_string()));
650 assert_eq!(
651 args_of(&archive.setup_command("p4")),
652 ["-p", "localhost:1666", "archive", "-n", "-D", "archives"]
653 );
654 }
655}