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/repo/git | |
| parent | implement basic cloning and updating with gix (diff) | |
reorganize repo git impls
Diffstat (limited to 'src/repo/git')
| -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 |
3 files changed, 232 insertions, 0 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())) + } +} |
