forked from kemitix/git-next
feat: add short git log graph to notifications
Closes kemitix/git-next#133
This commit is contained in:
parent
8c19680056
commit
ef24cb583c
13 changed files with 140 additions and 28 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -993,6 +993,7 @@ dependencies = [
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"take-until",
|
||||||
"test-log",
|
"test-log",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
@ -3635,6 +3636,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "take-until"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.10.1"
|
version = "3.10.1"
|
||||||
|
|
|
@ -84,6 +84,9 @@ anyhow = "1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
pike = "0.1"
|
pike = "0.1"
|
||||||
|
|
||||||
|
# iters
|
||||||
|
take-until = "0.2"
|
||||||
|
|
||||||
# file watcher
|
# file watcher
|
||||||
notify = "6.1"
|
notify = "6.1"
|
||||||
|
|
||||||
|
|
|
@ -297,7 +297,13 @@ Sample payload:
|
||||||
"main": "main"
|
"main": "main"
|
||||||
},
|
},
|
||||||
"forge_alias": "jo",
|
"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",
|
"timestamp": "1721760933",
|
||||||
"type": "branch.dev.not-on-main"
|
"type": "branch.dev.not-on-main"
|
||||||
|
@ -318,11 +324,16 @@ Sample payload:
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"commit": {
|
"commit": {
|
||||||
"sha": "98abef1af6825f9770d725a681e5cfc09d7fd4f2",
|
"sha": "c37bd2caf6825f9770d725a681e5cfc09d7fd4f2",
|
||||||
"message": "feat: add foo to bar template"
|
"message": "feat: add log graph to notifications (1 of 2)"
|
||||||
},
|
},
|
||||||
"forge_alias": "jo",
|
"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",
|
"timestamp": "1721760933",
|
||||||
"type": "cicheck.failed"
|
"type": "cicheck.failed"
|
||||||
|
|
|
@ -35,6 +35,7 @@ fn short_message(user_notification: &UserNotification) -> String {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
repo_alias,
|
repo_alias,
|
||||||
commit,
|
commit,
|
||||||
|
log: _,
|
||||||
} => format!("CI Check Failed: {forge_alias}/{repo_alias}: {commit}"),
|
} => format!("CI Check Failed: {forge_alias}/{repo_alias}: {commit}"),
|
||||||
UserNotification::RepoConfigLoadFailure {
|
UserNotification::RepoConfigLoadFailure {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
|
@ -53,6 +54,7 @@ fn short_message(user_notification: &UserNotification) -> String {
|
||||||
main_branch: _,
|
main_branch: _,
|
||||||
dev_commit: _,
|
dev_commit: _,
|
||||||
main_commit: _,
|
main_commit: _,
|
||||||
|
log: _,
|
||||||
} => format!("Dev not based on Main: {forge_alias}/{repo_alias}"),
|
} => format!("Dev not based on Main: {forge_alias}/{repo_alias}"),
|
||||||
};
|
};
|
||||||
format!("[git-next] {tail}")
|
format!("[git-next] {tail}")
|
||||||
|
@ -64,6 +66,7 @@ fn full_message(user_notification: &UserNotification) -> String {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
repo_alias,
|
repo_alias,
|
||||||
commit,
|
commit,
|
||||||
|
log,
|
||||||
} => {
|
} => {
|
||||||
let sha = commit.sha();
|
let sha = commit.sha();
|
||||||
let message = commit.message();
|
let message = commit.message();
|
||||||
|
@ -71,6 +74,8 @@ fn full_message(user_notification: &UserNotification) -> String {
|
||||||
"CI Checks had Failed".to_string(),
|
"CI Checks had Failed".to_string(),
|
||||||
format!("Forge: {forge_alias}\nRepo : {repo_alias}"),
|
format!("Forge: {forge_alias}\nRepo : {repo_alias}"),
|
||||||
format!("Commit:\n - {sha}\n - {message}"),
|
format!("Commit:\n - {sha}\n - {message}"),
|
||||||
|
"Log:".to_string(),
|
||||||
|
log.join("\n"),
|
||||||
]
|
]
|
||||||
.join("\n\n")
|
.join("\n\n")
|
||||||
}
|
}
|
||||||
|
@ -99,21 +104,16 @@ fn full_message(user_notification: &UserNotification) -> String {
|
||||||
repo_alias,
|
repo_alias,
|
||||||
dev_branch,
|
dev_branch,
|
||||||
main_branch,
|
main_branch,
|
||||||
dev_commit,
|
dev_commit: _,
|
||||||
main_commit,
|
main_commit: _,
|
||||||
} => {
|
log,
|
||||||
let dev_sha = dev_commit.sha();
|
} => [
|
||||||
let dev_message = dev_commit.message();
|
format!("The branch '{dev_branch}' is not based on the branch '{main_branch}'."),
|
||||||
let main_sha = main_commit.sha();
|
format!("TODO: Rebase '{dev_branch}' onto '{main_branch}'."),
|
||||||
let main_message = main_commit.message();
|
format!("Forge: {forge_alias}\nRepo : {repo_alias}"),
|
||||||
[
|
"Log:".to_string(),
|
||||||
format!("The branch '{dev_branch}' is not based on the branch '{main_branch}'."),
|
log.join("\n"),
|
||||||
format!("TODO: Rebase '{dev_branch}' onto '{main_branch}'."),
|
]
|
||||||
format!("Forge: {forge_alias}\nRepo : {repo_alias}"),
|
.join("\n\n"),
|
||||||
format!("{dev_branch}:\n - {dev_sha}\n - {dev_message}"),
|
|
||||||
format!("{main_branch}:\n - {main_sha}\n - {main_message}"),
|
|
||||||
]
|
|
||||||
.join("\n\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//
|
//
|
||||||
use actix::prelude::*;
|
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 tracing::debug;
|
||||||
|
|
||||||
use crate::repo::{
|
use crate::repo::{
|
||||||
|
@ -40,6 +40,7 @@ impl Handler<ReceiveCIStatus> for RepoActor {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
repo_alias,
|
repo_alias,
|
||||||
commit: next,
|
commit: next,
|
||||||
|
log: graph::log(&self.repo_details),
|
||||||
},
|
},
|
||||||
log.as_ref(),
|
log.as_ref(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,6 +14,7 @@ impl NotifyUser {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
repo_alias,
|
repo_alias,
|
||||||
commit,
|
commit,
|
||||||
|
log,
|
||||||
} => json!({
|
} => json!({
|
||||||
"type": "cicheck.failed",
|
"type": "cicheck.failed",
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
@ -23,7 +24,8 @@ impl NotifyUser {
|
||||||
"commit": {
|
"commit": {
|
||||||
"sha": commit.sha(),
|
"sha": commit.sha(),
|
||||||
"message": commit.message()
|
"message": commit.message()
|
||||||
}
|
},
|
||||||
|
"log": **log
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
UserNotification::RepoConfigLoadFailure {
|
UserNotification::RepoConfigLoadFailure {
|
||||||
|
@ -59,6 +61,7 @@ impl NotifyUser {
|
||||||
main_branch,
|
main_branch,
|
||||||
dev_commit,
|
dev_commit,
|
||||||
main_commit,
|
main_commit,
|
||||||
|
log,
|
||||||
} => json!({
|
} => json!({
|
||||||
"type": "branch.dev.not-on-main",
|
"type": "branch.dev.not-on-main",
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
@ -78,7 +81,8 @@ impl NotifyUser {
|
||||||
"sha": main_commit.sha(),
|
"sha": main_commit.sha(),
|
||||||
"message": main_commit.message()
|
"message": main_commit.message()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"log": **log
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,9 @@ serde_json = { workspace = true }
|
||||||
|
|
||||||
mockall = { workspace = true }
|
mockall = { workspace = true }
|
||||||
|
|
||||||
|
#iters
|
||||||
|
take-until = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Testing
|
# Testing
|
||||||
assert2 = { workspace = true }
|
assert2 = { workspace = true }
|
||||||
|
|
3
crates/core/src/config/graphs.rs
Normal file
3
crates/core/src/config/graphs.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
use crate::newtype;
|
||||||
|
|
||||||
|
newtype!(LogGraph, Vec<String>, "Git log showing branch positions");
|
|
@ -7,6 +7,7 @@ mod forge_config;
|
||||||
mod forge_details;
|
mod forge_details;
|
||||||
mod forge_type;
|
mod forge_type;
|
||||||
pub mod git_dir;
|
pub mod git_dir;
|
||||||
|
mod graphs;
|
||||||
mod host_name;
|
mod host_name;
|
||||||
mod registered_webhook;
|
mod registered_webhook;
|
||||||
mod remote_url;
|
mod remote_url;
|
||||||
|
|
69
crates/core/src/git/graph.rs
Normal file
69
crates/core/src/git/graph.rs
Normal 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()
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ pub mod forge;
|
||||||
mod generation;
|
mod generation;
|
||||||
mod git_ref;
|
mod git_ref;
|
||||||
mod git_remote;
|
mod git_remote;
|
||||||
|
pub mod graph;
|
||||||
pub mod push;
|
pub mod push;
|
||||||
mod repo_details;
|
mod repo_details;
|
||||||
pub mod repository;
|
pub mod repository;
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
use crate::{git::Commit, BranchName, ForgeAlias, RepoAlias};
|
use crate::{git::Commit, BranchName, ForgeAlias, RepoAlias};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use super::graph::Log;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum UserNotification {
|
pub enum UserNotification {
|
||||||
CICheckFailed {
|
CICheckFailed {
|
||||||
forge_alias: ForgeAlias,
|
forge_alias: ForgeAlias,
|
||||||
repo_alias: RepoAlias,
|
repo_alias: RepoAlias,
|
||||||
commit: Commit,
|
commit: Commit,
|
||||||
|
log: Log,
|
||||||
},
|
},
|
||||||
RepoConfigLoadFailure {
|
RepoConfigLoadFailure {
|
||||||
forge_alias: ForgeAlias,
|
forge_alias: ForgeAlias,
|
||||||
|
@ -26,6 +29,7 @@ pub enum UserNotification {
|
||||||
main_branch: BranchName,
|
main_branch: BranchName,
|
||||||
dev_commit: Commit,
|
dev_commit: Commit,
|
||||||
main_commit: Commit,
|
main_commit: Commit,
|
||||||
|
log: Log,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
impl UserNotification {
|
impl UserNotification {
|
||||||
|
@ -37,6 +41,7 @@ impl UserNotification {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
repo_alias,
|
repo_alias,
|
||||||
commit,
|
commit,
|
||||||
|
log,
|
||||||
} => json!({
|
} => json!({
|
||||||
"type": "cicheck.failed",
|
"type": "cicheck.failed",
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
@ -46,7 +51,8 @@ impl UserNotification {
|
||||||
"commit": {
|
"commit": {
|
||||||
"sha": commit.sha(),
|
"sha": commit.sha(),
|
||||||
"message": commit.message()
|
"message": commit.message()
|
||||||
}
|
},
|
||||||
|
"log": **log
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Self::RepoConfigLoadFailure {
|
Self::RepoConfigLoadFailure {
|
||||||
|
@ -59,7 +65,7 @@ impl UserNotification {
|
||||||
"data": {
|
"data": {
|
||||||
"forge_alias": forge_alias,
|
"forge_alias": forge_alias,
|
||||||
"repo_alias": repo_alias,
|
"repo_alias": repo_alias,
|
||||||
"reason": reason
|
"reason": reason,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Self::WebhookRegistration {
|
Self::WebhookRegistration {
|
||||||
|
@ -72,7 +78,7 @@ impl UserNotification {
|
||||||
"data": {
|
"data": {
|
||||||
"forge_alias": forge_alias,
|
"forge_alias": forge_alias,
|
||||||
"repo_alias": repo_alias,
|
"repo_alias": repo_alias,
|
||||||
"reason": reason
|
"reason": reason,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Self::DevNotBasedOnMain {
|
Self::DevNotBasedOnMain {
|
||||||
|
@ -82,6 +88,7 @@ impl UserNotification {
|
||||||
main_branch,
|
main_branch,
|
||||||
dev_commit,
|
dev_commit,
|
||||||
main_commit,
|
main_commit,
|
||||||
|
log,
|
||||||
} => json!({
|
} => json!({
|
||||||
"type": "branch.dev.not-on-main",
|
"type": "branch.dev.not-on-main",
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
@ -101,7 +108,8 @@ impl UserNotification {
|
||||||
"sha": main_commit.sha(),
|
"sha": main_commit.sha(),
|
||||||
"message": main_commit.message()
|
"message": main_commit.message()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"log": **log
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
use crate::{
|
use crate::{
|
||||||
git::{self, repository::open::OpenRepositoryLike, RepoDetails, UserNotification},
|
git::{self, graph::log, repository::open::OpenRepositoryLike, RepoDetails, UserNotification},
|
||||||
BranchName, RepoConfig,
|
BranchName, RepoConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ pub fn validate(
|
||||||
main_branch,
|
main_branch,
|
||||||
dev_commit: dev,
|
dev_commit: dev,
|
||||||
main_commit: main,
|
main_commit: main,
|
||||||
|
log: log(repo_details),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue