use crate::error::Result;
use crate::git;
use crate::store::Store;
use std::path::{Path, PathBuf};
pub fn squash_into_main(store: &Store, branch: &str, msg: &str) -> Result<Option<String>> {
if !is_bare_repo(&store.root)? {
return squash_in_place(&store.root, branch, msg);
}
squash_in_detached_worktree(store, branch, msg)
}
fn squash_in_place(dir: &Path, branch: &str, msg: &str) -> Result<Option<String>> {
git::git_merge_squash(dir, branch)?;
if git::has_staged_changes(dir)? {
git::git_commit(dir, msg)?;
Ok(Some(git::git_resolve_sha(dir, "HEAD")?))
} else {
Ok(None)
}
}
fn squash_in_detached_worktree(
store: &Store,
branch: &str,
msg: &str,
) -> Result<Option<String>> {
let main = git::git_current_branch(&store.root)?;
let tmp = squash_worktree_path(store);
scrub_path(&store.root, &tmp);
if let Some(parent) = tmp.parent() {
std::fs::create_dir_all(parent)?;
}
worktree_add_detach(&store.root, &tmp, &main)?;
let result = squash_in_place(&tmp, branch, msg);
if let Ok(Some(sha)) = result.as_ref() {
update_ref(&store.root, &format!("refs/heads/{main}"), sha)?;
}
scrub_path(&store.root, &tmp);
result
}
fn squash_worktree_path(store: &Store) -> PathBuf {
store.local_dir().join(format!("squash-{}", std::process::id()))
}
fn scrub_path(repo: &Path, path: &Path) {
if path.exists() {
let _ = git::git_worktree_remove(repo, path, true);
}
if path.exists() {
let _ = std::fs::remove_dir_all(path);
}
}
fn is_bare_repo(dir: &Path) -> Result<bool> {
Ok(git::run_git_ok(dir, &["rev-parse", "--is-bare-repository"])?.trim() == "true")
}
fn worktree_add_detach(dir: &Path, path: &Path, refname: &str) -> Result<()> {
let path_str = path.to_string_lossy().to_string();
git::run_git_ok(dir, &["worktree", "add", "--detach", &path_str, refname])?;
Ok(())
}
fn update_ref(dir: &Path, name: &str, sha: &str) -> Result<()> {
git::run_git_ok(dir, &["update-ref", name, sha])?;
Ok(())
}
#[cfg(test)]
#[path = "bare_squash_tests.rs"]
mod tests;