use crate::config::ConfigSet;
use crate::diff::zero_oid;
use crate::error::Result;
use crate::ident_resolve::resolve_loose_committer_identity_with;
use crate::objects::ObjectId;
use crate::refs::handle::ExistingRef;
use crate::refs::store::{Expected, RawRef, RefUpdate, RefUpdateFlags, ReflogUpdate};
use crate::refs::{
self, append_reflog_with_config, commit_ref_update_on_store, create_ref_with_reflog_on_store,
delete_ref,
};
use crate::repo::Repository;
use crate::state::{resolve_head, HeadState};
pub fn checkout_reflog_identity(repo: &Repository) -> String {
let env = repo.environment();
let config = ConfigSet::load(env, Some(repo.git_dir()), true).unwrap_or_default();
resolve_loose_committer_identity_with(env, &config, repo.identity_now())
}
#[must_use]
pub fn checkout_reflog_from_label(head: &HeadState) -> Option<String> {
match head {
HeadState::Branch { short_name, .. } => Some(short_name.clone()),
HeadState::Detached { oid } => Some(oid.to_hex()),
HeadState::Invalid => None,
}
}
#[must_use]
pub fn branch_create_reflog_source_label(head: &HeadState) -> String {
match head {
HeadState::Branch {
short_name,
oid: Some(_),
..
} => short_name.clone(),
HeadState::Detached { .. } => "HEAD".to_owned(),
HeadState::Branch { .. } | HeadState::Invalid => "HEAD".to_owned(),
}
}
#[must_use]
pub fn switch_create_reflog_source_label(start_point: Option<&str>) -> String {
match start_point {
None => "HEAD".to_owned(),
Some(spec) => spec.to_owned(),
}
}
pub fn append_head_checkout_reflog(
repo: &Repository,
old_oid: ObjectId,
new_oid: ObjectId,
from_label: &str,
to_label: &str,
) -> Result<()> {
let config =
ConfigSet::load(repo.environment(), Some(repo.git_dir()), true).unwrap_or_default();
let identity = checkout_reflog_identity(repo);
let message = format!("checkout: moving from {from_label} to {to_label}");
append_reflog_with_config(
repo.git_dir(),
"HEAD",
&old_oid,
&new_oid,
&identity,
&message,
false,
Some(&config),
)
}
pub fn create_branch_ref_with_reflog(
repo: &Repository,
branch_ref: &str,
start_oid: ObjectId,
source_label: &str,
) -> Result<()> {
let config = repo.config()?;
let identity =
resolve_loose_committer_identity_with(repo.environment(), &config, repo.identity_now());
let message = format!("branch: Created from {source_label}");
create_ref_with_reflog_on_store(
repo.refs(),
repo.git_dir(),
&config,
branch_ref,
&start_oid,
&identity,
&message,
)
}
pub(crate) fn reset_branch_ref_with_reflog(
repo: &Repository,
branch_ref: &str,
new_oid: ObjectId,
source_label: &str,
existing: &ExistingRef,
) -> Result<()> {
let identity = checkout_reflog_identity(repo);
let message = format!("branch: Reset to {source_label}");
let config =
ConfigSet::load(repo.environment(), Some(repo.git_dir()), true).unwrap_or_default();
let reflog =
if crate::refs::should_autocreate_reflog_with_config(&config, repo.git_dir(), branch_ref)
|| crate::reflog::reflog_exists(repo.git_dir(), branch_ref)
{
Some(ReflogUpdate {
identity,
message,
time: time::OffsetDateTime::now_utc(),
old_oid: None,
new_oid: None,
})
} else {
None
};
let (expected, no_deref) = match &existing.raw {
RawRef::Symbolic(target) => (Expected::Symref(target.clone()), true),
RawRef::Direct(oid) => (Expected::Oid(*oid), false),
};
commit_ref_update_on_store(
repo.refs(),
RefUpdate {
name: branch_ref.to_owned(),
new_value: Some(RawRef::Direct(new_oid)),
expected,
reflog,
flags: RefUpdateFlags {
no_deref,
..RefUpdateFlags::default()
},
},
)
}
fn restore_head(repo: &Repository, head_before: &HeadState) -> Result<()> {
match head_before {
HeadState::Branch { refname, .. } => {
refs::write_symbolic_ref(repo.git_dir(), "HEAD", refname)?;
}
HeadState::Detached { oid } => refs::write_ref(repo.git_dir(), "HEAD", oid)?,
HeadState::Invalid => {}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn finish_switch_ref_updates(
repo: &Repository,
head_before: &HeadState,
branch_create: Option<(String, ObjectId, String)>,
detached: bool,
branch_ref: &str,
target_oid: ObjectId,
from_label: &str,
to_label: &str,
) -> Result<()> {
let created_branch = branch_create.as_ref().map(|(name, _, _)| name.clone());
if let Some((refname, oid, source)) = branch_create {
create_branch_ref_with_reflog(repo, &refname, oid, &source)?;
}
let head_result = if detached {
refs::write_ref(repo.git_dir(), "HEAD", &target_oid)
} else {
refs::write_symbolic_ref(repo.git_dir(), "HEAD", branch_ref)
};
if let Err(err) = head_result {
if let Some(refname) = created_branch {
let _ = delete_ref(repo.git_dir(), &refname);
}
return Err(err);
}
let old_oid = match head_before {
HeadState::Branch { oid: None, .. } => zero_oid(),
_ => head_before.oid().copied().unwrap_or_else(zero_oid),
};
if let Err(err) = append_head_checkout_reflog(repo, old_oid, target_oid, from_label, to_label) {
let _ = restore_head(repo, head_before);
if let Some(refname) = created_branch {
let _ = delete_ref(repo.git_dir(), &refname);
}
return Err(err);
}
Ok(())
}
pub fn is_already_on_branch(
repo: &Repository,
branch_ref: &str,
target_oid: ObjectId,
) -> Result<bool> {
let head = resolve_head(repo.git_dir())?;
Ok(matches!(
head,
HeadState::Branch {
ref refname,
oid: Some(oid),
..
} if refname == branch_ref && oid == target_oid
))
}
pub fn is_already_detached_at(repo: &Repository, target_oid: ObjectId) -> Result<bool> {
let head = resolve_head(repo.git_dir())?;
Ok(matches!(
head,
HeadState::Detached { oid } if oid == target_oid
))
}