use crate::constants::{BRANCH_LOCKS_DIR, OXEN_HIDDEN_DIR};
use crate::core::index::{CommitReader, CommitWriter, EntryIndexer, RefReader, RefWriter};
use crate::error::OxenError;
use crate::model::{Branch, Commit, LocalRepository, RemoteBranch};
use crate::{api, util};
pub fn list(repo: &LocalRepository) -> Result<Vec<Branch>, OxenError> {
let ref_reader = RefReader::new(repo)?;
let branches = ref_reader.list_branches()?;
Ok(branches)
}
pub fn get_by_name(repo: &LocalRepository, name: &str) -> Result<Option<Branch>, OxenError> {
let ref_reader = RefReader::new(repo)?;
ref_reader.get_branch_by_name(name)
}
pub fn get_by_name_or_current(
repo: &LocalRepository,
branch_name: Option<&str>,
) -> Result<Branch, OxenError> {
if let Some(branch_name) = branch_name {
match api::local::branches::get_by_name(repo, branch_name)? {
Some(branch) => Ok(branch),
None => Err(OxenError::local_branch_not_found(branch_name)),
}
} else {
match api::local::branches::current_branch(repo)? {
Some(branch) => Ok(branch),
None => Err(OxenError::must_be_on_valid_branch()),
}
}
}
pub fn get_commit_id(repo: &LocalRepository, name: &str) -> Result<Option<String>, OxenError> {
match RefReader::new(repo) {
Ok(ref_reader) => ref_reader.get_commit_id_for_branch(name),
_ => Err(OxenError::basic_str("Could not read reference for repo.")),
}
}
pub fn exists(repo: &LocalRepository, name: &str) -> Result<bool, OxenError> {
match get_by_name(repo, name)? {
Some(_) => Ok(true),
None => Ok(false),
}
}
pub fn current_branch(repo: &LocalRepository) -> Result<Option<Branch>, OxenError> {
let ref_reader = RefReader::new(repo)?;
let branch = ref_reader.get_current_branch()?;
Ok(branch)
}
pub fn create_from_head(repo: &LocalRepository, name: &str) -> Result<Branch, OxenError> {
let ref_writer = RefWriter::new(repo)?;
let commit_reader = CommitReader::new(repo)?;
let head_commit = commit_reader.head_commit()?;
ref_writer.create_branch(name, &head_commit.id)
}
pub fn create(repo: &LocalRepository, name: &str, commit_id: &str) -> Result<Branch, OxenError> {
let ref_writer = RefWriter::new(repo)?;
let commit_reader = CommitReader::new(repo)?;
if commit_reader.commit_id_exists(commit_id) {
ref_writer.create_branch(name, commit_id)
} else {
Err(OxenError::commit_id_does_not_exist(commit_id))
}
}
pub fn create_checkout(repo: &LocalRepository, name: &str) -> Result<Branch, OxenError> {
println!("Create and checkout branch: {name}");
let head_commit = api::local::commits::head_commit(repo)?;
let ref_writer = RefWriter::new(repo)?;
let branch = ref_writer.create_branch(name, &head_commit.id)?;
ref_writer.set_head(name);
Ok(branch)
}
pub fn update(repo: &LocalRepository, name: &str, commit_id: &str) -> Result<Branch, OxenError> {
let ref_reader = RefReader::new(repo)?;
match ref_reader.get_branch_by_name(name)? {
Some(branch) => {
let ref_writer = RefWriter::new(repo)?;
match ref_writer.set_branch_commit_id(name, commit_id) {
Ok(()) => Ok(branch),
Err(err) => Err(err),
}
}
None => create(repo, name, commit_id),
}
}
pub fn delete(repo: &LocalRepository, name: &str) -> Result<(), OxenError> {
if let Ok(Some(branch)) = current_branch(repo) {
if branch.name == name {
let err = format!("Err: Cannot delete current checked out branch '{name}'");
return Err(OxenError::basic_str(err));
}
}
if branch_has_been_merged(repo, name)? {
let ref_writer = RefWriter::new(repo)?;
ref_writer.delete_branch(name)
} else {
let err = format!("Err: The branch '{name}' is not fully merged.\nIf you are sure you want to delete it, run 'oxen branch -D {name}'.");
Err(OxenError::basic_str(err))
}
}
pub fn force_delete(repo: &LocalRepository, name: &str) -> Result<(), OxenError> {
if let Ok(Some(branch)) = current_branch(repo) {
if branch.name == name {
let err = format!("Err: Cannot delete current checked out branch '{name}'");
return Err(OxenError::basic_str(err));
}
}
let ref_writer = RefWriter::new(repo)?;
ref_writer.delete_branch(name)
}
pub fn is_checked_out(repo: &LocalRepository, name: &str) -> bool {
match RefReader::new(repo) {
Ok(ref_reader) => {
if let Ok(Some(current_branch)) = ref_reader.get_current_branch() {
if current_branch.name == name {
return true;
}
}
false
}
_ => false,
}
}
pub async fn set_working_branch(repo: &LocalRepository, name: &str) -> Result<(), OxenError> {
let branch = api::local::branches::get_by_name(repo, name)?
.ok_or(OxenError::local_branch_not_found(name))?;
let commit = api::local::commits::get_by_id(repo, &branch.commit_id)?
.ok_or(OxenError::commit_id_does_not_exist(&branch.commit_id))?;
maybe_pull_missing_entries(repo, &commit).await?;
let commit_writer = CommitWriter::new(repo)?;
commit_writer.set_working_repo_to_commit(&commit).await
}
pub fn lock(repo: &LocalRepository, name: &str) -> Result<(), OxenError> {
let oxen_dir = repo.path.join(OXEN_HIDDEN_DIR);
let locks_dir = oxen_dir.join(BRANCH_LOCKS_DIR);
let clean_name = branch_name_no_slashes(name);
let branch_lock_file = locks_dir.join(clean_name);
log::debug!(
"Locking branch: {} to path {}",
name,
branch_lock_file.display()
);
if branch_lock_file.exists() {
return Err(OxenError::remote_branch_locked());
}
let maybe_branch = api::local::branches::get_by_name(repo, name)?;
let maybe_latest_commit;
if let Some(branch) = maybe_branch {
maybe_latest_commit = branch.commit_id;
} else {
maybe_latest_commit = "branch being created".to_string();
}
if !locks_dir.exists() {
util::fs::create_dir_all(&locks_dir)?;
}
util::fs::write_to_path(&branch_lock_file, maybe_latest_commit)?;
Ok(())
}
pub fn is_locked(repo: &LocalRepository, name: &str) -> Result<bool, OxenError> {
let oxen_dir = repo.path.join(OXEN_HIDDEN_DIR);
let locks_dir = oxen_dir.join(BRANCH_LOCKS_DIR);
if !locks_dir.exists() {
util::fs::create_dir_all(&locks_dir)?;
}
let clean_name = branch_name_no_slashes(name);
let branch_lock_file = locks_dir.join(clean_name);
log::debug!(
"Checking if branch is locked: {} at path {}",
name,
branch_lock_file.display()
);
Ok(branch_lock_file.exists())
}
pub fn read_lock_file(repo: &LocalRepository, name: &str) -> Result<String, OxenError> {
let oxen_dir = repo.path.join(OXEN_HIDDEN_DIR);
let locks_dir = oxen_dir.join(BRANCH_LOCKS_DIR);
let clean_name = branch_name_no_slashes(name);
let branch_lock_file = locks_dir.join(clean_name);
log::debug!(
"Reading lock file for branch: {} at path {}",
name,
branch_lock_file.display()
);
if !branch_lock_file.exists() {
let err = format!("Err: Branch '{name}' is not locked.");
return Err(OxenError::basic_str(err));
}
let contents = std::fs::read_to_string(branch_lock_file)?;
Ok(contents)
}
pub fn latest_synced_commit(repo: &LocalRepository, name: &str) -> Result<Commit, OxenError> {
if is_locked(repo, name)? {
let commit_id = read_lock_file(repo, name)?;
let commit = api::local::commits::get_by_id(repo, &commit_id)?
.ok_or(OxenError::commit_id_does_not_exist(&commit_id))?;
return Ok(commit);
}
let branch = api::local::branches::get_by_name(repo, name)?
.ok_or(OxenError::local_branch_not_found(name))?;
let commit = api::local::commits::get_by_id(repo, &branch.commit_id)?
.ok_or(OxenError::commit_id_does_not_exist(&branch.commit_id))?;
Ok(commit)
}
pub fn unlock(repo: &LocalRepository, name: &str) -> Result<(), OxenError> {
let oxen_dir = repo.path.join(OXEN_HIDDEN_DIR);
let locks_dir = oxen_dir.join(BRANCH_LOCKS_DIR);
let clean_name = branch_name_no_slashes(name);
let branch_lock_file = locks_dir.join(clean_name);
log::debug!(
"Unlocking branch: {} at path {}",
name,
branch_lock_file.display()
);
if !branch_lock_file.exists() {
log::debug!("Branch is not locked, nothing to do");
return Ok(());
}
util::fs::remove_file(&branch_lock_file)?;
Ok(())
}
async fn maybe_pull_missing_entries(
repo: &LocalRepository,
commit: &Commit,
) -> Result<(), OxenError> {
let rb = RemoteBranch::default();
let remote = repo.get_remote(&rb.remote);
if remote.is_none() {
log::debug!("No remote, no missing entries to fetch");
return Ok(());
}
let remote = remote.unwrap();
match api::remote::repositories::get_by_remote(&remote).await {
Ok(Some(remote_repo)) => {
let indexer = EntryIndexer::new(repo)?;
indexer
.pull_all_entries_for_commit(&remote_repo, commit)
.await?;
}
Ok(None) => {
log::debug!("No remote repo found, no entries to fetch");
}
Err(err) => {
log::error!("Error getting remote repo: {}", err);
}
};
Ok(())
}
pub async fn set_working_commit_id(
repo: &LocalRepository,
commit_id: &str,
) -> Result<(), OxenError> {
let commit = api::local::commits::get_by_id(repo, commit_id)?
.ok_or(OxenError::commit_id_does_not_exist(commit_id))?;
println!("Checkout commit: {commit}");
let commit_writer = CommitWriter::new(repo)?;
commit_writer.set_working_repo_to_commit(&commit).await
}
pub fn set_head(repo: &LocalRepository, value: &str) -> Result<(), OxenError> {
let ref_writer = RefWriter::new(repo)?;
ref_writer.set_head(value);
Ok(())
}
fn branch_has_been_merged(repo: &LocalRepository, name: &str) -> Result<bool, OxenError> {
let ref_reader = RefReader::new(repo)?;
let commit_reader = CommitReader::new(repo)?;
if let Some(branch_commit_id) = ref_reader.get_commit_id_for_branch(name)? {
if let Some(commit_id) = ref_reader.head_commit_id()? {
let history = commit_reader.history_from_commit_id(&commit_id)?;
for commit in history.iter() {
if commit.id == branch_commit_id {
return Ok(true);
}
}
Ok(false)
} else {
Ok(false)
}
} else {
let err = format!("Err: The branch '{name}' does not exist.");
Err(OxenError::basic_str(err))
}
}
pub fn rename_current_branch(repo: &LocalRepository, new_name: &str) -> Result<(), OxenError> {
if let Ok(Some(branch)) = current_branch(repo) {
let ref_writer = RefWriter::new(repo)?;
ref_writer.rename_branch(&branch.name, new_name)?;
ref_writer.set_head(new_name);
Ok(())
} else {
Err(OxenError::must_be_on_valid_branch())
}
}
fn branch_name_no_slashes(name: &str) -> String {
name.replace('/', "-")
}