From ef24cb583c1a709430b189153beb060538d42eae Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 6 Aug 2024 20:48:53 +0100 Subject: [PATCH] feat: add short git log graph to notifications Closes kemitix/git-next#133 --- Cargo.lock | 7 ++ Cargo.toml | 3 + crates/cli/README.md | 19 +++-- crates/cli/src/alerts/mod.rs | 32 ++++----- .../src/repo/handlers/receive_ci_status.rs | 3 +- crates/cli/src/repo/notifications.rs | 8 ++- crates/core/Cargo.toml | 3 + crates/core/src/config/graphs.rs | 3 + crates/core/src/config/mod.rs | 1 + crates/core/src/git/graph.rs | 69 +++++++++++++++++++ crates/core/src/git/mod.rs | 1 + crates/core/src/git/user_notification.rs | 16 +++-- crates/core/src/git/validation/positions.rs | 3 +- 13 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 crates/core/src/config/graphs.rs create mode 100644 crates/core/src/git/graph.rs diff --git a/Cargo.lock b/Cargo.lock index d87b59e7..ec22243d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -993,6 +993,7 @@ dependencies = [ "secrecy", "serde", "serde_json", + "take-until", "test-log", "thiserror", "time", @@ -3635,6 +3636,12 @@ dependencies = [ "libc", ] +[[package]] +name = "take-until" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" + [[package]] name = "tempfile" version = "3.10.1" diff --git a/Cargo.toml b/Cargo.toml index 98d5cae7..215b768a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,9 @@ anyhow = "1.0" thiserror = "1.0" pike = "0.1" +# iters +take-until = "0.2" + # file watcher notify = "6.1" diff --git a/crates/cli/README.md b/crates/cli/README.md index 5d9903f5..5963d02a 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -297,7 +297,13 @@ Sample payload: "main": "main" }, "forge_alias": "jo", - "repo_alias": "kxio" + "repo_alias": "kxio", + "log": [ + "* 9bfce91 (origin/dev) fix: add log graph to notifications", + "| * c37bd2c (origin/next, origin/main) feat: add log graph to notifications", + "|/", + "* 8c19680 refactor: macros use a more common syntax" + ] }, "timestamp": "1721760933", "type": "branch.dev.not-on-main" @@ -318,11 +324,16 @@ Sample payload: { "data": { "commit": { - "sha": "98abef1af6825f9770d725a681e5cfc09d7fd4f2", - "message": "feat: add foo to bar template" + "sha": "c37bd2caf6825f9770d725a681e5cfc09d7fd4f2", + "message": "feat: add log graph to notifications (1 of 2)" }, "forge_alias": "jo", - "repo_alias": "kxio" + "repo_alias": "kxio", + "log": [ + "* 9bfce91 (origin/dev) feat: add log graph to notifications (2 of 2)", + "* c37bd2c (origin/next) feat: add log graph to notifications (1 of 2)", + "* 8c19680 (origin/main) refactor: macros use a more common syntax" + ] }, "timestamp": "1721760933", "type": "cicheck.failed" diff --git a/crates/cli/src/alerts/mod.rs b/crates/cli/src/alerts/mod.rs index 18ae6b3f..fdc8f0b6 100644 --- a/crates/cli/src/alerts/mod.rs +++ b/crates/cli/src/alerts/mod.rs @@ -35,6 +35,7 @@ fn short_message(user_notification: &UserNotification) -> String { forge_alias, repo_alias, commit, + log: _, } => format!("CI Check Failed: {forge_alias}/{repo_alias}: {commit}"), UserNotification::RepoConfigLoadFailure { forge_alias, @@ -53,6 +54,7 @@ fn short_message(user_notification: &UserNotification) -> String { main_branch: _, dev_commit: _, main_commit: _, + log: _, } => format!("Dev not based on Main: {forge_alias}/{repo_alias}"), }; format!("[git-next] {tail}") @@ -64,6 +66,7 @@ fn full_message(user_notification: &UserNotification) -> String { forge_alias, repo_alias, commit, + log, } => { let sha = commit.sha(); let message = commit.message(); @@ -71,6 +74,8 @@ fn full_message(user_notification: &UserNotification) -> String { "CI Checks had Failed".to_string(), format!("Forge: {forge_alias}\nRepo : {repo_alias}"), format!("Commit:\n - {sha}\n - {message}"), + "Log:".to_string(), + log.join("\n"), ] .join("\n\n") } @@ -99,21 +104,16 @@ fn full_message(user_notification: &UserNotification) -> String { repo_alias, dev_branch, main_branch, - dev_commit, - main_commit, - } => { - let dev_sha = dev_commit.sha(); - let dev_message = dev_commit.message(); - let main_sha = main_commit.sha(); - let main_message = main_commit.message(); - [ - format!("The branch '{dev_branch}' is not based on the branch '{main_branch}'."), - format!("TODO: Rebase '{dev_branch}' onto '{main_branch}'."), - format!("Forge: {forge_alias}\nRepo : {repo_alias}"), - format!("{dev_branch}:\n - {dev_sha}\n - {dev_message}"), - format!("{main_branch}:\n - {main_sha}\n - {main_message}"), - ] - .join("\n\n") - } + dev_commit: _, + main_commit: _, + log, + } => [ + format!("The branch '{dev_branch}' is not based on the branch '{main_branch}'."), + format!("TODO: Rebase '{dev_branch}' onto '{main_branch}'."), + format!("Forge: {forge_alias}\nRepo : {repo_alias}"), + "Log:".to_string(), + log.join("\n"), + ] + .join("\n\n"), } } diff --git a/crates/cli/src/repo/handlers/receive_ci_status.rs b/crates/cli/src/repo/handlers/receive_ci_status.rs index 75c40bd9..6d8e272b 100644 --- a/crates/cli/src/repo/handlers/receive_ci_status.rs +++ b/crates/cli/src/repo/handlers/receive_ci_status.rs @@ -1,7 +1,7 @@ // use actix::prelude::*; -use git_next_core::git::{forge::commit::Status, UserNotification}; +use git_next_core::git::{forge::commit::Status, graph, UserNotification}; use tracing::debug; use crate::repo::{ @@ -40,6 +40,7 @@ impl Handler for RepoActor { forge_alias, repo_alias, commit: next, + log: graph::log(&self.repo_details), }, log.as_ref(), ); diff --git a/crates/cli/src/repo/notifications.rs b/crates/cli/src/repo/notifications.rs index c54e612a..9b70c89c 100644 --- a/crates/cli/src/repo/notifications.rs +++ b/crates/cli/src/repo/notifications.rs @@ -14,6 +14,7 @@ impl NotifyUser { forge_alias, repo_alias, commit, + log, } => json!({ "type": "cicheck.failed", "timestamp": timestamp, @@ -23,7 +24,8 @@ impl NotifyUser { "commit": { "sha": commit.sha(), "message": commit.message() - } + }, + "log": **log } }), UserNotification::RepoConfigLoadFailure { @@ -59,6 +61,7 @@ impl NotifyUser { main_branch, dev_commit, main_commit, + log, } => json!({ "type": "branch.dev.not-on-main", "timestamp": timestamp, @@ -78,7 +81,8 @@ impl NotifyUser { "sha": main_commit.sha(), "message": main_commit.message() } - } + }, + "log": **log } }), } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index ae3cc401..f798ea81 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -57,6 +57,9 @@ serde_json = { workspace = true } mockall = { workspace = true } +#iters +take-until = { workspace = true } + [dev-dependencies] # Testing assert2 = { workspace = true } diff --git a/crates/core/src/config/graphs.rs b/crates/core/src/config/graphs.rs new file mode 100644 index 00000000..890d53c5 --- /dev/null +++ b/crates/core/src/config/graphs.rs @@ -0,0 +1,3 @@ +use crate::newtype; + +newtype!(LogGraph, Vec, "Git log showing branch positions"); diff --git a/crates/core/src/config/mod.rs b/crates/core/src/config/mod.rs index e1d9f7f5..4f1e479b 100644 --- a/crates/core/src/config/mod.rs +++ b/crates/core/src/config/mod.rs @@ -7,6 +7,7 @@ mod forge_config; mod forge_details; mod forge_type; pub mod git_dir; +mod graphs; mod host_name; mod registered_webhook; mod remote_url; diff --git a/crates/core/src/git/graph.rs b/crates/core/src/git/graph.rs new file mode 100644 index 00000000..718c0daf --- /dev/null +++ b/crates/core/src/git/graph.rs @@ -0,0 +1,69 @@ +// + +use std::borrow::ToOwned; + +use take_until::TakeUntilExt; + +use crate::{newtype, GitDir, RepoBranches}; + +use super::RepoDetails; + +newtype!(Log, Vec, Default, Hash, "Git log of branches"); + +// create a graph to log relative positions +// +// ANCESTOR=$(git merge-base --octopus origin/main origin/next origin/dev) +// SHORT_ANCESTOR=$(echo $ANCESTOR | cut -b -7) +fn ancesstor(gitdir: &GitDir, branches: &RepoBranches) -> Option { + let result = std::process::Command::new("/usr/bin/git") + .current_dir(gitdir.to_path_buf()) + .args([ + "merge-base", + "--octopus", + format!("origin/{}", branches.main()).as_str(), + format!("origin/{}", branches.next()).as_str(), + format!("origin/{}", branches.dev()).as_str(), + ]) + .output(); + if let Ok(output) = result { + return String::from_utf8_lossy(output.stdout.as_slice()) + .split('\n') + .take(1) + .map(|line| line.chars().take(7).collect::()) + .collect::>() + .first() + .cloned(); + } + None +} + +// git log --oneline --graph --decorate origin/main origin/dev origin/next | awk "1;/$SHORT_ANCESTOR/{exit}" +pub fn log(repo_details: &RepoDetails) -> Log { + if let Some(repo_config) = &repo_details.repo_config { + let branches = repo_config.branches(); + let result = std::process::Command::new("/usr/bin/git") + .current_dir(repo_details.gitdir().to_path_buf()) + .args([ + "log", + "--oneline", + "--graph", + "--decorate", + format!("origin/{}", branches.main()).as_str(), + format!("origin/{}", branches.next()).as_str(), + format!("origin/{}", branches.dev()).as_str(), + ]) + .output(); + if let Ok(output) = result { + if let Some(ancestor) = ancesstor(repo_details.gitdir(), branches) { + return String::from_utf8_lossy(output.stdout.as_slice()) + .split('\n') + .take_until(|line| line.contains(&ancestor)) + .map(str::trim) + .map(ToOwned::to_owned) + .collect::>() + .into(); + } + } + } + Log::default() +} diff --git a/crates/core/src/git/mod.rs b/crates/core/src/git/mod.rs index d13411a6..2c29f03b 100644 --- a/crates/core/src/git/mod.rs +++ b/crates/core/src/git/mod.rs @@ -6,6 +6,7 @@ pub mod forge; mod generation; mod git_ref; mod git_remote; +pub mod graph; pub mod push; mod repo_details; pub mod repository; diff --git a/crates/core/src/git/user_notification.rs b/crates/core/src/git/user_notification.rs index e6986972..00692f33 100644 --- a/crates/core/src/git/user_notification.rs +++ b/crates/core/src/git/user_notification.rs @@ -2,12 +2,15 @@ use crate::{git::Commit, BranchName, ForgeAlias, RepoAlias}; use serde_json::json; +use super::graph::Log; + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum UserNotification { CICheckFailed { forge_alias: ForgeAlias, repo_alias: RepoAlias, commit: Commit, + log: Log, }, RepoConfigLoadFailure { forge_alias: ForgeAlias, @@ -26,6 +29,7 @@ pub enum UserNotification { main_branch: BranchName, dev_commit: Commit, main_commit: Commit, + log: Log, }, } impl UserNotification { @@ -37,6 +41,7 @@ impl UserNotification { forge_alias, repo_alias, commit, + log, } => json!({ "type": "cicheck.failed", "timestamp": timestamp, @@ -46,7 +51,8 @@ impl UserNotification { "commit": { "sha": commit.sha(), "message": commit.message() - } + }, + "log": **log } }), Self::RepoConfigLoadFailure { @@ -59,7 +65,7 @@ impl UserNotification { "data": { "forge_alias": forge_alias, "repo_alias": repo_alias, - "reason": reason + "reason": reason, } }), Self::WebhookRegistration { @@ -72,7 +78,7 @@ impl UserNotification { "data": { "forge_alias": forge_alias, "repo_alias": repo_alias, - "reason": reason + "reason": reason, } }), Self::DevNotBasedOnMain { @@ -82,6 +88,7 @@ impl UserNotification { main_branch, dev_commit, main_commit, + log, } => json!({ "type": "branch.dev.not-on-main", "timestamp": timestamp, @@ -101,7 +108,8 @@ impl UserNotification { "sha": main_commit.sha(), "message": main_commit.message() } - } + }, + "log": **log } }), } diff --git a/crates/core/src/git/validation/positions.rs b/crates/core/src/git/validation/positions.rs index 8d03ecdb..18d81c02 100644 --- a/crates/core/src/git/validation/positions.rs +++ b/crates/core/src/git/validation/positions.rs @@ -1,6 +1,6 @@ // use crate::{ - git::{self, repository::open::OpenRepositoryLike, RepoDetails, UserNotification}, + git::{self, graph::log, repository::open::OpenRepositoryLike, RepoDetails, UserNotification}, BranchName, RepoConfig, }; @@ -61,6 +61,7 @@ pub fn validate( main_branch, dev_commit: dev, main_commit: main, + log: log(repo_details), }, )); }