Skip to main content

perforce_cli/cmd/
admin.rs

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