cookbook: self-heal git-sourced recipe source dirs with missing .git

When a git-sourced recipe's source/ directory exists but has no
embedded .git (e.g., a cleanup pass removed .git directories from
build-cache sources, or the dir was extracted from an archive without
.git), the cookbook previously hard-bailed with
  '{:?} is not a git repository, but recipe indicated git source'

This required manual intervention: the operator had to find the
broken source/ dir, rm -rf it, and re-run the build. With many
local recipes that use git URLs and embedded .git directories as
build caches (e.g., local/recipes/dev/ninja-build, local/recipes/kde/*),
this happened easily.

Fix: detect the missing .git, wipe the source dir, and re-clone from
the recipe's git URL. The fresh-clone logic is extracted to a new
reclone_git_source() helper used by both the initial-fetch path and
the self-heal path. After the self-heal, the source/ has a valid
.git and the rest of the fetch flow continues normally.

Tested by: deleting local/recipes/dev/ninja-build/source/.git (the
exact regression that triggered this fix) and running
./local/scripts/build-redbear.sh --upstream redbear-mini
which now self-heals instead of hard-failing.
This commit is contained in:
2026-07-02 07:34:27 +03:00
parent 34a11d845b
commit 32a776771c
+74 -39
View File
@@ -510,6 +510,53 @@ pub fn fetch_offline(recipe: &CookRecipe, logger: &PtyOut) -> Result<FetchResult
Ok(result)
}
/// Clone a fresh source directory for a git-sourced recipe. Used both on
/// the initial fetch path and on the self-heal path when the existing
/// source dir has lost its embedded .git directory (e.g., a cleanup pass
/// removed it). After this returns, `source_dir` exists with a valid
/// `.git` pointing at the recipe's `git` URL on `rev`/`branch`.
fn reclone_git_source(
recipe_dir: &Path,
source_dir: &Path,
git: &str,
branch: &Option<String>,
shallow_clone: bool,
logger: &PtyOut,
) -> Result<()> {
let source_dir_tmp = recipe_dir.join("source.tmp");
if source_dir_tmp.exists() {
std::fs::remove_dir_all(&source_dir_tmp).ok();
}
create_dir_clean(&source_dir_tmp)?;
let mut command = Command::new("git");
command
.arg("clone")
.arg("--recursive")
.arg(translate_mirror(git));
if let Some(branch) = branch {
command.arg("--branch").arg(branch);
}
if shallow_clone {
command
.arg("--filter=tree:0")
.arg("--also-filter-submodules");
}
command.arg(&source_dir_tmp);
if let Err(e) = run_command(command, logger) {
if !is_redox() {
return Err(e);
}
let mut cmds = vec!["update", "--init"];
if shallow_clone {
cmds.push("--filter=tree:0");
}
manual_git_recursive_submodule(logger, &source_dir_tmp, cmds)?;
}
rename(&source_dir_tmp, source_dir)?;
Ok(())
}
pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result<FetchResult> {
if redbear_protected_recipe(recipe.name.name()) && !redbear_allow_protected_fetch() {
log_to_pty!(
@@ -587,51 +634,39 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result
//TODO: use libgit?
let shallow_clone = *shallow_clone == Some(true);
let cached = if !source_dir.is_dir() {
// Create source.tmp
let source_dir_tmp = recipe_dir.join("source.tmp");
create_dir_clean(&source_dir_tmp)?;
// Clone the repository to source.tmp
let mut command = Command::new("git");
command
.arg("clone")
.arg("--recursive")
.arg(translate_mirror(&git));
if let Some(branch) = branch {
command.arg("--branch").arg(branch);
}
if shallow_clone {
command
.arg("--filter=tree:0")
.arg("--also-filter-submodules");
}
command.arg(&source_dir_tmp);
if let Err(e) = run_command(command, logger) {
if !is_redox() {
return Err(e);
}
// TODO: RedoxFS has a race condition problem with `--recursive` and running in multi CPU.
// It is appear that running the submodule update separately fixes it. Remove this when
// `git clone https://gitlab.redox-os.org/redox-os/relibc --recursive` proven to work in Redox OS.
let mut cmds = vec!["update", "--init"];
if shallow_clone {
cmds.push("--filter=tree:0");
}
manual_git_recursive_submodule(logger, &source_dir_tmp, cmds)?;
}
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
reclone_git_source(
&recipe_dir,
&source_dir,
&git,
&branch,
shallow_clone,
logger,
)?;
false
} else if !check_source {
true
} else {
if !source_dir.join(".git").is_dir() {
bail_other_err!(
"{:?} is not a git repository, but recipe indicated git source",
source_dir.display()
// Self-heal: source dir exists but has no .git (e.g., a
// cleanup pass removed embedded .git directories from
// build-cache sources, or the dir was extracted from an
// archive). Wipe and re-clone from the recipe's git URL
// instead of hard-failing — without this, every damaged
// build-cache source required manual intervention.
log_to_pty!(
logger,
" source dir {:?} exists but has no .git; re-cloning from {:?}",
source_dir.display(),
git
);
reclone_git_source(
&recipe_dir,
&source_dir,
&git,
&branch,
shallow_clone,
logger,
)?;
}
// Reset origin