Skip to main content

perforce_cli/cmd/
admin.rs

1use std::ffi::OsStr;
2use std::path::PathBuf;
3use std::process::{Child, Command, Stdio};
4
5use super::{ExclusiveOption, SubCommand, Unselected};
6
7use crate::global::GlobalOpts;
8use crate::spawn::{ParameterizedSpawn, SpawnExt};
9
10/// Entry point for the `p4 admin` subcommands.
11///
12/// Allows Perforce superusers to perform administrative tasks even when
13/// working from a different machine than the one running the shared Perforce
14/// service. Use one of the builder methods to select the operation:
15/// [`checkpoint`](Self::checkpoint), [`journal`](Self::journal),
16/// [`stop`](Self::stop), [`restart`](Self::restart),
17/// [`updatespecdepot`](Self::updatespecdepot),
18#[cfg_attr(
19    not(feature = "lt2015_1"),
20    doc = " [`setldapusers`](Self::setldapusers),"
21)]
22#[cfg_attr(
23    not(feature = "lt2018_1"),
24    doc = " [`end_journal`](Self::end_journal),"
25)]
26#[cfg_attr(
27    not(feature = "lt2023_1"),
28    doc = " [`sysinfo`](Self::sysinfo), [`resource_monitor`](Self::resource_monitor),"
29)]
30#[cfg_attr(
31    not(feature = "lt2025_2"),
32    doc = " [`replica_filter_reconcile`](Self::replica_filter_reconcile),"
33)]
34/// or [`resetpassword`](Self::resetpassword).
35#[derive(Debug, Clone, Default)]
36pub struct AdminEntry {
37    bin: PathBuf,
38
39    global_opts: GlobalOpts,
40}
41
42impl AdminEntry {
43    /// Creates the entry point for `p4 admin` subcommands.
44    ///
45    /// `bin` is the path to the Perforce command-line executable.
46    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
47        Self {
48            bin: bin.into(),
49            global_opts,
50        }
51    }
52
53    /// Take a checkpoint.
54    ///
55    /// Equivalent to logging in to the server machine and running
56    /// `p4d -jc [prefix]`: a checkpoint is taken and the journal is copied to
57    /// a numbered file.
58    pub fn checkpoint(self) -> Admin<CheckPoint<Unselected>> {
59        Admin::new(self.bin, self.global_opts, CheckPoint::default())
60    }
61
62    /// Rotate the journal.
63    ///
64    /// Equivalent to `p4d -jj`. The files are created in the server root
65    /// specified when the Perforce service was started.
66    pub fn journal(self) -> Admin<Journal> {
67        Admin::new(self.bin, self.global_opts, Journal::default())
68    }
69
70    /// Stop the Perforce service.
71    ///
72    /// Locks the database to ensure that it is in a consistent state upon
73    /// restart, and then shuts down the Perforce background process.
74    pub fn stop(self) -> Admin<Stop> {
75        Admin::new(self.bin, self.global_opts, Stop)
76    }
77
78    /// Restart the Perforce service.
79    ///
80    /// Locks the database, restarts the service, and applies any
81    /// `p4 configure` settings that require a restart.
82    pub fn restart(self) -> Admin<Restart> {
83        Admin::new(self.bin, self.global_opts, Restart)
84    }
85
86    /// Archive stored forms into the spec depot.
87    ///
88    /// Causes the Perforce service to archive stored forms (specifically the
89    /// `client`, `depot`, `branch`, `label`, `typemap`, `group`, `user`, and
90    /// `job` forms) into the spec depot. Only those forms that have not yet
91    /// been archived are created. The spec depot must exist first.
92    pub fn updatespecdepot(self) -> Admin<UpdateSpecDepot<Unselected>> {
93        Admin::new(self.bin, self.global_opts, UpdateSpecDepot::default())
94    }
95
96    /// Force users to change their passwords.
97    ///
98    /// Forces specified users with existing passwords to change their
99    /// passwords before they can run another command.
100    pub fn resetpassword(self) -> Admin<ResetPassword<Unselected>> {
101        Admin::new(self.bin, self.global_opts, ResetPassword::default())
102    }
103
104    /// Set the LDAP users.
105    ///
106    /// Converts all existing non-super users to use LDAP authentication. The
107    /// command changes the `AuthMethod` field in the user specification for
108    /// each user from `perforce` to `ldap`. If super users want to use LDAP
109    /// authentication, they must set their `AuthMethod` manually.
110    #[cfg(not(feature = "lt2015_1"))]
111    pub fn setldapusers(self) -> Admin<SetLdapUsers> {
112        Admin::new(self.bin, self.global_opts, SetLdapUsers)
113    }
114
115    /// End journal replication at a failover consistency point.
116    ///
117    /// In a failover scenario, this command ends journal replication at the
118    /// most recent successfully replicated consistency point, returns the
119    /// journal number and the offset of that consistency point, and stops the
120    /// standby server's journalcopy thread.
121    #[cfg(not(feature = "lt2018_1"))]
122    pub fn end_journal(self) -> Admin<EndJournal> {
123        Admin::new(self.bin, self.global_opts, EndJournal)
124    }
125
126    /// Dump system information for Perforce Support.
127    ///
128    /// Dumps the output of reporting commands as run on the server host
129    /// operating system. This is intended for use under guidance of Perforce
130    /// Support to gather information about the environment of
131    #[cfg_attr(
132        all(not(feature = "lt2023_1"), feature = "lt2024_2"),
133        doc = "Helix Core Server."
134    )]
135    #[cfg_attr(not(feature = "lt2024_2"), doc = "P4 Server.")]
136    #[cfg(not(feature = "lt2023_1"))]
137    pub fn sysinfo(self) -> Admin<SysInfo> {
138        Admin::new(self.bin, self.global_opts, SysInfo)
139    }
140
141    /// Report server resource usage.
142    ///
143    /// Explained in the output of `p4 help admin-resource-monitor`. See also
144    /// System resources in the Performance tuning chapter of
145    #[cfg_attr(
146        all(not(feature = "lt2023_1"), feature = "lt2024_1"),
147        doc = "Helix Core Server",
148        doc = "Administrator Guide."
149    )]
150    #[cfg_attr(
151        all(not(feature = "lt2024_1"), feature = "lt2024_2"),
152        doc = "the Helix Core Server Administrator Guide."
153    )]
154    #[cfg_attr(
155        not(feature = "lt2024_2"),
156        doc = "P4 Server",
157        doc = "Administration Documentation."
158    )]
159    #[cfg(not(feature = "lt2023_1"))]
160    pub fn resource_monitor(self) -> Admin<ResourceMonitor> {
161        Admin::new(self.bin, self.global_opts, ResourceMonitor)
162    }
163
164    /// Reconcile a replica after its filter rules change.
165    ///
166    /// By default, if the filtering rules change in a replica or edge server
167    /// spec, replication adjusts automatically; a set of `rpl.filter.*`
168    /// configurables controls that behavior. This command performs the
169    /// reconciliation manually.
170    #[cfg(not(feature = "lt2025_2"))]
171    pub fn replica_filter_reconcile(self) -> Admin<ReplicaFilterReconcile<Unselected>> {
172        Admin::new(
173            self.bin,
174            self.global_opts,
175            ReplicaFilterReconcile::default(),
176        )
177    }
178}
179
180/// A `p4 admin` operation wrapping a selected [`SubCommand`].
181#[derive(Debug, Clone, Default)]
182pub struct Admin<T: SubCommand> {
183    bin: PathBuf,
184
185    global_opts: GlobalOpts,
186
187    sub_command: T,
188}
189
190impl<T: SubCommand> SubCommand for Admin<T> {
191    fn name(&self) -> &str {
192        "admin"
193    }
194
195    fn inject_local_args(&self, command: &mut Command) {
196        self.sub_command.inject_args(command);
197    }
198
199    fn global_opts(&self) -> Option<&GlobalOpts> {
200        Some(&self.global_opts)
201    }
202}
203
204// ---- Executors ----
205//
206// Each `p4 admin` subcommand type-state implements `ParameterizedSpawn`; for
207// the no-input states the blanket `SpawnExt`/`ParameterizedOutput`/`OutputExt`
208// impls in [crate::cmd] provide `spawn`, `output_with`, and `output`.
209
210impl<T: SubCommand> Admin<T> {
211    /// Spawns the assembled `p4 admin` command as a child process with piped
212    /// standard output and error streams; use the returned [`Child`] handle
213    /// to wait for it or interact with it.
214    fn spawn_piped(&mut self) -> Result<Child, std::io::Error> {
215        self.setup_command(&self.bin)
216            .stdout(Stdio::piped())
217            .stderr(Stdio::piped())
218            .spawn()
219    }
220}
221
222impl ParameterizedSpawn for Admin<Stop> {
223    type Input<'a> = ();
224    type Output<'a> = Child;
225    type Error = std::io::Error;
226
227    /// Spawns `p4 admin stop` as a child process with piped standard output
228    /// and error streams; use the returned [`Child`] handle to wait for it or
229    /// interact with it.
230    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
231        self.spawn_piped()
232    }
233}
234
235impl ParameterizedSpawn for Admin<Restart> {
236    type Input<'a> = ();
237    type Output<'a> = Child;
238    type Error = std::io::Error;
239
240    /// Spawns `p4 admin restart` as a child process with piped standard
241    /// output and error streams; use the returned [`Child`] handle to wait
242    /// for it or interact with it.
243    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
244        self.spawn_piped()
245    }
246}
247
248impl<S: ExclusiveOption> ParameterizedSpawn for Admin<UpdateSpecDepot<S>> {
249    type Input<'a> = ();
250    type Output<'a> = Child;
251    type Error = std::io::Error;
252
253    /// Spawns `p4 admin updatespecdepot` as a child process with piped
254    /// standard output and error streams; use the returned [`Child`] handle
255    /// to wait for it or interact with it.
256    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
257        self.spawn_piped()
258    }
259}
260
261impl<T: ExclusiveOption> ParameterizedSpawn for Admin<ResetPassword<T>> {
262    type Input<'a> = ();
263    type Output<'a> = Child;
264    type Error = std::io::Error;
265
266    /// Spawns `p4 admin resetpassword` as a child process with piped standard
267    /// output and error streams; use the returned [`Child`] handle to wait
268    /// for it or interact with it.
269    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
270        self.spawn_piped()
271    }
272}
273
274#[cfg(not(feature = "lt2015_1"))]
275impl ParameterizedSpawn for Admin<SetLdapUsers> {
276    type Input<'a> = ();
277    type Output<'a> = Child;
278    type Error = std::io::Error;
279
280    /// Spawns `p4 admin setldapusers` as a child process with piped standard
281    /// output and error streams; use the returned [`Child`] handle to wait
282    /// for it or interact with it.
283    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
284        self.spawn_piped()
285    }
286}
287
288#[cfg(not(feature = "lt2018_1"))]
289impl ParameterizedSpawn for Admin<EndJournal> {
290    type Input<'a> = ();
291    type Output<'a> = Child;
292    type Error = std::io::Error;
293
294    /// Spawns `p4 admin endjournal` as a child process with piped standard
295    /// output and error streams; use the returned [`Child`] handle to wait
296    /// for it or interact with it.
297    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
298        self.spawn_piped()
299    }
300}
301
302#[cfg(not(feature = "lt2023_1"))]
303impl ParameterizedSpawn for Admin<SysInfo> {
304    type Input<'a> = ();
305    type Output<'a> = Child;
306    type Error = std::io::Error;
307
308    /// Spawns `p4 admin sysinfo` as a child process with piped standard
309    /// output and error streams; use the returned [`Child`] handle to wait
310    /// for it or interact with it.
311    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
312        self.spawn_piped()
313    }
314}
315
316#[cfg(not(feature = "lt2023_1"))]
317impl ParameterizedSpawn for Admin<ResourceMonitor> {
318    type Input<'a> = ();
319    type Output<'a> = Child;
320    type Error = std::io::Error;
321
322    /// Spawns `p4 admin resource-monitor` as a child process with piped
323    /// standard output and error streams; use the returned [`Child`] handle
324    /// to wait for it or interact with it.
325    fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
326        self.spawn_piped()
327    }
328}
329
330impl<T: SubCommand> Admin<T> {
331    /// Creates a `p4 admin` command wrapping the given subcommand.
332    ///
333    /// `bin` is the path to the Perforce command-line executable.
334    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts, sub_command: T) -> Self {
335        Self {
336            bin: bin.into(),
337            global_opts,
338            sub_command,
339        }
340    }
341
342    /// # Description
343    ///
344    /// g-opts
345    ///
346    #[cfg_attr(
347        feature = "lt2014_2",
348        doc = "See the [Global Options](GlobalOpts) section."
349    )]
350    #[cfg_attr(
351        all(feature = "lt2015_1", not(feature = "lt2014_2")),
352        doc = "See the [“Global Options”](GlobalOpts) section."
353    )]
354    #[cfg_attr(
355        all(feature = "lt2017_1", not(feature = "lt2015_1")),
356        doc = "See [“Global Options”](GlobalOpts)."
357    )]
358    #[cfg_attr(
359        all(feature = "lt2018_2", not(feature = "lt2017_1")),
360        doc = "See [Global Options](GlobalOpts)."
361    )]
362    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
363    pub fn get_global_opts(&self) -> &GlobalOpts {
364        &self.global_opts
365    }
366
367    /// # Description
368    ///
369    /// g-opts
370    ///
371    #[cfg_attr(
372        feature = "lt2014_2",
373        doc = "See the [Global Options](GlobalOpts) section."
374    )]
375    #[cfg_attr(
376        all(feature = "lt2015_1", not(feature = "lt2014_2")),
377        doc = "See the [“Global Options”](GlobalOpts) section."
378    )]
379    #[cfg_attr(
380        all(feature = "lt2017_1", not(feature = "lt2015_1")),
381        doc = "See [“Global Options”](GlobalOpts)."
382    )]
383    #[cfg_attr(
384        all(feature = "lt2018_2", not(feature = "lt2017_1")),
385        doc = "See [Global Options](GlobalOpts)."
386    )]
387    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
388    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
389        self.global_opts = v;
390        self
391    }
392
393    /// # Description
394    ///
395    /// g-opts
396    ///
397    #[cfg_attr(
398        feature = "lt2014_2",
399        doc = "See the [Global Options](GlobalOpts) section."
400    )]
401    #[cfg_attr(
402        all(feature = "lt2015_1", not(feature = "lt2014_2")),
403        doc = "See the [“Global Options”](GlobalOpts) section."
404    )]
405    #[cfg_attr(
406        all(feature = "lt2017_1", not(feature = "lt2015_1")),
407        doc = "See [“Global Options”](GlobalOpts)."
408    )]
409    #[cfg_attr(
410        all(feature = "lt2018_2", not(feature = "lt2017_1")),
411        doc = "See [Global Options](GlobalOpts)."
412    )]
413    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
414    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
415        self.global_opts = v;
416        self
417    }
418}
419
420pub mod compression {
421    /// Compress both the checkpoint and the journal (`-z`).
422    #[derive(Debug, Clone, Copy, Default)]
423    pub struct Both;
424
425    /// Compress the checkpoint only, leaving the journal uncompressed
426    /// (`-Z`).
427    #[derive(Debug, Clone, Copy, Default)]
428    pub struct CheckPointOnly;
429}
430
431impl ExclusiveOption for compression::Both {
432    fn inject_args(&self, command: &mut Command) {
433        command.arg("-z");
434    }
435}
436
437impl ExclusiveOption for compression::CheckPointOnly {
438    fn inject_args(&self, command: &mut Command) {
439        command.arg("-Z");
440    }
441}
442
443#[cfg_attr(
444    feature = "lt2022_2",
445    doc = "`p4 admin checkpoint [-z | -Z] [prefix]`: take a checkpoint."
446)]
447#[cfg_attr(
448    all(feature = "lt2023_1", not(feature = "lt2022_2")),
449    doc = "`p4 admin checkpoint [[-z | -Z]] [prefix]`: take a checkpoint."
450)]
451#[cfg_attr(
452    not(feature = "lt2023_1"),
453    doc = "`p4 admin checkpoint [-z | -Z] [-p [-N threads] [-m]] [prefix]`: take a checkpoint."
454)]
455/// The `C` type parameter encodes the compression mode (none, `-z`, or
456/// `-Z`) at compile time; see [`ExclusiveOption`].
457#[derive(Debug, Clone, Default)]
458pub struct CheckPoint<C = Unselected> {
459    compression: C,
460
461    /// Added in p4 2023.1.
462    #[cfg(not(feature = "lt2023_1"))]
463    parallel: bool,
464
465    /// Added in p4 2023.1.
466    #[cfg(not(feature = "lt2023_1"))]
467    threads: Option<u32>,
468
469    /// Added in p4 2023.1.
470    #[cfg(not(feature = "lt2023_1"))]
471    multiple_files: bool,
472}
473
474impl<C: ExclusiveOption> SubCommand for CheckPoint<C> {
475    fn name(&self) -> &str {
476        "checkpoint"
477    }
478
479    fn inject_local_args(&self, command: &mut Command) {
480        self.compression.inject_args(command);
481
482        #[cfg(not(feature = "lt2023_1"))]
483        {
484            if self.parallel {
485                command.arg("-p");
486            }
487            if let Some(threads) = self.threads {
488                command.arg("-N").arg(threads.to_string());
489            }
490            if self.multiple_files {
491                command.arg("-m");
492            }
493        }
494    }
495}
496
497impl<C: ExclusiveOption> ParameterizedSpawn for Admin<CheckPoint<C>> {
498    type Input<'a> = &'a OsStr;
499    type Output<'a> = Child;
500    type Error = std::io::Error;
501
502    /// Spawns `p4 admin checkpoint` as a child process with piped standard
503    /// output and error streams; use the returned [`Child`] handle to wait
504    /// for it or interact with it.
505    ///
506    /// Pass `Some(prefix)` to name the checkpoint with the given prefix, or
507    /// `None` to use the default checkpoint name.
508    fn spawn_with<'a>(&mut self, prefix: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
509        self.setup_command(&self.bin)
510            .arg(prefix)
511            .stdout(Stdio::piped())
512            .stderr(Stdio::piped())
513            .spawn()
514    }
515}
516
517impl<C: ExclusiveOption> SpawnExt for Admin<CheckPoint<C>> {
518    fn spawn<'a>(&mut self) -> Result<Self::Output<'a>, Self::Error> {
519        self.setup_command(&self.bin)
520            .stdout(Stdio::piped())
521            .stderr(Stdio::piped())
522            .spawn()
523    }
524}
525
526impl<C: ExclusiveOption> Admin<CheckPoint<C>> {
527    /// # Description
528    ///
529    /// -p
530    ///
531    /// Requests a parallel checkpoint.
532    #[cfg(not(feature = "lt2023_1"))]
533    pub fn get_parallel(&self) -> bool {
534        self.sub_command.parallel
535    }
536
537    /// # Description
538    ///
539    /// -p
540    ///
541    /// Requests a parallel checkpoint.
542    #[cfg(not(feature = "lt2023_1"))]
543    pub fn set_parallel(&mut self, parallel: bool) -> &mut Self {
544        self.sub_command.parallel = parallel;
545        self
546    }
547
548    /// # Description
549    ///
550    /// -p
551    ///
552    /// Requests a parallel checkpoint.
553    #[cfg(not(feature = "lt2023_1"))]
554    pub fn parallel(mut self, parallel: bool) -> Self {
555        self.sub_command.parallel = parallel;
556        self
557    }
558
559    /// # Description
560    ///
561    /// -N threads
562    ///
563    /// Specifies the number of threads to use during the parallel request.
564    #[cfg(not(feature = "lt2023_1"))]
565    pub fn get_threads(&self) -> Option<&u32> {
566        self.sub_command.threads.as_ref()
567    }
568
569    /// # Description
570    ///
571    /// -N threads
572    ///
573    /// Specifies the number of threads to use during the parallel request.
574    #[cfg(not(feature = "lt2023_1"))]
575    pub fn set_threads(&mut self, threads: u32) -> &mut Self {
576        self.sub_command.threads = Some(threads);
577        self
578    }
579
580    /// # Description
581    ///
582    /// -N threads
583    ///
584    /// Specifies the number of threads to use during the parallel request.
585    #[cfg(not(feature = "lt2023_1"))]
586    pub fn threads(mut self, threads: u32) -> Self {
587        self.sub_command.threads = Some(threads);
588        self
589    }
590
591    /// # Description
592    ///
593    /// -m
594    ///
595    /// Uses multiple files if there are multiple parallel threads because
596    /// `db.checkpoint.threads` is greater than 1 or the `-N` option is greater
597    /// than 1. See Parallel checkpointing, dumping and recovery
598    #[cfg_attr(
599        all(not(feature = "lt2023_1"), feature = "lt2024_1"),
600        doc = "in Helix Core",
601        doc = "Server Administrator Guide. See also checkpoint examples."
602    )]
603    #[cfg_attr(
604        all(not(feature = "lt2024_1"), feature = "lt2024_2"),
605        doc = "in the Helix",
606        doc = "Core Server Administrator Guide. See also checkpoint examples."
607    )]
608    #[cfg_attr(
609        all(not(feature = "lt2024_2"), feature = "lt2025_1"),
610        doc = "in the P4",
611        doc = "Server Administration Documentation. See also checkpoint examples."
612    )]
613    #[cfg_attr(
614        not(feature = "lt2025_1"),
615        doc = "in the P4",
616        doc = "Server Administration Documentation. See also Checkpoint examples."
617    )]
618    #[cfg(not(feature = "lt2023_1"))]
619    pub fn get_multiple_files(&self) -> bool {
620        self.sub_command.multiple_files
621    }
622
623    /// # Description
624    ///
625    /// -m
626    ///
627    /// Uses multiple files if there are multiple parallel threads because
628    /// `db.checkpoint.threads` is greater than 1 or the `-N` option is greater
629    /// than 1. See Parallel checkpointing, dumping and recovery
630    #[cfg_attr(
631        all(not(feature = "lt2023_1"), feature = "lt2024_1"),
632        doc = "in Helix Core",
633        doc = "Server Administrator Guide. See also checkpoint examples."
634    )]
635    #[cfg_attr(
636        all(not(feature = "lt2024_1"), feature = "lt2024_2"),
637        doc = "in the Helix",
638        doc = "Core Server Administrator Guide. See also checkpoint examples."
639    )]
640    #[cfg_attr(
641        all(not(feature = "lt2024_2"), feature = "lt2025_1"),
642        doc = "in the P4",
643        doc = "Server Administration Documentation. See also checkpoint examples."
644    )]
645    #[cfg_attr(
646        not(feature = "lt2025_1"),
647        doc = "in the P4",
648        doc = "Server Administration Documentation. See also Checkpoint examples."
649    )]
650    #[cfg(not(feature = "lt2023_1"))]
651    pub fn set_multiple_files(&mut self, multiple_files: bool) -> &mut Self {
652        self.sub_command.multiple_files = multiple_files;
653        self
654    }
655
656    /// # Description
657    ///
658    /// -m
659    ///
660    /// Uses multiple files if there are multiple parallel threads because
661    /// `db.checkpoint.threads` is greater than 1 or the `-N` option is greater
662    /// than 1. See Parallel checkpointing, dumping and recovery
663    #[cfg_attr(
664        all(not(feature = "lt2023_1"), feature = "lt2024_1"),
665        doc = "in Helix Core",
666        doc = "Server Administrator Guide. See also checkpoint examples."
667    )]
668    #[cfg_attr(
669        all(not(feature = "lt2024_1"), feature = "lt2024_2"),
670        doc = "in the Helix",
671        doc = "Core Server Administrator Guide. See also checkpoint examples."
672    )]
673    #[cfg_attr(
674        all(not(feature = "lt2024_2"), feature = "lt2025_1"),
675        doc = "in the P4",
676        doc = "Server Administration Documentation. See also checkpoint examples."
677    )]
678    #[cfg_attr(
679        not(feature = "lt2025_1"),
680        doc = "in the P4",
681        doc = "Server Administration Documentation. See also Checkpoint examples."
682    )]
683    #[cfg(not(feature = "lt2023_1"))]
684    pub fn multiple_files(mut self, multiple_files: bool) -> Self {
685        self.sub_command.multiple_files = multiple_files;
686        self
687    }
688}
689
690impl Admin<CheckPoint<Unselected>> {
691    /// # Description
692    ///
693    /// -z
694    ///
695    #[cfg_attr(
696        feature = "lt2022_2",
697        doc = "For `p4 admin checkpoint` and `p4 admin journal`, save the checkpoint",
698        doc = "and saved journal file in compressed (gzip) format, appending the `.gz`",
699        doc = "suffix to the files."
700    )]
701    #[cfg_attr(
702        all(feature = "lt2023_1", not(feature = "lt2022_2")),
703        doc = "For `p4 admin checkpoint -z` and `p4 admin journal -z`, save the",
704        doc = "checkpoint and journal file in compressed format. The `.gz` suffix is",
705        doc = "appended to compressed journals and checkpoint files, which are in",
706        doc = "gzip format. If you do not specify `-z` or `-Z`, no compression occurs."
707    )]
708    #[cfg_attr(
709        not(feature = "lt2023_1"),
710        doc = "Save the checkpoint and journal file in compressed format. The `.gz`",
711        doc = "suffix is appended to compressed journals and checkpoint files, which",
712        doc = "are in gzip format. If you do not specify `-z` or `-Z`, no compression",
713        doc = "occurs."
714    )]
715    pub fn compress_both(self) -> Admin<CheckPoint<compression::Both>> {
716        Admin {
717            bin: self.bin,
718            global_opts: self.global_opts,
719            sub_command: CheckPoint::<compression::Both> {
720                compression: compression::Both,
721                #[cfg(not(feature = "lt2023_1"))]
722                parallel: self.sub_command.parallel,
723                #[cfg(not(feature = "lt2023_1"))]
724                threads: self.sub_command.threads,
725                #[cfg(not(feature = "lt2023_1"))]
726                multiple_files: self.sub_command.multiple_files,
727            },
728        }
729    }
730
731    /// # Description
732    ///
733    /// -Z
734    ///
735    #[cfg_attr(
736        feature = "lt2017_2",
737        doc = "For `p4 admin checkpoint` and `p4 admin journal`, save the checkpoint",
738        doc = "in compressed (gzip) format, appending the `.gz` suffix to the file, but",
739        doc = "leave the journal uncompressed for use by replica servers."
740    )]
741    #[cfg_attr(
742        all(feature = "lt2022_2", not(feature = "lt2017_2")),
743        doc = "For `p4 admin checkpoint`, save the checkpoint in compressed (gzip)",
744        doc = "format, appending the `.gz` suffix to the file, but leave the journal",
745        doc = "uncompressed for use by replica servers."
746    )]
747    #[cfg_attr(
748        all(feature = "lt2023_1", not(feature = "lt2022_2")),
749        doc = "For `p4 admin checkpoint -Z`, save the checkpoint in compressed format,",
750        doc = "but leave the journal uncompressed for use by replica servers."
751    )]
752    #[cfg_attr(
753        not(feature = "lt2023_1"),
754        doc = "For `p4 admin checkpoint -Z`, save the checkpoint in compressed format,",
755        doc = "but leave the journal uncompressed for use by replica servers. The",
756        doc = "`.gz` suffix is appended to compressed journals and checkpoint files,",
757        doc = "which are in gzip format. If you do not specify `-z` or `-Z`, no",
758        doc = "compression occurs."
759    )]
760    pub fn compress_checkpoint_only(self) -> Admin<CheckPoint<compression::CheckPointOnly>> {
761        Admin {
762            bin: self.bin,
763            global_opts: self.global_opts,
764            sub_command: CheckPoint::<compression::CheckPointOnly> {
765                compression: compression::CheckPointOnly,
766                #[cfg(not(feature = "lt2023_1"))]
767                parallel: self.sub_command.parallel,
768                #[cfg(not(feature = "lt2023_1"))]
769                threads: self.sub_command.threads,
770                #[cfg(not(feature = "lt2023_1"))]
771                multiple_files: self.sub_command.multiple_files,
772            },
773        }
774    }
775}
776
777/// `p4 admin journal [-z] [prefix]`: rotate the journal.
778#[derive(Debug, Clone, Default)]
779pub struct Journal {
780    gzip: bool,
781}
782
783impl SubCommand for Journal {
784    fn name(&self) -> &str {
785        "journal"
786    }
787
788    fn inject_local_args(&self, command: &mut Command) {
789        if self.gzip {
790            command.arg("-z");
791        }
792    }
793}
794
795impl ParameterizedSpawn for Admin<Journal> {
796    type Input<'a> = &'a OsStr;
797    type Output<'a> = Child;
798    type Error = std::io::Error;
799
800    /// Spawns `p4 admin journal` as a child process with piped standard
801    /// output and error streams; use the returned [`Child`] handle to wait
802    /// for it or interact with it.
803    ///
804    /// Pass `Some(prefix)` to name the journal with the given prefix, or
805    /// `None` to use the default journal name.
806    fn spawn_with<'a>(&mut self, prefix: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
807        self.setup_command(&self.bin)
808            .arg(prefix)
809            .stdout(Stdio::piped())
810            .stderr(Stdio::piped())
811            .spawn()
812    }
813}
814
815impl SpawnExt for Admin<Journal> {
816    fn spawn<'a>(&mut self) -> Result<Self::Output<'a>, Self::Error> {
817        self.setup_command(&self.bin)
818            .stdout(Stdio::piped())
819            .stderr(Stdio::piped())
820            .spawn()
821    }
822}
823
824impl Admin<Journal> {
825    /// # Description
826    ///
827    /// -z
828    ///
829    #[cfg_attr(
830        feature = "lt2022_2",
831        doc = "For `p4 admin checkpoint` and `p4 admin journal`, save the checkpoint",
832        doc = "and saved journal file in compressed (gzip) format, appending the `.gz`",
833        doc = "suffix to the files."
834    )]
835    #[cfg_attr(
836        all(feature = "lt2023_1", not(feature = "lt2022_2")),
837        doc = "For `p4 admin checkpoint -z` and `p4 admin journal -z`, save the",
838        doc = "checkpoint and journal file in compressed format. The `.gz` suffix is",
839        doc = "appended to compressed journals and checkpoint files, which are in",
840        doc = "gzip format. If you do not specify `-z` or `-Z`, no compression occurs."
841    )]
842    #[cfg_attr(
843        not(feature = "lt2023_1"),
844        doc = "Save the journal file in compressed format. The `.gz` suffix is",
845        doc = "appended to compressed journals and checkpoint files, which are in",
846        doc = "gzip format. If you do not specify `-z`, no compression occurs."
847    )]
848    pub fn get_gzip(&self) -> bool {
849        self.sub_command.gzip
850    }
851
852    /// # Description
853    ///
854    /// -z
855    ///
856    #[cfg_attr(
857        feature = "lt2022_2",
858        doc = "For `p4 admin checkpoint` and `p4 admin journal`, save the checkpoint",
859        doc = "and saved journal file in compressed (gzip) format, appending the `.gz`",
860        doc = "suffix to the files."
861    )]
862    #[cfg_attr(
863        all(feature = "lt2023_1", not(feature = "lt2022_2")),
864        doc = "For `p4 admin checkpoint -z` and `p4 admin journal -z`, save the",
865        doc = "checkpoint and journal file in compressed format. The `.gz` suffix is",
866        doc = "appended to compressed journals and checkpoint files, which are in",
867        doc = "gzip format. If you do not specify `-z` or `-Z`, no compression occurs."
868    )]
869    #[cfg_attr(
870        not(feature = "lt2023_1"),
871        doc = "Save the journal file in compressed format. The `.gz` suffix is",
872        doc = "appended to compressed journals and checkpoint files, which are in",
873        doc = "gzip format. If you do not specify `-z`, no compression occurs."
874    )]
875    pub fn set_gzip(&mut self, gzip: bool) -> &mut Self {
876        self.sub_command.gzip = gzip;
877        self
878    }
879
880    /// # Description
881    ///
882    /// -z
883    ///
884    #[cfg_attr(
885        feature = "lt2022_2",
886        doc = "For `p4 admin checkpoint` and `p4 admin journal`, save the checkpoint",
887        doc = "and saved journal file in compressed (gzip) format, appending the `.gz`",
888        doc = "suffix to the files."
889    )]
890    #[cfg_attr(
891        all(feature = "lt2023_1", not(feature = "lt2022_2")),
892        doc = "For `p4 admin checkpoint -z` and `p4 admin journal -z`, save the",
893        doc = "checkpoint and journal file in compressed format. The `.gz` suffix is",
894        doc = "appended to compressed journals and checkpoint files, which are in",
895        doc = "gzip format. If you do not specify `-z` or `-Z`, no compression occurs."
896    )]
897    #[cfg_attr(
898        not(feature = "lt2023_1"),
899        doc = "Save the journal file in compressed format. The `.gz` suffix is",
900        doc = "appended to compressed journals and checkpoint files, which are in",
901        doc = "gzip format. If you do not specify `-z`, no compression occurs."
902    )]
903    pub fn gzip(mut self, gzip: bool) -> Self {
904        self.sub_command.gzip = gzip;
905        self
906    }
907}
908
909/// `p4 admin stop`: stop the Perforce service.
910#[derive(Debug, Clone, Default)]
911pub struct Stop;
912
913impl SubCommand for Stop {
914    fn name(&self) -> &str {
915        "stop"
916    }
917
918    fn inject_local_args(&self, _: &mut Command) {}
919}
920
921/// `p4 admin restart`: restart the Perforce service.
922#[derive(Debug, Clone, Default)]
923pub struct Restart;
924
925impl SubCommand for Restart {
926    fn name(&self) -> &str {
927        "restart"
928    }
929
930    fn inject_local_args(&self, _: &mut Command) {}
931}
932
933/// The form specification type archived by `p4 admin updatespecdepot -s`.
934#[derive(Debug, Clone)]
935pub enum SpecifiedType {
936    Client,
937    Depot,
938    /// Added in p4 2018.1.
939    #[cfg(not(feature = "lt2018_1"))]
940    Repo,
941    Branch,
942    Label,
943    TypeMap,
944    Group,
945    User,
946    Job,
947    /// Added in p4 2016.1.
948    #[cfg(not(feature = "lt2016_1"))]
949    Stream,
950    /// Added in p4 2016.1.
951    #[cfg(not(feature = "lt2016_1"))]
952    Triggers,
953    /// Added in p4 2016.1.
954    #[cfg(not(feature = "lt2016_1"))]
955    Protect,
956    /// Added in p4 2016.1.
957    #[cfg(not(feature = "lt2016_1"))]
958    Server,
959    /// Added in p4 2016.1.
960    #[cfg(not(feature = "lt2016_1"))]
961    License,
962    /// Added in p4 2016.1.
963    #[cfg(not(feature = "lt2016_1"))]
964    JobSpec,
965}
966
967impl SpecifiedType {
968    /// CLI value used with `-s`, used when rendering the command arguments.
969    pub(crate) fn to_str(&self) -> &str {
970        match self {
971            Self::Client => "client",
972            Self::Depot => "depot",
973            #[cfg(not(feature = "lt2018_1"))]
974            Self::Repo => "repo",
975            Self::Branch => "branch",
976            Self::Label => "label",
977            Self::TypeMap => "typemap",
978            Self::Group => "group",
979            Self::User => "user",
980            Self::Job => "job",
981            #[cfg(not(feature = "lt2016_1"))]
982            Self::Stream => "stream",
983            #[cfg(not(feature = "lt2016_1"))]
984            Self::Triggers => "triggers",
985            #[cfg(not(feature = "lt2016_1"))]
986            Self::Protect => "protect",
987            #[cfg(not(feature = "lt2016_1"))]
988            Self::Server => "server",
989            #[cfg(not(feature = "lt2016_1"))]
990            Self::License => "license",
991            #[cfg(not(feature = "lt2016_1"))]
992            Self::JobSpec => "jobspec",
993        }
994    }
995}
996
997/// Variants of the `[-a | -s type]` mutually exclusive option group of
998/// `p4 admin updatespecdepot`.
999pub mod spec {
1000    use super::SpecifiedType;
1001
1002    /// Archive all current forms (`-a`).
1003    #[derive(Debug, Clone, Copy, Default)]
1004    pub struct All;
1005
1006    /// Archive forms of the specified type (`-s type`).
1007    #[derive(Debug, Clone)]
1008    pub struct Selected(pub SpecifiedType);
1009}
1010
1011impl ExclusiveOption for spec::All {
1012    fn inject_args(&self, command: &mut Command) {
1013        command.arg("-a");
1014    }
1015}
1016
1017impl ExclusiveOption for spec::Selected {
1018    fn inject_args(&self, command: &mut Command) {
1019        command.arg("-s").arg(self.0.to_str());
1020    }
1021}
1022
1023/// `p4 admin updatespecdepot [-a | -s type]`: archive forms into the spec
1024/// depot.
1025///
1026/// The `S` type parameter encodes the selected variant of the `[-a | -s type]`
1027/// group at compile time; see [`ExclusiveOption`] and [`spec`].
1028#[derive(Debug, Clone, Default)]
1029pub struct UpdateSpecDepot<S = Unselected> {
1030    spec: S,
1031}
1032
1033impl<S: ExclusiveOption> SubCommand for UpdateSpecDepot<S> {
1034    fn name(&self) -> &str {
1035        "updatespecdepot"
1036    }
1037
1038    fn inject_local_args(&self, command: &mut Command) {
1039        self.spec.inject_args(command);
1040    }
1041}
1042
1043impl Admin<UpdateSpecDepot<Unselected>> {
1044    /// # Description
1045    ///
1046    /// -a
1047    ///
1048    #[cfg_attr(
1049        feature = "lt2022_2",
1050        doc = "For `p4 admin updatespecdepot`, update the spec depot with all current",
1051        doc = "forms."
1052    )]
1053    #[cfg_attr(
1054        not(feature = "lt2022_2"),
1055        doc = "For `p4 admin updatespecdepot -a`, update the spec depot with all",
1056        doc = "current forms."
1057    )]
1058    pub fn all(self) -> Admin<UpdateSpecDepot<spec::All>> {
1059        Admin {
1060            bin: self.bin,
1061            global_opts: self.global_opts,
1062            sub_command: UpdateSpecDepot { spec: spec::All },
1063        }
1064    }
1065
1066    /// # Description
1067    ///
1068    /// -s type
1069    ///
1070    #[cfg_attr(
1071        feature = "lt2016_1",
1072        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1073        doc = "specified type, where type is one of `client`, `depot`, `branch`,",
1074        doc = "`label`, `typemap`, `group`, `user`, or `job`."
1075    )]
1076    #[cfg_attr(
1077        all(feature = "lt2018_1", not(feature = "lt2016_1")),
1078        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1079        doc = "specified type, where type is one of `client`, `depot`, `branch`,",
1080        doc = "`label`, `typemap`, `group`, `user`, `job`, `stream`, `triggers`,",
1081        doc = "`protect`, `server`, `license`, or `jobspec`."
1082    )]
1083    #[cfg_attr(
1084        all(feature = "lt2022_2", not(feature = "lt2018_1")),
1085        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1086        doc = "specified type, where type is one of `client`, `depot`, `repo`,",
1087        doc = "`branch`, `label`, `typemap`, `group`, `user`, `job`, `stream`,",
1088        doc = "`triggers`, `protect`, `server`, `license`, or `jobspec`."
1089    )]
1090    #[cfg_attr(
1091        not(feature = "lt2022_2"),
1092        doc = "For `p4 admin updatespecdepot -s`, update the spec depot with forms of",
1093        doc = "the specified type, where type is one of `client`, `depot`, `repo`,",
1094        doc = "`branch`, `label`, `typemap`, `group`, `user`, `job`, `stream`,",
1095        doc = "`triggers`, `protect`, `server`, `license`, or `jobspec`."
1096    )]
1097    pub fn specified_type(
1098        self,
1099        specified_type: SpecifiedType,
1100    ) -> Admin<UpdateSpecDepot<spec::Selected>> {
1101        Admin {
1102            bin: self.bin,
1103            global_opts: self.global_opts,
1104            sub_command: UpdateSpecDepot {
1105                spec: spec::Selected(specified_type),
1106            },
1107        }
1108    }
1109}
1110
1111impl Admin<UpdateSpecDepot<spec::Selected>> {
1112    /// # Description
1113    ///
1114    /// -s type
1115    ///
1116    #[cfg_attr(
1117        feature = "lt2016_1",
1118        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1119        doc = "specified type, where type is one of `client`, `depot`, `branch`,",
1120        doc = "`label`, `typemap`, `group`, `user`, or `job`."
1121    )]
1122    #[cfg_attr(
1123        all(feature = "lt2018_1", not(feature = "lt2016_1")),
1124        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1125        doc = "specified type, where type is one of `client`, `depot`, `branch`,",
1126        doc = "`label`, `typemap`, `group`, `user`, `job`, `stream`, `triggers`,",
1127        doc = "`protect`, `server`, `license`, or `jobspec`."
1128    )]
1129    #[cfg_attr(
1130        all(feature = "lt2022_2", not(feature = "lt2018_1")),
1131        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1132        doc = "specified type, where type is one of `client`, `depot`, `repo`,",
1133        doc = "`branch`, `label`, `typemap`, `group`, `user`, `job`, `stream`,",
1134        doc = "`triggers`, `protect`, `server`, `license`, or `jobspec`."
1135    )]
1136    #[cfg_attr(
1137        not(feature = "lt2022_2"),
1138        doc = "For `p4 admin updatespecdepot -s`, update the spec depot with forms of",
1139        doc = "the specified type, where type is one of `client`, `depot`, `repo`,",
1140        doc = "`branch`, `label`, `typemap`, `group`, `user`, `job`, `stream`,",
1141        doc = "`triggers`, `protect`, `server`, `license`, or `jobspec`."
1142    )]
1143    pub fn get_specified_type(&self) -> &SpecifiedType {
1144        &self.sub_command.spec.0
1145    }
1146
1147    /// # Description
1148    ///
1149    /// -s type
1150    ///
1151    #[cfg_attr(
1152        feature = "lt2016_1",
1153        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1154        doc = "specified type, where type is one of `client`, `depot`, `branch`,",
1155        doc = "`label`, `typemap`, `group`, `user`, or `job`."
1156    )]
1157    #[cfg_attr(
1158        all(feature = "lt2018_1", not(feature = "lt2016_1")),
1159        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1160        doc = "specified type, where type is one of `client`, `depot`, `branch`,",
1161        doc = "`label`, `typemap`, `group`, `user`, `job`, `stream`, `triggers`,",
1162        doc = "`protect`, `server`, `license`, or `jobspec`."
1163    )]
1164    #[cfg_attr(
1165        all(feature = "lt2022_2", not(feature = "lt2018_1")),
1166        doc = "For `p4 admin updatespecdepot`, update the spec depot with forms of the",
1167        doc = "specified type, where type is one of `client`, `depot`, `repo`,",
1168        doc = "`branch`, `label`, `typemap`, `group`, `user`, `job`, `stream`,",
1169        doc = "`triggers`, `protect`, `server`, `license`, or `jobspec`."
1170    )]
1171    #[cfg_attr(
1172        not(feature = "lt2022_2"),
1173        doc = "For `p4 admin updatespecdepot -s`, update the spec depot with forms of",
1174        doc = "the specified type, where type is one of `client`, `depot`, `repo`,",
1175        doc = "`branch`, `label`, `typemap`, `group`, `user`, `job`, `stream`,",
1176        doc = "`triggers`, `protect`, `server`, `license`, or `jobspec`."
1177    )]
1178    pub fn set_specified_type(&mut self, specified_type: SpecifiedType) -> &mut Self {
1179        self.sub_command.spec = spec::Selected(specified_type);
1180        self
1181    }
1182}
1183
1184/// Variants of the `{-a | -u user}` mutually exclusive option group of
1185/// `p4 admin resetpassword`.
1186pub mod set_password {
1187    /// Reset all users' passwords (`-a`).
1188    #[derive(Debug, Clone, Copy, Default)]
1189    pub struct All;
1190
1191    /// Reset a single user's password (`-u user`).
1192    #[derive(Debug, Clone)]
1193    pub struct User(pub String);
1194}
1195
1196impl ExclusiveOption for set_password::All {
1197    fn inject_args(&self, command: &mut Command) {
1198        command.arg("-a");
1199    }
1200}
1201
1202impl ExclusiveOption for set_password::User {
1203    fn inject_args(&self, command: &mut Command) {
1204        command.arg("-u").arg(&self.0);
1205    }
1206}
1207
1208/// `p4 admin resetpassword {-a | -u user} [-l]`: force users to reset their
1209/// passwords.
1210///
1211/// The `T` type parameter encodes the selected variant of the `{-a | -u user}`
1212/// group at compile time; see [`ExclusiveOption`] and [`set_password`].
1213#[cfg_attr(
1214    feature = "lt2025_2",
1215    doc = "`p4 admin resetpassword -a | -u user`: force users to reset their passwords."
1216)]
1217#[cfg_attr(
1218    not(feature = "lt2025_2"),
1219    doc = "`p4 admin resetpassword {-a | -u user} [-l]`: force users to reset their passwords."
1220)]
1221#[derive(Debug, Clone, Default)]
1222pub struct ResetPassword<T = Unselected> {
1223    set_password: T,
1224
1225    /// Added in p4 2025.2.
1226    #[cfg(not(feature = "lt2025_2"))]
1227    super_user: bool,
1228}
1229
1230impl<T: ExclusiveOption> SubCommand for ResetPassword<T> {
1231    fn name(&self) -> &str {
1232        "resetpassword"
1233    }
1234
1235    fn inject_local_args(&self, command: &mut Command) {
1236        self.set_password.inject_args(command);
1237        #[cfg(not(feature = "lt2025_2"))]
1238        if self.super_user {
1239            command.arg("-l");
1240        }
1241    }
1242}
1243
1244impl Admin<ResetPassword<Unselected>> {
1245    /// # Description
1246    ///
1247    /// -a
1248    ///
1249    #[cfg_attr(
1250        feature = "lt2023_1",
1251        doc = "Force password reset of all users with passwords, including the",
1252        doc = "superuser who issued the command. Only the passwords of users who",
1253        doc = "presently exist (and who have passwords) are reset."
1254    )]
1255    #[cfg_attr(not(feature = "lt2023_1"), doc = "All users.")]
1256    pub fn all(self) -> Admin<ResetPassword<set_password::All>> {
1257        Admin {
1258            bin: self.bin,
1259            global_opts: self.global_opts,
1260            sub_command: ResetPassword {
1261                set_password: set_password::All,
1262                #[cfg(not(feature = "lt2025_2"))]
1263                super_user: self.sub_command.super_user,
1264            },
1265        }
1266    }
1267
1268    /// # Description
1269    ///
1270    /// -u user
1271    ///
1272    #[cfg_attr(
1273        feature = "lt2023_1",
1274        doc = "Force a single user with an existing password to reset their password",
1275        doc = "before they can run another command."
1276    )]
1277    #[cfg_attr(not(feature = "lt2023_1"), doc = "The specified user.")]
1278    pub fn user(self, user: impl Into<String>) -> Admin<ResetPassword<set_password::User>> {
1279        Admin {
1280            bin: self.bin,
1281            global_opts: self.global_opts,
1282            sub_command: ResetPassword {
1283                set_password: set_password::User(user.into()),
1284                #[cfg(not(feature = "lt2025_2"))]
1285                super_user: self.sub_command.super_user,
1286            },
1287        }
1288    }
1289}
1290
1291impl Admin<ResetPassword<set_password::User>> {
1292    /// # Description
1293    ///
1294    /// -u user
1295    ///
1296    #[cfg_attr(
1297        feature = "lt2023_1",
1298        doc = "Force a single user with an existing password to reset their password",
1299        doc = "before they can run another command."
1300    )]
1301    #[cfg_attr(not(feature = "lt2023_1"), doc = "The specified user.")]
1302    pub fn get_user(&self) -> &str {
1303        &self.sub_command.set_password.0
1304    }
1305
1306    /// # Description
1307    ///
1308    /// -u user
1309    ///
1310    #[cfg_attr(
1311        feature = "lt2023_1",
1312        doc = "Force a single user with an existing password to reset their password",
1313        doc = "before they can run another command."
1314    )]
1315    #[cfg_attr(not(feature = "lt2023_1"), doc = "The specified user.")]
1316    pub fn set_user(&mut self, user: impl Into<String>) -> &mut Self {
1317        self.sub_command.set_password.0 = user.into();
1318        self
1319    }
1320}
1321
1322impl<T: ExclusiveOption> Admin<ResetPassword<T>> {
1323    /// # Description
1324    ///
1325    /// -l
1326    ///
1327    /// Super user.
1328    #[cfg(not(feature = "lt2025_2"))]
1329    pub fn get_super_user(&self) -> bool {
1330        self.sub_command.super_user
1331    }
1332
1333    /// # Description
1334    ///
1335    /// -l
1336    ///
1337    /// Super user.
1338    #[cfg(not(feature = "lt2025_2"))]
1339    pub fn set_super_user(&mut self, super_user: bool) -> &mut Self {
1340        self.sub_command.super_user = super_user;
1341        self
1342    }
1343
1344    /// # Description
1345    ///
1346    /// -l
1347    ///
1348    /// Super user.
1349    #[cfg(not(feature = "lt2025_2"))]
1350    pub fn super_user(mut self, super_user: bool) -> Self {
1351        self.sub_command.super_user = super_user;
1352        self
1353    }
1354}
1355
1356/// `p4 admin setldapusers`: convert existing non-super users to LDAP
1357/// authentication. Added in p4 2015.1.
1358#[cfg(not(feature = "lt2015_1"))]
1359#[derive(Debug, Clone, Default)]
1360pub struct SetLdapUsers;
1361
1362#[cfg(not(feature = "lt2015_1"))]
1363impl SubCommand for SetLdapUsers {
1364    fn name(&self) -> &str {
1365        "setldapusers"
1366    }
1367
1368    fn inject_local_args(&self, _: &mut Command) {}
1369}
1370
1371/// `p4 admin end-journal`: end journal replication at a failover consistency
1372/// point. Added in p4 2018.1.
1373#[cfg(not(feature = "lt2018_1"))]
1374#[derive(Debug, Clone, Default)]
1375pub struct EndJournal;
1376
1377#[cfg(not(feature = "lt2018_1"))]
1378impl SubCommand for EndJournal {
1379    fn name(&self) -> &str {
1380        "end-journal"
1381    }
1382
1383    fn inject_local_args(&self, _: &mut Command) {}
1384}
1385
1386/// `p4 admin sysinfo`: dump system information for Perforce Support. Added in
1387/// p4 2023.1.
1388#[cfg(not(feature = "lt2023_1"))]
1389#[derive(Debug, Clone, Default)]
1390pub struct SysInfo;
1391
1392#[cfg(not(feature = "lt2023_1"))]
1393impl SubCommand for SysInfo {
1394    fn name(&self) -> &str {
1395        "sysinfo"
1396    }
1397
1398    fn inject_local_args(&self, _: &mut Command) {}
1399}
1400
1401/// `p4 admin resource-monitor`: report server resource usage. Added in p4
1402/// 2023.1.
1403#[cfg(not(feature = "lt2023_1"))]
1404#[derive(Debug, Clone, Default)]
1405pub struct ResourceMonitor;
1406
1407#[cfg(not(feature = "lt2023_1"))]
1408impl SubCommand for ResourceMonitor {
1409    fn name(&self) -> &str {
1410        "resource-monitor"
1411    }
1412
1413    fn inject_local_args(&self, _: &mut Command) {}
1414}
1415
1416/// Variants of the `[--restrict-only | --expand-only]` mutually exclusive
1417/// option group of `p4 admin replica-filter-reconcile`.
1418#[cfg(not(feature = "lt2025_2"))]
1419pub mod reconcile {
1420    /// Only remove applicable database records (`--restrict-only`).
1421    #[derive(Debug, Clone, Copy, Default)]
1422    pub struct RestrictOnly;
1423
1424    /// Only add applicable database records (`--expand-only`).
1425    #[derive(Debug, Clone, Copy, Default)]
1426    pub struct ExpandOnly;
1427}
1428
1429#[cfg(not(feature = "lt2025_2"))]
1430impl ExclusiveOption for reconcile::RestrictOnly {
1431    fn inject_args(&self, command: &mut Command) {
1432        command.arg("--restrict-only");
1433    }
1434}
1435
1436#[cfg(not(feature = "lt2025_2"))]
1437impl ExclusiveOption for reconcile::ExpandOnly {
1438    fn inject_args(&self, command: &mut Command) {
1439        command.arg("--expand-only");
1440    }
1441}
1442
1443/// `p4 admin replica-filter-reconcile [--restrict-only | --expand-only]
1444/// [table ...]`: reconcile the replica database after filter changes. Added in
1445/// p4 2025.2.
1446///
1447/// The `M` type parameter encodes the selected variant of the
1448/// `[--restrict-only | --expand-only]` group at compile time; see
1449/// [`ExclusiveOption`] and [`reconcile`].
1450#[cfg(not(feature = "lt2025_2"))]
1451#[derive(Debug, Clone, Default)]
1452pub struct ReplicaFilterReconcile<M = Unselected> {
1453    reconcile: M,
1454}
1455
1456#[cfg(not(feature = "lt2025_2"))]
1457impl<M: ExclusiveOption> SubCommand for ReplicaFilterReconcile<M> {
1458    fn name(&self) -> &str {
1459        "replica-filter-reconcile"
1460    }
1461
1462    fn inject_local_args(&self, command: &mut Command) {
1463        self.reconcile.inject_args(command);
1464    }
1465}
1466
1467#[cfg(not(feature = "lt2025_2"))]
1468impl Admin<ReplicaFilterReconcile<Unselected>> {
1469    /// # Description
1470    ///
1471    /// --restrict-only
1472    ///
1473    /// Reconcile the replica database by only removing applicable database
1474    /// records.
1475    pub fn restrict_only(self) -> Admin<ReplicaFilterReconcile<reconcile::RestrictOnly>> {
1476        Admin {
1477            bin: self.bin,
1478            global_opts: self.global_opts,
1479            sub_command: ReplicaFilterReconcile {
1480                reconcile: reconcile::RestrictOnly,
1481            },
1482        }
1483    }
1484
1485    /// # Description
1486    ///
1487    /// --expand-only
1488    ///
1489    /// Reconcile the replica database by only adding applicable database
1490    /// records.
1491    pub fn expand_only(self) -> Admin<ReplicaFilterReconcile<reconcile::ExpandOnly>> {
1492        Admin {
1493            bin: self.bin,
1494            global_opts: self.global_opts,
1495            sub_command: ReplicaFilterReconcile {
1496                reconcile: reconcile::ExpandOnly,
1497            },
1498        }
1499    }
1500}
1501
1502#[cfg(not(feature = "lt2025_2"))]
1503impl<M: ExclusiveOption> ParameterizedSpawn for Admin<ReplicaFilterReconcile<M>> {
1504    type Input<'a> = &'a [&'a OsStr];
1505    type Output<'a> = Child;
1506    type Error = std::io::Error;
1507
1508    /// Spawns `p4 admin replica-filter-reconcile` for the given tables as a
1509    /// child process with piped standard output and error streams; use the
1510    /// returned [`Child`] handle to wait for it or interact with it.
1511    ///
1512    /// Pass an empty slice to reconcile all applicable database tables.
1513    fn spawn_with<'a>(&mut self, tables: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
1514        self.setup_command(&self.bin)
1515            .args(tables)
1516            .stdout(Stdio::piped())
1517            .stderr(Stdio::piped())
1518            .spawn()
1519    }
1520}
1521
1522#[cfg(test)]
1523mod tests {
1524    use super::*;
1525    use crate::cmd::args_of;
1526
1527    /// Dry-run checks of the assembled `p4 admin ...` command lines; no
1528    /// process is spawned.
1529    #[test]
1530    fn stop() {
1531        let admin = AdminEntry::new("p4", GlobalOpts::new()).stop();
1532
1533        assert_eq!(args_of(&admin.setup_command("p4")), ["admin", "stop"]);
1534    }
1535
1536    #[test]
1537    fn restart() {
1538        let admin = AdminEntry::new("p4", GlobalOpts::new()).restart();
1539
1540        assert_eq!(args_of(&admin.setup_command("p4")), ["admin", "restart"]);
1541    }
1542
1543    #[cfg(not(feature = "lt2015_1"))]
1544    #[test]
1545    fn setldapusers() {
1546        let admin = AdminEntry::new("p4", GlobalOpts::new()).setldapusers();
1547
1548        assert_eq!(
1549            args_of(&admin.setup_command("p4")),
1550            ["admin", "setldapusers"]
1551        );
1552    }
1553
1554    #[cfg(not(feature = "lt2018_1"))]
1555    #[test]
1556    fn end_journal() {
1557        let admin = AdminEntry::new("p4", GlobalOpts::new()).end_journal();
1558
1559        assert_eq!(
1560            args_of(&admin.setup_command("p4")),
1561            ["admin", "end-journal"]
1562        );
1563    }
1564
1565    #[cfg(not(feature = "lt2023_1"))]
1566    #[test]
1567    fn sysinfo() {
1568        let admin = AdminEntry::new("p4", GlobalOpts::new()).sysinfo();
1569
1570        assert_eq!(args_of(&admin.setup_command("p4")), ["admin", "sysinfo"]);
1571    }
1572
1573    #[cfg(not(feature = "lt2023_1"))]
1574    #[test]
1575    fn resource_monitor() {
1576        let admin = AdminEntry::new("p4", GlobalOpts::new()).resource_monitor();
1577
1578        assert_eq!(
1579            args_of(&admin.setup_command("p4")),
1580            ["admin", "resource-monitor"]
1581        );
1582    }
1583
1584    #[test]
1585    fn checkpoint_compress_both() {
1586        let admin = AdminEntry::new("p4", GlobalOpts::new())
1587            .checkpoint()
1588            .compress_both();
1589
1590        assert_eq!(
1591            args_of(&admin.setup_command("p4")),
1592            ["admin", "checkpoint", "-z"]
1593        );
1594    }
1595
1596    #[test]
1597    fn checkpoint_compress_checkpoint_only() {
1598        let admin = AdminEntry::new("p4", GlobalOpts::new())
1599            .checkpoint()
1600            .compress_checkpoint_only();
1601
1602        assert_eq!(
1603            args_of(&admin.setup_command("p4")),
1604            ["admin", "checkpoint", "-Z"]
1605        );
1606    }
1607
1608    #[test]
1609    fn checkpoint_with_prefix() {
1610        let admin = AdminEntry::new("p4", GlobalOpts::new())
1611            .checkpoint()
1612            .compress_both();
1613
1614        // Mirrors `spawn_with`/`output_with`, which append the prefix after
1615        // the assembled command.
1616        let mut command = admin.setup_command("p4");
1617        command.arg("ckp");
1618
1619        assert_eq!(args_of(&command), ["admin", "checkpoint", "-z", "ckp"]);
1620    }
1621
1622    #[cfg(not(feature = "lt2023_1"))]
1623    #[test]
1624    fn checkpoint_parallel_options() {
1625        let mut admin = AdminEntry::new("p4", GlobalOpts::new()).checkpoint();
1626        admin
1627            .set_parallel(true)
1628            .set_threads(4)
1629            .set_multiple_files(true);
1630
1631        assert_eq!(
1632            args_of(&admin.setup_command("p4")),
1633            ["admin", "checkpoint", "-p", "-N", "4", "-m"]
1634        );
1635    }
1636
1637    #[test]
1638    fn journal_gzip() {
1639        let mut admin = AdminEntry::new("p4", GlobalOpts::new()).journal();
1640        admin.set_gzip(true);
1641
1642        assert_eq!(
1643            args_of(&admin.setup_command("p4")),
1644            ["admin", "journal", "-z"]
1645        );
1646    }
1647
1648    #[test]
1649    fn updatespecdepot_all() {
1650        let admin = AdminEntry::new("p4", GlobalOpts::new())
1651            .updatespecdepot()
1652            .all();
1653
1654        assert_eq!(
1655            args_of(&admin.setup_command("p4")),
1656            ["admin", "updatespecdepot", "-a"]
1657        );
1658    }
1659
1660    /// The `-s type` variant is covered here because `SpecifiedType` is not
1661    /// exported outside the crate.
1662    #[test]
1663    fn updatespecdepot_specified_type() {
1664        let admin = AdminEntry::new("p4", GlobalOpts::new())
1665            .updatespecdepot()
1666            .specified_type(SpecifiedType::Client);
1667
1668        assert_eq!(
1669            args_of(&admin.setup_command("p4")),
1670            ["admin", "updatespecdepot", "-s", "client"]
1671        );
1672    }
1673
1674    #[test]
1675    fn resetpassword_all() {
1676        let admin = AdminEntry::new("p4", GlobalOpts::new())
1677            .resetpassword()
1678            .all();
1679
1680        assert_eq!(
1681            args_of(&admin.setup_command("p4")),
1682            ["admin", "resetpassword", "-a"]
1683        );
1684    }
1685
1686    #[test]
1687    fn resetpassword_single_user() {
1688        let admin = AdminEntry::new("p4", GlobalOpts::new())
1689            .resetpassword()
1690            .user("bruno");
1691
1692        assert_eq!(
1693            args_of(&admin.setup_command("p4")),
1694            ["admin", "resetpassword", "-u", "bruno"]
1695        );
1696    }
1697
1698    #[cfg(not(feature = "lt2025_2"))]
1699    #[test]
1700    fn resetpassword_super_user() {
1701        let admin = AdminEntry::new("p4", GlobalOpts::new())
1702            .resetpassword()
1703            .super_user(true);
1704
1705        assert_eq!(
1706            args_of(&admin.setup_command("p4")),
1707            ["admin", "resetpassword", "-l"]
1708        );
1709    }
1710
1711    #[cfg(not(feature = "lt2025_2"))]
1712    #[test]
1713    fn replica_filter_reconcile_restrict_only() {
1714        let admin = AdminEntry::new("p4", GlobalOpts::new())
1715            .replica_filter_reconcile()
1716            .restrict_only();
1717
1718        assert_eq!(
1719            args_of(&admin.setup_command("p4")),
1720            ["admin", "replica-filter-reconcile", "--restrict-only"]
1721        );
1722    }
1723
1724    #[cfg(not(feature = "lt2025_2"))]
1725    #[test]
1726    fn replica_filter_reconcile_expand_only() {
1727        let admin = AdminEntry::new("p4", GlobalOpts::new())
1728            .replica_filter_reconcile()
1729            .expand_only();
1730
1731        assert_eq!(
1732            args_of(&admin.setup_command("p4")),
1733            ["admin", "replica-filter-reconcile", "--expand-only"]
1734        );
1735    }
1736
1737    #[test]
1738    fn global_opts_are_injected_once() {
1739        let global_opts = GlobalOpts::new().port("localhost:1666");
1740        let admin = AdminEntry::new("p4", global_opts).checkpoint();
1741
1742        // The inner subcommand must not inject the global options a second
1743        // time.
1744        assert_eq!(
1745            args_of(&admin.setup_command("p4")),
1746            ["-p", "localhost:1666", "admin", "checkpoint"]
1747        );
1748    }
1749}