Explorar o código

Add `glacier` command to track ICEs (#526)

Elinvynia %!s(int64=5) %!d(string=hai) anos
pai
achega
fcbcc52cc1
Modificáronse 8 ficheiros con 315 adicións e 0 borrados
  1. 81 0
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 8 0
      parser/src/command.rs
  4. 114 0
      parser/src/command/glacier.rs
  5. 5 0
      src/config.rs
  6. 3 0
      src/handlers.rs
  7. 98 0
      src/handlers/glacier.rs
  8. 5 0
      src/main.rs

+ 81 - 0
Cargo.lock

@@ -21,6 +21,11 @@ name = "anyhow"
 version = "1.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "arc-swap"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "async-trait"
 version = "0.1.31"
@@ -172,6 +177,11 @@ dependencies = [
  "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "dotenv"
 version = "0.14.1"
@@ -506,6 +516,23 @@ dependencies = [
  "tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "hyperx"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "idna"
 version = "0.2.0"
@@ -555,6 +582,11 @@ dependencies = [
  "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "language-tags"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -716,6 +748,26 @@ name = "object"
 version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "octocrab"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyperx 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_path_to_error 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "snafu 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.4.0"
@@ -1156,6 +1208,26 @@ name = "smallvec"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "snafu"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)",
+ "doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "snafu-derive 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "snafu-derive"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "sourcefile"
 version = "0.1.4"
@@ -1348,6 +1420,7 @@ dependencies = [
  "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "octocrab 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)",
  "parser 0.1.0",
@@ -1422,6 +1495,7 @@ dependencies = [
  "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1616,6 +1690,7 @@ dependencies = [
 "checksum addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543"
 "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
 "checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
+"checksum arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
 "checksum async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
 "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
@@ -1637,6 +1712,7 @@ dependencies = [
 "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
 "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
 "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+"checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 "checksum dotenv 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4424bad868b0ffe6ae351ee463526ba625bbca817978293bbe6bb7dc1804a175"
 "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
 "checksum encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
@@ -1675,12 +1751,14 @@ dependencies = [
 "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
 "checksum hyper 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14"
 "checksum hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa"
+"checksum hyperx 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81d7ed6ec7d25c4de28b999a5693f14609a8b756137b1b4cb4927d119f59ef25"
 "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
 "checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
 "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
 "checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
 "checksum js-sys 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)" = "367647c532db6f1555d7151e619540ec5f713328235b8c062c6b4f63e84adfe3"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
 "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 "checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
 "checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
@@ -1700,6 +1778,7 @@ dependencies = [
 "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
 "checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
 "checksum object 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2"
+"checksum octocrab 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72189709b525449bf6f31bec64eb6fdc3106d2b5e58274f1d2a767aa9736ceb5"
 "checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
 "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
 "checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
@@ -1750,6 +1829,8 @@ dependencies = [
 "checksum siphasher 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
 "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 "checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
+"checksum snafu 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce"
+"checksum snafu-derive 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9"
 "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
 "checksum stringprep 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
 "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"

+ 1 - 0
Cargo.toml

@@ -32,6 +32,7 @@ tokio-postgres = { version = "0.5", features = ["with-chrono-0_4"] }
 postgres-native-tls = "0.3"
 native-tls = "0.2"
 serde_path_to_error = "0.1.2"
+octocrab = "0.3"
 
 [dependencies.serde]
 version = "1"

+ 8 - 0
parser/src/command.rs

@@ -3,6 +3,7 @@ use crate::error::Error;
 use crate::token::{Token, Tokenizer};
 
 pub mod assign;
+pub mod glacier;
 pub mod nominate;
 pub mod ping;
 pub mod prioritize;
@@ -21,6 +22,7 @@ pub enum Command<'a> {
     Nominate(Result<nominate::NominateCommand, Error<'a>>),
     Prioritize(Result<prioritize::PrioritizeCommand, Error<'a>>),
     Second(Result<second::SecondCommand, Error<'a>>),
+    Glacier(Result<glacier::GlacierCommand, Error<'a>>),
     None,
 }
 
@@ -109,6 +111,11 @@ impl<'a> Input<'a> {
             Command::Second,
             &original_tokenizer,
         ));
+        success.extend(parse_single_command(
+            glacier::GlacierCommand::parse,
+            Command::Glacier,
+            &original_tokenizer,
+        ));
 
         if success.len() > 1 {
             panic!(
@@ -149,6 +156,7 @@ impl<'a> Command<'a> {
             Command::Nominate(r) => r.is_ok(),
             Command::Prioritize(r) => r.is_ok(),
             Command::Second(r) => r.is_ok(),
+            Command::Glacier(r) => r.is_ok(),
             Command::None => true,
         }
     }

+ 114 - 0
parser/src/command/glacier.rs

@@ -0,0 +1,114 @@
+//! The glacier command parser.
+//!
+//! This adds the option to track ICEs. Do note that the gist must be from a playground link.
+//! The link must also be in quotes.
+//!
+//! The grammar is as follows:
+//!
+//! ```text
+//! Command: `@bot glacier <code-source>`
+//!
+//! <code-source>:
+//!   - "https://gist.github.com/.*"
+//! ```
+
+use crate::error::Error;
+use crate::token::{Token, Tokenizer};
+use std::fmt;
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct GlacierCommand {
+    pub source: String,
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum ParseError {
+    NoLink,
+    InvalidLink,
+}
+
+impl std::error::Error for ParseError {}
+
+impl fmt::Display for ParseError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::NoLink => write!(f, "no link provided - did you forget the quotes around it?"),
+            Self::InvalidLink => write!(f, "invalid link - must be from a playground gist"),
+        }
+    }
+}
+
+impl GlacierCommand {
+    pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<GlacierCommand>, Error<'a>> {
+        let mut toks = input.clone();
+        if let Some(Token::Word("glacier")) = toks.peek_token()? {
+            toks.next_token()?;
+            match toks.next_token()? {
+                Some(Token::Quote(s)) => {
+                    let source = s.to_owned();
+                    if source.starts_with("https://gist.github.com/") {
+                        return Ok(Some(GlacierCommand { source }));
+                    } else {
+                        return Err(toks.error(ParseError::InvalidLink));
+                    }
+                }
+                Some(Token::Word(_)) => {
+                    return Err(toks.error(ParseError::InvalidLink));
+                }
+                _ => {
+                    return Err(toks.error(ParseError::NoLink));
+                }
+            }
+        } else {
+            Ok(None)
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    fn parse<'a>(input: &'a str) -> Result<Option<GlacierCommand>, Error<'a>> {
+        let mut toks = Tokenizer::new(input);
+        Ok(GlacierCommand::parse(&mut toks)?)
+    }
+
+    #[test]
+    fn glacier_empty() {
+        use std::error::Error;
+        assert_eq!(
+            parse("glacier")
+                .unwrap_err()
+                .source()
+                .unwrap()
+                .downcast_ref(),
+            Some(&ParseError::NoLink),
+        );
+    }
+
+    #[test]
+    fn glacier_invalid() {
+        use std::error::Error;
+        assert_eq!(
+            parse("glacier hello")
+                .unwrap_err()
+                .source()
+                .unwrap()
+                .downcast_ref(),
+            Some(&ParseError::InvalidLink),
+        );
+    }
+
+    #[test]
+    fn glacier_valid() {
+        assert_eq!(
+            parse(
+                r#"glacier "https://gist.github.com/rust-play/89d6c8a2398dd2dd5fcb7ef3e8109c7b""#
+            ),
+            Ok(Some(GlacierCommand {
+                source: "https://gist.github.com/rust-play/89d6c8a2398dd2dd5fcb7ef3e8109c7b".into()
+            }))
+        );
+    }
+}

+ 5 - 0
src/config.rs

@@ -22,6 +22,7 @@ pub(crate) struct Config {
     pub(crate) nominate: Option<NominateConfig>,
     pub(crate) prioritize: Option<PrioritizeConfig>,
     pub(crate) major_change: Option<MajorChangeConfig>,
+    pub(crate) glacier: Option<GlacierConfig>,
 }
 
 #[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -92,6 +93,9 @@ pub(crate) struct MajorChangeConfig {
     pub(crate) zulip_stream: u64,
 }
 
+#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
+pub(crate) struct GlacierConfig {}
+
 pub(crate) async fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, ConfigurationError> {
     if let Some(config) = get_cached_config(repo) {
         log::trace!("returning config for {} from cache", repo);
@@ -226,6 +230,7 @@ mod tests {
                 }),
                 prioritize: None,
                 major_change: None,
+                glacier: None,
             }
         );
     }

+ 3 - 0
src/handlers.rs

@@ -1,6 +1,7 @@
 use crate::config::{self, ConfigurationError};
 use crate::github::{Event, GithubClient};
 use futures::future::BoxFuture;
+use octocrab::Octocrab;
 use std::fmt;
 use tokio_postgres::Client as DbClient;
 
@@ -80,12 +81,14 @@ handlers! {
     prioritize = prioritize::PrioritizeHandler,
     major_change = major_change::MajorChangeHandler,
     //tracking_issue = tracking_issue::TrackingIssueHandler,
+    glacier = glacier::GlacierHandler,
 }
 
 pub struct Context {
     pub github: GithubClient,
     pub db: DbClient,
     pub username: String,
+    pub octocrab: Octocrab,
 }
 
 pub trait Handler: Sync + Send {

+ 98 - 0
src/handlers/glacier.rs

@@ -0,0 +1,98 @@
+//! Allows team members to directly create a glacier PR with the code provided.
+
+use crate::{
+    config::GlacierConfig,
+    github::Event,
+    handlers::{Context, Handler},
+};
+
+use futures::future::{BoxFuture, FutureExt};
+use parser::command::glacier::GlacierCommand;
+use parser::command::{Command, Input};
+use octocrab::params::repos::Reference;
+use octocrab::models::Object;
+
+pub(super) struct GlacierHandler;
+
+impl Handler for GlacierHandler {
+    type Input = GlacierCommand;
+    type Config = GlacierConfig;
+
+    fn parse_input(
+        &self,
+        ctx: &Context,
+        event: &Event,
+        _: Option<&GlacierConfig>,
+    ) -> Result<Option<Self::Input>, String> {
+        let body = if let Some(b) = event.comment_body() {
+            b
+        } else {
+            // not interested in other events
+            return Ok(None);
+        };
+
+        let mut input = Input::new(&body, &ctx.username);
+        match input.parse_command() {
+            Command::Glacier(Ok(command)) => Ok(Some(command)),
+            Command::Glacier(Err(err)) => {
+                return Err(format!(
+                    "Parsing glacier command in [comment]({}) failed: {}",
+                    event.html_url().expect("has html url"),
+                    err
+                ));
+            }
+            _ => Ok(None),
+        }
+    }
+
+    fn handle_input<'a>(
+        &self,
+        ctx: &'a Context,
+        _config: &'a GlacierConfig,
+        event: &'a Event,
+        cmd: GlacierCommand,
+    ) -> BoxFuture<'a, anyhow::Result<()>> {
+        handle_input(ctx, event, cmd).boxed()
+    }
+}
+
+async fn handle_input(ctx: &Context, event: &Event, cmd: GlacierCommand) -> anyhow::Result<()> {
+    let is_team_member = event.user().is_team_member(&ctx.github).await.unwrap_or(false);
+
+    if !is_team_member {
+        return Ok(())
+    };
+
+    let url = cmd.source;
+    let response = ctx.github.raw().get(&format!("{}{}", url.replace("github", "githubusercontent"), "/playground.rs")).send().await?;
+    let body = response.text().await?;
+
+    let number = event.issue().unwrap().number;
+    let user = event.user();
+
+    let octocrab = &ctx.octocrab;
+
+    let fork = octocrab.repos("rustbot", "glacier");
+    let base = octocrab.repos("rust-lang", "glacier");
+
+    let master = base.get_ref(&Reference::Branch("master".to_string())).await?.object;
+    let master = if let Object::Commit { sha, ..} = master {
+        sha
+    } else {
+        log::error!("invalid commit sha - master {:?}", master);
+        unreachable!()
+    };
+
+    fork.create_ref(&Reference::Branch(format!("triagebot-ice-{}", number)), master).await?;
+    fork.create_file(format!("ices/{}.rs", number), format!("Add ICE reproduction for issue #{}.", number), body)
+        .branch(format!("triagebot-ice-{}", number))
+        .send()
+        .await?;
+
+    octocrab.pulls("rust-lang", "glacier")
+        .create(format!("ICE - {}", number), format!("rustbot:triagebot-ice-{}", number), "master")
+        .body(format!("Automatically created by @{} in issue #{}", user.login, number),)
+        .send()
+        .await?;
+    Ok(())
+}

+ 5 - 0
src/main.rs

@@ -185,10 +185,15 @@ async fn run_server(addr: SocketAddr) -> anyhow::Result<()> {
         client.clone(),
         env::var("GITHUB_API_TOKEN").expect("Missing GITHUB_API_TOKEN"),
     );
+    let oc = octocrab::OctocrabBuilder::new()
+        .personal_token(env::var("GITHUB_API_TOKEN").expect("Missing GITHUB_API_TOKEN"))
+        .build()
+        .expect("Failed to build octograb.");
     let ctx = Arc::new(Context {
         username: github::User::current(&gh).await.unwrap().login,
         db: db_client,
         github: gh,
+        octocrab: oc,
     });
 
     let svc = hyper::service::make_service_fn(move |_conn| {