mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-22 19:14:31 +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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitbutler-forge"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gitbutler-fs",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitbutler-fs"
|
||||
version = "0.0.0"
|
||||
@ -2464,6 +2473,7 @@ dependencies = [
|
||||
"fslock",
|
||||
"git2",
|
||||
"gitbutler-error",
|
||||
"gitbutler-forge",
|
||||
"gitbutler-id",
|
||||
"gitbutler-serde",
|
||||
"gitbutler-storage",
|
||||
@ -2648,6 +2658,7 @@ dependencies = [
|
||||
"gitbutler-edit-mode",
|
||||
"gitbutler-error",
|
||||
"gitbutler-feedback",
|
||||
"gitbutler-forge",
|
||||
"gitbutler-fs",
|
||||
"gitbutler-id",
|
||||
"gitbutler-operating-modes",
|
||||
|
@ -92,6 +92,7 @@ gitbutler-oxidize = { path = "crates/gitbutler-oxidize" }
|
||||
gitbutler-stack-api = { path = "crates/gitbutler-stack-api" }
|
||||
gitbutler-stack = { path = "crates/gitbutler-stack" }
|
||||
gitbutler-patch-reference = { path = "crates/gitbutler-patch-reference" }
|
||||
gitbutler-forge = { path = "crates/gitbutler-forge" }
|
||||
|
||||
[profile.release]
|
||||
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-id.workspace = true
|
||||
gitbutler-storage.workspace = true
|
||||
gitbutler-forge.workspace = true
|
||||
git2.workspace = true
|
||||
gix = { workspace = true, features = ["dirwalk", "credentials", "parallel"] }
|
||||
uuid.workspace = true
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::{
|
||||
path::{self, PathBuf},
|
||||
path::{self, Path, PathBuf},
|
||||
time,
|
||||
};
|
||||
|
||||
use gitbutler_forge::{forge::ForgeType, review::available_review_templates};
|
||||
use gitbutler_id::id::Id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -102,9 +103,21 @@ pub struct Project {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitHostSettings {
|
||||
#[serde(default)]
|
||||
pub host_type: Option<String>,
|
||||
pub host_type: Option<ForgeType>,
|
||||
#[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 {
|
||||
|
@ -73,6 +73,7 @@ gitbutler-diff.workspace = true
|
||||
gitbutler-operating-modes.workspace = true
|
||||
gitbutler-edit-mode.workspace = true
|
||||
gitbutler-sync.workspace = true
|
||||
gitbutler-forge.workspace = true
|
||||
open = "5"
|
||||
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 {
|
||||
use std::{collections::HashMap, path};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use gitbutler_fs::list_files;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
@ -80,28 +79,4 @@ pub mod commands {
|
||||
.context("Failed to parse response body")
|
||||
.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 config;
|
||||
pub mod error;
|
||||
pub mod forge;
|
||||
pub mod github;
|
||||
pub mod modes;
|
||||
pub mod open;
|
||||
|
@ -12,8 +12,8 @@
|
||||
)]
|
||||
|
||||
use gitbutler_tauri::{
|
||||
askpass, commands, config, github, logs, menu, modes, open, projects, remotes, repo, secret,
|
||||
stack, undo, users, virtual_branches, zip, App, WindowState,
|
||||
askpass, commands, config, forge, github, logs, menu, modes, open, projects, remotes, repo,
|
||||
secret, stack, undo, users, virtual_branches, zip, App, WindowState,
|
||||
};
|
||||
use tauri::{generate_context, Manager};
|
||||
use tauri_plugin_log::LogTarget;
|
||||
@ -146,6 +146,7 @@ fn main() {
|
||||
projects::commands::list_projects,
|
||||
projects::commands::set_project_active,
|
||||
projects::commands::open_project_in_window,
|
||||
projects::commands::update_project_git_host,
|
||||
repo::commands::git_get_local_config,
|
||||
repo::commands::git_set_local_config,
|
||||
repo::commands::check_signing_settings,
|
||||
@ -207,7 +208,6 @@ fn main() {
|
||||
menu::get_editor_link_scheme,
|
||||
github::commands::init_device_oauth,
|
||||
github::commands::check_auth_status,
|
||||
github::commands::available_pull_request_templates,
|
||||
askpass::commands::submit_prompt_response,
|
||||
remotes::list_remotes,
|
||||
remotes::add_remote,
|
||||
@ -216,7 +216,8 @@ fn main() {
|
||||
modes::save_edit_and_return_to_workspace,
|
||||
modes::abort_edit_and_return_to_workspace,
|
||||
modes::edit_initial_index_state,
|
||||
open::open_url
|
||||
open::open_url,
|
||||
forge::commands::get_available_review_templates,
|
||||
])
|
||||
.menu(menu::build(tauri_context.package_info()))
|
||||
.on_menu_event(|event| menu::handle_event(&event))
|
||||
|
@ -4,7 +4,9 @@ pub mod commands {
|
||||
use std::path;
|
||||
|
||||
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 tracing::instrument;
|
||||
|
||||
@ -19,6 +21,26 @@ pub mod commands {
|
||||
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)]
|
||||
#[instrument(skip(projects), err(Debug))]
|
||||
pub fn add_project(
|
||||
|
Loading…
Reference in New Issue
Block a user