Submodules

This commit is contained in:
nora 2024-01-18 19:45:39 +01:00
commit 43d792e148
10 changed files with 712 additions and 0 deletions

140
src/submodule.rs Normal file
View file

@ -0,0 +1,140 @@
//! Implements a simplistic form of git submodules.
//!
//! The config format is as follows:
//!
//! A list of
//! ```toml
//! [[submodule]]
//! name = ""
//! url = ""
//! commit = ""
//! ```
//!
//! For example,
//! ```toml
//! [[submodule]]
//! name = "nixos"
//! url = "https://github.com/Nilstrieb/nixos.git"
//! commit = "c5b2fc10b9266b105d792d958b8f13479866a7bd"
//! ```
//!
//! This module will check them out into a directory called `submodules` in the current directory.
//! Make sure to put this directory into `.gitignore`.
use std::{io, path, process};
use color_eyre::{
eyre::{Context, OptionExt},
Result,
};
use crate::utils;
pub struct Submodules {
configs: Vec<SyncConfig>,
}
pub struct SyncConfig {
name: String,
url: String,
commit: String,
}
impl Submodules {
pub fn parse(s: &str) -> Result<Submodules> {
let doc = s.parse::<toml::Table>().wrap_err("invalid toml")?;
let subs = doc
.get("submodule")
.ok_or_eyre("no top-level submodule tables")?;
let mods = subs.as_array().ok_or_eyre("submodule is not an array")?;
let mut configs = Vec::new();
for module in mods {
let map = module.as_table().ok_or_eyre("submodule is not a table")?;
let get_str = |name| -> Result<String> {
Ok(map
.get(name)
.ok_or_eyre(format!("{name} is missing"))?
.as_str()
.ok_or_eyre(format!("{name} is not a string"))?
.into())
};
configs.push(SyncConfig {
name: get_str("name")?,
url: get_str("url")?,
commit: get_str("commit")?,
});
}
Ok(Self { configs })
}
}
pub fn sync(config: &Submodules) -> color_eyre::Result<()> {
info!("Syncing submodules...");
let submodules_path = path::Path::new("submodules");
match std::fs::create_dir(submodules_path) {
Ok(()) => info!("Created ./submodules"),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
e => return e.wrap_err("failed to create submodules"),
}
for sync in &config.configs {
let name = &sync.name;
let url = sync.url.as_str();
let span = info_span!("Syncing submodule", ?name, ?url);
let _span = span.enter();
let sub_path = submodules_path.join(name);
if !sub_path.exists() {
info!(?name, ?url, "Cloning");
let mut cmd = process::Command::new("git");
cmd.args(&["clone", url, sub_path.to_str().unwrap()]);
utils::run_process(&mut cmd).wrap_err("running git clone")?;
} else {
debug!(?name, ?url, "Repo already exists");
}
let current_commit = utils::run_process(
process::Command::new("git")
.args(&["rev-parse", "HEAD"])
.current_dir(&sub_path),
)
.wrap_err("running git rev-parse HEAD")?;
debug!(?current_commit, "Current commit");
if current_commit.trim() != sync.commit {
info!("Need to change commit");
let commit_exists = utils::run_process(
process::Command::new("git")
.args(&["cat-file", "-t", sync.commit.as_str()])
.current_dir(&sub_path),
);
if !commit_exists.is_ok_and(|typ| typ == "commit\n".to_owned()) {
info!("Must fetch commit");
utils::run_process(process::Command::new("git").current_dir(&sub_path).args(&[
"fetch",
"origin",
sync.commit.as_str(),
]))
.wrap_err("git fetch")?;
}
utils::run_process(process::Command::new("git").current_dir(&sub_path).args(&[
"reset",
"--hard",
sync.commit.as_str(),
]))
.wrap_err("git reset --hard")?;
}
}
Ok(())
}