| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | |
|
| 6 | |
|
| 7 | | use super::*; |
| 8 | | use std::process::{Command, Stdio}; |
| 9 | |
|
| 10 | |
|
| 11 | 13971× | pub fn json_str(s: &str) -> String { |
| 12 | 13971× | let mut out = String::with_capacity(s.len() + 2); |
| 13 | 13971× | out.push('"'); |
| 14 | 271727× | for c in s.chars() { |
| 15 | 271693× | match c { |
| 16 | 5× | '"' => out.push_str("\\\""), |
| 17 | 1× | '\\' => out.push_str("\\\\"), |
| 18 | 28× | '\n' => out.push_str("\\n"), |
| 19 | 0× | '\r' => out.push_str("\\r"), |
| 20 | 0× | '\t' => out.push_str("\\t"), |
| 21 | 271693× | c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)), |
| 22 | 271693× | c => out.push(c), |
| 23 | | } |
| 24 | | } |
| 25 | 13971× | out.push('"'); |
| 26 | 13971× | out |
| 27 | 13971× | } |
| 28 | |
|
| 29 | 2363× | pub fn json_array<I: IntoIterator<Item = String>>(items: I) -> String { |
| 30 | 2363× | format!("[{}]", items.into_iter().collect::<Vec<_>>().join(",")) |
| 31 | 2363× | } |
| 32 | |
|
| 33 | |
|
| 34 | | #[derive(Debug, Clone, PartialEq, Eq)] |
| 35 | | pub struct HookOutcome { |
| 36 | | pub ran: bool, |
| 37 | | pub stdout: String, |
| 38 | | } |
| 39 | |
|
| 40 | | impl Repo { |
| 41 | 2502× | fn hook_path(&self, event: &str) -> PathBuf { |
| 42 | 2502× | self.oxy.join("hooks").join(event) |
| 43 | 2502× | } |
| 44 | |
|
| 45 | | |
| 46 | | |
| 47 | 2502× | pub fn run_hook(&self, event: &str, input: &str) -> Result<HookOutcome> { |
| 48 | 2502× | let path = self.hook_path(event); |
| 49 | 2502× | if !path.is_file() { |
| 50 | 2492× | return Ok(HookOutcome { |
| 51 | 2492× | ran: false, |
| 52 | 2492× | stdout: String::new(), |
| 53 | 2492× | }); |
| 54 | 10× | } |
| 55 | 10× | let mut child = Command::new(&path) |
| 56 | 10× | .current_dir(&self.root) |
| 57 | 10× | .env("OXY_EVENT", event) |
| 58 | 10× | .env("OXY_ROOT", &self.root) |
| 59 | 10× | .stdin(Stdio::piped()) |
| 60 | 10× | .stdout(Stdio::piped()) |
| 61 | 10× | .stderr(Stdio::piped()) |
| 62 | 10× | .spawn() |
| 63 | 10× | .map_err(|e| RepoError::Msg(format!("running hook {event}: {e}")))?; |
| 64 | 10× | if let Some(mut stdin) = child.stdin.take() { |
| 65 | 10× | let _ = stdin.write_all(input.as_bytes()); |
| 66 | 10× | } |
| 67 | 10× | let out = child.wait_with_output()?; |
| 68 | 10× | let stdout = String::from_utf8_lossy(&out.stdout).to_string(); |
| 69 | 10× | if !out.status.success() { |
| 70 | 2× | let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string(); |
| 71 | 2× | return fail(format!( |
| 72 | | "hook {event} refused: {}", |
| 73 | 2× | if stderr.is_empty() { |
| 74 | 0× | "(no message)".to_string() |
| 75 | | } else { |
| 76 | 2× | stderr |
| 77 | | } |
| 78 | | )); |
| 79 | 8× | } |
| 80 | 8× | Ok(HookOutcome { ran: true, stdout }) |
| 81 | 2502× | } |
| 82 | |
|
| 83 | | |
| 84 | 1× | pub(crate) fn semantic_deps( |
| 85 | 1× | &self, |
| 86 | 1× | change_id: &ChangeId, |
| 87 | 1× | touched: &[String], |
| 88 | 1× | candidates: &[(ChangeId, ChangeHash, Vec<String>)], |
| 89 | 1× | ) -> Result<Vec<ChangeHash>> { |
| 90 | 1× | if !self.config.semantic_deps { |
| 91 | 0× | return Ok(vec![]); |
| 92 | 1× | } |
| 93 | 1× | let input = format!( |
| 94 | | "{{\"change_id\":{},\"touched_paths\":{},\"candidate_deps\":{}}}", |
| 95 | 1× | json_str(&change_id.to_hex()), |
| 96 | 1× | json_array(touched.iter().map(|p| json_str(p))), |
| 97 | 2× | json_array(candidates.iter().map(|(id, h, paths)| format!( |
| 98 | | "{{\"change_id\":{},\"hash\":{},\"paths\":{}}}", |
| 99 | 2× | json_str(&id.to_hex()), |
| 100 | 2× | json_str(&h.to_hex()), |
| 101 | 3× | json_array(paths.iter().map(|p| json_str(p))) |
| 102 | | ))) |
| 103 | | ); |
| 104 | 1× | let out = self.run_hook("semantic-deps", &input)?; |
| 105 | 1× | if !out.ran { |
| 106 | 0× | return Ok(vec![]); |
| 107 | 1× | } |
| 108 | | |
| 109 | 1× | let body = out |
| 110 | 1× | .stdout |
| 111 | 1× | .split_once("\"deps\"") |
| 112 | 1× | .map(|(_, rest)| rest.to_string()) |
| 113 | 1× | .unwrap_or_default(); |
| 114 | 1× | let mut deps = Vec::new(); |
| 115 | 1× | let bytes = body.as_bytes(); |
| 116 | 1× | let mut i = 0; |
| 117 | 6× | while i + 64 <= bytes.len() { |
| 118 | 68× | if bytes[i..i + 64].iter().all(|b| b.is_ascii_hexdigit()) |
| 119 | 1× | && (i == 0 || !bytes[i - 1].is_ascii_hexdigit()) |
| 120 | 1× | && (i + 64 == bytes.len() || !bytes[i + 64].is_ascii_hexdigit()) |
| 121 | | { |
| 122 | 1× | if let Some(h) = ObjectRef::from_hex(&body[i..i + 64]) { |
| 123 | 1× | deps.push(h); |
| 124 | 1× | } |
| 125 | 1× | i += 64; |
| 126 | 4× | } else { |
| 127 | 4× | i += 1; |
| 128 | 4× | } |
| 129 | | } |
| 130 | 1× | Ok(deps) |
| 131 | 1× | } |
| 132 | | } |