xvc_file/
lib.rs

1//! Xvc operations on files
2//!
3//! Most of these commands require an Xvc repository [XvcRoot] to be present.
4//!
5//! Modules correspond to subcommands, and are documented separately.
6//!
7#![warn(missing_docs)]
8#![forbid(unsafe_code)]
9pub mod common;
10pub use common::compare;
11
12pub mod bring;
13pub mod carry_in;
14pub mod copy;
15pub mod error;
16pub mod hash;
17pub mod list;
18pub mod mv;
19pub mod recheck;
20pub mod remove;
21pub mod send;
22pub mod share;
23pub mod track;
24pub mod untrack;
25
26pub use bring::cmd_bring;
27pub use carry_in::cmd_carry_in;
28pub use copy::cmd_copy;
29pub use hash::cmd_hash;
30pub use list::cmd_list;
31pub use mv::cmd_move;
32pub use recheck::cmd_recheck;
33pub use remove::cmd_remove;
34pub use send::cmd_send;
35use share::ShareCLI;
36pub use track::cmd_track;
37pub use untrack::cmd_untrack;
38
39use crate::error::{Error, Result};
40use crate::share::cmd_share;
41use clap::Subcommand;
42use crossbeam::thread;
43use crossbeam_channel::bounded;
44
45use log::{debug, error, info, warn, LevelFilter};
46use std::io;
47use std::io::Write;
48use std::path::PathBuf;
49use xvc_core::default_project_config;
50use xvc_core::setup_logging;
51use xvc_core::types::xvcroot::load_xvc_root;
52use xvc_core::AbsolutePath;
53use xvc_core::XvcConfigParams;
54use xvc_core::XvcRoot;
55use xvc_core::XvcVerbosity;
56use xvc_core::CHANNEL_BOUND;
57use xvc_core::{XvcOutputLine, XvcOutputSender};
58
59pub use bring::BringCLI;
60pub use carry_in::CarryInCLI;
61pub use copy::CopyCLI;
62pub use hash::HashCLI;
63pub use list::ListCLI;
64pub use mv::MoveCLI;
65pub use recheck::RecheckCLI;
66pub use remove::RemoveCLI;
67pub use send::SendCLI;
68pub use track::TrackCLI;
69pub use untrack::UntrackCLI;
70
71use clap::Parser;
72
73/// xvc file subcommands
74#[derive(Debug, Clone, Subcommand)]
75#[command(author, version)]
76pub enum XvcFileSubCommand {
77    /// Add file and directories to Xvc
78    #[command(visible_aliases=&["t"])]
79    Track(TrackCLI),
80
81    /// Get digest hash of files with the supported algorithms
82    #[command(visible_aliases=&["h"])]
83    Hash(HashCLI),
84
85    /// Get files from cache by copy or *link
86    #[command(visible_aliases=&["checkout", "r"])]
87    Recheck(RecheckCLI),
88
89    /// Carry in changed files to cache
90    #[command(visible_aliases = &[ "commit", "c"])]
91    CarryIn(CarryInCLI),
92
93    /// Copy from source to another location in the workspace
94    #[command(visible_aliases=&["C"])]
95    Copy(CopyCLI),
96
97    /// Move files to another location in the workspace
98    #[command(visible_aliases=&["M"])]
99    Move(MoveCLI),
100
101    /// List tracked and untracked elements in the workspace
102    #[command(visible_aliases=&["l"])]
103    List(ListCLI),
104
105    /// Send files to external storages
106    #[command(visible_aliases=&["s", "upload", "push"])]
107    Send(SendCLI),
108
109    /// Bring files from external storages
110    #[command(visible_aliases=&["b", "download", "pull"])]
111    Bring(BringCLI),
112
113    /// Remove files from Xvc cache and storages
114    #[command(visible_aliases=&["R"])]
115    Remove(RemoveCLI),
116
117    /// Untrack (delete) files from Xvc and storages
118    #[command(visible_aliases=&["U"])]
119    Untrack(UntrackCLI),
120
121    /// Share a file from (S3 compatible) storage for a limited time
122    #[command(visible_aliases=&["S"])]
123    Share(ShareCLI),
124}
125
126/// Operations on data files
127///
128/// This command can be used to operate on files, like
129///
130/// - adding files to xvc cache and link by various methods
131///
132/// - calculating hash of files (even outside of xvc repo)
133///
134/// - listing files in repo (even if they are deleted from workspace)
135///
136/// - moving files to other locations
137///
138/// - deleting files and all their associated cache content
139#[derive(Debug, Clone, Parser)]
140pub struct XvcFileCLI {
141    /// Verbosity level. Use multiple times to increase command output detail.
142    #[arg(
143        long = "verbose",
144        short,
145        action = clap::ArgAction::Count
146    )]
147    pub verbosity: u8,
148
149    /// Don't show any messages.
150    #[arg(long, help = "Suppress error messages")]
151    pub quiet: bool,
152
153    /// Set the working directory to run the command as if it's in that directory.
154    #[arg(short = 'C', default_value = ".")]
155    pub workdir: String,
156
157    /// Configuration options set from the command line in the form section.key=value
158    #[arg(long, short = 'c')]
159    pub config: Option<Vec<String>>,
160
161    /// Ignore system config file
162    #[arg(long)]
163    pub no_system_config: bool,
164
165    /// Ignore user config file
166    #[arg(long)]
167    pub no_user_config: bool,
168
169    /// Ignore project config (.xvc/config)
170    #[arg(long)]
171    pub no_project_config: bool,
172
173    /// Ignore local config (.xvc/config.local)
174    #[arg(long)]
175    pub no_local_config: bool,
176
177    /// Ignore configuration options from the environment
178    #[arg(long)]
179    pub no_env_config: bool,
180
181    /// Subcommand for xvc file
182    #[command(subcommand)]
183    subcommand: XvcFileSubCommand,
184}
185
186/// Entry point for the `xvc file` command.
187///
188/// It runs the subcommand specified in the command line arguments.
189pub fn run(
190    output_snd: &XvcOutputSender,
191    xvc_root: Option<&XvcRoot>,
192    opts: XvcFileCLI,
193) -> Result<()> {
194    match opts.subcommand {
195        XvcFileSubCommand::Track(opts) => cmd_track(
196            output_snd,
197            xvc_root.ok_or(Error::RequiresXvcRepository)?,
198            opts,
199        ),
200        XvcFileSubCommand::Hash(opts) => cmd_hash(output_snd, xvc_root, opts),
201        XvcFileSubCommand::CarryIn(opts) => cmd_carry_in(
202            output_snd,
203            xvc_root.ok_or(Error::RequiresXvcRepository)?,
204            opts,
205        ),
206        XvcFileSubCommand::Recheck(opts) => cmd_recheck(
207            output_snd,
208            xvc_root.ok_or(Error::RequiresXvcRepository)?,
209            opts,
210        ),
211        XvcFileSubCommand::List(opts) => cmd_list(
212            output_snd,
213            xvc_root.ok_or(Error::RequiresXvcRepository)?,
214            opts,
215        ),
216        XvcFileSubCommand::Send(opts) => cmd_send(
217            output_snd,
218            xvc_root.ok_or(Error::RequiresXvcRepository)?,
219            opts,
220        ),
221        XvcFileSubCommand::Bring(opts) => cmd_bring(
222            output_snd,
223            xvc_root.ok_or(Error::RequiresXvcRepository)?,
224            opts,
225        ),
226        XvcFileSubCommand::Copy(opts) => cmd_copy(
227            output_snd,
228            xvc_root.ok_or(Error::RequiresXvcRepository)?,
229            opts,
230        ),
231        XvcFileSubCommand::Move(opts) => cmd_move(
232            output_snd,
233            xvc_root.ok_or(Error::RequiresXvcRepository)?,
234            opts,
235        ),
236        XvcFileSubCommand::Untrack(opts) => cmd_untrack(
237            output_snd,
238            xvc_root.ok_or(Error::RequiresXvcRepository)?,
239            opts,
240        ),
241        XvcFileSubCommand::Remove(opts) => cmd_remove(
242            output_snd,
243            xvc_root.ok_or(Error::RequiresXvcRepository)?,
244            opts,
245        ),
246        XvcFileSubCommand::Share(opts) => cmd_share(
247            output_snd,
248            xvc_root.ok_or(Error::RequiresXvcRepository)?,
249            opts,
250        ),
251    }
252}
253
254/// Dispatch function for the `xvc-file` binary.
255///
256/// This works almost identically with the [xvc::dispatch] function.
257pub fn dispatch(cli_opts: XvcFileCLI) -> Result<()> {
258    let verbosity = if cli_opts.quiet {
259        XvcVerbosity::Quiet
260    } else {
261        match cli_opts.verbosity {
262            0 => XvcVerbosity::Default,
263            1 => XvcVerbosity::Warn,
264            2 => XvcVerbosity::Info,
265            3 => XvcVerbosity::Debug,
266            _ => XvcVerbosity::Trace,
267        }
268    };
269
270    let term_log_level = match verbosity {
271        XvcVerbosity::Quiet => LevelFilter::Off,
272        XvcVerbosity::Default => LevelFilter::Error,
273        XvcVerbosity::Warn => LevelFilter::Warn,
274        XvcVerbosity::Info => LevelFilter::Info,
275        XvcVerbosity::Debug => LevelFilter::Debug,
276        XvcVerbosity::Trace => LevelFilter::Trace,
277    };
278
279    setup_logging(Some(term_log_level), None);
280    let dir = PathBuf::from(cli_opts.workdir.clone());
281    let current_dir = if dir.is_absolute() {
282        AbsolutePath::from(dir)
283    } else {
284        AbsolutePath::from(std::env::current_dir()?.join(dir).canonicalize()?)
285    };
286    // try to create root
287    let xvc_config_params = XvcConfigParams {
288        current_dir,
289        include_system_config: !cli_opts.no_system_config,
290        include_user_config: !cli_opts.no_user_config,
291        project_config_path: None,
292        local_config_path: None,
293        include_environment_config: !cli_opts.no_env_config,
294        command_line_config: cli_opts.config.clone(),
295        default_configuration: default_project_config(true),
296    };
297
298    let xvc_root = match load_xvc_root(xvc_config_params) {
299        Ok(r) => Some(r),
300        Err(e) => {
301            e.info();
302            None
303        }
304    };
305
306    thread::scope(move |s| {
307        let (output_snd, output_rec) = bounded::<Option<XvcOutputLine>>(CHANNEL_BOUND);
308        s.spawn(move |_| {
309            let mut output = io::stdout();
310            while let Ok(Some(output_line)) = output_rec.recv() {
311                match output_line {
312                    XvcOutputLine::Output(m) => writeln!(output, "{}", m).unwrap(),
313                    XvcOutputLine::Info(m) => info!("[INFO] {}", m),
314                    XvcOutputLine::Warn(m) => warn!("[WARN] {}", m),
315                    XvcOutputLine::Error(m) => error!("[ERROR] {}", m),
316                    XvcOutputLine::Panic(m) => panic!("[PANIC] {}", m),
317                    XvcOutputLine::Debug(m) => debug!("[DEBUG] {}", m),
318                    XvcOutputLine::Tick(_) => {}
319                }
320            }
321        });
322
323        s.spawn(move |_| run(&output_snd, xvc_root.as_ref(), cli_opts).map_err(|e| e.error()));
324    })
325    .map_err(|e| error!("{:?}", e))
326    .expect("Crossbeam scope error");
327
328    Ok(())
329}
330
331/// This is run during `xvc init` for `xvc file` related initialization.
332///
333/// It's a NOOP currently.
334pub fn init(_xvc_root: &XvcRoot) -> Result<()> {
335    Ok(())
336}
337
338/// Crossbeam channel capacity for channels in this crate
339pub const CHANNEL_CAPACITY: usize = 100000;