diff --git a/runbot_merge/models/pull_requests.py b/runbot_merge/models/pull_requests.py index 998ea02d..3e05e020 100644 --- a/runbot_merge/models/pull_requests.py +++ b/runbot_merge/models/pull_requests.py @@ -2232,7 +2232,9 @@ class Message: msg, handle_break = (msg, False) if isinstance(msg, str) else (msg.message, True) headers = [] body = [] - for line in reversed(msg.splitlines()): + # don't process the title (first line) of the commit message + msg = msg.splitlines() + for line in reversed(msg[1:]): if maybe_setex: # NOTE: actually slightly more complicated: it's a SETEX heading # only if preceding line(s) can be interpreted as a @@ -2268,6 +2270,10 @@ class Message: body.append(line) in_headers = False + # if there are non-title body lines, add a separation after the title + if body and body[-1]: + body.append('') + body.append(msg[0]) return cls('\n'.join(reversed(body)), Headers(reversed(headers))) def __init__(self, body, headers=None): diff --git a/runbot_merge/tests/test_basic.py b/runbot_merge/tests/test_basic.py index 3d756d0a..022e214f 100644 --- a/runbot_merge/tests/test_basic.py +++ b/runbot_merge/tests/test_basic.py @@ -1803,7 +1803,7 @@ removed repo.make_commits(root, Commit('Commit\n\nfirst\n***\nsecond', tree={'a': 'b'}), ref=f'heads/change') pr = repo.make_pr(title="PR", body=f'first\n***\nsecond', - target='master', head=f'change') + target='master', head='change') repo.post_status(pr.head, 'success', 'legal/cla') repo.post_status(pr.head, 'success', 'ci/runbot') pr.post_comment('hansen r+', config['role_reviewer']['token']) @@ -1827,6 +1827,44 @@ removed Signed-off-by: {reviewer} """).strip(), "squashed / rebased messages should not be stripped" + def test_title_no_edit(self, repo, env, users, config): + """The first line of a commit message should not be taken in account for + rewriting, especially as it can be untagged and interpreted as a + pseudo-header + """ + with repo: + repo.make_commits(None, Commit("0", tree={'a': '1'}), ref='heads/master') + repo.make_commits( + 'master', + Commit('Some: thing\n\nis odd', tree={'b': '1'}), + Commit('thing: thong', tree={'b': '2'}), + ref='heads/change') + + pr = repo.make_pr(target='master', head='change') + repo.post_status(pr.head, 'success', 'legal/cla') + repo.post_status(pr.head, 'success', 'ci/runbot') + pr.post_comment('hansen rebase-ff r+', config['role_reviewer']['token']) + env.run_crons() + + pr_id = to_pr(env, pr) + assert pr_id.staging_id # check PR is staged + + + reviewer = get_partner(env, users["reviewer"]).formatted_email + staging_head = repo.commit('staging.master') + assert staging_head.message == f"""\ +thing: thong + +closes {pr_id.display_name} + +Signed-off-by: {reviewer}""" + assert repo.commit(staging_head.parents[0]).message == f"""\ +Some: thing + +is odd + +Part-of: {pr_id.display_name}""" + def test_pr_mergehead(self, repo, env, config): """ if the head of the PR is a merge commit and one of the parents is in the target, replicate the merge commit instead of merging