xvc_file/
lib.rs

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
//! Xvc operations on files
//!
//! Most of these commands require an Xvc repository [XvcRoot] to be present.
//!
//! Modules correspond to subcommands, and are documented separately.
//!
#![warn(missing_docs)]
#![forbid(unsafe_code)]
pub mod common;
pub use common::compare;

pub mod bring;
pub mod carry_in;
pub mod copy;
pub mod error;
pub mod hash;
pub mod list;
pub mod mv;
pub mod recheck;
pub mod remove;
pub mod send;
pub mod share;
pub mod track;
pub mod untrack;

pub use bring::cmd_bring;
pub use carry_in::cmd_carry_in;
pub use copy::cmd_copy;
pub use hash::cmd_hash;
pub use list::cmd_list;
pub use mv::cmd_move;
pub use recheck::cmd_recheck;
pub use remove::cmd_remove;
pub use send::cmd_send;
use share::ShareCLI;
pub use track::cmd_track;
pub use untrack::cmd_untrack;

use crate::error::{Error, Result};
use crate::share::cmd_share;
use clap::Subcommand;
use crossbeam::thread;
use crossbeam_channel::bounded;

use log::{debug, error, info, warn, LevelFilter};
use std::io;
use std::io::Write;
use std::path::PathBuf;
use xvc_config::XvcConfigParams;
use xvc_config::XvcVerbosity;
use xvc_core::default_project_config;
use xvc_core::types::xvcroot::load_xvc_root;
use xvc_core::XvcRoot;
use xvc_core::CHANNEL_BOUND;
use xvc_logging::{setup_logging, watch};
use xvc_logging::{XvcOutputLine, XvcOutputSender};
use xvc_walker::AbsolutePath;

pub use bring::BringCLI;
pub use carry_in::CarryInCLI;
pub use copy::CopyCLI;
pub use hash::HashCLI;
pub use list::ListCLI;
pub use mv::MoveCLI;
pub use recheck::RecheckCLI;
pub use remove::RemoveCLI;
pub use send::SendCLI;
pub use track::TrackCLI;
pub use untrack::UntrackCLI;

use clap::Parser;

/// xvc file subcommands
#[derive(Debug, Clone, Subcommand)]
#[command(author, version)]
pub enum XvcFileSubCommand {
    /// Add file and directories to Xvc
    Track(TrackCLI),
    /// Get digest hash of files with the supported algorithms
    Hash(HashCLI),
    /// Get files from cache by copy or *link
    #[command(alias = "checkout")]
    Recheck(RecheckCLI),
    /// Carry (commit) changed files to cache
    #[command(alias = "commit")]
    CarryIn(CarryInCLI),
    /// Copy from source to another location in the workspace
    Copy(CopyCLI),
    /// Move files to another location in the workspace
    Move(MoveCLI),
    /// List tracked and untracked elements in the workspace
    List(ListCLI),
    /// Send (push, upload) files to external storages
    Send(SendCLI),
    /// Bring (download, pull, fetch) files from external storages
    Bring(BringCLI),
    /// Remove files from Xvc and possibly storages
    Remove(RemoveCLI),
    /// Untrack (delete) files from Xvc and possibly storages
    Untrack(UntrackCLI),
    /// Share a file from S3 compatible storage for a limited time
    Share(ShareCLI),
}

/// Operations on data files
///
/// This command can be used to operate on files, like
///
/// - adding files to xvc cache and link by various methods
///
/// - calculating hash of files (even outside of xvc repo)
///
/// - listing files in repo (even if they are deleted from workspace)
///
/// - moving files to other locations
///
/// - deleting files and all their associated cache content
#[derive(Debug, Clone, Parser)]
pub struct XvcFileCLI {
    /// Verbosity level. Use multiple times to increase command output detail.
    #[arg(
        long = "verbose",
        short,
        action = clap::ArgAction::Count
    )]
    pub verbosity: u8,

    /// Don't show any messages.
    #[arg(long, help = "Suppress error messages")]
    pub quiet: bool,

    /// Set the working directory to run the command as if it's in that directory.
    #[arg(short = 'C', default_value = ".")]
    pub workdir: String,

    /// Configuration options set from the command line in the form section.key=value
    #[arg(long, short = 'c')]
    pub config: Option<Vec<String>>,

    /// Ignore system config file
    #[arg(long)]
    pub no_system_config: bool,

    /// Ignore user config file
    #[arg(long)]
    pub no_user_config: bool,

    /// Ignore project config (.xvc/config)
    #[arg(long)]
    pub no_project_config: bool,

    /// Ignore local config (.xvc/config.local)
    #[arg(long)]
    pub no_local_config: bool,

    /// Ignore configuration options from the environment
    #[arg(long)]
    pub no_env_config: bool,

    /// Subcommand for xvc file
    #[command(subcommand)]
    subcommand: XvcFileSubCommand,
}

