diff options
| author | Max Audron <audron@cocaine.farm> | 2024-03-27 13:40:42 +0100 |
|---|---|---|
| committer | Max Audron <audron@cocaine.farm> | 2024-03-27 13:40:42 +0100 |
| commit | f7aca5859b3a40ec00c8789383e8ab84d7821ae6 (patch) | |
| tree | 9a242aef7f0b0547949f4fefcff97eeaef1af356 /src | |
| parent | implement basic cloning and updating with gix (diff) | |
reorganize repo git impls
Diffstat (limited to 'src')
| -rw-r--r-- | src/repo/git/checkout.rs | 68 | ||||
| -rw-r--r-- | src/repo/git/fetch.rs | 51 | ||||
| -rw-r--r-- | src/repo/git/mod.rs | 113 | ||||
| -rw-r--r-- | src/repo/mod.rs | 212 |
4 files changed, 235 insertions, 209 deletions
diff --git a/src/repo/git/checkout.rs b/src/repo/git/checkout.rs new file mode 100644 index 0000000..418a0af --- /dev/null +++ b/src/repo/git/checkout.rs @@ -0,0 +1,68 @@ +use super::{Repo, RepoError}; + +use anyhow::Context; +use gix::{ + clone::checkout::main_worktree::ProgressId, interrupt::IS_INTERRUPTED, progress, remote, Id, + Progress, +}; + +use gix_index::{File, State}; +use tracing::debug; + +impl Repo { + #[tracing::instrument(level = "trace", skip(progress))] + pub fn checkout( + &self, + remote: remote::Name, + head: Id, + progress: &mut dyn progress::DynNestedProgress, + ) -> Result<(), RepoError> { + let repo = self.repo()?; + + let workdir = repo.work_dir().ok_or(RepoError::NoWorktree)?; + let head_tree = head + .object() + .context("could not find object HEAD points to")? + .peel_to_tree() + .context("failed to peel HEAD object")? + .id(); + + let index = State::from_tree(&head_tree, &repo.objects).context("index from tree")?; + let mut index = File::from_state(index, repo.index_path()); + + let mut files = + progress.add_child_with_id("checkout".to_string(), ProgressId::CheckoutFiles.into()); + let mut bytes = + progress.add_child_with_id("writing".to_string(), ProgressId::BytesWritten.into()); + + files.init(Some(index.entries().len()), progress::count("files")); + bytes.init(None, progress::bytes()); + + let start = std::time::Instant::now(); + + debug!("workdir: {:?}", workdir); + let opts = gix_worktree_state::checkout::Options::default(); + let outcome = gix_worktree_state::checkout( + &mut index, + workdir, + repo.objects.clone().into_arc().unwrap(), + &files, + &bytes, + &IS_INTERRUPTED, + opts, + ) + .context("checkout: failed"); + + files.show_throughput(start); + bytes.show_throughput(start); + + debug!("outcome: {:?}", outcome); + debug!("is interrupted: {:?}", &gix::interrupt::IS_INTERRUPTED); + + index + .write(Default::default()) + .context("checkout: write index")?; + + Ok(()) + } +} diff --git a/src/repo/git/fetch.rs b/src/repo/git/fetch.rs new file mode 100644 index 0000000..5f51523 --- /dev/null +++ b/src/repo/git/fetch.rs @@ -0,0 +1,51 @@ +use super::{Repo, RepoError}; + +use anyhow::Context; +use gix::{ + interrupt::IS_INTERRUPTED, + progress::Discard, + remote::{fetch::Status, Direction}, +}; + +impl Repo { + #[tracing::instrument(level = "trace")] + pub fn clone(&self, url: &str) -> Result<(), RepoError> { + std::fs::create_dir_all(&self.path).unwrap(); + + let mut prepare_clone = gix::prepare_clone(url, &self.path).unwrap(); + + let (mut prepare_checkout, _) = prepare_clone + .fetch_then_checkout(Discard, &IS_INTERRUPTED) + .unwrap(); + + let (_repo, _) = prepare_checkout + .main_worktree(Discard, &IS_INTERRUPTED) + .unwrap(); + + Ok(()) + } + + #[tracing::instrument(level = "trace")] + pub fn fetch<'a>(&mut self) -> Result<bool, RepoError> { + let remote = self.default_remote()?; + let conn = remote.connect(Direction::Fetch).unwrap(); + let outcome = conn + .prepare_fetch(Discard, gix::remote::ref_map::Options::default()) + .context("fetch: failed to prepare patch")? + .receive(Discard, &IS_INTERRUPTED) + .context("fetch: failed to receive")?; + + match outcome.status { + Status::NoPackReceived { + dry_run: _, + negotiate: _, + update_refs: _, + } => Ok(false), + Status::Change { + negotiate: _, + write_pack_bundle: _, + update_refs: _, + } => Ok(true), + } + } +} diff --git a/src/repo/git/mod.rs b/src/repo/git/mod.rs new file mode 100644 index 0000000..7370ee8 --- /dev/null +++ b/src/repo/git/mod.rs @@ -0,0 +1,113 @@ +use super::{LocalRepoState, Repo, RepoError}; + +use anyhow::Context; +use gix::{ + bstr::BString, + refs::{ + transaction::{LogChange, PreviousValue, RefEdit}, + FullName, + }, + remote, Id, ObjectId, Remote, +}; + +mod checkout; +mod fetch; + +impl Repo { + #[tracing::instrument(level = "debug")] + pub fn is_clean(&self) -> Result<LocalRepoState, RepoError> { + let repo = self.repo()?; + + if let Some(state) = repo.state() { + Ok(LocalRepoState::InProgress(state)) + } else { + let head = repo.head().unwrap(); + + if head.is_detached() { + return Ok(LocalRepoState::DetachedHead); + } + + if head.is_unborn() { + return Ok(LocalRepoState::UnbornHead); + } + + Ok(LocalRepoState::Clean) + } + } + + pub fn default_remote(&self) -> Result<Remote, RepoError> { + Ok(self + .repo()? + .find_default_remote(gix::remote::Direction::Fetch) + .ok_or(RepoError::NoRemoteFound)? + .context("fetch: failed to find default remote")?) + } + + pub fn default_branch(&self) -> Result<BString, RepoError> { + let repo = self.repo()?; + let remote = self.default_remote()?; + let remote_name = remote.name().context("remote does not have name")?; + + let origin_ref = repo + .find_reference(&format!("remotes/{}/HEAD", remote_name.as_bstr())) + .context("the remotes HEAD references does not exist")?; + + if let Some(origin_ref) = origin_ref.target().try_name() { + Ok(origin_ref.shorten().to_owned()) + } else { + Err(RepoError::NoDefaultBranch) + } + } + + pub fn refedit(target: ObjectId, name: &str, message: &str) -> RefEdit { + RefEdit { + change: gix::refs::transaction::Change::Update { + log: LogChange { + mode: gix::refs::transaction::RefLog::AndReference, + force_create_reflog: false, + message: message.into(), + }, + expected: PreviousValue::Any, + new: gix::refs::Target::Peeled(target), + }, + name: FullName::try_from(name).unwrap(), + deref: true, + } + } + + pub fn update_default_branch_ref( + &self, + remote: remote::Name, + head: Id, + ) -> Result<(), RepoError> { + let default_branch = self.default_branch()?; + let repo = self.repo()?; + + repo.edit_reference(Repo::refedit( + head.into(), + &format!("heads/{}", default_branch), + &format!("checkout: {}/HEAD with gtree", remote.as_bstr()), + )) + .context("checkout: failed to edit ref")?; + + Ok(()) + } + + pub fn default_remote_head(&self) -> Result<(remote::Name, Id), RepoError> { + let repo = self.repo()?; + + let remote = repo + .find_fetch_remote(None) + .context("could not find remote to fetch")?; + let remote = remote.name().context("remote does not have name")?; + + let head_ref = repo + .find_reference(&format!("remotes/{}/HEAD", remote.as_bstr())) + .context("the remotes HEAD references does not exist")?; + let head = head_ref + .into_fully_peeled_id() + .context("failed to peel ref")?; + + Ok((remote.to_owned(), head.to_owned())) + } +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 4eb9043..60fb34d 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,22 +1,14 @@ use std::{collections::HashMap, fmt::Debug, path::PathBuf, sync::RwLock}; -use anyhow::Context; use thiserror::Error; -use gix::{ - bstr::BString, - clone::checkout::main_worktree::ProgressId, - refs::{ - transaction::{LogChange, PreviousValue, RefEdit}, - FullName, - }, - remote, Id, ObjectId, Progress, Remote, Repository, -}; -use tracing::{debug, error}; +use gix::Repository; +use tracing::error; use crate::forge::Project; mod aggregate; +mod git; mod repostate; pub use aggregate::*; @@ -47,204 +39,6 @@ impl Repo { None => Err(RepoError::NoLocalRepo), } } - - #[tracing::instrument(level = "debug")] - pub fn is_clean(&self) -> Result<LocalRepoState, RepoError> { - let repo = self.repo()?; - - if let Some(state) = repo.state() { - Ok(LocalRepoState::InProgress(state)) - } else { - let head = repo.head().unwrap(); - - if head.is_detached() { - return Ok(LocalRepoState::DetachedHead); - } - - if head.is_unborn() { - return Ok(LocalRepoState::UnbornHead); - } - - Ok(LocalRepoState::Clean) - } - } - - pub fn default_remote(&self) -> Result<Remote, RepoError> { - Ok(self - .repo()? - .find_default_remote(gix::remote::Direction::Fetch) - .ok_or(RepoError::NoRemoteFound)? - .context("fetch: failed to find default remote")?) - } - - pub fn default_branch(&self) -> Result<BString, RepoError> { - let repo = self.repo()?; - let remote = self.default_remote()?; - let remote_name = remote.name().context("remote does not have name")?; - - let origin_ref = repo - .find_reference(&format!("remotes/{}/HEAD", remote_name.as_bstr())) - .context("the remotes HEAD references does not exist")?; - - if let Some(origin_ref) = origin_ref.target().try_name() { - Ok(origin_ref.shorten().to_owned()) - } else { - Err(RepoError::NoDefaultBranch) - } - } - - #[tracing::instrument(level = "trace")] - pub fn clone(&self, url: &str) -> Result<(), RepoError> { - std::fs::create_dir_all(&self.path).unwrap(); - - let mut prepare_clone = gix::prepare_clone(url, &self.path).unwrap(); - - let (mut prepare_checkout, _) = prepare_clone - .fetch_then_checkout(gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) - .unwrap(); - - let (_repo, _) = prepare_checkout - .main_worktree(gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) - .unwrap(); - - Ok(()) - } - - #[tracing::instrument(level = "trace")] - pub fn fetch<'a>(&mut self) -> Result<bool, RepoError> { - let remote = self.default_remote()?; - let conn = remote.connect(gix::remote::Direction::Fetch).unwrap(); - let outcome = conn - .prepare_fetch( - gix::progress::Discard, - gix::remote::ref_map::Options::default(), - ) - .context("fetch: failed to prepare patch")? - .receive(gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) - .context("fetch: failed to receive")?; - - match outcome.status { - gix::remote::fetch::Status::NoPackReceived { - dry_run: _, - negotiate: _, - update_refs: _, - } => Ok(false), - gix::remote::fetch::Status::Change { - negotiate: _, - write_pack_bundle: _, - update_refs: _, - } => Ok(true), - } - } - - pub fn refedit(target: ObjectId, name: &str, message: &str) -> RefEdit { - RefEdit { - change: gix::refs::transaction::Change::Update { - log: LogChange { - mode: gix::refs::transaction::RefLog::AndReference, - force_create_reflog: false, - message: message.into(), - }, - expected: PreviousValue::Any, - new: gix::refs::Target::Peeled(target), - }, - name: FullName::try_from(name).unwrap(), - deref: true, - } - } - - pub fn update_default_branch_ref( - &self, - remote: remote::Name, - head: Id, - ) -> Result<(), RepoError> { - let default_branch = self.default_branch()?; - let repo = self.repo()?; - - repo.edit_reference(Repo::refedit( - head.into(), - &format!("heads/{}", default_branch), - &format!("checkout: {}/HEAD with gtree", remote.as_bstr()), - )) - .context("checkout: failed to edit ref")?; - - Ok(()) - } - - pub fn default_remote_head(&self) -> Result<(remote::Name, Id), RepoError> { - let repo = self.repo()?; - - let remote = repo - .find_fetch_remote(None) - .context("could not find remote to fetch")?; - let remote = remote.name().context("remote does not have name")?; - - let head_ref = repo - .find_reference(&format!("remotes/{}/HEAD", remote.as_bstr())) - .context("the remotes HEAD references does not exist")?; - let head = head_ref - .into_fully_peeled_id() - .context("failed to peel ref")?; - - Ok((remote.to_owned(), head.to_owned())) - } - - #[tracing::instrument(level = "trace", skip(progress))] - pub fn checkout( - &self, - remote: remote::Name, - head: Id, - progress: &mut dyn gix::progress::DynNestedProgress, - ) -> Result<(), RepoError> { - let repo = self.repo()?; - - let workdir = repo.work_dir().ok_or(RepoError::NoWorktree)?; - let head_tree = head - .object() - .context("could not find object HEAD points to")? - .peel_to_tree() - .context("failed to peel HEAD object")? - .id(); - - let index = - gix_index::State::from_tree(&head_tree, &repo.objects).context("index from tree")?; - let mut index = gix_index::File::from_state(index, repo.index_path()); - - let mut files = - progress.add_child_with_id("checkout".to_string(), ProgressId::CheckoutFiles.into()); - let mut bytes = - progress.add_child_with_id("writing".to_string(), ProgressId::BytesWritten.into()); - - files.init(Some(index.entries().len()), gix::progress::count("files")); - bytes.init(None, gix::progress::bytes()); - - let start = std::time::Instant::now(); - - debug!("workdir: {:?}", workdir); - let opts = gix_worktree_state::checkout::Options::default(); - let outcome = gix_worktree_state::checkout( - &mut index, - workdir, - repo.objects.clone().into_arc().unwrap(), - &files, - &bytes, - &gix::interrupt::IS_INTERRUPTED, - opts, - ) - .context("checkout: failed"); - - files.show_throughput(start); - bytes.show_throughput(start); - - debug!("outcome: {:?}", outcome); - debug!("is interrupted: {:?}", &gix::interrupt::IS_INTERRUPTED); - - index - .write(Default::default()) - .context("checkout: write index")?; - - Ok(()) - } } #[derive(Error, Debug)] |
