Skip to content
Snippets Groups Projects
main.rs 15.73 KiB
// SPDX-FileCopyrightText: 2023 Nicolas Fontrodona
//
// SPDX-License-Identifier: AGPL-3.0-or-later

use crate::compact::Compact;
use crate::delete::Delete;
use crate::prune::Prune;
use clap::{Args, Parser, Subcommand};
use colored::Colorize;
use configt::PartialPrune;
mod checkout;
mod clean;
mod clone;
mod commit;
mod compact;
mod config_structure;
mod configt;
mod create_hooks;
mod delete;
mod diff;
mod init;
mod list;
mod mount;
mod prune;
mod pull;
mod push;
mod remote;
mod restore;

#[derive(Debug, Parser)]
#[clap(name = "gblk")]
/// A tool used to link borg and git together
///
/// This tool was created to link borg and git together and ease the management of developpment artifact versionning using git
struct Cli {
    #[clap(subcommand)]
    commands: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    /// Initialize a borg repository inside a git project
    Init(Init),
    /// Save the results folder of a git repository in an archive
    ///
    /// The archive will be named as the current commit in git
    Commit(Commit),
    /// List the content of the .borg archive
    List(List),
    /// Check if a checkout can be performed without losing data
    #[clap(name = "pre-co")]
    PreCo,
    /// Checkout results to the current git commit
    #[clap(alias = "co")]
    Checkout(Checkout),
    /// Create github hooks to use gbl automaticaly after commit, before and after checkout
    #[clap(
        name = "create-hooks",
        alias = "ch",
        long_about = "Create github hooks to use gbl automaticaly after commit, \
        before and after checkout \n \n\
        This command should be used after git init and glb init. \n \n\
        This will create 2 hooks file: \n\
        1. post-commit: `glb commit` will be launched after git commit \n\
        2. post-checkout: `gbl checkout` will be launched after git checkout
        "
    )]
    CreateHooks(CreateHooks),
    /// Remove the post-checkout and the post-commit hooks
    #[clap(name = "delete-hooks", alias = "dh")]
    DeleteHooks,
    /// Show differences between two commits of the `results` folder
    Diff(Diff),
    /// Mount an old file/directory from one or multiple archive named after
    /// git commits into the .mount folder inside the project directory.
    Mount(Mount),
    /// Unmount everything in the folder .mount
    Umount,
    /// This command deletes an archive from the repository or the complete repository
    ///
    /// This is basically a wrapper of the borg delete command
    ///
    /// You can visit:
    /// <https://borgbackup.readthedocs.io/en/stable/usage/prune.html> for more details
    ///
    /// This function don't free disk space until gblk compact is used
    Delete(Delete),
    /// This command prunes the .borg repository. This can be used to keep only
    /// archive created during a given time interval
    ///
    /// This is basically a wrapper of the borg prune command
    ///
    /// You can visit:
    /// <https://borgbackup.readthedocs.io/en/stable/usage/prune.html> for more details
    ///
    /// This function don't free disk space until gblk compact is used
    Prune(Prune),
    /// This command frees repository space by compacting segments.
    ///
    /// It is especially useful after deleting archives because compaction will
    /// free repository space
    Compact(Compact),
    /// This command can bed used to add gblk configuration
    ///
    /// It's useful when you want to defined to always keep archive in given
    /// time interval without typing always the same prune command
    #[clap(subcommand)]
    Config(Config),
    /// This command can be used to add a new remote for push and pull commands
    #[clap(subcommand)]
    Remote(Remote),
    /// This command can be used to push a repository using a remote
    Push(Push),
    /// This command can be used to pull a repository using a remote
    ///
    /// This commands pull a remote repository inside the folder .borg. Before
    /// erasing the content of the .borg repostory, it's content is saved
    /// inside .tmp/<PROJECT_DIR>_bkp where PROJECT_DIR is the folder name at
    /// the root of your project. If something goes wrong, you can restore your
    /// .borg folder with the command gblk restore. If everything is ok, remove
    /// the content of the .tmp folder using gblk clean
    Pull(Pull),
    /// This command cleans the .tmp repository of the project folder
    Clean,
    /// This command moves the borg folder .tmp/<PROJECT_DIR>_bkp folder into
    /// .borg repository
    ///
    /// The purpose of this command is to be used if a pull command fails after
    /// removing content inside the .borg folder
    Restore,
    /// Clones a repository given a destination
    Clone(Clone),
}

