Skip to content

Commit 3ea959f

Browse files
committed
Add missing code to remove function
1 parent 514a1c5 commit 3ea959f

File tree

71 files changed

+2078
-125
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2078
-125
lines changed

process_pr_v2.py

Lines changed: 100 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,12 +1822,10 @@ def should_ignore_issue(repo_config: types.ModuleType, repo: Any, issue: Any) ->
18221822
if issue.title and re.match(BUILD_REL, issue.title):
18231823
return True
18241824

1825-
# Check if body has ignore marker on first line
1825+
# Check if body has ignore marker anywhere
18261826
if issue.body:
1827-
# Get first non-blank line
18281827
try:
1829-
first_line = issue.body.split("\n", 1)[0].strip()
1830-
if first_line and RE_CMS_BOT_IGNORE.search(first_line):
1828+
if RE_CMS_BOT_IGNORE.search(issue.body):
18311829
return True
18321830
except (AttributeError, TypeError):
18331831
# issue.body may be None or not a string
@@ -1836,27 +1834,14 @@ def should_ignore_issue(repo_config: types.ModuleType, repo: Any, issue: Any) ->
18361834
return False
18371835

18381836

1839-
def should_ignore_pr_body(pr_body: str) -> bool:
1840-
"""
1841-
Check if PR should be ignored based on description only.
1842-
1843-
Returns True if first non-blank line matches <cms-bot></cms-bot>.
1844-
1845-
Note: For full ignore checking including IGNORE_ISSUES and BUILD_REL,
1846-
use should_ignore_issue() instead.
1847-
"""
1848-
first_line, _ = extract_command_line(pr_body or "")
1849-
if not first_line:
1850-
return False
1851-
return bool(RE_CMS_BOT_IGNORE.match(first_line))
1852-
1853-
18541837
def should_notify_without_at(pr_body: str) -> bool:
18551838
"""
18561839
Check if notifications should omit @ symbol.
18571840
1858-
Returns True if <notify></notify> is found anywhere in issue body.
1841+
Returns True if body contains <notify></notify> anywhere.
18591842
"""
1843+
if not pr_body:
1844+
return False
18601845
return bool(RE_NOTIFY_NO_AT.search(pr_body))
18611846

18621847

