Skip to content

Commit 1ebd5d9

Browse files
Add patches direct in yaml
1 parent b2f19f8 commit 1ebd5d9

File tree

5 files changed

+136
-18
lines changed

5 files changed

+136
-18
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,13 @@ Contributors
353353
* Simone Orsi (camptocamp_)
354354
* Artem Kostyuk
355355
* Jan Verbeek
356+
* Michael Tietz (MT_Software_)
356357

357358
.. _ACSONE: https://www.acsone.eu
358359
.. _Tecnativa: https://www.tecnativa.com
359360
.. _camptocamp: https://www.camptocamp.com
360361
.. _LasLabs: https://laslabs.com
362+
.. _MT_Software: https://github.com/mt-software-de
361363

362364
Maintainer
363365
----------

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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@
99

1010
from ._compat import string_types
1111
from .exception import ConfigException
12+
from .patch import Patches
13+
1214

1315
log = logging.getLogger(__name__)
1416

1517

18+
def update_patches(repo_dict, repo_data):
19+
"""Check and update repo_dict with patch files"""
20+
patches_data = repo_data.get("patches")
21+
patches = repo_dict.setdefault("patches", Patches())
22+
if not patches_data:
23+
return
24+
for patch in patches_data:
25+
patches += Patches.prepare_patches(patch, repo_dict.get("cwd"))
26+
27+
1628
def get_repos(config, force=False):
1729
"""Return a :py:obj:`list` list of repos from config file.
1830
:param config: the repos config in :py:class:`dict` format.
@@ -126,6 +138,7 @@ def get_repos(config, force=False):
126138
cmds = [cmds]
127139
commands = cmds
128140
repo_dict['shell_command_after'] = commands
141+
update_patches(repo_dict, repo_data)
129142
repo_list.append(repo_dict)
130143
return repo_list
131144

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
@@ -11,6 +11,7 @@
1111

1212
from ._compat import console_to_str
1313
from .exception import DirtyException, GitAggregatorException
14+
from .command import CommandExecutor
1415

1516
FETCH_DEFAULTS = ("depth", "shallow-since", "shallow-exclude")
1617
logger = logging.getLogger(__name__)
@@ -30,13 +31,13 @@ def ishex(s):
3031
return True
3132

3233

33-
class Repo:
34+
class Repo(CommandExecutor):
3435

3536
_git_version = None
3637

3738
def __init__(self, cwd, remotes, merges, target,
3839
shell_command_after=None, fetch_all=False, defaults=None,
39-
force=False):
40+
force=False, patches=None):
4041
"""Initialize a git repository aggregator
4142
4243
:param cwd: path to the directory where to initialize the repository
@@ -56,7 +57,7 @@ def __init__(self, cwd, remotes, merges, target,
5657
:param bool force:
5758
When ``False``, it will stop if repo is dirty.
5859
"""
59-
self.cwd = cwd
60+
super().__init__(cwd)
6061
self.remotes = remotes
6162
if fetch_all is True:
6263
self.fetch_all = frozenset(r["name"] for r in remotes)
@@ -67,6 +68,7 @@ def __init__(self, cwd, remotes, merges, target,
6768
self.shell_command_after = shell_command_after or []
6869
self.defaults = defaults or dict()
6970
self.force = force
71+
self.patches = patches
7072

7173
@property
7274
def git_version(self):
@@ -148,21 +150,6 @@ def query_remote_ref(self, remote, ref):
148150
return 'HEAD', sha
149151
return None, ref
150152

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

@@ -313,6 +301,24 @@ def _merge(self, merge):
313301
cmd += self._fetch_options(merge) + (merge["remote"], merge["ref"])
314302
self.log_call(cmd, cwd=self.cwd)
315303

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

0 commit comments

Comments
 (0)