Set up the flake repo directory.

This commit is contained in:
Tom Alexander
2026-02-14 16:00:37 -05:00
parent 9344e5708f
commit 7ec243e3e4
11 changed files with 449 additions and 19 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target /target
/work

10
Cargo.lock generated
View File

@@ -1023,6 +1023,15 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.11" version = "0.4.11"
@@ -1149,6 +1158,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2 0.6.1", "socket2 0.6.1",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys 0.61.2",

View File

@@ -14,7 +14,7 @@ opentelemetry-otlp = { version = "0.13.0", optional = true }
opentelemetry-semantic-conventions = { version = "0.12.0", optional = true } opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
serde = { version = "1.0.228", default-features = false, features = ["std", "derive"] } serde = { version = "1.0.228", default-features = false, features = ["std", "derive"] }
serde_json = { version = "1.0.149", default-features = false, features = ["std"] } serde_json = { version = "1.0.149", default-features = false, features = ["std"] }
tokio = { version = "1.49.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] } tokio = { version = "1.49.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util", "process"] }
toml = { version = "0.9.11", default-features = false, features = ["display", "parse", "serde", "std"] } toml = { version = "0.9.11", default-features = false, features = ["display", "parse", "serde", "std"] }
tracing = { version = "0.1.37", optional = true } tracing = { version = "0.1.37", optional = true }
tracing-opentelemetry = { version = "0.20.0", optional = true } tracing-opentelemetry = { version = "0.20.0", optional = true }

View File

@@ -1,17 +1,17 @@
work_directory = "/home/nixworker/persist/nix_builder" # work_directory = "/home/nixworker/persist/nix_builder"
[[targets]] [[targets]]
name = "odo" name = "odo"
repo = "https://code.fizz.buzz/talexander/machine_setup.git" repo = "https://code.fizz.buzz/talexander/machine_setup.git"
branch = "nix" branch = "nix"
path = "nix/configuration" path = "nix/configuration"
attr = "odo" attr = "odo"
[[targets]] [[targets]]
name = "odo_update" name = "odo_update"
repo = "https://code.fizz.buzz/talexander/machine_setup.git" repo = "https://code.fizz.buzz/talexander/machine_setup.git"
branch = "nix" branch = "nix"
path = "nix/configuration" path = "nix/configuration"
attr = "odo" attr = "odo"
update = true update = true
update_branch = "nix_update" update_branch = "nix_update"

View File

@@ -2,6 +2,10 @@ use crate::cli::parameters::BuildArgs;
use crate::config::Config; use crate::config::Config;
use crate::config::TargetConfig; use crate::config::TargetConfig;
use crate::error::CustomError; use crate::error::CustomError;
use crate::fs_util::assert_directory;
use crate::fs_util::is_git_repo;
use crate::git_util::git_force_into_state;
use crate::git_util::git_init_at_rev;
pub(crate) async fn run_build(args: BuildArgs) -> Result<(), CustomError> { pub(crate) async fn run_build(args: BuildArgs) -> Result<(), CustomError> {
println!("{:?}", args); println!("{:?}", args);
@@ -18,12 +22,41 @@ pub(crate) async fn run_build(args: BuildArgs) -> Result<(), CustomError> {
} }
}; };
prepare_flake_repo(target_config).await?; prepare_flake_repo(&config, target_config).await?;
} }
Ok(()) Ok(())
} }
async fn prepare_flake_repo(target_config: &TargetConfig) -> Result<(), CustomError> { async fn prepare_flake_repo(
todo!() config_root: &Config,
target_config: &TargetConfig,
) -> Result<(), CustomError> {
let flake_directory = target_config.get_flake_directory(config_root)?;
assert_directory!(
&flake_directory,
"Creating flake directory {}",
(&flake_directory).to_string_lossy()
);
if is_git_repo(&flake_directory).await? {
// Clean up the existing clone
git_force_into_state(
&flake_directory,
target_config.get_branch()?,
"origin",
target_config.get_repo()?,
target_config.get_revision()?,
)
.await?;
} else {
git_init_at_rev(
&flake_directory,
target_config.get_branch()?,
target_config.get_repo()?,
target_config.get_revision()?,
)
.await?;
}
Ok(())
} }

View File

