1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use std::{process::{Command, Output}, error::Error, time::{SystemTime, SystemTimeError}, path::Path};

use config::Config;

pub mod config;
mod file_watcher;

/// To run the earthquake procedure
pub fn earthquake_procedure(config: Config) -> Result<(), Box<dyn Error>> {
    let current_branch = current_branch()?;
    let user_email = user_email()?;
    let elapsed = current_unix_epoch()?;
    let branch_name = format!("earthquake/{current_branch}-{user_email}-{elapsed}");

    checkout(&branch_name)?;
    if any_uncommited_changes()? {
        add()?;
        commit(&config.commit_message)?;
    }
    push(&branch_name)?;

    Ok(())
}

fn any_uncommited_changes() -> Result<bool, Box<dyn Error>> {
    let output = Command::new("git").args(&["status", "--porcelain"]).output()?;
    let output = String::from_utf8(output.stdout)?;
    Ok(!output.is_empty())
}

fn current_branch() -> Result<String, Box<dyn Error>> {
    let output = Command::new("git").args(&["branch", "--show-current"]).output()?;
    read_git_info(output)
}

fn user_email() -> Result<String, Box<dyn Error>> {
    let output = Command::new("git").args(&["config", "--get", "user.email"]).output()?;
    read_git_info(output)
}

fn current_unix_epoch() -> Result<u64, SystemTimeError> {
    let current_unix_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
    Ok(current_unix_epoch.as_secs())
}

fn checkout(branch_name: &str) -> Result<(), Box<dyn Error>> {
    execute_git_command(&["checkout", "-b", branch_name])
}

fn add() -> Result<(), Box<dyn Error>> {
    execute_git_command(&["add", "--all"])
}

fn commit(message: &str) -> Result<(), Box<dyn Error>> {
    execute_git_command(&["commit", "-m", message])
}

fn push(branch_name: &str) -> Result<(), Box<dyn Error>> {
    execute_git_command(&["push", "-u", "origin", branch_name])
}

fn execute_git_command(args: &[&str]) -> Result<(), Box<dyn Error>> {
    wait_git_lock_released()?;
    let mut child = Command::new("git").args(args).spawn()?;
    child.wait()?;
    Ok(())
}

fn wait_git_lock_released() -> Result<(), Box<dyn Error>> {
    let output = Command::new("git").args(&["rev-parse", "--show-toplevel"]).output()?;
    let git_root_directory = read_git_info(output)?;
    let lock_path = Path::new(&git_root_directory).join(".git/index.lock");

    if lock_path.exists() {
        file_watcher::wait_until_deleted(&lock_path);
    }
    Ok(())
}

fn read_git_info(output: Output) -> Result<String, Box<dyn Error>> {
    let mut info = String::from_utf8(output.stdout)?;
    trim_newline(&mut info);
    Ok(info)
}

fn trim_newline(s: &mut String) {
    if s.ends_with('\n') {
        s.pop();
        if s.ends_with('\r') {
            s.pop();
        }
    }
}