Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add patches direct in yaml #80

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,13 @@ Contributors
* Simone Orsi (camptocamp_)
* Artem Kostyuk
* Jan Verbeek
* Michael Tietz (MT_Software_)

.. _ACSONE: https://www.acsone.eu
.. _Tecnativa: https://www.tecnativa.com
.. _camptocamp: https://www.camptocamp.com
.. _LasLabs: https://laslabs.com
.. _MT_Software: https://github.com/mt-software-de

Maintainer
----------
Expand Down
31 changes: 31 additions & 0 deletions git_aggregator/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# © 2015 ACSONE SA/NV
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPLv3 (http://www.gnu.org/licenses/agpl-3.0-standalone.html)
# Parts of the code comes from ANYBOX
# https://github.com/anybox/anybox.recipe.odoo
import logging
import subprocess

from ._compat import console_to_str

logger = logging.getLogger(__name__)


class CommandExecutor:
def __init__(self, cwd):
self.cwd = cwd

def log_call(self, cmd, callwith=subprocess.check_call,
log_level=logging.DEBUG, **kw):
"""Wrap a subprocess call with logging
:param meth: the calling method to use.
"""
logger.log(log_level, "%s> call %r", self.cwd, cmd)
try:
ret = callwith(cmd, **kw)
except Exception:
logger.error("%s> error calling %r", self.cwd, cmd)
raise
if callwith == subprocess.check_output:
ret = console_to_str(ret)
return ret
12 changes: 12 additions & 0 deletions git_aggregator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@

from ._compat import string_types
from .exception import ConfigException
from .patch import Patches

log = logging.getLogger(__name__)


def update_patches(repo_dict, repo_data):
"""Check and update repo_dict with patch files"""
patches_data = repo_data.get("patches")
patches = repo_dict.setdefault("patches", Patches())
if not patches_data:
return
for patch in patches_data:
patches += Patches.prepare_patches(patch, repo_dict.get("cwd"))


def get_repos(config, force=False):
"""Return a :py:obj:`list` list of repos from config file.
:param config: the repos config in :py:class:`dict` format.
Expand Down Expand Up @@ -126,6 +137,7 @@ def get_repos(config, force=False):
cmds = [cmds]
commands = cmds
repo_dict['shell_command_after'] = commands
update_patches(repo_dict, repo_data)
repo_list.append(repo_dict)
return repo_list

Expand Down
66 changes: 66 additions & 0 deletions git_aggregator/patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPLv3 (http://www.gnu.org/licenses/agpl-3.0-standalone.html)
import logging
import subprocess
from pathlib import Path

from .command import CommandExecutor

logger = logging.getLogger(__name__)


class Patch(CommandExecutor):
is_local = False

def __init__(self, path, cwd):
super().__init__(cwd)
self.path = path
path = Path(path)
if path.exists():
self.is_local = True

def retrive_data(self):
path = self.path
if self.is_local:
patch_path = Path(path).absolute()
path = f"FILE:{str(patch_path)}"
cmd = [
"curl",
path,
]
if logger.getEffectiveLevel() != logging.DEBUG:
cmd.append('-s')
return self.log_call(
cmd,
callwith=subprocess.Popen,
stdout=subprocess.PIPE
)

def apply(self):
res = self.retrive_data()
cmd = [
"git",
"am",
]
if logger.getEffectiveLevel() != logging.DEBUG:
cmd.append('--quiet')
self.log_call(cmd, cwd=self.cwd, stdin=res.stdout)


class Patches(list):
"""List of patches"""
@staticmethod
def prepare_patches(path, cwd):
_path = Path(path)
patches = Patches()
if not _path.exists() or _path.is_file():
patches.append(Patch(path, cwd))
elif _path.is_dir():
for fpath in _path.iterdir():
if fpath.is_file():
patches.append(Patch(str(fpath), cwd))
return patches

def apply(self):
for patch in self:
patch.apply()
42 changes: 24 additions & 18 deletions git_aggregator/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import requests

from ._compat import console_to_str
from .command import CommandExecutor
from .exception import DirtyException, GitAggregatorException

FETCH_DEFAULTS = ("depth", "shallow-since", "shallow-exclude")
Expand All @@ -30,13 +31,13 @@ def ishex(s):
return True


class Repo:
class Repo(CommandExecutor):

_git_version = None

def __init__(self, cwd, remotes, merges, target,
shell_command_after=None, fetch_all=False, defaults=None,
force=False):
force=False, patches=None):
"""Initialize a git repository aggregator

:param cwd: path to the directory where to initialize the repository
Expand All @@ -56,7 +57,7 @@ def __init__(self, cwd, remotes, merges, target,
:param bool force:
When ``False``, it will stop if repo is dirty.
"""
self.cwd = cwd
super().__init__(cwd)
self.remotes = remotes
if fetch_all is True:
self.fetch_all = frozenset(r["name"] for r in remotes)
Expand All @@ -67,6 +68,7 @@ def __init__(self, cwd, remotes, merges, target,
self.shell_command_after = shell_command_after or []
self.defaults = defaults or dict()
self.force = force
self.patches = patches

@property
def git_version(self):
Expand Down Expand Up @@ -148,21 +150,6 @@ def query_remote_ref(self, remote, ref):
return 'HEAD', sha
return None, ref

def log_call(self, cmd, callwith=subprocess.check_call,
log_level=logging.DEBUG, **kw):
"""Wrap a subprocess call with logging
:param meth: the calling method to use.
"""
logger.log(log_level, "%s> call %r", self.cwd, cmd)
try:
ret = callwith(cmd, **kw)
except Exception:
logger.error("%s> error calling %r", self.cwd, cmd)
raise
if callwith == subprocess.check_output:
ret = console_to_str(ret)
return ret

def aggregate(self):
""" Aggregate all merges into the target branch
If the target_dir doesn't exist, create an empty git repo otherwise
Expand All @@ -187,6 +174,7 @@ def aggregate(self):
self._reset_to(origin["remote"], origin["ref"])
for merge in merges:
self._merge(merge)
self.patches.apply()
self._execute_shell_command_after()
logger.info('End aggregation of %s', self.cwd)

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

def _patch(self, patch_path):
cmd = (
"patch",
"-p1",
"--no-backup-if-mismatch",
"-t",
"-i",
str(patch_path.resolve()),
)
if logger.getEffectiveLevel() != logging.DEBUG:
cmd += ('--quiet',)
self.log_call(cmd, cwd=self.cwd)
self.log_call(("git", "add", "."), cwd=self.cwd)
self.log_call(
("git", "commit", "-am", "Applied patch %s" % str(patch_path)),
cwd=self.cwd,
)

def _get_remotes(self):
lines = self.log_call(
['git', 'remote', '-v'],
Expand Down