use crate::errors::OxenHttpError;
use crate::helpers::get_repo;
use crate::params::{app_data, parse_base_head, path_param, resolve_base_head_branches};
use actix_web::{HttpRequest, HttpResponse};
use liboxen::error::OxenError;
use liboxen::repositories;
use liboxen::view::StatusMessage;
use liboxen::view::merge::{
MergeConflictFile, MergeResult, MergeSuccessResponse, Mergeable, MergeableResponse,
};
#[utoipa::path(
get,
path = "/api/repos/{namespace}/{repo_name}/merge/{base_head}",
tag = "Merge",
description = "Check if two branches can be merged and list any conflicts.",
params(
("namespace" = String, Path, description = "Namespace of the repository", example = "ox"),
("repo_name" = String, Path, description = "Name of the repository", example = "satellite-images"),
("base_head" = String, Path, description = "The base and head revisions separated by '..'", example = "main..feature/add-labels"),
),
responses(
(status = 200, description = "Merge status returned successfully", body = MergeableResponse),
(status = 404, description = "Repository or one of the revisions not found")
)
)]
pub async fn show(req: HttpRequest) -> actix_web::Result<HttpResponse, OxenHttpError> {
let app_data = app_data(&req)?;
let namespace = path_param(&req, "namespace")?.to_string();
let name = path_param(&req, "repo_name")?.to_string();
let base_head = path_param(&req, "base_head")?.to_string();
let repository = get_repo(app_data, namespace, name)?;
let (base, head) = parse_base_head(&base_head)?;
let (base_commit, head_commit) = resolve_base_head_branches(&repository, &base, &head)?;
let base = base_commit.ok_or_else(|| OxenError::RevisionNotFound(base.into()))?;
let head = head_commit.ok_or_else(|| OxenError::RevisionNotFound(head.into()))?;
let conflicts =
repositories::merge::list_conflicts_between_branches(&repository, &base, &head).await?;
let conflicts: Vec<MergeConflictFile> = conflicts
.into_iter()
.map(|path| MergeConflictFile {
path: path.to_string_lossy().to_string(),
})
.collect();
let is_mergeable = conflicts.is_empty();
let commits = repositories::merge::list_commits_between_branches(&repository, &base, &head)?;
let response = MergeableResponse {
status: StatusMessage::resource_found(),
mergeable: Mergeable {
is_mergeable,
conflicts,
commits,
},
};
Ok(HttpResponse::Ok().json(response))
}
#[utoipa::path(
post,
path = "/api/repos/{namespace}/{repo_name}/merge/{base_head}",
tag = "Merge",
description = "Merge the head branch into the base branch, creating a merge commit.",
params(
("namespace" = String, Path, description = "Namespace of the repository", example = "ox"),
("repo_name" = String, Path, description = "Name of the repository", example = "satellite-images"),
("base_head" = String, Path, description = "The base and head revisions separated by '..'", example = "main..feature/add-labels"),
),
responses(
(status = 200, description = "Branches merged successfully", body = MergeSuccessResponse),
(status = 409, description = "Merge conflict", body = StatusMessage),
(status = 404, description = "Repository or one of the revisions not found"),
)
)]
pub async fn merge(req: HttpRequest) -> actix_web::Result<HttpResponse, OxenHttpError> {
let app_data = app_data(&req)?;
let namespace = path_param(&req, "namespace")?.to_string();
let name = path_param(&req, "repo_name")?.to_string();
let base_head = path_param(&req, "base_head")?.to_string();
let repo = get_repo(app_data, namespace, name)?;
let (base, head) = parse_base_head(&base_head)?;
let (maybe_base_branch, maybe_head_branch) = resolve_base_head_branches(&repo, &base, &head)?;
let Some(base_branch) = maybe_base_branch else {
return Err(OxenError::RevisionNotFound(base.into()).into());
};
let Some(head_branch) = maybe_head_branch else {
return Err(OxenError::RevisionNotFound(head.into()).into());
};
let base_commit = repositories::commits::get_by_id(&repo, &base_branch.commit_id)?.unwrap();
let head_commit = repositories::commits::get_by_id(&repo, &head_branch.commit_id)?.unwrap();
let merge_commit =
repositories::merge::merge_into_base(&repo, &head_branch, &base_branch).await?;
let response = MergeSuccessResponse {
status: StatusMessage::resource_found(),
commits: MergeResult {
base: base_commit,
head: head_commit,
merge: merge_commit,
},
};
Ok(HttpResponse::Ok().json(response))
}