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