mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 03:26:36 +03:00
gitbulter-forge crate
Create a centralized crate for forge actions that is provider agnostic. Move the logic behind fetching the PR templates to it.
This commit is contained in:
parent
e37275e8c9
commit
4ee01031f6
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -2339,6 +2339,15 @@ dependencies = [
|
|||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitbutler-forge"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"gitbutler-fs",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gitbutler-fs"
|
name = "gitbutler-fs"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@ -2464,6 +2473,7 @@ dependencies = [
|
|||||||
"fslock",
|
"fslock",
|
||||||
"git2",
|
"git2",
|
||||||
"gitbutler-error",
|
"gitbutler-error",
|
||||||
|
"gitbutler-forge",
|
||||||
"gitbutler-id",
|
"gitbutler-id",
|
||||||
"gitbutler-serde",
|
"gitbutler-serde",
|
||||||
"gitbutler-storage",
|
"gitbutler-storage",
|
||||||
@ -2648,6 +2658,7 @@ dependencies = [
|
|||||||
"gitbutler-edit-mode",
|
"gitbutler-edit-mode",
|
||||||
"gitbutler-error",
|
"gitbutler-error",
|
||||||
"gitbutler-feedback",
|
"gitbutler-feedback",
|
||||||
|
"gitbutler-forge",
|
||||||
"gitbutler-fs",
|
"gitbutler-fs",
|
||||||
"gitbutler-id",
|
"gitbutler-id",
|
||||||
"gitbutler-operating-modes",
|
"gitbutler-operating-modes",
|
||||||
|
@ -92,6 +92,7 @@ gitbutler-oxidize = { path = "crates/gitbutler-oxidize" }
|
|||||||
gitbutler-stack-api = { path = "crates/gitbutler-stack-api" }
|
gitbutler-stack-api = { path = "crates/gitbutler-stack-api" }
|
||||||
gitbutler-stack = { path = "crates/gitbutler-stack" }
|
gitbutler-stack = { path = "crates/gitbutler-stack" }
|
||||||
gitbutler-patch-reference = { path = "crates/gitbutler-patch-reference" }
|
gitbutler-patch-reference = { path = "crates/gitbutler-patch-reference" }
|
||||||
|
gitbutler-forge = { path = "crates/gitbutler-forge" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||||
|
11
crates/gitbutler-forge/Cargo.toml
Normal file
11
crates/gitbutler-forge/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "gitbutler-forge"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["GitButler <gitbutler@gitbutler.com>"]
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { workspace = true, features = ["std"] }
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
gitbutler-fs.workspace = true
|
11
crates/gitbutler-forge/src/forge.rs
Normal file
11
crates/gitbutler-forge/src/forge.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
|
/// Supported git forge types
|
||||||
|
pub enum ForgeType {
|
||||||
|
GitHub,
|
||||||
|
GitLab,
|
||||||
|
Bitbucket,
|
||||||
|
Azure,
|
||||||
|
}
|
2
crates/gitbutler-forge/src/lib.rs
Normal file
2
crates/gitbutler-forge/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod forge;
|
||||||
|
pub mod review;
|
103
crates/gitbutler-forge/src/review.rs
Normal file
103
crates/gitbutler-forge/src/review.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use std::path;
|
||||||
|
|
||||||
|
use gitbutler_fs::list_files;
|
||||||
|
|
||||||
|
use crate::forge::ForgeType;
|
||||||
|
|
||||||
|
/// Get a list of available review template paths for a project
|
||||||
|
///
|
||||||
|
/// The paths are relative to the root path
|
||||||
|
pub fn available_review_templates(root_path: &path::Path, forge_type: &ForgeType) -> Vec<String> {
|
||||||
|
let (is_review_template, get_root) = match forge_type {
|
||||||
|
ForgeType::GitHub => (
|
||||||
|
is_review_template_github as fn(&str) -> bool,
|
||||||
|
get_github_directory_path as fn(&path::Path) -> path::PathBuf,
|
||||||
|
),
|
||||||
|
ForgeType::GitLab => (
|
||||||
|
is_review_template_gitlab as fn(&str) -> bool,
|
||||||
|
get_gitlab_directory_path as fn(&path::Path) -> path::PathBuf,
|
||||||
|
),
|
||||||
|
ForgeType::Bitbucket => (
|
||||||
|
is_review_template_bitbucket as fn(&str) -> bool,
|
||||||
|
get_bitbucket_directory_path as fn(&path::Path) -> path::PathBuf,
|
||||||
|
),
|
||||||
|
ForgeType::Azure => (
|
||||||
|
is_review_template_azure as fn(&str) -> bool,
|
||||||
|
get_azure_directory_path as fn(&path::Path) -> path::PathBuf,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let forge_root_path = get_root(root_path);
|
||||||
|
let forge_root_path = forge_root_path.as_path();
|
||||||
|
|
||||||
|
let walked_paths = list_files(forge_root_path, &[forge_root_path]).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut available_paths = Vec::new();
|
||||||
|
for entry in walked_paths {
|
||||||
|
let path_entry = entry.as_path();
|
||||||
|
let path_str = path_entry.to_string_lossy();
|
||||||
|
|
||||||
|
if is_review_template(&path_str) {
|
||||||
|
if let Ok(template_path) = forge_root_path.join(path_entry).strip_prefix(root_path) {
|
||||||
|
available_paths.push(template_path.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
available_paths
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_github_directory_path(root_path: &path::Path) -> path::PathBuf {
|
||||||
|
let mut path = root_path.to_path_buf();
|
||||||
|
path.push(".github");
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_review_template_github(path_str: &str) -> bool {
|
||||||
|
path_str == "PULL_REQUEST_TEMPLATE.md"
|
||||||
|
|| path_str == "pull_request_template.md"
|
||||||
|
|| path_str.contains("PULL_REQUEST_TEMPLATE/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_gitlab_directory_path(root_path: &path::Path) -> path::PathBuf {
|
||||||
|
// TODO: implement
|
||||||
|
root_path.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_review_template_gitlab(_path_str: &str) -> bool {
|
||||||
|
// TODO: implement
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bitbucket_directory_path(root_path: &path::Path) -> path::PathBuf {
|
||||||
|
// TODO: implement
|
||||||
|
root_path.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_review_template_bitbucket(_path_str: &str) -> bool {
|
||||||
|
// TODO: implement
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_azure_directory_path(root_path: &path::Path) -> path::PathBuf {
|
||||||
|
// TODO: implement
|
||||||
|
root_path.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_review_template_azure(_path_str: &str) -> bool {
|
||||||
|
// TODO: implement
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_review_template_github() {
|
||||||
|
assert!(is_review_template_github("PULL_REQUEST_TEMPLATE.md"));
|
||||||
|
assert!(is_review_template_github("pull_request_template.md"));
|
||||||
|
assert!(is_review_template_github("PULL_REQUEST_TEMPLATE/"));
|
||||||
|
assert!(!is_review_template_github("README.md"));
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ gitbutler-error.workspace = true
|
|||||||
gitbutler-serde.workspace = true
|
gitbutler-serde.workspace = true
|
||||||
gitbutler-id.workspace = true
|
gitbutler-id.workspace = true
|
||||||
gitbutler-storage.workspace = true
|
gitbutler-storage.workspace = true
|
||||||
|
gitbutler-forge.workspace = true
|
||||||
git2.workspace = true
|
git2.workspace = true
|
||||||
gix = { workspace = true, features = ["dirwalk", "credentials", "parallel"] }
|
gix = { workspace = true, features = ["dirwalk", "credentials", "parallel"] }
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::{self, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use gitbutler_forge::{forge::ForgeType, review::available_review_templates};
|
||||||
use gitbutler_id::id::Id;
|
use gitbutler_id::id::Id;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -102,9 +103,21 @@ pub struct Project {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GitHostSettings {
|
pub struct GitHostSettings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub host_type: Option<String>,
|
pub host_type: Option<ForgeType>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub pull_request_template_path: Option<String>,
|
pub review_template_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitHostSettings {
|
||||||
|
pub fn init(&mut self, project_path: &Path) {
|
||||||
|
if let Some(forge_type) = &self.host_type {
|
||||||
|
if self.review_template_path.is_none() {
|
||||||
|
self.review_template_path = available_review_templates(project_path, forge_type)
|
||||||
|
.first()
|
||||||
|
.cloned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
@ -73,6 +73,7 @@ gitbutler-diff.workspace = true
|
|||||||
gitbutler-operating-modes.workspace = true
|
gitbutler-operating-modes.workspace = true
|
||||||
gitbutler-edit-mode.workspace = true
|
gitbutler-edit-mode.workspace = true
|
||||||
gitbutler-sync.workspace = true
|
gitbutler-sync.workspace = true
|
||||||
|
gitbutler-forge.workspace = true
|
||||||
open = "5"
|
open = "5"
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
|
|
||||||
|
24
crates/gitbutler-tauri/src/forge.rs
Normal file
24
crates/gitbutler-tauri/src/forge.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
pub mod commands {
|
||||||
|
use gitbutler_forge::review::available_review_templates;
|
||||||
|
use gitbutler_project::{Controller, ProjectId};
|
||||||
|
use tauri::State;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
#[tauri::command(async)]
|
||||||
|
#[instrument(skip(projects), err(Debug))]
|
||||||
|
pub fn get_available_review_templates(
|
||||||
|
projects: State<'_, Controller>,
|
||||||
|
project_id: ProjectId,
|
||||||
|
) -> Result<Vec<String>, Error> {
|
||||||
|
let project = projects.get_validated(project_id)?;
|
||||||
|
let root_path = &project.path;
|
||||||
|
let forge_type = project.git_host.host_type;
|
||||||
|
|
||||||
|
let review_templates = forge_type
|
||||||
|
.map(|forge_type| available_review_templates(root_path, &forge_type))
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok(review_templates)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
pub mod commands {
|
pub mod commands {
|
||||||
use std::{collections::HashMap, path};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use gitbutler_fs::list_files;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@ -80,28 +79,4 @@ pub mod commands {
|
|||||||
.context("Failed to parse response body")
|
.context("Failed to parse response body")
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
|
||||||
#[instrument]
|
|
||||||
pub fn available_pull_request_templates(root_path: &path::Path) -> Result<Vec<String>, Error> {
|
|
||||||
let walked_paths = list_files(root_path, &[root_path])?;
|
|
||||||
|
|
||||||
let mut available_paths = Vec::new();
|
|
||||||
for entry in walked_paths {
|
|
||||||
let path_entry = entry.as_path();
|
|
||||||
let path_str = path_entry.to_string_lossy();
|
|
||||||
// TODO: Refactor these paths out in the future to something like a common
|
|
||||||
// gitHosts.pullRequestTemplatePaths map, an entry for each gitHost type and
|
|
||||||
// their valid files / directories. So that this 'get_available_templates'
|
|
||||||
// can be more generic and we can add / modify paths more easily for all supported githost types
|
|
||||||
if path_str == "PULL_REQUEST_TEMPLATE.md"
|
|
||||||
|| path_str == "pull_request_template.md"
|
|
||||||
|| path_str.contains("PULL_REQUEST_TEMPLATE/")
|
|
||||||
{
|
|
||||||
available_paths.push(root_path.join(path_entry).to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(available_paths)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ pub use window::state::WindowState;
|
|||||||
pub mod askpass;
|
pub mod askpass;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod forge;
|
||||||
pub mod github;
|
pub mod github;
|
||||||
pub mod modes;
|
pub mod modes;
|
||||||
pub mod open;
|
pub mod open;
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
use gitbutler_tauri::{
|
use gitbutler_tauri::{
|
||||||
askpass, commands, config, github, logs, menu, modes, open, projects, remotes, repo, secret,
|
askpass, commands, config, forge, github, logs, menu, modes, open, projects, remotes, repo,
|
||||||
stack, undo, users, virtual_branches, zip, App, WindowState,
|
secret, stack, undo, users, virtual_branches, zip, App, WindowState,
|
||||||
};
|
};
|
||||||
use tauri::{generate_context, Manager};
|
use tauri::{generate_context, Manager};
|
||||||
use tauri_plugin_log::LogTarget;
|
use tauri_plugin_log::LogTarget;
|
||||||
@ -146,6 +146,7 @@ fn main() {
|
|||||||
projects::commands::list_projects,
|
projects::commands::list_projects,
|
||||||
projects::commands::set_project_active,
|
projects::commands::set_project_active,
|
||||||
projects::commands::open_project_in_window,
|
projects::commands::open_project_in_window,
|
||||||
|
projects::commands::update_project_git_host,
|
||||||
repo::commands::git_get_local_config,
|
repo::commands::git_get_local_config,
|
||||||
repo::commands::git_set_local_config,
|
repo::commands::git_set_local_config,
|
||||||
repo::commands::check_signing_settings,
|
repo::commands::check_signing_settings,
|
||||||
@ -207,7 +208,6 @@ fn main() {
|
|||||||
menu::get_editor_link_scheme,
|
menu::get_editor_link_scheme,
|
||||||
github::commands::init_device_oauth,
|
github::commands::init_device_oauth,
|
||||||
github::commands::check_auth_status,
|
github::commands::check_auth_status,
|
||||||
github::commands::available_pull_request_templates,
|
|
||||||
askpass::commands::submit_prompt_response,
|
askpass::commands::submit_prompt_response,
|
||||||
remotes::list_remotes,
|
remotes::list_remotes,
|
||||||
remotes::add_remote,
|
remotes::add_remote,
|
||||||
@ -216,7 +216,8 @@ fn main() {
|
|||||||
modes::save_edit_and_return_to_workspace,
|
modes::save_edit_and_return_to_workspace,
|
||||||
modes::abort_edit_and_return_to_workspace,
|
modes::abort_edit_and_return_to_workspace,
|
||||||
modes::edit_initial_index_state,
|
modes::edit_initial_index_state,
|
||||||
open::open_url
|
open::open_url,
|
||||||
|
forge::commands::get_available_review_templates,
|
||||||
])
|
])
|
||||||
.menu(menu::build(tauri_context.package_info()))
|
.menu(menu::build(tauri_context.package_info()))
|
||||||
.on_menu_event(|event| menu::handle_event(&event))
|
.on_menu_event(|event| menu::handle_event(&event))
|
||||||
|
@ -4,7 +4,9 @@ pub mod commands {
|
|||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use gitbutler_project::{self as projects, Controller, ProjectId};
|
use gitbutler_project::{
|
||||||
|
self as projects, Controller, GitHostSettings, ProjectId, UpdateRequest,
|
||||||
|
};
|
||||||
use tauri::{State, Window};
|
use tauri::{State, Window};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@ -19,6 +21,26 @@ pub mod commands {
|
|||||||
Ok(projects.update(&project)?)
|
Ok(projects.update(&project)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command(async)]
|
||||||
|
#[instrument(skip(projects), err(Debug))]
|
||||||
|
pub fn update_project_git_host(
|
||||||
|
projects: State<'_, Controller>,
|
||||||
|
project_id: ProjectId,
|
||||||
|
git_host: GitHostSettings,
|
||||||
|
) -> Result<projects::Project, Error> {
|
||||||
|
let project = projects.get_validated(project_id)?;
|
||||||
|
let root_path = &project.path;
|
||||||
|
let mut git_host = git_host.clone();
|
||||||
|
git_host.init(root_path);
|
||||||
|
|
||||||
|
let request = UpdateRequest {
|
||||||
|
id: project_id,
|
||||||
|
git_host: Some(git_host),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Ok(projects.update(&request)?)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
#[tauri::command(async)]
|
||||||
#[instrument(skip(projects), err(Debug))]
|
#[instrument(skip(projects), err(Debug))]
|
||||||
pub fn add_project(
|
pub fn add_project(
|
||||||
|
Loading…
Reference in New Issue
Block a user