pijul 1.0.0-alpha

The sound distributed version control system.
#[macro_use]
extern crate clap;
#[macro_use]
extern crate thiserror;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
#[macro_use]
extern crate lazy_static;

use clap::Clap;
use human_panic::setup_panic;
mod commands;
mod config;
mod remote;
mod repository;
use commands::*;

const DEFAULT_CHANNEL: &'static str = "main";
const PROTOCOL_VERSION: usize = 3;

#[derive(Clap, Debug)]
#[clap(version = crate_version!(), author = crate_authors!())]
pub struct Opts {
    #[clap(subcommand)]
    pub subcmd: SubCommand,
}

#[derive(Clap, Debug)]
pub enum SubCommand {
    #[clap(name = "init")]
    Init(Init),
    #[clap(name = "clone")]
    Clone(Clone),
    #[clap(name = "record", alias = "rec")]
    Record(Record),
    #[clap(name = "diff")]
    Diff(Diff),
    #[clap(name = "log")]
    Log(Log),
    #[clap(name = "push")]
    Push(Push),
    #[clap(name = "pull")]
    Pull(Pull),
    #[clap(name = "change")]
    Change(Change),
    #[clap(name = "channel")]
    Channel(Channel),
    #[clap(name = "protocol", setting = clap::AppSettings::Hidden)]
    Protocol(Protocol),
    #[cfg(feature = "git")]
    #[clap(name = "git")]
    Git(Git),
    #[clap(name = "mv")]
    Mv(Mv),
    #[clap(name = "ls")]
    Ls(Ls),
    #[clap(name = "add")]
    Add(Add),
    #[clap(name = "reset")]
    Reset(Reset),
    #[cfg(debug_assertions)]
    #[clap(name = "debug")]
    Debug(Debug),
    #[clap(name = "fork")]
    Fork(Fork),
    #[clap(name = "unrecord", alias = "unrec", alias = "un")]
    Unrecord(Unrecord),
    #[clap(name = "apply")]
    Apply(Apply),
    #[clap(name = "remote")]
    Remote(Remote),
    #[clap(name = "archive")]
    Archive(Archive),
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("No Pijul repository found")]
    NoRepoRoot,
    #[error("Cannot access working directory")]
    CannotAccessWorkingDirectory,
    #[error("Already in a repository")]
    AlreadyInARepo,
    #[error("No such channel: {}", channel)]
    NoSuchChannel { channel: String },
    #[error("Protocol error: {:?}", line)]
    ProtocolError { line: Vec<u8> },
    #[error("Not authenticated")]
    NotAuthenticated,
    #[error("No change message")]
    NoChangeMessage,
    #[error("Incorrect remote: {}", name)]
    IncorrectRemote { name: String },
    #[error("Unknown host key")]
    UnknownHostKey,
    #[error("Cannot record a binary change interactively. Use -a")]
    RecordBinaryChange,
    #[error("Unknown remote type")]
    UnknownRemoteType,
    #[error("No global config directory")]
    NoGlobalConfigDir,
    #[error("Could not parse global config")]
    CouldNotParseGlobal,
    #[error("Cannot dry-reset multiple files")]
    CannotDryReset,
    #[error("Remote error: {}", msg)]
    Remote { msg: String },
    #[error("Remote exited with status {}", status)]
    RemoteExit { status: u32 },
    #[error("Missing remote")]
    MissingRemote,
    #[error("State not found in remote: {:?}", state)]
    StateNotFound { state: libanu::pristine::Merkle },
    #[error("Missing dependencies for change {:?}", h)]
    MissingDep { h: libanu::pristine::Hash },
    #[error("Ambiguous path: {:?}", path)]
    AmbiguousPath { path: String },
    #[error("No prefixes given. Use `.` to record the current directory.")]
    NoRecordPrefixes,
    #[error("HTTP error: {}", status.as_str())]
    Http { status: reqwest::StatusCode },
}

#[tokio::main]
async fn main() {
    if !cfg!(debug_assertions) {
        setup_panic!();
    }
    env_logger::init();
    let opts: Opts = Opts::parse();
    if let Err(e) = run(opts).await {
        eprintln!("Error: {}", e);
        std::process::exit(1);
    }
}

async fn run(opts: Opts) -> Result<(), anyhow::Error> {
    match opts.subcmd {
        SubCommand::Log(l) => l.run(),
        SubCommand::Init(init) => init.run(),
        SubCommand::Clone(clone) => clone.run().await,
        SubCommand::Record(record) => record.run().await,
        SubCommand::Diff(diff) => diff.run(),
        SubCommand::Push(push) => push.run().await,
        SubCommand::Pull(pull) => pull.run().await,
        SubCommand::Change(change) => change.run(),
        SubCommand::Channel(channel) => channel.run(),
        SubCommand::Protocol(protocol) => protocol.run(),
        #[cfg(feature = "git")]
        SubCommand::Git(git) => git.run(),
        SubCommand::Mv(mv) => mv.run(),
        SubCommand::Ls(ls) => ls.run(),
        SubCommand::Add(add) => add.run(),
        SubCommand::Reset(reset) => reset.run(),
        #[cfg(debug_assertions)]
        SubCommand::Debug(debug) => debug.run(),
        SubCommand::Fork(fork) => fork.run(),
        SubCommand::Unrecord(unrecord) => unrecord.run(),
        SubCommand::Apply(apply) => apply.run(),
        SubCommand::Remote(remote) => remote.run(),
        SubCommand::Archive(archive) => archive.run().await,
    }
}

pub fn current_dir() -> Result<std::path::PathBuf, Error> {
    std::env::current_dir().map_err(|_| Error::CannotAccessWorkingDirectory)
}