feat: add short git log graph to notifications
All checks were successful
Rust / build (push) Successful in 2m7s
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
Release Please / Release-plz (push) Successful in 40s

Closes kemitix/git-next#133
This commit is contained in:
Paul Campbell 2024-08-06 20:48:53 +01:00
parent 8c19680056
commit ef24cb583c
13 changed files with 140 additions and 28 deletions

7
Cargo.lock generated
View file

@ -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"

View file

@ -84,6 +84,9 @@ anyhow = "1.0"
thiserror = "1.0"
pike = "0.1"
# iters
take-until = "0.2"
# file watcher
notify = "6.1"

View file

@ -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"

View file

@ -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"),
}
}

View file

@ -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<ReceiveCIStatus> for RepoActor {
forge_alias,
repo_alias,
commit: next,
log: graph::log(&self.repo_details),
},
log.as_ref(),
);

View file

@ -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
}
}),
}

View file

@ -57,6 +57,9 @@ serde_json = { workspace = true }
mockall = { workspace = true }
#iters
take-until = { workspace = true }
[dev-dependencies]
# Testing
assert2 = { workspace = true }

View file

@ -0,0 +1,3 @@
use crate::newtype;
newtype!(LogGraph, Vec<String>, "Git log showing branch positions");

View file

@ -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;

View file

@ -0,0 +1,69 @@
//
use std::borrow::ToOwned;
use take_until::TakeUntilExt;
use crate::{newtype, GitDir, RepoBranches};
use super::RepoDetails;
newtype!(Log, Vec<String>, 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<String> {
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::<String>())
.collect::<Vec<_>>()
.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::<Vec<_>>()
.into();
}
}
}
Log::default()
}

View file

@ -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;

View file

@ -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
}
}),
}

View file

@ -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),
},
));
}