summaryrefslogtreecommitdiff
path: root/server/src/githook.rs
blob: f0e872a399d51d4cd3e97618313116f23dd6e1de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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;

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 {}

#[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 input = io::stdin();
    let reader = BufReader::new(input);
    let mut lines = reader.lines();

    let mut request = git_socket::GitHookRequest {
        pre,
        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(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()))?;
        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"))
    }
}