diff --git a/src/app.rs b/src/app.rs index d9dddaa..1e85aa0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -661,7 +661,7 @@ async fn push_handler(State(state): State) -> 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) -> 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>, + remote_name: &str, + branch: &str, +) -> Result<(), Box> { + 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) -> 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);