Skip to content

Commit c80c33b

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

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

git_aggregator/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,21 @@
99

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

1314
log = logging.getLogger(__name__)
1415

1516

17+
def update_patches(repo_dict, repo_data):
18+
"""Check and update repo_dict with patch files"""
19+
patches_data = repo_data.get("patches")
20+
patches = repo_dict.setdefault("patches", Patches())
21+
if not patches_data:
22+
return
23+
for patch in patches_data:
24+
patches += Patches.prepare_patches(patch, repo_dict.get("cwd"))
25+
26+
1627
def get_repos(config, force=False):
1728
"""Return a :py:obj:`list` list of repos from config file.
1829
:param config: the repos config in :py:class:`dict` format.
@@ -126,6 +137,7 @@ def get_repos(config, force=False):
126137
cmds = [cmds]
127138
commands = cmds
128139
repo_dict['shell_command_after'] = commands
140+
update_patches(repo_dict, repo_data)
129141
repo_list.append(repo_dict)
130142
return repo_list
131143

git_aggregator/patch.py

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

git_aggregator/repo.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import requests
1111

1212
from ._compat import console_to_str
13+
from .command import CommandExecutor
1314
from .exception import DirtyException, GitAggregatorException
1415

1516
FETCH_DEFAULTS = ("depth", "shallow-since", "shallow-exclude")
@@ -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)