feat: commit, push, pull
- test ok but must call pull first before do commit or push Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
bca1c911d3
commit
ae9d9fa66b
3 changed files with 667 additions and 555 deletions
1034
Cargo.lock
generated
1034
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
183
src/app.rs
183
src/app.rs
|
|
@ -13,7 +13,10 @@ use axum_macros::debug_handler;
|
|||
use bb8::{Pool, PooledConnection};
|
||||
use bb8_redis::RedisConnectionManager;
|
||||
use brotli::CompressorWriter;
|
||||
use git2::{Cred, FetchOptions, Object, PushOptions, RemoteCallbacks, Repository};
|
||||
use git2::{
|
||||
Cred, FetchOptions, Object, ObjectType, PushOptions, RemoteCallbacks, Repository, ResetType,
|
||||
build::CheckoutBuilder,
|
||||
};
|
||||
|
||||
use image::load_from_memory;
|
||||
use log::{error, info, warn};
|
||||
|
|
@ -711,23 +714,22 @@ async fn commit_file_content(
|
|||
message: &str,
|
||||
branch: String,
|
||||
) -> Result<git2::Oid, Box<dyn std::error::Error>> {
|
||||
let repo_clone = repo.clone();
|
||||
let blob_oid = repo_clone.lock().await.blob(content)?;
|
||||
info!("blob oid: {blob_oid}");
|
||||
let mut index = repo_clone.lock().await.index()?;
|
||||
info!("index pass");
|
||||
let rlock = repo_clone.try_lock()?;
|
||||
let parent = match rlock.head() {
|
||||
Ok(head) => {
|
||||
let commit = head.peel_to_commit()?;
|
||||
let repo_guard = repo.lock().await;
|
||||
|
||||
let blob_oid = repo_guard.blob(content)?;
|
||||
|
||||
let mut index = repo_guard.index()?;
|
||||
|
||||
let target_ref = format!("refs/heads/{branch}");
|
||||
let parent_commit = match repo_guard.find_reference(&target_ref) {
|
||||
Ok(reference) => {
|
||||
let commit = reference.peel_to_commit()?;
|
||||
index.read_tree(&commit.tree()?)?;
|
||||
Some(commit.clone())
|
||||
Some(commit)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
info!("parent pass");
|
||||
|
||||
index.add(&git2::IndexEntry {
|
||||
ctime: git2::IndexTime::new(0, 0),
|
||||
mtime: git2::IndexTime::new(0, 0),
|
||||
|
|
@ -743,23 +745,19 @@ async fn commit_file_content(
|
|||
path: path.as_bytes().to_vec(),
|
||||
})?;
|
||||
|
||||
info!("index added");
|
||||
|
||||
let tree_oid = index.write_tree()?;
|
||||
|
||||
info!("write to tree");
|
||||
// let tlock = repo_clone.try_lock()?;
|
||||
info!("acquire try lock");
|
||||
let tree = rlock.find_tree(tree_oid)?;
|
||||
info!("find tree ok");
|
||||
let tree = repo_guard.find_tree(tree_oid)?;
|
||||
|
||||
let sig = git2::Signature::now(&author.username, &author.email)?;
|
||||
info!("generated signature");
|
||||
|
||||
let parents: Vec<&git2::Commit> = parent.iter().collect();
|
||||
let oid = rlock.commit(
|
||||
let parents = match &parent_commit {
|
||||
Some(c) => vec![c],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let oid = repo_guard.commit(
|
||||
//"refs/heads/master"
|
||||
Some(&format!("refs/heads/{branch}")),
|
||||
Some(&target_ref),
|
||||
&sig,
|
||||
&sig,
|
||||
message,
|
||||
|
|
@ -775,22 +773,13 @@ async fn commit_file_content(
|
|||
async fn push_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) = push(config, repo, "origin", branch) {
|
||||
if let Err(e) = push(config, repo, "origin", branch).await {
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": e.to_string()})),
|
||||
|
|
@ -803,51 +792,52 @@ async fn push_handler(State(state): State<AppState>) -> impl IntoResponse {
|
|||
)
|
||||
}
|
||||
|
||||
fn push(
|
||||
async fn push(
|
||||
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 push_opts = PushOptions::new();
|
||||
push_opts.remote_callbacks(callback);
|
||||
let refspec = format!("refs/heads/{0}:refs/heads/{0}", branch);
|
||||
rem.push(&[&refspec], Some(&mut push_opts))?;
|
||||
// 1. Acquire lock (awaiting if another task is committing/pulling)
|
||||
let repo_guard = repo.lock().await;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
// 2. Find the remote
|
||||
let mut remote = repo_guard.find_remote(remote_name)?;
|
||||
|
||||
Err("cannot lock repo".into())
|
||||
// 3. Setup Credentials
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.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 push_opts = git2::PushOptions::new();
|
||||
push_opts.remote_callbacks(callbacks);
|
||||
|
||||
// 4. Define the Refspec
|
||||
// format: local_ref:remote_ref
|
||||
let refspec = format!("refs/heads/{}:refs/heads/{}", branch, branch);
|
||||
|
||||
// 5. Execute Push
|
||||
info!("Pushing {branch} to {remote_name}...");
|
||||
remote.push(&[&refspec], Some(&mut push_opts))?;
|
||||
|
||||
info!("Push successful.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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) {
|
||||
if let Err(e) = pull(config, repo, "origin", branch).await {
|
||||
return (
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": e.to_string()})),
|
||||
|
|
@ -861,50 +851,51 @@ async fn pull_handler(State(state): State<AppState>) -> impl IntoResponse {
|
|||
}
|
||||
|
||||
// Pull is fetch + merge
|
||||
fn pull(
|
||||
async 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);
|
||||
let repo_guard = repo.lock().await;
|
||||
|
||||
info!("fetching from {}...", remote_name);
|
||||
rem.fetch(&[branch], Some(&mut fetch_options), None)?;
|
||||
// 1. Fetch from remote
|
||||
let mut remote = repo_guard.find_remote(remote_name)?;
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.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()),
|
||||
)
|
||||
});
|
||||
|
||||
// resolve fetch vs merge
|
||||
let fetch_head = rlock.find_reference("FETCH_HEAD")?;
|
||||
let fetch_commit = rlock.reference_to_annotated_commit(&fetch_head)?;
|
||||
let mut fetch_options = FetchOptions::new();
|
||||
fetch_options.remote_callbacks(callbacks);
|
||||
fetch_options.download_tags(git2::AutotagOption::All);
|
||||
|
||||
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_up_to_date() {
|
||||
info!("already up to date");
|
||||
} else {
|
||||
return Err("cannot fast-forward, local changes would be overwritten".into());
|
||||
}
|
||||
info!("Fetching {branch} from {remote_name}...");
|
||||
remote.fetch(&[branch], Some(&mut fetch_options), None)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
// 2. Get FETCH_HEAD (the tip of what we just fetched)
|
||||
let fetch_head = repo_guard.find_reference("FETCH_HEAD")?;
|
||||
let fetch_commit = repo_guard.reference_to_annotated_commit(&fetch_head)?;
|
||||
let target_object = repo_guard.find_object(fetch_commit.id(), Some(ObjectType::Commit))?;
|
||||
|
||||
Err("cannot lock repo".into())
|
||||
// 3. Force Hard Reset
|
||||
// This moves the branch pointer AND updates the working directory files,
|
||||
// effectively wiping away your 50-commit reset or any local uncommitted changes.
|
||||
let mut checkout_opts = CheckoutBuilder::new();
|
||||
checkout_opts.force();
|
||||
|
||||
repo_guard.reset(&target_object, ResetType::Hard, Some(&mut checkout_opts))?;
|
||||
|
||||
// 4. Ensure local branch reference is updated to this OID
|
||||
let refname = format!("refs/heads/{}", branch);
|
||||
repo_guard.reference(&refname, fetch_commit.id(), true, "Force Sync Pull")?;
|
||||
repo_guard.set_head(&refname)?;
|
||||
|
||||
info!("Repository synced to remote tip: {}", fetch_commit.id());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return git state & server state
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@ mod tx;
|
|||
// mod reg;
|
||||
|
||||
fn setup_log(config: gcm::Configure) -> gcm::StandardResult {
|
||||
let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?;
|
||||
// NOTE: disable logging file, use send to log service instead
|
||||
// let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?;
|
||||
|
||||
Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
|
||||
.target(env_logger::Target::Pipe(Box::new(logfile)))
|
||||
// .target(env_logger::Target::Pipe(Box::new(logfile)))
|
||||
.init();
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue