-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathrendered_link.rs
More file actions
140 lines (126 loc) · 5.2 KB
/
Copy pathrendered_link.rs
File metadata and controls
140 lines (126 loc) · 5.2 KB
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::borrow::Cow;
use anyhow::bail;
use crate::{
config::RenderedLinkConfig,
github::{Event, IssuesAction, IssuesEvent, PullRequestFileStatus},
handlers::Context,
utils::ModifiedPathMatcher,
};
pub(super) async fn handle(
ctx: &Context,
event: &Event,
config: &RenderedLinkConfig,
) -> anyhow::Result<()> {
let Event::Issue(e) = event else {
return Ok(());
};
if !e.issue.is_pr() {
return Ok(());
}
if let Err(e) = add_rendered_link(ctx, e, config).await {
tracing::error!("Error adding rendered link: {e:?}");
}
Ok(())
}
async fn add_rendered_link(
ctx: &Context,
e: &IssuesEvent,
config: &RenderedLinkConfig,
) -> anyhow::Result<()> {
if e.action == IssuesAction::Opened
|| e.action == IssuesAction::Closed
|| e.action == IssuesAction::Reopened
|| e.action == IssuesAction::Synchronize
{
let files = e.issue.files(&ctx.github).await?;
let trigger_matcher = ModifiedPathMatcher::new(&config.trigger_files);
let exclude_matcher = ModifiedPathMatcher::new(&config.exclude_files);
let rendered_link = files
.iter()
.filter(|f| {
trigger_matcher.is_match(&f.filename) && !exclude_matcher.is_match(&f.filename)
})
.filter(|f| match f.status {
PullRequestFileStatus::Added
| PullRequestFileStatus::Modified
| PullRequestFileStatus::Changed
| PullRequestFileStatus::Renamed => true,
PullRequestFileStatus::Removed
| PullRequestFileStatus::Copied
| PullRequestFileStatus::Unchanged => false,
})
// Sort the relavant files by the total number of lines changed, as to
// improve our guess for the relevant file to show the link to.
.max_by_key(|f| f.additions + f.deletions + f.changes)
.and_then(|file| {
let head = e.issue.head.as_ref()?;
let base = e.issue.base.as_ref()?;
let is_merged = e.issue.merged_at.is_some();
// This URL should be stable while the PR is open, even if the
// user pushes new commits.
//
// It will go away if the user deletes their branch, or if
// they reset it (such as if they created a PR from master).
// That should usually only happen after the PR is closed
// a which point we switch to a SHA-based url.
//
// If the PR is merged we use a URL that points to the actual
// repository, as to be resilient to branch deletion, as well
// be in sync with current "master" branch.
//
// For a PR "octocat:master" <- "Bob:patch-1", we generate,
// - if merged: `https://github.com/octocat/REPO/blob/master/FILEPATH`
// - if open: `https://github.com/Bob/REPO/blob/patch-1/FILEPATH`
// - if closed: `https://github.com/octocat/REPO/blob/SHA/FILEPATH`
Some(format!(
"[Rendered](https://github.com/{}/blob/{}/{})",
if is_merged || e.action == IssuesAction::Closed {
&e.repository.full_name
} else {
&head.repo.as_ref()?.full_name
},
if is_merged {
&base.git_ref
} else if e.action == IssuesAction::Closed {
&head.sha
} else {
&head.git_ref
},
file.filename
))
});
let new_body: Cow<'_, str> = if !e.issue.body.contains("[Rendered]") {
if let Some(rendered_link) = rendered_link {
// add rendered link to the end of the body
format!("{}\n\n{rendered_link}", e.issue.body).into()
} else {
// or return the original body since we don't have
// a rendered link to add
e.issue.body.as_str().into()
}
} else if let Some(start_pos) = e.issue.body.find("[Rendered](") {
let Some(end_offset) = &e.issue.body[start_pos..].find(')') else {
bail!("no `)` after `[Rendered]` found")
};
// replace the current rendered link with the new one or replace
// it with an empty string if we don't have one
e.issue
.body
.replace(
&e.issue.body[start_pos..=(start_pos + end_offset)],
rendered_link.as_deref().unwrap_or(""),
)
.into()
} else {
bail!(
"found `[Rendered]` but not its associated link, can't replace it or remove it, bailing out"
)
};
// avoid an expensive GitHub api call by first checking if we actually
// edited the pull request body
if e.issue.body != new_body {
e.issue.edit_body(&ctx.github, &new_body).await?;
}
}
Ok(())
}