@@ -1914,6 +1899,9 @@ class PRContext:
19141899
pending_reactions: Dict[int, str] = field(default_factory=dict) # comment_id -> reaction
19151900
holds: List[Hold] = field(default_factory=list) # Active holds on the PR
19161901
pending_labels: Set[str] = field(default_factory=set) # Labels to add
1902+
pending_labels_to_remove: Set[str] = field(
1903+
default_factory=set
1904+
) # Labels to remove (from type command)
19171905
signing_categories: Set[str] = field(default_factory=set) # Categories requiring signatures
19181906
manually_assigned_categories: Set[str] = field(
19191907
default_factory=set
@@ -3229,60 +3217,108 @@ def handle_file_count_override(context: PRContext, match: re.Match, comment: Any
32293217

32303218
@command(
32313219
"type",
3232-
r"^type (?P<label>[\w-]+)$",
3233-
description="Add a type label to the PR/Issue",
3220+
r"^type (?P<labels>[-+]?[\w-]+(,[-+]?[\w-]+)*)$",
3221+
description="Add/remove type labels on the PR/Issue",
32343222
)
32353223
def handle_type(context: PRContext, match: re.Match, comment: Any) -> bool:
32363224
"""
3237-
Handle type <label> command.
3225+
Handle type <labels> command.
32383226
3239-
Adds a non-blocking label to the PR/Issue. The label must be defined
3227+
Adds or removes non-blocking labels on the PR/Issue. Labels must be defined
32403228
in TYPE_COMMANDS to be valid.
32413229
32423230
Labels have two modes:
32433231
- 'type': Only the last one applies (replaces previous type labels)
32443232
- 'mtype': Accumulates (multiple can coexist)
32453233
3234+
Prefixes:
3235+
- No prefix or '+': Add the label
3236+
- '-': Remove the label
3237+
32463238
Syntax:
3247-
type <label>
3239+
type <label>[,<label>...]
32483240
32493241
Examples:
32503242
type bug-fix
3251-
type new-feature
3252-
type documentation
3243+
type new-feature,urgent
3244+
type +bug-fix,-documentation
3245+
type -obsolete
32533246
"""
32543247
user = comment.user.login
3255-
label = match.group("label")
3248+
labels_str = match.group("labels")
3249+
labels_raw = [l.strip() for l in labels_str.split(",")]
3250+
3251+
errors = []
3252+
labels_to_add = []
3253+
labels_to_remove = []
3254+
3255+
for label_raw in labels_raw:
3256+
if not label_raw:
3257+
continue
32563258

3257-
# Validate label is in TYPE_COMMANDS
3258-
if label not in TYPE_COMMANDS:
3259+
# Parse prefix
3260+
if label_raw.startswith("+"):
3261+
label = label_raw[1:]
3262+
action = "add"
3263+
elif label_raw.startswith("-"):
3264+
label = label_raw[1:]
3265+
action = "remove"
3266+
else:
3267+
label = label_raw
3268+
action = "add"
3269+
3270+
# Validate label is in TYPE_COMMANDS
3271+
if label not in TYPE_COMMANDS:
3272+
errors.append(label)
3273+
continue
3274+
3275+
if action == "add":
3276+
labels_to_add.append(label)
3277+
else:
3278+
labels_to_remove.append(label)
3279+
3280+
# Report errors if any
3281+
if errors:
32593282
valid_labels = ", ".join(sorted(TYPE_COMMANDS.keys()))
3260-
context.messages.append(f"Invalid type label '{label}'. Valid labels: {valid_labels}")
3261-
logger.warning(f"Invalid type label: {label}")
3283+
context.messages.append(
3284+
f"Invalid type label(s): {', '.join(errors)}. Valid labels: {valid_labels}"
3285+
)
3286+
logger.warning(f"Invalid type labels: {errors}")
32623287
return False
32633288

3264-
# Get label type (type or mtype)
3265-
# TYPE_COMMANDS[label] = [color, regexp, label_type]
3266-
label_info = TYPE_COMMANDS[label]
3267-
label_type = label_info[2] if len(label_info) > 2 else "mtype"
3268-
3269-
if label_type == "type":
3270-
# 'type' labels replace previous - remove other 'type' labels
3271-
type_labels_to_remove = set()
3272-
for existing_label in context.pending_labels:
3273-
if existing_label in TYPE_COMMANDS:
3274-
existing_info = TYPE_COMMANDS[existing_label]
3275-
existing_type = existing_info[2] if len(existing_info) > 2 else "mtype"
3276-
if existing_type == "type":
3277-
type_labels_to_remove.add(existing_label)
3278-
3279-
context.pending_labels -= type_labels_to_remove
3280-
if type_labels_to_remove:
3281-
logger.debug(f"Removed previous type labels: {type_labels_to_remove}")
3282-
3283-
# Add the new label
3284-
context.pending_labels.add(label)
3285-
logger.info(f"Type label '{label}' ({label_type}) added by {user}")
3289+
# Process labels to add
3290+
for label in labels_to_add:
3291+
# Get label type (type or mtype)
3292+
# TYPE_COMMANDS[label] = [color, regexp, label_type]
3293+
label_info = TYPE_COMMANDS[label]
3294+
label_type = label_info[2] if len(label_info) > 2 else "mtype"
3295+
3296+
if label_type == "type":
3297+
# 'type' labels replace previous - remove other 'type' labels
3298+
type_labels_to_remove = set()
3299+
for existing_label in context.pending_labels:
3300+
if existing_label in TYPE_COMMANDS:
3301+
existing_info = TYPE_COMMANDS[existing_label]
3302+
existing_type = existing_info[2] if len(existing_info) > 2 else "mtype"
3303+
if existing_type == "type":
3304+
type_labels_to_remove.add(existing_label)
3305+
3306+
context.pending_labels -= type_labels_to_remove
3307+
if type_labels_to_remove:
3308+
logger.debug(f"Removed previous type labels: {type_labels_to_remove}")
3309+
3310+
# Add the new label (also remove from pending removal if it was there)
3311+
context.pending_labels.add(label)
3312+
context.pending_labels_to_remove.discard(label)
3313+
logger.info(f"Type label '{label}' ({label_type}) added by {user}")
3314+
3315+
# Process labels to remove
3316+
for label in labels_to_remove:
3317+
# Remove from pending adds if it was there
3318+
context.pending_labels.discard(label)
3319+
# Mark for removal from existing labels
3320+
context.pending_labels_to_remove.add(label)
3321+
logger.info(f"Type label '{label}' marked for removal by {user}")
32863322

32873323
# Note: type commands are not cached - only signatures (+1/-1) are cached
32883324
return True
@@ -5751,6 +5787,11 @@ def update_pr_status(context: PRContext) -> Tuple[Set[str], Set[str]]:
57515787
if label not in old_labels:
57525788
labels_to_add.add(label)
57535789

5790+
# Remove labels marked for removal via 'type -label' command
5791+
for label in context.pending_labels_to_remove:
5792+
if label in old_labels:
5793+
labels_to_remove.add(label)
5794+
57545795
# Keep labels that aren't being removed
57555796
for label in old_labels:
57565797
if label not in labels_to_remove:
@@ -6757,6 +6798,7 @@ def process_pr(
67576798
"pr_state": None,
67586799
"categories": {},
67596800
"holds": [],
6801+
"labels": [],
67606802
"messages": [],
67616803
"tests_triggered": [],
67626804
}
@@ -6776,6 +6818,7 @@ def process_pr(
67766818
"pr_state": None,
67776819
"categories": {},
67786820
"holds": [],
6821+
"labels": [],
67796822
"messages": [],
67806823
"tests_triggered": [],
67816824
}
@@ -6881,6 +6924,7 @@ def process_pr(
68816924
"pr_state": None,
68826925
"categories": {},
68836926
"holds": [],
6927+
"labels": [],
68846928
"messages": [],
68856929
"tests_triggered": [],
68866930
}
@@ -6937,6 +6981,7 @@ def process_pr(
69376981
"pr_state": None,
69386982
"categories": {},
69396983
"holds": [],
6984+
"labels": [],
69406985
"messages": [],
69416986
"tests_triggered": [],
69426987
}
@@ -7123,9 +7168,6 @@ def process_pr(
71237168
pre_checks = signing_checks.pre_checks
71247169
extra_checks = signing_checks.extra_checks
71257170

7126-
# Collect all labels
7127-
all_labels = set(context.pending_labels)
7128-
71297171
# Return results
71307172
return {
71317173
"pr_number": issue.number,
@@ -7144,7 +7186,7 @@ def process_pr(
71447186
for name, state in category_states.items()
71457187
},
71467188
"holds": [{"category": h.category, "user": h.user} for h in context.holds],
7147-
"labels": sorted(all_labels),
7189+
"labels": sorted(new_labels),
71487190
"messages": context.messages,
71497191
"status_message": status_message,
71507192
"test_params": context.test_params,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"test_name": "test_add_then_remove_same_label",
3+
"action_count": 6,
4+
"actions": [
5+
{
6+
"sequence": 1,
7+
"action": "create_reaction",
8+
"details": {
9+
"comment_id": 100,
10+
"reaction": "+1"
11+
}
12+
},
13+
{
14+
"sequence": 2,
15+
"action": "create_reaction",
16+
"details": {
17+
"comment_id": 101,
18+
"reaction": "+1"
19+
}
20+
},
21+
{
22+
"sequence": 3,
23+
"action": "add_labels",
24+
"details": {
25+
"issue_number": 1,
26+
"labels": [
27+
"core-pending",
28+
"pending-signatures",
29+
"tests-pending"
30+
]
31+
}
32+
},
33+
{
34+
"sequence": 4,
35+
"action": "create_comment",
36+
"details": {
37+
"issue_number": 1,
38+
"body": "A new Pull Request was created by @testuser.\n\n@Dr15Jones, @makortel, @smuzaffar can you please review it and eventually sign/assign? Thanks.\ncms-bot commands are listed <a href=\"http://cms-sw.github.io/cms-bot-cmssw-issues.html\">here</a>\n\n<!--welcome-->"
39+
}
40+
},
41+
{
42+
"sequence": 5,
43+
"action": "create_status",
44+
"details": {
45+
"sha": "commit123",
46+
"state": "pending",
47+
"target_url": "",
48+
"description": "Waiting for authorized user to issue the test command.",
49+
"context": "bot/1/jenkins"
50+
}
51+
},
52+
{
53+
"sequence": 6,
54+
"action": "create_comment",
55+
"details": {
56+
"issue_number": 1,
57+
"body": "cms-bot internal usage<!-- {\"comments\":{},\"emoji\":{\"100\":\"+1\",\"101\":\"+1\"},\"fv\":{\"Package/Core/main.py::file_sha_123\":{\"cats\":[\"core\"],\"ts\":\"2025-12-12T11:00:00Z\"}}} -->"
58+
}
59+
}
60+
]
61+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"test_name": "test_comma_separated_labels",
3+
"action_count": 5,
4+
"actions": [
5+
{
6+
"sequence": 1,
7+
"action": "create_reaction",
8+
"details": {
9+
"comment_id": 100,
10+
"reaction": "+1"
11+
}
12+
},
13+
{
14+
"sequence": 2,
15+
"action": "add_labels",
16+
"details": {
17+
"issue_number": 1,
18+
"labels": [
19+
"core-pending",
20+
"documentation",
21+
"pending-signatures",
22+
"root",
23+
"tests-pending"
24+
]
25+
}
26+
},
27+
{
28+
"sequence": 3,
29+
"action": "create_comment",
30+
"details": {
31+
"issue_number": 1,
32+
"body": "A new Pull Request was created by @testuser.\n\n@Dr15Jones, @makortel, @smuzaffar can you please review it and eventually sign/assign? Thanks.\ncms-bot commands are listed <a href=\"http://cms-sw.github.io/cms-bot-cmssw-issues.html\">here</a>\n\n<!--welcome-->"
33+
}
34+
},
35+
{
36+
"sequence": 4,
37+
"action": "create_status",
38+
"details": {
39+
"sha": "commit123",
40+
"state": "pending",
41+
"target_url": "",
42+
"description": "Waiting for authorized user to issue the test command.",
43+
"context": "bot/1/jenkins"
44+
}
45+
},
46+
{
47+
"sequence": 5,
48+
"action": "create_comment",
49+
"details": {
50+
"issue_number": 1,
51+
"body": "cms-bot internal usage<!-- {\"comments\":{},\"emoji\":{\"100\":\"+1\"},\"fv\":{\"Package/Core/main.py::file_sha_123\":{\"cats\":[\"core\"],\"ts\":\"2025-12-12T11:00:00Z\"}}} -->"
52+
}
53+
}
54+
]
55+
}

0 commit comments

Comments
 (0)