1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
use std::path;
use std::vec;

use error;
use p4;

/// Synchronize the client with its view of the depot
///
/// Sync updates the client workspace to reflect its current view (if
/// it has changed) and the current contents of the depot (if it has
/// changed). The client view maps client and depot file names and
/// locations.
///
/// Sync adds files that are in the client view and have not been
/// retrieved before.  Sync deletes previously retrieved files that
/// are no longer in the client view or have been deleted from the
/// depot.  Sync updates files that are still in the client view and
/// have been updated in the depot.
///
/// By default, sync affects all files in the client workspace. If file
/// arguments are given, sync limits its operation to those files.
/// The file arguments can contain wildcards.
///
/// If the file argument includes a revision specifier, then the given
/// revision is retrieved.  Normally, the head revision is retrieved.
///
/// If the file argument includes a revision range specification,
/// only files selected by the revision range are updated, and the
/// highest revision in the range is used.
///
/// See 'p4 help revisions' for help specifying revisions or ranges.
///
/// Normally, sync does not overwrite workspace files that the user has
/// manually made writable.  Setting the 'clobber' option in the
/// client specification disables this safety check.
///
/// # Examples
///
/// ```rust,no_run
/// let p4 = p4_cmd::P4::new();
/// let dirs = p4.sync("//depot/dir/*").run().unwrap();
/// for dir in dirs {
///     println!("{:?}", dir);
/// }
/// ```
#[derive(Debug, Clone)]
pub struct SyncCommand<'p, 'f> {
    connection: &'p p4::P4,
    file: Vec<&'f str>,

    force: bool,
    preview: bool,
    server_only: bool,
    client_only: bool,
    verify: bool,
    max_files: Option<usize>,
    parallel: Option<usize>,
}

impl<'p, 'f> SyncCommand<'p, 'f> {
    pub fn new(connection: &'p p4::P4, file: &'f str) -> Self {
        Self {
            connection: connection,
            file: vec![file],
            force: false,
            preview: false,
            server_only: false,
            client_only: false,
            verify: false,
            max_files: None,
            parallel: None,
        }
    }

    pub fn file(mut self, dir: &'f str) -> Self {
        self.file.push(dir);
        self
    }

    /// The -f flag forces resynchronization even if the client already
    /// has the file, and overwriting any writable files.  This flag doesn't
    /// affect open files.
    pub fn force(mut self, force: bool) -> Self {
        self.force = force;
        self
    }

    /// The -n flag previews the operation without updating the workspace.
    pub fn preview(mut self, preview: bool) -> Self {
        self.preview = preview;
        self
    }

    /// The -k flag updates server metadata without syncing files. It is
    /// intended to enable you to ensure that the server correctly reflects
    /// the state of files in the workspace while avoiding a large data
    /// transfer. Caution: an erroneous update can cause the server to
    /// incorrectly reflect the state of the workspace.
    pub fn server_only(mut self, server_only: bool) -> Self {
        self.server_only = server_only;
        self
    }

    /// The -p flag populates the client workspace, but does not update the
    /// server to reflect those updates.  Any file that is already synced or
    /// opened will be bypassed with a warning message.  This option is very
    /// useful for build clients or when publishing content without the
    /// need to track the state of the client workspace.
    pub fn client_only(mut self, client_only: bool) -> Self {
        self.client_only = client_only;
        self
    }

    /// The -s flag adds a safety check before sending content to the client
    /// workspace.  This check uses MD5 digests to compare the content on the
    /// clients workspace against content that was last synced.  If the file
    /// has been modified outside of Perforce's control then an error message
    /// is displayed and the file is not overwritten.  This check adds some
    /// extra processing which will affect the performance of the operation.
    /// Clients with 'allwrite' and 'noclobber' set do this check by default.
    pub fn verify(mut self, verify: bool) -> Self {
        self.verify = verify;
        self
    }

    /// The -m flag limits sync to the first 'max' number of files. This
    /// option is useful in conjunction with tagged output and the '-n'
    /// flag, to preview how many files will be synced without transferring
    /// all the file data.
    pub fn max_files(mut self, max_files: usize) -> Self {
        self.max_files = Some(max_files);
        self
    }

    /// The --parallel flag specifies options for parallel file transfer. If
    /// your administrator has enabled parallel file transfer by setting the
    /// net.parallel.max configurable, and if there are sufficient resources
    /// across the system, a sync command may execute more rapidly by
    /// transferring multiple files in parallel. Specify threads=N to request
    /// files be sent concurrently, using N independent network connections.
    /// The N threads grab work in batches; specify batch=N to control the
    /// number of files in a batch, or batchsize=N to control the number of
    /// bytes in a batch. A sync that is too small will not initiate parallel
    /// file transfers; specify min=N to control the minimum number of files
    /// in a parallel sync, or minsize=N to control the minimum number of
    /// bytes in a parallel sync. Requesting progress indicators causes the
    /// --parallel flag to be ignored.
    ///
    /// Auto parallel sync may be enabled by setting the net.parallel.threads
    /// configurable to the desired number of threads to be used by all sync
    /// commands. This value must be less than or equal to the value of
    /// net.parallel.max. Other net.parallel.* configurables may be specified
    /// as well, but are not required. See 'p4 help configurables' to see
    /// the options and their defaults. Auto parallel sync is turned off by
    /// unsetting the net.parallel.threads configurable. A user may override
    /// the configured auto parallel sync options on the command line, or may
    /// disable it via 'p4 sync --parallel=0'.
    pub fn parallel(mut self, parallel: usize) -> Self {
        self.parallel = Some(parallel);
        self
    }

