Skip to main content

perforce_cli/cmd/
admin.rs

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