///| Shared helper functions for native + js targets
///|
fn io_error(err : @fs.IOError) -> @bitcore.GitError {
@bitcore.GitError::IoError(@debug.render(Repr(err)))
}
///|
fn collect_args(args : Array[String], start : Int) -> Array[String] {
let out : Array[String] = []
for i in start.. String {
if path.length() == 0 || path == "/" {
return "/"
}
let sep = @path.sep.to_string()
match path.rev_find(sep) {
None => ""
Some(idx) => if idx == 0 { sep } else { string_slice_to(path, idx) }
}
}
///|
fn string_slice_from(text : String, start : Int) -> String {
String::unsafe_substring(text, start~, end=text.length())
}
///|
fn string_slice_to(text : String, end : Int) -> String {
let base = string_slice_from(text, 0)
String::unsafe_substring(base, start=0, end~)
}
///|
fn ensure_dir(path : String) -> Unit raise @bitcore.GitError {
if path.length() == 0 || path == "/" || path == "." {
return ()
}
if @fs.path_exists(path) {
let is_dir = @fs.is_dir(path) catch { _ => false }
if is_dir {
return ()
}
raise @bitcore.GitError::IoError("path exists and is not dir: " + path)
}
let parent = parent_dir(path)
if parent.length() > 0 && parent != path {
ensure_dir(parent)
}
@fs.create_dir(path) catch {
err => raise io_error(err)
}
}
///|
fn normalize_path(path : String) -> String {
let parts : Array[String] = []
for part_view in path.split("/") {
let part = part_view.to_owned()
if part == "" || part == "." {
continue
} else if part == ".." {
if parts.length() > 0 && parts[parts.length() - 1] != ".." {
let _ = parts.pop()
} else if !path.has_prefix("/") {
parts.push(part)
}
} else {
parts.push(part)
}
}
let result = parts.join("/")
if path.has_prefix("/") {
if result.length() == 0 {
"/"
} else {
"/" + result
}
} else if result.length() == 0 {
"."
} else {
result
}
}
///|
fn join_repo_marker(root : String, marker : String) -> String {
if root == "/" {
"/" + marker
} else if root == "." {
marker
} else {
root + "/" + marker
}
}
///|
fn is_path_prefix(path : String, prefix : String) -> Bool {
if prefix.length() > path.length() {
return false
}
if prefix == path {
return true
}
path.has_prefix(prefix) &&
path.length() > prefix.length() &&
path[prefix.length()] == '/'
}
///|
fn is_bare_repo_dir(path : String) -> Bool {
let head_ok = @fs.is_file(path + "/HEAD") catch { _ => false }
let objs_ok = @fs.is_dir(path + "/objects") catch { _ => false }
head_ok && objs_ok
}
///|
fn find_repo_root(start : String) -> String? {
let mut current = normalize_path(start)
if current.length() == 0 {
current = "."
}
let ceiling_dirs : Array[String] = []
match @env.get_env_var("GIT_CEILING_DIRECTORIES") {
Some(raw) =>
for view in raw.split(":") {
let dir = view.to_owned()
if dir.length() == 0 {
continue
}
ceiling_dirs.push(normalize_path(dir))
}
None => ()
}
// Compute min_offset: longest ceiling that is a strict component-aligned
// prefix of start (ceiling == start is skipped per git behavior).
let mut min_ceil_len = 0
for ceiling in ceiling_dirs {
if ceiling == current {
continue // start dir is always searchable
}
if is_path_prefix(current, ceiling) && ceiling.length() > min_ceil_len {
min_ceil_len = ceiling.length()
}
}
let mut last = ""
while true {
let bit_path = join_repo_marker(current, ".git")
let is_git_dir = @fs.is_dir(bit_path) catch { _ => false }
let is_git_file = if is_git_dir {
false
} else {
@fs.is_file(bit_path) catch {
_ => false
}
}
if is_git_dir || is_git_file {
return Some(current)
}
let bit_path = join_repo_marker(current, ".bit")
let is_bit_dir = @fs.is_dir(bit_path) catch { _ => false }
let is_bit_file = if is_bit_dir {
false
} else {
@fs.is_file(bit_path) catch {
_ => false
}
}
if is_bit_dir || is_bit_file {
return Some(current)
}
let parent = parent_dir(current)
let next = if parent.length() == 0 { "." } else { parent }
if next == current || next == last {
break
}
// Block ascending if we would reach at or above a ceiling directory
if min_ceil_len > 0 && next.length() <= min_ceil_len {
return None
}
last = current
current = next
}
None
}
///|
fn get_work_root() -> String {
match @env.get_env_var("GIT_SHIM_CWD") {
Some(dir) =>
// -C dir sets CWD to a subdirectory; find the repo root from there
if is_bare_repo_dir(dir) {
dir
} else {
find_repo_root(dir).unwrap_or(dir)
}
None =>
match @env.get_env_var("GIT_WORK_TREE") {
Some(dir) => dir
None =>
match @env.get_env_var("GIT_DIR") {
Some(dir) => normalize_repo_root(dir)
None =>
match @env.get_env_var("GIT_SHIM_PWD") {
Some(pwd) =>
if is_bare_repo_dir(pwd) {
pwd
} else {
find_repo_root(pwd).unwrap_or(".")
}
None => {
if @env.get_env_var("PWD") is Some(pwd) {
if is_bare_repo_dir(pwd) {
return pwd
}
if find_repo_root(pwd) is Some(root) {
return root
}
}
if @env.current_dir() is Some(dir) {
if is_bare_repo_dir(dir) {
return dir
}
if find_repo_root(dir) is Some(root) {
return root
}
}
if is_bare_repo_dir(".") {
return "."
}
"."
}
}
}
}
}
}
///|
fn normalize_repo_root(path : String) -> String {
if path == ".git" || path == ".bit" {
return "."
}
if path.has_suffix("/.git") && path.length() > 5 {
return string_slice_to(path, path.length() - 5)
}
if path.has_suffix("/.bit") && path.length() > 5 {
return string_slice_to(path, path.length() - 5)
}
path
}