From 8f5f71bc4f645c2b30cfd78a4e809edd5343dace Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 9 May 2026 16:28:42 +0700 Subject: [PATCH 1/6] Add capture-rev command --- mk/repo.mk | 9 ++++- src/bin/repo.rs | 92 +++++++++++++++++++++++++++++-------------------- src/cook/fs.rs | 12 +++++++ 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/mk/repo.mk b/mk/repo.mk index 7a2a95226..f89687b42 100644 --- a/mk/repo.mk +++ b/mk/repo.mk @@ -281,7 +281,7 @@ cc.%: $(FSTOOLS_TAG) FORCE ifeq ($(PODMAN_BUILD),1) $(PODMAN_RUN) make $@ else - $(REPO_BIN) change-rule --set-rule= $(foreach f,$(subst $(comma), ,$*),$(f)) --with-package-deps + $(REPO_BIN) change-rule --unset $(foreach f,$(subst $(comma), ,$*),$(f)) --with-package-deps endif # Set recipe rule to "binary" then invoke clean and rebuild @@ -294,6 +294,13 @@ scr.%: $(FSTOOLS_TAG) FORCE $(MAKE) sc.$* $(MAKE) r.$*,--with-package-deps +repo-lock: +ifeq ($(PODMAN_BUILD),1) + $(PODMAN_RUN) make $@ +else + $(REPO_BIN) capture-rev $(COOKBOOK_OPTS) --with-package-deps +endif + export DEBUG_BIN?= # Debug a statically linked program with gdbgui, for example: debug.drivers-initfs DEBUG_BIN=pcid diff --git a/src/bin/repo.rs b/src/bin/repo.rs index e8a85a94c..51dcb6253 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -2,7 +2,9 @@ use ansi_to_tui::IntoText; use cookbook::config::{CookConfig, CookLockOpt, get_config, init_config}; use cookbook::cook::cook_build::{build, get_stage_dirs, remove_stage_dir}; use cookbook::cook::fetch::{FetchResult, fetch, fetch_offline}; -use cookbook::cook::fs::{create_dir, create_target_dir, remove_all, run_command}; +use cookbook::cook::fs::{ + create_dir, create_target_dir, get_git_commit_date, get_git_head_rev, remove_all, run_command, +}; use cookbook::cook::package::{package, package_handle_push}; use cookbook::cook::pty::{PtyOut, UnixSlavePty, flush_pty, setup_pty, write_to_pty}; use cookbook::cook::script::KILL_ALL_PID; @@ -58,15 +60,15 @@ const REPO_HELP_STR: &str = r#" common flags: --cookbook= the "recipes" folder, default to $PWD/recipes --repo= the "repo" folder, default to $PWD/repo - --sysroot= the "root" folder used for "push" command - For Redox, defaults to "/", else default to $PWD/sysroot --with-package-deps include package deps (always implied in push command) --all apply to all recipes in --category= apply to all recipes in / --filesystem= override recipes config using installer file --repo-binary override recipes config to use repo_binary + --sysroot= used in "push", the "root" dir, default to $PWD/sysroot --set-rule= used in "change-rule", set wanted config rule - --rollback= used in "capture-rev", rollback to a commit instead + --rollback used in "capture-rev", allow git to rollback + --unset used in "capture-rev" and "change-rule", unset locks cook env and their defaults: CI= set to any value to disable TUI @@ -91,8 +93,9 @@ struct CliConfig { logs_dir: Option, category: Option, filesystem: Option, - rollback: Option, set_rule: Option, + unset: bool, + with_rollback: bool, with_package_deps: bool, all: bool, cook: CookConfig, @@ -187,16 +190,13 @@ impl CliConfig { None }, category: None, - sysroot_dir: if cfg!(target_os = "redox") { - PathBuf::from("/") - } else { - current_dir.join("sysroot") - }, + sysroot_dir: current_dir.join("sysroot"), with_package_deps: false, cook: get_config().cook.clone(), all: false, + unset: false, filesystem: None, - rollback: None, + with_rollback: false, set_rule: None, }) } @@ -262,11 +262,8 @@ fn main_inner() -> Result<()> { if command == CliCommand::Push { return handle_push(&recipes, &config); } - if command == CliCommand::ChangeRule { - return handle_change_rule(&recipes, &config); - } - if command == CliCommand::CaptureRev { - return handle_capture_rev(&recipes, &config); + if matches!(command, CliCommand::ChangeRule | CliCommand::CaptureRev) { + return handle_change_rule(&recipes, &config, &command); } let verbose = config.cook.verbose; @@ -454,7 +451,6 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec config.sysroot_dir = PathBuf::from(value), "--category" => config.category = Some(PathBuf::from(value)), "--set-rule" => config.set_rule = Some(value.into()), - "--rollback" => config.rollback = Some(value.into()), "--filesystem" => { config.filesystem = Some({ let r = redox_installer::Config::from_file(&PathBuf::from(value)); @@ -470,6 +466,8 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec override_filesystem_repo_binary = true, "--with-package-deps" => config.with_package_deps = true, + "--rollback" => config.with_rollback = true, + "--unset" => config.unset = true, "--all" => config.all = true, _ => bail_options_err!("Error: Unknown flag: {}", arg), } @@ -968,53 +966,73 @@ fn handle_tree(recipes: &Vec, is_build_tree: bool, _config: &CliConf Ok(()) } -fn handle_change_rule(recipes: &Vec, config: &CliConfig) -> Result<()> { +fn handle_change_rule( + recipes: &Vec, + config: &CliConfig, + command: &CliCommand, +) -> Result<()> { let mut lock = get_config().recipe_lock.clone(); - let is_pruning = config.set_rule.as_ref().is_some_and(|s| s.is_empty()); + let _cookbook_date = get_git_commit_date(&PathBuf::from("."))?; + let is_change_rule = matches!(command, CliCommand::ChangeRule); + let is_capture_rev = matches!(command, CliCommand::CaptureRev); for recipe in recipes { - if recipe.name.is_host() { + if is_change_rule && recipe.name.is_host() { // host packages will always be "source" so it's pointless to change their rule continue; } + if is_capture_rev && !matches!(recipe.recipe.source, Some(SourceRecipe::Git { .. })) { + continue; + } let recipe_name = recipe.name.without_prefix(); let mut recipe_lock = lock.get(recipe_name).cloned().unwrap_or_default(); - let cached = if is_pruning { - recipe_lock.fsrule.take().is_none() - } else { - let new_rule = config - .set_rule - .as_ref() - .cloned() - .unwrap_or_else(|| recipe.rule.clone()); + let cached = if is_change_rule { + if config.unset { + recipe_lock.fsrule.take().is_none() + } else { + let new_rule = config + .set_rule + .as_ref() + .cloned() + .unwrap_or_else(|| recipe.rule.clone()); - let old_rule = recipe_lock.fsrule.replace(new_rule.clone()); - old_rule == Some(new_rule) + let old_rule = recipe_lock.fsrule.replace(new_rule.clone()); + old_rule == Some(new_rule) + } + } else if is_capture_rev { + if config.unset { + recipe_lock.gitrev.take().is_none() + } else { + if config.with_rollback { + todo!(); + } + let (rev, _) = get_git_head_rev(&recipe.dir.join("source"))?; + let old_rev = recipe_lock.gitrev.replace(rev.clone()); + old_rev == Some(rev) + } + } else { + unreachable!() }; if recipe_lock.is_empty() { lock.remove(recipe_name); } else { lock.insert(recipe_name.to_string(), recipe_lock); } - let clean_cached = if !cached { + let clean_cached = if !cached && is_change_rule { handle_clean(recipe, config, &CliCommand::Clean)? } else { true }; if cached && clean_cached { - print_cached(&CliCommand::ChangeRule, &recipe.name); + print_cached(command, &recipe.name); } else { - print_success(&CliCommand::ChangeRule, &recipe.name); + print_success(command, &recipe.name); } } CookLockOpt { recipes: lock }.save(); Ok(()) } -fn handle_capture_rev(_recipes: &Vec, _config: &CliConfig) -> Result<()> { - todo!() -} - // // ------------- TUI SPECIFIC CODE ------------------- // diff --git a/src/cook/fs.rs b/src/cook/fs.rs index d07116864..c24efa2ae 100644 --- a/src/cook/fs.rs +++ b/src/cook/fs.rs @@ -476,3 +476,15 @@ fn get_git_branch_name(local_branch_path: &str) -> Result { ))? .to_string()) } + +pub fn get_git_commit_date(dir: &PathBuf) -> Result { + let mut git = process::Command::new("git"); + git.args(["log", "-1", "--date=iso-strict-local", "--format=%ad"]); + git.env("TZ", "UTC"); + git.current_dir(dir); + git.stdout(Stdio::piped()); + + git.output() + .map_err(wrap_io_err!("Executing git")) + .map(|s| String::from_utf8_lossy(&s.stdout).to_string()) +} From ca7e4737da2d192d935dc637fcd2114f4c6cea53 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 9 May 2026 18:32:32 +0700 Subject: [PATCH 2/6] Implement git rollback --- src/bin/repo.rs | 25 ++++++++++++++++++------- src/cook/fs.rs | 24 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 51dcb6253..898a11f9a 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -3,7 +3,8 @@ use cookbook::config::{CookConfig, CookLockOpt, get_config, init_config}; use cookbook::cook::cook_build::{build, get_stage_dirs, remove_stage_dir}; use cookbook::cook::fetch::{FetchResult, fetch, fetch_offline}; use cookbook::cook::fs::{ - create_dir, create_target_dir, get_git_commit_date, get_git_head_rev, remove_all, run_command, + create_dir, create_target_dir, get_git_commit_date, get_git_head_rev, get_git_rev_before_date, + remove_all, run_command, }; use cookbook::cook::package::{package, package_handle_push}; use cookbook::cook::pty::{PtyOut, UnixSlavePty, flush_pty, setup_pty, write_to_pty}; @@ -972,7 +973,7 @@ fn handle_change_rule( command: &CliCommand, ) -> Result<()> { let mut lock = get_config().recipe_lock.clone(); - let _cookbook_date = get_git_commit_date(&PathBuf::from("."))?; + let cookbook_date = get_git_commit_date(&PathBuf::from("."))?; let is_change_rule = matches!(command, CliCommand::ChangeRule); let is_capture_rev = matches!(command, CliCommand::CaptureRev); for recipe in recipes { @@ -1002,12 +1003,22 @@ fn handle_change_rule( if config.unset { recipe_lock.gitrev.take().is_none() } else { - if config.with_rollback { - todo!(); + let source_dir = recipe.dir.join("source"); + let rev = if config.with_rollback { + get_git_rev_before_date(&source_dir, &cookbook_date) + } else { + get_git_head_rev(&source_dir).map(|r| r.0) + }; + match rev { + Ok(rev) => { + let old_rev = recipe_lock.gitrev.replace(rev.clone()); + old_rev == Some(rev) + } + Err(e) => { + eprintln!("Skipped: {e}"); + continue; + } } - let (rev, _) = get_git_head_rev(&recipe.dir.join("source"))?; - let old_rev = recipe_lock.gitrev.replace(rev.clone()); - old_rev == Some(rev) } } else { unreachable!() diff --git a/src/cook/fs.rs b/src/cook/fs.rs index c24efa2ae..87f9a3442 100644 --- a/src/cook/fs.rs +++ b/src/cook/fs.rs @@ -485,6 +485,26 @@ pub fn get_git_commit_date(dir: &PathBuf) -> Result { git.stdout(Stdio::piped()); git.output() - .map_err(wrap_io_err!("Executing git")) - .map(|s| String::from_utf8_lossy(&s.stdout).to_string()) + .map_err(wrap_io_err!("Executing git log")) + .map(|s| String::from_utf8_lossy(&s.stdout).trim().to_string()) +} + +pub fn get_git_rev_before_date(dir: &PathBuf, date: &str) -> Result { + let mut git = process::Command::new("git"); + git.args(["rev-list", "-n", "1", &format!("--before={}", date), "HEAD"]); + git.current_dir(dir); + git.stdout(Stdio::piped()); + + let output = git + .output() + .map_err(wrap_io_err!("Executing git rev-list"))?; + let rev = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if rev.is_empty() { + return Err(Error::from(format!( + "No commit found before {} in {:?}", + date, dir + ))); + } + Ok(rev) } From 0e2ad35d671d3fbd20199e3ec6e9745f8426bd1d Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 9 May 2026 18:38:08 +0700 Subject: [PATCH 3/6] Don't run git fetch when cached and detached --- src/cook/fetch.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/cook/fetch.rs b/src/cook/fetch.rs index 1c70b444b..01ddf6953 100644 --- a/src/cook/fetch.rs +++ b/src/cook/fetch.rs @@ -186,6 +186,7 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result }) => { //TODO: use libgit? let shallow_clone = *shallow_clone == Some(true); + let mut fetch_is_ran = false; let cached = if !source_dir.is_dir() { // Create source.tmp let source_dir_tmp = recipe_dir.join("source.tmp"); @@ -234,18 +235,6 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result ); } - // Reset origin - let mut command = Command::new("git"); - command.arg("-C").arg(&source_dir); - command.arg("remote").arg("set-url").arg("origin").arg(git); - run_command(command, logger)?; - - // Fetch origin - let mut command = Command::new("git"); - command.arg("-C").arg(&source_dir); - command.arg("fetch").arg("origin"); - run_command(command, logger)?; - let (head_rev, detached_rev) = get_git_head_rev(&source_dir)?; match (rev, detached_rev) { (Some(rev), true) => { @@ -274,6 +263,8 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result } else if remote_name != "origin" || &remote_url != chop_dot_git(git) { false } else { + git_run_fetch(logger, &source_dir, git)?; + fetch_is_ran = true; match get_git_fetch_rev(&source_dir, &remote_url, &remote_branch) { Ok(fetch_rev) => fetch_rev == head_rev, Err(e) => { @@ -288,6 +279,9 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result }; if !cached { + if !fetch_is_ran { + git_run_fetch(logger, &source_dir, git)?; + } if let Some(_upstream) = upstream { //TODO: set upstream URL (is this needed?) // git remote set-url upstream "$GIT_UPSTREAM" &> /dev/null || @@ -459,6 +453,18 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result Ok(result) } +fn git_run_fetch(logger: &PtyOut, source_dir: &PathBuf, git: &String) -> Result<()> { + let mut command = Command::new("git"); + command.arg("-C").arg(source_dir); + command.arg("remote").arg("set-url").arg("origin").arg(git); + run_command(command, logger)?; + let mut command = Command::new("git"); + command.arg("-C").arg(source_dir); + command.arg("fetch").arg("origin"); + run_command(command, logger)?; + Ok(()) +} + fn manual_git_recursive_submodule( logger: &PtyOut, source_dir: &PathBuf, From 66d336baff1129a76f1881a35dec202cfe0e5b97 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 9 May 2026 19:10:49 +0700 Subject: [PATCH 4/6] Add rollback make target --- mk/repo.mk | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mk/repo.mk b/mk/repo.mk index f89687b42..43c314fc3 100644 --- a/mk/repo.mk +++ b/mk/repo.mk @@ -294,13 +294,37 @@ scr.%: $(FSTOOLS_TAG) FORCE $(MAKE) sc.$* $(MAKE) r.$*,--with-package-deps -repo-lock: +# Save current git rev for next recipe fetch, locking git recipes frozen in time +repo-lock: $(FSTOOLS_TAG) FORCE ifeq ($(PODMAN_BUILD),1) $(PODMAN_RUN) make $@ else $(REPO_BIN) capture-rev $(COOKBOOK_OPTS) --with-package-deps endif +# Undo repo-lock, allowing git recipes to get updated by next recipe fetch +repo-unlock: $(FSTOOLS_TAG) FORCE +ifeq ($(PODMAN_BUILD),1) + $(PODMAN_RUN) make $@ +else + $(REPO_BIN) capture-rev $(COOKBOOK_OPTS) --unset --with-package-deps +endif + +# Like repo-lock, but also checking out the specified git rev. +# Revert this operation by "git checkout master" "make pull" then "make repo-unlock". +# Will not work if rolling back to a commit before 2026. +repo-rollback.%: $(FSTOOLS_TAG) FORCE +ifeq ($(PODMAN_BUILD),1) + $(PODMAN_RUN) make $@ +# have to be done otherwise podman will rebuild + touch $(CONTAINER_TAG) +else + git checkout $* + $(REPO_BIN) capture-rev $(COOKBOOK_OPTS) --rollback --with-package-deps +endif +# have to be done otherwise cookbook will rebuild + touch $(FSTOOLS_TAG) + export DEBUG_BIN?= # Debug a statically linked program with gdbgui, for example: debug.drivers-initfs DEBUG_BIN=pcid From b356334d14e0f865c10641a4bd2ff2c604f4709f Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 9 May 2026 19:37:00 +0700 Subject: [PATCH 5/6] Run fetch before capturing rollback, add unfetch target --- mk/repo.mk | 8 ++++++++ src/bin/repo.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/mk/repo.mk b/mk/repo.mk index 43c314fc3..dba690c94 100644 --- a/mk/repo.mk +++ b/mk/repo.mk @@ -47,6 +47,14 @@ else $(REPO_BIN) fetch $(COOKBOOK_OPTS) --with-package-deps endif +# Unfetch and clean all recipes source or binary from filesystem config +unfetch: prefix $(FSTOOLS_TAG) FORCE +ifeq ($(PODMAN_BUILD),1) + $(PODMAN_RUN) make $@ +else + $(REPO_BIN) unfetch $(COOKBOOK_OPTS) --with-package-deps +endif + # Fetch Cargo dependencies for the cookbook tool (needed for REPO_OFFLINE=1 builds) cargo-fetch: FORCE ifeq ($(PODMAN_BUILD),1) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 898a11f9a..c604da735 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -1005,7 +1005,11 @@ fn handle_change_rule( } else { let source_dir = recipe.dir.join("source"); let rev = if config.with_rollback { - get_git_rev_before_date(&source_dir, &cookbook_date) + // invoke fetch as the git tracking can be different + match handle_fetch(recipe, config, false, &None) { + Ok(_) => get_git_rev_before_date(&source_dir, &cookbook_date), + Err(e) => Err(e), + } } else { get_git_head_rev(&source_dir).map(|r| r.0) }; @@ -1015,7 +1019,7 @@ fn handle_change_rule( old_rev == Some(rev) } Err(e) => { - eprintln!("Skipped: {e}"); + eprintln!("Skipping {}: {e}", recipe.name.as_str()); continue; } } From 35288e173d8bb15cb08aa04fdd39bc859f8e0a87 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 9 May 2026 20:34:52 +0700 Subject: [PATCH 6/6] Check pinned rev to fix reload on TUI --- src/bin/repo.rs | 3 ++- src/recipe.rs | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index c604da735..b54a3cf5f 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -717,7 +717,7 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec) -> Result<(CliConfig, CliCommand, Vec Result<(), PackageError> { + if self.pinned { + // TODO: print? + return Ok(()); + } self.recipe = Self::from_path(&self.dir, true, self.name.is_host())?.recipe; let _ = self.apply_filesystem_config(&self.rule.clone()); Ok(())