From 11730aa7af33a16b8ebbca433fe925dec7f05b33 Mon Sep 17 00:00:00 2001 From: Nikita Galaiko Date: Tue, 18 Jul 2023 15:49:13 +0200 Subject: [PATCH] create git branch name module --- butler/Cargo.lock | 6 +- src-tauri/src/app.rs | 6 +- src-tauri/src/main.rs | 6 +- src-tauri/src/project_repository/branch.rs | 113 ++++++++++++++++++ src-tauri/src/project_repository/mod.rs | 1 + .../src/project_repository/repository.rs | 40 +++---- 6 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 src-tauri/src/project_repository/branch.rs diff --git a/butler/Cargo.lock b/butler/Cargo.lock index 0faebaaab..a3ecdd029 100644 --- a/butler/Cargo.lock +++ b/butler/Cargo.lock @@ -1655,13 +1655,8 @@ name = "git-butler-tauri" version = "0.0.0" dependencies = [ "anyhow", - "async-trait", "bytes", - "clap", - "colored 2.0.4", - "dialoguer", "diffy", - "dirs 5.0.1", "filetime", "fslock", "futures", @@ -1696,6 +1691,7 @@ dependencies = [ "tokio", "tokio-tungstenite 0.18.0", "tokio-util", + "url", "urlencoding", "uuid", "walkdir", diff --git a/src-tauri/src/app.rs b/src-tauri/src/app.rs index ed6644fd5..220b7b50b 100644 --- a/src-tauri/src/app.rs +++ b/src-tauri/src/app.rs @@ -6,7 +6,7 @@ use tokio_util::sync::CancellationToken; use crate::{ bookmarks, database, deltas, events, files, gb_repository, - project_repository::{self, activity, conflicts, diff}, + project_repository::{self, activity, conflicts, diff, branch}, projects, pty, search, sessions, storage, users, virtual_branches, watcher, }; @@ -577,14 +577,14 @@ impl App { project_repository.git_match_paths(pattern) } - pub fn git_branches(&self, project_id: &str) -> Result> { + pub fn git_branches(&self, project_id: &str) -> Result> { let project = self.gb_project(project_id)?; let project_repository = project_repository::Repository::open(&project) .context("failed to open project repository")?; project_repository.git_branches() } - pub fn git_remote_branches(&self, project_id: &str) -> Result> { + pub fn git_remote_branches(&self, project_id: &str) -> Result> { let project = self.gb_project(project_id)?; let project_repository = project_repository::Repository::open(&project) .context("failed to open project repository")?; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 71944b10e..72b4a428c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -38,7 +38,7 @@ use tauri_plugin_log::{ use thiserror::Error; use timed::timed; -use crate::project_repository::activity; +use crate::project_repository::{activity, branch}; #[derive(Debug, Error)] pub enum Error { @@ -386,7 +386,7 @@ async fn git_match_paths( #[timed(duration(printer = "debug!"))] #[tauri::command(async)] -async fn git_branches(handle: tauri::AppHandle, project_id: &str) -> Result, Error> { +async fn git_branches(handle: tauri::AppHandle, project_id: &str) -> Result, Error> { let app = handle.state::(); let branches = app .git_branches(project_id) @@ -399,7 +399,7 @@ async fn git_branches(handle: tauri::AppHandle, project_id: &str) -> Result Result, Error> { +) -> Result, Error> { let app = handle.state::(); let branches = app.git_remote_branches(project_id).with_context(|| { format!( diff --git a/src-tauri/src/project_repository/branch.rs b/src-tauri/src/project_repository/branch.rs new file mode 100644 index 000000000..41b05877d --- /dev/null +++ b/src-tauri/src/project_repository/branch.rs @@ -0,0 +1,113 @@ +use std::fmt; + +use serde::Serialize; + +#[derive(Debug)] +pub struct RemoteName { + // contains name of the remote, e.x. "origin" or "upstream" + remote: String, + // contains name of the branch, e.x. "master" or "main" + branch: String, +} + +impl fmt::Display for RemoteName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.remote, self.branch) + } +} + +impl Serialize for RemoteName { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +impl TryFrom<&git2::Branch<'_>> for RemoteName { + type Error = git2::Error; + + fn try_from(value: &git2::Branch<'_>) -> std::result::Result { + if !value.get().is_remote() { + return Err(git2::Error::from_str("not a remote branch")); + } + let name = String::from_utf8(value.name_bytes()?.to_vec()) + .map_err(|e| git2::Error::from_str(&e.to_string()))?; + + if let Some((remote, branch)) = name.split_once('/') { + Ok(Self { + remote: remote.to_string(), + branch: branch.to_string(), + }) + } else { + Err(git2::Error::from_str("invalid remote branch name")) + } + } +} + +#[derive(Debug)] +pub struct LocalName { + // contains name of the branch, e.x. "master" or "main" + branch: String, + // contains name of the remote branch, if the local branch is tracking a remote branch + remote: Option, +} + +impl Serialize for LocalName { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.branch) + } +} + +impl fmt::Display for LocalName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.branch) + } +} + +impl TryFrom<&git2::Branch<'_>> for LocalName { + type Error = git2::Error; + + fn try_from(value: &git2::Branch<'_>) -> std::result::Result { + if value.get().is_remote() { + return Err(git2::Error::from_str("not a local branch")); + } + let name = String::from_utf8(value.name_bytes()?.to_vec()) + .map_err(|e| git2::Error::from_str(&e.to_string()))?; + match value.upstream() { + Ok(upstream) => { + let remote_branch_name = RemoteName::try_from(&upstream)?; + Ok(Self { + branch: name, + remote: Some(remote_branch_name), + }) + } + Err(error) => { + if error.code() == git2::ErrorCode::NotFound { + Ok(Self { + branch: name, + remote: None, + }) + } else { + Err(error) + } + } + } + } +} + +#[derive(Debug)] +pub enum Name { + Remote(RemoteName), + Local(LocalName), +} + +impl TryFrom<&git2::Branch<'_>> for Name { + type Error = git2::Error; + + fn try_from(value: &git2::Branch<'_>) -> std::result::Result { + if value.get().is_remote() { + Ok(Self::Remote(RemoteName::try_from(value)?)) + } else { + Ok(Self::Local(LocalName::try_from(value)?)) + } + } +} diff --git a/src-tauri/src/project_repository/mod.rs b/src-tauri/src/project_repository/mod.rs index a02eeafd9..ddebe32f0 100644 --- a/src-tauri/src/project_repository/mod.rs +++ b/src-tauri/src/project_repository/mod.rs @@ -1,6 +1,7 @@ pub mod activity; pub mod conflicts; pub mod diff; +pub mod branch; mod repository; pub use repository::{Error, FileStatus, Repository}; diff --git a/src-tauri/src/project_repository/repository.rs b/src-tauri/src/project_repository/repository.rs index d72f463c1..319b32480 100644 --- a/src-tauri/src/project_repository/repository.rs +++ b/src-tauri/src/project_repository/repository.rs @@ -8,6 +8,8 @@ use walkdir::WalkDir; use crate::{project_repository::activity, projects, reader}; +use super::branch; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] @@ -218,30 +220,28 @@ impl<'repository> Repository<'repository> { Ok(files) } - pub fn git_branches(&self) -> Result> { - let mut branches = vec![]; - for branch in self - .git_repository + pub fn git_branches(&self) -> Result> { + self.git_repository .branches(Some(git2::BranchType::Local))? - { - let (branch, _) = branch?; - let name = branch.name()?.unwrap().to_string(); - branches.push(name); - } - Ok(branches) + .flatten() + .map(|(branch, _)| branch) + .map(|branch| { + branch::LocalName::try_from(&branch) + .context("failed to convert branch to local name") + }) + .collect::>>() } - pub fn git_remote_branches(&self) -> Result> { - let mut branches = vec![]; - for branch in self - .git_repository + pub fn git_remote_branches(&self) -> Result> { + self.git_repository .branches(Some(git2::BranchType::Remote))? - { - let (branch, _) = branch?; - let name = branch.name()?.unwrap().to_string(); - branches.push(name); - } - Ok(branches) + .flatten() + .map(|(branch, _)| branch) + .map(|branch| { + branch::RemoteName::try_from(&branch) + .context("failed to convert branch to remote name") + }) + .collect::>>() } // returns a list of commit oids from the first oid to the second oid