1+ use anyhow:: bail;
2+
13use crate :: {
24 github:: { Event , IssuesAction , IssuesEvent } ,
35 handlers:: Context ,
@@ -10,6 +12,10 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
1012 return Ok ( ( ) ) ;
1113 } ;
1214
15+ if !e. issue . is_pr ( ) {
16+ return Ok ( ( ) ) ;
17+ }
18+
1319 let repo = e. issue . repository ( ) ;
1420 let prefix = match ( & * repo. organization , & * repo. repository ) {
1521 ( "rust-lang" , "rfcs" ) => "text/" ,
@@ -25,32 +31,69 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
2531}
2632
2733async fn add_rendered_link ( ctx : & Context , e : & IssuesEvent , prefix : & str ) -> anyhow:: Result < ( ) > {
28- if e. action == IssuesAction :: Opened {
34+ if e. action == IssuesAction :: Opened
35+ || e. action == IssuesAction :: Closed
36+ || e. action == IssuesAction :: Reopened
37+ {
2938 let files = e. issue . files ( & ctx. github ) . await ?;
3039
3140 if let Some ( file) = files. iter ( ) . find ( |f| f. filename . starts_with ( prefix) ) {
32- if !e. issue . body . contains ( "[Rendered]" ) {
33- // This URL should be stable while the PR is open, even if the
34- // user pushes new commits.
35- //
36- // It will go away if the user deletes their branch, or if
37- // they reset it (such as if they created a PR from master).
38- // That should usually only happen after the PR is closed.
39- // During the closing process, the closer should update the
40- // Rendered link to the new location (which we should
41- // automate!).
42- let head = e. issue . head . as_ref ( ) . unwrap ( ) ;
43- let url = format ! (
44- "https://github.com/{}/blob/{}/{}" ,
45- head. repo. full_name, head. git_ref, file. filename
46- ) ;
47- e. issue
48- . edit_body (
49- & ctx. github ,
50- & format ! ( "{}\n \n [Rendered]({})" , e. issue. body, url) ,
51- )
52- . await ?;
53- }
41+ let head = e. issue . head . as_ref ( ) . unwrap ( ) ;
42+ let base = e. issue . base . as_ref ( ) . unwrap ( ) ;
43+
44+ // This URL should be stable while the PR is open, even if the
45+ // user pushes new commits.
46+ //
47+ // It will go away if the user deletes their branch, or if
48+ // they reset it (such as if they created a PR from master).
49+ // That should usually only happen after the PR is closed
50+ // a which point we switch to a SHA-based url.
51+ //
52+ // If the PR is merged we use a URL that points to the actual
53+ // repository, as to be resilient to branch deletion, as well
54+ // be in sync with current "master" branch.
55+ //
56+ // For a PR "octocat:master" <- "Bob:patch-1", we generate,
57+ // - if merged: `https://github.com/octocat/REPO/blob/master/FILEPATH`
58+ // - if open: `https://github.com/Bob/REPO/blob/patch-1/FILEPATH`
59+ // - if closed: `https://github.com/octocat/REPO/blob/SHA/FILEPATH`
60+ let rendered_link = format ! (
61+ "[Rendered](https://github.com/{}/blob/{}/{})" ,
62+ if e. issue. merged || e. action == IssuesAction :: Closed {
63+ & e. repository. full_name
64+ } else {
65+ & head. repo. full_name
66+ } ,
67+ if e. issue. merged {
68+ & base. git_ref
69+ } else if e. action == IssuesAction :: Closed {
70+ & head. sha
71+ } else {
72+ & head. git_ref
73+ } ,
74+ file. filename
75+ ) ;
76+
77+ let new_body = if !e. issue . body . contains ( "[Rendered]" ) {
78+ // add rendered link to the end of the body
79+ format ! ( "{}\n \n {rendered_link}" , e. issue. body)
80+ } else if let Some ( start_pos) = e. issue . body . find ( "[Rendered](" ) {
81+ let Some ( end_offset) = & e. issue . body [ start_pos..] . find ( ')' ) else {
82+ bail ! ( "no `)` after `[Rendered]` found" )
83+ } ;
84+
85+ // replace the current rendered link with the new one
86+ e. issue . body . replace (
87+ & e. issue . body [ start_pos..=( start_pos + end_offset) ] ,
88+ & rendered_link,
89+ )
90+ } else {
91+ bail ! (
92+ "found `[Rendered]` but not it's associated link, can't replace it, bailing out"
93+ )
94+ } ;
95+
96+ e. issue . edit_body ( & ctx. github , & new_body) . await ?;
5497 }
5598 }
5699
0 commit comments