mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[FIX] runbot_merge: format_patch if --no-prefix
Turns out you can configure format-patch with `--no-prefix` and some people (*cough cough* mat) have that in their standard setup, so the assumption of needing to strip 1 level of prefix does not necessarily hold. Also fix a few more issues: - some people (*cough cough* still mat) also use `-n` by default, which adds the series sequence (`n/m`) even for a single patch, handle that correctly - logging patch application errors is pretty useful when patching fails and I'm trying to get the information via logs, do that - especially when I decide to add error messages to tracking *but forgot to show the chatter by default*, fix that as well The commit-based patcher worked first try, and patch-based would have worked too if not for those meddling kids. In the future it might be a good idea to reify the stripping level (`-p`) on the patch object though, and maybe provide a computed preview of the list of files to patch, so issues are easier for the operator to diagnose.
This commit is contained in:
parent
a12e593fba
commit
5441ba12ae
@ -28,8 +28,8 @@ _logger = logging.getLogger(__name__)
|
|||||||
FILE_PATTERN = re.compile(r"""
|
FILE_PATTERN = re.compile(r"""
|
||||||
# paths with spaces don't work well as the path can be followed by a timestamp
|
# paths with spaces don't work well as the path can be followed by a timestamp
|
||||||
# (in an unspecified format?)
|
# (in an unspecified format?)
|
||||||
---\x20a/(?P<file_from>\S+)(:?\s.*)?\n
|
---\x20(?P<prefix_a>a/)?(?P<file_from>\S+)(:?\s.*)?\n
|
||||||
\+\+\+\x20b/(?P<file_to>\S+)(:?\s.*)?\n
|
\+\+\+\x20(?P<prefix_b>b/)?(?P<file_to>\S+)(:?\s.*)?\n
|
||||||
@@\x20-(\d+(,\d+)?)\x20\+(\d+(,\d+)?)\x20@@ # trailing garbage
|
@@\x20-(\d+(,\d+)?)\x20\+(\d+(,\d+)?)\x20@@ # trailing garbage
|
||||||
""", re.VERBOSE)
|
""", re.VERBOSE)
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ def parse_format_patch(p: Patch) -> ParseResult:
|
|||||||
|
|
||||||
name, email = parseaddr(m['from'])
|
name, email = parseaddr(m['from'])
|
||||||
author = (name, email, m['date'])
|
author = (name, email, m['date'])
|
||||||
msg = m['subject'].removeprefix('[PATCH] ')
|
msg = re.sub(r'^\[PATCH( \d+/\d+)?\] ', '', m['subject'])
|
||||||
body, _, rest = m.get_content().partition('---\n')
|
body, _, rest = m.get_content().partition('---\n')
|
||||||
if body:
|
if body:
|
||||||
msg += '\n\n' + body
|
msg += '\n\n' + body
|
||||||
@ -225,12 +225,16 @@ class Patch(models.Model):
|
|||||||
patch.repository.name,
|
patch.repository.name,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
c = patch._apply_commit(r) if patch.commit else patch._apply_patch(r)
|
if patch.commit:
|
||||||
|
c = patch._apply_commit(r)
|
||||||
|
else:
|
||||||
|
c = patch._apply_patch(r)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(e, PatchFailure):
|
if isinstance(e, PatchFailure):
|
||||||
subject = "Unable to apply patch"
|
subject = "Unable to apply patch"
|
||||||
else:
|
else:
|
||||||
subject = "Unknown error while trying to apply patch"
|
subject = "Unknown error while trying to apply patch"
|
||||||
|
_logger.error("%s:\n%s", subject, str(e))
|
||||||
patch.message_post(body=plaintext2html(e), subject=subject)
|
patch.message_post(body=plaintext2html(e), subject=subject)
|
||||||
continue
|
continue
|
||||||
# `.` is the local "remote", so this updates target to c
|
# `.` is the local "remote", so this updates target to c
|
||||||
@ -276,10 +280,17 @@ class Patch(models.Model):
|
|||||||
|
|
||||||
def _apply_patch(self, r: git.Repo) -> str:
|
def _apply_patch(self, r: git.Repo) -> str:
|
||||||
p = self._parse_patch()
|
p = self._parse_patch()
|
||||||
files = dict.fromkeys(
|
files = {}
|
||||||
(m['file_to'] for m in FILE_PATTERN.finditer(p.patch)),
|
def reader(_r, f):
|
||||||
lambda _r, f: pathlib.Path(tmpdir, f).read_text(encoding="utf-8"),
|
return pathlib.Path(tmpdir, f).read_text(encoding="utf-8")
|
||||||
)
|
|
||||||
|
prefix = 0
|
||||||
|
for m in FILE_PATTERN.finditer(p.patch):
|
||||||
|
if not prefix and m['prefix_a'] and m['prefix_b']:
|
||||||
|
prefix = 1
|
||||||
|
|
||||||
|
files[m['file_to']] = reader
|
||||||
|
|
||||||
archiver = r.stdout(True)
|
archiver = r.stdout(True)
|
||||||
# if the parent is checked then we can't get rid of the kwarg and popen doesn't support it
|
# if the parent is checked then we can't get rid of the kwarg and popen doesn't support it
|
||||||
archiver._config.pop('check', None)
|
archiver._config.pop('check', None)
|
||||||
@ -289,7 +300,7 @@ class Patch(models.Model):
|
|||||||
tempfile.TemporaryDirectory() as tmpdir:
|
tempfile.TemporaryDirectory() as tmpdir:
|
||||||
tf.extractall(tmpdir)
|
tf.extractall(tmpdir)
|
||||||
patch = subprocess.run(
|
patch = subprocess.run(
|
||||||
['patch', '-p1', '-d', tmpdir],
|
['patch', f'-p{prefix}', '-d', tmpdir],
|
||||||
input=p.patch,
|
input=p.patch,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
|
@ -23,6 +23,51 @@ index 000000000000..000000000000 100644
|
|||||||
+2
|
+2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
FORMAT_PATCH_XMO = """\
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: 3 Discos Down <bar@example.org>
|
||||||
|
Date: Sat, 24 Apr 2021 17:09:14 +0000
|
||||||
|
Subject: [PATCH] [I18N] whop
|
||||||
|
|
||||||
|
whop whop
|
||||||
|
---
|
||||||
|
b | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/b b/b
|
||||||
|
index 000000000000..000000000000 100644
|
||||||
|
--- a/b
|
||||||
|
+++ b/b
|
||||||
|
@@ -1,1 +1,1 @@
|
||||||
|
-1
|
||||||
|
+2
|
||||||
|
--
|
||||||
|
2.46.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
# slightly different format than the one I got, possibly because older?
|
||||||
|
FORMAT_PATCH_MAT = """\
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: 3 Discos Down <bar@example.org>
|
||||||
|
Date: Sat, 24 Apr 2021 17:09:14 +0000
|
||||||
|
Subject: [PATCH 1/1] [I18N] whop
|
||||||
|
|
||||||
|
whop whop
|
||||||
|
---
|
||||||
|
b | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git b b
|
||||||
|
index 000000000000..000000000000 100644
|
||||||
|
--- b
|
||||||
|
+++ b
|
||||||
|
@@ -1,1 +1,1 @@
|
||||||
|
-1
|
||||||
|
+2
|
||||||
|
--
|
||||||
|
2.34.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def _setup(repo):
|
def _setup(repo):
|
||||||
@ -148,41 +193,34 @@ def test_apply_udiff(env, project, repo, users):
|
|||||||
assert not p.active
|
assert not p.active
|
||||||
|
|
||||||
|
|
||||||
def test_apply_format_patch(env, project, repo, users):
|
@pytest.mark.parametrize('patch', [
|
||||||
|
pytest.param(FORMAT_PATCH_XMO, id='xmo'),
|
||||||
|
pytest.param(FORMAT_PATCH_MAT, id='mat'),
|
||||||
|
])
|
||||||
|
def test_apply_format_patch(env, project, repo, users, patch):
|
||||||
p = env['runbot_merge.patch'].create({
|
p = env['runbot_merge.patch'].create({
|
||||||
'target': project.branch_ids.id,
|
'target': project.branch_ids.id,
|
||||||
'repository': project.repo_ids.id,
|
'repository': project.repo_ids.id,
|
||||||
'patch': """\
|
'patch': patch,
|
||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: 3 Discos Down <bar@example.org>
|
|
||||||
Date: Sat, 24 Apr 2021 17:09:14 +0000
|
|
||||||
Subject: [PATCH] whop
|
|
||||||
|
|
||||||
whop whop
|
|
||||||
---
|
|
||||||
b | 2 +-
|
|
||||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/b b/b
|
|
||||||
index 000000000000..000000000000 100644
|
|
||||||
--- a/b
|
|
||||||
+++ b/b
|
|
||||||
@@ -1,1 +1,1 @@
|
|
||||||
-1
|
|
||||||
+2
|
|
||||||
--
|
|
||||||
2.46.2
|
|
||||||
""",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
env.run_crons()
|
env.run_crons()
|
||||||
|
|
||||||
|
bot = env['res.users'].browse((1,))
|
||||||
|
assert p.message_ids[::-1].mapped(lambda m: (
|
||||||
|
m.author_id.display_name,
|
||||||
|
m.body,
|
||||||
|
list(map(read_tracking_value, m.tracking_value_ids)),
|
||||||
|
)) == [
|
||||||
|
(p.create_uid.partner_id.display_name, '<p>Unstaged direct-application patch created</p>', []),
|
||||||
|
(bot.partner_id.display_name, "", [('active', 1, 0)]),
|
||||||
|
]
|
||||||
HEAD = repo.commit('master')
|
HEAD = repo.commit('master')
|
||||||
assert repo.read_tree(HEAD) == {
|
assert repo.read_tree(HEAD) == {
|
||||||
'a': '2',
|
'a': '2',
|
||||||
'b': '2\n',
|
'b': '2\n',
|
||||||
}
|
}
|
||||||
assert HEAD.message == "whop\n\nwhop whop"
|
assert HEAD.message == "[I18N] whop\n\nwhop whop"
|
||||||
assert HEAD.author['name'] == "3 Discos Down"
|
assert HEAD.author['name'] == "3 Discos Down"
|
||||||
assert HEAD.author['email'] == "bar@example.org"
|
assert HEAD.author['email'] == "bar@example.org"
|
||||||
assert not p.active
|
assert not p.active
|
||||||
|
@ -143,6 +143,10 @@
|
|||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
<div class="oe_chatter">
|
||||||
|
<field name="message_follower_ids" widget="mail_followers"/>
|
||||||
|
<field name="message_ids" widget="mail_thread"/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
Loading…
Reference in New Issue
Block a user