Skip to content

Commit 5c9b381

Browse files
Add patches direct in yaml
1 parent 4a706d7 commit 5c9b381

File tree

5 files changed

+135
-18
lines changed

5 files changed

+135
-18
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,13 @@ Contributors
343343
* Simone Orsi (camptocamp_)
344344
* Artem Kostyuk
345345
* Jan Verbeek
346+
* Michael Tietz (MT_Software_)
346347

347348
.. _ACSONE: https://www.acsone.eu
348349
.. _Tecnativa: https://www.tecnativa.com
349350
.. _camptocamp: https://www.camptocamp.com
350351
.. _LasLabs: https://laslabs.com
352+
.. _MT_Software: https://github.com/mt-software-de
351353

352354
Maintainer
353355
----------

git_aggregator/command.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2015 ACSONE SA/NV
3+
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
4+
# License AGPLv3 (http://www.gnu.org/licenses/agpl-3.0-standalone.html)
5+
# Parts of the code comes from ANYBOX
6+
# https://github.com/anybox/anybox.recipe.odoo
7+
import subprocess
8+
import logging
9+
from ._compat import console_to_str
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class CommandExecutor(object):
14+
def __init__(self, cwd):
15+
self.cwd = cwd
16+
17+
def log_call(self, cmd, callwith=subprocess.check_call,
18+
log_level=logging.DEBUG, **kw):
19+
"""Wrap a subprocess call with logging
20+
:param meth: the calling method to use.
21+
"""
22+
logger.log(log_level, "%s> call %r", self.cwd, cmd)
23+
try:
24+
ret = callwith(cmd, **kw)
25+
except Exception:
26+
logger.error("%s> error calling %r", self.cwd, cmd)
27+
raise
28+
if callwith == subprocess.check_output:
29+
ret = console_to_str(ret)
30+
return ret

git_aggregator/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,22 @@
1010

1111
from .exception import ConfigException
1212
from ._compat import string_types
13+
from .patch import Patches
1314

1415

1516
log = logging.getLogger(__name__)
1617

1718

