diff --git a/mk/repo.mk b/mk/repo.mk index 7a2a9522..dba690c9 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) @@ -281,7 +289,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 +302,37 @@ scr.%: $(FSTOOLS_TAG) FORCE $(MAKE) sc.$* $(MAKE) r.$*,--with-package-deps +# 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 diff --git a/src/bin/repo.rs b/src/bin/repo.rs index e8a85a94..b54a3cf5 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -2,7 +2,10 @@ 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, 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}; use cookbook::cook::script::KILL_ALL_PID; @@ -58,15 +61,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 +94,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 +191,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 +263,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 +452,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 +467,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), } @@ -718,7 +717,7 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec) -> Result<(CliConfig, CliCommand, 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 { + let source_dir = recipe.dir.join("source"); + let rev = if config.with_rollback { + // 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) + }; + match rev { + Ok(rev) => { + let old_rev = recipe_lock.gitrev.replace(rev.clone()); + old_rev == Some(rev) + } + Err(e) => { + eprintln!("Skipping {}: {e}", recipe.name.as_str()); + continue; + } + } + } + } 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/fetch.rs b/src/cook/fetch.rs index 1c70b444..01ddf695 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, diff --git a/src/cook/fs.rs b/src/cook/fs.rs index d0711686..87f9a344 100644 --- a/src/cook/fs.rs +++ b/src/cook/fs.rs @@ -476,3 +476,35 @@ 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 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) +} diff --git a/src/recipe.rs b/src/recipe.rs index 051f8b2d..fd084c93 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -187,6 +187,8 @@ pub struct CookRecipe { /// If false, it's listed on install config pub is_deps: bool, pub rule: String, + /// whether if this recipe is pinned from cookbook.lock + pub pinned: bool, } impl Recipe { @@ -244,6 +246,7 @@ impl CookRecipe { target, is_deps: false, rule: "".into(), + pinned: false, }) } @@ -426,6 +429,10 @@ impl CookRecipe { } pub fn reload_recipe(&mut self) -> 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(())