asyncgit 0.22.1

allow using git2 in a asynchronous context
Documentation
use std::path::{Path, PathBuf};

use git2::{
	Repository, RepositoryOpenFlags, Submodule,
	SubmoduleUpdateOptions,
};
use scopetime::scope_time;

use super::{repo, CommitId, RepoPath};
use crate::{error::Result, sync::utils::work_dir, Error};

pub use git2::SubmoduleStatus;

///
#[derive(Debug)]
pub struct SubmoduleInfo {
	///
	pub name: String,
	///
	pub path: PathBuf,
	///
	pub url: Option<String>,
	///
	pub id: Option<CommitId>,
	///
	pub head_id: Option<CommitId>,
	///
	pub status: SubmoduleStatus,
}

///
#[derive(Debug)]
pub struct SubmoduleParentInfo {
	/// where to find parent repo
	pub parent_gitpath: PathBuf,
	/// where to find submodule git path
	pub submodule_gitpath: PathBuf,
	/// `submodule_info` from perspective of parent repo
	pub submodule_info: SubmoduleInfo,
}

impl SubmoduleInfo {
	///
	pub fn get_repo_path(
		&self,
		repo_path: &RepoPath,
	) -> Result<RepoPath> {
		let repo = repo(repo_path)?;
		let wd = repo.workdir().ok_or(Error::NoWorkDir)?;

		Ok(RepoPath::Path(wd.join(self.path.clone())))
	}
}

fn submodule_to_info(s: &Submodule, r: &Repository) -> SubmoduleInfo {
	let status = r
		.submodule_status(
			s.name().unwrap_or_default(),
			git2::SubmoduleIgnore::None,
		)
		.unwrap_or(SubmoduleStatus::empty());

	SubmoduleInfo {
		name: s.name().unwrap_or_default().into(),
		path: s.path().to_path_buf(),
		id: s.workdir_id().map(CommitId::from),
		head_id: s.head_id().map(CommitId::from),
		url: s.url().map(String::from),
		status,
	}
}

///
pub fn get_submodules(
	repo_path: &RepoPath,
) -> Result<Vec<SubmoduleInfo>> {
	scope_time!("get_submodules");

	let (r, repo2) = (repo(repo_path)?, repo(repo_path)?);

	let res = r
		.submodules()?
		.iter()
		.map(|s| submodule_to_info(s, &repo2))
		.collect();

	Ok(res)
}

///
pub fn update_submodule(
	repo_path: &RepoPath,
	name: &str,
) -> Result<()> {
	scope_time!("update_submodule");

	let repo = repo(repo_path)?;

	let mut submodule = repo.find_submodule(name)?;

	let mut options = SubmoduleUpdateOptions::new();
	options.allow_fetch(true);

	submodule.update(true, Some(&mut options))?;

	Ok(())
}

/// query whether `repo_path` points to a repo that is part of a parent git which contains it as a submodule
pub fn submodule_parent_info(
	repo_path: &RepoPath,
) -> Result<Option<SubmoduleParentInfo>> {
	scope_time!("submodule_parent_info");

	let repo = repo(repo_path)?;
	let repo_wd = work_dir(&repo)?.to_path_buf();

	log::trace!("[sub] repo_wd: {:?}", repo_wd);
	log::trace!("[sub] repo_path: {:?}", repo.path());

	if let Some(parent_path) = repo_wd.parent() {
		log::trace!("[sub] parent_path: {:?}", parent_path);

		if let Ok(parent) = Repository::open_ext(
			parent_path,
			RepositoryOpenFlags::empty(),
			Vec::<&Path>::new(),
		) {
			let parent_wd = work_dir(&parent)?.to_path_buf();
			log::trace!("[sub] parent_wd: {:?}", parent_wd);

			let submodule_name = repo_wd
				.strip_prefix(parent_wd)?
				.to_string_lossy()
				.to_string();

			log::trace!("[sub] submodule_name: {:?}", submodule_name);

			if let Ok(submodule) =
				parent.find_submodule(&submodule_name)
			{
				return Ok(Some(SubmoduleParentInfo {
					parent_gitpath: parent.path().to_path_buf(),
					submodule_gitpath: repo.path().to_path_buf(),
					submodule_info: submodule_to_info(
						&submodule, &parent,
					),
				}));
			}
		}
	}

	Ok(None)
}

#[cfg(test)]
mod tests {
	use super::get_submodules;
	use crate::sync::{
		submodules::submodule_parent_info, tests::repo_init, RepoPath,
	};
	use git2::Repository;
	use pretty_assertions::assert_eq;
	use std::path::Path;

	#[test]
	fn test_smoke() {
		let (dir, _r) = repo_init().unwrap();

		{
			let r = Repository::open(dir.path()).unwrap();
			let mut s = r
				.submodule(
					//TODO: use local git
					"https://github.com/extrawurst/brewdump.git",
					Path::new("foo/bar"),
					false,
				)
				.unwrap();

			let _sub_r = s.clone(None).unwrap();
			s.add_finalize().unwrap();
		}

		let repo_p = RepoPath::Path(dir.into_path());
		let subs = get_submodules(&repo_p).unwrap();

		assert_eq!(subs.len(), 1);
		assert_eq!(&subs[0].name, "foo/bar");

		let info = submodule_parent_info(
			&subs[0].get_repo_path(&repo_p).unwrap(),
		)
		.unwrap()
		.unwrap();

		dbg!(&info);

		assert_eq!(&info.submodule_info.name, "foo/bar");
	}
}