autolabel.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. use crate::{
  2. config::AutolabelConfig,
  3. github::{files_changed, IssuesAction, IssuesEvent, Label},
  4. handlers::Context,
  5. };
  6. use anyhow::Context as _;
  7. use tracing as log;
  8. pub(super) struct AutolabelInput {
  9. add: Vec<Label>,
  10. remove: Vec<Label>,
  11. }
  12. pub(super) async fn parse_input(
  13. ctx: &Context,
  14. event: &IssuesEvent,
  15. config: Option<&AutolabelConfig>,
  16. ) -> Result<Option<AutolabelInput>, String> {
  17. // On opening a new PR or sync'ing the branch, look at the diff and try to
  18. // add any appropriate labels.
  19. //
  20. // FIXME: This will re-apply labels after a push that the user had tried to
  21. // remove. Not much can be done about that currently; the before/after on
  22. // synchronize may be straddling a rebase, which will break diff generation.
  23. if let Some(config) = config {
  24. if event.action == IssuesAction::Opened || event.action == IssuesAction::Synchronize {
  25. if let Some(diff) = event
  26. .issue
  27. .diff(&ctx.github)
  28. .await
  29. .map_err(|e| {
  30. log::error!("failed to fetch diff: {:?}", e);
  31. })
  32. .unwrap_or_default()
  33. {
  34. let files = files_changed(&diff);
  35. let mut autolabels = Vec::new();
  36. for (label, cfg) in config.labels.iter() {
  37. if cfg
  38. .trigger_files
  39. .iter()
  40. .any(|f| files.iter().any(|diff_file| diff_file.starts_with(f)))
  41. {
  42. autolabels.push(Label {
  43. name: label.to_owned(),
  44. });
  45. }
  46. }
  47. if !autolabels.is_empty() {
  48. return Ok(Some(AutolabelInput {
  49. add: autolabels,
  50. remove: vec![],
  51. }));
  52. }
  53. }
  54. }
  55. }
  56. if event.action == IssuesAction::Labeled {
  57. if let Some(config) = config {
  58. let mut autolabels = Vec::new();
  59. let applied_label = &event.label.as_ref().expect("label").name;
  60. 'outer: for (label, config) in config.get_by_trigger(applied_label) {
  61. let exclude_patterns: Vec<glob::Pattern> = config
  62. .exclude_labels
  63. .iter()
  64. .filter_map(|label| match glob::Pattern::new(label) {
  65. Ok(exclude_glob) => Some(exclude_glob),
  66. Err(error) => {
  67. log::error!("Invalid glob pattern: {}", error);
  68. None
  69. }
  70. })
  71. .collect();
  72. for label in event.issue.labels() {
  73. for pat in &exclude_patterns {
  74. if pat.matches(&label.name) {
  75. // If we hit an excluded label, ignore this autolabel and check the next
  76. continue 'outer;
  77. }
  78. }
  79. }
  80. // If we reach here, no excluded labels were found, so we should apply the autolabel.
  81. autolabels.push(Label {
  82. name: label.to_owned(),
  83. });
  84. }
  85. if !autolabels.is_empty() {
  86. return Ok(Some(AutolabelInput {
  87. add: autolabels,
  88. remove: vec![],
  89. }));
  90. }
  91. }
  92. }
  93. if event.action == IssuesAction::Closed {
  94. let labels = event.issue.labels();
  95. if labels.iter().any(|x| x.name == "I-prioritize") {
  96. return Ok(Some(AutolabelInput {
  97. add: vec![],
  98. remove: vec![Label {
  99. name: "I-prioritize".to_owned(),
  100. }],
  101. }));
  102. }
  103. }
  104. Ok(None)
  105. }
  106. pub(super) async fn handle_input(
  107. ctx: &Context,
  108. _config: &AutolabelConfig,
  109. event: &IssuesEvent,
  110. input: AutolabelInput,
  111. ) -> anyhow::Result<()> {
  112. event.issue.add_labels(&ctx.github, input.add).await?;
  113. for label in input.remove {
  114. event
  115. .issue
  116. .remove_label(&ctx.github, &label.name)
  117. .await
  118. .with_context(|| {
  119. format!(
  120. "failed to remove {:?} from {:?}",
  121. label,
  122. event.issue.global_id()
  123. )
  124. })?;
  125. }
  126. Ok(())
  127. }