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}