    /// Run the `sync` command.
    pub fn run(self) -> Result<Files, error::P4Error> {
        let mut cmd = self.connection.connect_with_retries(None);
        cmd.arg("sync");
        if self.force {
            cmd.arg("-f");
        }
        if self.preview {
            cmd.arg("-n");
        }
        if self.server_only {
            cmd.arg("-k");
        }
        if self.client_only {
            cmd.arg("-p");
        }
        if self.verify {
            cmd.arg("-s");
        }
        if let Some(max_files) = self.max_files {
            let max_files = format!("{}", max_files);
            cmd.args(&["-m", &max_files]);
        }
        if let Some(parallel) = self.parallel {
            let parallel = format!("{}", parallel);
            cmd.args(&["--parallel", &parallel]);
        }
        for file in self.file {
            cmd.arg(file);
        }
        let data = cmd.output().map_err(|e| {
            error::ErrorKind::SpawnFailed
                .error()
                .set_cause(e)
                .set_context(format!("Command: {:?}", cmd))
        })?;
        let (_remains, (mut items, exit)) = files_parser::files(&data.stdout).map_err(|_| {
            error::ErrorKind::ParseFailed
                .error()
                .set_context(format!("Command: {:?}", cmd))
        })?;
        items.push(exit);
        Ok(Files(items))
    }
}

pub type FileItem = error::Item<File>;

pub struct Files(Vec<FileItem>);

impl IntoIterator for Files {
    type Item = FileItem;
    type IntoIter = FilesIntoIter;

    fn into_iter(self) -> FilesIntoIter {
        FilesIntoIter(self.0.into_iter())
    }
}

#[derive(Debug)]
pub struct FilesIntoIter(vec::IntoIter<FileItem>);

impl Iterator for FilesIntoIter {
    type Item = FileItem;

    #[inline]
    fn next(&mut self) -> Option<FileItem> {
        self.0.next()
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }

    #[inline]
    fn count(self) -> usize {
        self.0.count()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileContent {
    #[doc(hidden)]
    __Nonexhaustive,

    Text(Vec<String>),
    Binary(Vec<u8>),
}

impl FileContent {
    pub fn as_text(&self) -> Option<&[String]> {
        match self {
            FileContent::Text(c) => Some(&c),
            _ => None,
        }
    }

    pub fn as_binary(&self) -> Option<&[u8]> {
        match self {
            FileContent::Binary(c) => Some(&c),
            _ => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct File {
    pub depot_file: String,
    pub client_file: path::PathBuf,
    pub rev: usize,
    pub action: p4::Action,
    pub file_size: usize,
    non_exhaustive: (),
}

mod files_parser {
    use super::*;

    use super::super::parser::*;

    named!(pub file<&[u8], File>,
        do_parse!(
            depot_file: depot_file >>
            client_file: client_file >>
            rev: rev >>
            action: action >>
            file_size: file_size >>
            _ignore: opt!(delimited!(ignore_info1, ignore_info1, change)) >>
            (
                File {
                    depot_file: depot_file.path.to_owned(),
                    client_file: path::PathBuf::from(client_file.path),
                    rev: rev.rev,
                    action: action.action.parse().expect("`Unknown` to capture all"),
                    file_size: file_size.size,
                    non_exhaustive: (),
                }
            )
        )
    );

    named!(item<&[u8], FileItem>,
        alt!(
            map!(file, data_to_item) |
            map!(error, error_to_item) |
            map!(info, info_to_item)
        )
    );

    named!(pub files<&[u8], (Vec<FileItem>, FileItem)>,
        pair!(
            many0!(item),
            map!(exit, exit_to_item)
        )
    );
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn sync_single() {
        let output: &[u8] = br#"info1: depotFile //depot/dir/file
info1: clientFile /home/user/depot/dir/file
info1: rev 1
info1: action added
info1: fileSize 1016
info1: totalFileSize 865153
info1: totalFileCount 24
info1: change 25662947
exit: 0
"#;
        let (_remains, (items, exit)) = files_parser::files(output).unwrap();
        let first = items[0].as_data().unwrap();
        assert_eq!(first.depot_file, "//depot/dir/file");
        assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
    }

    #[test]
    fn sync_multi() {
        let output: &[u8] = br#"info1: depotFile //depot/dir/file
info1: clientFile /home/user/depot/dir/file
info1: rev 1
info1: action added
info1: fileSize 1016
info1: totalFileSize 865153
info1: totalFileCount 24
info1: change 25662947
info1: depotFile //depot/dir/file1
info1: clientFile /home/user/depot/dir/file1
info1: rev 1
info1: action added
info1: fileSize 729154
exit: 0
"#;
        let (_remains, (items, exit)) = files_parser::files(output).unwrap();
        let first = items[0].as_data().unwrap();
        let last = items[1].as_data().unwrap();
        assert_eq!(first.depot_file, "//depot/dir/file");
        assert_eq!(last.depot_file, "//depot/dir/file1");
        assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
    }
}