Mark as ready removes only one Draft: prefix when the title has several
Summary
When a merge request title contains the draft prefix more than once (for example
Draft: Draft: Fix a thing), the Mark as ready action removes only the first prefix. The MR
stays in draft state, and the user has to click Mark as ready again for each extra prefix.
Steps to reproduce
- Create a merge request with the title
Draft: Draft: Fix a thing. - Select Mark as ready.
- The title becomes
Draft: Fix a thingand the MR is still a draft.
A doubled prefix is easy to create by accident: glab mr create --draft --title "Draft: My title"
sends Draft: My title while also setting the draft flag, and the result is stored as
Draft: Draft: My title. The API accepts a doubled prefix directly too.
Actual vs expected
| Actual | one prefix is stripped per action; the MR remains a draft |
| Expected | Mark as ready clears the draft state in one action, whatever the prefix count |
Root cause
MergeRequest::DRAFT_REGEX (app/models/merge_request.rb:935) looks like it already handles
repeats, because of the +:
DRAFT_REGEX = /\A*#{Gitlab::Regex.merge_request_draft}+\s*/iBut Gitlab::Regex.merge_request_draft carries its own \A anchor, so the compiled pattern is:
/\A*(?-mix:\A(?i)(\[draft\]|\(draft\)|draft:))+\s*/iThe + applies to a group that starts with \A. A second repetition would have to match at the
start of the string as well, which is impossible — so the + can never match more than once.
MergeRequest.draftless_title is a single String#sub, so exactly one prefix is removed.
Mark as ready goes through app/services/merge_requests/base_service.rb:181:
when 'ready' then MergeRequest.draftless_title(title)The resulting title still matches DRAFT_REGEX, so MergeRequest#draft? stays true.
The outer \A* is also degenerate — a quantified anchor, which is almost certainly not what was
intended.
Verified behaviour
Run against master (bundle exec rails runner):
| title | after draftless_title |
still a draft? |
|---|---|---|
Draft: Fix a thing |
Fix a thing |
no |
Draft: Draft: Fix a thing |
Draft: Fix a thing |
yes |
Draft:Draft: Fix a thing |
Draft: Fix a thing |
yes |
Draft: Draft: Draft: Fix a thing |
Draft: Draft: Fix a thing |
yes |
[Draft] Draft: Fix a thing |
Draft: Fix a thing |
yes |
The no-space case (Draft:Draft:) is the interesting one: it shows the blocker is the \A inside
the interpolated pattern, not the whitespace between prefixes.
Possible fix
Strip repeated prefixes rather than one. Either loop sub until the title no longer matches, or
build the regex so the anchor sits outside the repeated group, roughly:
DRAFT_REGEX = /\A(?:#{Gitlab::Regex.merge_request_draft_without_anchor}\s*)+/iBoth draft? and draftless_title should then agree for any prefix count. Worth adding specs for
the doubled and mixed-delimiter cases in the table above.
Low priority — cosmetic annoyance rather than data loss — but it is confusing, because the button appears not to work.