Skip to main content

perforce_cli/cmd/
cachepurge.rs

1use std::path::{Path, PathBuf};
2use std::process::{Child, Command, Stdio};
3
4use super::{ExclusiveOption, SubCommand, Unselected};
5
6use crate::global::GlobalOpts;
7use crate::spawn::ParameterizedSpawn;
8
9// ---------------------------------------------------------------------------
10// Purge-goal dimension: `-a` vs `-f n` vs `-m n` vs `-s n`
11// ---------------------------------------------------------------------------
12
13/// The purge goal `-a`: delete all file content.
14///
15/// This goal reclaims the maximum amount of disk space, but any file
16/// content must be retrieved from the target server. Entered with
17/// [`CachePurge::purge_all`]; mutually exclusive with [`FreeSpaceMode`],
18/// [`RevisionCountMode`], and [`SizeMode`].
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct PurgeAllMode;
21
22/// The purge goal `-f n`: delete sufficient file content to leave `n`
23/// bytes of free space for the file system.
24///
25/// Entered with [`CachePurge::free_space`]; mutually exclusive with the
26/// other purge goals.
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
28pub struct FreeSpaceMode {
29    free_space: u64,
30}
31
32/// The purge goal `-m n`: delete `n` file revisions. The amount of space
33/// this frees up depends on the size of the files.
34///
35/// Entered with [`CachePurge::revision_count`]; mutually exclusive with the
36/// other purge goals.
37#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct RevisionCountMode {
39    revision_count: u64,
40}
41
42/// The purge goal `-s n`: delete `n` bytes of file data. This can be
43/// helpful in those cases when you can predict the growth rate of file
44/// system resources.
45///
46/// Entered with [`CachePurge::size`]; mutually exclusive with the other
47/// purge goals.
48#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct SizeMode {
50    size: u64,
51}
52
53// ---------------------------------------------------------------------------
54// ExclusiveOption implementations
55// ---------------------------------------------------------------------------
56
57impl ExclusiveOption for PurgeAllMode {
58    fn inject_args(&self, command: &mut Command) {
59        command.arg("-a");
60    }
61}
62
63impl ExclusiveOption for FreeSpaceMode {
64    fn inject_args(&self, command: &mut Command) {
65        command.arg("-f").arg(self.free_space.to_string());
66    }
67}
68
69impl ExclusiveOption for RevisionCountMode {
70    fn inject_args(&self, command: &mut Command) {
71        command.arg("-m").arg(self.revision_count.to_string());
72    }
73}
74
75impl ExclusiveOption for SizeMode {
76    fn inject_args(&self, command: &mut Command) {
77        command.arg("-s").arg(self.size.to_string());
78    }
79}
80
81/// Marker for a selected purge goal: one of [`PurgeAllMode`],
82/// [`FreeSpaceMode`], [`RevisionCountMode`], or [`SizeMode`].
83///
84/// Exactly one of `-a` / `-f` / `-m` / `-s` is required, so the command can
85/// only be assembled and spawned once [`CachePurge`] has transitioned into
86/// one of these states.
87pub trait PurgeGoal: ExclusiveOption {}
88
89impl PurgeGoal for PurgeAllMode {}
90impl PurgeGoal for FreeSpaceMode {}
91impl PurgeGoal for RevisionCountMode {}
92impl PurgeGoal for SizeMode {}
93
94// ---------------------------------------------------------------------------
95// Command struct
96// ---------------------------------------------------------------------------
97
98/// Type-safe builder for `p4 cachepurge`.
99///
100/// Reclaim disk space on a replicated server by deleting versioned file
101/// content from the replica's cache; file content is deleted only from the
102/// replica, and purged content is retrieved from the target server on
103/// demand. This command requires `super` access.
104///
105/// # Syntax
106///
107/// ```text
108/// p4 [g-opts] cachepurge -a [-i N -n -R -S N -O] [-D file[revRange]]
109/// p4 [g-opts] cachepurge -f n [-i N -n -R -S N -O] [-D file[revRange]]
110/// p4 [g-opts] cachepurge -m n [-i N -n -R -S N -O] [-D file[revRange]]
111/// p4 [g-opts] cachepurge -s n [-i N -n -R -S N -O] [-D file[revRange]]
112/// ```
113///
114/// The `M` type parameter tracks the purge goal at compile time: it starts
115/// as [`Unselected`], in which the command has no effect and cannot be
116/// spawned. One of [`Self::purge_all`] (`-a`), [`Self::free_space`]
117/// (`-f n`), [`Self::revision_count`] (`-m n`), or [`Self::size`] (`-s n`)
118/// must be used to select a goal before the command can be spawned. The
119/// goal can be switched freely with the same methods; the common options
120/// are preserved and the previously selected goal is discarded.
121///
122/// The deletion-order options `-R` / `-O` are available in every goal
123/// state through [`CachePurge::purge_in_order`] and
124/// [`CachePurge::purge_oldest_first`].
125#[derive(Debug, Clone, Default)]
126pub struct CachePurge<M = Unselected> {
127    bin: PathBuf,
128
129    global_opts: GlobalOpts,
130
131    /// `-i N`
132    interval: Option<u64>,
133
134    /// `-n`
135    preview: bool,
136
137    /// `-D file[revRange]`
138    filter: Option<String>,
139
140    /// `-S N`
141    retain_recent_revision: Option<u64>,
142
143    /// -R
144    purge_in_order: bool,
145
146    /// -O
147    purge_oldest_first: bool,
148
149    mode: M,
150}
151
152impl CachePurge<Unselected> {
153    /// Creates a new `p4 cachepurge` command with no purge goal selected.
154    ///
155    /// `bin` is the path to the Perforce command-line executable. The
156    /// command has no effect in this state; use [`Self::purge_all`],
157    /// [`Self::free_space`], [`Self::revision_count`], or [`Self::size`] to
158    /// select the required `-a` / `-f` / `-m` / `-s` goal.
159    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
160        Self {
161            bin: bin.into(),
162            global_opts,
163            ..Default::default()
164        }
165    }
166}
167
168impl<M> CachePurge<M> {
169    /// Get the Perforce command path.
170    pub fn get_bin(&self) -> &Path {
171        &self.bin
172    }
173
174    /// Set the Perforce command path.
175    pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
176        self.bin = bin.into();
177        self
178    }
179
180    /// Set the Perforce command path.
181    pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
182        self.set_bin(bin);
183        self
184    }
185
186    /// Selects `-a`: delete all file content.
187    ///
188    /// This transitions into the [`PurgeAllMode`] state, discarding any
189    /// previously selected purge goal; the common options are preserved.
190    pub fn purge_all(self) -> CachePurge<PurgeAllMode> {
191        CachePurge {
192            bin: self.bin,
193            global_opts: self.global_opts,
194            interval: self.interval,
195            preview: self.preview,
196            filter: self.filter,
197            retain_recent_revision: self.retain_recent_revision,
198            purge_in_order: self.purge_in_order,
199            purge_oldest_first: self.purge_oldest_first,
200            mode: PurgeAllMode,
201        }
202    }
203
204    /// Selects `-f n`: delete sufficient file content to leave `n` bytes of
205    /// free space for the file system.
206    ///
207    /// This transitions into the [`FreeSpaceMode`] state, discarding any
208    /// previously selected purge goal; the common options are preserved.
209    pub fn free_space(self, n: u64) -> CachePurge<FreeSpaceMode> {
210        CachePurge {
211            bin: self.bin,
212            global_opts: self.global_opts,
213            interval: self.interval,
214            preview: self.preview,
215            filter: self.filter,
216            retain_recent_revision: self.retain_recent_revision,
217            purge_in_order: self.purge_in_order,
218            purge_oldest_first: self.purge_oldest_first,
219            mode: FreeSpaceMode { free_space: n },
220        }
221    }
222
223    /// Selects `-m n`: delete `n` file revisions.
224    ///
225    /// This transitions into the [`RevisionCountMode`] state, discarding any
226    /// previously selected purge goal; the common options are preserved.
227    pub fn revision_count(self, n: u64) -> CachePurge<RevisionCountMode> {
228        CachePurge {
229            bin: self.bin,
230            global_opts: self.global_opts,
231            interval: self.interval,
232            preview: self.preview,
233            filter: self.filter,
234            retain_recent_revision: self.retain_recent_revision,
235            purge_in_order: self.purge_in_order,
236            purge_oldest_first: self.purge_oldest_first,
237            mode: RevisionCountMode { revision_count: n },
238        }
239    }
240
241    /// Selects `-s n`: delete `n` bytes of file data.
242    ///
243    /// This transitions into the [`SizeMode`] state, discarding any
244    /// previously selected purge goal; the common options are preserved.
245    pub fn size(self, n: u64) -> CachePurge<SizeMode> {
246        CachePurge {
247            bin: self.bin,
248            global_opts: self.global_opts,
249            interval: self.interval,
250            preview: self.preview,
251            filter: self.filter,
252            retain_recent_revision: self.retain_recent_revision,
253            purge_in_order: self.purge_in_order,
254            purge_oldest_first: self.purge_oldest_first,
255            mode: SizeMode { size: n },
256        }
257    }
258
259    /// # Description
260    ///
261    /// g-opts
262    ///
263    #[cfg_attr(
264        all(feature = "lt2018_2", not(feature = "lt2017_1")),
265        doc = "See [Global Options](GlobalOpts)."
266    )]
267    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
268    pub fn get_global_opts(&self) -> &GlobalOpts {
269        &self.global_opts
270    }
271
272    /// # Description
273    ///
274    /// g-opts
275    ///
276    #[cfg_attr(
277        all(feature = "lt2018_2", not(feature = "lt2017_1")),
278        doc = "See [Global Options](GlobalOpts)."
279    )]
280    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
281    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
282        self.global_opts = v;
283        self
284    }
285
286    /// # Description
287    ///
288    /// g-opts
289    ///
290    #[cfg_attr(
291        all(feature = "lt2018_2", not(feature = "lt2017_1")),
292        doc = "See [Global Options](GlobalOpts)."
293    )]
294    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
295    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
296        self.global_opts = v;
297        self
298    }
299
300    /// # Description
301    ///
302    /// -i N
303    ///
304    /// Repeat the command every `N` seconds. If you omit this option, the
305    /// command runs only once.
306    pub fn get_interval(&self) -> Option<u64> {
307        self.interval
308    }
309
310    /// # Description
311    ///
312    /// -i N
313    ///
314    /// Repeat the command every `N` seconds. If you omit this option, the
315    /// command runs only once.
316    pub fn set_interval(&mut self, v: u64) -> &mut Self {
317        self.interval = Some(v);
318        self
319    }
320
321    /// # Description
322    ///
323    /// -i N
324    ///
325    /// Repeat the command every `N` seconds. If you omit this option, the
326    /// command runs only once.
327    pub fn interval(mut self, v: u64) -> Self {
328        self.interval = Some(v);
329        self
330    }
331
332    /// # Description
333    ///
334    /// -n
335    ///
336    /// Display a preview of the `cachepurge` operation without deleting any
337    /// files.
338    pub fn get_preview(&self) -> bool {
339        self.preview
340    }
341
342    /// # Description
343    ///
344    /// -n
345    ///
346    /// Display a preview of the `cachepurge` operation without deleting any
347    /// files.
348    pub fn set_preview(&mut self, v: bool) -> &mut Self {
349        self.preview = v;
350        self
351    }
352
353    /// # Description
354    ///
355    /// -n
356    ///
357    /// Display a preview of the `cachepurge` operation without deleting any
358    /// files.
359    pub fn preview(mut self, v: bool) -> Self {
360        self.preview = v;
361        self
362    }
363
364    /// # Description
365    ///
366    /// `-D file[revRange]`
367    ///
368    /// Limit the action of the command to the specified set of files (and
369    /// revision range, if specified).
370    pub fn get_filter(&self) -> Option<&str> {
371        self.filter.as_deref()
372    }
373
374    /// # Description
375    ///
376    /// `-D file[revRange]`
377    ///
378    /// Limit the action of the command to the specified set of files (and
379    /// revision range, if specified).
380    pub fn set_filter(&mut self, v: impl Into<String>) -> &mut Self {
381        self.filter = Some(v.into());
382        self
383    }
384
385    /// # Description
386    ///
387    /// `-D file[revRange]`
388    ///
389    /// Limit the action of the command to the specified set of files (and
390    /// revision range, if specified).
391    pub fn filter(mut self, v: impl Into<String>) -> Self {
392        self.filter = Some(v.into());
393        self
394    }
395
396    /// # Description
397    ///
398    /// -S N
399    ///
400    /// Do not delete the `N` most recent revisions of each file. For
401    /// example, specifying `-S 1` means that the head revision of each file
402    /// is retained in the replica's cache if it is already there.
403    pub fn get_retain_recent_revision(&self) -> Option<u64> {
404        self.retain_recent_revision
405    }
406
407    /// # Description
408    ///
409    /// -S N
410    ///
411    /// Do not delete the `N` most recent revisions of each file. For
412    /// example, specifying `-S 1` means that the head revision of each file
413    /// is retained in the replica's cache if it is already there.
414    pub fn set_retain_recent_revision(&mut self, v: u64) -> &mut Self {
415        self.retain_recent_revision = Some(v);
416        self
417    }
418
419    /// # Description
420    ///
421    /// -S N
422    ///
423    /// Do not delete the `N` most recent revisions of each file. For
424    /// example, specifying `-S 1` means that the head revision of each file
425    /// is retained in the replica's cache if it is already there.
426    pub fn retain_recent_revision(mut self, v: u64) -> Self {
427        self.retain_recent_revision = Some(v);
428        self
429    }
430    /// # Description
431    ///
432    /// -R
433    ///
434    /// Delete files in the order specified by the `-O` option. If the
435    /// `-O` option is not specified, file names are used to determine
436    /// order of deletion.
437    pub fn get_purge_in_order(&self) -> bool {
438        self.purge_in_order
439    }
440
441    /// # Description
442    ///
443    /// -R
444    ///
445    /// Delete files in the order specified by the `-O` option. If the
446    /// `-O` option is not specified, file names are used to determine
447    /// order of deletion.
448    pub fn set_purge_in_order(&mut self, v: bool) -> &mut Self {
449        self.purge_in_order = v;
450        self
451    }
452
453    /// # Description
454    ///
455    /// -R
456    ///
457    /// Delete files in the order specified by the `-O` option. If the
458    /// `-O` option is not specified, file names are used to determine
459    /// order of deletion.
460    pub fn purge_in_order(mut self, v: bool) -> Self {
461        self.purge_in_order = v;
462        self
463    }
464
465    /// # Description
466    ///
467    /// -O
468    ///
469    /// Delete the files from the oldest to the newest; that is, delete
470    /// older files before deleting newer files.
471    pub fn get_purge_oldest_first(&self) -> bool {
472        self.purge_oldest_first
473    }
474
475    /// # Description
476    ///
477    /// -O
478    ///
479    /// Delete the files from the oldest to the newest; that is, delete
480    /// older files before deleting newer files.
481    pub fn set_purge_oldest_first(&mut self, v: bool) -> &mut Self {
482        self.purge_oldest_first = v;
483        self
484    }
485
486    /// # Description
487    ///
488    /// -O
489    ///
490    /// Delete the files from the oldest to the newest; that is, delete
491    /// older files before deleting newer files.
492    pub fn purge_oldest_first(mut self, v: bool) -> Self {
493        self.purge_oldest_first = v;
494        self
495    }
496}
497
498impl CachePurge<FreeSpaceMode> {
499    /// The free-space goal `n` selected with `-f`.
500    pub fn get_free_space(&self) -> u64 {
501        self.mode.free_space
502    }
503}
504
505impl CachePurge<RevisionCountMode> {
506    /// The revision count `n` selected with `-m`.
507    pub fn get_revision_count(&self) -> u64 {
508        self.mode.revision_count
509    }
510}
511
512impl CachePurge<SizeMode> {
513    /// The byte count `n` selected with `-s`.
514    pub fn get_size(&self) -> u64 {
515        self.mode.size
516    }
517}
518
519// ---------------------------------------------------------------------------
520// SubCommand: only after a purge goal (`-a` / `-f` / `-m` / `-s`) is selected
521// ---------------------------------------------------------------------------
522
523impl<M: PurgeGoal> SubCommand for CachePurge<M> {
524    fn name(&self) -> &str {
525        "cachepurge"
526    }
527
528    fn inject_local_args(&self, command: &mut Command) {
529        self.mode.inject_args(command);
530
531        if let Some(interval) = self.interval {
532            command.arg("-i").arg(interval.to_string());
533        }
534
535        if self.preview {
536            command.arg("-n");
537        }
538
539        if self.purge_in_order {
540            command.arg("-R");
541        }
542
543        if let Some(retain) = self.retain_recent_revision {
544            command.arg("-S").arg(retain.to_string());
545        }
546
547        if self.purge_oldest_first {
548            command.arg("-O");
549        }
550
551        if let Some(filter) = &self.filter {
552            command.arg("-D").arg(filter);
553        }
554    }
555
556    fn global_opts(&self) -> Option<&GlobalOpts> {
557        Some(&self.global_opts)
558    }
559}
560
561// ---------------------------------------------------------------------------
562// Spawning: only after a purge goal (`-a` / `-f` / `-m` / `-s`) is selected
563// ---------------------------------------------------------------------------
564
565impl<M: PurgeGoal> ParameterizedSpawn<()> for CachePurge<M> {
566    type Output = Child;
567    type Error = std::io::Error;
568
569    /// Spawns `p4 cachepurge` as a child process with piped standard output
570    /// and error streams; use the returned [`Child`] handle to wait for it
571    /// or interact with it.
572    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
573        self.setup_command(&self.bin)
574            .stdout(Stdio::piped())
575            .stderr(Stdio::piped())
576            .spawn()
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use super::*;
583    use crate::cmd::args_of;
584
585    #[test]
586    fn bin_accessors_update_executable_path() {
587        let mut command = CachePurge::new("p4", GlobalOpts::new()).purge_all();
588        assert_eq!(command.get_bin(), Path::new("p4"));
589
590        command.set_bin("/usr/local/bin/p4");
591        assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
592
593        let command = command.bin("p4.exe");
594        assert_eq!(command.get_bin(), Path::new("p4.exe"));
595    }
596
597    /// Dry-run checks of the assembled `p4 cachepurge` command line; no
598    /// process is spawned.
599    #[test]
600    fn purge_all_goal_only() {
601        let purge = CachePurge::new("p4", GlobalOpts::new()).purge_all();
602
603        assert_eq!(args_of(&purge.setup_command("p4")), ["cachepurge", "-a"]);
604    }
605
606    #[test]
607    fn free_space_goal_only() {
608        let purge = CachePurge::new("p4", GlobalOpts::new()).free_space(1_073_741_824);
609
610        assert_eq!(
611            args_of(&purge.setup_command("p4")),
612            ["cachepurge", "-f", "1073741824"]
613        );
614        assert_eq!(purge.get_free_space(), 1_073_741_824);
615    }
616
617    #[test]
618    fn revision_count_goal_only() {
619        let purge = CachePurge::new("p4", GlobalOpts::new()).revision_count(100);
620
621        assert_eq!(
622            args_of(&purge.setup_command("p4")),
623            ["cachepurge", "-m", "100"]
624        );
625        assert_eq!(purge.get_revision_count(), 100);
626    }
627
628    #[test]
629    fn size_goal_only() {
630        let purge = CachePurge::new("p4", GlobalOpts::new()).size(2_048);
631
632        assert_eq!(
633            args_of(&purge.setup_command("p4")),
634            ["cachepurge", "-s", "2048"]
635        );
636        assert_eq!(purge.get_size(), 2_048);
637    }
638
639    #[test]
640    fn order_flags_are_injected() {
641        let purge = CachePurge::new("p4", GlobalOpts::new())
642            .purge_all()
643            .purge_in_order(true);
644
645        assert_eq!(
646            args_of(&purge.setup_command("p4")),
647            ["cachepurge", "-a", "-R"]
648        );
649        assert!(purge.get_purge_in_order());
650        assert!(!purge.get_purge_oldest_first());
651
652        let purge = purge.purge_oldest_first(true);
653
654        assert_eq!(
655            args_of(&purge.setup_command("p4")),
656            ["cachepurge", "-a", "-R", "-O"]
657        );
658        assert!(purge.get_purge_in_order());
659        assert!(purge.get_purge_oldest_first());
660    }
661
662    #[test]
663    fn order_flags_available_in_every_goal_state() {
664        let purge = CachePurge::new("p4", GlobalOpts::new())
665            .free_space(1_024)
666            .purge_in_order(true)
667            .purge_oldest_first(true);
668
669        assert_eq!(
670            args_of(&purge.setup_command("p4")),
671            ["cachepurge", "-f", "1024", "-R", "-O"]
672        );
673
674        let purge = purge.revision_count(10);
675        assert_eq!(
676            args_of(&purge.setup_command("p4")),
677            ["cachepurge", "-m", "10", "-R", "-O"]
678        );
679
680        let purge = purge.size(2_048);
681        assert_eq!(
682            args_of(&purge.setup_command("p4")),
683            ["cachepurge", "-s", "2048", "-R", "-O"]
684        );
685    }
686
687    #[test]
688    fn all_options_injected_in_syntax_order() {
689        let purge = CachePurge::new("p4", GlobalOpts::new())
690            .purge_all()
691            .purge_in_order(true)
692            .purge_oldest_first(true)
693            .interval(86_400)
694            .preview(true)
695            .retain_recent_revision(1)
696            .filter("//depot/...");
697
698        assert_eq!(
699            args_of(&purge.setup_command("p4")),
700            [
701                "cachepurge",
702                "-a",
703                "-i",
704                "86400",
705                "-n",
706                "-R",
707                "-S",
708                "1",
709                "-O",
710                "-D",
711                "//depot/..."
712            ]
713        );
714    }
715
716    #[test]
717    fn switching_goals_preserves_common_options() {
718        let purge = CachePurge::new("p4", GlobalOpts::new())
719            .interval(60)
720            .purge_all()
721            .purge_oldest_first(true)
722            .size(4_096);
723
724        assert_eq!(
725            args_of(&purge.setup_command("p4")),
726            ["cachepurge", "-s", "4096", "-i", "60", "-O"]
727        );
728        assert_eq!(purge.get_size(), 4_096);
729        assert!(purge.get_purge_oldest_first());
730
731        let purge = purge.free_space(1_024);
732
733        assert_eq!(
734            args_of(&purge.setup_command("p4")),
735            ["cachepurge", "-f", "1024", "-i", "60", "-O"]
736        );
737        assert_eq!(purge.get_free_space(), 1_024);
738    }
739
740    #[test]
741    fn global_opts_are_injected_once() {
742        let purge =
743            CachePurge::new("p4", GlobalOpts::new().port("localhost:1666")).revision_count(10);
744
745        assert_eq!(
746            args_of(&purge.setup_command("p4")),
747            ["-p", "localhost:1666", "cachepurge", "-m", "10"]
748        );
749    }
750
751    #[test]
752    fn setters_update_the_same_command() {
753        let mut purge = CachePurge::new("p4", GlobalOpts::new()).free_space(2_048);
754        purge
755            .set_preview(true)
756            .set_purge_in_order(true)
757            .set_purge_oldest_first(true)
758            .set_retain_recent_revision(3)
759            .set_filter("//depot/src/...")
760            .set_interval(300);
761
762        assert!(purge.get_preview());
763        assert!(purge.get_purge_in_order());
764        assert!(purge.get_purge_oldest_first());
765        assert_eq!(purge.get_retain_recent_revision(), Some(3));
766        assert_eq!(purge.get_filter(), Some("//depot/src/..."));
767        assert_eq!(purge.get_interval(), Some(300));
768        assert_eq!(
769            args_of(&purge.setup_command("p4")),
770            [
771                "cachepurge",
772                "-f",
773                "2048",
774                "-i",
775                "300",
776                "-n",
777                "-R",
778                "-S",
779                "3",
780                "-O",
781                "-D",
782                "//depot/src/..."
783            ]
784        );
785    }
786}