pijul 0.12.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
use super::default_explain;
use super::record;
use chrono;
use clap::{ArgMatches, SubCommand};
use commands::hooks::run_hook;
use commands::record::{decide_authors, decide_patch_message, record_args};
use commands::{BasicOptions, StaticSubcommand};
use error::Error;
use libpijul::patch::PatchFlags;
use libpijul::Hash;
use meta::{load_signing_key, Global, Meta};
use std::collections::HashSet;
use std::mem::drop;

pub fn invocation() -> StaticSubcommand {
    record_args(
        SubCommand::with_name("tag").about(
            "Create a tag, i.e. an empty patch with all patches on the branch as dependencies",
        ),
    )
}

pub fn run(args: &ArgMatches) -> Result<Option<Hash>, Error> {
    let opts = BasicOptions::from_args(args)?;
    let patch_name_arg = args.value_of("message");
    let patch_descr_arg = args.value_of("description");
    let authors_arg = args.values_of("author").map(|x| x.collect::<Vec<_>>());
    let branch_name = opts.branch();

    let mut save_meta = false;

    let (mut global, save_global) = Global::load().map(|g| (g, false)).unwrap_or_else(|e| {
        info!("loading global key, error {:?}", e);
        (Global::new(), true)
    });

    let mut meta = match Meta::load(&opts.repo_root) {
        Ok(m) => m,
        Err(_) => {
            save_meta = true;
            Meta::new()
        }
    };

    let repo = opts.open_repo()?;
    let patch = {
        let txn = repo.txn_begin()?;
        debug!("meta:{:?}", meta);

        let authors = decide_authors(authors_arg, &meta, &global)?;

        if meta.authors.len() == 0 {
            meta.authors = authors.clone();
            save_meta = true;
        }

        if global.author.is_none() {
            global.author = Some(authors[0].clone());
        }

        debug!("authors:{:?}", authors);

        let (patch_name, description) = decide_patch_message(
            patch_name_arg,
            patch_descr_arg,
            String::from(""),
            !args.is_present("no-editor"),
            &opts.repo_root,
            &meta,
            &global,
        )?;

        run_hook(&opts.repo_root, "patch-name", Some(&patch_name))?;

        debug!("patch_name:{:?}", patch_name);
        if save_meta {
            meta.save(&opts.repo_root)?
        }
        if save_global {
            global.save()?
        }
        debug!("new");
        let branch = txn.get_branch(&branch_name).unwrap();

        let mut included = HashSet::new();
        let mut patches = Vec::new();
        for (_, patch) in txn.rev_iter_applied(&branch, None) {
            // `patch` is already implied if a patch on the branch
            // depends on `patch`. Let's look at all patches known to
            // the repository that depend on `patch`, and see if a
            // patch on the branch (i.e. all patches in `included`,
            // since we're considering patches in reverse order of
            // application) depends on `patch`.
            let mut already_in = false;
            for (p, revdep) in txn.iter_revdep(Some((patch, None))) {
                if p == patch {
                    if included.contains(&revdep) {
                        already_in = true
                    }
                } else {
                    break;
                }
            }
            if !already_in {
                let patch = txn.get_external(patch).unwrap();
                patches.push(patch.to_owned());
            }
            included.insert(patch.to_owned());
        }
        txn.new_patch(
            &branch,
            authors,
            patch_name,
            description,
            chrono::Utc::now(),
            Vec::new(),
            patches.into_iter(),
            PatchFlags::TAG,
        )
    };
    drop(repo);

    let patches_dir = opts.repo_root.patches_dir();
    let mut key = meta
        .signing_key
        .or(global.signing_key)
        .and_then(|s| load_signing_key(s).ok());
    let hash = if let Some(ref mut key) = key {
        key.check_author(&patch.header().authors)?;
        patch.save(&patches_dir, key.keys.get_mut(0))?
    } else {
        patch.save(&patches_dir, None)?
    };

    let pristine_dir = opts.pristine_dir();
    let mut increase = 40960;
    loop {
        match record::record_no_resize(
            &pristine_dir,
            &opts.repo_root,
            &branch_name,
            &hash,
            &patch,
            &HashSet::new(),
            increase,
        ) {
            Err(ref e) if e.lacks_space() => increase *= 2,
            _ => break,
        }
    }
    Ok(Some(hash))
}

pub fn explain(res: Result<Option<Hash>, Error>) {
    default_explain(res)
}