feat: pull handler

- test simple pull ok

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-04-10 10:11:53 +07:00
parent bb3e55eecb
commit d19dab7561

View file

@ -661,7 +661,7 @@ async fn push_handler(State(state): State<AppState>) -> impl IntoResponse {
.map(|x| x.to_string())
.unwrap_or("master".to_string());
if let Err(e) = push(config, repo, remote_name, branch) {
if let Err(e) = push(config, repo, "origin", branch) {
return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": e.to_string()})),
@ -700,6 +700,84 @@ fn push(
Err("cannot lock repo".into())
}
async fn pull_handler(State(state): State<AppState>) -> impl IntoResponse {
let config = state.clone().get_all_configures();
let repo = state.repo.clone();
let remote_name = match state.get_config("GIT_REPO_REMOTE") {
Some(s) => s,
None => {
return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "repo remote not existed"})),
);
}
};
let branch = &config
.get("GIT_REPO_BRANCH_NAME")
.map(|x| x.to_string())
.unwrap_or("master".to_string());
if let Err(e) = pull(config, repo, "origin", branch) {
return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": e.to_string()})),
);
}
(
axum::http::StatusCode::OK,
Json(json!({"result": "pull completed"})),
)
}
// Pull is fetch + merge
fn pull(
config: gcm::Configure,
repo: Arc<Mutex<Repository>>,
remote_name: &str,
branch: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if let Ok(rlock) = repo.try_lock() {
let mut rem = rlock.find_remote(remote_name)?;
let mut callback = RemoteCallbacks::new();
callback.credentials(|_url, _user, _allowed| {
Cred::userpass_plaintext(
config.get("GIT_REPO_USERNAME").unwrap_or(&"".to_string()),
config.get("GIT_REPO_PASSWORD").unwrap_or(&"".to_string()),
)
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callback);
fetch_options.download_tags(git2::AutotagOption::All);
info!("fetching from {}...", remote_name);
rem.fetch(&[branch], Some(&mut fetch_options), None)?;
// resolve fetch vs merge
let fetch_head = rlock.find_reference("FETCH_HEAD")?;
let fetch_commit = rlock.reference_to_annotated_commit(&fetch_head)?;
let analysis = rlock.merge_analysis(&[&fetch_commit])?;
if analysis.0.is_fast_forward() {
let refname = format!("refs/heads/{branch}");
let mut reference = rlock.find_reference(&refname)?;
reference.set_target(fetch_commit.id(), "Fast-Forward")?;
rlock.set_head(&refname)?;
rlock.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?;
info!("Fast-forwared to {}", fetch_commit.id());
} else if analysis.0.is_normal() {
warn!("need manual merge");
} else {
info!("already up to date");
}
return Ok(());
}
Err("cannot lock repo".into())
}
/// Return git state & server state
async fn health_handler(State(_): State<AppState>) -> impl IntoResponse {
(
@ -748,6 +826,7 @@ pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
.route("/fetch", get(fetch_handler))
.route("/commit", post(commit_handler))
.route("/push", get(push_handler))
.route("/pull", get(pull_handler))
.route("/health", get(health_handler))
// .route("/healthz", get(reg::health))
.with_state(state);