p4_cmd/
sync.rs

1use std::path;
2use std::vec;
3
4use error;
5use p4;
6
7/// Synchronize the client with its view of the depot
8///
9/// Sync updates the client workspace to reflect its current view (if
10/// it has changed) and the current contents of the depot (if it has
11/// changed). The client view maps client and depot file names and
12/// locations.
13///
14/// Sync adds files that are in the client view and have not been
15/// retrieved before.  Sync deletes previously retrieved files that
16/// are no longer in the client view or have been deleted from the
17/// depot.  Sync updates files that are still in the client view and
18/// have been updated in the depot.
19///
20/// By default, sync affects all files in the client workspace. If file
21/// arguments are given, sync limits its operation to those files.
22/// The file arguments can contain wildcards.
23///
24/// If the file argument includes a revision specifier, then the given
25/// revision is retrieved.  Normally, the head revision is retrieved.
26///
27/// If the file argument includes a revision range specification,
28/// only files selected by the revision range are updated, and the
29/// highest revision in the range is used.
30///
31/// See 'p4 help revisions' for help specifying revisions or ranges.
32///
33/// Normally, sync does not overwrite workspace files that the user has
34/// manually made writable.  Setting the 'clobber' option in the
35/// client specification disables this safety check.
36///
37/// # Examples
38///
39/// ```rust,no_run
40/// let p4 = p4_cmd::P4::new();
41/// let dirs = p4.sync("//depot/dir/*").run().unwrap();
42/// for dir in dirs {
43///     println!("{:?}", dir);
44/// }
45/// ```
46#[derive(Debug, Clone)]
47pub struct SyncCommand<'p, 'f> {
48    connection: &'p p4::P4,
49    file: Vec<&'f str>,
50
51    force: bool,
52    preview: bool,
53    server_only: bool,
54    client_only: bool,
55    verify: bool,
56    max_files: Option<usize>,
57    parallel: Option<usize>,
58}
59
60impl<'p, 'f> SyncCommand<'p, 'f> {
61    pub fn new(connection: &'p p4::P4, file: &'f str) -> Self {
62        Self {
63            connection: connection,
64            file: vec![file],
65            force: false,
66            preview: false,
67            server_only: false,
68            client_only: false,
69            verify: false,
70            max_files: None,
71            parallel: None,
72        }
73    }
74
75    pub fn file(mut self, dir: &'f str) -> Self {
76        self.file.push(dir);
77        self
78    }
79
80    /// The -f flag forces resynchronization even if the client already
81    /// has the file, and overwriting any writable files.  This flag doesn't
82    /// affect open files.
83    pub fn force(mut self, force: bool) -> Self {
84        self.force = force;
85        self
86    }
87
88    /// The -n flag previews the operation without updating the workspace.
89    pub fn preview(mut self, preview: bool) -> Self {
90        self.preview = preview;
91        self
92    }
93
94    /// The -k flag updates server metadata without syncing files. It is
95    /// intended to enable you to ensure that the server correctly reflects
96    /// the state of files in the workspace while avoiding a large data
97    /// transfer. Caution: an erroneous update can cause the server to
98    /// incorrectly reflect the state of the workspace.
99    pub fn server_only(mut self, server_only: bool) -> Self {
100        self.server_only = server_only;
101        self
102    }
103
104    /// The -p flag populates the client workspace, but does not update the
105    /// server to reflect those updates.  Any file that is already synced or
106    /// opened will be bypassed with a warning message.  This option is very
107    /// useful for build clients or when publishing content without the
108    /// need to track the state of the client workspace.
109    pub fn client_only(mut self, client_only: bool) -> Self {
110        self.client_only = client_only;
111        self
112    }
113
114    /// The -s flag adds a safety check before sending content to the client
115    /// workspace.  This check uses MD5 digests to compare the content on the
116    /// clients workspace against content that was last synced.  If the file
117    /// has been modified outside of Perforce's control then an error message
118    /// is displayed and the file is not overwritten.  This check adds some
119    /// extra processing which will affect the performance of the operation.
120    /// Clients with 'allwrite' and 'noclobber' set do this check by default.
121    pub fn verify(mut self, verify: bool) -> Self {
122        self.verify = verify;
123        self
124    }
125
126    /// The -m flag limits sync to the first 'max' number of files. This
127    /// option is useful in conjunction with tagged output and the '-n'
128    /// flag, to preview how many files will be synced without transferring
129    /// all the file data.
130    pub fn max_files(mut self, max_files: usize) -> Self {
131        self.max_files = Some(max_files);
132        self
133    }
134
135    /// The --parallel flag specifies options for parallel file transfer. If
136    /// your administrator has enabled parallel file transfer by setting the
137    /// net.parallel.max configurable, and if there are sufficient resources
138    /// across the system, a sync command may execute more rapidly by
139    /// transferring multiple files in parallel. Specify threads=N to request
140    /// files be sent concurrently, using N independent network connections.
141    /// The N threads grab work in batches; specify batch=N to control the
142    /// number of files in a batch, or batchsize=N to control the number of
143    /// bytes in a batch. A sync that is too small will not initiate parallel
144    /// file transfers; specify min=N to control the minimum number of files
145    /// in a parallel sync, or minsize=N to control the minimum number of
146    /// bytes in a parallel sync. Requesting progress indicators causes the
147    /// --parallel flag to be ignored.
148    ///
149    /// Auto parallel sync may be enabled by setting the net.parallel.threads
150    /// configurable to the desired number of threads to be used by all sync
151    /// commands. This value must be less than or equal to the value of
152    /// net.parallel.max. Other net.parallel.* configurables may be specified
153    /// as well, but are not required. See 'p4 help configurables' to see
154    /// the options and their defaults. Auto parallel sync is turned off by
155    /// unsetting the net.parallel.threads configurable. A user may override
156    /// the configured auto parallel sync options on the command line, or may
157    /// disable it via 'p4 sync --parallel=0'.
158    pub fn parallel(mut self, parallel: usize) -> Self {
159        self.parallel = Some(parallel);
160        self
161    }
162
163    /// Run the `sync` command.
164    pub fn run(self) -> Result<Files, error::P4Error> {
165        let mut cmd = self.connection.connect_with_retries(None);
166        cmd.arg("sync");
167        if self.force {
168            cmd.arg("-f");
169        }
170        if self.preview {
171            cmd.arg("-n");
172        }
173        if self.server_only {
174            cmd.arg("-k");
175        }
176        if self.client_only {
177            cmd.arg("-p");
178        }
179        if self.verify {
180            cmd.arg("-s");
181        }
182        if let Some(max_files) = self.max_files {
183            let max_files = format!("{}", max_files);
184            cmd.args(&["-m", &max_files]);
185        }
186        if let Some(parallel) = self.parallel {
187            let parallel = format!("{}", parallel);
188            cmd.args(&["--parallel", &parallel]);
189        }
190        for file in self.file {
191            cmd.arg(file);
192        }
193        let data = cmd.output().map_err(|e| {
194            error::ErrorKind::SpawnFailed
195                .error()
196                .set_cause(e)
197                .set_context(format!("Command: {:?}", cmd))
198        })?;
199        let (_remains, (mut items, exit)) = files_parser::files(&data.stdout).map_err(|_| {
200            error::ErrorKind::ParseFailed
201                .error()
202                .set_context(format!("Command: {:?}", cmd))
203        })?;
204        items.push(exit);
205        Ok(Files(items))
206    }
207}
208
209pub type FileItem = error::Item<File>;
210
211pub struct Files(Vec<FileItem>);
212
213impl IntoIterator for Files {
214    type Item = FileItem;
215    type IntoIter = FilesIntoIter;
216
217    fn into_iter(self) -> FilesIntoIter {
218        FilesIntoIter(self.0.into_iter())
219    }
220}
221
222#[derive(Debug)]
223pub struct FilesIntoIter(vec::IntoIter<FileItem>);
224
225impl Iterator for FilesIntoIter {
226    type Item = FileItem;
227
228    #[inline]
229    fn next(&mut self) -> Option<FileItem> {
230        self.0.next()
231    }
232
233    #[inline]
234    fn size_hint(&self) -> (usize, Option<usize>) {
235        self.0.size_hint()
236    }
237
238    #[inline]
239    fn count(self) -> usize {
240        self.0.count()
241    }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum FileContent {
246    #[doc(hidden)]
247    __Nonexhaustive,
248
249    Text(Vec<String>),
250    Binary(Vec<u8>),
251}
252
253impl FileContent {
254    pub fn as_text(&self) -> Option<&[String]> {
255        match self {
256            FileContent::Text(c) => Some(&c),
257            _ => None,
258        }
259    }
260
261    pub fn as_binary(&self) -> Option<&[u8]> {
262        match self {
263            FileContent::Binary(c) => Some(&c),
264            _ => None,
265        }
266    }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
270pub struct File {
271    pub depot_file: String,
272    pub client_file: path::PathBuf,
273    pub rev: usize,
274    pub action: p4::Action,
275    pub file_size: usize,
276    non_exhaustive: (),
277}
278
279mod files_parser {
280    use super::*;
281
282    use super::super::parser::*;
283
284    named!(pub file<&[u8], File>,
285        do_parse!(
286            depot_file: depot_file >>
287            client_file: client_file >>
288            rev: rev >>
289            action: action >>
290            file_size: file_size >>
291            _ignore: opt!(delimited!(ignore_info1, ignore_info1, change)) >>
292            (
293                File {
294                    depot_file: depot_file.path.to_owned(),
295                    client_file: path::PathBuf::from(client_file.path),
296                    rev: rev.rev,
297                    action: action.action.parse().expect("`Unknown` to capture all"),
298                    file_size: file_size.size,
299                    non_exhaustive: (),
300                }
301            )
302        )
303    );
304
305    named!(item<&[u8], FileItem>,
306        alt!(
307            map!(file, data_to_item) |
308            map!(error, error_to_item) |
309            map!(info, info_to_item)
310        )
311    );
312
313    named!(pub files<&[u8], (Vec<FileItem>, FileItem)>,
314        pair!(
315            many0!(item),
316            map!(exit, exit_to_item)
317        )
318    );
319}
320
321#[cfg(test)]
322mod test {
323    use super::*;
324
325    #[test]
326    fn sync_single() {
327        let output: &[u8] = br#"info1: depotFile //depot/dir/file
328info1: clientFile /home/user/depot/dir/file
329info1: rev 1
330info1: action added
331info1: fileSize 1016
332info1: totalFileSize 865153
333info1: totalFileCount 24
334info1: change 25662947
335exit: 0
336"#;
337        let (_remains, (items, exit)) = files_parser::files(output).unwrap();
338        let first = items[0].as_data().unwrap();
339        assert_eq!(first.depot_file, "//depot/dir/file");
340        assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
341    }
342
343    #[test]
344    fn sync_multi() {
345        let output: &[u8] = br#"info1: depotFile //depot/dir/file
346info1: clientFile /home/user/depot/dir/file
347info1: rev 1
348info1: action added
349info1: fileSize 1016
350info1: totalFileSize 865153
351info1: totalFileCount 24
352info1: change 25662947
353info1: depotFile //depot/dir/file1
354info1: clientFile /home/user/depot/dir/file1
355info1: rev 1
356info1: action added
357info1: fileSize 729154
358exit: 0
359"#;
360        let (_remains, (items, exit)) = files_parser::files(output).unwrap();
361        let first = items[0].as_data().unwrap();
362        let last = items[1].as_data().unwrap();
363        assert_eq!(first.depot_file, "//depot/dir/file");
364        assert_eq!(last.depot_file, "//depot/dir/file1");
365        assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
366    }
367}