Skip to main content

perforce_cli/cmd/
add.rs

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