use super::Repository;
use backend::DEFAULT_BRANCH;
use backend::{Hash, HashRef};
use backend::{MutTxn, ROOT_INODE};
use bs58;
use flate2;
use ignore::overrides::OverrideBuilder;
use ignore::WalkBuilder;
use patch::{Patch, PatchHeader};
use rand::distributions::Alphanumeric;
use rand::Rng;
use std;
use std::fs::canonicalize;
use std::fs::{create_dir_all, metadata, File};
use std::io::{BufReader, Read, Write};
use std::path::{Path, PathBuf};
use {Result, Error};
use std::ffi::OsStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct RepoPath<P: ?Sized>(pub P);
pub fn in_repo_root() -> RepoPath<&'static Path> {
RepoPath(Path::new(""))
}
impl RepoPath<std::path::PathBuf> {
pub fn push(&mut self, x: &str) {
self.0.push(x)
}
pub fn pop(&mut self) -> bool {
self.0.pop()
}
pub fn new() -> Self {
RepoPath(PathBuf::new())
}
pub fn as_ref(&self) -> RepoPath<&Path> {
RepoPath(self.0.as_ref())
}
pub fn set_file_name(&mut self, filename: &OsStr) {
self.0.set_file_name(filename)
}
pub fn from_string(path: String) -> Self {
RepoPath(PathBuf::from(path))
}
}
impl<P: AsRef<Path>> RepoPath<P> {
pub fn as_path(&self) -> &std::path::Path {
self.0.as_ref()
}
}
impl<P: AsRef<Path>> RepoPath<P> {
pub fn parent(&self) -> Option<RepoPath<&std::path::Path>> {
self.as_path().parent().map(RepoPath)
}
pub fn file_name(&self) -> Option<&OsStr> {
self.as_path().file_name()
}
pub fn split(&self) -> Option<(RepoPath<&std::path::Path>, &OsStr)> {
self.parent().map(|p| {(p, self.file_name().expect("file_name and parent should be consistent"))})
}
pub fn components(&self) -> std::path::Components {
self.as_path().components()
}
pub fn to_path_buf(&self) -> PathBuf {
self.as_path().to_path_buf()
}
pub fn display(&self) -> std::path::Display {
self.as_path().display()
}
pub fn to_owned(&self) -> RepoPath<PathBuf> {
RepoPath(self.0.as_ref().to_path_buf())
}
pub fn join(&self, path: &Path) -> RepoPath<PathBuf> {
let joined_path = self.as_path().join(path);
RepoPath(joined_path)
}
}
impl<P: AsRef<Path>> RepoPath<P> {
pub fn empty(&self) -> bool {
self.as_path() == Path::new("")
}
}
#[derive(Clone, Copy, Debug)]
pub struct RepoRoot<P: AsRef<Path>> {
pub repo_root: P,
}
pub const PIJUL_DIR_NAME: &'static str = ".pijul";
pub fn branch_changes_base_path(b: &str) -> String {
"changes.".to_string() + &bs58::encode(b.as_bytes()).into_string()
}
pub fn patch_file_name(hash: HashRef) -> String {
hash.to_base58() + ".gz"
}
impl<P: AsRef<Path>> RepoRoot<P> {
pub fn repo_dir(&self) -> PathBuf {
self.repo_root.as_ref().join(PIJUL_DIR_NAME)
}
pub fn pristine_dir(&self) -> PathBuf {
self.repo_dir().join("pristine")
}
pub fn patches_dir(&self) -> PathBuf {
self.repo_dir().join("patches")
}
pub fn branch_changes_file(&self, b: &str) -> PathBuf {
self.repo_dir().join(branch_changes_base_path(b))
}
pub fn meta_file(&self) -> PathBuf {
self.repo_dir().join("meta.toml")
}
pub fn id_file(&self) -> PathBuf {
self.repo_dir().join("id")
}
pub fn read_patch(&self, hash: HashRef) -> Result<Patch> {
let patch_dir = self.patches_dir();
let path = patch_dir.join(&patch_file_name(hash));
let f = File::open(path)?;
let mut f = BufReader::new(f);
let (_, _, patch) = Patch::from_reader_compressed(&mut f)?;
Ok(patch)
}
pub fn read_patch_nochanges(&self, hash: HashRef) -> Result<PatchHeader> {
let patch_dir = self.patches_dir();
let path = patch_dir.join(&patch_file_name(hash));
let f = File::open(path)?;
let mut f = flate2::bufread::GzDecoder::new(BufReader::new(f));
Ok(PatchHeader::from_reader_nochanges(&mut f)?)
}
pub fn read_dependencies(&self, hash: HashRef) -> Result<Vec<Hash>> {
let patch_dir = self.patches_dir();
let path = patch_dir.join(&patch_file_name(hash));
let f = File::open(path)?;
let mut f = flate2::bufread::GzDecoder::new(BufReader::new(f));
Ok(Patch::read_dependencies(&mut f)?)
}
pub fn local_ignore_file(&self) -> PathBuf {
self.repo_dir().join("local").join("ignore")
}
pub fn get_current_branch(&self) -> Result<String> {
let mut path = self.repo_dir();
path.push("current_branch");
if let Ok(mut f) = File::open(&path) {
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s.trim().to_string())
} else {
Ok(DEFAULT_BRANCH.to_string())
}
}
pub fn set_current_branch(&self, branch: &str) -> Result<()> {
let mut path = self.repo_dir();
path.push("current_branch");
let mut f = File::create(&path)?;
f.write_all(branch.trim().as_ref())?;
f.write_all(b"\n")?;
Ok(())
}
pub fn open_repo(&self, increase: Option<u64>) -> Result<Repository> {
Repository::open(self.pristine_dir(), increase)
}
pub fn relativize<'a>(&self, path: &'a Path) -> Result<RepoPath<&'a Path>> {
match path.strip_prefix(&self.repo_root) {
Ok(p) => Ok(RepoPath(p)),
Err(_) => Err(Error::FileNotInRepo(path.to_path_buf()))
}
}
pub fn absolutize<'a>(&self, path: &RepoPath<impl AsRef<Path>>) -> PathBuf {
self.repo_root.as_ref().join(path.as_path())
}
}
impl<P: AsRef<Path> + 'static> RepoRoot<P> {
pub fn untracked_files<T: rand::Rng, Q: AsRef<Path>>(
&self,
txn: &MutTxn<T>,
path: Q,
) -> impl Iterator<Item = RepoPath<PathBuf>> + '_ {
let known_files = txn.list_files(ROOT_INODE).unwrap_or_else(|_| vec![]);
let o = OverrideBuilder::new(self.repo_root.as_ref())
.add("!.pijul")
.unwrap()
.build()
.unwrap();
let mut w = WalkBuilder::new(path.as_ref());
w.git_ignore(false)
.git_exclude(false)
.git_global(false)
.hidden(false)
.add_custom_ignore_filename(".pijulignore");
w.add_ignore(self.local_ignore_file());
w.overrides(o);
w.build().filter_map(move |f| {
if let Ok(f) = f {
let p = f.path();
if p == self.repo_root.as_ref() {
return None;
}
let p_in_repo = self.relativize(&p).unwrap();
if known_files.iter().any(|t| t.as_ref() == p_in_repo) {
return None
}
Some(p_in_repo.to_owned())
} else {
None
}
})
}
}
pub fn find_repo_root<'a>(dir: &'a Path) -> Option<RepoRoot<PathBuf>> {
let mut p = dir.to_path_buf();
loop {
p.push(PIJUL_DIR_NAME);
match metadata(&p) {
Ok(ref attr) if attr.is_dir() => {
p.pop();
return Some(RepoRoot { repo_root: p });
}
_ => {}
}
p.pop();
if !p.pop() {
return None;
}
}
}
#[doc(hidden)]
pub const ID_LENGTH: usize = 100;
pub fn create<R: Rng>(dir: &Path, mut rng: R) -> std::io::Result<RepoRoot<PathBuf>> {
let r = RepoRoot {
repo_root: canonicalize(dir)?,
};
let mut repo_dir = r.repo_dir();
create_dir_all(&repo_dir)?;
repo_dir.push("pristine");
create_dir_all(&repo_dir)?;
repo_dir.pop();
repo_dir.push("patches");
create_dir_all(&repo_dir)?;
repo_dir.pop();
repo_dir.push("id");
let mut f = std::fs::File::create(&repo_dir)?;
let mut x = String::new();
x.extend(rng.sample_iter(&Alphanumeric).take(ID_LENGTH));
f.write_all(x.as_bytes())?;
repo_dir.pop();
repo_dir.push("version");
let mut f = std::fs::File::create(&repo_dir)?;
writeln!(f, "{}", env!("CARGO_PKG_VERSION"))?;
repo_dir.pop();
repo_dir.push("local");
create_dir_all(&repo_dir)?;
repo_dir.pop();
repo_dir.push("hooks");
create_dir_all(&repo_dir)?;
repo_dir.pop();
repo_dir.push("local");
repo_dir.push("ignore");
std::fs::File::create(&repo_dir)?;
repo_dir.pop();
repo_dir.pop();
Ok(r)
}