aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar BanceDev 2026-03-02 14:22:55 -0500
committerGravatar BanceDev 2026-03-02 14:22:55 -0500
commitf76baa98acffc6a0d637ff360dd890a5fa10a973 (patch)
treed153197e38c5f26c0398a296259f0b1a98ce2ab4
parentdont fail on empty command (diff)
use lockfile for update tracking instead of trusting repo head
-rw-r--r--src/action.rs38
-rw-r--r--src/lock.rs46
-rw-r--r--src/main.rs1
-rw-r--r--src/util.rs33
4 files changed, 108 insertions, 10 deletions
diff --git a/src/action.rs b/src/action.rs
index dacf289..9816a6a 100644
--- a/src/action.rs
+++ b/src/action.rs
@@ -1,4 +1,5 @@
use crate::config::{self, Config, ConfigCommand, TEMP_CONFIG_PATH};
+use crate::lock::{self, Lockfile, Package};
use crate::util::{self, BASE_CONFIG_PATH, BASE_REPO_PATH, PackageList};
use git2::Repository;
use std::fs;
@@ -112,6 +113,17 @@ fn add(url: &str) -> Result<(), String> {
config::run_config_command(&config_path, &clone_path, ConfigCommand::PostInstall)?;
}
+ // only add to lockfile if installed
+ let oid = util::get_commit_hash_full(&clone_path)
+ .map_err(|e| format!("could not get commit hash: {e}"))?;
+
+ let mut lockfile = Lockfile::new();
+ lockfile.update_pkg(Package {
+ name: repo_name.to_string(),
+ source: url.to_string(),
+ checksum: oid.to_string(),
+ })?;
+
Ok(())
}
@@ -123,8 +135,19 @@ fn update() -> Result<(), String> {
let package_paths = util::collect_packages()?;
let mut update_pkgs: PackageList = vec![];
+ let mut lockfile = Lockfile::new();
for (name, path, config_path) in package_paths {
- if util::pull_repo(&path).map_err(|e| format!("failed to update repo: {e}"))? {
+ util::pull_repo(&path).map_err(|e| format!("failed to update repo: {e}"))?;
+ let oid = util::get_commit_hash_full(&path)
+ .map_err(|e| format!("could not get commit hash: {e}"))?;
+ let url = util::get_remote_url(&path)
+ .map_err(|e| format!("failed to get url for remote origin: {e}"))?;
+ let p = Package {
+ name: name.clone(),
+ source: url,
+ checksum: oid.to_string(),
+ };
+ if lockfile.out_of_date(p) {
update_pkgs.push((name, path, config_path));
}
}
@@ -135,6 +158,17 @@ fn update() -> Result<(), String> {
for (name, path, cfg_path) in update_pkgs {
config::run_config_command(&cfg_path, &path, ConfigCommand::Build)?;
config::run_config_command(&cfg_path, &path, ConfigCommand::Install)?;
+
+ let oid = util::get_commit_hash_full(&path)
+ .map_err(|e| format!("could not get commit hash: {e}"))?;
+ let url = util::get_remote_url(&path)
+ .map_err(|e| format!("failed to get url for remote origin: {e}"))?;
+ lockfile.update_pkg(lock::Package {
+ name: name.clone(),
+ source: url,
+ checksum: oid.to_string(),
+ })?;
+
println!("Upgraded {}", name);
}
}
@@ -189,7 +223,7 @@ fn list() -> Result<(), String> {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
if path.is_dir() {
- let oid = util::get_commit_hash(&path)
+ let oid = util::get_commit_hash_short(&path)
.map_err(|e| format!("failed to get commit hash: {e}"))?;
let oid = oid.as_str().unwrap();
if let Some(stem) = path.file_stem() {
diff --git a/src/lock.rs b/src/lock.rs
new file mode 100644
index 0000000..3aaacac
--- /dev/null
+++ b/src/lock.rs
@@ -0,0 +1,46 @@
+use serde::{Deserialize, Serialize};
+use std::fs;
+
+pub const LOCK_PATH: &str = "/var/db/forge/forge.lock";
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+pub struct Package {
+ pub name: String,
+ pub source: String,
+ pub checksum: String,
+}
+
+#[derive(Deserialize, Serialize, Default)]
+pub struct Lockfile {
+ pub package: Vec<Package>,
+}
+
+impl Lockfile {
+ pub fn new() -> Self {
+ if let Ok(contents) = fs::read_to_string(LOCK_PATH) {
+ toml::from_str(&contents).unwrap_or_default()
+ } else {
+ Lockfile::default()
+ }
+ }
+
+ pub fn out_of_date(&self, update: Package) -> bool {
+ if let Some(existing) = self.package.iter().find(|p| p.name == update.name) {
+ return existing.checksum != update.checksum;
+ }
+ true
+ }
+
+ pub fn update_pkg(&mut self, package: Package) -> Result<(), String> {
+ if let Some(existing) = self.package.iter_mut().find(|p| p.name == package.name) {
+ *existing = package;
+ } else {
+ self.package.push(package);
+ }
+
+ let toml_string = toml::to_string_pretty(&self)
+ .map_err(|e| format!("failed to serialize lockfile: {e}"))?;
+
+ fs::write(LOCK_PATH, toml_string).map_err(|e| format!("failed to write to lockfile: {e}"))
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 529b875..37a308a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,7 @@ use crate::config::TEMP_CONFIG_PATH;
mod action;
mod config;
+mod lock;
mod util;
fn main() {
diff --git a/src/util.rs b/src/util.rs
index 9d44e70..a829327 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,4 +1,4 @@
-use git2::{Buf, Cred, FetchOptions, RemoteCallbacks, Repository, build::CheckoutBuilder};
+use git2::{Buf, Cred, FetchOptions, Oid, RemoteCallbacks, Repository, build::CheckoutBuilder};
use std::env;
use std::fs;
use std::io;
@@ -71,7 +71,15 @@ pub fn dir_size(path: &Path) -> std::io::Result<u64> {
Ok(size)
}
-pub fn get_commit_hash(path: &Path) -> Result<Buf, git2::Error> {
+pub fn get_commit_hash_full(path: &Path) -> Result<Oid, git2::Error> {
+ let repo = Repository::open(path)?;
+ let head = repo.head()?;
+
+ let commit = head.peel_to_commit()?;
+ Ok(commit.id())
+}
+
+pub fn get_commit_hash_short(path: &Path) -> Result<Buf, git2::Error> {
let repo = Repository::open(path)?;
let head = repo.head()?;
@@ -85,6 +93,18 @@ pub fn get_editor() -> String {
.unwrap_or_else(|_| "nano".to_string())
}
+pub fn get_remote_url(path: &Path) -> Result<String, git2::Error> {
+ let repo = Repository::open(path)?;
+
+ let remote = repo.find_remote("origin")?;
+
+ if let Some(url) = remote.url() {
+ Ok(url.to_string())
+ } else {
+ Err(git2::Error::from_str("Remote 'origin' has no URL"))
+ }
+}
+
pub fn open_in_editor(editor: &str, file: &str) -> Result<(), String> {
let status = Command::new(editor)
.arg(file)
@@ -110,7 +130,7 @@ pub fn print_collected_packages(packages: &PackageList, message: &str) {
);
}
-pub fn pull_repo(path: &Path) -> Result<bool, git2::Error> {
+pub fn pull_repo(path: &Path) -> Result<(), git2::Error> {
let repo = Repository::open(path)?;
let head = repo.head()?;
@@ -140,13 +160,10 @@ pub fn pull_repo(path: &Path) -> Result<bool, git2::Error> {
reference.set_target(fetch_commit.id(), "Fast-Forward")?;
repo.set_head(&refname)?;
repo.checkout_head(Some(CheckoutBuilder::default().force()))?;
- Ok(true)
- } else if analysis.is_up_to_date() {
- Ok(false)
- } else {
+ } else if !analysis.is_up_to_date() {
println!("Non fast-forward merge required (manual merge needed).");
- Ok(false)
}
+ Ok(())
}
pub fn yn_prompt(prompt: &str) -> bool {