create pull
This commit is contained in:
parent
09ae29fe9b
commit
a2437d1297
@ -1,7 +1,7 @@
|
|||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use eyre::ContextCompat;
|
use eyre::ContextCompat;
|
||||||
use git2::{Cred, PushOptions, RemoteCallbacks, Repository};
|
use git2::{AutotagOption, Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::storage::DynStorageEngine;
|
use crate::storage::DynStorageEngine;
|
||||||
@ -25,6 +25,112 @@ impl LocalGitProvider {
|
|||||||
options,
|
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]
|
#[async_trait::async_trait]
|
||||||
@ -84,17 +190,14 @@ impl GitProvider for LocalGitProvider {
|
|||||||
branch_name: &String,
|
branch_name: &String,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
let repo = repo.lock().await;
|
let repo = repo.lock().await;
|
||||||
|
let branch_name = branch_name.to_lowercase().replace(" ", "-");
|
||||||
|
|
||||||
let head_commit_oid = repo
|
let head_commit_oid = repo
|
||||||
.head()?
|
.head()?
|
||||||
.target()
|
.target()
|
||||||
.ok_or(eyre::anyhow!("could not get access to target commit"))?;
|
.ok_or(eyre::anyhow!("could not get access to target commit"))?;
|
||||||
let head_commit = repo.find_commit(head_commit_oid)?;
|
let head_commit = repo.find_commit(head_commit_oid)?;
|
||||||
let newbranch = repo.branch(
|
let newbranch = repo.branch(&branch_name, &head_commit, true)?;
|
||||||
&branch_name.to_lowercase().replace(" ", "-"),
|
|
||||||
&head_commit,
|
|
||||||
true,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
repo.set_head(
|
repo.set_head(
|
||||||
newbranch
|
newbranch
|
||||||
@ -103,6 +206,43 @@ impl GitProvider for LocalGitProvider {
|
|||||||
.ok_or(eyre::anyhow!("could not get name of reference"))?,
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,15 +257,6 @@ impl GitProvider for LocalGitProvider {
|
|||||||
tracing::trace!("pulling signature from local git");
|
tracing::trace!("pulling signature from local git");
|
||||||
let signature = repo.signature()?;
|
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");
|
tracing::trace!("fetching index and adding changed files to working tree");
|
||||||
let mut index = repo.index()?;
|
let mut index = repo.index()?;
|
||||||
index.add_all(&["."], git2::IndexAddOption::DEFAULT, None)?;
|
index.add_all(&["."], git2::IndexAddOption::DEFAULT, None)?;
|
||||||
|
@ -19,13 +19,16 @@ impl StorageEngine for LocalStorageEngine {
|
|||||||
async fn allocate_dir(&self) -> eyre::Result<super::TemporaryDir> {
|
async fn allocate_dir(&self) -> eyre::Result<super::TemporaryDir> {
|
||||||
let subdir_name = Standard.sample_string(&mut rand::thread_rng(), 2);
|
let subdir_name = Standard.sample_string(&mut rand::thread_rng(), 2);
|
||||||
let mut path = self.root.clone();
|
let mut path = self.root.clone();
|
||||||
|
path.push("tmp");
|
||||||
path.push(hex::encode(subdir_name));
|
path.push(hex::encode(subdir_name));
|
||||||
|
|
||||||
Ok(super::TemporaryDir::new(path))
|
Ok(super::TemporaryDir::new(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cleanup(&self) -> eyre::Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user