@@ -12,16 +12,23 @@ use super::Config;
pub(crate) struct TargetConfig { pub(crate) struct TargetConfig {
pub(super) name: String, pub(super) name: String,
#[serde(default)] pub(super) repo: String,
pub(super) repo: Option<String>,
#[serde(default)] #[serde(default)]
pub(super) branch: Option<String>, pub(super) branch: Option<String>,
#[serde(default)]
pub(super) revision: Option<String>,
#[serde(default)] #[serde(default)]
pub(super) path: Option<String>, pub(super) path: Option<String>,
#[serde(default)] #[serde(default)]
pub(super) attr: Option<String>, pub(super) attr: Option<String>,
#[serde(default)] #[serde(default)]
pub(super) update: Option<bool>, pub(super) update: Option<bool>,
#[serde(default)] #[serde(default)]
pub(super) update_branch: Option<String>, pub(super) update_branch: Option<String>,
} }
@@ -42,4 +49,20 @@ impl TargetConfig {
let target_directory = self.get_target_directory(config_root)?; let target_directory = self.get_target_directory(config_root)?;
Ok(Cow::Owned(target_directory.join("flake"))) Ok(Cow::Owned(target_directory.join("flake")))
} }
pub(crate) fn get_repo(&'_ self) -> Result<&String, CustomError> {
Ok(&self.repo)
}
pub(crate) fn get_branch(&'_ self) -> Result<Cow<'_, str>, CustomError> {
if let Some(b) = &self.branch {
Ok(Cow::Borrowed(b))
} else {
Ok(Cow::Borrowed("main"))
}
}
pub(crate) fn get_revision(&self) -> Result<&Option<String>, CustomError> {
Ok(&self.revision)
}
} }

36
src/fs_util.rs Normal file
View File

@@ -0,0 +1,36 @@
macro_rules! assert_directory {
($dir:expr, $($params:tt)*) => {{
let dir = $dir;
let metadata = tokio::fs::metadata(&dir).await;
match metadata {
Ok(metadata) if !metadata.is_dir() => {
tracing::info!($($params)*);
tokio::fs::create_dir_all(dir).await?;
}
Err(_) => {
tracing::info!($($params)*);
tokio::fs::create_dir_all(dir).await?;
}
Ok(_) => {}
};
}};
}
use std::path::Path;
pub(crate) use assert_directory;
use crate::error::CustomError;
pub(crate) async fn is_directory<D: AsRef<Path>>(dir: D) -> Result<bool, CustomError> {
let metadata = tokio::fs::metadata(dir).await;
let result = match metadata {
Ok(metadata) if metadata.is_dir() => true,
_ => false,
};
Ok(result)
}
pub(crate) async fn is_git_repo<D: AsRef<Path>>(dir: D) -> Result<bool, CustomError> {
let dot_git = dir.as_ref().join(".git");
is_directory(dot_git).await
}

236
src/git_util/core.rs Normal file
View File

@@ -0,0 +1,236 @@
use tokio::process::Command;
use crate::error::CustomError;
use std::ffi::OsStr;
use std::path::Path;
pub(crate) async fn git_init<P, B>(dest: P, branch: B) -> Result<(), CustomError>
where
P: AsRef<Path>,
B: AsRef<str>,
{
// git -C $dest init --initial-branch=$branch
let mut command = Command::new("git");
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
command.arg("-C");
command.arg(dest);
command.args(&["init"]);
command.arg(format!("--initial-branch={}", branch.as_ref()));
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to init git repository: {}",
String::from_utf8(output.stderr)?
)
.into());
}
Ok(())
}
pub(crate) async fn git_remote_add<P, N, U>(dest: P, name: N, url: U) -> Result<(), CustomError>
where
P: AsRef<Path>,
N: AsRef<str>,
U: AsRef<str>,
{
// git -C $dest remote add $name $url
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
let name = name.as_ref();
let url = url.as_ref();
let mut command = Command::new("git");
command.arg("-C");
command.arg(dest);
command.args(&["remote", "add"]);
command.arg(name);
command.arg(url);
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to add remote to git repo: {}",
String::from_utf8(output.stderr)?
)
.into());
}
Ok(())
}
pub(crate) async fn git_fetch<P, N, R>(
dest: P,
name: N,
rev: R,
depth: Option<usize>,
blobless: bool,
treeless: bool,
) -> Result<(), CustomError>
where
P: AsRef<Path>,
N: AsRef<str>,
R: AsRef<str>,
{
// git -C $dest fetch [--depth=$depth] [--filter=blob:none] [--filter=tree:0] $name $rev
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
let name = name.as_ref();
let mut command = Command::new("git");
command.arg("-C");
command.arg(dest);
command.args(&["fetch"]);
if let Some(d) = depth {
command.arg(format!("--depth={}", d));
}
if blobless {
command.arg("--filter=blob:none");
}
if treeless {
command.arg("--filter=tree:0");
}
command.arg(name);
command.arg(rev.as_ref());
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to git fetch: {}",
String::from_utf8(output.stderr)?
)
.into());
}
Ok(())
}
pub(crate) async fn git_checkout<P, R>(
dest: P,
rev: R,
force: bool,
detach: bool,
) -> Result<(), CustomError>
where
P: AsRef<Path>,
R: AsRef<str>,
{
// git -C $dest checkout [--force] [--detach] $rev
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
let mut command = Command::new("git");
command.arg("-C");
command.arg(dest);
command.args(&["checkout"]);
if force {
command.arg("--force");
}
if detach {
command.arg("--detach");
}
command.arg(rev.as_ref());
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to git checkout: {}",
String::from_utf8(output.stderr)?
)
.into());
}
Ok(())
}
pub(crate) async fn git_get_remote<P, R>(dest: P, remote: R) -> Result<String, CustomError>
where
P: AsRef<Path>,
R: AsRef<str>,
{
// git -C $dest remote get-url $remote
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
let mut command = Command::new("git");
command.arg("-C");
command.arg(dest);
command.args(&["remote", "get-url"]);
command.arg(remote.as_ref());
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to get git remote: {}",
String::from_utf8(output.stderr)?
)
.into());
}
let url = {
let mut url = String::from_utf8(output.stdout)?;
url.truncate(url.trim_ascii_end().len());
url
};
Ok(url)
}
#[allow(dead_code)]
pub(crate) async fn git_reset<P, R>(dest: P, hard: bool, rev: R) -> Result<(), CustomError>
where
P: AsRef<Path>,
R: AsRef<str>,
{
// git -C $dest reset [--hard] $rev
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
let rev = rev.as_ref();
let mut command = Command::new("git");
command.arg("-C");
command.arg(dest);
command.args(&["reset"]);
if hard {
command.arg("--hard");
}
command.arg(rev);
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to git reset: {}",
String::from_utf8(output.stderr)?
)
.into());
}
Ok(())
}
pub(crate) async fn git_clean<P>(
dest: P,
recurse_into_untracked_directories: bool, // -d
force: bool, // -f
dont_use_ignore_rules: bool, // -x
) -> Result<(), CustomError>
where
P: AsRef<Path>,
{
// git -C $dest clean [-dfx]
let dest = AsRef::<OsStr>::as_ref(dest.as_ref());
let mut command = Command::new("git");
command.arg("-C");
command.arg(dest);
command.args(&["clean"]);
if recurse_into_untracked_directories {
command.arg("-d");
}
if force {
command.arg("--force");
}
if dont_use_ignore_rules {
command.arg("-x");
}
command.kill_on_drop(true);
let output = command.output().await?;
if !output.status.success() {
return Err(format!(
"Failing to git clean: {}",
String::from_utf8(output.stderr)?
)
.into());
}
Ok(())
}

