aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMax Audron <audron@cocaine.farm>2024-03-27 13:40:42 +0100
committerMax Audron <audron@cocaine.farm>2024-03-27 13:40:42 +0100
commitf7aca5859b3a40ec00c8789383e8ab84d7821ae6 (patch)
tree9a242aef7f0b0547949f4fefcff97eeaef1af356 /src
parentimplement basic cloning and updating with gix (diff)
reorganize repo git impls
Diffstat (limited to 'src')
-rw-r--r--src/repo/git/checkout.rs68
-rw-r--r--src/repo/git/fetch.rs51
-rw-r--r--src/repo/git/mod.rs113
-rw-r--r--src/repo/mod.rs212
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)]