#[derive(Debug, Args)]
struct Init {
    /// If specified, hooks are created inside `.git/hooks repository`
    #[clap(takes_value = false, short = 'H', long)]
    hooks: bool,
    /// The compression to use automatically at each commit if hooks are created
    #[clap(short, long, default_value = "lz4", value_name = "COMPRESSION")]
    compression: String,
    /// The checkout mode used by gblk automatically after a git checkout: soft or hard.
    /// This option is only used if hooks are created.

    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "hard", value_name = "MODE")]
    mode: String,
}

#[derive(Debug, Args)]
struct Commit {
    /// The compression used to save the results folder (no, lz4, zstd, zlib or lzma)
    #[clap(short, long, default_value = "lz4", value_name = "COMPRESSION")]
    compression: String,
    /// Use this flag to update the content of the current commit archive if it has changed
    #[clap(takes_value = false, short, long)]
    update: bool,
    /// Use this flag to revert your results folder like it was before for this commit
    #[clap(takes_value = false, short, long)]
    revert: bool,
}

#[derive(Debug, Args)]
struct List {
    /// consider first N archives
    #[clap(short, long, default_value_t = 0, value_name = "N")]
    first: i32,
    /// consider last N archives
    #[clap(short, long, default_value_t = 0, value_name = "N")]
    last: i32,
    /// If set list the files in this archive
    #[clap(short, long, default_value = "", value_name = "ARCHIVE")]
    archive: String,
}

#[derive(Debug, Args)]
struct Checkout {
    /// The checkout mode: hard or soft
    ///
    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "soft", value_name = "MODE")]
    mode: String,
}

#[derive(Debug, Args)]
struct CreateHooks {
    /// The compression that will automatically be used after each commit
    #[clap(short, long, default_value = "lz4", value_name = "COMPRESSION")]
    compression: String,
    /// The checkout mode used by gblk automatically after a git checkout: soft or hard.

    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "hard", value_name = "MODE")]
    mode: String,
}

#[derive(Debug, Args)]
struct Diff {
    /// The SHA1 of a commit
    commit1: String,
    /// The SHA1 of another commit.
    /// If you leave this blank,
    /// it will check the different between the commit1 and
    ///  your current result folder
    commit2: Option<String>,
}

#[derive(Debug, Args)]
struct Mount {
    /// Commit name, sh: Glob is supported. This is an optional parameter: if
    /// not set then all commit archives will be mounted into the .mount directory
    #[clap(short, long, value_name = "COMMIT")]
    commit: Option<String>,
    /// The file/directory to extract. This is an optional parameter. If not set
    /// then all files in the archive will be displayed
    #[clap(short, long, value_name = "PATH")]
    path: Option<String>,
    /// If set, displays the .mount directory in 'version view'.
    ///
    /// - Normal view: The `.mount` directory contains a subfolder with the name
    ///   of archives.
    ///
    /// - Version view: The `.mount` directory contains the results folder and
    ///   every file within it becomes a directory storing every version of
    ///   that file
    ///
    /// This option is deactivated when used with --diff
    #[clap(short, long, takes_value = false)]
    versions: bool,
    /// Displays the differences between two files mounted corresponding to the
    /// given path.
    ///
    /// Note that if only one file is recovered then, the other is taken from
    /// the current result folder
    #[clap(short, long, takes_value = false)]
    diff: bool,
    /// Consider last N archive after  other filter were applied
    #[clap(short, long, value_name = "N")]
    last: Option<u8>,
}

#[derive(Debug, Subcommand)]
enum Config {
    /// Add a new parameter in the gblk config file to be able to automatically
    /// prune some commits
    Add(Add),
    /// Display the current gblk configuration
    Show(Show),
    /// Remove the configuration of a given key in prune
    Rm(Rm),
    /// Prune using the project configuration
    Prune(PartialPrune),
}

#[derive(Debug, Args)]
struct Add {
    /// The name of the option to add, see optional arguments of the prune subcommands
    key: String,
    /// The value of the option to add in the configuration file
    value: String,
    /// Use this flag if you wan to add an option in the global gblk
    /// configuration file
    #[clap(short, long, takes_value = false)]
    global: bool,
}

#[derive(Debug, Args)]
struct Rm {
    /// The name of the option to remove, see optional arguments of the prune subcommands
    key: String,
    /// Use this flag if you want to remove an argument in the global gblk
    /// configuration file
    #[clap(short, long, takes_value = false)]
    global: bool,
}

#[derive(Debug, Args)]
struct Show {
    /// Use this flag if you want to show an argument in the global gblk
    /// configuration file
    #[clap(short, long, takes_value = false)]
    global: bool,
}

#[derive(Debug, Subcommand)]
enum Remote {
    /// Add or update a remote in the configuration file
    Add(RemoteAdd),
    /// Display globally and locally defined remotes
    Show,
    /// Remove the configuration of a given key in prune
    Rm(RemoteRm),
}

