Skip to main content

perforce_cli/cmd/
add.rs

1use std::{
2    ffi::OsStr,
3    path::{Path, PathBuf},
4    process::{Child, Stdio},
5};
6
7use super::SubCommand;
8
9use crate::{global::GlobalOpts, spawn::ParameterizedSpawn};
10
11/// `p4 [g-opts] add [-c changelist] [-d -f -I -n] [-t filetype] file ...`
12///
13/// Open files in a client workspace for addition to the depot.
14#[derive(Debug, Clone, Default)]
15pub struct Add {
16    bin: PathBuf,
17
18    global_opts: GlobalOpts,
19
20    change_list: Option<String>,
21
22    downgrade: bool,
23
24    force_literal_filenames: bool,
25
26    skip_ignore: bool,
27
28    preview: bool,
29
30    filetype: Option<String>,
31}
32
33impl SubCommand for Add {
34    fn name(&self) -> &str {
35        "add"
36    }
37
38    fn inject_local_args(&self, command: &mut std::process::Command) {
39        if let Some(change_list) = self.change_list.as_ref() {
40            command.arg("-c").arg(change_list);
41        }
42        if self.downgrade {
43            command.arg("-d");
44        }
45        if self.force_literal_filenames {
46            command.arg("-f");
47        }
48        if self.skip_ignore {
49            command.arg("-I");
50        }
51        if self.preview {
52            command.arg("-n");
53        }
54        if let Some(filetype) = self.filetype.as_ref() {
55            command.arg("-t").arg(filetype);
56        }
57    }
58
59    fn global_opts(&self) -> Option<&GlobalOpts> {
60        Some(&self.global_opts)
61    }
62}
63
64impl<S, I> ParameterizedSpawn<(S,)> for Add
65where
66    S: IntoIterator<Item = I>,
67    I: AsRef<OsStr>,
68{
69    type Output = Child;
70    type Error = std::io::Error;
71
72    /// Spawns `p4 add` for the given files as a child process.
73    ///
74    /// The child process inherits the standard input, output, and error
75    /// streams of the current process, and runs asynchronously; use the
76    /// returned [`Child`] handle to wait for it or interact with it.
77    fn spawn_with(&mut self, (input,): (S,)) -> Result<Self::Output, Self::Error> {
78        self.setup_command(&self.bin)
79            .args(input)
80            .stdout(Stdio::piped())
81            .stderr(Stdio::piped())
82            .spawn()
83    }
84}
85
86impl Add {
87    /// Get the Perforce command path.
88    pub fn get_bin(&self) -> &Path {
89        &self.bin
90    }
91
92    /// Set the Perforce command path.
93    pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
94        self.bin = bin.into();
95        self
96    }
97
98    /// Set the Perforce command path.
99    pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
100        self.set_bin(bin);
101        self
102    }
103
104    /// Open files in a client workspace for addition to the depot.
105    ///
106    /// `bin` is the path to the Perforce command-line executable.
107    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
108        Self {
109            bin: bin.into(),
110            global_opts,
111            ..Default::default()
112        }
113    }
114
115    /// # Description
116    ///
117    /// g-opts
118    ///
119    #[cfg_attr(
120        feature = "lt2014_2",
121        doc = "See the [Global Options](GlobalOpts) section."
122    )]
123    #[cfg_attr(
124        all(feature = "lt2015_1", not(feature = "lt2014_2")),
125        doc = "See the [“Global Options”](GlobalOpts) section."
126    )]
127    #[cfg_attr(
128        all(feature = "lt2017_1", not(feature = "lt2015_1")),
129        doc = "See [“Global Options”](GlobalOpts)."
130    )]
131    #[cfg_attr(
132        all(feature = "lt2018_2", not(feature = "lt2017_1")),
133        doc = "See [Global Options](GlobalOpts)."
134    )]
135    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
136    pub fn get_global_opts(&self) -> &GlobalOpts {
137        &self.global_opts
138    }
139
140    /// # Description
141    ///
142    /// g-opts
143    ///
144    #[cfg_attr(
145        feature = "lt2014_2",
146        doc = "See the [Global Options](GlobalOpts) section."
147    )]
148    #[cfg_attr(
149        all(feature = "lt2015_1", not(feature = "lt2014_2")),
150        doc = "See the [“Global Options”](GlobalOpts) section."
151    )]
152    #[cfg_attr(
153        all(feature = "lt2017_1", not(feature = "lt2015_1")),
154        doc = "See [“Global Options”](GlobalOpts)."
155    )]
156    #[cfg_attr(
157        all(feature = "lt2018_2", not(feature = "lt2017_1")),
158        doc = "See [Global Options](GlobalOpts)."
159    )]
160    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
161    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
162        self.global_opts = v;
163        self
164    }
165
166    /// # Description
167    ///
168    /// g-opts
169    ///
170    #[cfg_attr(
171        feature = "lt2014_2",
172        doc = "See the [Global Options](GlobalOpts) section."
173    )]
174    #[cfg_attr(
175        all(feature = "lt2015_1", not(feature = "lt2014_2")),
176        doc = "See the [“Global Options”](GlobalOpts) section."
177    )]
178    #[cfg_attr(
179        all(feature = "lt2017_1", not(feature = "lt2015_1")),
180        doc = "See [“Global Options”](GlobalOpts)."
181    )]
182    #[cfg_attr(
183        all(feature = "lt2018_2", not(feature = "lt2017_1")),
184        doc = "See [Global Options](GlobalOpts)."
185    )]
186    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
187    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
188        self.global_opts = v;
189        self
190    }
191
192    /// # Description
193    ///
194    /// -c changelist
195    ///
196    /// Opens the files for add within the specified changelist. If this
197    #[cfg_attr(feature = "lt2014_2", doc = "flag is")]
198    #[cfg_attr(not(feature = "lt2014_2"), doc = "option is")]
199    /// not used, the files are linked to the default changelist.
200    pub fn get_change_list(&self) -> Option<&String> {
201        self.change_list.as_ref()
202    }
203
204    /// # Description
205    ///
206    /// -c changelist
207    ///
208    /// Opens the files for add within the specified changelist. If this
209    #[cfg_attr(feature = "lt2014_2", doc = "flag is")]
210    #[cfg_attr(not(feature = "lt2014_2"), doc = "option is")]
211    /// not used, the files are linked to the default changelist.
212    pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
213        self.change_list = Some(v.into());
214        self
215    }
216
217    /// # Description
218    ///
219    /// -c changelist
220    ///
221    /// Opens the files for add within the specified changelist. If this
222    #[cfg_attr(feature = "lt2014_2", doc = "flag is")]
223    #[cfg_attr(not(feature = "lt2014_2"), doc = "option is")]
224    /// not used, the files are linked to the default changelist.
225    pub fn change_list(mut self, v: impl Into<String>) -> Self {
226        self.change_list = Some(v.into());
227        self
228    }
229
230    /// # Description
231    ///
232    /// -d
233    ///
234    /// Downgrade file open status to simple add.
235    pub fn get_downgrade(&self) -> bool {
236        self.downgrade
237    }
238    /// # Description
239    ///
240    /// -d
241    ///
242    /// Downgrade file open status to simple add.
243    pub fn set_downgrade(&mut self, v: bool) -> &mut Self {
244        self.downgrade = v;
245        self
246    }
247    /// # Description
248    ///
249    /// -d
250    ///
251    /// Downgrade file open status to simple add.
252    pub fn downgrade(mut self, v: bool) -> Self {
253        self.downgrade = v;
254        self
255    }
256
257    /// # Description
258    ///
259    /// -f
260    ///
261    /// Use the -f
262    #[cfg_attr(
263        feature = "lt2014_2",
264        doc = "flag to force inclusion of wildcards in filenames. See the",
265        doc = "File Specifications chapter for details."
266    )]
267    #[cfg_attr(
268        all(feature = "lt2015_1", not(feature = "lt2014_2")),
269        doc = "option to force inclusion of wildcards in filenames. See the",
270        doc = "“File Specifications” chapter for details."
271    )]
272    #[cfg_attr(
273        all(feature = "lt2017_1", not(feature = "lt2015_1")),
274        doc = "option to force inclusion of wildcards in filenames. See",
275        doc = "“File Specifications” for details."
276    )]
277    #[cfg_attr(
278        all(feature = "lt2018_2", not(feature = "lt2017_1")),
279        doc = "option to force inclusion of wildcards in filenames. See File",
280        doc = "Specifications for details."
281    )]
282    #[cfg_attr(
283        all(feature = "lt2025_1", not(feature = "lt2018_2")),
284        doc = "option to force inclusion of wildcards in filenames. See File",
285        doc = "specifications for details."
286    )]
287    #[cfg_attr(
288        not(feature = "lt2025_1"),
289        doc = "option to force inclusion of wildcards in filenames. See File",
290        doc = "specifications for details.",
291        doc = "",
292        doc = "Filenames that contain the special characters '@', '#', '%', or '*' are",
293        doc = "reformatted to encode the characters using ASCII hexadecimal",
294        doc = "representation. After the files are added, refer to them using the",
295        doc = "reformatted file name instead of the local file system name."
296    )]
297    pub fn get_force_literal_filenames(&self) -> bool {
298        self.force_literal_filenames
299    }
300
301    /// # Description
302    ///
303    /// -f
304    ///
305    /// Use the -f
306    #[cfg_attr(
307        feature = "lt2014_2",
308        doc = "flag to force inclusion of wildcards in filenames. See the",
309        doc = "File Specifications chapter for details."
310    )]
311    #[cfg_attr(
312        all(feature = "lt2015_1", not(feature = "lt2014_2")),
313        doc = "option to force inclusion of wildcards in filenames. See the",
314        doc = "“File Specifications” chapter for details."
315    )]
316    #[cfg_attr(
317        all(feature = "lt2017_1", not(feature = "lt2015_1")),
318        doc = "option to force inclusion of wildcards in filenames. See",
319        doc = "“File Specifications” for details."
320    )]
321    #[cfg_attr(
322        all(feature = "lt2018_2", not(feature = "lt2017_1")),
323        doc = "option to force inclusion of wildcards in filenames. See File",
324        doc = "Specifications for details."
325    )]
326    #[cfg_attr(
327        all(feature = "lt2025_1", not(feature = "lt2018_2")),
328        doc = "option to force inclusion of wildcards in filenames. See File",
329        doc = "specifications for details."
330    )]
331    #[cfg_attr(
332        not(feature = "lt2025_1"),
333        doc = "option to force inclusion of wildcards in filenames. See File",
334        doc = "specifications for details.",
335        doc = "",
336        doc = "Filenames that contain the special characters '@', '#', '%', or '*' are",
337        doc = "reformatted to encode the characters using ASCII hexadecimal",
338        doc = "representation. After the files are added, refer to them using the",
339        doc = "reformatted file name instead of the local file system name."
340    )]
341    pub fn set_force_literal_filenames(&mut self, v: bool) -> &mut Self {
342        self.force_literal_filenames = v;
343        self
344    }
345
346    /// # Description
347    ///
348    /// -f
349    ///
350    /// Use the -f
351    #[cfg_attr(
352        feature = "lt2014_2",
353        doc = "flag to force inclusion of wildcards in filenames. See the",
354        doc = "File Specifications chapter for details."
355    )]
356    #[cfg_attr(
357        all(feature = "lt2015_1", not(feature = "lt2014_2")),
358        doc = "option to force inclusion of wildcards in filenames. See the",
359        doc = "“File Specifications” chapter for details."
360    )]
361    #[cfg_attr(
362        all(feature = "lt2017_1", not(feature = "lt2015_1")),
363        doc = "option to force inclusion of wildcards in filenames. See",
364        doc = "“File Specifications” for details."
365    )]
366    #[cfg_attr(
367        all(feature = "lt2018_2", not(feature = "lt2017_1")),
368        doc = "option to force inclusion of wildcards in filenames. See File",
369        doc = "Specifications for details."
370    )]
371    #[cfg_attr(
372        all(feature = "lt2025_1", not(feature = "lt2018_2")),
373        doc = "option to force inclusion of wildcards in filenames. See File",
374        doc = "specifications for details."
375    )]
376    #[cfg_attr(
377        not(feature = "lt2025_1"),
378        doc = "option to force inclusion of wildcards in filenames. See File",
379        doc = "specifications for details.",
380        doc = "",
381        doc = "Filenames that contain the special characters '@', '#', '%', or '*' are",
382        doc = "reformatted to encode the characters using ASCII hexadecimal",
383        doc = "representation. After the files are added, refer to them using the",
384        doc = "reformatted file name instead of the local file system name."
385    )]
386    pub fn force_literal_filenames(mut self, v: bool) -> Self {
387        self.force_literal_filenames = v;
388        self
389    }
390
391    /// # Description
392    ///
393    /// -I
394    ///
395    /// Do not perform any ignore checking; ignore any settings specified by
396    /// P4IGNORE.
397    pub fn get_skip_ignore(&self) -> bool {
398        self.skip_ignore
399    }
400    /// # Description
401    ///
402    /// -I
403    ///
404    /// Do not perform any ignore checking; ignore any settings specified by
405    /// P4IGNORE.
406    pub fn set_skip_ignore(&mut self, v: bool) -> &mut Self {
407        self.skip_ignore = v;
408        self
409    }
410    /// # Description
411    ///
412    /// -I
413    ///
414    /// Do not perform any ignore checking; ignore any settings specified by
415    /// P4IGNORE.
416    pub fn skip_ignore(mut self, v: bool) -> Self {
417        self.skip_ignore = v;
418        self
419    }
420
421    /// # Description
422    ///
423    /// -n
424    ///
425    /// Preview which files would be opened for add, without actually changing
426    /// any files or metadata.
427    pub fn get_preview(&self) -> bool {
428        self.preview
429    }
430    /// # Description
431    ///
432    /// -n
433    ///
434    /// Preview which files would be opened for add, without actually changing
435    /// any files or metadata.
436    pub fn set_preview(&mut self, v: bool) -> &mut Self {
437        self.preview = v;
438        self
439    }
440    /// # Description
441    ///
442    /// -n
443    ///
444    /// Preview which files would be opened for add, without actually changing
445    /// any files or metadata.
446    pub fn preview(mut self, v: bool) -> Self {
447        self.preview = v;
448        self
449    }
450
451    /// # Description
452    ///
453    /// -t filetype
454    ///
455    /// Adds the file as the specified filetype, overriding any settings in the
456    /// typemap table.
457    #[cfg_attr(
458        feature = "lt2014_2",
459        doc = "Please see the File Types chapter for a list of Perforce",
460        doc = "file types."
461    )]
462    #[cfg_attr(
463        all(feature = "lt2015_1", not(feature = "lt2014_2")),
464        doc = "Please see the “File Types” chapter for a list of",
465        doc = "Perforce file types."
466    )]
467    #[cfg_attr(
468        all(feature = "lt2017_1", not(feature = "lt2015_1")),
469        doc = "See “File Types” for a list of Perforce file types."
470    )]
471    #[cfg_attr(
472        all(feature = "lt2017_2", not(feature = "lt2017_1")),
473        doc = "See File Types for a list of Perforce file types."
474    )]
475    #[cfg_attr(
476        all(feature = "lt2018_2", not(feature = "lt2017_2")),
477        doc = "See File Types for a list of Helix Server file types."
478    )]
479    #[cfg_attr(
480        all(feature = "lt2019_1", not(feature = "lt2018_2")),
481        doc = "See File types for a list of Helix Server file types."
482    )]
483    #[cfg_attr(
484        all(feature = "lt2021_1", not(feature = "lt2019_1")),
485        doc = "See File types for a list of Helix server file types."
486    )]
487    #[cfg_attr(
488        all(feature = "lt2024_1", not(feature = "lt2021_1")),
489        doc = "See File types for a list of Helix Server file types."
490    )]
491    #[cfg_attr(
492        all(feature = "lt2025_2", not(feature = "lt2024_1")),
493        doc = "See File types as well as the lbr.autocompress",
494        doc = "configurable."
495    )]
496    #[cfg_attr(
497        not(feature = "lt2025_2"),
498        doc = "See Base file types and modifiers as well as the",
499        doc = "lbr.autocompress configurable."
500    )]
501    pub fn get_filetype(&self) -> Option<&String> {
502        self.filetype.as_ref()
503    }
504
505    /// # Description
506    ///
507    /// -t filetype
508    ///
509    /// Adds the file as the specified filetype, overriding any settings in the
510    /// typemap table.
511    #[cfg_attr(
512        feature = "lt2014_2",
513        doc = "Please see the File Types chapter for a list of Perforce",
514        doc = "file types."
515    )]
516    #[cfg_attr(
517        all(feature = "lt2015_1", not(feature = "lt2014_2")),
518        doc = "Please see the “File Types” chapter for a list of",
519        doc = "Perforce file types."
520    )]
521    #[cfg_attr(
522        all(feature = "lt2017_1", not(feature = "lt2015_1")),
523        doc = "See “File Types” for a list of Perforce file types."
524    )]
525    #[cfg_attr(
526        all(feature = "lt2017_2", not(feature = "lt2017_1")),
527        doc = "See File Types for a list of Perforce file types."
528    )]
529    #[cfg_attr(
530        all(feature = "lt2018_2", not(feature = "lt2017_2")),
531        doc = "See File Types for a list of Helix Server file types."
532    )]
533    #[cfg_attr(
534        all(feature = "lt2019_1", not(feature = "lt2018_2")),
535        doc = "See File types for a list of Helix Server file types."
536    )]
537    #[cfg_attr(
538        all(feature = "lt2021_1", not(feature = "lt2019_1")),
539        doc = "See File types for a list of Helix server file types."
540    )]
541    #[cfg_attr(
542        all(feature = "lt2024_1", not(feature = "lt2021_1")),
543        doc = "See File types for a list of Helix Server file types."
544    )]
545    #[cfg_attr(
546        all(feature = "lt2025_2", not(feature = "lt2024_1")),
547        doc = "See File types as well as the lbr.autocompress",
548        doc = "configurable."
549    )]
550    #[cfg_attr(
551        not(feature = "lt2025_2"),
552        doc = "See Base file types and modifiers as well as the",
553        doc = "lbr.autocompress configurable."
554    )]
555    pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
556        self.filetype = Some(v.into());
557        self
558    }
559
560    /// # Description
561    ///
562    /// -t filetype
563    ///
564    /// Adds the file as the specified filetype, overriding any settings in the
565    /// typemap table.
566    #[cfg_attr(
567        feature = "lt2014_2",
568        doc = "Please see the File Types chapter for a list of Perforce",
569        doc = "file types."
570    )]
571    #[cfg_attr(
572        all(feature = "lt2015_1", not(feature = "lt2014_2")),
573        doc = "Please see the “File Types” chapter for a list of",
574        doc = "Perforce file types."
575    )]
576    #[cfg_attr(
577        all(feature = "lt2017_1", not(feature = "lt2015_1")),
578        doc = "See “File Types” for a list of Perforce file types."
579    )]
580    #[cfg_attr(
581        all(feature = "lt2017_2", not(feature = "lt2017_1")),
582        doc = "See File Types for a list of Perforce file types."
583    )]
584    #[cfg_attr(
585        all(feature = "lt2018_2", not(feature = "lt2017_2")),
586        doc = "See File Types for a list of Helix Server file types."
587    )]
588    #[cfg_attr(
589        all(feature = "lt2019_1", not(feature = "lt2018_2")),
590        doc = "See File types for a list of Helix Server file types."
591    )]
592    #[cfg_attr(
593        all(feature = "lt2021_1", not(feature = "lt2019_1")),
594        doc = "See File types for a list of Helix server file types."
595    )]
596    #[cfg_attr(
597        all(feature = "lt2024_1", not(feature = "lt2021_1")),
598        doc = "See File types for a list of Helix Server file types."
599    )]
600    #[cfg_attr(
601        all(feature = "lt2025_2", not(feature = "lt2024_1")),
602        doc = "See File types as well as the lbr.autocompress",
603        doc = "configurable."
604    )]
605    #[cfg_attr(
606        not(feature = "lt2025_2"),
607        doc = "See Base file types and modifiers as well as the",
608        doc = "lbr.autocompress configurable."
609    )]
610    pub fn filetype(mut self, v: impl Into<String>) -> Self {
611        self.filetype = Some(v.into());
612        self
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    use super::*;
619    use crate::cmd::args_of;
620
621    #[test]
622    fn bin_accessors_update_executable_path() {
623        let mut command = Add::new("p4", GlobalOpts::new());
624        assert_eq!(command.get_bin(), Path::new("p4"));
625
626        command.set_bin("/usr/local/bin/p4");
627        assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
628
629        let command = command.bin("p4.exe");
630        assert_eq!(command.get_bin(), Path::new("p4.exe"));
631    }
632
633    /// Dry-run check of the assembled `p4 add` command line; no process is
634    /// spawned.
635    #[test]
636    fn without_options() {
637        let add = Add::new("p4", GlobalOpts::new());
638
639        let command = add.setup_command("p4");
640
641        assert_eq!(command.get_program(), OsStr::new("p4"));
642        assert_eq!(args_of(&command), ["add"]);
643    }
644
645    #[test]
646    fn all_local_options() {
647        let mut add = Add::new("p4", GlobalOpts::new());
648        add.set_change_list("42")
649            .set_downgrade(true)
650            .set_force_literal_filenames(true)
651            .set_skip_ignore(true)
652            .set_preview(true)
653            .set_filetype("text");
654
655        assert_eq!(
656            args_of(&add.setup_command("p4")),
657            ["add", "-c", "42", "-d", "-f", "-I", "-n", "-t", "text"]
658        );
659    }
660}