use std::path::Path;
use crate::error::{Error, Result};
use crate::index::{MODE_EXECUTABLE, MODE_SYMLINK};
pub fn apply_index_file_mode(abs_path: &Path, mode: u32) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(abs_path)?.permissions();
let new_mode = if mode == MODE_EXECUTABLE {
0o755
} else {
0o644
};
perms.set_mode(new_mode);
std::fs::set_permissions(abs_path, perms)?;
Ok(())
}
pub fn prepare_parent_dirs_for_checkout(work_tree: &Path, rel_path: &str) -> Result<()> {
use std::path::Component;
let path = Path::new(rel_path);
let Some(parent_rel) = path.parent() else {
return Ok(());
};
if parent_rel.as_os_str().is_empty() {
return Ok(());
}
let mut cur = work_tree.to_path_buf();
for comp in parent_rel.components() {
if let Component::Normal(name) = comp {
cur.push(name);
if let Ok(meta) = std::fs::symlink_metadata(&cur) {
if meta.file_type().is_symlink() {
std::fs::remove_file(&cur)?;
} else if !meta.is_dir() {
std::fs::remove_file(&cur)?;
}
}
}
}
Ok(())
}
pub fn write_to_worktree(work_tree: &Path, rel_path: &str, data: &[u8], mode: u32) -> Result<()> {
let abs_path = work_tree.join(rel_path);
prepare_parent_dirs_for_checkout(work_tree, rel_path)?;
if let Some(parent) = abs_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
Error::PathError(format!("creating parent directories for '{rel_path}': {e}"))
})?;
}
if let Ok(meta) = std::fs::symlink_metadata(&abs_path) {
if meta.file_type().is_symlink() {
std::fs::remove_file(&abs_path)?;
} else if meta.is_dir() {
std::fs::remove_dir_all(&abs_path)?;
} else {
std::fs::remove_file(&abs_path)?;
}
}
if mode == MODE_SYMLINK {
let target = std::str::from_utf8(data).map_err(|_| {
Error::PathError(format!("symlink target for '{rel_path}' is not UTF-8"))
})?;
std::os::unix::fs::symlink(target, &abs_path)
.map_err(|e| Error::PathError(format!("creating symlink '{rel_path}': {e}")))?;
} else {
std::fs::write(&abs_path, data)
.map_err(|e| Error::PathError(format!("writing '{rel_path}': {e}")))?;
if mode == MODE_EXECUTABLE {
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&abs_path)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&abs_path, perms)?;
}
}
Ok(())
}
pub fn remove_empty_parent_dirs(work_tree: &Path, path: &Path) {
let cwd = std::env::current_dir().ok();
let mut current = path.parent();
while let Some(dir) = current {
if dir == work_tree {
break;
}
if cwd
.as_ref()
.is_some_and(|cwd| cwd == dir || cwd.starts_with(dir))
{
break;
}
match std::fs::remove_dir(dir) {
Ok(()) => current = dir.parent(),
Err(_) => break,
}
}
}
pub fn is_glob_pattern(spec: &str) -> bool {
spec.contains('*') || spec.contains('?') || spec.contains('[')
}
pub fn glob_matches(pattern: &str, path: &str) -> bool {
glob_matches_inner(pattern.as_bytes(), path.as_bytes())
}
fn glob_matches_inner(pattern: &[u8], path: &[u8]) -> bool {
let mut pi = 0; let mut si = 0; let mut star_pi = usize::MAX;
let mut star_si = 0;
while si < path.len() {
if pi < pattern.len() && pattern[pi] == b'?' {
pi += 1;
si += 1;
} else if pi < pattern.len() && pattern[pi] == b'*' {
if pi + 1 < pattern.len() && pattern[pi + 1] == b'*' {
let rest = &pattern[pi + 2..];
let rest = if !rest.is_empty() && rest[0] == b'/' {
&rest[1..]
} else {
rest
};
for i in si..=path.len() {
if glob_matches_inner(rest, &path[i..]) {
return true;
}
}
return false;
}
star_pi = pi;
star_si = si;
pi += 1;
} else if pi < pattern.len() && pattern[pi] == b'[' {
pi += 1;
let negate = pi < pattern.len() && (pattern[pi] == b'!' || pattern[pi] == b'^');
if negate {
pi += 1;
}
let mut found = false;
let ch = path[si];
while pi < pattern.len() && pattern[pi] != b']' {
if pi + 2 < pattern.len() && pattern[pi + 1] == b'-' {
if ch >= pattern[pi] && ch <= pattern[pi + 2] {
found = true;
}
pi += 3;
} else {
if ch == pattern[pi] {
found = true;
}
pi += 1;
}
}
if pi < pattern.len() {
pi += 1;
} if found == negate {
if star_pi != usize::MAX {
pi = star_pi + 1;
star_si += 1;
si = star_si;
} else {
return false;
}
} else {
si += 1;
}
} else if pi < pattern.len() && pattern[pi] == path[si] {
pi += 1;
si += 1;
} else if star_pi != usize::MAX {
pi = star_pi + 1;
star_si += 1;
si = star_si;
} else {
return false;
}
}
while pi < pattern.len() && pattern[pi] == b'*' {
pi += 1;
}
pi == pattern.len()
}