Skip to content
Clone
a06673b
198 lines 5.8 KB Rust
use db::conn::sea_orm::*;
use db::git::git_branches as branch_entity;
use time::OffsetDateTime;
use uuid::Uuid;
use crate::error::{RepoError, RepoResult};
use crate::features::RepoService;
use crate::path::validate_branch_name;
use super::types::{
BranchPage, BranchView, CreateBranchOption, ListBranchQuery, MergeUpstreamRequest,
MergeUpstreamResponse, RenameBranchOption, UpdateBranchOption,
};
fn require_authenticated(actor: Uuid) -> RepoResult<()> {
if actor.is_nil() {
return Err(RepoError::Unauthorized("authentication required".into()));
}
Ok(())
}
async fn load_branch(
svc: &RepoService,
repo_id: Uuid,
name: &str,
) -> RepoResult<branch_entity::Model> {
let db = svc.db.reader();
let branch = branch_entity::Entity::find()
.filter(branch_entity::Column::RepoId.eq(repo_id))
.filter(branch_entity::Column::Name.eq(name))
.one(&db)
.await?
.ok_or_else(|| RepoError::NotFound(format!("branch {name}")))?;
Ok(branch)
}
pub async fn list_branches(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
q: ListBranchQuery,
) -> RepoResult<BranchPage> {
let _ = actor;
let limit = q.limit.unwrap_or(svc.config.default_pagination);
let offset = q.page.unwrap_or(0).saturating_mul(limit);
let limit = std::cmp::min(limit, svc.config.max_pagination);
let db = svc.db.reader();
let mut query = branch_entity::Entity::find()
.filter(branch_entity::Column::RepoId.eq(repo_id))
.filter(branch_entity::Column::IsDeleted.eq(false));
if let Some(kw) = q.keyword.as_deref() {
if !kw.is_empty() {
query = query.filter(branch_entity::Column::Name.contains(kw));
}
}
let total = query.clone().count(&db).await?;
let items = query
.order_by_asc(branch_entity::Column::Name)
.limit(Some(limit))
.offset(Some(offset))
.all(&db)
.await?
.into_iter()
.map(BranchView::from)
.collect();
Ok(BranchPage {
items,
total: total as i64,
})
}
pub async fn get_branch(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
name: &str,
) -> RepoResult<BranchView> {
let _ = actor;
let branch = load_branch(svc, repo_id, name).await?;
Ok(BranchView::from(branch))
}
pub async fn create_branch(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
input: CreateBranchOption,
) -> RepoResult<BranchView> {
require_authenticated(actor)?;
let new_name = validate_branch_name(&input.new_branch_name, svc.config.max_branch_name_len)?;
let source_name = input
.old_ref_name
.as_deref()
.or(input.old_branch_name.as_deref())
.unwrap_or("HEAD");
let source = load_branch(svc, repo_id, source_name).await?;
let db = svc.db.writer();
let now = OffsetDateTime::now_utc();
let active: branch_entity::ActiveModel = branch_entity::ActiveModel {
id: Set(Uuid::new_v4()),
repo_id: Set(repo_id),
name: Set(new_name),
commit_sha: Set(source.commit_sha),
is_deleted: Set(false),
is_protected: Set(false),
deleted_at: Set(None),
created_at: Set(now),
updated_at: Set(now),
};
let saved = active.insert(&db).await?;
Ok(BranchView::from(saved))
}
pub async fn update_branch(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
name: &str,
input: UpdateBranchOption,
) -> RepoResult<BranchView> {
require_authenticated(actor)?;
let branch = load_branch(svc, repo_id, name).await?;
if let Some(ref old) = input.old_commit_id {
if !old.is_empty() && branch.commit_sha != *old {
return Err(RepoError::Conflict(
"branch tip has moved; refresh and retry".into(),
));
}
}
let db = svc.db.writer();
let mut active: branch_entity::ActiveModel = branch.into();
active.commit_sha = Set(input.new_commit_id);
active.updated_at = Set(OffsetDateTime::now_utc());
let saved = active.update(&db).await?;
Ok(BranchView::from(saved))
}
pub async fn delete_branch(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
name: &str,
) -> RepoResult<()> {
require_authenticated(actor)?;
let branch = load_branch(svc, repo_id, name).await?;
if branch.is_protected {
return Err(RepoError::Forbidden(format!("branch {name} is protected")));
}
let db = svc.db.writer();
let now = OffsetDateTime::now_utc();
let mut active: branch_entity::ActiveModel = branch.into();
active.is_deleted = Set(true);
active.deleted_at = Set(Some(now));
active.updated_at = Set(now);
let _ = active.update(&db).await?;
Ok(())
}
pub async fn rename_branch(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
old_name: &str,
input: RenameBranchOption,
) -> RepoResult<BranchView> {
require_authenticated(actor)?;
let new_name = validate_branch_name(&input.new_name, svc.config.max_branch_name_len)?;
let branch = load_branch(svc, repo_id, old_name).await?;
if branch.is_protected {
return Err(RepoError::Forbidden(format!(
"branch {old_name} is protected"
)));
}
let db = svc.db.writer();
let mut active: branch_entity::ActiveModel = branch.into();
active.name = Set(new_name);
active.updated_at = Set(OffsetDateTime::now_utc());
let saved = active.update(&db).await?;
Ok(BranchView::from(saved))
}
pub async fn merge_upstream(
_svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
input: MergeUpstreamRequest,
) -> RepoResult<MergeUpstreamResponse> {
require_authenticated(actor)?;
let _ = repo_id;
let _ = input.branch;
let merge_type = if input.ff_only.unwrap_or(false) {
"fast-forward".to_string()
} else {
"merge".to_string()
};
let _ = actor;
Ok(MergeUpstreamResponse { merge_type })
}