aboutsummaryrefslogtreecommitdiff
path: root/hooked
diff options
context:
space:
mode:
Diffstat (limited to 'hooked')
-rw-r--r--hooked/src/main.rs181
-rw-r--r--hooked/tests/init.rs66
2 files changed, 211 insertions, 36 deletions
diff --git a/hooked/src/main.rs b/hooked/src/main.rs
index 7e33499..1c8388e 100644
--- a/hooked/src/main.rs
+++ b/hooked/src/main.rs
@@ -1,8 +1,9 @@
//! git hook manager tool
-#[cfg(windows)]
-use anyhow::bail;
-use anyhow::Result;
+use anyhow::{
+ bail,
+ Result,
+};
use log::*;
use shared::find_root;
#[cfg(not(windows))]
@@ -16,6 +17,7 @@ use std::{
env,
fs,
io::Write,
+ path::Path,
};
const HOOKS: [&str; 18] = [
@@ -41,8 +43,21 @@ const HOOKS: [&str; 18] = [
#[derive(structopt::StructOpt)]
enum Args {
- /// Initialize the repo to use ticket
- Init,
+ /// Initialize the repo to use hooked
+ Init(Language),
+ /// Link pre existing hooks to your .git folder
+ Link,
+}
+
+/// Which language the repo should be initialized with for hooks
+#[derive(Clone, Copy, structopt::StructOpt)]
+enum Language {
+ /// Use Bash for your git hooks
+ Bash,
+ /// Use Python 3 for your git hooks
+ Python,
+ /// Use Ruby for your git hooks
+ Ruby,
}
#[paw::main]
@@ -52,60 +67,172 @@ fn main(args: Args) {
.map_or_else(|| env::set_var("RUST_LOG", "info"), drop);
pretty_env_logger::init();
if let Err(e) = match args {
- Args::Init => init(),
+ Args::Init(lang) => init(lang),
+ Args::Link => link(),
} {
error!("{}", e);
std::process::exit(1);
}
}
-fn init() -> Result<()> {
- #[cfg(windows)]
- bail!("Windows is currently unsupported!");
-
+fn init(lang: Language) -> Result<()> {
let root = find_root()?;
let git_hooks = &root.join(".git").join("hooks");
debug!("git_hooks base path: {}", git_hooks.display());
let root = root.join(".dev-suite").join("hooked");
debug!("root base path: {}", root.display());
- fs::create_dir_all(&root)?;
+ let wrapper_dir = &root.join("wrapper");
+ fs::create_dir_all(&wrapper_dir)?;
for hook in &HOOKS {
- let path = &root.join(hook);
+ let mut path = (&root).join(hook);
debug!("dev-suite hook path: {}", path.display());
let git_hook = &git_hooks.join(hook);
debug!("git_hook path: {}", git_hook.display());
+ let mut wrapper_hook = (&wrapper_dir).join(hook);
+ let _ = wrapper_hook.set_extension("sh");
+ let _ = match lang {
+ Language::Bash => path.set_extension("sh"),
+ Language::Python => path.set_extension("py"),
+ Language::Ruby => path.set_extension("rb"),
+ };
if path.exists() {
debug!("git hook {} already exists. Skipping creation.", hook);
} else {
debug!("Creating dev-suite hook.");
let mut file = fs::File::create(&path)?;
+ let mut wrapper = fs::File::create(&wrapper_hook)?;
trace!("File created.");
- let mut perms = file.metadata()?.permissions();
- debug!("Setting dev-suite hook to be executable.");
- perms.set_mode(0o755);
- file.set_permissions(perms)?;
- trace!("Permissions were set.");
- file.write_all(b"#! /bin/bash")?;
+ #[cfg(not(windows))]
+ {
+ let mut perms = file.metadata()?.permissions();
+ let mut wrapper_perms = wrapper.metadata()?.permissions();
+ debug!("Setting dev-suite hook to be executable.");
+ perms.set_mode(0o755);
+ wrapper_perms.set_mode(0o755);
+ file.set_permissions(perms)?;
+ wrapper.set_permissions(wrapper_perms)?;
+ trace!("Permissions were set.");
+ }
+ match lang {
+ Language::Bash => {
+ file.write_all(b"#!/usr/bin/env bash")?;
+ wrapper.write_all(
+ format!(
+ "#!C:\\Program Files\\Git\\bin\\sh.exe\n\
+ bash.exe .dev-suite/hooked/{}.sh\n",
+ hook
+ )
+ .as_bytes(),
+ )?;
+ }
+ Language::Python => {
+ file.write_all(b"#!/usr/bin/env python3")?;
+ wrapper.write_all(
+ format!(
+ "#!C:\\Program Files\\Git\\bin\\sh.exe\n\
+ py.exe .dev-suite/hooked/{}.py\n",
+ hook
+ )
+ .as_bytes(),
+ )?;
+ }
+ Language::Ruby => {
+ file.write_all(b"#!/usr/bin/env ruby")?;
+ wrapper.write_all(
+ format!(
+ "#!C:\\Program Files\\Git\\bin\\sh.exe\n\
+ ruby.exe .dev-suite/hooked/{}.rb\n",
+ hook
+ )
+ .as_bytes(),
+ )?;
+ }
+ }
debug!("Writing data to file.");
debug!("Created git hook {}.", hook);
}
- let path = path.canonicalize()?;
- if !git_hook.exists() {
- debug!("Symlinking git hook {}.", hook);
- #[cfg(not(windows))]
- symlink(&path, &git_hook)?;
- #[cfg(windows)]
- symlink_file(&path, &git_hook)?;
- trace!("Symlinked git hook {} to .dev-suite/hooked/{}.", hook, hook);
- }
+ #[cfg(not(windows))]
+ let link_path = path.canonicalize()?;
+ #[cfg(windows)]
+ let link_path = wrapper_hook.canonicalize()?;
+
+ inner_link(&link_path, &git_hook, hook)?;
}
info!(
"Created and symlinked tickets to .git/hooks from {}.",
root.display()
);
+ #[cfg(windows)]
+ {
+ warn!("Make sure to add the hooks into git with 'git add --chmod=+x .dev-suite\\hooked'");
+ warn!("If you don't they won't be set as executable on unix systems");
+ }
+
+ Ok(())
+}
+
+fn link() -> Result<()> {
+ let root = find_root()?;
+ let git_hooks = &root.join(".git").join("hooks");
+ debug!("git_hooks base path: {}", git_hooks.display());
+ let root = root.join(".dev-suite").join("hooked");
+ debug!("root base path: {}", root.display());
+
+ for hook in &HOOKS {
+ let path = {
+ #[cfg(windows)]
+ let mut path = root.join("wrapper").join(hook);
+ #[cfg(not(windows))]
+ let mut path = root.join(hook);
+ debug!("PATH: {}", path.display());
+ let mut path_python = path.clone();
+ let _ = path_python.set_extension("py");
+ let mut path_ruby = path.clone();
+ let _ = path_ruby.set_extension("rb");
+ let mut path_bash = path.clone();
+ let _ = path_bash.set_extension("sh");
+
+ if path_python.exists() {
+ path_python
+ } else if path_ruby.exists() {
+ path_ruby
+ } else if path_bash.exists() {
+ path_bash
+ } else {
+ let _ = path.set_extension("");
+ bail!(
+ "The path {} does not exist. Have you initialized the repo to use hooked?",
+ path.display()
+ );
+ }
+ };
+ let path = path.canonicalize()?;
+ debug!("dev-suite hook path: {}", path.display());
+ let git_hook = &git_hooks.join(hook);
+ debug!("git_hook path: {}", git_hook.display());
+ inner_link(&path, &git_hook, hook)?;
+ }
+
+ info!("Successfully symlinked all githooks to .git/hooks");
+ Ok(())
+}
+
+fn inner_link(path: &Path, git_hook: &Path, hook: &str) -> Result<()> {
+ if !git_hook.exists() {
+ debug!("Symlinking git hook {}.", hook);
+ #[cfg(not(windows))]
+ symlink(&path, &git_hook)?;
+ #[cfg(windows)]
+ symlink_file(&path, &git_hook)?;
+ debug!(
+ "Symlinked git hook {} to {}",
+ git_hook.display(),
+ path.display()
+ );
+ }
Ok(())
}
diff --git a/hooked/tests/init.rs b/hooked/tests/init.rs
index 65c4aab..9c68ff3 100644
--- a/hooked/tests/init.rs
+++ b/hooked/tests/init.rs
@@ -1,10 +1,10 @@
use assert_cmd::prelude::*;
use git2::Repository;
+#[cfg(not(windows))]
+use std::os::unix::fs::PermissionsExt;
use std::{
- env,
error::Error,
fs,
- os::unix::fs::PermissionsExt,
process::Command,
};
use tempfile::tempdir;
@@ -30,19 +30,36 @@ const HOOKS: [&str; 18] = [
"sendemail-validate",
];
-#[test]
-fn init() -> Result<(), Box<dyn Error>> {
+fn lang(lang: &str) -> Result<(), Box<dyn Error>> {
let dir = tempdir()?;
let _ = Repository::init(&dir)?;
- let mut cmd = Command::cargo_bin("hooked")?;
- env::set_current_dir(&dir)?;
- let _ = cmd.arg("init").assert().success();
+ let _ = Command::cargo_bin("hooked")?
+ .arg("init")
+ .arg(lang)
+ .current_dir(&dir)
+ .assert()
+ .success();
let git = &dir.path().join(".git").join("hooks");
let dev = &dir.path().join(".dev-suite").join("hooked");
for hook in &HOOKS {
let git_hook = git.join(hook);
- let dev_hook = dev.join(hook);
+ #[cfg(not(windows))]
+ let mut dev_hook = dev.join(hook);
+ #[cfg(windows)]
+ let mut dev_hook = dev.join("wrapper").join(hook);
+
+ #[cfg(not(windows))]
+ let _ = match lang {
+ "bash" => dev_hook.set_extension("sh"),
+ "python" => dev_hook.set_extension("py"),
+ "ruby" => dev_hook.set_extension("rb"),
+ _ => unreachable!(),
+ };
+
+ #[cfg(windows)]
+ let _ = dev_hook.set_extension("sh");
+
assert!(&git_hook.exists());
assert!(&dev_hook.exists());
assert!(fs::symlink_metadata(&git_hook)?.file_type().is_symlink());
@@ -54,8 +71,39 @@ fn init() -> Result<(), Box<dyn Error>> {
// numbers essentially, allowing us to test the actual value we wanted to
// test, and making this test work without special casing it if git ever
// changes.
+ #[cfg(not(windows))]
assert_eq!(dev_hook.metadata()?.permissions().mode() & 511, 0o755);
- }
+ let shebang = fs::read_to_string(&dev_hook)?
+ .lines()
+ .nth(0)
+ .ok_or_else(|| "File is empty and has no shebang line")?
+ .to_owned();
+
+ #[cfg(not(windows))]
+ match lang {
+ "bash" => assert_eq!(shebang, "#!/usr/bin/env bash"),
+ "python" => assert_eq!(shebang, "#!/usr/bin/env python3"),
+ "ruby" => assert_eq!(shebang, "#!/usr/bin/env ruby"),
+ _ => unreachable!(),
+ }
+ #[cfg(windows)]
+ assert_eq!(shebang, "#!C:\\Program Files\\Git\\bin\\sh.exe")
+ }
Ok(())
}
+
+#[test]
+fn init_bash() -> Result<(), Box<dyn Error>> {
+ lang("bash")
+}
+
+#[test]
+fn init_python() -> Result<(), Box<dyn Error>> {
+ lang("python")
+}
+
+#[test]
+fn init_ruby() -> Result<(), Box<dyn Error>> {
+ lang("ruby")
+}