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, 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 #[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 get_global_opts(&self) -> &GlobalOpts {
248 &self.global_opts
249 }
250
251 #[cfg_attr(
256 feature = "lt2014_2",
257 doc = "See the [Global Options](GlobalOpts) section."
258 )]
259 #[cfg_attr(
260 all(feature = "lt2015_1", not(feature = "lt2014_2")),
261 doc = "See the [“Global Options”](GlobalOpts) section."
262 )]
263 #[cfg_attr(
264 all(feature = "lt2017_1", not(feature = "lt2015_1")),
265 doc = "See [“Global Options”](GlobalOpts)."
266 )]
267 #[cfg_attr(
268 all(feature = "lt2018_2", not(feature = "lt2017_1")),
269 doc = "See [Global Options](GlobalOpts)."
270 )]
271 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
272 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
273 self.global_opts = v;
274 self
275 }
276
277 #[cfg_attr(
282 feature = "lt2014_2",
283 doc = "See the [Global Options](GlobalOpts) section."
284 )]
285 #[cfg_attr(
286 all(feature = "lt2015_1", not(feature = "lt2014_2")),
287 doc = "See the [“Global Options”](GlobalOpts) section."
288 )]
289 #[cfg_attr(
290 all(feature = "lt2017_1", not(feature = "lt2015_1")),
291 doc = "See [“Global Options”](GlobalOpts)."
292 )]
293 #[cfg_attr(
294 all(feature = "lt2018_2", not(feature = "lt2017_1")),
295 doc = "See [Global Options](GlobalOpts)."
296 )]
297 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
298 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
299 self.global_opts = v;
300 self
301 }
302
303 pub fn get_depot(&self) -> Option<&String> {
309 self.depot.as_ref()
310 }
311
312 pub fn set_depot(&mut self, v: impl Into<String>) -> &mut Self {
318 self.depot = Some(v.into());
319 self
320 }
321
322 pub fn depot(mut self, v: impl Into<String>) -> Self {
328 self.depot = Some(v.into());
329 self
330 }
331
332 #[cfg_attr(
337 feature = "lt2018_1",
338 doc = "Do not archive revisions; report on which revisions would have been archived."
339 )]
340 #[cfg_attr(
341 not(feature = "lt2018_1"),
342 doc = "Do not archive revisions. Instead, report on which revisions would have been",
343 doc = "archived."
344 )]
345 pub fn get_preview(&self) -> bool {
346 self.preview
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 set_preview(&mut self, v: bool) -> &mut Self {
363 self.preview = v;
364 self
365 }
366
367 #[cfg_attr(
372 feature = "lt2018_1",
373 doc = "Do not archive revisions; report on which revisions would have been archived."
374 )]
375 #[cfg_attr(
376 not(feature = "lt2018_1"),
377 doc = "Do not archive revisions. Instead, report on which revisions would have been",
378 doc = "archived."
379 )]
380 pub fn preview(mut self, v: bool) -> Self {
381 self.preview = v;
382 self
383 }
384
385 #[cfg_attr(
390 feature = "lt2018_1",
391 doc = "Quiet mode; suppress messages about skipped revisions."
392 )]
393 #[cfg_attr(
394 not(feature = "lt2018_1"),
395 doc = "Quiet mode, which suppresses messages about skipped revisions."
396 )]
397 pub fn get_quiet_mode(&self) -> bool {
398 self.quiet_mode
399 }
400
401 #[cfg_attr(
406 feature = "lt2018_1",
407 doc = "Quiet mode; suppress messages about skipped revisions."
408 )]
409 #[cfg_attr(
410 not(feature = "lt2018_1"),
411 doc = "Quiet mode, which suppresses messages about skipped revisions."
412 )]
413 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
414 self.quiet_mode = v;
415 self
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 quiet_mode(mut self, v: bool) -> Self {
431 self.quiet_mode = v;
432 self
433 }
434
435 pub fn get_archive_delta(&self) -> bool {
442 self.archive_delta
443 }
444
445 pub fn set_archive_delta(&mut self, v: bool) -> &mut Self {
452 self.archive_delta = v;
453 self
454 }
455
456 pub fn archive_delta(mut self, v: bool) -> Self {
463 self.archive_delta = v;
464 self
465 }
466}
467
468impl Archive<SafeArchive> {
469 pub fn get_skip_head_revisions(&self) -> bool {
475 self.archive_mode.skip_head_revisions
476 }
477
478 pub fn set_skip_head_revisions(&mut self, v: bool) -> &mut Self {
484 self.archive_mode.skip_head_revisions = v;
485 self
486 }
487
488 pub fn skip_head_revisions(mut self, v: bool) -> Self {
494 self.archive_mode.skip_head_revisions = v;
495 self
496 }
497
498 #[cfg_attr(
503 all(feature = "lt2021_1", not(feature = "lt2019_1")),
504 doc = "Can reduce the use of disk space because it includes in the archive any files",
505 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
506 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
507 doc = "met. Unless all copies are archived, the original file remains in the depot."
508 )]
509 #[cfg_attr(
510 not(feature = "lt2021_1"),
511 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
512 doc = "with `-z`."
513 )]
514 #[cfg(not(feature = "lt2019_1"))]
515 pub fn get_include_lazy_copies(&self) -> bool {
516 self.archive_mode.include_lazy_copies
517 }
518
519 #[cfg_attr(
524 all(feature = "lt2021_1", not(feature = "lt2019_1")),
525 doc = "Can reduce the use of disk space because it includes in the archive any files",
526 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
527 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
528 doc = "met. Unless all copies are archived, the original file remains in the depot."
529 )]
530 #[cfg_attr(
531 not(feature = "lt2021_1"),
532 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
533 doc = "with `-z`."
534 )]
535 #[cfg(not(feature = "lt2019_1"))]
536 pub fn set_include_lazy_copies(&mut self, v: bool) -> &mut Self {
537 self.archive_mode.include_lazy_copies = v;
538 self
539 }
540
541 #[cfg_attr(
546 all(feature = "lt2021_1", not(feature = "lt2019_1")),
547 doc = "Can reduce the use of disk space because it includes in the archive any files",
548 doc = "that have lazy copies or are lazy copies. (A lazy copy is a reference to the",
549 doc = "location of the full file.) With this flag, only Criteria 1 and 2 must be",
550 doc = "met. Unless all copies are archived, the original file remains in the depot."
551 )]
552 #[cfg_attr(
553 not(feature = "lt2021_1"),
554 doc = "Includes any files that have lazy copies or are lazy copies. See Criteria",
555 doc = "with `-z`."
556 )]
557 #[cfg(not(feature = "lt2019_1"))]
558 pub fn include_lazy_copies(mut self, v: bool) -> Self {
559 self.archive_mode.include_lazy_copies = v;
560 self
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::cmd::args_of;
568
569 #[test]
572 fn without_options() {
573 let archive = Archive::new("p4", GlobalOpts::new());
574
575 assert_eq!(args_of(&archive.setup_command("p4")), ["archive"]);
576 }
577
578 #[test]
579 fn all_safe_archive_options() {
580 let mut archive = Archive::new("p4", GlobalOpts::new());
581 archive
582 .set_skip_head_revisions(true)
583 .set_preview(true)
584 .set_quiet_mode(true)
585 .set_archive_delta(true)
586 .set_depot("archives");
587 #[cfg(not(feature = "lt2019_1"))]
588 archive.set_include_lazy_copies(true);
589
590 #[cfg_attr(feature = "lt2019_1", allow(unused_mut))]
591 let mut expected = vec!["archive", "-h"];
592 #[cfg(not(feature = "lt2019_1"))]
593 expected.push("-z");
594 expected.extend(["-n", "-q", "-t", "-D", "archives"]);
595
596 assert_eq!(args_of(&archive.setup_command("p4")), expected);
597 }
598
599 #[test]
600 fn physical_purge_mode() {
601 let mut archive = Archive::new("p4", GlobalOpts::new());
602 archive
603 .set_preview(true)
604 .set_quiet_mode(true)
605 .set_archive_delta(true)
606 .set_depot("archives");
607
608 let archive = archive.purge_physical_files();
609
610 assert_eq!(
611 args_of(&archive.setup_command("p4")),
612 ["archive", "-p", "-n", "-q", "-t", "-D", "archives"]
613 );
614 }
615
616 #[test]
619 fn archive_selection_flags_not_injected_after_purge() {
620 let mut archive = Archive::new("p4", GlobalOpts::new());
621 archive.set_skip_head_revisions(true);
622 #[cfg(not(feature = "lt2019_1"))]
623 archive.set_include_lazy_copies(true);
624
625 let archive = archive.purge_physical_files();
626
627 assert_eq!(args_of(&archive.setup_command("p4")), ["archive", "-p"]);
628 }
629
630 #[cfg(not(feature = "lt2019_1"))]
631 #[test]
632 fn lazy_copies_flag() {
633 let archive = Archive::new("p4", GlobalOpts::new())
634 .depot("archives")
635 .include_lazy_copies(true);
636
637 assert!(archive.get_include_lazy_copies());
638 assert_eq!(
639 args_of(&archive.setup_command("p4")),
640 ["archive", "-z", "-D", "archives"]
641 );
642 }
643
644 #[test]
645 fn builder_style_with_str_and_global_opts() {
646 let archive = Archive::new("p4", GlobalOpts::new().port("localhost:1666"))
647 .depot("archives")
648 .preview(true);
649
650 assert_eq!(archive.get_depot(), Some(&"archives".to_string()));
651 assert_eq!(
652 args_of(&archive.setup_command("p4")),
653 ["-p", "localhost:1666", "archive", "-n", "-D", "archives"]
654 );
655 }
656}