summaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/git.rs64
-rw-r--r--server/src/git_root.rs46
-rw-r--r--server/src/git_socket.rs5
-rw-r--r--server/src/githook.rs35
4 files changed, 120 insertions, 30 deletions
diff --git a/server/src/git.rs b/server/src/git.rs
index 9fb0593..7add644 100644
--- a/server/src/git.rs
+++ b/server/src/git.rs
@@ -31,6 +31,8 @@ impl fmt::Display for Error {
impl std::error::Error for Error {}
+pub const EMPTY: &str = "0000000000000000000000000000000000000000";
+
struct RepoData {
// Only one fetch at a time, and they should be in queue
fetch_semaphore: Semaphore,
@@ -50,10 +52,30 @@ pub struct Repository {
lock: RwLock<RepoData>,
}
+#[allow(dead_code)]
+pub struct User {
+ pub name: String,
+ pub email: String,
+ // Part before '@' in email
+ pub username: String,
+}
+
fn io_err(action: &str, e: std::io::Error) -> Error {
Error::new(format!("{action}: {e}"))
}
+fn parse_user(output: String) -> User {
+ let mut lines = output.lines();
+ let name = lines.next().unwrap_or("").to_string();
+ let username = lines.next().unwrap_or("").to_string();
+ let email = lines.next().unwrap_or("").to_string();
+ User {
+ name,
+ email,
+ username,
+ }
+}
+
impl RepoData {
fn new() -> Self {
Self {
@@ -246,6 +268,34 @@ impl RepoData {
self.check(&mut cmd).await
}
+ async fn get_author(&self, repo: &Repository, commit: &str) -> Result<User, Error> {
+ self.get_log_format(repo, commit, "%an%n%al%n%ae")
+ .map_ok(|output| parse_user(output))
+ .await
+ }
+
+ async fn get_commiter(&self, repo: &Repository, commit: &str) -> Result<User, Error> {
+ self.get_log_format(repo, commit, "%cn%n%cl%n%ce")
+ .map_ok(|output| parse_user(output))
+ .await
+ }
+
+ async fn get_log_format(
+ &self,
+ repo: &Repository,
+ commit: &str,
+ format: &str,
+ ) -> Result<String, Error> {
+ let mut cmd = self.git_cmd(repo);
+ cmd.arg("log")
+ .arg("-1")
+ .arg("--no-decorate")
+ .arg("--no-mailmap")
+ .arg(format!("--pretty=format:{format}"))
+ .arg(commit);
+ self.output(&mut cmd).await
+ }
+
fn git_cmd(&self, repo: &Repository) -> Command {
let mut cmd = Command::new("git");
// Run as if git was started in <path> instead of the current working directory.
@@ -445,6 +495,20 @@ impl Repository {
data.is_equal_content(self, commit1.as_str(), commit2.as_str())
.await
}
+
+ pub async fn get_author(&self, commit: impl Into<String>) -> Result<User, Error> {
+ let commit = commit.into();
+ let data = self.lock.read().await;
+
+ data.get_author(self, commit.as_str()).await
+ }
+
+ pub async fn get_commiter(&self, commit: impl Into<String>) -> Result<User, Error> {
+ let commit = commit.into();
+ let data = self.lock.read().await;
+
+ data.get_commiter(self, commit.as_str()).await
+ }
}
#[cfg(not(test))]
diff --git a/server/src/git_root.rs b/server/src/git_root.rs
index c6ee1fb..8102b18 100644
--- a/server/src/git_root.rs
+++ b/server/src/git_root.rs
@@ -84,8 +84,6 @@ impl std::fmt::Display for IoError {
impl std::error::Error for IoError {}
-const EMPTY: &str = "0000000000000000000000000000000000000000";
-
fn is_printable(name: &str) -> bool {
name.as_bytes().iter().all(|c| c.is_ascii_graphic())
}
@@ -116,7 +114,30 @@ async fn git_process_prehook(
let branch = row.reference.strip_prefix("refs/heads/").unwrap();
- if row.old_value == EMPTY {
+ if row.new_value != git::EMPTY {
+ match row.commiter {
+ Some(ref commiter) => match sqlx::query!(
+ "SELECT id FROM users WHERE id=? AND dn IS NOT NULL",
+ commiter,
+ )
+ .fetch_one(&mut *db)
+ .map_err(|_| IoError::new(format!("{branch}: Unknown commiter {}", commiter)))
+ .await
+ {
+ Ok(_) => {}
+ Err(e) => {
+ errors.push(e.message);
+ continue;
+ }
+ },
+ None => {
+ errors.push(format!("{branch}: Missing commiter"));
+ continue;
+ }
+ }
+ }
+
+ if row.old_value == git::EMPTY {
// Creating new review, nothing to check (yet).
continue;
}
@@ -136,7 +157,7 @@ async fn git_process_prehook(
.map_err(|_| IoError::new(format!("{branch}: Unknown branch")))
.await;
- if row.new_value == EMPTY {
+ if row.new_value == git::EMPTY {
// Do not allow to delete branch if there is a review connected to the branch.
// All branches should be connected to a branch, but in case of errors this might
// be relevant.
@@ -209,7 +230,6 @@ async fn git_process_prehook(
async fn git_process_posthook(
repo: &git::Repository,
mut db: DbConnection,
- user_id: &String,
receive: &Vec<git_socket::GitReceive>,
) -> git_socket::GitHookResponse {
let mut messages: Vec<String> = Vec::new();
@@ -218,12 +238,20 @@ async fn git_process_posthook(
for row in receive {
let branch = row.reference.strip_prefix("refs/heads/").unwrap();
- if row.old_value == EMPTY {
+ if row.old_value == git::EMPTY {
+ let commiter = match repo.get_commiter(row.reference.as_str()).await {
+ Ok(user) => user,
+ Err(e) => {
+ messages.push(format!("{branch}: {e}"));
+ continue;
+ }
+ };
+
// Create review
match sqlx::query!(
"INSERT INTO reviews (project, owner, title, branch) VALUES (?, ?, ?, ?)",
repo.project_id(),
- user_id,
+ commiter.username,
"Unnamed",
branch
)
@@ -242,7 +270,7 @@ async fn git_process_posthook(
messages.push(format!("{branch}: Error {e}",));
}
};
- } else if row.new_value == EMPTY {
+ } else if row.new_value == git::EMPTY {
// Delete branch, prehook already checked that it is not connected to a branch.
} else {
match sqlx::query!(
@@ -312,7 +340,7 @@ async fn git_socket_process(
let response = if request.pre {
git_process_prehook(repo, db, &request.receive).await?
} else {
- git_process_posthook(repo, db, &request.user, &request.receive).await
+ git_process_posthook(repo, db, &request.receive).await
};
task::spawn_blocking(move || {
diff --git a/server/src/git_socket.rs b/server/src/git_socket.rs
index 90f9dc2..a4805be 100644
--- a/server/src/git_socket.rs
+++ b/server/src/git_socket.rs
@@ -5,12 +5,15 @@ pub struct GitReceive {
pub old_value: String,
pub new_value: String,
pub reference: String,
+ // Only set for pre hooks, because server can't read the objects the pre-hook has not yet
+ // accepted, so to be able to validate the commiter, send them. Also only set if new_value
+ // is not empty.
+ pub commiter: Option<String>,
}
#[derive(Deserialize, Serialize)]
pub struct GitHookRequest {
pub pre: bool,
- pub user: String,
pub receive: Vec<GitReceive>,
}
diff --git a/server/src/githook.rs b/server/src/githook.rs
index 057ee47..f0e872a 100644
--- a/server/src/githook.rs
+++ b/server/src/githook.rs
@@ -6,7 +6,6 @@ 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;
@@ -33,11 +32,6 @@ impl fmt::Display for IoError {
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()?
@@ -49,40 +43,41 @@ async fn main() -> Result<(), Box<dyn Error>> {
_ => 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(),
};
+
+ let repo = git::Repository::new(PathBuf::from("."), true, None::<String>, None::<String>);
+
while let Some(line) = lines.next_line().await? {
let data: Vec<&str> = line.split(' ').collect();
if data.len() == 3 {
+ let mut commiter: Option<String> = None;
+ if pre && data[1] != git::EMPTY {
+ match repo.get_commiter(data[1]).await {
+ Ok(user) => {
+ commiter = Some(user.username);
+ }
+ Err(_) => {}
+ }
+ }
+
request.receive.push(git_socket::GitReceive {
old_value: data[0].to_string(),
new_value: data[1].to_string(),
reference: data[2].to_string(),
+ commiter: commiter,
})
}
}
- let socket = PathBuf::from(get_socket().await?);
+ let socket = PathBuf::from(repo.config_get("eyeballs.socket").await?);
let response = task::spawn_blocking(move || {
let stream = UnixStream::connect(socket).map_err(|e| IoError::new(e.to_string()))?;