@@ -9,9 +9,52 @@ open Github_j
9
9
10
10
let log = Log. from " action"
11
11
12
- let touching_prefix rule name =
13
- let has_prefix s = List. exists ~f: (fun prefix -> String. is_prefix s ~prefix ) in
14
- (List. is_empty rule.prefix || has_prefix name rule.prefix) && not (has_prefix name rule.ignore)
12
+ type prefix_match =
13
+ | Match of int
14
+ | NoMatch
15
+
16
+ let chan_of_prefix_rule (r : prefix_rule ) = r.chan
17
+
18
+ let touching_prefix (rule : Notabot_t.prefix_rule ) name =
19
+ let match_lengths filename prefixes =
20
+ List. filter_map
21
+ ~f: (fun prefix -> if String. is_prefix filename ~prefix then Some (String. length prefix) else None )
22
+ prefixes
23
+ in
24
+ match match_lengths name rule.ignore with
25
+ | _ :: _ -> NoMatch
26
+ | [] ->
27
+ match rule.prefix with
28
+ | [] -> Match 0
29
+ | _ ->
30
+ match List. max_elt (match_lengths name rule.prefix) ~compare: Int. compare with
31
+ | Some x -> Match x
32
+ | None -> NoMatch
33
+
34
+ let longest_touching_prefix_rule rules name =
35
+ let get_m rule = touching_prefix rule name in
36
+ let reduce_to_longest_match longest_rule_match_pair current_rule =
37
+ let _, longest_match = longest_rule_match_pair in
38
+ let current_match = get_m current_rule in
39
+ let current_rule_match_pair = current_rule, current_match in
40
+ match longest_match with
41
+ | NoMatch -> current_rule_match_pair
42
+ | Match x ->
43
+ match current_match with
44
+ | NoMatch -> longest_rule_match_pair
45
+ | Match y -> if y > x then current_rule_match_pair else longest_rule_match_pair
46
+ in
47
+ match rules with
48
+ | [] -> None
49
+ | (x : prefix_rule ) :: xs ->
50
+ match List. fold_left xs ~init: (x, get_m x) ~f: reduce_to_longest_match with
51
+ | _ , NoMatch -> None
52
+ | r , Match _ -> Some r
53
+
54
+ let chan_of_file rules file = Option. map ~f: chan_of_prefix_rule @@ longest_touching_prefix_rule rules file
55
+
56
+ let unique_chans_of_files rules files =
57
+ List. dedup_and_sort ~compare: String. compare @@ List. filter_map files ~f: (chan_of_file rules)
15
58
16
59
let touching_label rule name =
17
60
let name_lc = String. lowercase name in
@@ -23,6 +66,15 @@ let touching_label rule name =
23
66
24
67
let is_main_merge_message ~msg :message ~branch cfg =
25
68
match cfg.main_branch_name with
69
+ | Some main_branch when String. equal branch main_branch ->
70
+ (*
71
+ handle "Merge <main branch> into <feature branch>" commits when they are merged into main branch
72
+ we should have already seen these commits on the feature branch but for some reason they are distinct:true
73
+ *)
74
+ let prefix = sprintf " Merge branch '%s' into " main_branch in
75
+ let prefix2 = sprintf " Merge remote-tracking branch 'origin/%s' into " main_branch in
76
+ let title = first_line message in
77
+ String. is_prefix title ~prefix || String. is_prefix title ~prefix: prefix2
26
78
| Some main_branch ->
27
79
let expect = sprintf " Merge branch '%s' into %s" main_branch branch in
28
80
let expect2 = sprintf " Merge remote-tracking branch 'origin/%s' into %s" main_branch branch in
@@ -31,18 +83,12 @@ let is_main_merge_message ~msg:message ~branch cfg =
31
83
| _ -> false
32
84
33
85
let filter_push rules commit =
34
- let matching_push rule files = List. exists files ~f: (fun file -> touching_prefix rule file) in
35
- List. filter_map rules ~f: (fun rule ->
36
- let filter =
37
- matching_push rule commit.added || matching_push rule commit.removed || matching_push rule commit.modified
38
- in
39
- match filter with
40
- | false -> None
41
- | true -> Some (rule.chan, commit))
86
+ let files = List. concat [ commit.added; commit.removed; commit.modified ] in
87
+ List. map ~f: (fun chan -> chan, commit) @@ unique_chans_of_files rules files
42
88
43
- let group_commit webhook l =
44
- List. filter_map l ~f: (fun (chan , commit ) ->
45
- match String. equal webhook chan with
89
+ let group_commit chan l =
90
+ List. filter_map l ~f: (fun (chan' , commit ) ->
91
+ match String. equal chan chan' with
46
92
| false -> None
47
93
| true -> Some commit)
48
94
@@ -61,15 +107,14 @@ let partition_push cfg n =
61
107
match filter_push rules commit with
62
108
| [] -> default commit
63
109
| l -> l)
110
+ |> List. concat
64
111
in
65
- let concat_chan = List. concat channels in
66
112
let prefix_chans =
67
- let chans = List. map rules ~f: (fun (rule : prefix_rule ) -> rule.chan) in
68
- let chans = Option. value_map cfg.prefix_rules.default ~default: chans ~f: (fun default -> default :: chans) in
113
+ let chans = Option. to_list cfg.prefix_rules.default @ List. map rules ~f: (fun (rule : prefix_rule ) -> rule.chan) in
69
114
List. dedup_and_sort chans ~compare: String. compare
70
115
in
71
116
List. filter_map prefix_chans ~f: (fun chan ->
72
- match group_commit chan concat_chan with
117
+ match group_commit chan channels with
73
118
| [] -> None
74
119
| l -> Some (chan, { n with commits = l }))
75
120
@@ -81,7 +126,7 @@ let filter_label rules (label : Github_j.label) =
81
126
| true -> Some rule.chan)
82
127
83
128
let partition_label cfg (labels : Github_j.label list ) =
84
- let default = Option. value_map cfg.label_rules.default ~default: [] ~f: ( fun webhook -> [ webhook ]) in
129
+ let default = Option. to_list cfg.label_rules.default in
85
130
match labels with
86
131
| [] -> default
87
132
| labels ->
@@ -131,41 +176,33 @@ let partition_pr_review cfg (n : pr_review_notification) =
131
176
| Submitted , _ , _ -> partition_label cfg n.pull_request.labels
132
177
| _ -> []
133
178
134
- let filter_commit rules filename =
135
- rules
136
- |> List. filter_map ~f: (fun rule ->
137
- match touching_prefix rule filename with
138
- | false -> None
139
- | true -> Some rule.chan)
140
-
141
179
let partition_commit cfg files =
142
- let default = Option. value_map cfg.prefix_rules.default ~default: [] ~f: (fun webhook -> [ webhook ]) in
143
- match files with
144
- | [] ->
145
- log#error " this commit contains no files" ;
146
- []
147
- | files ->
148
- let rules = cfg.prefix_rules.rules in
149
- let channels =
150
- files
151
- |> List. map ~f: (fun file ->
152
- match filter_commit rules file.filename with
153
- | [] -> default
154
- | l -> l)
155
- in
156
- List. dedup_and_sort ~compare: String. compare (List. concat channels)
180
+ let names = List. map ~f: (fun f -> f.filename) files in
181
+ match unique_chans_of_files cfg.prefix_rules.rules names with
182
+ | _ :: _ as xs -> xs
183
+ | [] -> Option. to_list cfg.prefix_rules.default
157
184
158
185
let hide_cancelled (notification : status_notification ) cfg =
159
- let is_cancelled_status =
186
+ let find_cancelled status_state =
187
+ match status_state with
188
+ | Config. Cancelled r -> Some r
189
+ | _ -> None
190
+ in
191
+ let regexp_opt = List. find_map cfg.status_rules.status ~f: find_cancelled in
192
+ match regexp_opt with
193
+ | None -> false
194
+ | Some regexp ->
160
195
let { state; description; _ } = notification in
161
- let r = Re.Str. regexp_case_fold " ^ \\ (Build #[0-9]+ canceled by .+ \\ |Failed (exit status 255) \\ )$ " in
162
- match description, state with
196
+ let r = Re.Str. regexp_case_fold regexp in
197
+ ( match description, state with
163
198
| Some s , Failure when Re.Str. string_match r s 0 -> true
164
199
| _ -> false
165
- in
166
- is_cancelled_status && cfg.suppress_cancelled_events
200
+ )
167
201
168
202
let hide_success (n : status_notification ) (ctx : Context.t ) =
203
+ match List. exists ctx.cfg.status_rules.status ~f: (Poly. equal Config. HideConsecutiveSuccess ) with
204
+ | false -> false
205
+ | true ->
169
206
match n.state with
170
207
| Success ->
171
208
List. exists
@@ -179,21 +216,37 @@ let hide_success (n : status_notification) (ctx : Context.t) =
179
216
let partition_status (ctx : Context.t ) (n : status_notification ) =
180
217
let cfg = ctx.cfg in
181
218
let get_commit_info () =
182
- match % lwt Github. generate_query_commmit cfg ~url: n.commit.url ~sha: n.commit.sha with
183
- | None ->
184
- let default = Option. value_map cfg.prefix_rules.default ~default: [] ~f: (fun webhook -> [ webhook ]) in
185
- Lwt. return default
186
- | Some commit ->
187
- match
188
- List. exists n.branches ~f: (fun { name } -> is_main_merge_message ~msg: commit.commit.message ~branch: name cfg)
189
- with
219
+ let default () = Lwt. return @@ Option. to_list cfg.prefix_rules.default in
220
+ match cfg.main_branch_name with
221
+ | None -> default ()
222
+ | Some main_branch_name ->
223
+ (* non-main branch build notifications go to default channel to reduce spam in topic channels *)
224
+ match List. exists n.branches ~f: (fun { name } -> String. equal name main_branch_name) with
225
+ | false -> default ()
190
226
| true ->
191
- log#info " main branch merge, ignoring status event %s: %s" n.context (first_line commit.commit.message);
192
- Lwt. return []
193
- | false -> Lwt. return (partition_commit cfg commit.files)
227
+ ( match % lwt Github. generate_query_commmit cfg ~url: n.commit.url ~sha: n.commit.sha with
228
+ | None -> default ()
229
+ | Some commit ->
230
+ (*
231
+ match
232
+ List.exists n.branches ~f:(fun { name } -> is_main_merge_message ~msg:commit.commit.message ~branch:name cfg)
233
+ with
234
+ | true ->
235
+ log#info "main branch merge, ignoring status event %s: %s" n.context (first_line commit.commit.message);
236
+ Lwt.return []
237
+ | false ->
238
+ *)
239
+ Lwt. return (partition_commit cfg commit.files)
240
+ )
194
241
in
195
242
let res =
196
- match List. exists cfg.status_rules.status ~f: (Poly. equal n.state) with
243
+ match
244
+ List. exists cfg.status_rules.status ~f: (fun x ->
245
+ match x with
246
+ | State s -> Poly. equal s n.state
247
+ | HideConsecutiveSuccess -> Poly. equal Success n.state
248
+ | _ -> false )
249
+ with
197
250
| false -> Lwt. return []
198
251
| true ->
199
252
match List. exists ~f: id [ hide_cancelled n cfg; hide_success n ctx ] with
@@ -210,17 +263,17 @@ let partition_status (ctx : Context.t) (n : status_notification) =
210
263
res
211
264
212
265
let partition_commit_comment cfg n =
213
- let default = Option. value_map cfg.prefix_rules.default ~default: [] ~f: ( fun webhook -> [ webhook ]) in
266
+ let default = Option. to_list cfg.prefix_rules.default in
214
267
match n.comment.path with
215
268
| None ->
216
269
( match % lwt Github. generate_commit_from_commit_comment cfg n with
217
270
| None -> Lwt. return default
218
271
| Some commit -> Lwt. return (partition_commit cfg commit.files)
219
272
)
220
273
| Some p ->
221
- match filter_commit cfg.prefix_rules.rules p with
222
- | [] -> Lwt. return default
223
- | l -> Lwt. return l
274
+ match chan_of_file cfg.prefix_rules.rules p with
275
+ | None -> Lwt. return default
276
+ | Some chan -> Lwt. return [ chan ]
224
277
225
278
let generate_notifications (ctx : Context.t ) req =
226
279
let cfg = ctx.cfg in
@@ -248,7 +301,7 @@ let generate_notifications (ctx : Context.t) req =
248
301
Lwt. return notifs
249
302
| Status n ->
250
303
let % lwt webhooks = partition_status ctx n in
251
- let notifs = List. map ~f: (fun webhook -> webhook, generate_status_notification n) webhooks in
304
+ let notifs = List. map ~f: (fun webhook -> webhook, generate_status_notification cfg n) webhooks in
252
305
Lwt. return notifs
253
306
| _ -> Lwt. return []
254
307
0 commit comments