1use std::{
2 path::Path,
3 process::{Command, Output},
4 time::{SystemTime, SystemTimeError},
5};
6
7use anyhow::Result;
8use config::Config;
9
10pub mod config;
11mod file_watcher;
12
13pub fn earthquake_procedure(config: Config) -> Result<()> {
15 let current_branch = current_branch()?;
16 let remote = remote(¤t_branch)?;
17 let user_email = user_email()?;
18 let elapsed = current_unix_epoch()?;
19 let branch_name = format!("earthquake/{current_branch}-{user_email}-{elapsed}");
20
21 checkout(&branch_name)?;
22 if any_uncommited_changes()? {
23 add()?;
24 commit(&config.commit_message)?;
25 }
26 push(&branch_name, &remote)?;
27
28 Ok(())
29}
30
31fn any_uncommited_changes() -> Result<bool> {
32 let output = output_git_command(&["status", "--porcelain"])?;
33 Ok(!output.is_empty())
34}
35
36fn current_branch() -> Result<String> {
37 output_git_command(&["branch", "--show-current"])
38}
39
40fn user_email() -> Result<String> {
41 let output = output_git_command(&["config", "--get", "user.email"])?;
42 if output.is_empty() {
43 return Err(anyhow::Error::msg("No user email is configured"));
44 }
45 Ok(output)
46}
47
48fn remote(branch_name: &str) -> Result<String> {
49 let config_path = format!("branch.{branch_name}.remote");
50 let output = output_git_command(&["config", "--get", &config_path])?;
51 if output.is_empty() {
52 return Err(anyhow::Error::msg("No remote branch is configured"));
53 }
54 Ok(output)
55}
56
57fn current_unix_epoch() -> Result<u64, SystemTimeError> {
58 let current_unix_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
59 Ok(current_unix_epoch.as_secs())
60}
61
62fn checkout(branch_name: &str) -> Result<()> {
63 spawn_git_command(&["checkout", "-b", branch_name])
64}
65
66fn add() -> Result<()> {
67 spawn_git_command(&["add", "--all"])
69}
70
71fn commit(message: &str) -> Result<()> {
72 spawn_git_command(&["commit", "--no-gpg-sign", "--no-verify", "-m", message])
74}
75
76fn push(branch_name: &str, remote: &str) -> Result<()> {
77 spawn_git_command(&["push", "-u", remote, branch_name])
78}
79
80fn spawn_git_command(args: &[&str]) -> Result<()> {
81 wait_git_lock_released()?;
82 let mut child = Command::new("git").args(args).spawn()?;
83 child.wait()?;
84 Ok(())
85}
86
87fn output_git_command(args: &[&str]) -> Result<String> {
88 let output = Command::new("git").args(args).output()?;
89 read_git_info(output)
90}
91
92fn wait_git_lock_released() -> Result<()> {
93 let git_root_directory = output_git_command(&["rev-parse", "--show-toplevel"])?;
94 let lock_path = Path::new(&git_root_directory).join(".git/index.lock");
95
96 if lock_path.exists() {
97 file_watcher::wait_until_deleted(&lock_path);
98 }
99 Ok(())
100}
101
102fn read_git_info(output: Output) -> Result<String> {
103 let info = String::from_utf8(output.stdout)?;
104 Ok(info.trim_end().to_string())
105}