summaryrefslogtreecommitdiff
path: root/server/src/githook.rs
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2025-01-26 21:58:42 +0100
committerJoel Klinghed <the_jk@spawned.biz>2025-01-26 21:58:42 +0100
commit9e80b8cd1e44fcf863d926055d9fa458db46e0d3 (patch)
tree1fd262ac79127e60d91f9e7efac9702ec3058cdb /server/src/githook.rs
parent42334c32226f0ff3248d6d0c7641b7170ca962ce (diff)
Add basic git support
Pushing a commit to a new branch creates a review. Each project has its own git directory, with githooks installed that talkes with server process via unix sockets.
Diffstat (limited to 'server/src/githook.rs')
-rw-r--r--server/src/githook.rs108
1 files changed, 108 insertions, 0 deletions
diff --git a/server/src/githook.rs b/server/src/githook.rs
new file mode 100644
index 0000000..057ee47
--- /dev/null
+++ b/server/src/githook.rs
@@ -0,0 +1,108 @@
+use rmp_serde::{decode, Serializer};
+use serde::ser::Serialize;
+use std::error::Error;
+use std::fmt;
+use std::os::unix::net::UnixStream;
+use std::path::PathBuf;
+use tokio::io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader};
+use tokio::task;
+use users::get_current_username;
+
+mod fs_utils;
+mod git;
+mod git_socket;
+
+#[derive(Debug)]
+struct IoError {
+ message: String,
+}
+
+impl IoError {
+ fn new(message: impl Into<String>) -> Self {
+ Self {
+ message: message.into(),
+ }
+ }
+}
+
+impl fmt::Display for IoError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.message)
+ }
+}
+
+impl Error for IoError {}
+
+async fn get_socket() -> Result<String, git::Error> {
+ let repo = git::Repository::new(PathBuf::from("."), true, None::<String>, None::<String>);
+ repo.config_get("eyeballs.socket").await
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error>> {
+ let pre = match std::env::current_exe()?
+ .file_name()
+ .and_then(|x| x.to_str())
+ {
+ Some("pre-receive") => true,
+ Some("post-receive") => false,
+ _ => return Err(Box::<dyn Error>::from("Invalid hook executable name")),
+ };
+
+ let user = match get_current_username() {
+ Some(username) => match username.into_string() {
+ Ok(valid_username) => valid_username,
+ Err(_) => return Err(Box::<dyn Error>::from("Invalid username for current user")),
+ },
+ None => {
+ return Err(Box::<dyn Error>::from(
+ "Unable to get username of current user",
+ ))
+ }
+ };
+
+ let input = io::stdin();
+ let reader = BufReader::new(input);
+ let mut lines = reader.lines();
+
+ let mut request = git_socket::GitHookRequest {
+ pre,
+ user,
+ receive: Vec::new(),
+ };
+ while let Some(line) = lines.next_line().await? {
+ let data: Vec<&str> = line.split(' ').collect();
+
+ if data.len() == 3 {
+ request.receive.push(git_socket::GitReceive {
+ old_value: data[0].to_string(),
+ new_value: data[1].to_string(),
+ reference: data[2].to_string(),
+ })
+ }
+ }
+
+ let socket = PathBuf::from(get_socket().await?);
+
+ let response = task::spawn_blocking(move || {
+ let stream = UnixStream::connect(socket).map_err(|e| IoError::new(e.to_string()))?;
+ let mut serializer = Serializer::new(&stream);
+ request
+ .serialize(&mut serializer)
+ .map_err(|e| IoError::new(e.to_string()))?;
+ let result: Result<git_socket::GitHookResponse, IoError> =
+ decode::from_read(stream).map_err(|e| IoError::new(e.to_string()));
+ result
+ })
+ .await?
+ .map_err(Box::<dyn Error>::from)?;
+
+ let mut output = io::stdout();
+ output.write_all(response.message.as_bytes()).await?;
+
+ if response.ok {
+ Ok(())
+ } else {
+ Err(Box::<dyn Error>::from("Hook failed"))
+ }
+}