Skip to main content

perforce_cli/cmd/
admin.rs

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