mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[ADD] runbot_merge: support for file creation in patch
Had been left out of the original impl, but turns out it might be useful e.g. when a new lang gets added to the i18n export. Fixes #1042
This commit is contained in:
parent
54337aeccc
commit
1097c5f19e
@ -263,12 +263,17 @@ class Repo:
|
||||
f, _, local = f.rpartition("/")
|
||||
# tree to update, `{tree}:` works as an alias for tree
|
||||
lstree = repo.ls_tree(f"{tree}:{f}").stdout.splitlines(keepends=False)
|
||||
new_tree = "".join(
|
||||
new_tree = []
|
||||
seen = False
|
||||
for mode, typ, sha, name in map(methodcaller("split", None, 3), lstree):
|
||||
if name == local:
|
||||
sha = oid
|
||||
seen = True
|
||||
# tab before name is critical to the format
|
||||
f"{mode} {typ} {oid if name == local else sha}\t{name}\n"
|
||||
for mode, typ, sha, name in map(methodcaller("split", None, 3), lstree)
|
||||
)
|
||||
oid = repo.with_config(input=new_tree, check=True).mktree().stdout.strip()
|
||||
new_tree.append(f"{mode} {typ} {sha}\t{name}\n")
|
||||
if not seen:
|
||||
new_tree.append(f"100644 blob {oid}\t{local}\n")
|
||||
oid = repo.with_config(input="".join(new_tree), check=True).mktree().stdout.strip()
|
||||
tree = oid
|
||||
return tree
|
||||
|
||||
|
@ -6,6 +6,7 @@ overhead, or FBI backdoors oh wait forget about that last one.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import pathlib
|
||||
import re
|
||||
@ -179,7 +180,7 @@ class Patch(models.Model):
|
||||
p.committer = f"{name} <{email}>"
|
||||
p.commitdate = date
|
||||
p.file_ids = File.concat(*(
|
||||
File.new({'name': m['file_from']})
|
||||
File.new({'name': m['file_to']})
|
||||
for m in FILE_PATTERN.finditer(p.patch)
|
||||
))
|
||||
p.message = r.message
|
||||
@ -233,7 +234,7 @@ class Patch(models.Model):
|
||||
has_files = False
|
||||
for m in FILE_PATTERN.finditer(patch.patch):
|
||||
has_files = True
|
||||
if m['file_from'] != m['file_to']:
|
||||
if m['file_from'] != m['file_to'] and m['file_from'] != '/dev/null':
|
||||
raise ValidationError("Only patches updating a file in place are supported, not creation, removal, or renaming.")
|
||||
if not has_files:
|
||||
raise ValidationError("Patches should have files they patch, found none.")
|
||||
@ -321,25 +322,32 @@ class Patch(models.Model):
|
||||
|
||||
def _apply_patch(self, r: git.Repo) -> str:
|
||||
p = self._parse_patch()
|
||||
files = {}
|
||||
def reader(_r, f):
|
||||
return pathlib.Path(tmpdir, f).read_text(encoding="utf-8")
|
||||
|
||||
prefix = 0
|
||||
read = set()
|
||||
patched = {}
|
||||
for m in FILE_PATTERN.finditer(p.patch):
|
||||
if not prefix and m['prefix_a'] and m['prefix_b']:
|
||||
if not prefix and (m['prefix_a'] or m['file_from'] == '/dev/null') and m['prefix_b']:
|
||||
prefix = 1
|
||||
|
||||
files[m['file_to']] = reader
|
||||
if m['file_from'] != '/dev/null':
|
||||
read.add(m['file_from'])
|
||||
patched[m['file_to']] = reader
|
||||
|
||||
archiver = r.stdout(True)
|
||||
# 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.runner = subprocess.Popen
|
||||
with archiver.archive(self.target.name, *files) as out, \
|
||||
tarfile.open(fileobj=out.stdout, mode='r|') as tf,\
|
||||
with contextlib.ExitStack() as stack,\
|
||||
tempfile.TemporaryDirectory() as tmpdir:
|
||||
tf.extractall(tmpdir)
|
||||
# if there's no file to *update*, `archive` will extract the entire
|
||||
# tree which is unnecessary
|
||||
if read:
|
||||
out = stack.enter_context(archiver.archive(self.target.name, *read))
|
||||
tf = stack.enter_context(tarfile.open(fileobj=out.stdout, mode='r|'))
|
||||
tf.extractall(tmpdir)
|
||||
patch = subprocess.run(
|
||||
['patch', f'-p{prefix}', '--directory', tmpdir, '--verbose'],
|
||||
input=p.patch,
|
||||
@ -349,7 +357,7 @@ class Patch(models.Model):
|
||||
)
|
||||
if patch.returncode:
|
||||
raise PatchFailure("\n---------\n".join(filter(None, [p.patch, patch.stdout.strip(), patch.stderr.strip()])))
|
||||
new_tree = r.update_tree(self.target.name, files)
|
||||
new_tree = r.update_tree(self.target.name, patched)
|
||||
|
||||
sha = r.stdout().with_config(encoding='utf-8')\
|
||||
.show('--no-patch', '--pretty=%H', self.target.name)\
|
||||
|
@ -15,7 +15,7 @@ Date: 2021-04-24T17:09:14Z
|
||||
whop whop
|
||||
|
||||
diff --git a/b b/b
|
||||
index 000000000000..000000000000 100644
|
||||
index d00491fd7e5b..0cfbf08886fc 100644
|
||||
--- a/b
|
||||
+++ b/b
|
||||
@@ -1,1 +1,1 @@
|
||||
@ -35,7 +35,7 @@ whop whop
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/b b/b
|
||||
index 000000000000..000000000000 100644
|
||||
index d00491fd7e5b..0cfbf08886fc 100644
|
||||
--- a/b
|
||||
+++ b/b
|
||||
@@ -1,1 +1,1 @@
|
||||
@ -47,7 +47,7 @@ index 000000000000..000000000000 100644
|
||||
|
||||
# slightly different format than the one I got, possibly because older?
|
||||
FORMAT_PATCH_MAT = """\
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From 3000000000000000000000000000000000000000 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
|
||||
@ -58,7 +58,7 @@ whop whop
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git b b
|
||||
index 000000000000..000000000000 100644
|
||||
index d00491fd7e5b..0cfbf08886fc 100644
|
||||
--- b
|
||||
+++ b
|
||||
@@ -1,1 +1,1 @@
|
||||
@ -268,3 +268,73 @@ def test_patch_conflict(env, project, repo, users):
|
||||
), (
|
||||
False, '', [('active', 1, 0)]
|
||||
)]
|
||||
|
||||
CREATE_FILE_FORMAT_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] [I18N] whop
|
||||
|
||||
whop whop
|
||||
---
|
||||
x | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
create mode 100644 b
|
||||
|
||||
diff --git a/x b/x
|
||||
new file mode 100644
|
||||
index 000000000000..d00491fd7e5b
|
||||
--- /dev/null
|
||||
+++ b/x
|
||||
@@ -0,0 +1 @@
|
||||
+1
|
||||
--
|
||||
2.48.1
|
||||
"""
|
||||
|
||||
CREATE_FILE_SHOW = """\
|
||||
commit 0000000000000000000000000000000000000000
|
||||
Author: 3 Discos Down <bar@example.org>
|
||||
Date: 2021-04-24T17:09:14Z
|
||||
|
||||
[I18N] whop
|
||||
|
||||
whop whop
|
||||
|
||||
diff --git a/x b/x
|
||||
new file mode 100644
|
||||
index 000000000000..d00491fd7e5b
|
||||
--- /dev/null
|
||||
+++ b/x
|
||||
@@ -0,0 +1 @@
|
||||
+1
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize('patch', [
|
||||
pytest.param(CREATE_FILE_SHOW, id='show'),
|
||||
pytest.param(CREATE_FILE_FORMAT_PATCH, id='format-patch'),
|
||||
])
|
||||
def test_apply_creation(env, project, repo, users, patch):
|
||||
assert repo.read_tree(repo.commit('master')) == {
|
||||
'a': '2',
|
||||
'b': '1\n',
|
||||
}
|
||||
|
||||
env['runbot_merge.patch'].create({
|
||||
'target': project.branch_ids.id,
|
||||
'repository': project.repo_ids.id,
|
||||
'patch': patch,
|
||||
})
|
||||
# trying to check the list of files doesn't work, even using web_read
|
||||
|
||||
env.run_crons()
|
||||
|
||||
HEAD = repo.commit('master')
|
||||
assert repo.read_tree(HEAD) == {
|
||||
'a': '2',
|
||||
'b': '1\n',
|
||||
'x': '1\n',
|
||||
}
|
||||
assert HEAD.message == "[I18N] whop\n\nwhop whop"
|
||||
assert HEAD.author['name'] == "3 Discos Down"
|
||||
assert HEAD.author['email'] == "bar@example.org"
|
||||
|
Loading…
Reference in New Issue
Block a user