#[derive(Debug, Args)]
struct RemoteAdd {
    /// The name of the remote to add
    key: String,
    /// The path where the remote is pointing
    value: String,
    /// Use this flag if you wan to add a remote in the global gblk
    /// configuration file
    #[clap(short, long, takes_value = false)]
    global: bool,
}

#[derive(Debug, Args)]
struct RemoteRm {
    /// The name of the remote to remove
    key: String,
    /// Use this flag if you want to remove the remote in the global gblk
    /// configuration file
    #[clap(short, long, takes_value = false)]
    global: bool,
}

#[derive(Debug, Args)]
struct Push {
    /// The name of the remote to use
    key: String,
    /// The name of the archive to push
    archive: String,
    /// The compression to use when pushin a commit
    #[clap(short, long, default_value = "lz4", value_name = "COMPRESSION")]
    compression: String,
}

#[derive(Debug, Args)]
struct Pull {
    /// The name of the remote to use
    key: String,
}

#[derive(Debug, Args)]
struct Clone {
    /// The path pointing to a borg folder
    path: String,
    /// If specified, hooks are created inside `.git/hooks repository`
    #[clap(takes_value = false, short = 'H', long)]
    hooks: bool,
    /// The compression to use automatically at each commit if hooks are created
    #[clap(short, long, default_value = "lz4", value_name = "COMPRESSION")]
    compression: String,
    /// The checkout mode used by gblk automatically after a git checkout: soft or hard.
    /// This option is only used if hooks are created.

    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "hard", value_name = "MODE")]
    mode: String,
}

fn main() {
    let args = Cli::parse();

    match args.commands {
        Commands::Init(init) => {
            init::init_and_hook(init.hooks, &init.compression, &init.mode);
        }
        Commands::Commit(commit) => {
            mount::umount_archive(true);
            if commit.revert {
                if commit.revert == commit.update {
                    eprintln!(
                        "{} You moust choose between `{}` and `{}` option",
                        "Error:".red(),
                        "--revert".blue(),
                        "--update".blue()
                    )
                }
            }
            if !commit.revert {
                commit::commit(commit.compression, String::from(""), commit.update);
            } else {
                commit::revert_commit();
            }
        }
        Commands::List(list) => {
            list::borg_list(list.first, list.last, &list.archive);
        }
        Commands::PreCo => {
            mount::umount_archive(true);
            checkout::prepare_checkout();
        }
        Commands::Checkout(co) => {
            mount::umount_archive(true);
            checkout::checkout(&co.mode);
        }
        Commands::CreateHooks(ch) => {
            create_hooks::create_hooks(&ch.compression, &ch.mode);
        }
        Commands::Diff(diff) => {
            diff::compute_diff(&diff.commit1, &diff.commit2);
        }
        Commands::DeleteHooks => {
            create_hooks::delete_hooks();
        }
        Commands::Mount(mount) => {
            mount::umount_archive(true);
            mount::mount_archive(
                &mount.commit,
                &mount.path,
                mount.versions,
                mount.diff,
                mount.last,
            );
        }
        Commands::Umount => {
            mount::umount_archive(false);
        }
        Commands::Delete(my_delete) => {
            delete::deletion_launcher(my_delete);
        }
        Commands::Prune(my_prune) => {
            prune::prune_launcher(my_prune);
        }
        Commands::Compact(my_compact) => {
            compact::launch_compact(my_compact);
        }
        Commands::Config(conf) => match conf {
            Config::Add(e) => configt::update_config(&e.key, &e.value, e.global),
            Config::Show(e) => configt::show(e.global),
            Config::Rm(e) => configt::remove_config(&e.key, e.global),
            Config::Prune(pp) => configt::launch_config_prune(pp),
        },
        Commands::Remote(remote) => match remote {
            Remote::Add(e) => remote::update_config(&e.key, &e.value, e.global),
            Remote::Show => {
                remote::show();
            }
            Remote::Rm(e) => remote::remove_config(&e.key, e.global),
        },
        Commands::Push(p) => {
            mount::umount_archive(true);
            push::push(&p.key, &p.archive, &p.compression);
        }
        Commands::Pull(_) => {
            // pull::pull(&p.key);
            eprintln!("function disabled");
        }
        Commands::Clean => {
            clean::clean();
        }
        Commands::Restore => {
            // restore::restore();
            eprintln!("function disabled");
        }
        Commands::Clone(_) => {
            // clone::clone(&c.path, c.hooks, &c.compression, &c.mode);
            eprintln!("function disabled");
        }
    }
}