/// Entry point for the `xvc file` command.
///
/// It runs the subcommand specified in the command line arguments.
pub fn run(
    output_snd: &XvcOutputSender,
    xvc_root: Option<&XvcRoot>,
    opts: XvcFileCLI,
) -> Result<()> {
    watch!(opts);
    match opts.subcommand {
        XvcFileSubCommand::Track(opts) => cmd_track(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Hash(opts) => cmd_hash(output_snd, xvc_root, opts),
        XvcFileSubCommand::CarryIn(opts) => cmd_carry_in(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Recheck(opts) => cmd_recheck(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::List(opts) => cmd_list(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Send(opts) => cmd_send(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Bring(opts) => cmd_bring(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Copy(opts) => cmd_copy(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Move(opts) => cmd_move(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Untrack(opts) => cmd_untrack(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Remove(opts) => cmd_remove(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
        XvcFileSubCommand::Share(opts) => cmd_share(
            output_snd,
            xvc_root.ok_or(Error::RequiresXvcRepository)?,
            opts,
        ),
    }
}

/// Dispatch function for the `xvc-file` binary.
///
/// This works almost identically with the [xvc::dispatch] function.
pub fn dispatch(cli_opts: XvcFileCLI) -> Result<()> {
    let verbosity = if cli_opts.quiet {
        XvcVerbosity::Quiet
    } else {
        match cli_opts.verbosity {
            0 => XvcVerbosity::Default,
            1 => XvcVerbosity::Warn,
            2 => XvcVerbosity::Info,
            3 => XvcVerbosity::Debug,
            _ => XvcVerbosity::Trace,
        }
    };

    let term_log_level = match verbosity {
        XvcVerbosity::Quiet => LevelFilter::Off,
        XvcVerbosity::Default => LevelFilter::Error,
        XvcVerbosity::Warn => LevelFilter::Warn,
        XvcVerbosity::Info => LevelFilter::Info,
        XvcVerbosity::Debug => LevelFilter::Debug,
        XvcVerbosity::Trace => LevelFilter::Trace,
    };

    setup_logging(Some(term_log_level), None);
    let dir = PathBuf::from(cli_opts.workdir.clone());
    let current_dir = if dir.is_absolute() {
        AbsolutePath::from(dir)
    } else {
        AbsolutePath::from(std::env::current_dir()?.join(dir).canonicalize()?)
    };
    // try to create root
    let xvc_config_params = XvcConfigParams {
        current_dir,
        include_system_config: !cli_opts.no_system_config,
        include_user_config: !cli_opts.no_user_config,
        project_config_path: None,
        local_config_path: None,
        include_environment_config: !cli_opts.no_env_config,
        command_line_config: cli_opts.config.clone(),
        default_configuration: default_project_config(true),
    };

    let xvc_root = match load_xvc_root(xvc_config_params) {
        Ok(r) => Some(r),
        Err(e) => {
            e.info();
            None
        }
    };

    thread::scope(move |s| {
        let (output_snd, output_rec) = bounded::<Option<XvcOutputLine>>(CHANNEL_BOUND);
        s.spawn(move |_| {
            let mut output = io::stdout();
            while let Ok(Some(output_line)) = output_rec.recv() {
                match output_line {
                    XvcOutputLine::Output(m) => writeln!(output, "{}", m).unwrap(),
                    XvcOutputLine::Info(m) => info!("[INFO] {}", m),
                    XvcOutputLine::Warn(m) => warn!("[WARN] {}", m),
                    XvcOutputLine::Error(m) => error!("[ERROR] {}", m),
                    XvcOutputLine::Panic(m) => panic!("[PANIC] {}", m),
                    XvcOutputLine::Debug(m) => debug!("[DEBUG] {}", m),
                    XvcOutputLine::Tick(_) => {}
                }
            }
        });

        s.spawn(move |_| run(&output_snd, xvc_root.as_ref(), cli_opts).map_err(|e| e.error()));
    })
    .map_err(|e| error!("{:?}", e))
    .expect("Crossbeam scope error");

    Ok(())
}

/// This is run during `xvc init` for `xvc file` related initialization.
///
/// It's a NOOP currently.
pub fn init(_xvc_root: &XvcRoot) -> Result<()> {
    Ok(())
}

/// Crossbeam channel capacity for channels in this crate
pub const CHANNEL_CAPACITY: usize = 100000;