Skip to main content

perforce_cli/cmd/
sync.rs

1use std::{
2    ffi::OsStr,
3    path::PathBuf,
4    process::{Child, Command, Stdio},
5};
6
7use super::{ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12/// Configuration for the `--parallel` option of `p4 sync`.
13///
14/// Controls how files are transferred in parallel. `threads` is required;
15/// all other sub-options are optional and fall back to server defaults when
16/// omitted.
17#[derive(Debug, Clone, Default)]
18pub struct ParallelConfig {
19    /// Number of concurrent network connections (`threads=N`).
20    pub threads: u64,
21
22    /// Number of files in a batch (`batch=N`).
23    pub batch_files: Option<u64>,
24
25    /// Number of bytes in a batch (`batchsize=N`).
26    pub batch_size_bytes: Option<u64>,
27
28    /// Minimum number of files for a parallel sync (`min=N`).
29    pub min_files: Option<u64>,
30
31    /// Minimum number of bytes for a parallel sync (`minsize=N`).
32    pub min_size_bytes: Option<u64>,
33}
34
35impl ParallelConfig {
36    /// Builds the value passed to `--parallel=...`, e.g.
37    /// `threads=4,batch=8,batchsize=512K,min=9,minsize=576K`.
38    pub fn as_arg(&self) -> String {
39        let mut parts = vec![format!("threads={}", self.threads)];
40
41        if let Some(v) = self.batch_files {
42            parts.push(format!("batch={}", v));
43        }
44        if let Some(v) = self.batch_size_bytes {
45            parts.push(format!("batchsize={}", v));
46        }
47        if let Some(v) = self.min_files {
48            parts.push(format!("min={}", v));
49        }
50        if let Some(v) = self.min_size_bytes {
51            parts.push(format!("minsize={}", v));
52        }
53
54        parts.join(",")
55    }
56}
57
58/// The `--use-stream-change` value controlling which stream specification
59/// version is used to generate the client view.
60#[cfg(not(feature = "lt2022_2"))]
61#[derive(Debug, Clone, Copy)]
62pub enum StreamSpecVersion {
63    /// `--use-stream-change` with no value: the maximum change number in the
64    /// file list determines the stream spec version.
65    MaxInFilelists,
66    /// `--use-stream-change=0`: use the current stream spec version.
67    Current,
68    /// `--use-stream-change=N`: use the stream spec version at or before
69    /// change `N`.
70    ChangeNumber(u32),
71}
72
73#[cfg(not(feature = "lt2022_2"))]
74impl StreamSpecVersion {
75    /// `--use-stream-change` with no value: the maximum change number in the
76    /// file list determines the stream spec version.
77    pub fn max_in_filelists() -> Self {
78        StreamSpecVersion::MaxInFilelists
79    }
80
81    /// `--use-stream-change=0`: use the current stream spec version.
82    pub fn current() -> Self {
83        StreamSpecVersion::Current
84    }
85
86    /// `--use-stream-change=N`: use the stream spec version at or before
87    /// change `n`.
88    pub fn at_change(n: u32) -> Self {
89        StreamSpecVersion::ChangeNumber(n)
90    }
91
92    /// Injects the `--use-stream-change` argument(s) into `command`.
93    pub fn inject_arg(&self, command: &mut Command) {
94        match self {
95            StreamSpecVersion::MaxInFilelists => {
96                command.arg("--use-stream-change");
97            }
98            StreamSpecVersion::Current => {
99                command.arg("--use-stream-change=0");
100            }
101            StreamSpecVersion::ChangeNumber(n) => {
102                command.arg(format!("--use-stream-change={}", n));
103            }
104        }
105    }
106}
107
108/// Preview mode of `p4 sync` (`-n`): display the results of the sync without
109/// actually performing the sync.
110///
111/// Entered with [`Sync::preview_result`]. Mutually exclusive with
112/// [`PreviewNetworkTraffic`].
113#[derive(Debug, Clone, Copy, Default)]
114pub struct PreviewResult;
115
116impl ExclusiveOption for PreviewResult {
117    fn inject_args(&self, command: &mut Command) {
118        command.arg("-n");
119    }
120}
121
122/// Preview mode of `p4 sync` (`-N`): display a summary of the expected
123/// network traffic associated with a sync, without performing the sync.
124///
125/// Entered with [`Sync::preview_network_traffic`]. Mutually exclusive with
126/// [`PreviewResult`].
127#[derive(Debug, Clone, Copy, Default)]
128pub struct PreviewNetworkTraffic;
129
130impl ExclusiveOption for PreviewNetworkTraffic {
131    fn inject_args(&self, command: &mut Command) {
132        command.arg("-N");
133    }
134}
135
136/// Force execution sub-mode of [`RegularMode`]: entered when any of `-f`,
137/// `-k`, or `-r` is set.
138///
139/// In this state the command is locked into the regular sync form and can no
140/// longer transition to [`SafeCheckMode`] or [`PopulateMode`].
141#[derive(Debug, Clone, Default)]
142pub struct ForceRegularMode {
143    force: bool,
144
145    metadata_only: bool,
146
147    #[cfg(not(feature = "lt2015_1"))]
148    reopen_moved_files: bool,
149}
150
151impl ExclusiveOption for ForceRegularMode {
152    fn inject_args(&self, command: &mut Command) {
153        if self.force {
154            command.arg("-f");
155        }
156
157        if self.metadata_only {
158            command.arg("-k");
159        }
160
161        #[cfg(not(feature = "lt2015_1"))]
162        if self.reopen_moved_files {
163            command.arg("-r");
164        }
165    }
166}
167
168/// Safe sync sub-mode of [`RegularMode`] (`-s`): compare the content in the
169/// client workspace against what was last synced and do not overwrite files
170/// that were modified outside of P4 Server's control.
171///
172/// Entered with [`Sync::safe_check`].
173#[derive(Debug, Clone, Copy, Default)]
174pub struct SafeCheckMode;
175
176impl ExclusiveOption for SafeCheckMode {
177    fn inject_args(&self, command: &mut Command) {
178        command.arg("-s");
179    }
180}
181
182/// Populate sub-mode of [`RegularMode`] (`-p`): populate a client workspace
183/// but do not update the have list.
184///
185/// Entered with [`Sync::populate`].
186#[derive(Debug, Clone, Copy, Default)]
187pub struct PopulateMode;
188
189impl ExclusiveOption for PopulateMode {
190    fn inject_args(&self, command: &mut Command) {
191        command.arg("-p");
192    }
193}
194
195/// Regular mode of `p4 sync`: the command forms that operate on the client
196/// workspace.
197///
198/// All common options are stored directly on this struct. The `Mode` type
199/// parameter selects the mutually exclusive execution sub-mode:
200///
201/// - [`Unselected`] (default): the first command form, plain regular sync.
202/// - [`ForceRegularMode`]: entered by setting `-f`, `-k`, or `-r`; locks out
203///   further transitions to [`SafeCheckMode`] or [`PopulateMode`].
204/// - [`SafeCheckMode`]: entered by [`Sync::safe_check`] (`-s`).
205/// - [`PopulateMode`]: entered by [`Sync::populate`] (`-p`).
206///
207/// The `P` type parameter tracks the preview mode ([`Unselected`] by default,
208/// [`PreviewResult`] or [`PreviewNetworkTraffic`] otherwise).
209#[derive(Debug, Clone, Default)]
210pub struct RegularMode<Mode = Unselected, P = Unselected> {
211    #[cfg(not(feature = "lt2022_2"))]
212    verify_edge_replication: bool,
213
214    script_list_mode: bool,
215
216    #[cfg(not(feature = "lt2022_1"))]
217    suppress_keyword_expansion: bool,
218
219    quiet_mode: bool,
220
221    limit: Option<u64>,
222
223    parallel: Option<ParallelConfig>,
224
225    #[cfg(not(feature = "lt2022_2"))]
226    stream_spec_version: Option<StreamSpecVersion>,
227
228    mode: Mode,
229
230    preview: P,
231}
232
233impl<Mode: ExclusiveOption, P: ExclusiveOption> ExclusiveOption for RegularMode<Mode, P> {
234    fn inject_args(&self, command: &mut Command) {
235        #[cfg(not(feature = "lt2022_2"))]
236        if self.verify_edge_replication {
237            command.arg("-E");
238        }
239
240        if self.script_list_mode {
241            command.arg("-L");
242        }
243
244        #[cfg(not(feature = "lt2022_1"))]
245        if self.suppress_keyword_expansion {
246            command.arg("-K");
247        }
248
249        if self.quiet_mode {
250            command.arg("-q");
251        }
252
253        self.mode.inject_args(command);
254
255        self.preview.inject_args(command);
256
257        if let Some(max) = self.limit {
258            command.arg("-m").arg(max.to_string());
259        }
260
261        if let Some(parallel) = &self.parallel {
262            command.arg(format!("--parallel={}", parallel.as_arg()));
263        }
264
265        #[cfg(not(feature = "lt2022_2"))]
266        if let Some(version) = &self.stream_spec_version {
267            version.inject_arg(command);
268        }
269    }
270}
271
272/// Sync-time mode of `p4 sync` (`-k --sync-time=N`): update the have list to
273/// reflect the state of the depot at the given time without transferring
274/// files.
275///
276/// Entered with [`Sync::sync_time`]. This mode always implies `-k` (metadata
277/// only), so no separate interface is provided for it.
278#[cfg(not(feature = "lt2025_1"))]
279#[derive(Debug, Clone)]
280pub struct SyncTimeMode {
281    sync_time: String,
282}
283
284#[cfg(not(feature = "lt2025_1"))]
285impl ExclusiveOption for SyncTimeMode {
286    fn inject_args(&self, command: &mut Command) {
287        command
288            .arg("-k")
289            .arg(format!("--sync-time={}", self.sync_time));
290    }
291}
292
293///
294/// Update the client workspace to reflect the contents of the depot.
295///
296/// The `M` type parameter tracks the top-level mode at compile time. The
297/// default [`Unselected`] state syncs files without local options; setting
298/// any regular option or calling a mode-transition method moves into
299/// [`RegularMode`]
300#[cfg_attr(feature = "lt2025_1", doc = ".")]
301#[cfg_attr(
302    not(feature = "lt2025_1"),
303    doc = "; [`Self::sync_time`] moves into [`SyncTimeMode`]."
304)]
305#[derive(Debug, Clone, Default)]
306pub struct Sync<M = Unselected> {
307    bin: PathBuf,
308
309    global_opts: GlobalOpts,
310
311    mode: M,
312}
313
314impl Sync<Unselected> {
315    /// Creates a new `p4 sync` command.
316    ///
317    /// `bin` is the path to the Perforce command-line executable.
318    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
319        Self {
320            bin: bin.into(),
321            global_opts,
322            mode: Unselected,
323        }
324    }
325
326    /// # Description
327    ///
328    /// `-k --sync-time=N`
329    ///
330    /// Update the have list to reflect the state of the depot at the given
331    /// time without transferring files.
332    #[cfg_attr(
333        all(feature = "lt2025_2", not(feature = "lt2025_1")),
334        doc = "The value of `N` can be Unix epoch time or the perforce date",
335        doc = "time format."
336    )]
337    #[cfg_attr(
338        not(feature = "lt2025_2"),
339        doc = "The value of `N` can be Unix epoch time or the Perforce date",
340        doc = "time format."
341    )]
342    ///
343    /// This mode always implies `-k`, so no separate interface is provided
344    /// for it. Transitions this command to the [`SyncTimeMode`] state.
345    #[cfg(not(feature = "lt2025_1"))]
346    pub fn sync_time(self, time: impl Into<String>) -> Sync<SyncTimeMode> {
347        Sync {
348            bin: self.bin,
349            global_opts: self.global_opts,
350            mode: SyncTimeMode {
351                sync_time: time.into(),
352            },
353        }
354    }
355
356    /// # Description
357    ///
358    /// `-s`
359    ///
360    /// Safe sync: compare the content in the client workspace against what
361    /// was last synced.
362    #[cfg_attr(
363        feature = "lt2017_2",
364        doc = "If the file was modified outside of Perforce control, an error",
365        doc = "message is displayed and the file is not overwritten."
366    )]
367    #[cfg_attr(
368        all(feature = "lt2024_1", not(feature = "lt2017_2")),
369        doc = "If the file was modified outside of the control of Helix",
370        doc = "Server, an error message is displayed and the file is not",
371        doc = "overwritten."
372    )]
373    #[cfg_attr(
374        all(feature = "lt2024_2", not(feature = "lt2024_1")),
375        doc = "If the file was modified outside of the control of Helix Core",
376        doc = "Server, an error message is displayed and the file is not",
377        doc = "overwritten."
378    )]
379    #[cfg_attr(
380        not(feature = "lt2024_2"),
381        doc = "If the file was modified outside of the control of P4 Server,",
382        doc = "an error message is displayed and the file is not overwritten."
383    )]
384    ///
385    /// Transitions this command to the [`RegularMode`] state with the
386    /// [`SafeCheckMode`] sub-mode.
387    pub fn enable_safe_check(self) -> Sync<RegularMode<SafeCheckMode>> {
388        Sync {
389            bin: self.bin,
390            global_opts: self.global_opts,
391            mode: RegularMode {
392                mode: SafeCheckMode,
393                ..RegularMode::default()
394            },
395        }
396    }
397
398    /// # Description
399    ///
400    /// `-p`
401    ///
402    /// Populate a client workspace, but do not update the have list. Any
403    /// file that is already synced or opened is bypassed with a warning
404    /// message.
405    ///
406    /// Transitions this command to the [`RegularMode`] state with the
407    /// [`PopulateMode`] sub-mode.
408    pub fn populate_client_workspace(self) -> Sync<RegularMode<PopulateMode>> {
409        Sync {
410            bin: self.bin,
411            global_opts: self.global_opts,
412            mode: RegularMode {
413                mode: PopulateMode,
414                ..RegularMode::default()
415            },
416        }
417    }
418
419    /// # Description
420    ///
421    /// `-E`
422    ///
423    /// For edge servers replicating from a commit or an upstream edge, verify
424    /// that any changelists specified in the revSpec are submitted before
425    /// continuing with the sync.
426    ///
427    /// Transitions this command to the [`RegularMode`] state.
428    #[cfg(not(feature = "lt2022_2"))]
429    pub fn verify_edge_replication(self, v: bool) -> Sync<RegularMode> {
430        Sync {
431            bin: self.bin,
432            global_opts: self.global_opts,
433            mode: RegularMode {
434                verify_edge_replication: v,
435                ..RegularMode::default()
436            },
437        }
438    }
439
440    /// # Description
441    ///
442    /// `-L`
443    ///
444    /// For scripting purposes, perform the sync on a list of valid file
445    /// arguments in full depot syntax with a valid revision number.
446    #[cfg_attr(
447        not(feature = "lt2016_1"),
448        doc = "",
449        doc = "When this flag is used, the arguments are processed together by",
450        doc = "building an internal table similar to a label. This file list",
451        doc = "processing is significantly faster than having to call the",
452        doc = "internal query engine for each individual file argument. However,",
453        doc = "the file argument syntax is strict and the command will not run",
454        doc = "if an error is encountered."
455    )]
456    ///
457    /// Transitions this command to the [`RegularMode`] state.
458    pub fn script_list_mode(self, v: bool) -> Sync<RegularMode> {
459        Sync {
460            bin: self.bin,
461            global_opts: self.global_opts,
462            mode: RegularMode {
463                script_list_mode: v,
464                ..RegularMode::default()
465            },
466        }
467    }
468
469    /// # Description
470    ///
471    /// `-K`
472    ///
473    /// Suppress keyword expansion when updating `+k` type files on the
474    /// client.
475    #[cfg_attr(feature = "lt2024_2", doc = "See File type modifiers.")]
476    #[cfg_attr(
477        not(feature = "lt2024_2"),
478        doc = "To learn more, see File type modifiers."
479    )]
480    ///
481    /// Transitions this command to the [`RegularMode`] state.
482    #[cfg(not(feature = "lt2022_1"))]
483    pub fn suppress_keyword_expansion(self, v: bool) -> Sync<RegularMode> {
484        Sync {
485            bin: self.bin,
486            global_opts: self.global_opts,
487            mode: RegularMode {
488                suppress_keyword_expansion: v,
489                ..RegularMode::default()
490            },
491        }
492    }
493
494    /// # Description
495    ///
496    /// `-q`
497    ///
498    /// Quiet operation: suppress normal output messages. Messages describing
499    /// errors or exceptional conditions are not suppressed.
500    ///
501    /// Transitions this command to the [`RegularMode`] state.
502    pub fn quiet_mode(self, v: bool) -> Sync<RegularMode> {
503        Sync {
504            bin: self.bin,
505            global_opts: self.global_opts,
506            mode: RegularMode {
507                quiet_mode: v,
508                ..RegularMode::default()
509            },
510        }
511    }
512
513    /// # Description
514    ///
515    /// `-m max`
516    ///
517    /// Sync only the first `max` files specified.
518    #[cfg_attr(
519        not(feature = "lt2022_2"),
520        doc = "",
521        doc = "This option is useful in conjunction with tagged output and the",
522        doc = "`-n` flag, to preview how many files will be synced without",
523        doc = "transferring all the file data."
524    )]
525    ///
526    /// Transitions this command to the [`RegularMode`] state.
527    pub fn limit(self, v: u64) -> Sync<RegularMode> {
528        Sync {
529            bin: self.bin,
530            global_opts: self.global_opts,
531            mode: RegularMode {
532                limit: Some(v),
533                ..RegularMode::default()
534            },
535        }
536    }
537
538    /// # Description
539    ///
540    /// `--parallel=threads=N[,batch=N][,batchsize=N][,min=N][,minsize=N]`
541    ///
542    /// Specify options for parallel file transfer.
543    ///
544    /// Transitions this command to the [`RegularMode`] state.
545    pub fn parallel(self, v: ParallelConfig) -> Sync<RegularMode> {
546        Sync {
547            bin: self.bin,
548            global_opts: self.global_opts,
549            mode: RegularMode {
550                parallel: Some(v),
551                ..RegularMode::default()
552            },
553        }
554    }
555
556    /// # Description
557    ///
558    /// `--use-stream-change[N]`
559    ///
560    /// Specify the stream specification version to use for generating the
561    /// client view for sync.
562    ///
563    /// Transitions this command to the [`RegularMode`] state.
564    #[cfg(not(feature = "lt2022_2"))]
565    pub fn stream_spec_version(self, v: StreamSpecVersion) -> Sync<RegularMode> {
566        Sync {
567            bin: self.bin,
568            global_opts: self.global_opts,
569            mode: RegularMode {
570                stream_spec_version: Some(v),
571                ..RegularMode::default()
572            },
573        }
574    }
575
576    /// `--use-stream-change` (no value): the maximum change number in the
577    /// file list determines the stream spec version.
578    ///
579    /// Transitions this command to the [`RegularMode`] state.
580    #[cfg(not(feature = "lt2022_2"))]
581    pub fn sc_max_change_number(self) -> Sync<RegularMode> {
582        self.stream_spec_version(StreamSpecVersion::MaxInFilelists)
583    }
584
585    /// `--use-stream-change=0`: use the current stream spec version.
586    ///
587    /// Transitions this command to the [`RegularMode`] state.
588    #[cfg(not(feature = "lt2022_2"))]
589    pub fn sc_current_stream_spec(self) -> Sync<RegularMode> {
590        self.stream_spec_version(StreamSpecVersion::Current)
591    }
592
593    /// `--use-stream-change=N`: use the stream spec version at or before
594    /// change `n`.
595    ///
596    /// Transitions this command to the [`RegularMode`] state.
597    #[cfg(not(feature = "lt2022_2"))]
598    pub fn sc_change_number(self, n: u32) -> Sync<RegularMode> {
599        self.stream_spec_version(StreamSpecVersion::ChangeNumber(n))
600    }
601
602    /// # Description
603    ///
604    /// `-n`
605    ///
606    #[cfg_attr(
607        feature = "lt2016_1",
608        doc = "Display the results of the sync without actually performing the",
609        doc = "sync.",
610        doc = "",
611        doc = "This lets you make sure that the sync does what you think it",
612        doc = "does before you do it."
613    )]
614    #[cfg_attr(
615        not(feature = "lt2016_1"),
616        doc = "Preview mode: display the results of the sync without actually",
617        doc = "performing the sync."
618    )]
619    ///
620    /// Transitions this command to the [`RegularMode`] state with the
621    /// [`PreviewResult`] preview mode.
622    pub fn preview_result(self) -> Sync<RegularMode<Unselected, PreviewResult>> {
623        Sync {
624            bin: self.bin,
625            global_opts: self.global_opts,
626            mode: RegularMode {
627                preview: PreviewResult,
628                ..RegularMode::default()
629            },
630        }
631    }
632
633    /// # Description
634    ///
635    /// `-N`
636    ///
637    #[cfg_attr(
638        feature = "lt2016_1",
639        doc = "Display a summary of the expected network traffic associated",
640        doc = "with a sync, without performing the sync."
641    )]
642    #[cfg_attr(
643        all(feature = "lt2021_2", not(feature = "lt2016_1")),
644        doc = "Preview mode: display a summary of the expected network traffic",
645        doc = "associated with a sync, without performing the sync."
646    )]
647    #[cfg_attr(
648        not(feature = "lt2021_2"),
649        doc = "Preview mode: display a summary of the expected network traffic",
650        doc = "associated with a sync, without performing the sync.",
651        doc = "",
652        doc = "This tells you how many files are to be added or updated, which",
653        doc = "is useful if there are many large files, limits on bandwidth, or",
654        doc = "limits on disk space."
655    )]
656    ///
657    /// Transitions this command to the [`RegularMode`] state with the
658    /// [`PreviewNetworkTraffic`] preview mode.
659    pub fn preview_network_traffic(self) -> Sync<RegularMode<Unselected, PreviewNetworkTraffic>> {
660        Sync {
661            bin: self.bin,
662            global_opts: self.global_opts,
663            mode: RegularMode {
664                preview: PreviewNetworkTraffic,
665                ..RegularMode::default()
666            },
667        }
668    }
669
670    /// # Description
671    ///
672    /// `-f`
673    ///
674    #[cfg_attr(
675        feature = "lt2014_2",
676        doc = "Force the sync. Perforce performs the sync even if the client",
677        doc = "workspace already has the file at the specified revision. If the",
678        doc = "file is writable, it is overwritten.",
679        doc = "",
680        doc = "This flag does not affect open files, but it does override the",
681        doc = "noclobber client option."
682    )]
683    #[cfg_attr(
684        all(feature = "lt2017_2", not(feature = "lt2014_2")),
685        doc = "Force the sync. Perforce performs the sync even if the client",
686        doc = "workspace already has the file at the specified revision. If the",
687        doc = "file is writable, it is overwritten.",
688        doc = "",
689        doc = "This option does not affect open files, but it does override the",
690        doc = "noclobber client option."
691    )]
692    #[cfg_attr(
693        all(feature = "lt2024_1", not(feature = "lt2017_2")),
694        doc = "Force the sync. Helix Server performs the sync even if the",
695        doc = "client workspace already has the file at the specified",
696        doc = "revision. If the file is writable, it is overwritten.",
697        doc = "",
698        doc = "This option does not affect open files, but it does override the",
699        doc = "noclobber client option (see p4 client)."
700    )]
701    #[cfg_attr(
702        all(feature = "lt2024_2", not(feature = "lt2024_1")),
703        doc = "Force the sync. Helix Core Server performs the sync even if the",
704        doc = "client workspace already has the file at the specified",
705        doc = "revision. If the file is writable, it is overwritten.",
706        doc = "",
707        doc = "This option does not affect open files, but it does override the",
708        doc = "noclobber client option (see p4 client)."
709    )]
710    #[cfg_attr(
711        not(feature = "lt2024_2"),
712        doc = "Force the sync. P4 Server performs the sync even if the client",
713        doc = "workspace already has the file at the specified revision. If the",
714        doc = "file is writable, it is overwritten.",
715        doc = "",
716        doc = "This option does not affect open files, but it does override the",
717        doc = "noclobber client option (see p4 client)."
718    )]
719    ///
720    /// Transitions this command to the [`RegularMode`] state with the
721    /// [`ForceRegularMode`] sub-mode, which prevents further transitions to
722    /// [`SafeCheckMode`] or [`PopulateMode`].
723    pub fn force(self, v: bool) -> Sync<RegularMode<ForceRegularMode>> {
724        Sync {
725            bin: self.bin,
726            global_opts: self.global_opts,
727            mode: RegularMode {
728                mode: ForceRegularMode {
729                    force: v,
730                    ..ForceRegularMode::default()
731                },
732                ..RegularMode::default()
733            },
734        }
735    }
736
737    /// # Description
738    ///
739    /// `-k`
740    ///
741    #[cfg_attr(
742        feature = "lt2022_2",
743        doc = "Keep existing workspace files; update the have list without",
744        doc = "updating the client workspace."
745    )]
746    #[cfg_attr(
747        not(feature = "lt2022_2"),
748        doc = "Update server metadata without syncing files. Keep existing",
749        doc = "workspace files and update the have list without updating the",
750        doc = "client workspace."
751    )]
752    ///
753    /// Transitions this command to the [`RegularMode`] state with the
754    /// [`ForceRegularMode`] sub-mode, which prevents further transitions to
755    /// [`SafeCheckMode`] or [`PopulateMode`].
756    pub fn metadata_only(self, v: bool) -> Sync<RegularMode<ForceRegularMode>> {
757        Sync {
758            bin: self.bin,
759            global_opts: self.global_opts,
760            mode: RegularMode {
761                mode: ForceRegularMode {
762                    metadata_only: v,
763                    ..ForceRegularMode::default()
764                },
765                ..RegularMode::default()
766            },
767        }
768    }
769
770    /// # Description
771    ///
772    /// `-r`
773    ///
774    /// Reopen files that are mapped to new locations in the depot, in the new
775    /// location.
776    ///
777    /// Transitions this command to the [`RegularMode`] state with the
778    /// [`ForceRegularMode`] sub-mode, which prevents further transitions to
779    /// [`SafeCheckMode`] or [`PopulateMode`].
780    #[cfg(not(feature = "lt2015_1"))]
781    pub fn reopen_moved_files(self, v: bool) -> Sync<RegularMode<ForceRegularMode>> {
782        Sync {
783            bin: self.bin,
784            global_opts: self.global_opts,
785            mode: RegularMode {
786                mode: ForceRegularMode {
787                    reopen_moved_files: v,
788                    ..ForceRegularMode::default()
789                },
790                ..RegularMode::default()
791            },
792        }
793    }
794}
795
796// ---- Common option accessors (available in every RegularMode sub-mode) ----
797
798impl<Mode: ExclusiveOption, P: ExclusiveOption> Sync<RegularMode<Mode, P>> {
799    /// Returns whether edge replication is verified (`-E`).
800    #[cfg(not(feature = "lt2022_2"))]
801    pub fn get_verify_edge_replication(&self) -> bool {
802        self.mode.verify_edge_replication
803    }
804
805    /// Sets whether edge replication is verified (`-E`).
806    #[cfg(not(feature = "lt2022_2"))]
807    pub fn set_verify_edge_replication(&mut self, v: bool) -> &mut Self {
808        self.mode.verify_edge_replication = v;
809        self
810    }
811
812    /// Sets whether edge replication is verified (`-E`).
813    #[cfg(not(feature = "lt2022_2"))]
814    pub fn verify_edge_replication(mut self, v: bool) -> Self {
815        self.mode.verify_edge_replication = v;
816        self
817    }
818
819    /// Returns whether script list mode is enabled (`-L`).
820    pub fn get_script_list_mode(&self) -> bool {
821        self.mode.script_list_mode
822    }
823
824    /// Sets whether script list mode is enabled (`-L`).
825    pub fn set_script_list_mode(&mut self, v: bool) -> &mut Self {
826        self.mode.script_list_mode = v;
827        self
828    }
829
830    /// Sets whether script list mode is enabled (`-L`).
831    pub fn script_list_mode(mut self, v: bool) -> Self {
832        self.mode.script_list_mode = v;
833        self
834    }
835
836    /// Returns whether keyword expansion is suppressed (`-K`).
837    #[cfg(not(feature = "lt2022_1"))]
838    pub fn get_suppress_keyword_expansion(&self) -> bool {
839        self.mode.suppress_keyword_expansion
840    }
841
842    /// Sets whether keyword expansion is suppressed (`-K`).
843    #[cfg(not(feature = "lt2022_1"))]
844    pub fn set_suppress_keyword_expansion(&mut self, v: bool) -> &mut Self {
845        self.mode.suppress_keyword_expansion = v;
846        self
847    }
848
849    /// Sets whether keyword expansion is suppressed (`-K`).
850    #[cfg(not(feature = "lt2022_1"))]
851    pub fn suppress_keyword_expansion(mut self, v: bool) -> Self {
852        self.mode.suppress_keyword_expansion = v;
853        self
854    }
855
856    /// Returns whether quiet mode is enabled (`-q`).
857    pub fn get_quiet_mode(&self) -> bool {
858        self.mode.quiet_mode
859    }
860
861    /// Sets whether quiet mode is enabled (`-q`).
862    pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
863        self.mode.quiet_mode = v;
864        self
865    }
866
867    /// Sets whether quiet mode is enabled (`-q`).
868    pub fn quiet_mode(mut self, v: bool) -> Self {
869        self.mode.quiet_mode = v;
870        self
871    }
872
873    /// Returns the maximum number of files to sync (`-m max`).
874    pub fn get_limit(&self) -> Option<u64> {
875        self.mode.limit
876    }
877
878    /// Sets the maximum number of files to sync (`-m max`).
879    pub fn set_limit(&mut self, v: u64) -> &mut Self {
880        self.mode.limit = Some(v);
881        self
882    }
883
884    /// Sets the maximum number of files to sync (`-m max`).
885    pub fn limit(mut self, v: u64) -> Self {
886        self.mode.limit = Some(v);
887        self
888    }
889
890    /// Returns the parallel sync configuration (`--parallel`).
891    pub fn get_parallel(&self) -> Option<&ParallelConfig> {
892        self.mode.parallel.as_ref()
893    }
894
895    /// Sets the parallel sync configuration (`--parallel`).
896    pub fn set_parallel(&mut self, v: ParallelConfig) -> &mut Self {
897        self.mode.parallel = Some(v);
898        self
899    }
900
901    /// Sets the parallel sync configuration (`--parallel`).
902    pub fn parallel(mut self, v: ParallelConfig) -> Self {
903        self.mode.parallel = Some(v);
904        self
905    }
906
907    /// Returns the stream spec version (`--use-stream-change`).
908    #[cfg(not(feature = "lt2022_2"))]
909    pub fn get_stream_spec_version(&self) -> Option<StreamSpecVersion> {
910        self.mode.stream_spec_version
911    }
912
913    /// Sets the stream spec version (`--use-stream-change`).
914    #[cfg(not(feature = "lt2022_2"))]
915    pub fn set_stream_spec_version(&mut self, v: StreamSpecVersion) -> &mut Self {
916        self.mode.stream_spec_version = Some(v);
917        self
918    }
919
920    /// Sets the stream spec version (`--use-stream-change`).
921    #[cfg(not(feature = "lt2022_2"))]
922    pub fn stream_spec_version(mut self, v: StreamSpecVersion) -> Self {
923        self.mode.stream_spec_version = Some(v);
924        self
925    }
926
927    /// `--use-stream-change` (no value): the maximum change number in the
928    /// file list determines the stream spec version.
929    #[cfg(not(feature = "lt2022_2"))]
930    pub fn set_sc_max_change_number(&mut self) -> &mut Self {
931        self.mode.stream_spec_version = Some(StreamSpecVersion::MaxInFilelists);
932        self
933    }
934
935    /// `--use-stream-change` (no value): the maximum change number in the
936    /// file list determines the stream spec version.
937    #[cfg(not(feature = "lt2022_2"))]
938    pub fn sc_max_change_number(mut self) -> Self {
939        self.mode.stream_spec_version = Some(StreamSpecVersion::MaxInFilelists);
940        self
941    }
942
943    /// `--use-stream-change=0`: use the current stream spec version.
944    #[cfg(not(feature = "lt2022_2"))]
945    pub fn set_sc_current_stream_spec(&mut self) -> &mut Self {
946        self.mode.stream_spec_version = Some(StreamSpecVersion::Current);
947        self
948    }
949
950    /// `--use-stream-change=0`: use the current stream spec version.
951    #[cfg(not(feature = "lt2022_2"))]
952    pub fn sc_current_stream_spec(mut self) -> Self {
953        self.mode.stream_spec_version = Some(StreamSpecVersion::Current);
954        self
955    }
956
957    /// `--use-stream-change=N`: use the stream spec version at or before
958    /// change `n`.
959    #[cfg(not(feature = "lt2022_2"))]
960    pub fn set_sc_change_number(&mut self, n: u32) -> &mut Self {
961        self.mode.stream_spec_version = Some(StreamSpecVersion::ChangeNumber(n));
962        self
963    }
964
965    /// `--use-stream-change=N`: use the stream spec version at or before
966    /// change `n`.
967    #[cfg(not(feature = "lt2022_2"))]
968    pub fn sc_change_number(mut self, n: u32) -> Self {
969        self.mode.stream_spec_version = Some(StreamSpecVersion::ChangeNumber(n));
970        self
971    }
972}
973
974// ---- Sub-mode transitions (only from the Unselected sub-mode) ----
975
976impl<P: ExclusiveOption> Sync<RegularMode<Unselected, P>> {
977    /// # Description
978    ///
979    /// `-f`
980    ///
981    #[cfg_attr(
982        feature = "lt2014_2",
983        doc = "Force the sync. Perforce performs the sync even if the client",
984        doc = "workspace already has the file at the specified revision. If the",
985        doc = "file is writable, it is overwritten.",
986        doc = "",
987        doc = "This flag does not affect open files, but it does override the",
988        doc = "noclobber client option."
989    )]
990    #[cfg_attr(
991        all(feature = "lt2017_2", not(feature = "lt2014_2")),
992        doc = "Force the sync. Perforce performs the sync even if the client",
993        doc = "workspace already has the file at the specified revision. If the",
994        doc = "file is writable, it is overwritten.",
995        doc = "",
996        doc = "This option does not affect open files, but it does override the",
997        doc = "noclobber client option."
998    )]
999    #[cfg_attr(
1000        all(feature = "lt2024_1", not(feature = "lt2017_2")),
1001        doc = "Force the sync. Helix Server performs the sync even if the",
1002        doc = "client workspace already has the file at the specified",
1003        doc = "revision. If the file is writable, it is overwritten.",
1004        doc = "",
1005        doc = "This option does not affect open files, but it does override the",
1006        doc = "noclobber client option (see p4 client)."
1007    )]
1008    #[cfg_attr(
1009        all(feature = "lt2024_2", not(feature = "lt2024_1")),
1010        doc = "Force the sync. Helix Core Server performs the sync even if the",
1011        doc = "client workspace already has the file at the specified",
1012        doc = "revision. If the file is writable, it is overwritten.",
1013        doc = "",
1014        doc = "This option does not affect open files, but it does override the",
1015        doc = "noclobber client option (see p4 client)."
1016    )]
1017    #[cfg_attr(
1018        not(feature = "lt2024_2"),
1019        doc = "Force the sync. P4 Server performs the sync even if the client",
1020        doc = "workspace already has the file at the specified revision. If the",
1021        doc = "file is writable, it is overwritten.",
1022        doc = "",
1023        doc = "This option does not affect open files, but it does override the",
1024        doc = "noclobber client option (see p4 client)."
1025    )]
1026    ///
1027    /// Transitions the sub-mode to [`ForceRegularMode`], which prevents
1028    /// further transitions to [`SafeCheckMode`] or [`PopulateMode`].
1029    pub fn force(self, v: bool) -> Sync<RegularMode<ForceRegularMode, P>> {
1030        Sync {
1031            bin: self.bin,
1032            global_opts: self.global_opts,
1033            mode: RegularMode {
1034                #[cfg(not(feature = "lt2022_2"))]
1035                verify_edge_replication: self.mode.verify_edge_replication,
1036                script_list_mode: self.mode.script_list_mode,
1037                #[cfg(not(feature = "lt2022_1"))]
1038                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1039                quiet_mode: self.mode.quiet_mode,
1040                limit: self.mode.limit,
1041                parallel: self.mode.parallel,
1042                #[cfg(not(feature = "lt2022_2"))]
1043                stream_spec_version: self.mode.stream_spec_version,
1044                mode: ForceRegularMode {
1045                    force: v,
1046                    ..ForceRegularMode::default()
1047                },
1048                preview: self.mode.preview,
1049            },
1050        }
1051    }
1052
1053    /// # Description
1054    ///
1055    /// `-k`
1056    ///
1057    #[cfg_attr(
1058        feature = "lt2022_2",
1059        doc = "Keep existing workspace files; update the have list without",
1060        doc = "updating the client workspace."
1061    )]
1062    #[cfg_attr(
1063        not(feature = "lt2022_2"),
1064        doc = "Update server metadata without syncing files. Keep existing",
1065        doc = "workspace files and update the have list without updating the",
1066        doc = "client workspace."
1067    )]
1068    ///
1069    /// Transitions the sub-mode to [`ForceRegularMode`], which prevents
1070    /// further transitions to [`SafeCheckMode`] or [`PopulateMode`].
1071    pub fn metadata_only(self, v: bool) -> Sync<RegularMode<ForceRegularMode, P>> {
1072        Sync {
1073            bin: self.bin,
1074            global_opts: self.global_opts,
1075            mode: RegularMode {
1076                #[cfg(not(feature = "lt2022_2"))]
1077                verify_edge_replication: self.mode.verify_edge_replication,
1078                script_list_mode: self.mode.script_list_mode,
1079                #[cfg(not(feature = "lt2022_1"))]
1080                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1081                quiet_mode: self.mode.quiet_mode,
1082                limit: self.mode.limit,
1083                parallel: self.mode.parallel,
1084                #[cfg(not(feature = "lt2022_2"))]
1085                stream_spec_version: self.mode.stream_spec_version,
1086                mode: ForceRegularMode {
1087                    metadata_only: v,
1088                    ..ForceRegularMode::default()
1089                },
1090                preview: self.mode.preview,
1091            },
1092        }
1093    }
1094
1095    /// # Description
1096    ///
1097    /// `-r`
1098    ///
1099    /// Reopen files that are mapped to new locations in the depot, in the new
1100    /// location.
1101    ///
1102    /// Transitions the sub-mode to [`ForceRegularMode`], which prevents
1103    /// further transitions to [`SafeCheckMode`] or [`PopulateMode`].
1104    #[cfg(not(feature = "lt2015_1"))]
1105    pub fn reopen_moved_files(self, v: bool) -> Sync<RegularMode<ForceRegularMode, P>> {
1106        Sync {
1107            bin: self.bin,
1108            global_opts: self.global_opts,
1109            mode: RegularMode {
1110                #[cfg(not(feature = "lt2022_2"))]
1111                verify_edge_replication: self.mode.verify_edge_replication,
1112                script_list_mode: self.mode.script_list_mode,
1113                #[cfg(not(feature = "lt2022_1"))]
1114                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1115                quiet_mode: self.mode.quiet_mode,
1116                limit: self.mode.limit,
1117                parallel: self.mode.parallel,
1118                #[cfg(not(feature = "lt2022_2"))]
1119                stream_spec_version: self.mode.stream_spec_version,
1120                mode: ForceRegularMode {
1121                    reopen_moved_files: v,
1122                    ..ForceRegularMode::default()
1123                },
1124                preview: self.mode.preview,
1125            },
1126        }
1127    }
1128
1129    /// # Description
1130    ///
1131    /// `-s`
1132    ///
1133    /// Safe sync: compare the content in the client workspace against what
1134    /// was last synced.
1135    #[cfg_attr(
1136        feature = "lt2017_2",
1137        doc = "If the file was modified outside of Perforce control, an error",
1138        doc = "message is displayed and the file is not overwritten."
1139    )]
1140    #[cfg_attr(
1141        all(feature = "lt2024_1", not(feature = "lt2017_2")),
1142        doc = "If the file was modified outside of the control of Helix",
1143        doc = "Server, an error message is displayed and the file is not",
1144        doc = "overwritten."
1145    )]
1146    #[cfg_attr(
1147        all(feature = "lt2024_2", not(feature = "lt2024_1")),
1148        doc = "If the file was modified outside of the control of Helix Core",
1149        doc = "Server, an error message is displayed and the file is not",
1150        doc = "overwritten."
1151    )]
1152    #[cfg_attr(
1153        not(feature = "lt2024_2"),
1154        doc = "If the file was modified outside of the control of P4 Server,",
1155        doc = "an error message is displayed and the file is not overwritten."
1156    )]
1157    ///
1158    /// Transitions the sub-mode to [`SafeCheckMode`].
1159    pub fn safe_check(self) -> Sync<RegularMode<SafeCheckMode, P>> {
1160        Sync {
1161            bin: self.bin,
1162            global_opts: self.global_opts,
1163            mode: RegularMode {
1164                #[cfg(not(feature = "lt2022_2"))]
1165                verify_edge_replication: self.mode.verify_edge_replication,
1166                script_list_mode: self.mode.script_list_mode,
1167                #[cfg(not(feature = "lt2022_1"))]
1168                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1169                quiet_mode: self.mode.quiet_mode,
1170                limit: self.mode.limit,
1171                parallel: self.mode.parallel,
1172                #[cfg(not(feature = "lt2022_2"))]
1173                stream_spec_version: self.mode.stream_spec_version,
1174                mode: SafeCheckMode,
1175                preview: self.mode.preview,
1176            },
1177        }
1178    }
1179
1180    /// # Description
1181    ///
1182    /// `-p`
1183    ///
1184    /// Populate a client workspace, but do not update the have list. Any
1185    /// file that is already synced or opened is bypassed with a warning
1186    /// message.
1187    ///
1188    /// Transitions the sub-mode to [`PopulateMode`].
1189    pub fn populate(self) -> Sync<RegularMode<PopulateMode, P>> {
1190        Sync {
1191            bin: self.bin,
1192            global_opts: self.global_opts,
1193            mode: RegularMode {
1194                #[cfg(not(feature = "lt2022_2"))]
1195                verify_edge_replication: self.mode.verify_edge_replication,
1196                script_list_mode: self.mode.script_list_mode,
1197                #[cfg(not(feature = "lt2022_1"))]
1198                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1199                quiet_mode: self.mode.quiet_mode,
1200                limit: self.mode.limit,
1201                parallel: self.mode.parallel,
1202                #[cfg(not(feature = "lt2022_2"))]
1203                stream_spec_version: self.mode.stream_spec_version,
1204                mode: PopulateMode,
1205                preview: self.mode.preview,
1206            },
1207        }
1208    }
1209}
1210
1211// ---- Preview transitions (only when preview is Unselected) ----
1212
1213impl<Mode: ExclusiveOption> Sync<RegularMode<Mode, Unselected>> {
1214    /// # Description
1215    ///
1216    /// `-n`
1217    ///
1218    #[cfg_attr(
1219        feature = "lt2016_1",
1220        doc = "Display the results of the sync without actually performing the",
1221        doc = "sync.",
1222        doc = "",
1223        doc = "This lets you make sure that the sync does what you think it",
1224        doc = "does before you do it."
1225    )]
1226    #[cfg_attr(
1227        not(feature = "lt2016_1"),
1228        doc = "Preview mode: display the results of the sync without actually",
1229        doc = "performing the sync."
1230    )]
1231    ///
1232    /// Transitions the preview mode to [`PreviewResult`].
1233    pub fn preview_result(self) -> Sync<RegularMode<Mode, PreviewResult>> {
1234        Sync {
1235            bin: self.bin,
1236            global_opts: self.global_opts,
1237            mode: RegularMode {
1238                #[cfg(not(feature = "lt2022_2"))]
1239                verify_edge_replication: self.mode.verify_edge_replication,
1240                script_list_mode: self.mode.script_list_mode,
1241                #[cfg(not(feature = "lt2022_1"))]
1242                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1243                quiet_mode: self.mode.quiet_mode,
1244                limit: self.mode.limit,
1245                parallel: self.mode.parallel,
1246                #[cfg(not(feature = "lt2022_2"))]
1247                stream_spec_version: self.mode.stream_spec_version,
1248                mode: self.mode.mode,
1249                preview: PreviewResult,
1250            },
1251        }
1252    }
1253
1254    /// # Description
1255    ///
1256    /// `-N`
1257    ///
1258    #[cfg_attr(
1259        feature = "lt2016_1",
1260        doc = "Display a summary of the expected network traffic associated",
1261        doc = "with a sync, without performing the sync."
1262    )]
1263    #[cfg_attr(
1264        all(feature = "lt2021_2", not(feature = "lt2016_1")),
1265        doc = "Preview mode: display a summary of the expected network traffic",
1266        doc = "associated with a sync, without performing the sync."
1267    )]
1268    #[cfg_attr(
1269        not(feature = "lt2021_2"),
1270        doc = "Preview mode: display a summary of the expected network traffic",
1271        doc = "associated with a sync, without performing the sync.",
1272        doc = "",
1273        doc = "This tells you how many files are to be added or updated, which",
1274        doc = "is useful if there are many large files, limits on bandwidth, or",
1275        doc = "limits on disk space."
1276    )]
1277    ///
1278    /// Transitions the preview mode to [`PreviewNetworkTraffic`].
1279    pub fn preview_network_traffic(self) -> Sync<RegularMode<Mode, PreviewNetworkTraffic>> {
1280        Sync {
1281            bin: self.bin,
1282            global_opts: self.global_opts,
1283            mode: RegularMode {
1284                #[cfg(not(feature = "lt2022_2"))]
1285                verify_edge_replication: self.mode.verify_edge_replication,
1286                script_list_mode: self.mode.script_list_mode,
1287                #[cfg(not(feature = "lt2022_1"))]
1288                suppress_keyword_expansion: self.mode.suppress_keyword_expansion,
1289                quiet_mode: self.mode.quiet_mode,
1290                limit: self.mode.limit,
1291                parallel: self.mode.parallel,
1292                #[cfg(not(feature = "lt2022_2"))]
1293                stream_spec_version: self.mode.stream_spec_version,
1294                mode: self.mode.mode,
1295                preview: PreviewNetworkTraffic,
1296            },
1297        }
1298    }
1299}
1300
1301// ---- Force-only option accessors (only in ForceRegularMode sub-mode) ----
1302
1303impl<P: ExclusiveOption> Sync<RegularMode<ForceRegularMode, P>> {
1304    /// Returns whether the sync is forced (`-f`).
1305    pub fn get_force(&self) -> bool {
1306        self.mode.mode.force
1307    }
1308
1309    /// Sets whether the sync is forced (`-f`).
1310    pub fn set_force(&mut self, v: bool) -> &mut Self {
1311        self.mode.mode.force = v;
1312        self
1313    }
1314
1315    /// Sets whether the sync is forced (`-f`).
1316    pub fn force(mut self, v: bool) -> Self {
1317        self.mode.mode.force = v;
1318        self
1319    }
1320
1321    /// Returns whether only metadata is updated (`-k`).
1322    pub fn get_metadata_only(&self) -> bool {
1323        self.mode.mode.metadata_only
1324    }
1325
1326    /// Sets whether only metadata is updated (`-k`).
1327    pub fn set_metadata_only(&mut self, v: bool) -> &mut Self {
1328        self.mode.mode.metadata_only = v;
1329        self
1330    }
1331
1332    /// Sets whether only metadata is updated (`-k`).
1333    pub fn metadata_only(mut self, v: bool) -> Self {
1334        self.mode.mode.metadata_only = v;
1335        self
1336    }
1337
1338    /// Returns whether moved files are reopened (`-r`).
1339    #[cfg(not(feature = "lt2015_1"))]
1340    pub fn get_reopen_moved_files(&self) -> bool {
1341        self.mode.mode.reopen_moved_files
1342    }
1343
1344    /// Sets whether moved files are reopened (`-r`).
1345    #[cfg(not(feature = "lt2015_1"))]
1346    pub fn set_reopen_moved_files(&mut self, v: bool) -> &mut Self {
1347        self.mode.mode.reopen_moved_files = v;
1348        self
1349    }
1350
1351    /// Sets whether moved files are reopened (`-r`).
1352    #[cfg(not(feature = "lt2015_1"))]
1353    pub fn reopen_moved_files(mut self, v: bool) -> Self {
1354        self.mode.mode.reopen_moved_files = v;
1355        self
1356    }
1357}
1358
1359// ---- SyncTimeMode accessors ----
1360
1361#[cfg(not(feature = "lt2025_1"))]
1362impl Sync<SyncTimeMode> {
1363    /// Returns the sync time value (`--sync-time=N`).
1364    pub fn get_sync_time(&self) -> &str {
1365        &self.mode.sync_time
1366    }
1367
1368    /// Sets the sync time value (`--sync-time=N`). The value can be Unix
1369    /// epoch time or the Perforce date time format.
1370    pub fn set_sync_time(&mut self, v: impl Into<String>) -> &mut Self {
1371        self.mode.sync_time = v.into();
1372        self
1373    }
1374
1375    /// Sets the sync time value (`--sync-time=N`). The value can be Unix
1376    /// epoch time or the Perforce date time format.
1377    pub fn sync_time(mut self, v: impl Into<String>) -> Self {
1378        self.mode.sync_time = v.into();
1379        self
1380    }
1381}
1382
1383// ---- Shared: executors + global opts ----
1384
1385impl<M: ExclusiveOption, S, I> ParameterizedSpawn<(S,)> for Sync<M>
1386where
1387    S: IntoIterator<Item = I>,
1388    I: AsRef<OsStr>,
1389{
1390    type Output = Child;
1391    type Error = std::io::Error;
1392
1393    /// Spawns `p4 sync` for the given files as a child process with piped
1394    /// standard output and error streams; use the returned [`Child`] handle
1395    /// to wait for it or interact with it.
1396    fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
1397        self.setup_command(&self.bin)
1398            .args(files)
1399            .stdout(Stdio::piped())
1400            .stderr(Stdio::piped())
1401            .spawn()
1402    }
1403}
1404
1405impl<M: ExclusiveOption> Sync<M> {
1406    /// # Description
1407    ///
1408    /// g-opts
1409    ///
1410    #[cfg_attr(
1411        feature = "lt2014_2",
1412        doc = "See the [Global Options](GlobalOpts) section."
1413    )]
1414    #[cfg_attr(
1415        all(feature = "lt2015_1", not(feature = "lt2014_2")),
1416        doc = "See the [“Global Options”](GlobalOpts) section."
1417    )]
1418    #[cfg_attr(
1419        all(feature = "lt2017_1", not(feature = "lt2015_1")),
1420        doc = "See [“Global Options”](GlobalOpts)."
1421    )]
1422    #[cfg_attr(
1423        all(feature = "lt2018_2", not(feature = "lt2017_1")),
1424        doc = "See [Global Options](GlobalOpts)."
1425    )]
1426    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
1427    pub fn get_global_opts(&self) -> &GlobalOpts {
1428        &self.global_opts
1429    }
1430
1431    /// # Description
1432    ///
1433    /// g-opts
1434    ///
1435    #[cfg_attr(
1436        feature = "lt2014_2",
1437        doc = "See the [Global Options](GlobalOpts) section."
1438    )]
1439    #[cfg_attr(
1440        all(feature = "lt2015_1", not(feature = "lt2014_2")),
1441        doc = "See the [“Global Options”](GlobalOpts) section."
1442    )]
1443    #[cfg_attr(
1444        all(feature = "lt2017_1", not(feature = "lt2015_1")),
1445        doc = "See [“Global Options”](GlobalOpts)."
1446    )]
1447    #[cfg_attr(
1448        all(feature = "lt2018_2", not(feature = "lt2017_1")),
1449        doc = "See [Global Options](GlobalOpts)."
1450    )]
1451    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
1452    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
1453        self.global_opts = v;
1454        self
1455    }
1456
1457    /// # Description
1458    ///
1459    /// g-opts
1460    ///
1461    #[cfg_attr(
1462        feature = "lt2014_2",
1463        doc = "See the [Global Options](GlobalOpts) section."
1464    )]
1465    #[cfg_attr(
1466        all(feature = "lt2015_1", not(feature = "lt2014_2")),
1467        doc = "See the [“Global Options”](GlobalOpts) section."
1468    )]
1469    #[cfg_attr(
1470        all(feature = "lt2017_1", not(feature = "lt2015_1")),
1471        doc = "See [“Global Options”](GlobalOpts)."
1472    )]
1473    #[cfg_attr(
1474        all(feature = "lt2018_2", not(feature = "lt2017_1")),
1475        doc = "See [Global Options](GlobalOpts)."
1476    )]
1477    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
1478    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
1479        self.global_opts = v;
1480        self
1481    }
1482}
1483
1484impl<M: ExclusiveOption> SubCommand for Sync<M> {
1485    fn name(&self) -> &str {
1486        "sync"
1487    }
1488
1489    fn inject_local_args(&self, command: &mut Command) {
1490        self.mode.inject_args(command);
1491    }
1492
1493    fn global_opts(&self) -> Option<&GlobalOpts> {
1494        Some(&self.global_opts)
1495    }
1496}
1497
1498#[cfg(test)]
1499mod tests {
1500    use super::*;
1501    use crate::cmd::args_of;
1502
1503    #[test]
1504    fn without_options() {
1505        let sync = Sync::new("p4", GlobalOpts::new());
1506
1507        assert_eq!(args_of(&sync.setup_command("p4")), ["sync"]);
1508    }
1509
1510    #[test]
1511    #[cfg(not(feature = "lt2025_1"))]
1512    fn sync_time_mode() {
1513        let sync = Sync::new("p4", GlobalOpts::new()).sync_time("2024/01/01");
1514
1515        assert_eq!(
1516            args_of(&sync.setup_command("p4")),
1517            ["sync", "-k", "--sync-time=2024/01/01"]
1518        );
1519    }
1520
1521    #[test]
1522    #[cfg(not(feature = "lt2025_1"))]
1523    fn sync_time_mode_epoch() {
1524        let sync = Sync::new("p4", GlobalOpts::new()).sync_time("1700000000");
1525
1526        assert_eq!(
1527            args_of(&sync.setup_command("p4")),
1528            ["sync", "-k", "--sync-time=1700000000"]
1529        );
1530    }
1531
1532    #[test]
1533    #[cfg(not(feature = "lt2025_1"))]
1534    fn sync_time_set_style() {
1535        let mut sync = Sync::new("p4", GlobalOpts::new()).sync_time("2024/01/01");
1536        sync.set_sync_time("2024/06/01");
1537
1538        assert_eq!(sync.get_sync_time(), "2024/06/01");
1539        assert_eq!(
1540            args_of(&sync.setup_command("p4")),
1541            ["sync", "-k", "--sync-time=2024/06/01"]
1542        );
1543    }
1544
1545    #[test]
1546    fn regular_mode_common_options() {
1547        let sync = Sync::new("p4", GlobalOpts::new())
1548            .script_list_mode(true)
1549            .quiet_mode(true)
1550            .limit(5);
1551
1552        #[cfg(not(feature = "lt2022_2"))]
1553        {
1554            let sync = sync.verify_edge_replication(true);
1555            assert_eq!(
1556                args_of(&sync.setup_command("p4")),
1557                ["sync", "-E", "-L", "-q", "-m", "5"]
1558            );
1559        }
1560
1561        #[cfg(feature = "lt2022_2")]
1562        assert_eq!(
1563            args_of(&sync.setup_command("p4")),
1564            ["sync", "-L", "-q", "-m", "5"]
1565        );
1566    }
1567
1568    #[test]
1569    fn regular_mode_force_options() {
1570        let sync = Sync::new("p4", GlobalOpts::new())
1571            .force(true)
1572            .metadata_only(true);
1573
1574        #[cfg(not(feature = "lt2015_1"))]
1575        let sync = sync.reopen_moved_files(true);
1576
1577        #[cfg(not(feature = "lt2015_1"))]
1578        let expected = vec!["sync", "-f", "-k", "-r"];
1579        #[cfg(feature = "lt2015_1")]
1580        let expected = vec!["sync", "-f", "-k"];
1581
1582        assert_eq!(args_of(&sync.setup_command("p4")), expected);
1583    }
1584
1585    #[test]
1586    fn regular_mode_combined() {
1587        let sync = Sync::new("p4", GlobalOpts::new())
1588            .quiet_mode(true)
1589            .force(true)
1590            .limit(10);
1591
1592        assert_eq!(
1593            args_of(&sync.setup_command("p4")),
1594            ["sync", "-q", "-f", "-m", "10"]
1595        );
1596    }
1597
1598    #[test]
1599    fn regular_mode_preview_result() {
1600        let sync = Sync::new("p4", GlobalOpts::new()).preview_result();
1601
1602        assert_eq!(args_of(&sync.setup_command("p4")), ["sync", "-n"]);
1603    }
1604
1605    #[test]
1606    fn regular_mode_preview_network_traffic() {
1607        let sync = Sync::new("p4", GlobalOpts::new()).preview_network_traffic();
1608
1609        assert_eq!(args_of(&sync.setup_command("p4")), ["sync", "-N"]);
1610    }
1611
1612    #[test]
1613    fn regular_mode_preview_with_options() {
1614        let sync = Sync::new("p4", GlobalOpts::new())
1615            .quiet_mode(true)
1616            .preview_result()
1617            .limit(3);
1618
1619        assert_eq!(
1620            args_of(&sync.setup_command("p4")),
1621            ["sync", "-q", "-n", "-m", "3"]
1622        );
1623    }
1624
1625    #[test]
1626    fn regular_mode_parallel() {
1627        let sync = Sync::new("p4", GlobalOpts::new()).parallel(ParallelConfig {
1628            threads: 4,
1629            batch_files: Some(8),
1630            batch_size_bytes: None,
1631            min_files: Some(9),
1632            min_size_bytes: None,
1633        });
1634
1635        assert_eq!(
1636            args_of(&sync.setup_command("p4")),
1637            ["sync", "--parallel=threads=4,batch=8,min=9"]
1638        );
1639    }
1640
1641    #[test]
1642    #[cfg(not(feature = "lt2022_2"))]
1643    fn regular_mode_stream_spec_auto() {
1644        let sync = Sync::new("p4", GlobalOpts::new())
1645            .stream_spec_version(StreamSpecVersion::MaxInFilelists);
1646
1647        assert_eq!(
1648            args_of(&sync.setup_command("p4")),
1649            ["sync", "--use-stream-change"]
1650        );
1651    }
1652
1653    #[test]
1654    #[cfg(not(feature = "lt2022_2"))]
1655    fn regular_mode_stream_spec_current() {
1656        let sync =
1657            Sync::new("p4", GlobalOpts::new()).stream_spec_version(StreamSpecVersion::Current);
1658
1659        assert_eq!(
1660            args_of(&sync.setup_command("p4")),
1661            ["sync", "--use-stream-change=0"]
1662        );
1663    }
1664
1665    #[test]
1666    #[cfg(not(feature = "lt2022_2"))]
1667    fn regular_mode_stream_spec_specific() {
1668        let sync = Sync::new("p4", GlobalOpts::new())
1669            .stream_spec_version(StreamSpecVersion::ChangeNumber(123));
1670
1671        assert_eq!(
1672            args_of(&sync.setup_command("p4")),
1673            ["sync", "--use-stream-change=123"]
1674        );
1675    }
1676
1677    #[test]
1678    fn safe_check_mode() {
1679        let sync = Sync::new("p4", GlobalOpts::new()).enable_safe_check();
1680
1681        assert_eq!(args_of(&sync.setup_command("p4")), ["sync", "-s"]);
1682    }
1683
1684    #[test]
1685    fn safe_check_mode_with_options() {
1686        let sync = Sync::new("p4", GlobalOpts::new())
1687            .enable_safe_check()
1688            .quiet_mode(true)
1689            .limit(5);
1690
1691        assert_eq!(
1692            args_of(&sync.setup_command("p4")),
1693            ["sync", "-q", "-s", "-m", "5"]
1694        );
1695    }
1696
1697    #[test]
1698    fn populate_mode() {
1699        let sync = Sync::new("p4", GlobalOpts::new()).populate_client_workspace();
1700
1701        assert_eq!(args_of(&sync.setup_command("p4")), ["sync", "-p"]);
1702    }
1703
1704    #[test]
1705    fn populate_mode_with_options() {
1706        let sync = Sync::new("p4", GlobalOpts::new())
1707            .populate_client_workspace()
1708            .quiet_mode(true)
1709            .limit(5);
1710
1711        assert_eq!(
1712            args_of(&sync.setup_command("p4")),
1713            ["sync", "-q", "-p", "-m", "5"]
1714        );
1715    }
1716
1717    #[test]
1718    fn transition_to_safe_check_from_regular() {
1719        let sync = Sync::new("p4", GlobalOpts::new())
1720            .quiet_mode(true)
1721            .limit(5)
1722            .safe_check();
1723
1724        assert_eq!(
1725            args_of(&sync.setup_command("p4")),
1726            ["sync", "-q", "-s", "-m", "5"]
1727        );
1728    }
1729
1730    #[test]
1731    fn transition_to_populate_from_regular() {
1732        let sync = Sync::new("p4", GlobalOpts::new())
1733            .quiet_mode(true)
1734            .limit(5)
1735            .populate();
1736
1737        assert_eq!(
1738            args_of(&sync.setup_command("p4")),
1739            ["sync", "-q", "-p", "-m", "5"]
1740        );
1741    }
1742
1743    #[test]
1744    fn transition_preserves_preview() {
1745        let sync = Sync::new("p4", GlobalOpts::new())
1746            .preview_result()
1747            .quiet_mode(true)
1748            .safe_check();
1749
1750        assert_eq!(
1751            args_of(&sync.setup_command("p4")),
1752            ["sync", "-q", "-s", "-n"]
1753        );
1754    }
1755
1756    #[test]
1757    fn force_mode_blocks_safe_check() {
1758        // Once in ForceRegularMode, safe_check/populate are unavailable at
1759        // compile time. This test just exercises force mode injection.
1760        let sync = Sync::new("p4", GlobalOpts::new())
1761            .force(true)
1762            .quiet_mode(true);
1763
1764        assert_eq!(args_of(&sync.setup_command("p4")), ["sync", "-q", "-f"]);
1765    }
1766
1767    #[test]
1768    fn all_regular_options_order() {
1769        let sync = Sync::new("p4", GlobalOpts::new())
1770            .script_list_mode(true)
1771            .quiet_mode(true)
1772            .force(true)
1773            .metadata_only(true)
1774            .limit(5)
1775            .parallel(ParallelConfig {
1776                threads: 2,
1777                batch_files: None,
1778                batch_size_bytes: None,
1779                min_files: None,
1780                min_size_bytes: None,
1781            });
1782
1783        #[cfg(not(feature = "lt2022_2"))]
1784        let sync = sync.verify_edge_replication(true);
1785        #[cfg(not(feature = "lt2022_1"))]
1786        let sync = sync.suppress_keyword_expansion(true);
1787        #[cfg(not(feature = "lt2015_1"))]
1788        let sync = sync.reopen_moved_files(true);
1789        #[cfg(not(feature = "lt2022_2"))]
1790        let sync = sync.stream_spec_version(StreamSpecVersion::Current);
1791
1792        let mut expected: Vec<&str> = vec!["sync", "-L", "-q", "-f", "-k"];
1793        #[cfg(not(feature = "lt2022_2"))]
1794        expected.insert(1, "-E");
1795        #[cfg(not(feature = "lt2022_1"))]
1796        {
1797            let k = expected.iter().position(|&a| a == "-L").unwrap() + 1;
1798            expected.insert(k, "-K");
1799        }
1800        #[cfg(not(feature = "lt2015_1"))]
1801        expected.push("-r");
1802        expected.extend(["-m", "5", "--parallel=threads=2"]);
1803        #[cfg(not(feature = "lt2022_2"))]
1804        expected.push("--use-stream-change=0");
1805
1806        assert_eq!(args_of(&sync.setup_command("p4")), expected);
1807    }
1808}