Skip to main content

perforce_cli/cmd/
edit.rs

1use std::{
2    ffi::OsStr,
3    path::{Path, PathBuf},
4    process::{Child, Command, Stdio},
5};
6
7use super::{ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12/// File edit mode of `p4 edit`: the file form carrying `-k`, `-n`,
13/// `--remote`, and `-t`.
14///
15/// Entered with [`Edit::keep_workspace`], [`Edit::preview`],
16/// [`Edit::remote_server`], or [`Edit::filetype`].
17#[derive(Debug, Clone, Default)]
18pub struct FileEditMode {
19    keep_workspace: bool,
20
21    preview: bool,
22
23    remote_server: Option<String>,
24
25    filetype: Option<String>,
26}
27
28impl ExclusiveOption for FileEditMode {
29    fn inject_args(&self, command: &mut Command) {
30        if self.keep_workspace {
31            command.arg("-k");
32        }
33
34        if self.preview {
35            command.arg("-n");
36        }
37
38        if let Some(remote) = &self.remote_server {
39            command.arg(format!("--remote={remote}"));
40        }
41
42        if let Some(filetype) = &self.filetype {
43            command.arg("-t").arg(filetype);
44        }
45    }
46}
47
48/// Stream spec edit mode of `p4 edit` (`-So`): opens the current stream
49/// spec for edit.
50///
51/// No list of files is allowed, and `-So` may only be combined with
52/// `-c changelist`. Entered with [`Edit::edit_stream_spec`].
53#[cfg(not(feature = "lt2019_1"))]
54#[derive(Debug, Clone, Copy, Default)]
55pub struct StreamSpecEditMode;
56
57#[cfg(not(feature = "lt2019_1"))]
58impl ExclusiveOption for StreamSpecEditMode {
59    fn inject_args(&self, command: &mut Command) {
60        command.arg("-So");
61    }
62}
63
64#[cfg_attr(
65    feature = "lt2019_1",
66    doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`"
67)]
68#[cfg_attr(
69    not(feature = "lt2019_1"),
70    doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`\n\n\
71           `p4 [g-opts] edit -So [-c changelist]`"
72)]
73///
74/// Opens files in a client workspace for edit, or open the current stream
75/// spec.
76///
77/// The `M` type parameter tracks the command form at compile time. The
78/// default [`Unselected`] state opens plain files with no edit-mode options;
79/// [`Self::keep_workspace`], [`Self::preview`], [`Self::remote_server`], and
80/// [`Self::filetype`] transition to the [`FileEditMode`] state
81#[cfg_attr(feature = "lt2019_1", doc = ".")]
82#[cfg_attr(
83    not(feature = "lt2019_1"),
84    doc = ", while [`Self::edit_stream_spec`] transitions to the [`StreamSpecEditMode`] state."
85)]
86#[derive(Debug, Clone, Default)]
87pub struct Edit<M = Unselected> {
88    bin: PathBuf,
89
90    global_opts: GlobalOpts,
91
92    change_list: Option<String>,
93
94    mode: M,
95}
96
97impl Edit<Unselected> {
98    /// Creates a new `p4 edit` command.
99    ///
100    /// `bin` is the path to the Perforce command-line executable.
101    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
102        Self {
103            bin: bin.into(),
104            global_opts,
105            change_list: None,
106            mode: Unselected,
107        }
108    }
109
110    /// # Description
111    ///
112    /// -k
113    ///
114    /// Keep existing workspace files; mark the file as open for edit even if
115    /// the file is not in the client view. Use `p4 edit -k` only in the
116    /// context of reconciling work performed while disconnected from the
117    /// shared versioning service.
118    ///
119    /// Transitions this command to the [`FileEditMode`] state.
120    pub fn keep_workspace(self, v: bool) -> Edit<FileEditMode> {
121        Edit {
122            bin: self.bin,
123            global_opts: self.global_opts,
124            change_list: self.change_list,
125            mode: FileEditMode {
126                keep_workspace: v,
127                preview: false,
128                remote_server: None,
129                filetype: None,
130            },
131        }
132    }
133
134    /// # Description
135    ///
136    /// -n
137    ///
138    /// Preview which files would be opened for edit, without actually changing
139    /// any files or metadata.
140    ///
141    /// Transitions this command to the [`FileEditMode`] state.
142    pub fn preview(self, v: bool) -> Edit<FileEditMode> {
143        Edit {
144            bin: self.bin,
145            global_opts: self.global_opts,
146            change_list: self.change_list,
147            mode: FileEditMode {
148                keep_workspace: false,
149                preview: v,
150                remote_server: None,
151                filetype: None,
152            },
153        }
154    }
155
156    /// # Description
157    ///
158    /// `--remote=remote`
159    ///
160    /// Opens the file for edit in your personal server, and additionally — if
161    /// the file is of type `+l` — takes a global exclusive lock on the file in
162    /// the shared server from which you cloned the file.
163    ///
164    /// Transitions this command to the [`FileEditMode`] state.
165    pub fn remote_server(self, v: impl Into<String>) -> Edit<FileEditMode> {
166        Edit {
167            bin: self.bin,
168            global_opts: self.global_opts,
169            change_list: self.change_list,
170            mode: FileEditMode {
171                keep_workspace: false,
172                preview: false,
173                remote_server: Some(v.into()),
174                filetype: None,
175            },
176        }
177    }
178
179    /// # Description
180    ///
181    /// `-t type`
182    ///
183    /// Stores the new file revision as the specified type, overriding the file
184    /// type of the previous revision of the same file. To forcibly re-detect a
185    /// file's filetype upon editing a file, use `p4 edit -t auto`. This assigns
186    /// a file type as if the file were being newly added.
187    ///
188    #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
189    #[cfg_attr(
190        not(feature = "lt2024_1"),
191        doc = "See File types as well as the lbr.autocompress configurable."
192    )]
193    ///
194    /// Transitions this command to the [`FileEditMode`] state.
195    pub fn filetype(self, v: impl Into<String>) -> Edit<FileEditMode> {
196        Edit {
197            bin: self.bin,
198            global_opts: self.global_opts,
199            change_list: self.change_list,
200            mode: FileEditMode {
201                keep_workspace: false,
202                preview: false,
203                remote_server: None,
204                filetype: Some(v.into()),
205            },
206        }
207    }
208
209    /// # Description
210    ///
211    /// `-So`
212    ///
213    /// Can be used with `-c changelist` to open the client's stream spec for
214    /// edit. No list of files is allowed. `p4 edit -So` is an alias for
215    /// `p4 stream edit` (see also `p4 help streamcmds`).
216    ///
217    /// Transitions this command to the [`StreamSpecEditMode`] state.
218    #[cfg(not(feature = "lt2019_1"))]
219    pub fn edit_stream_spec(self) -> Edit<StreamSpecEditMode> {
220        Edit {
221            bin: self.bin,
222            global_opts: self.global_opts,
223            change_list: self.change_list,
224            mode: StreamSpecEditMode,
225        }
226    }
227}
228
229impl<S, I> ParameterizedSpawn<(S,)> for Edit<Unselected>
230where
231    S: IntoIterator<Item = I>,
232    I: AsRef<OsStr>,
233{
234    type Output = Child;
235    type Error = std::io::Error;
236
237    /// Spawns `p4 edit` for the given files as a child process with piped
238    /// standard output and error streams; use the returned [`Child`] handle
239    /// to wait for it or interact with it.
240    ///
241    /// This corresponds to the file form of the command:
242    /// `p4 edit [options] file ...`.
243    fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
244        self.setup_command(&self.bin)
245            .args(files)
246            .stdout(Stdio::piped())
247            .stderr(Stdio::piped())
248            .spawn()
249    }
250}
251
252impl<S, I> ParameterizedSpawn<(S,)> for Edit<FileEditMode>
253where
254    S: IntoIterator<Item = I>,
255    I: AsRef<OsStr>,
256{
257    type Output = Child;
258    type Error = std::io::Error;
259
260    /// Spawns `p4 edit` for the given files as a child process with piped
261    /// standard output and error streams; use the returned [`Child`] handle
262    /// to wait for it or interact with it.
263    ///
264    /// This corresponds to the file form of the command:
265    /// `p4 edit [options] file ...`.
266    fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
267        self.setup_command(&self.bin)
268            .args(files)
269            .stdout(Stdio::piped())
270            .stderr(Stdio::piped())
271            .spawn()
272    }
273}
274
275impl Edit<FileEditMode> {
276    /// # Description
277    ///
278    /// -k
279    ///
280    /// Keep existing workspace files; mark the file as open for edit even if
281    /// the file is not in the client view. Use `p4 edit -k` only in the
282    /// context of reconciling work performed while disconnected from the
283    /// shared versioning service.
284    pub fn get_keep_workspace(&self) -> bool {
285        self.mode.keep_workspace
286    }
287
288    /// # Description
289    ///
290    /// -k
291    ///
292    /// Keep existing workspace files; mark the file as open for edit even if
293    /// the file is not in the client view. Use `p4 edit -k` only in the
294    /// context of reconciling work performed while disconnected from the
295    /// shared versioning service.
296    pub fn set_keep_workspace(&mut self, v: bool) -> &mut Self {
297        self.mode.keep_workspace = v;
298        self
299    }
300
301    /// # Description
302    ///
303    /// -k
304    ///
305    /// Keep existing workspace files; mark the file as open for edit even if
306    /// the file is not in the client view. Use `p4 edit -k` only in the
307    /// context of reconciling work performed while disconnected from the
308    /// shared versioning service.
309    pub fn keep_workspace(mut self, v: bool) -> Self {
310        self.mode.keep_workspace = v;
311        self
312    }
313
314    /// # Description
315    ///
316    /// -n
317    ///
318    /// Preview which files would be opened for edit, without actually changing
319    /// any files or metadata.
320    pub fn get_preview(&self) -> bool {
321        self.mode.preview
322    }
323
324    /// # Description
325    ///
326    /// -n
327    ///
328    /// Preview which files would be opened for edit, without actually changing
329    /// any files or metadata.
330    pub fn set_preview(&mut self, v: bool) -> &mut Self {
331        self.mode.preview = v;
332        self
333    }
334
335    /// # Description
336    ///
337    /// -n
338    ///
339    /// Preview which files would be opened for edit, without actually changing
340    /// any files or metadata.
341    pub fn preview(mut self, v: bool) -> Self {
342        self.mode.preview = v;
343        self
344    }
345
346    /// # Description
347    ///
348    /// `--remote=remote`
349    ///
350    /// Opens the file for edit in your personal server, and additionally — if
351    /// the file is of type `+l` — takes a global exclusive lock on the file in
352    /// the shared server from which you cloned the file.
353    pub fn get_remote_server(&self) -> Option<&String> {
354        self.mode.remote_server.as_ref()
355    }
356
357    /// # Description
358    ///
359    /// `--remote=remote`
360    ///
361    /// Opens the file for edit in your personal server, and additionally — if
362    /// the file is of type `+l` — takes a global exclusive lock on the file in
363    /// the shared server from which you cloned the file.
364    pub fn set_remote_server(&mut self, v: impl Into<String>) -> &mut Self {
365        self.mode.remote_server = Some(v.into());
366        self
367    }
368
369    /// # Description
370    ///
371    /// `--remote=remote`
372    ///
373    /// Opens the file for edit in your personal server, and additionally — if
374    /// the file is of type `+l` — takes a global exclusive lock on the file in
375    /// the shared server from which you cloned the file.
376    pub fn remote_server(mut self, v: impl Into<String>) -> Self {
377        self.mode.remote_server = Some(v.into());
378        self
379    }
380
381    /// # Description
382    ///
383    /// `-t type`
384    ///
385    /// Stores the new file revision as the specified type, overriding the file
386    /// type of the previous revision of the same file. To forcibly re-detect a
387    /// file's filetype upon editing a file, use `p4 edit -t auto`. This assigns
388    /// a file type as if the file were being newly added.
389    ///
390    #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
391    #[cfg_attr(
392        not(feature = "lt2024_1"),
393        doc = "See File types as well as the lbr.autocompress configurable."
394    )]
395    pub fn get_filetype(&self) -> Option<&String> {
396        self.mode.filetype.as_ref()
397    }
398
399    /// # Description
400    ///
401    /// `-t type`
402    ///
403    /// Stores the new file revision as the specified type, overriding the file
404    /// type of the previous revision of the same file. To forcibly re-detect a
405    /// file's filetype upon editing a file, use `p4 edit -t auto`. This assigns
406    /// a file type as if the file were being newly added.
407    ///
408    #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
409    #[cfg_attr(
410        not(feature = "lt2024_1"),
411        doc = "See File types as well as the lbr.autocompress configurable."
412    )]
413    pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
414        self.mode.filetype = Some(v.into());
415        self
416    }
417
418    /// # Description
419    ///
420    /// `-t type`
421    ///
422    /// Stores the new file revision as the specified type, overriding the file
423    /// type of the previous revision of the same file. To forcibly re-detect a
424    /// file's filetype upon editing a file, use `p4 edit -t auto`. This assigns
425    /// a file type as if the file were being newly added.
426    ///
427    #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
428    #[cfg_attr(
429        not(feature = "lt2024_1"),
430        doc = "See File types as well as the lbr.autocompress configurable."
431    )]
432    pub fn filetype(mut self, v: impl Into<String>) -> Self {
433        self.mode.filetype = Some(v.into());
434        self
435    }
436}
437
438#[cfg(not(feature = "lt2019_1"))]
439impl ParameterizedSpawn<()> for Edit<StreamSpecEditMode> {
440    type Output = Child;
441    type Error = std::io::Error;
442
443    /// Spawns `p4 edit -So` to open the current stream spec for edit as a
444    /// child process with piped standard output and error streams; use the
445    /// returned [`Child`] handle to wait for it or interact with it.
446    ///
447    /// This corresponds to the stream spec form of the command:
448    /// `p4 edit -So [-c changelist]`, which takes no file arguments.
449    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
450        self.setup_command(&self.bin)
451            .stdout(Stdio::piped())
452            .stderr(Stdio::piped())
453            .spawn()
454    }
455}
456
457impl<M: ExclusiveOption> Edit<M> {
458    /// Get the Perforce command path.
459    pub fn get_bin(&self) -> &Path {
460        &self.bin
461    }
462
463    /// Set the Perforce command path.
464    pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
465        self.bin = bin.into();
466        self
467    }
468
469    /// Set the Perforce command path.
470    pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
471        self.set_bin(bin);
472        self
473    }
474
475    /// # Description
476    ///
477    /// g-opts
478    ///
479    #[cfg_attr(
480        feature = "lt2014_2",
481        doc = "See the [Global Options](GlobalOpts) section."
482    )]
483    #[cfg_attr(
484        all(feature = "lt2015_1", not(feature = "lt2014_2")),
485        doc = "See the [“Global Options”](GlobalOpts) section."
486    )]
487    #[cfg_attr(
488        all(feature = "lt2017_1", not(feature = "lt2015_1")),
489        doc = "See [“Global Options”](GlobalOpts)."
490    )]
491    #[cfg_attr(
492        all(feature = "lt2018_2", not(feature = "lt2017_1")),
493        doc = "See [Global Options](GlobalOpts)."
494    )]
495    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
496    pub fn get_global_opts(&self) -> &GlobalOpts {
497        &self.global_opts
498    }
499
500    /// # Description
501    ///
502    /// g-opts
503    ///
504    #[cfg_attr(
505        feature = "lt2014_2",
506        doc = "See the [Global Options](GlobalOpts) section."
507    )]
508    #[cfg_attr(
509        all(feature = "lt2015_1", not(feature = "lt2014_2")),
510        doc = "See the [“Global Options”](GlobalOpts) section."
511    )]
512    #[cfg_attr(
513        all(feature = "lt2017_1", not(feature = "lt2015_1")),
514        doc = "See [“Global Options”](GlobalOpts)."
515    )]
516    #[cfg_attr(
517        all(feature = "lt2018_2", not(feature = "lt2017_1")),
518        doc = "See [Global Options](GlobalOpts)."
519    )]
520    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
521    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
522        self.global_opts = v;
523        self
524    }
525
526    /// # Description
527    ///
528    /// g-opts
529    ///
530    #[cfg_attr(
531        feature = "lt2014_2",
532        doc = "See the [Global Options](GlobalOpts) section."
533    )]
534    #[cfg_attr(
535        all(feature = "lt2015_1", not(feature = "lt2014_2")),
536        doc = "See the [“Global Options”](GlobalOpts) section."
537    )]
538    #[cfg_attr(
539        all(feature = "lt2017_1", not(feature = "lt2015_1")),
540        doc = "See [“Global Options”](GlobalOpts)."
541    )]
542    #[cfg_attr(
543        all(feature = "lt2018_2", not(feature = "lt2017_1")),
544        doc = "See [Global Options](GlobalOpts)."
545    )]
546    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
547    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
548        self.global_opts = v;
549        self
550    }
551
552    /// # Description
553    ///
554    /// `-c changelist`
555    ///
556    /// Opens the files for edit within the specified changelist. If this
557    /// option is not provided, the files are linked to the default changelist.
558    pub fn get_change_list(&self) -> Option<&String> {
559        self.change_list.as_ref()
560    }
561
562    /// # Description
563    ///
564    /// `-c changelist`
565    ///
566    /// Opens the files for edit within the specified changelist. If this
567    /// option is not provided, the files are linked to the default changelist.
568    pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
569        self.change_list = Some(v.into());
570        self
571    }
572
573    /// # Description
574    ///
575    /// `-c changelist`
576    ///
577    /// Opens the files for edit within the specified changelist. If this
578    /// option is not provided, the files are linked to the default changelist.
579    pub fn change_list(mut self, v: impl Into<String>) -> Self {
580        self.change_list = Some(v.into());
581        self
582    }
583}
584
585impl<M: ExclusiveOption> SubCommand for Edit<M> {
586    fn name(&self) -> &str {
587        "edit"
588    }
589
590    fn inject_local_args(&self, command: &mut Command) {
591        if let Some(change_list) = &self.change_list {
592            command.arg("-c").arg(change_list);
593        }
594
595        self.mode.inject_args(command);
596    }
597
598    fn global_opts(&self) -> Option<&GlobalOpts> {
599        Some(&self.global_opts)
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606    use crate::cmd::args_of;
607
608    #[test]
609    fn bin_accessors_update_executable_path() {
610        let mut command = Edit::new("p4", GlobalOpts::new());
611        assert_eq!(command.get_bin(), Path::new("p4"));
612
613        command.set_bin("/usr/local/bin/p4");
614        assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
615
616        let command = command.bin("p4.exe");
617        assert_eq!(command.get_bin(), Path::new("p4.exe"));
618    }
619
620    #[test]
621    fn without_options() {
622        let edit = Edit::new("p4", GlobalOpts::default());
623        let mut cmd = edit.setup_command("p4");
624        cmd.arg("//depot/file.txt");
625        assert_eq!(args_of(&cmd), vec!["edit", "//depot/file.txt"]);
626    }
627
628    #[test]
629    fn change_list() {
630        let edit = Edit::new("p4", GlobalOpts::default()).change_list("14");
631        let mut cmd = edit.setup_command("p4");
632        cmd.arg("//depot/file.txt");
633        assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "//depot/file.txt"]);
634    }
635
636    #[test]
637    fn keep_workspace_and_preview() {
638        let edit = Edit::new("p4", GlobalOpts::default())
639            .keep_workspace(true)
640            .preview(true);
641        let mut cmd = edit.setup_command("p4");
642        cmd.arg("//depot/file.txt");
643        assert_eq!(args_of(&cmd), vec!["edit", "-k", "-n", "//depot/file.txt"]);
644    }
645
646    #[test]
647    fn remote_option_uses_equals_sign() {
648        let edit = Edit::new("p4", GlobalOpts::default()).remote_server("origin");
649        let mut cmd = edit.setup_command("p4");
650        cmd.arg("//depot/file.txt");
651        assert_eq!(
652            args_of(&cmd),
653            vec!["edit", "--remote=origin", "//depot/file.txt"]
654        );
655    }
656
657    #[test]
658    fn filetype() {
659        let edit = Edit::new("p4", GlobalOpts::default()).filetype("text+k");
660        let mut cmd = edit.setup_command("p4");
661        cmd.arg("//depot/file.txt");
662        assert_eq!(
663            args_of(&cmd),
664            vec!["edit", "-t", "text+k", "//depot/file.txt"]
665        );
666    }
667
668    #[test]
669    fn file_mode_transition_preserves_change_list() {
670        let edit = Edit::new("p4", GlobalOpts::default())
671            .change_list("14")
672            .keep_workspace(true);
673        let mut cmd = edit.setup_command("p4");
674        cmd.arg("//depot/file.txt");
675        assert_eq!(
676            args_of(&cmd),
677            vec!["edit", "-c", "14", "-k", "//depot/file.txt"]
678        );
679    }
680
681    #[test]
682    fn file_mode_accessors() {
683        let mut edit = Edit::new("p4", GlobalOpts::default()).keep_workspace(true);
684        edit.set_remote_server("origin").set_preview(true);
685        assert!(edit.get_keep_workspace());
686        assert!(edit.get_preview());
687        assert_eq!(edit.get_remote_server(), Some(&"origin".to_string()));
688        assert_eq!(edit.get_filetype(), None);
689
690        let mut cmd = edit.setup_command("p4");
691        cmd.arg("//depot/file.txt");
692        assert_eq!(
693            args_of(&cmd),
694            vec!["edit", "-k", "-n", "--remote=origin", "//depot/file.txt"]
695        );
696    }
697
698    #[cfg(not(feature = "lt2019_1"))]
699    #[test]
700    fn stream_spec() {
701        let edit = Edit::new("p4", GlobalOpts::default()).edit_stream_spec();
702        let cmd = edit.setup_command("p4");
703        assert_eq!(args_of(&cmd), vec!["edit", "-So"]);
704    }
705
706    #[cfg(not(feature = "lt2019_1"))]
707    #[test]
708    fn stream_spec_with_change_list() {
709        let edit = Edit::new("p4", GlobalOpts::default())
710            .change_list("14")
711            .edit_stream_spec();
712        let cmd = edit.setup_command("p4");
713        assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
714    }
715
716    #[cfg(not(feature = "lt2019_1"))]
717    #[test]
718    fn stream_spec_change_list_after_transition() {
719        let edit = Edit::new("p4", GlobalOpts::default())
720            .edit_stream_spec()
721            .change_list("14");
722        let cmd = edit.setup_command("p4");
723        assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
724    }
725
726    #[test]
727    fn all_options_order() {
728        let edit = Edit::new("p4", GlobalOpts::default())
729            .change_list("14")
730            .keep_workspace(true)
731            .preview(true)
732            .remote_server("origin")
733            .filetype("binary");
734        let mut cmd = edit.setup_command("p4");
735        cmd.arg("//depot/file.txt");
736        assert_eq!(
737            args_of(&cmd),
738            vec![
739                "edit",
740                "-c",
741                "14",
742                "-k",
743                "-n",
744                "--remote=origin",
745                "-t",
746                "binary",
747                "//depot/file.txt"
748            ]
749        );
750    }
751
752    #[test]
753    fn set_style_with_global_opts() {
754        let mut edit = Edit::new("p4", GlobalOpts::default());
755        edit.set_change_list("14");
756        let mut edit = edit.filetype("binary");
757        edit.set_preview(true);
758        let mut cmd = edit.setup_command("p4");
759        cmd.arg("//depot/file.txt");
760        assert_eq!(
761            args_of(&cmd),
762            vec!["edit", "-c", "14", "-n", "-t", "binary", "//depot/file.txt"]
763        );
764    }
765}