Skip to content

Commit

Permalink
JB-12 Minimal working hook
Browse files Browse the repository at this point in the history
  • Loading branch information
Baarsgaard committed Sep 7, 2023
1 parent e539e17 commit 2225f38
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 27 deletions.
3 changes: 0 additions & 3 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,3 @@ protocol = "sparse"

[profile.release]
strip = true

[alias]
b = "build --features cloud"
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[workspace]
resolver = "2"
members = [
"jira",
"jig-cli",
"jira",
]
default-members = [
"jig-cli"
Expand Down
18 changes: 14 additions & 4 deletions jig-cli/src/commands/init_config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::config::{self, RawConfig};
use crate::config::{self, GitHooksRawConfig, RawConfig};
use clap::Args;
use color_eyre::eyre::{eyre, Result, WrapErr};
use inquire::{Confirm, CustomType, Password, Select, Text};
Expand Down Expand Up @@ -37,6 +37,7 @@ impl InitConfig {
user_login: None,
api_token: None,
pat_token: None,
jira_timeout_seconds: None,
issue_query: String::from("assignee = currentUser() ORDER BY updated DESC"),
retry_query: String::from("reporter = currentUser() ORDER BY updated DESC"),
always_confirm_date: None,
Expand All @@ -45,7 +46,7 @@ impl InitConfig {
enable_comment_prompts: None,
one_transition_auto_move: None,
inclusive_filters: None,
timeout: None,
git_hooks: None,
};

InitConfig::set_credentials(&mut new_cfg)?;
Expand All @@ -71,7 +72,7 @@ impl InitConfig {
.with_default(new_cfg.max_query_results.unwrap())
.prompt()?,
);
new_cfg.timeout = Some(
new_cfg.jira_timeout_seconds = Some(
CustomType::new("Rest Timeout (Seconds):")
.with_default(10u64)
.with_help_message("How long to wait on server to respond")
Expand Down Expand Up @@ -102,7 +103,6 @@ impl InitConfig {
.with_default(false)
.prompt()?,
);

#[cfg(feature = "cloud")]
{
new_cfg.inclusive_filters = Some(
Expand All @@ -112,6 +112,16 @@ impl InitConfig {
.prompt()?,
);
}
let mut new_git_hooks = GitHooksRawConfig {
allow_branch_missing_issue_key: None,
};
new_git_hooks.allow_branch_missing_issue_key = Some(
Confirm::new("Skip transition select on one valid transition:")
.with_default(false)
.prompt()?,
);

new_cfg.git_hooks = Some(new_git_hooks);

InitConfig::write_config(&new_cfg, config_file).wrap_err("Failed to write full config")
}
Expand Down
4 changes: 2 additions & 2 deletions jig-cli/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ impl ExecCommand for Query {
let query = String::default();

let issues = match client
.query_issues(query)
.query_issues(&query)
.wrap_err("First issue query failed")
{
Ok(issue_body) => issue_body.issues.unwrap(),
Err(_) => client
.query_issues(cfg.retry_query.clone())
.query_issues(&cfg.retry_query)
.wrap_err(eyre!("Retry query failed"))?
.issues
.unwrap(),
Expand Down
33 changes: 30 additions & 3 deletions jig-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use toml::from_str;
// Proof of concept
static CONFIG_FILE: OnceLock<PathBuf> = OnceLock::new();

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RawConfig {
pub jira_url: String,
Expand All @@ -20,13 +20,24 @@ pub struct RawConfig {
pub user_login: Option<String>,
pub api_token: Option<String>,
pub pat_token: Option<String>,
pub jira_timeout_seconds: Option<u64>,
pub always_confirm_date: Option<bool>,
pub always_short_branch_names: Option<bool>,
pub max_query_results: Option<u32>,
pub enable_comment_prompts: Option<bool>,
pub one_transition_auto_move: Option<bool>,
pub inclusive_filters: Option<bool>,
pub timeout: Option<u64>,
pub git_hooks: Option<GitHooksRawConfig>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitHooksRawConfig {
pub allow_branch_missing_issue_key: Option<bool>,
}

#[derive(Debug, Clone)]
pub struct GitHooksConfig {
pub allow_branch_missing_issue_key: bool,
}

#[derive(Debug, Clone)]
Expand All @@ -39,6 +50,7 @@ pub struct Config {
pub one_transition_auto_move: Option<bool>,
pub inclusive_filters: Option<bool>,
pub jira_cfg: JiraClientConfig,
pub hooks_cfg: GitHooksConfig,
}

impl Config {
Expand Down Expand Up @@ -89,6 +101,20 @@ impl Config {
}
}

impl From<Option<GitHooksRawConfig>> for GitHooksConfig {
fn from(value: Option<GitHooksRawConfig>) -> Self {
if let Some(cfg) = value {
GitHooksConfig {
allow_branch_missing_issue_key: cfg.allow_branch_missing_issue_key.unwrap_or(false),
}
} else {
GitHooksConfig {
allow_branch_missing_issue_key: false,
}
}
}
}

impl From<RawConfig> for Config {
fn from(cfg: RawConfig) -> Self {
let credential = if let Some(pat) = cfg.pat_token {
Expand All @@ -114,8 +140,9 @@ impl From<RawConfig> for Config {
credential,
max_query_results: cfg.max_query_results.unwrap_or(50u32),
url: cfg.jira_url,
timeout: cfg.timeout.unwrap_or(10u64),
timeout: cfg.jira_timeout_seconds.unwrap_or(10u64),
},
hooks_cfg: GitHooksConfig::from(cfg.git_hooks),
}
}
}
Expand Down
121 changes: 121 additions & 0 deletions jig-cli/src/hooks/commit_msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::interactivity::{prompt_user_with_issue_select, query_issues_with_retry};
use crate::{
config::Config,
repo::{self, Repository},
};
use color_eyre::{eyre::eyre, eyre::WrapErr, Result};
use jira::types::IssueKey;
use jira::JiraAPIClient;
use regex::Regex;
use std::path::PathBuf;

use super::lib::Hook;

pub struct CommitMsg {
commit_msg_file: PathBuf,
repo: Repository,
}

impl CommitMsg {
fn write_commit(self, commit_msg: String) -> Result<()> {
std::fs::write(self.commit_msg_file, commit_msg).wrap_err("Failed to write new commit_msg")
}
// fn validate(commit_msg: &String) -> Result<()> {
// Ok(())
// }
}

impl Hook for CommitMsg {
fn new() -> CommitMsg {
let commit_msg_file = PathBuf::from(
std::env::args()
.nth(1)
.expect("Expected commit_msg_file as first argument"),
);
let repo = repo::Repository::open()
.wrap_err("Failed to open repository")
.unwrap();

CommitMsg {
commit_msg_file,
repo,
}
}

fn exec(self, cfg: &Config) -> Result<()> {
let mut commit_msg = std::fs::read_to_string(self.commit_msg_file.clone()).unwrap();
let branch = self.repo.get_branch_name();

// Pre-checks to verify commit should be processed
if branch.is_empty() {
// Sanity check
return Err(eyre!("Branch is empty, how?"));
} else if branch == *"HEAD" {
// Allow rebasing
return Ok(());
}
let fixup_commit_re =
Regex::new(r"^(squash|fixup|amend)!.*").expect("Unable to compile fixup_commits_re");
if fixup_commit_re.captures(&commit_msg).is_some() {
// Allow fixup commits without messages
return Ok(());
}

// Processing starts
let branch_issue_key = IssueKey::try_from(branch.clone());
let commit_issue_key = IssueKey::try_from(commit_msg.clone());

let final_msg = match (branch_issue_key, commit_issue_key) {
// Most common case
(Ok(bik), Err(_)) => {
// Easy uppercase when commit does not contain valid issue key
let first_char = commit_msg.chars().next().unwrap();
if first_char.is_ascii_alphabetic() && first_char.is_lowercase() {
commit_msg.replace_range(..1, &first_char.to_ascii_uppercase().to_string());
}

Ok(format!("{} {}", bik, commit_msg))
}
(Ok(bik), Ok(cik)) => {
if (bik.to_string() != cik.to_string())
|| !branch.starts_with(&bik.0)
|| !commit_msg.starts_with(&cik.0)
{
Err(eyre!("Branch and commit 'Issue key' mismatch\nEnsure branch and commit message are *prefixed* with the same Issue key"))
} else if branch.starts_with(cik.to_string().as_str()) {
Ok(commit_msg)
} else {
Err(eyre!("Issue "))
}
}
(Err(_), Ok(_cik)) => {
// Allow branches without issue key, off by default
if cfg.hooks_cfg.allow_branch_missing_issue_key {
Err(eyre!(
"Branch is missing Issue key, cannot infer commit Issue key"
))
} else {
Ok(commit_msg)
}
}
(Err(_), Err(_)) => {
// TODO if config allows bik and cik is empty, prompt to select issue
if cfg.hooks_cfg.allow_branch_missing_issue_key {
Err(eyre!(
"Branch is missing Issue key, cannot infer commit Issue key"
))
} else {
let client = JiraAPIClient::new(&cfg.jira_cfg)?;
let issues = query_issues_with_retry(&client, cfg)?;
let issue_key = prompt_user_with_issue_select(issues)?.key;
Ok(format!("{} {}", issue_key, commit_msg))
}
}
}?;

// TODO Check result against various regexes in a check function to ensure Conformity, if failing, attempt to fix and retry conformity checks
// TODO Copy error from work script
// let res = CommitMsg::validate(&final_msg)?;
CommitMsg::write_commit(self, final_msg)
}
}
47 changes: 47 additions & 0 deletions jig-cli/src/hooks/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::path::PathBuf;

use super::*;

pub trait Hook {
fn new() -> Self;
fn exec(self, cfg: &Config) -> Result<()>;
}

pub fn is_git_hook() -> Option<impl Hook> {
let bin_path = PathBuf::from(
std::env::args()
.next()
.expect("First argument to be path to commit_msg_file"),
);

match bin_path.file_name().unwrap().to_str() {
Some("commit-msg") => Some(CommitMsg::new()),
// Some("applypatch-msg") => Ok(None),
// Some("pre-applypatch") => Ok(None),
// Some("post-applypatch") => Ok(None),
// Some("pre-commit") => Ok(None),
// Some("pre-merge-commit") => Ok(None),
// Some("prepare-commit-msg") => Ok(None),
// Some("post-commit") => Ok(None),
// Some("pre-rebase") => Some(),
// Some("post-checkout") => Ok(None),
// Some("post-merge") => Ok(None),
// Some("pre-push") => Ok(None),
// Some("pre-receive") => Ok(None),
// Some("update") => Ok(None),
// Some("proc-receive") => Ok(None),
// Some("post-update") => Ok(None),
// Some("reference-transaction") => Ok(None),
// Some("push-to-checkout") => Ok(None),
// Some("pre-auto-gc") => Ok(None),
// Some("post-rewrite") => Ok(None),
// Some("sendemail-validate") => Ok(None),
// Some("fsmonitor-watchman") => Ok(None),
// Some("p4-changelist") => Ok(None),
// Some("p4-prepare-changelist") => Ok(None),
// Some("p4-post-changelist") => Ok(None),
// Some("p4-pre-submit") => Ok(None),
// Some("post-index-change") => Ok(None),
_ => None,
}
}
8 changes: 8 additions & 0 deletions jig-cli/src/hooks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use crate::config::Config;
use color_eyre::Result;

mod commit_msg;
mod lib;

pub use commit_msg::CommitMsg;
pub use lib::*;
Loading

0 comments on commit 2225f38

Please sign in to comment.