xvc_file/common/
gitignore.rsuse chrono::Utc;
use crossbeam_channel::Sender;
use relative_path::RelativePathBuf;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use std::thread::JoinHandle;
use xvc_core::util::git::build_gitignore;
use crate::{Result, CHANNEL_CAPACITY};
use xvc_core::{XvcPath, XvcRoot};
use xvc_logging::{debug, error, info, uwr, XvcOutputSender};
use xvc_walker::{AbsolutePath, IgnoreRules, MatchResult};
pub enum IgnoreOperation {
IgnoreDir {
dir: XvcPath,
},
IgnoreFile {
file: XvcPath,
},
}
pub type IgnoreOp = Option<IgnoreOperation>;
pub fn make_ignore_handler(
output_snd: &XvcOutputSender,
xvc_root: &XvcRoot,
) -> Result<(Sender<IgnoreOp>, JoinHandle<()>)> {
let (sender, receiver) = crossbeam_channel::bounded(CHANNEL_CAPACITY);
let output_snd = output_snd.clone();
let xvc_root = xvc_root.absolute_path().clone();
let handle = std::thread::spawn(move || {
let mut ignore_dirs = Vec::<XvcPath>::new();
let mut ignore_files = Vec::<XvcPath>::new();
let gitignore = build_gitignore(&xvc_root).unwrap();
for op in receiver {
if let Some(op) = op {
match op {
IgnoreOperation::IgnoreDir { dir } => {
let path = dir.to_absolute_path(&xvc_root).to_path_buf();
if !ignore_dirs.contains(&dir)
&& matches!(gitignore.check(&path), MatchResult::NoMatch)
{
ignore_dirs.push(dir);
}
}
IgnoreOperation::IgnoreFile { file } => {
let path = file.to_absolute_path(&xvc_root).to_path_buf();
if !ignore_files.contains(&file)
&& matches!(gitignore.check(&path), MatchResult::NoMatch)
{
ignore_files.push(file);
}
}
}
} else {
break;
}
}
debug!(output_snd, "Writing directories to .gitignore");
uwr!(
update_dir_gitignores(&xvc_root, &gitignore, &ignore_dirs),
output_snd
);
let gitignore = build_gitignore(&xvc_root).unwrap();
debug!(output_snd, "Writing files to .gitignore");
uwr!(
update_file_gitignores(&xvc_root, &gitignore, &ignore_files),
output_snd
);
});
Ok((sender, handle))
}
pub fn update_dir_gitignores(
xvc_root: &AbsolutePath,
current_gitignore: &IgnoreRules,
dirs: &[XvcPath],
) -> Result<()> {
let dirs: Vec<XvcPath> = dirs
.iter()
.filter_map(|dir| {
let abs_path = if dir.ends_with("/") {
xvc_root.join(dir.to_string())
} else {
xvc_root.join(format!("{}/", dir))
};
let ignore_res = current_gitignore.check(&abs_path);
match ignore_res {
MatchResult::Ignore => {
info!("Path is already gitignored: {}", abs_path.to_string_lossy());
None
}
MatchResult::NoMatch => {
Some(dir.clone())
}
MatchResult::Whitelist => {
error!("Path is whitelisted in Git. Please remove/modify the whitelisting rule: {}",
abs_path.to_string_lossy());
None
}
}}).collect();
let mut changes = HashMap::<RelativePathBuf, Vec<String>>::new();
for dir in dirs {
let gi = dir
.parent()
.map(|p| p.join(".gitignore"))
.unwrap_or_else(|| RelativePathBuf::from(".gitignore"));
if !changes.contains_key(&gi) {
changes.insert(gi.clone(), Vec::<String>::new());
}
let path_v = changes.get_mut(&gi).unwrap();
path_v.push(
dir.file_name()
.map(|d| format!("/{}/", d))
.unwrap_or_else(|| "## Path Contains final ..".to_string()),
);
}
for (gitignore_file, values) in changes {
let append_str = format!(
"### Following {} lines are added by xvc on {}\n{}",
values.len(),
Utc::now().to_rfc2822(),
values.join("\n")
);
let gitignore_path = gitignore_file.to_path(xvc_root);
let mut file_o = OpenOptions::new()
.create(true)
.append(true)
.open(gitignore_path)?;
writeln!(file_o, "{}", append_str)?;
}
Ok(())
}
pub fn update_file_gitignores(
xvc_root: &AbsolutePath,
current_gitignore: &IgnoreRules,
files: &[XvcPath],
) -> Result<()> {
let files: Vec<XvcPath> = files.iter().filter_map(|f| match current_gitignore.check(&f.to_absolute_path(xvc_root)) {
MatchResult::NoMatch => {
Some(f.clone())
}
MatchResult::Ignore => {
info!("Already gitignored: {}", f.to_string());
None
}
MatchResult::Whitelist => {
error!("Path is whitelisted in Gitignore, please modify/remove the whitelisting rule: {}", f.to_string());
None
}}).collect();
let mut changes = HashMap::<RelativePathBuf, Vec<String>>::new();
for f in files {
let gi = f
.parent()
.map(|p| p.join(".gitignore"))
.unwrap_or_else(|| RelativePathBuf::from(".gitignore"));
if !changes.contains_key(&gi) {
changes.insert(gi.clone(), Vec::<String>::new());
}
let path_v = changes.get_mut(&gi).unwrap();
path_v.push(
f.file_name()
.map(|f| format!("/{}", f))
.unwrap_or_else(|| "## Path Contains final ..".to_string()),
);
}
for (gitignore_file, values) in changes {
let append_str = format!(
"### Following {} lines are added by xvc on {}\n{}",
values.len(),
Utc::now().to_rfc2822(),
values.join("\n")
);
let gitignore_path = gitignore_file.to_path(xvc_root);
let mut file_o = OpenOptions::new()
.create(true)
.append(true)
.open(gitignore_path)?;
writeln!(file_o, "{}", append_str)?;
}
Ok(())
}