From a2437d129717dec2a74cf087ea671823c265e95f Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sun, 27 Nov 2022 11:55:57 +0100 Subject: [PATCH] create pull --- crates/octopush_core/src/git/git.rs | 161 ++++++++++++++++++++-- crates/octopush_core/src/storage/local.rs | 5 +- 2 files changed, 150 insertions(+), 16 deletions(-) diff --git a/crates/octopush_core/src/git/git.rs b/crates/octopush_core/src/git/git.rs index cd2785b..44334f0 100644 --- a/crates/octopush_core/src/git/git.rs +++ b/crates/octopush_core/src/git/git.rs @@ -1,7 +1,7 @@ use std::{path::PathBuf, sync::Arc}; use eyre::ContextCompat; -use git2::{Cred, PushOptions, RemoteCallbacks, Repository}; +use git2::{AutotagOption, Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository}; use tokio::sync::Mutex; use crate::storage::DynStorageEngine; @@ -25,6 +25,112 @@ impl LocalGitProvider { options, } } + + fn fast_forward( + repo: &Repository, + lb: &mut git2::Reference, + rc: &git2::AnnotatedCommit, + ) -> Result<(), git2::Error> { + let name = match lb.name() { + Some(s) => s.to_string(), + None => String::from_utf8_lossy(lb.name_bytes()).to_string(), + }; + let msg = format!("Fast-Forward: Setting {} to id: {}", name, rc.id()); + println!("{}", msg); + lb.set_target(rc.id(), &msg)?; + repo.set_head(&name)?; + repo.checkout_head(Some( + git2::build::CheckoutBuilder::default() + // For some reason the force is required to make the working directory actually get updated + // I suspect we should be adding some logic to handle dirty working directory states + // but this is just an example so maybe not. + .force(), + ))?; + Ok(()) + } + + fn normal_merge( + repo: &Repository, + local: &git2::AnnotatedCommit, + remote: &git2::AnnotatedCommit, + ) -> Result<(), git2::Error> { + let local_tree = repo.find_commit(local.id())?.tree()?; + let remote_tree = repo.find_commit(remote.id())?.tree()?; + let ancestor = repo + .find_commit(repo.merge_base(local.id(), remote.id())?)? + .tree()?; + let mut idx = repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?; + + if idx.has_conflicts() { + println!("Merge conficts detected..."); + repo.checkout_index(Some(&mut idx), None)?; + return Ok(()); + } + let result_tree = repo.find_tree(idx.write_tree_to(repo)?)?; + // now create the merge commit + let msg = format!("Merge: {} into {}", remote.id(), local.id()); + let sig = repo.signature()?; + let local_commit = repo.find_commit(local.id())?; + let remote_commit = repo.find_commit(remote.id())?; + // Do our merge commit and set current branch head to that commit. + let _merge_commit = repo.commit( + Some("HEAD"), + &sig, + &sig, + &msg, + &result_tree, + &[&local_commit, &remote_commit], + )?; + // Set working tree to match head. + repo.checkout_head(None)?; + Ok(()) + } + + fn do_merge<'a>( + repo: &'a Repository, + remote_branch: &str, + fetch_commit: git2::AnnotatedCommit<'a>, + ) -> Result<(), git2::Error> { + // 1. do a merge analysis + let analysis = repo.merge_analysis(&[&fetch_commit])?; + + // 2. Do the appopriate merge + if analysis.0.is_fast_forward() { + println!("Doing a fast forward"); + // do a fast forward + let refname = format!("refs/heads/{}", remote_branch); + match repo.find_reference(&refname) { + Ok(mut r) => { + Self::fast_forward(repo, &mut r, &fetch_commit)?; + } + Err(_) => { + // The branch doesn't exist so just set the reference to the + // commit directly. Usually this is because you are pulling + // into an empty repository. + repo.reference( + &refname, + fetch_commit.id(), + true, + &format!("Setting {} to {}", remote_branch, fetch_commit.id()), + )?; + repo.set_head(&refname)?; + repo.checkout_head(Some( + git2::build::CheckoutBuilder::default() + .allow_conflicts(true) + .conflict_style_merge(true) + .force(), + ))?; + } + }; + } else if analysis.0.is_normal() { + // do a normal merge + let head_commit = repo.reference_to_annotated_commit(&repo.head()?)?; + Self::normal_merge(&repo, &head_commit, &fetch_commit)?; + } else { + println!("Nothing to do..."); + } + Ok(()) + } } #[async_trait::async_trait] @@ -84,17 +190,14 @@ impl GitProvider for LocalGitProvider { branch_name: &String, ) -> eyre::Result<()> { let repo = repo.lock().await; + let branch_name = branch_name.to_lowercase().replace(" ", "-"); let head_commit_oid = repo .head()? .target() .ok_or(eyre::anyhow!("could not get access to target commit"))?; let head_commit = repo.find_commit(head_commit_oid)?; - let newbranch = repo.branch( - &branch_name.to_lowercase().replace(" ", "-"), - &head_commit, - true, - )?; + let newbranch = repo.branch(&branch_name, &head_commit, true)?; repo.set_head( newbranch @@ -103,6 +206,43 @@ impl GitProvider for LocalGitProvider { .ok_or(eyre::anyhow!("could not get name of reference"))?, )?; + tracing::trace!("pulling from origin"); + let options = self.options.clone(); + let remote = "origin"; + let mut cb = RemoteCallbacks::new(); + cb.credentials(|url, username_from_url, _allowed_types| { + tracing::debug!(username_from_url, url, "pulling key from ssh-agent"); + + if let Some(auth) = &options.http_auth { + tracing::trace!(auth, "authenticating"); + let (user, pass) = auth + .split_once(":") + .ok_or("http_auth is not formatted correctly") + .unwrap(); + + Cred::userpass_plaintext(user, pass) + } else { + let username = username_from_url.unwrap(); + Cred::ssh_key_from_agent(username) + } + }); + let mut remote = repo + .find_remote(remote) + .or_else(|_| repo.remote_anonymous(remote))?; + + let mut fo = FetchOptions::new(); + fo.remote_callbacks(cb); + let head = repo.head()?; + let refspec = &[head + .name() + .ok_or(eyre::anyhow!("could not find head.name"))?]; + + remote.fetch(refspec, Some(&mut fo), None)?; + + let fetch_head = repo.find_reference("FETCH_HEAD")?; + let commit = repo.reference_to_annotated_commit(&fetch_head)?; + Self::do_merge(&repo, &branch_name, commit)?; + Ok(()) } @@ -117,15 +257,6 @@ impl GitProvider for LocalGitProvider { tracing::trace!("pulling signature from local git"); let signature = repo.signature()?; - for rev in repo.revwalk()? { - let rev = rev?; - let commit = repo.find_commit(rev)?; - let summary = commit - .summary() - .ok_or(eyre::anyhow!("could not find commit"))?; - tracing::info!(summary, "summary") - } - tracing::trace!("fetching index and adding changed files to working tree"); let mut index = repo.index()?; index.add_all(&["."], git2::IndexAddOption::DEFAULT, None)?; diff --git a/crates/octopush_core/src/storage/local.rs b/crates/octopush_core/src/storage/local.rs index 411ff89..acdc4fd 100644 --- a/crates/octopush_core/src/storage/local.rs +++ b/crates/octopush_core/src/storage/local.rs @@ -19,13 +19,16 @@ impl StorageEngine for LocalStorageEngine { async fn allocate_dir(&self) -> eyre::Result { let subdir_name = Standard.sample_string(&mut rand::thread_rng(), 2); let mut path = self.root.clone(); + path.push("tmp"); path.push(hex::encode(subdir_name)); Ok(super::TemporaryDir::new(path)) } async fn cleanup(&self) -> eyre::Result<()> { - tokio::fs::remove_dir_all(self.root.clone()).await?; + let mut path = self.root.clone(); + path.push("tmp"); + tokio::fs::remove_dir_all(path).await?; Ok(()) }