Skip to main content

perforce_cli/cmd/
changes.rs

1use std::{
2    ffi::OsStr,
3    path::PathBuf,
4    process::{Child, Stdio},
5};
6
7use super::{ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12/// `-l`: full text of each changelist description.
13#[derive(Debug, Clone, Copy, Default)]
14pub struct FullDescription;
15
16impl ExclusiveOption for FullDescription {
17    fn inject_args(&self, command: &mut std::process::Command) {
18        command.arg("-l");
19    }
20}
21
22/// `-L`: full text truncated at 250 characters.
23#[derive(Debug, Clone, Copy, Default)]
24pub struct TruncatedDescription;
25
26impl ExclusiveOption for TruncatedDescription {
27    fn inject_args(&self, command: &mut std::process::Command) {
28        command.arg("-L");
29    }
30}
31
32/// User filter for the `-u` / `--me` options.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum User {
35    /// List only changes made from the named user (`-u user`).
36    User(String),
37    /// Equivalent to `-u $P4USER` (`--me`).
38    #[cfg(not(feature = "lt2016_1"))]
39    Me,
40}
41
42/// Status of a changelist as accepted by the `-s` option.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum Status {
45    /// Pending changelists.
46    Pending,
47    /// Submitted changelists.
48    Submitted,
49    /// Shelved changelists.
50    Shelved,
51}
52
53impl Status {
54    /// Returns the command-line representation of this status.
55    pub fn as_str(&self) -> &'static str {
56        match self {
57            Status::Pending => "pending",
58            Status::Submitted => "submitted",
59            Status::Shelved => "shelved",
60        }
61    }
62}
63
64#[cfg_attr(
65    feature = "lt2015_2",
66    doc = "`p4 [g-opts] changes [-i -t -l -L -f] [-c client] [-m max] [-s status] [-u user] [file[RevRange] ...]`"
67)]
68#[cfg_attr(
69    all(feature = "lt2016_1", not(feature = "lt2015_2")),
70    doc = "`p4 [g-opts] changes [-i -t -l -L -f] [-c client] [ -e changelist#][-m max] [-s status] [-u user][file[RevRange] ...]`"
71)]
72#[cfg_attr(
73    all(feature = "lt2017_2", not(feature = "lt2016_1")),
74    doc = "`p4 [g-opts] changes [-i -t -l -L -f] [-c client] [ -e changelist#][-m max] [-s status] [-u user | --me][file[RevRange] ...]`"
75)]
76#[cfg_attr(
77    all(feature = "lt2022_2", not(feature = "lt2017_2")),
78    doc = "`p4 [g-opts] changes [-i -t -l -L -f] [-c client] [ -e changelist#][-m max] [-r] [-s status] [-u user | --me] [file[RevRange] ...]`"
79)]
80#[cfg_attr(
81    not(feature = "lt2022_2"),
82    doc = "`p4 [g-opts] changes [-i -t -l -L -f] [-c client] [ -e changelist#][-m max] [-r] [-s status] [-u user | --me] [file[RevRange] ...] [--stream | --nostream]`"
83)]
84///
85/// List submitted and pending changelists.
86///
87/// The command `p4 changelists` is an alias for `p4 changes`.
88///
89/// The `L` type parameter tracks the `-l` / `-L` long output mode at
90/// compile time; see [`FullDescription`], [`TruncatedDescription`],
91/// [`Self::long_output_full`], and [`Self::long_output_truncated`].
92#[derive(Debug, Clone, Default)]
93pub struct Changes<L = Unselected> {
94    bin: PathBuf,
95
96    global_opts: GlobalOpts,
97
98    #[cfg(not(feature = "lt2015_2"))]
99    min_change_list: Option<String>,
100
101    include_restricted: bool,
102
103    include_integrated: bool,
104
105    include_time: bool,
106
107    long_output: L,
108
109    limit: Option<u64>,
110
111    #[cfg(not(feature = "lt2017_2"))]
112    reverse_order: bool,
113
114    filter_status: Option<Status>,
115
116    filter_users: Option<Vec<User>>,
117
118    filter_clients: Option<Vec<String>>,
119
120    #[cfg(not(feature = "lt2025_1"))]
121    client_case_insensitive: bool,
122
123    #[cfg(not(feature = "lt2022_2"))]
124    stream: Option<bool>,
125}
126
127impl Changes<Unselected> {
128    /// Creates a new `p4 changes` command.
129    ///
130    /// `bin` is the path to the Perforce command-line executable.
131    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
132        Self {
133            bin: bin.into(),
134            global_opts,
135            ..Self::default()
136        }
137    }
138
139    /// # Description
140    ///
141    /// `-l`
142    ///
143    /// List long output, with the full text of each changelist description.
144    ///
145    /// Transitions this command to the [`FullDescription`] state.
146    pub fn long_output_full(self) -> Changes<FullDescription> {
147        Changes {
148            bin: self.bin,
149            global_opts: self.global_opts,
150            #[cfg(not(feature = "lt2015_2"))]
151            min_change_list: self.min_change_list,
152            include_restricted: self.include_restricted,
153            include_integrated: self.include_integrated,
154            include_time: self.include_time,
155            long_output: FullDescription,
156            limit: self.limit,
157            #[cfg(not(feature = "lt2017_2"))]
158            reverse_order: self.reverse_order,
159            filter_status: self.filter_status,
160            filter_users: self.filter_users,
161            filter_clients: self.filter_clients,
162            #[cfg(not(feature = "lt2025_1"))]
163            client_case_insensitive: self.client_case_insensitive,
164            #[cfg(not(feature = "lt2022_2"))]
165            stream: self.stream,
166        }
167    }
168
169    /// # Description
170    ///
171    /// `-L`
172    ///
173    /// List long output, with the full text of each changelist description
174    /// truncated at 250 characters.
175    ///
176    /// Transitions this command to the [`TruncatedDescription`] state.
177    pub fn long_output_truncated(self) -> Changes<TruncatedDescription> {
178        Changes {
179            bin: self.bin,
180            global_opts: self.global_opts,
181            #[cfg(not(feature = "lt2015_2"))]
182            min_change_list: self.min_change_list,
183            include_restricted: self.include_restricted,
184            include_integrated: self.include_integrated,
185            include_time: self.include_time,
186            long_output: TruncatedDescription,
187            limit: self.limit,
188            #[cfg(not(feature = "lt2017_2"))]
189            reverse_order: self.reverse_order,
190            filter_status: self.filter_status,
191            filter_users: self.filter_users,
192            filter_clients: self.filter_clients,
193            #[cfg(not(feature = "lt2025_1"))]
194            client_case_insensitive: self.client_case_insensitive,
195            #[cfg(not(feature = "lt2022_2"))]
196            stream: self.stream,
197        }
198    }
199}
200
201impl<L: ExclusiveOption, S, I> ParameterizedSpawn<(S,)> for Changes<L>
202where
203    S: IntoIterator<Item = I>,
204    I: AsRef<OsStr>,
205{
206    type Output = Child;
207    type Error = std::io::Error;
208
209    /// Spawns `p4 changes` for the given files as a child process with piped
210    /// standard output and error streams; use the returned [`Child`] handle
211    /// to wait for it or interact with it.
212    ///
213    /// If files are specified, only changelists that affect those files are
214    /// listed. Pass an empty slice to list all changelists.
215    fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
216        self.setup_command(&self.bin)
217            .args(files)
218            .stdout(Stdio::piped())
219            .stderr(Stdio::piped())
220            .spawn()
221    }
222}
223
224impl<L: ExclusiveOption> Changes<L> {
225    /// # Description
226    ///
227    /// g-opts
228    ///
229    #[cfg_attr(
230        feature = "lt2014_2",
231        doc = "See the [Global Options](GlobalOpts) section."
232    )]
233    #[cfg_attr(
234        all(feature = "lt2015_1", not(feature = "lt2014_2")),
235        doc = "See the [“Global Options”](GlobalOpts) section."
236    )]
237    #[cfg_attr(
238        all(feature = "lt2017_1", not(feature = "lt2015_1")),
239        doc = "See [“Global Options”](GlobalOpts)."
240    )]
241    #[cfg_attr(
242        all(feature = "lt2018_2", not(feature = "lt2017_1")),
243        doc = "See [Global Options](GlobalOpts)."
244    )]
245    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
246    pub fn get_global_opts(&self) -> &GlobalOpts {
247        &self.global_opts
248    }
249
250    /// # Description
251    ///
252    /// g-opts
253    ///
254    #[cfg_attr(
255        feature = "lt2014_2",
256        doc = "See the [Global Options](GlobalOpts) section."
257    )]
258    #[cfg_attr(
259        all(feature = "lt2015_1", not(feature = "lt2014_2")),
260        doc = "See the [“Global Options”](GlobalOpts) section."
261    )]
262    #[cfg_attr(
263        all(feature = "lt2017_1", not(feature = "lt2015_1")),
264        doc = "See [“Global Options”](GlobalOpts)."
265    )]
266    #[cfg_attr(
267        all(feature = "lt2018_2", not(feature = "lt2017_1")),
268        doc = "See [Global Options](GlobalOpts)."
269    )]
270    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
271    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
272        self.global_opts = v;
273        self
274    }
275
276    /// # Description
277    ///
278    /// g-opts
279    ///
280    #[cfg_attr(
281        feature = "lt2014_2",
282        doc = "See the [Global Options](GlobalOpts) section."
283    )]
284    #[cfg_attr(
285        all(feature = "lt2015_1", not(feature = "lt2014_2")),
286        doc = "See the [“Global Options”](GlobalOpts) section."
287    )]
288    #[cfg_attr(
289        all(feature = "lt2017_1", not(feature = "lt2015_1")),
290        doc = "See [“Global Options”](GlobalOpts)."
291    )]
292    #[cfg_attr(
293        all(feature = "lt2018_2", not(feature = "lt2017_1")),
294        doc = "See [Global Options](GlobalOpts)."
295    )]
296    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
297    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
298        self.global_opts = v;
299        self
300    }
301
302    /// # Description
303    ///
304    /// `-c client`
305    ///
306    /// List only changes made from the named client workspace. This option
307    /// can be repeated to filter for multiple clients.
308    pub fn get_filter_clients(&self) -> Option<&[String]> {
309        self.filter_clients.as_deref()
310    }
311
312    /// # Description
313    ///
314    /// `-c client`
315    ///
316    /// List only changes made from the named client workspace. This option
317    /// can be repeated to filter for multiple clients.
318    pub fn set_filter_client(&mut self, v: impl Into<String>) -> &mut Self {
319        self.filter_clients
320            .get_or_insert_with(Vec::new)
321            .push(v.into());
322        self
323    }
324
325    /// # Description
326    ///
327    /// `-c client`
328    ///
329    /// List only changes made from the named client workspace. This option
330    /// can be repeated to filter for multiple clients.
331    pub fn filter_client(mut self, v: impl Into<String>) -> Self {
332        self.filter_clients
333            .get_or_insert_with(Vec::new)
334            .push(v.into());
335        self
336    }
337
338    /// # Description
339    ///
340    /// `-e changelist#`
341    ///
342    /// Display only changes where the changelist number is equal to, or
343    /// higher than, the specified changelist number.
344    #[cfg(not(feature = "lt2015_2"))]
345    pub fn get_min_change_list(&self) -> Option<&String> {
346        self.min_change_list.as_ref()
347    }
348
349    /// # Description
350    ///
351    /// `-e changelist#`
352    ///
353    /// Display only changes where the changelist number is equal to, or
354    /// higher than, the specified changelist number.
355    #[cfg(not(feature = "lt2015_2"))]
356    pub fn set_min_change_list(&mut self, v: impl Into<String>) -> &mut Self {
357        self.min_change_list = Some(v.into());
358        self
359    }
360
361    /// # Description
362    ///
363    /// `-e changelist#`
364    ///
365    /// Display only changes where the changelist number is equal to, or
366    /// higher than, the specified changelist number.
367    #[cfg(not(feature = "lt2015_2"))]
368    pub fn min_change_list(mut self, v: impl Into<String>) -> Self {
369        self.min_change_list = Some(v.into());
370        self
371    }
372
373    /// # Description
374    ///
375    /// `-f`
376    ///
377    /// View restricted changes (requires admin permission).
378    pub fn get_include_restricted(&self) -> bool {
379        self.include_restricted
380    }
381
382    /// # Description
383    ///
384    /// `-f`
385    ///
386    /// View restricted changes (requires admin permission).
387    pub fn set_include_restricted(&mut self, v: bool) -> &mut Self {
388        self.include_restricted = v;
389        self
390    }
391
392    /// # Description
393    ///
394    /// `-f`
395    ///
396    /// View restricted changes (requires admin permission).
397    pub fn include_restricted(mut self, v: bool) -> Self {
398        self.include_restricted = v;
399        self
400    }
401
402    /// # Description
403    ///
404    /// `-i`
405    ///
406    /// Include changelists that affected files that were integrated with the
407    /// specified files.
408    pub fn get_include_integrated(&self) -> bool {
409        self.include_integrated
410    }
411
412    /// # Description
413    ///
414    /// `-i`
415    ///
416    /// Include changelists that affected files that were integrated with the
417    /// specified files.
418    pub fn set_include_integrated(&mut self, v: bool) -> &mut Self {
419        self.include_integrated = v;
420        self
421    }
422
423    /// # Description
424    ///
425    /// `-i`
426    ///
427    /// Include changelists that affected files that were integrated with the
428    /// specified files.
429    pub fn include_integrated(mut self, v: bool) -> Self {
430        self.include_integrated = v;
431        self
432    }
433
434    /// # Description
435    ///
436    /// `-m max`
437    ///
438    /// List only the highest numbered `max` changes.
439    pub fn get_limit(&self) -> Option<u64> {
440        self.limit
441    }
442
443    /// # Description
444    ///
445    /// `-m max`
446    ///
447    /// List only the highest numbered `max` changes.
448    pub fn set_limit(&mut self, v: u64) -> &mut Self {
449        self.limit = Some(v);
450        self
451    }
452
453    /// # Description
454    ///
455    /// `-m max`
456    ///
457    /// List only the highest numbered `max` changes.
458    pub fn limit(mut self, v: u64) -> Self {
459        self.limit = Some(v);
460        self
461    }
462
463    /// # Description
464    ///
465    /// `-r`
466    ///
467    /// Reverse the order of the list, earliest first instead of most recent
468    /// first.
469    #[cfg(not(feature = "lt2017_2"))]
470    pub fn get_reverse_order(&self) -> bool {
471        self.reverse_order
472    }
473
474    /// # Description
475    ///
476    /// `-r`
477    ///
478    /// Reverse the order of the list, earliest first instead of most recent
479    /// first.
480    #[cfg(not(feature = "lt2017_2"))]
481    pub fn set_reverse_order(&mut self, v: bool) -> &mut Self {
482        self.reverse_order = v;
483        self
484    }
485
486    /// # Description
487    ///
488    /// `-r`
489    ///
490    /// Reverse the order of the list, earliest first instead of most recent
491    /// first.
492    #[cfg(not(feature = "lt2017_2"))]
493    pub fn reverse_order(mut self, v: bool) -> Self {
494        self.reverse_order = v;
495        self
496    }
497
498    /// # Description
499    ///
500    /// `-s status`
501    ///
502    /// Limit the list to the changelists with the specified status:
503    /// `pending`, `submitted`, or `shelved`.
504    pub fn get_status(&self) -> Option<Status> {
505        self.filter_status
506    }
507
508    /// # Description
509    ///
510    /// `-s status`
511    ///
512    /// Limit the list to the changelists with the specified status:
513    /// `pending`, `submitted`, or `shelved`.
514    pub fn set_status(&mut self, v: Status) -> &mut Self {
515        self.filter_status = Some(v);
516        self
517    }
518
519    /// # Description
520    ///
521    /// `-s status`
522    ///
523    /// Limit the list to the changelists with the specified status:
524    /// `pending`, `submitted`, or `shelved`.
525    pub fn status(mut self, v: Status) -> Self {
526        self.filter_status = Some(v);
527        self
528    }
529
530    /// # Description
531    ///
532    /// `-t`
533    ///
534    /// Display the time as well as the date of each change.
535    pub fn get_include_time(&self) -> bool {
536        self.include_time
537    }
538
539    /// # Description
540    ///
541    /// `-t`
542    ///
543    /// Display the time as well as the date of each change.
544    pub fn set_include_time(&mut self, v: bool) -> &mut Self {
545        self.include_time = v;
546        self
547    }
548
549    /// # Description
550    ///
551    /// `-t`
552    ///
553    /// Display the time as well as the date of each change.
554    pub fn include_time(mut self, v: bool) -> Self {
555        self.include_time = v;
556        self
557    }
558
559    /// # Description
560    ///
561    #[cfg_attr(feature = "lt2016_1", doc = "-u user")]
562    #[cfg_attr(not(feature = "lt2016_1"), doc = "-u user / --me")]
563    ///
564    #[cfg_attr(
565        feature = "lt2016_1",
566        doc = "List only changes made from the named user. This option can be repeated to filter for multiple users."
567    )]
568    #[cfg_attr(
569        not(feature = "lt2016_1"),
570        doc = "List only changes made from the named user, or, with [`User::Me`], the current user (equivalent to `-u $P4USER`). This option can be repeated to filter for multiple users."
571    )]
572    pub fn get_filter_users(&self) -> Option<&[User]> {
573        self.filter_users.as_deref()
574    }
575
576    /// # Description
577    ///
578    /// `-u user`
579    ///
580    /// List only changes made from the named user. This option can be
581    /// repeated to filter for multiple users.
582    pub fn set_filter_user(&mut self, v: impl Into<String>) -> &mut Self {
583        self.filter_users
584            .get_or_insert_with(Vec::new)
585            .push(User::User(v.into()));
586        self
587    }
588
589    /// # Description
590    ///
591    /// `-u user`
592    ///
593    /// List only changes made from the named user. This option can be
594    /// repeated to filter for multiple users.
595    pub fn filter_user(mut self, v: impl Into<String>) -> Self {
596        self.filter_users
597            .get_or_insert_with(Vec::new)
598            .push(User::User(v.into()));
599        self
600    }
601
602    /// # Description
603    ///
604    /// `--me`
605    ///
606    /// Equivalent to `-u $P4USER`.
607    #[cfg(not(feature = "lt2016_1"))]
608    pub fn set_filter_me(&mut self) -> &mut Self {
609        self.filter_users
610            .get_or_insert_with(Vec::new)
611            .push(User::Me);
612        self
613    }
614
615    /// # Description
616    ///
617    /// `--me`
618    ///
619    /// Equivalent to `-u $P4USER`.
620    #[cfg(not(feature = "lt2016_1"))]
621    pub fn filter_me(mut self) -> Self {
622        self.filter_users
623            .get_or_insert_with(Vec::new)
624            .push(User::Me);
625        self
626    }
627
628    /// # Description
629    ///
630    /// `--client-case-insensitive`
631    ///
632    /// Makes the `-c client` search pattern case-insensitive, even on a
633    /// case-sensitive server.
634    #[cfg(not(feature = "lt2025_1"))]
635    pub fn get_client_case_insensitive(&self) -> bool {
636        self.client_case_insensitive
637    }
638
639    /// # Description
640    ///
641    /// `--client-case-insensitive`
642    ///
643    /// Makes the `-c client` search pattern case-insensitive, even on a
644    /// case-sensitive server.
645    #[cfg(not(feature = "lt2025_1"))]
646    pub fn set_client_case_insensitive(&mut self, v: bool) -> &mut Self {
647        self.client_case_insensitive = v;
648        self
649    }
650
651    /// # Description
652    ///
653    /// `--client-case-insensitive`
654    ///
655    /// Makes the `-c client` search pattern case-insensitive, even on a
656    /// case-sensitive server.
657    #[cfg(not(feature = "lt2025_1"))]
658    pub fn client_case_insensitive(mut self, v: bool) -> Self {
659        self.client_case_insensitive = v;
660        self
661    }
662
663    /// # Description
664    ///
665    /// `--stream` / `--nostream`
666    ///
667    /// With `true`, display only changes that contain a stream spec
668    /// (`--stream`). With `false`, display only changes that do not
669    /// contain a stream spec (`--nostream`).
670    #[cfg(not(feature = "lt2022_2"))]
671    pub fn get_stream(&self) -> Option<bool> {
672        self.stream
673    }
674
675    /// # Description
676    ///
677    /// `--stream` / `--nostream`
678    ///
679    /// With `true`, display only changes that contain a stream spec
680    /// (`--stream`). With `false`, display only changes that do not
681    /// contain a stream spec (`--nostream`).
682    #[cfg(not(feature = "lt2022_2"))]
683    pub fn set_stream(&mut self, v: bool) -> &mut Self {
684        self.stream = Some(v);
685        self
686    }
687
688    /// # Description
689    ///
690    /// `--stream` / `--nostream`
691    ///
692    /// With `true`, display only changes that contain a stream spec
693    /// (`--stream`). With `false`, display only changes that do not
694    /// contain a stream spec (`--nostream`).
695    #[cfg(not(feature = "lt2022_2"))]
696    pub fn stream(mut self, v: bool) -> Self {
697        self.stream = Some(v);
698        self
699    }
700}
701
702impl<L: ExclusiveOption> SubCommand for Changes<L> {
703    fn name(&self) -> &str {
704        "changes"
705    }
706
707    fn inject_local_args(&self, command: &mut std::process::Command) {
708        if let Some(ref clients) = self.filter_clients {
709            for client in clients {
710                command.arg("-c").arg(client);
711            }
712        }
713        #[cfg(not(feature = "lt2025_1"))]
714        if self.client_case_insensitive {
715            command.arg("--client-case-insensitive");
716        }
717        #[cfg(not(feature = "lt2015_2"))]
718        if let Some(ref min_change) = self.min_change_list {
719            command.arg("-e").arg(min_change);
720        }
721        if self.include_restricted {
722            command.arg("-f");
723        }
724        if self.include_integrated {
725            command.arg("-i");
726        }
727        self.long_output.inject_args(command);
728        if let Some(max) = self.limit {
729            command.arg("-m").arg(max.to_string());
730        }
731        #[cfg(not(feature = "lt2017_2"))]
732        if self.reverse_order {
733            command.arg("-r");
734        }
735        if let Some(status) = self.filter_status {
736            command.arg("-s").arg(status.as_str());
737        }
738        if self.include_time {
739            command.arg("-t");
740        }
741        if let Some(ref users) = self.filter_users {
742            for user in users {
743                match user {
744                    User::User(name) => {
745                        command.arg("-u").arg(name);
746                    }
747                    #[cfg(not(feature = "lt2016_1"))]
748                    User::Me => {
749                        command.arg("--me");
750                    }
751                }
752            }
753        }
754        #[cfg(not(feature = "lt2022_2"))]
755        if let Some(stream) = self.stream {
756            if stream {
757                command.arg("--stream");
758            } else {
759                command.arg("--nostream");
760            }
761        }
762    }
763
764    fn global_opts(&self) -> Option<&GlobalOpts> {
765        Some(&self.global_opts)
766    }
767}
768
769#[cfg(test)]
770mod tests {
771    use super::*;
772    use crate::cmd::args_of;
773
774    #[test]
775    fn without_options() {
776        let changes = Changes::new("p4", GlobalOpts::default());
777        let cmd = changes.setup_command("p4");
778        assert_eq!(args_of(&cmd), vec!["changes"]);
779    }
780
781    #[test]
782    fn with_files() {
783        let changes = Changes::new("p4", GlobalOpts::default());
784        let mut cmd = changes.setup_command("p4");
785        cmd.arg("//depot/project/...");
786        assert_eq!(args_of(&cmd), vec!["changes", "//depot/project/..."]);
787    }
788
789    #[test]
790    fn client() {
791        let changes = Changes::new("p4", GlobalOpts::default()).filter_client("eds_elm");
792        let cmd = changes.setup_command("p4");
793        assert_eq!(args_of(&cmd), vec!["changes", "-c", "eds_elm"]);
794    }
795
796    #[test]
797    fn multiple_clients() {
798        let changes = Changes::new("p4", GlobalOpts::default())
799            .filter_client("eds_elm")
800            .filter_client("build_ws");
801        let cmd = changes.setup_command("p4");
802        assert_eq!(
803            args_of(&cmd),
804            vec!["changes", "-c", "eds_elm", "-c", "build_ws"]
805        );
806    }
807
808    #[cfg(not(feature = "lt2015_2"))]
809    #[test]
810    fn min_change_list() {
811        let changes = Changes::new("p4", GlobalOpts::default()).min_change_list("800");
812        let cmd = changes.setup_command("p4");
813        assert_eq!(args_of(&cmd), vec!["changes", "-e", "800"]);
814    }
815
816    #[test]
817    fn include_restricted() {
818        let changes = Changes::new("p4", GlobalOpts::default()).include_restricted(true);
819        let cmd = changes.setup_command("p4");
820        assert_eq!(args_of(&cmd), vec!["changes", "-f"]);
821    }
822
823    #[test]
824    fn include_integrated() {
825        let changes = Changes::new("p4", GlobalOpts::default()).include_integrated(true);
826        let cmd = changes.setup_command("p4");
827        assert_eq!(args_of(&cmd), vec!["changes", "-i"]);
828    }
829
830    #[test]
831    fn long_output_full() {
832        let changes = Changes::new("p4", GlobalOpts::default()).long_output_full();
833        let cmd = changes.setup_command("p4");
834        assert_eq!(args_of(&cmd), vec!["changes", "-l"]);
835    }
836
837    #[test]
838    fn long_output_truncated() {
839        let changes = Changes::new("p4", GlobalOpts::default()).long_output_truncated();
840        let cmd = changes.setup_command("p4");
841        assert_eq!(args_of(&cmd), vec!["changes", "-L"]);
842    }
843
844    #[test]
845    fn limit() {
846        let changes = Changes::new("p4", GlobalOpts::default()).limit(5);
847        let cmd = changes.setup_command("p4");
848        assert_eq!(args_of(&cmd), vec!["changes", "-m", "5"]);
849    }
850
851    #[cfg(not(feature = "lt2017_2"))]
852    #[test]
853    fn reverse_order() {
854        let changes = Changes::new("p4", GlobalOpts::default()).reverse_order(true);
855        let cmd = changes.setup_command("p4");
856        assert_eq!(args_of(&cmd), vec!["changes", "-r"]);
857    }
858
859    #[test]
860    fn status_pending() {
861        let changes = Changes::new("p4", GlobalOpts::default()).status(Status::Pending);
862        let cmd = changes.setup_command("p4");
863        assert_eq!(args_of(&cmd), vec!["changes", "-s", "pending"]);
864    }
865
866    #[test]
867    fn status_submitted() {
868        let changes = Changes::new("p4", GlobalOpts::default()).status(Status::Submitted);
869        let cmd = changes.setup_command("p4");
870        assert_eq!(args_of(&cmd), vec!["changes", "-s", "submitted"]);
871    }
872
873    #[test]
874    fn status_shelved() {
875        let changes = Changes::new("p4", GlobalOpts::default()).status(Status::Shelved);
876        let cmd = changes.setup_command("p4");
877        assert_eq!(args_of(&cmd), vec!["changes", "-s", "shelved"]);
878    }
879
880    #[test]
881    fn include_time() {
882        let changes = Changes::new("p4", GlobalOpts::default()).include_time(true);
883        let cmd = changes.setup_command("p4");
884        assert_eq!(args_of(&cmd), vec!["changes", "-t"]);
885    }
886
887    #[test]
888    fn user_name() {
889        let changes = Changes::new("p4", GlobalOpts::default()).filter_user("edk");
890        let cmd = changes.setup_command("p4");
891        assert_eq!(args_of(&cmd), vec!["changes", "-u", "edk"]);
892    }
893
894    #[test]
895    fn multiple_users() {
896        let changes = Changes::new("p4", GlobalOpts::default())
897            .filter_user("maria")
898            .filter_user("edk");
899        let cmd = changes.setup_command("p4");
900        assert_eq!(args_of(&cmd), vec!["changes", "-u", "maria", "-u", "edk"]);
901    }
902
903    #[cfg(not(feature = "lt2016_1"))]
904    #[test]
905    fn user_me() {
906        let changes = Changes::new("p4", GlobalOpts::default()).filter_me();
907        let cmd = changes.setup_command("p4");
908        assert_eq!(args_of(&cmd), vec!["changes", "--me"]);
909    }
910
911    #[cfg(not(feature = "lt2016_1"))]
912    #[test]
913    fn multiple_users_with_me() {
914        let changes = Changes::new("p4", GlobalOpts::default())
915            .filter_user("maria")
916            .filter_me();
917        let cmd = changes.setup_command("p4");
918        assert_eq!(args_of(&cmd), vec!["changes", "-u", "maria", "--me"]);
919    }
920
921    #[cfg(not(feature = "lt2025_1"))]
922    #[test]
923    fn client_case_insensitive() {
924        let changes = Changes::new("p4", GlobalOpts::default())
925            .filter_client("eds_elm")
926            .client_case_insensitive(true);
927        let cmd = changes.setup_command("p4");
928        assert_eq!(
929            args_of(&cmd),
930            vec!["changes", "-c", "eds_elm", "--client-case-insensitive"]
931        );
932    }
933
934    #[cfg(not(feature = "lt2022_2"))]
935    #[test]
936    fn stream_spec() {
937        let changes = Changes::new("p4", GlobalOpts::default()).stream(true);
938        let cmd = changes.setup_command("p4");
939        assert_eq!(args_of(&cmd), vec!["changes", "--stream"]);
940    }
941
942    #[cfg(not(feature = "lt2022_2"))]
943    #[test]
944    fn no_stream_spec() {
945        let changes = Changes::new("p4", GlobalOpts::default()).stream(false);
946        let cmd = changes.setup_command("p4");
947        assert_eq!(args_of(&cmd), vec!["changes", "--nostream"]);
948    }
949
950    #[test]
951    fn all_options_order() {
952        let changes = Changes::new("p4", GlobalOpts::default())
953            .filter_client("eds_elm")
954            .include_restricted(true)
955            .include_integrated(true)
956            .long_output_full()
957            .limit(5)
958            .status(Status::Submitted)
959            .include_time(true)
960            .filter_user("edk");
961        #[cfg(not(feature = "lt2015_2"))]
962        let changes = changes.min_change_list("800");
963        #[cfg(not(feature = "lt2017_2"))]
964        let changes = changes.reverse_order(true);
965        #[cfg(not(feature = "lt2022_2"))]
966        let changes = changes.stream(true);
967        let cmd = changes.setup_command("p4");
968        let mut expected = vec!["changes", "-c", "eds_elm"];
969        #[cfg(not(feature = "lt2015_2"))]
970        expected.extend(["-e", "800"]);
971        expected.extend(["-f", "-i", "-l", "-m", "5"]);
972        #[cfg(not(feature = "lt2017_2"))]
973        expected.push("-r");
974        expected.extend(["-s", "submitted", "-t", "-u", "edk"]);
975        #[cfg(not(feature = "lt2022_2"))]
976        expected.push("--stream");
977        assert_eq!(args_of(&cmd), expected);
978    }
979
980    #[test]
981    fn long_output_full_preserves_other_options() {
982        let changes = Changes::new("p4", GlobalOpts::default())
983            .include_restricted(true)
984            .include_time(true)
985            .long_output_full();
986        let cmd = changes.setup_command("p4");
987        assert_eq!(args_of(&cmd), vec!["changes", "-f", "-l", "-t"]);
988    }
989}