///|
/// Execute hq get command
///
/// Clone a repository to the bhq root directory.
/// If subdir is specified, use sparse checkout.
pub fn hq_get(
repo : RepoPath,
config : HqConfig,
options : HqGetOptions,
/// Execute shell command and return (exit_code, stdout, stderr)
exec : (String) -> (Int, String, String),
/// Check if path exists
path_exists : (String) -> Bool,
) -> HqGetResult {
let local_path = repo.local_path(config.root)
// Check if already exists
if path_exists(local_path) {
if options.update {
if options.unshallow {
let (unshallow_code, _unshallow_stdout, unshallow_stderr) = exec(
"cd \{local_path} && git fetch --unshallow",
)
if unshallow_code != 0 {
return HqGetResult::Error("Failed to unshallow: \{unshallow_stderr}")
}
}
// Update existing repository
let cmd = "cd \{local_path} && git pull --ff-only"
let (code, _stdout, stderr) = exec(cmd)
if code == 0 {
HqGetResult::Updated(local_path)
} else {
HqGetResult::Error("Failed to update: \{stderr}")
}
} else {
HqGetResult::Skipped(local_path)
}
} else {
// Clone new repository
let clone_result = if repo.needs_sparse() {
clone_sparse(repo, local_path, options, exec)
} else {
clone_normal(repo, local_path, options, exec)
}
match clone_result {
Ok(_) => HqGetResult::Cloned(local_path)
Err(msg) => HqGetResult::Error(msg)
}
}
}
///|
fn resolve_hq_clone_depth(options : HqGetOptions) -> Int {
if options.depth > 0 {
options.depth
} else if options.shallow {
1
} else {
0
}
}
///|
fn clone_normal(
repo : RepoPath,
local_path : String,
options : HqGetOptions,
exec : (String) -> (Int, String, String),
) -> Result[Unit, String] {
let url = repo.clone_url(ssh=options.ssh)
let depth = resolve_hq_clone_depth(options)
let args : Array[String] = ["git", "clone"]
if depth > 0 {
args.push("--depth=" + depth.to_string())
}
match options.branch {
Some(branch) => {
args.push("--branch")
args.push(branch)
}
None => ()
}
args.push(url)
args.push(local_path)
let cmd = args.iter().map(s => s).join(" ")
let (code, _stdout, stderr) = exec(cmd)
if code == 0 {
Ok(())
} else {
Err("Clone failed: \{stderr}")
}
}
///|
fn clone_sparse(
repo : RepoPath,
local_path : String,
options : HqGetOptions,
exec : (String) -> (Int, String, String),
) -> Result[Unit, String] {
let url = repo.clone_url(ssh=options.ssh)
let depth = resolve_hq_clone_depth(options)
let subdir = repo.subdir.unwrap_or("")
// Step 1: Clone with filter and no checkout
let clone_args : Array[String] = ["git", "clone", "--no-checkout"]
if depth > 0 {
clone_args.push("--depth=" + depth.to_string())
}
match options.branch {
Some(branch) => {
clone_args.push("--branch")
clone_args.push(branch)
}
None => ()
}
clone_args.push(url)
clone_args.push(local_path)
let clone_cmd = clone_args.iter().map(s => s).join(" ")
let (code1, _stdout1, stderr1) = exec(clone_cmd)
if code1 != 0 {
return Err("Clone failed: \{stderr1}")
}
// Step 2: Initialize sparse checkout
let sparse_init_cmd = "cd \{local_path} && git sparse-checkout init --cone"
let (code2, _stdout2, stderr2) = exec(sparse_init_cmd)
if code2 != 0 {
return Err("Sparse checkout init failed: \{stderr2}")
}
// Step 3: Set sparse checkout paths
let sparse_dir = if subdir.has_prefix("/") { subdir } else { "/" + subdir }
let sparse_dir_with_sep = if sparse_dir.has_suffix("/") {
sparse_dir
} else {
sparse_dir + "/"
}
let sparse_set_cmd = "cd \{local_path} && git sparse-checkout set \{sparse_dir_with_sep}"
let (code3, _stdout3, stderr3) = exec(sparse_set_cmd)
if code3 != 0 {
return Err("Sparse checkout set failed: \{stderr3}")
}
Ok(())
}
///|
/// Build the git clone command string (for display/logging)
pub fn build_clone_command(
repo : RepoPath,
local_path : String,
options : HqGetOptions,
) -> String {
let url = repo.clone_url(ssh=options.ssh)
let depth = resolve_hq_clone_depth(options)
if repo.needs_sparse() {
let subdir = repo.subdir.unwrap_or("")
let sparse_dir = if subdir.has_prefix("/") { subdir } else { "/" + subdir }
let sparse_dir_with_sep = if sparse_dir.has_suffix("/") {
sparse_dir
} else {
sparse_dir + "/"
}
let sparse_args : Array[String] = ["git", "clone", "--no-checkout"]
if depth > 0 {
sparse_args.push("--depth=" + depth.to_string())
}
match options.branch {
Some(branch) => {
sparse_args.push("--branch")
sparse_args.push(branch)
}
None => ()
}
sparse_args.push(url)
sparse_args.push(local_path)
let clone_cmd = sparse_args.iter().map(s => s).join(" ")
"\{clone_cmd} && cd \{local_path} && git sparse-checkout init --cone && git sparse-checkout set \{sparse_dir_with_sep}"
} else {
let args : Array[String] = ["git", "clone"]
if depth > 0 {
args.push("--depth=" + depth.to_string())
}
match options.branch {
Some(branch) => {
args.push("--branch")
args.push(branch)
}
None => ()
}
args.push(url)
args.push(local_path)
args.iter().map(s => s).join(" ")
}
}