git_pusher/
lib.rs

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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();
}