Skip to main content

perforce_cli/cmd/
changes.rs

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