aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/git_tools.rs106
-rw-r--r--src/main.rs91
2 files changed, 197 insertions, 0 deletions
diff --git a/src/git_tools.rs b/src/git_tools.rs
new file mode 100644
index 0000000..1c361ca
--- /dev/null
+++ b/src/git_tools.rs
@@ -0,0 +1,106 @@
+use std::io::{Result, Error, ErrorKind};
+use std::process::Command;
+use std::path::Path;
+
+pub fn git_save_previous_branch(path: &Path) -> Result<String> {
+ let output = Command::new("git")
+ .arg("rev-parse")
+ .arg("--abbrev-ref")
+ .arg("HEAD")
+ .current_dir(path)
+ .output()?;
+ if !output.status.success() {
+ Err(Error::new(ErrorKind::Other, "Failed to get current branch"))
+ } else {
+ Ok(String::from_utf8(output.stdout).unwrap().trim().to_string())
+ }
+}
+
+pub fn git_checkout(path: &Path, branch: &str) -> Result<()> {
+ let output = Command::new("git")
+ .arg("checkout")
+ .arg(branch)
+ .current_dir(path)
+ .output()?;
+ if !output.status.success() {
+ Err(Error::new(ErrorKind::Other, format!("Failed to checkout {}", branch)))
+ } else {
+ Ok(())
+ }
+}
+
+pub fn git_fetch(path: &Path) -> Result<()> {
+ let output = Command::new("git")
+ .arg("fetch")
+ .current_dir(path)
+ .output()?;
+ if !output.status.success() {
+ return Err(Error::new(ErrorKind::Other, "Failed to fetch"));
+ }
+ Ok(())
+}
+
+pub fn git_check_for_conflicts(path: &Path) -> Result<bool> {
+ let merge_output = Command::new("git")
+ .arg("merge")
+ .arg("--no-commit")
+ .arg("--no-ff")
+ .arg("origin/main")
+ .current_dir(path)
+ .output()?;
+ if !merge_output.status.success() {
+ return Err(Error::new(ErrorKind::Other, "Failed to merge"));
+ }
+ let conflicts_output = Command::new("git")
+ .arg("ls-files")
+ .arg("--unmerged")
+ .current_dir(path)
+ .output()?;
+ Command::new("git")
+ .arg("merge")
+ .arg("--abort")
+ .current_dir(path)
+ .output()?;
+ Ok(!conflicts_output.stdout.is_empty())
+}
+
+pub fn git_merge(path: &Path) -> Result<()> {
+ let output = Command::new("git")
+ .arg("merge")
+ .arg("origin/main")
+ .current_dir(path)
+ .output()?;
+ if !output.status.success() {
+ Err(Error::new(ErrorKind::Other, "Failed to merge"))
+ } else {
+ Ok(())
+ }
+}
+
+pub fn git_push(path: &Path) -> Result<()> {
+ let output = Command::new("git")
+ .arg("push")
+ .arg("origin")
+ .arg("main")
+ .current_dir(path)
+ .output()?;
+ if !output.status.success() {
+ Err(Error::new(ErrorKind::Other, "Failed to push to origin"))
+ } else {
+ Ok(())
+ }
+}
+
+pub fn get_commit(path: &Path, revision: &str) -> Result<String> {
+ let output = Command::new("git")
+ .arg("rev-parse")
+ .arg("--short")
+ .arg(revision)
+ .current_dir(path)
+ .output()?;
+ if !output.status.success() {
+ Err(Error::new(ErrorKind::Other, "Failed to get current commit"))
+ } else {
+ Ok(String::from_utf8(output.stdout).unwrap().trim().to_string())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..3b9caea
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,91 @@
+mod git_tools;
+
+use std::env;
+use std::fs;
+use std::io::{Result, Error, ErrorKind};
+use std::path::Path;
+use std::process::exit;
+
+use git_tools::*;
+
+fn main() -> Result<()> {
+ if let Ok(cwd) = env::current_dir() {
+ if let Err(e) = scan_directories_to_synchronize(&cwd) {
+ eprintln!("Could not check directory for git repositories: {}", e);
+ exit(1);
+ }
+ } else {
+ eprintln!("Could not find current directory.");
+ exit(1);
+ }
+ Ok(())
+}
+
+fn scan_directories_to_synchronize(path: &Path) -> Result<()> {
+ let sync_ignore_file = path.join(".syncignore");
+ if sync_ignore_file.exists() {
+ return Ok(())
+ }
+ let git_directory = path.join(".git");
+ if git_directory.exists() {
+ if let Err(e) = synchronize_directory(&path) {
+ let relative_path = get_relative_path(path).unwrap();
+ eprintln!("Sync failed for {:?}. {}", relative_path, e);
+ }
+ } else {
+ for entry in fs::read_dir(path)? {
+ let path = entry?.path();
+ if path.is_dir() {
+ scan_directories_to_synchronize(&path)?;
+ }
+ }
+ }
+ Ok(())
+}
+
+fn synchronize_directory(path: &Path) -> Result<()> {
+ let previous_branch = git_save_previous_branch(path)?;
+ git_checkout(path, "main")?;
+ let result = attempt_synchronization(path);
+ git_checkout(path, &previous_branch)?;
+ result
+}
+
+fn attempt_synchronization(path: &Path) -> Result<()> {
+ let relative_path = get_relative_path(path).unwrap();
+ eprint!("{:?}. ", relative_path);
+ let previous_commit = get_commit(path, "HEAD")?;
+ git_fetch(path)?;
+ let remote_commit = get_commit(path, "origin/main")?;
+ if previous_commit == remote_commit {
+ eprintln!("Already up to date.");
+ return Ok(());
+ }
+ match git_check_for_conflicts(path) {
+ Ok(conflicts) => {
+ if !conflicts {
+ git_merge(path)?;
+ let new_commit = get_commit(path, "HEAD")?;
+ eprint!("Merged origin {:?}->{:?}. ", previous_commit, new_commit);
+ git_push(path)?;
+ eprintln!("Pushed.");
+ } else {
+ return Err(Error::new(ErrorKind::Other, "Conflicts detected"));
+ }
+ },
+ Err(e) => {
+ return Err(e);
+ }
+ }
+ Ok(())
+}
+
+// Path Helpers
+
+fn get_relative_path(path: &Path) -> Option<String> {
+ let cwd = env::current_dir().unwrap();
+ path.strip_prefix(cwd)
+ .ok()
+ .and_then(|p| p.to_str())
+ .map(|s| s.to_string())
+}