create pull
This commit is contained in:
parent
09ae29fe9b
commit
a2437d1297
@ -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)?;
|
||||
|
@ -19,13 +19,16 @@ impl StorageEngine for LocalStorageEngine {
|
||||
async fn allocate_dir(&self) -> eyre::Result<super::TemporaryDir> {
|
||||
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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user