pub mod git_apply;
pub mod git_branch;
pub mod git_commit;
pub mod git_diff;
pub mod git_merge;
pub mod git_rebase;
pub use git_apply::{
apply_cached_patch, apply_cached_patch_reverse, apply_patch, apply_patch_reverse,
restore_staged_patch,
};
pub use git_branch::{
branch_create, branch_delete, branch_force_create, branch_rename, branch_switch,
branch_switch_create_tracking, branch_switch_detach, branch_validate_name,
};
pub use git_commit::{
commit, commit_amend, commit_amend_no_edit, commit_with_editor, reset_hard, reset_mixed,
stage_all, stage_files, stage_path,
};
pub use git_diff::{
diff_cached_file, diff_cached_file_is_binary, diff_cached_files, diff_commit, diff_commit_file,
diff_commit_file_is_binary, diff_commit_name_status, diff_file, diff_file_is_binary, diff_head,
diff_head_file, diff_head_file_is_binary, diff_head_files, diff_head_name_only,
};
pub use git_merge::{MergeOutcome, continue_merge, merge_abort, merge_is_in_progress, merge_no_ff};
#[cfg(test)]
pub use git_rebase::rebase_onto;
pub use git_rebase::{
RebaseOutcome, continue_rebase, continue_rebase_or_abort, rebase, rebase_abort,
rebase_is_in_progress,
};
use std::path::Path;
use std::process::Command;
use std::time::Instant;
use anyhow::{Context, Result, bail};
use crate::trace as loom_trace;
const MIN_GIT_VERSION: (u32, u32) = (2, 38);
fn run_git_captured(workdir: &Path, args: &[&str]) -> Result<std::process::Output> {
let start = Instant::now();
let output = Command::new("git")
.current_dir(workdir)
.args(args)
.output()?;
let duration_ms = start.elapsed().as_millis();
let stderr = String::from_utf8_lossy(&output.stderr);
let cmd = args.join(" ");
loom_trace::log_command("git", &cmd, duration_ms, output.status.success(), &stderr);
if !output.status.success() {
bail!("git {} failed", args[0]);
}
Ok(output)
}
pub fn run_git(workdir: &Path, args: &[&str]) -> Result<()> {
run_git_captured(workdir, args).map(|_| ())
}
pub fn run_git_stdout(workdir: &Path, args: &[&str]) -> Result<String> {
let output = run_git_captured(workdir, args)?;
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
pub fn check_git_version() -> Result<()> {
let output = Command::new("git").arg("--version").output()?;
let version_str = String::from_utf8_lossy(&output.stdout);
let (major, minor) = parse_git_version(&version_str)
.with_context(|| format!("Could not parse Git version from: {}", version_str.trim()))?;
if (major, minor) < MIN_GIT_VERSION {
bail!(
"Git {}.{} is too old, git-loom requires Git {}.{} or later (for --update-refs)\n\
Current version: {}",
major,
minor,
MIN_GIT_VERSION.0,
MIN_GIT_VERSION.1,
version_str.trim()
);
}
Ok(())
}
fn parse_git_version(version_str: &str) -> Option<(u32, u32)> {
let version_part = version_str.trim().strip_prefix("git version ")?;
let mut parts = version_part.split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next()?.parse().ok()?;
Some((major, minor))
}
pub fn run_git_interactive(workdir: &Path, args: &[&str]) -> Result<()> {
let start = Instant::now();
let status = Command::new("git")
.current_dir(workdir)
.args(args)
.status()?;
let duration_ms = start.elapsed().as_millis();
let cmd = args.join(" ");
loom_trace::log_command("git", &cmd, duration_ms, status.success(), "");
if !status.success() {
bail!("git {} failed", args[0]);
}
Ok(())
}
pub fn unstage_files(workdir: &Path, files: &[&str]) -> Result<()> {
let mut args = vec!["reset", "HEAD", "--"];
args.extend(files);
run_git(workdir, &args)
}
pub fn restore_files_to_head(workdir: &Path, files: &[&str]) -> Result<()> {
let mut args = vec!["checkout", "HEAD", "--"];
args.extend(files);
run_git(workdir, &args)
}
pub fn rev_parse(workdir: &Path, reference: &str) -> Result<String> {
let out = run_git_stdout(workdir, &["rev-parse", reference])?;
Ok(out.trim().to_string())
}
pub fn short_hash(hash: &str) -> &str {
&hash[..7.min(hash.len())]
}
pub fn loom_exe_path() -> Result<std::path::PathBuf> {
let exe = std::env::current_exe()?;
if let Some(parent) = exe.parent()
&& parent.file_name().and_then(|n| n.to_str()) == Some("deps")
{
let bin_name = if cfg!(windows) {
"git-loom.exe"
} else {
"git-loom"
};
if let Some(profile_dir) = parent.parent() {
let actual = profile_dir.join(bin_name);
if actual.exists() {
return Ok(actual);
}
}
}
Ok(exe)
}