git_pusher/
lib.rs

1use config::{Config, ConfigError, File, FileFormat};
2use git2::{Cred, Repository};
3use simple_home_dir::*;
4use std::{env, fs};
5
6const CONFIG_PATH: &str = "/.config/git-pusher/";
7const CONFIG_FILE: &str = "config.yaml";
8
9fn get_homedir() -> String {
10    home_dir().unwrap().into_os_string().into_string().unwrap()
11}
12
13fn get_config_full_path() -> String {
14    let home_dir_string = get_homedir();
15    home_dir_string + CONFIG_PATH + CONFIG_FILE
16}
17
18fn is_config_file_exist() -> bool {
19    let config_full_path_string = get_config_full_path();
20    std::path::Path::new(&config_full_path_string[..]).exists()
21}
22
23pub fn load_config() -> Result<Config, ConfigError> {
24    if !is_config_file_exist() {
25        create_default_config_file();
26    }
27    let config_full_path_string = get_config_full_path();
28    let config_full_path = &config_full_path_string[..];
29    let mut builder = Config::builder();
30    builder = builder.set_default("commit_msg_template", "updated <files>")?;
31    builder = builder.set_default("cron", "* * 0 * * * *")?;
32    builder = builder.set_default("commit_author_name", "Git Pusher")?;
33    builder = builder.set_default("commit_author_email", "git@pusher.com")?;
34    builder = builder.set_default("repos", Vec::<String>::new())?;
35    builder = builder.add_source(File::new(config_full_path, FileFormat::Yaml));
36    builder.build()
37}
38
39fn create_default_config_file() {
40    let config_dir_str = get_homedir() + CONFIG_PATH;
41    let config_file_str = config_dir_str.clone() + CONFIG_FILE;
42    fs::create_dir_all(&config_dir_str[..]).unwrap();
43    fs::File::create_new(&config_file_str[..]).unwrap();
44}
45
46fn get_repos(config: &Config) -> Vec<String> {
47    let repos = config
48        .get_array("repos")
49        .expect("need repos to watch for changes");
50    let repo_strings: Vec<String> = repos.iter().map(|c| c.kind.clone().to_string()).collect();
51    repo_strings
52}
53
54fn get_commit_author_name(config: &Config) -> String {
55    config.get_string("commit_author_name").unwrap()
56}
57
58fn get_commit_author_email(config: &Config) -> String {
59    config.get_string("commit_author_email").unwrap()
60}
61
62pub fn get_cron(config: &Config) -> String {
63    config.get_string("cron").unwrap()
64}
65
66fn is_repo_updated(repo: &Repository) -> bool {
67    repo.statuses(None)
68        .unwrap()
69        .iter()
70        .any(|entry| !entry.status().is_ignored() || !entry.status().is_empty())
71}
72
73pub fn autopush_repos(config: &Config) {
74    let repos = get_repos(config);
75    for repo in repos {
76        let git_repo =
77            Repository::open(repo).expect("Failed to open repo. Check if the path is correct");
78        if !is_repo_updated(&git_repo) {
79            continue;
80        }
81        commit_changes(config, &git_repo);
82        push_to_remote(&git_repo);
83    }
84}
85
86fn push_to_remote(git_repo: &Repository) {
87    let mut remote = git_repo.find_remote("origin").unwrap();
88    let mut callbacks = git2::RemoteCallbacks::new();
89    callbacks.credentials(|_url, username_from_url, _allowed_types| {
90        Cred::ssh_key(
91            username_from_url.unwrap(),
92            None,
93            std::path::Path::new(&format!("{}/.ssh/id_ed25519", env::var("HOME").unwrap())),
94            None,
95        )
96    });
97    let mut push_options = git2::PushOptions::new();
98    push_options.remote_callbacks(callbacks);
99    let head = git_repo.head().unwrap();
100    let branch_name = head.shorthand().unwrap();
101    let refspecs = format!("refs/heads/{}:refs/heads/{}", branch_name, branch_name);
102    remote.push(&[refspecs], Some(&mut push_options)).unwrap();
103}
104
105fn commit_changes(config: &Config, git_repo: &Repository) {
106    let mut index = git_repo.index().unwrap();
107    index
108        .add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)
109        .unwrap();
110    index.write().unwrap();
111    let oid = index.write_tree().unwrap();
112    let author = get_commit_author_name(config);
113    let email = get_commit_author_email(config);
114    let signature = git2::Signature::now(author.as_str(), email.as_str()).unwrap();
115    let parent_commit = git_repo.head().unwrap().peel_to_commit().unwrap();
116    let tree = git_repo.find_tree(oid).unwrap();
117    git_repo
118        .commit(
119            Some("HEAD"),
120            &signature,
121            &signature,
122            "Automated commit",
123            &tree,
124            &[&parent_commit],
125        )
126        .unwrap();
127}