View File

@@ -0,0 +1,85 @@
use crate::error::CustomError;
use crate::fs_util::assert_directory;
use crate::git_util::git_checkout;
use crate::git_util::git_clean;
use crate::git_util::git_fetch;
use crate::git_util::git_get_remote;
use crate::git_util::git_init;
use crate::git_util::git_remote_add;
use std::path::Path;
pub(crate) async fn git_init_at_rev<P, B, U>(
dest: P,
branch: B,
url: U,
rev: &Option<String>,
) -> Result<(), CustomError>
where
P: AsRef<Path>,
B: AsRef<str>,
U: AsRef<str>,
{
// mkdir -p $dest
// git -C $dest init --initial-branch=main
// git -C $dest remote add origin $url
// git -C $dest fetch --depth 1 origin ($rev|$branch)
// git -C $dest checkout FETCH_HEAD
assert_directory!(&dest, "Cloning git repo into {}", dest.as_ref().display());
let fetch_target = rev.as_deref().unwrap_or(branch.as_ref());
git_init(&dest, "main").await?;
git_remote_add(&dest, "origin", url).await?;
git_fetch(&dest, "origin", fetch_target, Some(1), false, false).await?;
git_checkout(&dest, "FETCH_HEAD", false, false).await?;
Ok(())
}
pub(crate) async fn git_force_into_state<P, B, R, U>(
dest: P,
branch: B,
remote: R,
url: U,
rev: &Option<String>,
) -> Result<(), CustomError>
where
P: AsRef<Path>,
B: AsRef<str>,
R: AsRef<str>,
U: AsRef<str>,
{
// Maybe instead
// git -C $dest fetch --depth 1 $remote ($rev|$branch)
// git -C $dest checkout --detach --force FETCH_HEAD
// git -C $dest clean -dfx
let fetch_target = rev.as_deref().unwrap_or(branch.as_ref());
check_remote_correct(&dest, &remote, url).await?;
git_fetch(&dest, &remote, fetch_target, Some(1), false, false).await?;
git_checkout(&dest, "FETCH_HEAD", true, true).await?;
git_clean(&dest, true, true, true).await?;
Ok(())
}
async fn check_remote_correct<F, R, U>(folder: F, remote: R, url: U) -> Result<(), CustomError>
where
F: AsRef<Path>,
R: AsRef<str>,
U: AsRef<str>,
{
let existing_remote = git_get_remote(folder, &remote).await?;
if existing_remote != url.as_ref() {
return Err(format!(
"Existing remote {} does not have the expected URL {} != {}",
remote.as_ref(),
existing_remote,
url.as_ref()
)
.into());
}
Ok(())
}

4
src/git_util/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
mod core;
mod high_level;
pub(crate) use core::*;
pub(crate) use high_level::*;

View File

@@ -13,6 +13,8 @@ mod cli;
mod command; mod command;
mod config; mod config;
mod error; mod error;
mod fs_util;
mod git_util;
mod init_tracing; mod init_tracing;
fn main() -> Result<ExitCode, CustomError> { fn main() -> Result<ExitCode, CustomError> {