Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 162 additions & 61 deletions WebMoMMI/src/github/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fs::{read_dir, File};
use std::fs::{create_dir_all, read_dir, File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::process::Command;
Expand All @@ -16,12 +16,57 @@ use std::sync::{Mutex, MutexGuard};
use std::thread;
use std::time::{Duration, Instant};

const LOG_DIR: &str = "logs";
const LOG_FILE: &str = "logs/changelog.log";

fn get_timestamp() -> String {
Command::new("date")
.arg("+%Y-%m-%d %H:%M:%S")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|_| "???".to_string())
}

fn log_to_file(level: &str, message: &str) {
let timestamp = get_timestamp();
let line = format!("{} [{}] {}\n", timestamp, level, message);

match level {
"ERROR" | "WARNING" => eprint!("{}", line),
_ => print!("{}", line),
}

let _ = create_dir_all(LOG_DIR);
if let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open(LOG_FILE)
{
let _ = file.write_all(line.as_bytes());
}
}

fn log_info(message: &str) {
log_to_file("INFO", message);
}

fn log_warning(message: &str) {
log_to_file("WARNING", message);
}

fn log_error(message: &str) {
log_to_file("ERROR", message);
}

pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfig>) {
log_info(&format!("Received PR event: action={:?}, merged={}, PR#{}",
event.action, event.pull_request.merged, event.number));

if event.action != PullRequestAction::Closed
|| !event.pull_request.merged
|| !config.has_changelog_repo_path()
{
// Not a merge
log_info("PR not a merge or no changelog repo configured, skipping");
return;
}

Expand All @@ -34,9 +79,12 @@ pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfi
let additions = parse_body_changelog(&event.pull_request.body);

if additions.len() == 0 {
log_info("No changelogs found in PR body");
return;
}

log_info(&format!("Found {} changelogs in PR#{}", additions.len(), event.number));

let changelog = Changelog {
author: event.pull_request.user.login.clone(),
changes: additions,
Expand All @@ -47,8 +95,8 @@ pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfi
changelog_path.push(&format!("html/changelogs/PR-{}-temp.yml", event.number));

match write_temp_changelog(&changelog_path, changelog) {
Err(e) => eprintln!("Error writing changelog temp file: {:?}", e),
_ => {}
Err(e) => log_error(&format!("Error writing changelog temp file: {:?}", e)),
_ => log_info(&format!("Wrote temp changelog for PR-{}", event.number)),
};

process_changelogs(config);
Expand All @@ -70,7 +118,7 @@ pub fn try_handle_changelog_push(event: &PushEvent, config: &Arc<MoMMIConfig>) {
.iter()
.flat_map(|c| c.added.iter().chain(c.modified.iter()))
{
println!("{}", filename);
log_info(&format!("Push event file: {}", filename));
if IS_CHANGELOG_RE.is_match(filename) {
process_changelogs(config);
return;
Expand Down Expand Up @@ -206,12 +254,14 @@ pub enum ChangelogEntryType {
}

pub fn process_changelogs(config: &Arc<MoMMIConfig>) {
log_info("process_changelogs called");
let mut lock = CHANGELOG_MANAGER.lock().unwrap();
let should_spawn_thread = lock.last_time.is_none();
lock.last_time = Some(Instant::now());

if should_spawn_thread {
// Nobody currently processing.
log_info("Spawning new changelog thread");
lock.last_time = Some(Instant::now());
let config = config.clone();
thread::Builder::new()
Expand All @@ -220,32 +270,40 @@ pub fn process_changelogs(config: &Arc<MoMMIConfig>) {
handle_changelog_thread(config);
})
.unwrap();
} else {
log_info("Changelog thread already running, updated last_time");
}
}

fn handle_changelog_thread(config: Arc<MoMMIConfig>) {
let delay = config.get_changelog_delay();
log_info(&format!("Changelog thread started, delay={}s", delay));

loop {
let time = {
let lock = CHANGELOG_MANAGER.lock().unwrap();
let elapsed = lock.last_time.as_ref().unwrap().elapsed();
if elapsed.as_secs() > delay {
log_info("Delay elapsed, starting changelog processing");
return do_changelog(lock, config);
}

match Duration::from_secs(delay).checked_sub(elapsed) {
Some(t) => t,
None => return do_changelog(lock, config),
None => {
log_info("Delay elapsed, starting changelog processing");
return do_changelog(lock, config);
}
}
};
log_info(&format!("Waiting {:?} before processing", time));
thread::sleep(time);
}
}

// Pass the lock directly so we don't risk race conditions.
fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>) {
println!("Running changelogs!");
log_info("Running changelogs!");
// Get what we need and drop the lock.
// so we don't hang everything for the time it takes for the git commands and stuff.
lock.last_time = None;
Expand All @@ -256,19 +314,39 @@ fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>
.get_ssh_key()
.map(|p| format!("ssh -i {}", p.to_string_lossy()));

// Git pull the repo.
let checkout_status = Command::new("git")
.arg("checkout")
.arg("Bleeding-Edge")
.current_dir(&path)
.status();

if let Ok(s) = checkout_status {
if !s.success() {
log_error(&format!("git checkout failed: {:?}", s));
return;
}
}

// Git pull the repo, resolve conflicts using remote version
let mut command = Command::new("git");
command
.arg("pull")
.arg("origin")
.arg("--rebase")
.arg("origin")
.arg("Bleeding-Edge")
.arg("-X")
.arg("theirs")
.current_dir(&path);
if let Some(ref ssh_command) = ssh_config {
command.env("GIT_SSH_COMMAND", &ssh_command);
}
let status = command.status().unwrap();

assert!(status.success());
if !status.success() {
log_error(&format!("Pull failed: {:?}", status));
return;
}
log_info("git pull successful");

let mut changelog_dir_path = path.to_owned();
changelog_dir_path.push("html/changelogs");
Expand All @@ -286,16 +364,32 @@ fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>
continue;
}

println!("{}", file_name);
log_info(&format!("Processing changelog file: {}", file_name));

let file = File::open(entry.path()).unwrap();
let data: Changelog = serde_yaml::from_reader(&file).unwrap();
let file = match File::open(entry.path()) {
Ok(f) => f,
Err(e) => {
log_error(&format!("Failed to open {}: {:?}", file_name, e));
continue;
}
};
let data: Changelog = match serde_yaml::from_reader(&file) {
Ok(d) => d,
Err(e) => {
log_error(&format!("Failed to parse {}: {:?}", file_name, e));
continue;
}
};

if data.changes.len() == 0 {
log_warning(&format!("Changelog {} has no changes, skipping", file_name));
continue;
}

commloop(addr, pass, "changelog", "", data).unwrap();
match commloop(addr, pass, "changelog", "", &data) {
Ok(_) => log_info(&format!("Changelog for {} sent to commloop", file_name)),
Err(e) => log_error(&format!("Failed sending changelog for {}: {:?}", file_name, e)),
}
}
}

Expand All @@ -305,63 +399,70 @@ fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>
.arg("html/changelog.html")
.arg("html/changelogs")
.current_dir(&path)
.status()
.unwrap();
.status();

assert!(status.success());
match status {
Ok(s) if s.success() => log_info("Changelog script successful"),
Ok(s) => log_error(&format!("Changelog script failed: {:?}", s)),
Err(e) => log_error(&format!("Changelog script failed badly: {:?}", e)),
}

Command::new("git")
.arg("update-index")
.arg("--refresh")
.current_dir(&path)
.status()
.unwrap();

// See if repo is dirty.
let status = Command::new("git")
.arg("diff-index")
.arg("--exit-code")
.arg("HEAD")
.current_dir(&path)
.status()
.unwrap();

if status.code().unwrap_or(0) == 0 {
// No changes, nothing to commit.
return;
}
// Job below is handled by a github action now

let status = Command::new("git")
.arg("add")
.arg(".")
.arg("-A")
.current_dir(&path)
.status()
.unwrap();

assert!(status.success());
// Command::new("git")
// .arg("update-index")
// .arg("--refresh")
// .current_dir(&path)
// .status()
// .unwrap();

let status = Command::new("git")
.arg("commit")
.arg("-m")
.arg("[ci skip] Automatic changelog update.")
.current_dir(&path)
.status()
.unwrap();
// // See if repo is dirty.
// let status = Command::new("git")
// .arg("diff-index")
// .arg("--exit-code")
// .arg("HEAD")
// .current_dir(&path)
// .status()
// .unwrap();

assert!(status.success());
// if status.code().unwrap_or(0) == 0 {
// // No changes, nothing to commit.
// return;
// }

// Git push the repo.
let mut command = Command::new("git");
command.arg("push").arg("origin").current_dir(&path);
if let Some(ref ssh_command) = ssh_config {
command.env("GIT_SSH_COMMAND", &ssh_command);
}
let status = command.status().unwrap();
// let status = Command::new("git")
// .arg("add")
// .arg(".")
// .arg("-A")
// .current_dir(&path)
// .status()
// .unwrap();

assert!(status.success());
// assert!(status.success());

println!("done");
// let status = Command::new("git")
// .arg("commit")
// .arg("-m")
// .arg("[ci skip] Automatic changelog update.")
// .current_dir(&path)
// .status()
// .unwrap();

// assert!(status.success());

// Git push the repo.
//let mut command = Command::new("git");
//command.arg("push").arg("origin").current_dir(&path);
//if let Some(ref ssh_command) = ssh_config {
// command.env("GIT_SSH_COMMAND", &ssh_command);
//}
//let status = command.status().unwrap();
//
//assert!(status.success());

log_info("Changelog processing complete");
}

#[cfg(test)]
Expand Down
5 changes: 4 additions & 1 deletion config/example/main.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ password = "secret" #COMMLOOP PASSWORD
type="gamenudge"
"server_status" = [[692441846739238952, 1160976587844501586]] #server id, channel id
"ick" = [[692441846739238952, 1160976569435688970]]
"adminhelp" = [[692441846739238952, 1160976627078017175]]
"adminhelp" = [[692441846739238952, 1160976627078017175]]

[commloop.route.changelog]
"" = [[SERVER_ID, CHANGELOG_CHANNEL_ID]] #leading quotes are empty