19+
def update_patches(repo_dict, repo_data):
20+
"""Check and update repo_dict with patch files"""
21+
patches_data = repo_data.get("patches")
22+
patches = repo_dict.setdefault("patches", Patches())
23+
if not patches_data:
24+
return
25+
for patch in patches_data:
26+
patches += Patches.prepare_patches(patch, repo_dict.get("cwd"))
27+
28+
1829
def get_repos(config, force=False):
1930
"""Return a :py:obj:`list` list of repos from config file.
2031
:param config: the repos config in :py:class:`dict` format.
@@ -128,6 +139,7 @@ def get_repos(config, force=False):
128139
cmds = [cmds]
129140
commands = cmds
130141
repo_dict['shell_command_after'] = commands
142+
update_patches(repo_dict, repo_data)
131143
repo_list.append(repo_dict)
132144
return repo_list
133145

git_aggregator/patch.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
3+
# License AGPLv3 (http://www.gnu.org/licenses/agpl-3.0-standalone.html)
4+
import logging
5+
from pathlib import Path
6+
import subprocess
7+
8+
from .command import CommandExecutor
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class Patch(CommandExecutor):
14+
is_local = False
15+
16+
def __init__(self, path, cwd):
17+
super().__init__(cwd)
18+
self.path = path
19+
path = Path(path)
20+
if path.exists():
21+
self.is_local = True
22+
23+
def retrive_data(self):
24+
path = self.path
25+
if self.is_local:
26+
patch_path = Path(path).absolute()
27+
path = "FILE:{}".format(str(patch_path))
28+
cmd = [
29+
"curl",
30+
path,
31+
]
32+
if logger.getEffectiveLevel() != logging.DEBUG:
33+
cmd.append('-s')
34+
return self.log_call(
35+
cmd,
36+
callwith=subprocess.Popen,
37+
stdout=subprocess.PIPE
38+
)
39+
40+
def apply(self):
41+
res = self.retrive_data()
42+
cmd = [
43+
"git",
44+
"am",
45+
]
46+
if logger.getEffectiveLevel() != logging.DEBUG:
47+
cmd.append('--quiet')
48+
self.log_call(cmd, cwd=self.cwd, stdin=res.stdout)
49+
50+
51+
class Patches(list):
52+
"""List of patches"""
53+
@staticmethod
54+
def prepare_patches(path, cwd):
55+
_path = Path(path)
56+
patches = Patches()
57+
if not _path.exists() or _path.is_file():
58+
patches.append(Patch(path, cwd))
59+
elif _path.is_dir():
60+
for fpath in _path.iterdir():
61+
if fpath.is_file():
62+
patches.append(Patch(str(fpath), cwd))
63+
return patches
64+
65+
def apply(self):
66+
for patch in self:
67+
patch.apply()

git_aggregator/repo.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from .exception import DirtyException, GitAggregatorException
1515
from ._compat import console_to_str
16+
from .command import CommandExecutor
1617

1718
FETCH_DEFAULTS = ("depth", "shallow-since", "shallow-exclude")
1819
logger = logging.getLogger(__name__)
@@ -32,13 +33,13 @@ def ishex(s):
3233
return True
3334

3435

35-
class Repo(object):
36+
class Repo(CommandExecutor):
3637

3738
_git_version = None
3839

3940
def __init__(self, cwd, remotes, merges, target,
4041
shell_command_after=None, fetch_all=False, defaults=None,
41-
force=False):
42+
force=False, patches=None):
4243
"""Initialize a git repository aggregator
4344
4445
:param cwd: path to the directory where to initialize the repository
@@ -58,7 +59,7 @@ def __init__(self, cwd, remotes, merges, target,
5859
:param bool force:
5960
When ``False``, it will stop if repo is dirty.
6061
"""
61-
self.cwd = cwd
62+
super().__init__(cwd)
6263
self.remotes = remotes
6364
if fetch_all is True:
6465
self.fetch_all = frozenset(r["name"] for r in remotes)
@@ -69,6 +70,7 @@ def __init__(self, cwd, remotes, merges, target,
6970
self.shell_command_after = shell_command_after or []
7071
self.defaults = defaults or dict()
7172
self.force = force
73+
self.patches = patches
7274

7375
@property
7476
def git_version(self):
@@ -150,21 +152,6 @@ def query_remote_ref(self, remote, ref):
150152
return 'HEAD', sha
151153
return None, ref
152154

153-
def log_call(self, cmd, callwith=subprocess.check_call,
154-
log_level=logging.DEBUG, **kw):
155-
"""Wrap a subprocess call with logging
156-
:param meth: the calling method to use.
157-
"""
158-
logger.log(log_level, "%s> call %r", self.cwd, cmd)
159-
try:
160-
ret = callwith(cmd, **kw)
161-
except Exception:
162-
logger.error("%s> error calling %r", self.cwd, cmd)
163-
raise
164-
if callwith == subprocess.check_output:
165-
ret = console_to_str(ret)
166-
return ret
167-
168155
def aggregate(self):
169156
""" Aggregate all merges into the target branch
170157
If the target_dir doesn't exist, create an empty git repo otherwise
@@ -189,6 +176,7 @@ def aggregate(self):
189176
self._reset_to(origin["remote"], origin["ref"])
190177
for merge in merges:
191178
self._merge(merge)
179+
self.patches.apply()
192180
self._execute_shell_command_after()
193181
logger.info('End aggregation of %s', self.cwd)
194182

@@ -315,6 +303,24 @@ def _merge(self, merge):
315303
cmd += self._fetch_options(merge) + (merge["remote"], merge["ref"])
316304
self.log_call(cmd, cwd=self.cwd)
317305

306+
def _patch(self, patch_path):
307+
cmd = (
308+
"patch",
309+
"-p1",
310+
"--no-backup-if-mismatch",
311+
"-t",
312+
"-i",
313+
str(patch_path.resolve()),
314+
)
315+
if logger.getEffectiveLevel() != logging.DEBUG:
316+
cmd += ('--quiet',)
317+
self.log_call(cmd, cwd=self.cwd)
318+
self.log_call(("git", "add", "."), cwd=self.cwd)
319+
self.log_call(
320+
("git", "commit", "-am", "Applied patch %s" % str(patch_path)),
321+
cwd=self.cwd,
322+
)
323+
318324
def _get_remotes(self):
319325
lines = self.log_call(
320326
['git', 'remote', '-v'],

0 commit comments

Comments
 (0)