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