use super::{parse_form, resolve_repo_cwd};
use crate::server::app::AppState;
use crate::server::errors::error;
use axum::body::Bytes;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::routing::post;
use axum::{Json, Router};
use nomoreide_actions::git::{GitActions, PushCredential};
use nomoreide_core::git_manager::GitManager;
use serde::Serialize;
pub(super) fn routes() -> Router<AppState> {
Router::new()
.route("/api/git/push", post(push))
.route("/api/git/pull", post(pull))
.route("/api/git/merge", post(merge))
.route("/api/git/rebase", post(rebase))
.route("/api/git/default-branch/pull", post(pull_default))
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct OutputEnvelope {
ok: bool,
output: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct PushEnvelope {
ok: bool,
#[serde(flatten)]
result: nomoreide_actions::git::GitPushResult,
#[serde(skip_serializing_if = "Option::is_none")]
pushed_as: Option<String>,
}
async fn push(State(state): State<AppState>, body: Bytes) -> Response {
let form = parse_form(&body);
let (cwd, repository) =
match resolve_repo_cwd(&state, form.get("repo").map(String::as_str)).await {
Ok(resolved) => resolved,
Err(response) => return response,
};
let remote = form
.get("remote")
.map(|value| value.trim())
.filter(|value| !value.is_empty());
let config = match state.config_store.load().await {
Ok(config) => config,
Err(reason) => return error(StatusCode::BAD_REQUEST, &reason.to_string()),
};
let remote_url = GitManager::remote_url(&cwd, remote.unwrap_or("origin"))
.await
.unwrap_or_default();
let credential = nomoreide_core::git_identity::resolve_push_credential(
&config,
repository.as_ref(),
remote_url.as_deref(),
)
.await;
let push_credential = credential.as_ref().map(|(token, login)| PushCredential {
token,
username: login.as_deref(),
});
match GitActions::new(cwd).push(remote, push_credential).await {
Ok(result) => Json(PushEnvelope {
ok: true,
result,
pushed_as: credential.and_then(|(_, login)| login),
})
.into_response(),
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}
async fn pull(State(state): State<AppState>, body: Bytes) -> Response {
let form = parse_form(&body);
let (cwd, _repository) =
match resolve_repo_cwd(&state, form.get("repo").map(String::as_str)).await {
Ok(resolved) => resolved,
Err(response) => return response,
};
match GitActions::new(cwd).pull().await {
Ok(output) => Json(OutputEnvelope { ok: true, output }).into_response(),
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}
async fn merge(State(state): State<AppState>, body: Bytes) -> Response {
integrate(state, &body, Integration::Merge).await
}
async fn rebase(State(state): State<AppState>, body: Bytes) -> Response {
integrate(state, &body, Integration::Rebase).await
}
enum Integration {
Merge,
Rebase,
}
async fn integrate(state: AppState, body: &Bytes, kind: Integration) -> Response {
let form = parse_form(body);
let (cwd, _repository) =
match resolve_repo_cwd(&state, form.get("repo").map(String::as_str)).await {
Ok(resolved) => resolved,
Err(response) => return response,
};
let Some(branch) = form
.get("branch")
.map(|value| value.trim())
.filter(|value| !value.is_empty())
else {
return error(StatusCode::BAD_REQUEST, "branch is required");
};
let actions = GitActions::new(cwd);
let result = match kind {
Integration::Merge => actions.merge(branch).await,
Integration::Rebase => actions.rebase(branch).await,
};
match result {
Ok(output) => Json(OutputEnvelope { ok: true, output }).into_response(),
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct PullDefaultEnvelope {
ok: bool,
#[serde(flatten)]
result: nomoreide_actions::git::PullDefaultResult,
}
async fn pull_default(State(state): State<AppState>) -> Response {
let cwd = state.workspace_cwd().await;
match GitActions::new(cwd).pull_default(None).await {
Ok(result) => Json(PullDefaultEnvelope { ok: true, result }).into_response(),
Err(reason) => error(StatusCode::BAD_REQUEST, &reason.to_string()),
}
}