pijul 0.12.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
extern crate regex;

use std::path::PathBuf;
use std::str;

use clap::{Arg, ArgMatches, SubCommand};
use commands::{default_explain, BasicOptions, StaticSubcommand};
use regex::Regex;

use libpijul::{graph, Branch, Key, PatchId, Transaction, Txn, Value, ROOT_KEY};

use error;

type Result<T> = std::result::Result<T, error::Error>;

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("grep")
        .about("Search repository files for regex")
        .arg(
            Arg::with_name("regex")
                .help("Regex pattern to search for")
                .required(true),
        )
        .arg(
            Arg::with_name("branch")
                .long("branch")
                .help("The branch to search, defaults to the current branch.")
                .takes_value(true)
                .required(false),
        );
}

struct Grep<'a> {
    pattern: &'a Regex,
    path: &'a str,
    line: usize,
}

impl<'a, 'b, T: 'a + Transaction> graph::LineBuffer<'a, T> for Grep<'b> {
    fn output_line(
        &mut self,
        _: &Key<PatchId>,
        contents: Value<'a, T>,
    ) -> std::result::Result<(), libpijul::Error> {
        let c = contents.into_cow();
        if let Ok(s) = str::from_utf8(&c) {
            if self.pattern.is_match(s) {
                println!("{}:{}: {}", self.path, self.line, s)
            }
        }
        self.line += 1;
        Ok(())
    }
    fn output_conflict_marker(&mut self, s: &'a str) -> std::result::Result<(), libpijul::Error> {
        let value: Value<'a, T> = Value::from_slice(s.as_bytes());
        self.output_line(&ROOT_KEY, value)
    }
}

fn grep(
    txn: &Txn,
    branch: &Branch,
    directory: Key<PatchId>,
    path: &mut PathBuf,
    pattern: &Regex,
) -> Result<()> {
    let files = txn.list_files_under_node(branch, directory);
    for (node, names) in files {
        if names.len() > 1 {
            error!("file has several names: {:?}", names);
        }
        path.push(names[0].1);
        if names[0].0.is_dir() {
            grep(txn, branch, node, path, pattern)?;
        } else {
            let mut buffer = Grep {
                pattern,
                path: path.to_str().unwrap_or("<non-UTF path>"),
                line: 1,
            };
            let mut forward = Vec::new();
            let mut graph = txn.retrieve(branch, node);
            txn.output_file(branch, &mut buffer, &mut graph, &mut forward)?;
        }
        path.pop();
    }
    Ok(())
}

pub fn run(args: &ArgMatches) -> Result<()> {
    let opts = BasicOptions::from_args(args)?;
    let repo = opts.open_repo()?;
    let txn = repo.txn_begin()?;
    let branch = txn
        .get_branch(&opts.branch())
        .ok_or(error::Error::NoSuchBranch)?;
    let pattern = Regex::new(args.value_of("regex").unwrap())?;
    grep(&txn, &branch, ROOT_KEY, &mut PathBuf::new(), &pattern)
}

pub fn explain(res: Result<()>) {
    default_explain(res)
}