use config::{Config, ConfigError, File, FileFormat};
use git2::{Cred, Repository};
use simple_home_dir::*;
use std::{env, fs};
const CONFIG_PATH: &str = "/.config/git-pusher/";
const CONFIG_FILE: &str = "config.yaml";
fn get_homedir() -> String {
home_dir().unwrap().into_os_string().into_string().unwrap()
}
fn get_config_full_path() -> String {
let home_dir_string = get_homedir();
home_dir_string + CONFIG_PATH + CONFIG_FILE
}
fn is_config_file_exist() -> bool {
let config_full_path_string = get_config_full_path();
std::path::Path::new(&config_full_path_string[..]).exists()
}
pub fn load_config() -> Result<Config, ConfigError> {
if !is_config_file_exist() {
create_default_config_file();
}
let config_full_path_string = get_config_full_path();
let config_full_path = &config_full_path_string[..];
let mut builder = Config::builder();
builder = builder.set_default("commit_msg_template", "updated <files>")?;
builder = builder.set_default("cron", "* * 0 * * * *")?;
builder = builder.set_default("commit_author_name", "Git Pusher")?;
builder = builder.set_default("commit_author_email", "git@pusher.com")?;
builder = builder.set_default("repos", Vec::<String>::new())?;
builder = builder.add_source(File::new(config_full_path, FileFormat::Yaml));
builder.build()
}
fn create_default_config_file() {
let config_dir_str = get_homedir() + CONFIG_PATH;
let config_file_str = config_dir_str.clone() + CONFIG_FILE;
fs::create_dir_all(&config_dir_str[..]).unwrap();
fs::File::create_new(&config_file_str[..]).unwrap();
}
fn get_repos(config: &Config) -> Vec<String> {
let repos = config
.get_array("repos")
.expect("need repos to watch for changes");
let repo_strings: Vec<String> = repos.iter().map(|c| c.kind.clone().to_string()).collect();
repo_strings
}
fn get_commit_author_name(config: &Config) -> String {
config.get_string("commit_author_name").unwrap()
}
fn get_commit_author_email(config: &Config) -> String {
config.get_string("commit_author_email").unwrap()
}
pub fn get_cron(config: &Config) -> String {
config.get_string("cron").unwrap()
}
fn is_repo_updated(repo: &Repository) -> bool {
repo.statuses(None)
.unwrap()
.iter()
.any(|entry| !entry.status().is_ignored() && !entry.status().is_empty())
}
pub fn autopush_repos(config: &Config) {
let repos = get_repos(config);
for repo in repos {
let git_repo =
Repository::open(repo).expect("Failed to open repo. Check if the path is correct");
if is_repo_updated(&git_repo) {
continue;
}
commit_changes(config, &git_repo);
push_to_remote(&git_repo);
}
}
fn push_to_remote(git_repo: &Repository) {
let mut remote = git_repo.find_remote("origin").unwrap();
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(|_url, username_from_url, _allowed_types| {
Cred::ssh_key(
username_from_url.unwrap(),
None,
std::path::Path::new(&format!("{}/.ssh/id_ed25519", env::var("HOME").unwrap())),
None,
)
});
let mut push_options = git2::PushOptions::new();
push_options.remote_callbacks(callbacks);
let head = git_repo.head().unwrap();
let branch_name = head.shorthand().unwrap();
let refspecs = format!("refs/heads/{}:refs/heads/{}", branch_name, branch_name);
remote.push(&[refspecs], Some(&mut push_options)).unwrap();
}
fn commit_changes(config: &Config, git_repo: &Repository) {
let mut index = git_repo.index().unwrap();
index
.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)
.unwrap();
index.write().unwrap();
let oid = index.write_tree().unwrap();
let author = get_commit_author_name(config);
let email = get_commit_author_email(config);
let signature = git2::Signature::now(author.as_str(), email.as_str()).unwrap();
let parent_commit = git_repo.head().unwrap().peel_to_commit().unwrap();
let tree = git_repo.find_tree(oid).unwrap();
git_repo
.commit(
Some("HEAD"),
&signature,
&signature,
"Automated commit",
&tree,
&[&parent_commit],
)
.unwrap();
}