fix case when no files were commited in remote push trigger

The issue appears together with --intent-to-add flag for adding new
files. Original testing has been performed by having already added new
files, thus it passed all checks.

This commit also adds `commit_author` option which will allow to
overwrite the author.
This commit is contained in:
Evgenii Alekseev 2022-11-14 00:59:43 +02:00
parent b2ed383de0
commit cdd66ee780
6 changed files with 68 additions and 29 deletions

View File

@ -104,6 +104,7 @@ It supports authorization; to do so you'd need to prefix the url with authorizat
Remote push trigger Remote push trigger
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
* ``commit_author`` - git commit author, string, optional. In case if not set, the git will generate author for you. Note, however, that in this case it will disclosure your hostname.
* ``push_url`` - url of the remote repository to which PKGBUILDs should be pushed after build process, string, required. * ``push_url`` - url of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
* ``push_branch`` - branch of the remote repository to which PKGBUILDs should be pushed after build process, string, optional, default is ``master``. * ``push_branch`` - branch of the remote repository to which PKGBUILDs should be pushed after build process, string, optional, default is ``master``.

View File

@ -161,12 +161,12 @@ class Sources(LazyLogging):
str: patch as plain text str: patch as plain text
""" """
instance = Sources() instance = Sources()
instance.add(sources_dir, *pattern) instance.add(sources_dir, *pattern, intent_to_add=True)
diff = instance.diff(sources_dir) diff = instance.diff(sources_dir)
return f"{diff}\n" # otherwise, patch will be broken return f"{diff}\n" # otherwise, patch will be broken
@staticmethod @staticmethod
def push(sources_dir: Path, remote: RemoteSource, *pattern: str) -> None: def push(sources_dir: Path, remote: RemoteSource, *pattern: str, commit_author: Optional[str] = None) -> None:
""" """
commit selected changes and push files to the remote repository commit selected changes and push files to the remote repository
@ -174,19 +174,21 @@ class Sources(LazyLogging):
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
remote(RemoteSource): remote target, branch and url remote(RemoteSource): remote target, branch and url
*pattern(str): glob patterns *pattern(str): glob patterns
commit_author(Optional[str]): commit author in form of git config (i.e. ``user <user@host>``)
""" """
instance = Sources() instance = Sources()
instance.add(sources_dir, *pattern) instance.add(sources_dir, *pattern)
instance.commit(sources_dir) instance.commit(sources_dir, author=commit_author)
Sources._check_output("git", "push", remote.git_url, remote.branch, cwd=sources_dir, logger=instance.logger) Sources._check_output("git", "push", remote.git_url, remote.branch, cwd=sources_dir, logger=instance.logger)
def add(self, sources_dir: Path, *pattern: str) -> None: def add(self, sources_dir: Path, *pattern: str, intent_to_add: bool = False) -> None:
""" """
track found files via git track found files via git
Args: Args:
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
*pattern(str): glob patterns *pattern(str): glob patterns
intent_to_add(bool): record only the fact that it will be added later, acts as --intent-to-add git flag
""" """
# glob directory to find files which match the specified patterns # glob directory to find files which match the specified patterns
found_files: List[Path] = [] found_files: List[Path] = []
@ -196,23 +198,26 @@ class Sources(LazyLogging):
return # no additional files found return # no additional files found
self.logger.info("found matching files %s", found_files) self.logger.info("found matching files %s", found_files)
# add them to index # add them to index
Sources._check_output("git", "add", "--intent-to-add", args = ["--intent-to-add"] if intent_to_add else []
*[str(fn.relative_to(sources_dir)) for fn in found_files], Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
cwd=sources_dir, logger=self.logger) cwd=sources_dir, logger=self.logger)
def commit(self, sources_dir: Path, commit_message: Optional[str] = None) -> None: def commit(self, sources_dir: Path, message: Optional[str] = None, author: Optional[str] = None) -> None:
""" """
commit changes commit changes
Args: Args:
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
commit_message(Optional[str]): optional commit message if any. If none set, message will be generated message(Optional[str]): optional commit message if any. If none set, message will be generated according to
according to the current timestamp the current timestamp
author(Optional[str]): optional commit author if any
""" """
if commit_message is None: if message is None:
commit_message = f"Autogenerated commit at {datetime.datetime.utcnow()}" message = f"Autogenerated commit at {datetime.datetime.utcnow()}"
Sources._check_output("git", "commit", "--allow-empty", "--message", commit_message, args = ["--allow-empty", "--message", message]
cwd=sources_dir, logger=self.logger) if author is not None:
args.extend(["--author", author])
Sources._check_output("git", "commit", *args, cwd=sources_dir, logger=self.logger)
def diff(self, sources_dir: Path) -> str: def diff(self, sources_dir: Path) -> str:
""" """

View File

@ -38,6 +38,7 @@ class RemotePush(LazyLogging):
sync PKGBUILDs to remote repository after actions sync PKGBUILDs to remote repository after actions
Attributes: Attributes:
commit_author(Optional[str]): optional commit author in form of git config (i.e. ``user <user@host>``)
remote_source(RemoteSource): repository remote source (remote pull url and branch) remote_source(RemoteSource): repository remote source (remote pull url and branch)
""" """
@ -49,6 +50,7 @@ class RemotePush(LazyLogging):
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
remote_push_trigger.py remote_push_trigger.py
""" """
self.commit_author = configuration.get(section, "commit_author", fallback=None)
self.remote_source = RemoteSource( self.remote_source = RemoteSource(
git_url=configuration.get(section, "push_url"), git_url=configuration.get(section, "push_url"),
web_url="", web_url="",
@ -73,10 +75,8 @@ class RemotePush(LazyLogging):
# firstly, we need to remove old data to make sure that removed files are not tracked anymore... # firstly, we need to remove old data to make sure that removed files are not tracked anymore...
package_target_dir = target_dir / package.base package_target_dir = target_dir / package.base
shutil.rmtree(package_target_dir, ignore_errors=True) shutil.rmtree(package_target_dir, ignore_errors=True)
# ...secondly, we copy whole tree... # ...secondly, we clone whole tree...
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)): Sources.fetch(package_target_dir, package.remote)
Sources.fetch(clone_dir, package.remote)
shutil.copytree(clone_dir, package_target_dir)
# ...and last, but not least, we remove the dot-git directory... # ...and last, but not least, we remove the dot-git directory...
shutil.rmtree(package_target_dir / ".git", ignore_errors=True) shutil.rmtree(package_target_dir / ".git", ignore_errors=True)
# ...and finally return path to the copied directory # ...and finally return path to the copied directory
@ -107,7 +107,8 @@ class RemotePush(LazyLogging):
try: try:
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)): with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
Sources.fetch(clone_dir, self.remote_source) Sources.fetch(clone_dir, self.remote_source)
Sources.push(clone_dir, self.remote_source, *RemotePush.packages_update(result, clone_dir)) Sources.push(clone_dir, self.remote_source, *RemotePush.packages_update(result, clone_dir),
commit_author=self.commit_author)
except Exception: except Exception:
self.logger.exception("git push failed") self.logger.exception("git push failed")
raise GitRemoteError() raise GitRemoteError()

View File

@ -193,7 +193,7 @@ def test_patch_create(mocker: MockerFixture) -> None:
diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.diff") diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.diff")
Sources.patch_create(Path("local"), "glob") Sources.patch_create(Path("local"), "glob")
add_mock.assert_called_once_with(Path("local"), "glob") add_mock.assert_called_once_with(Path("local"), "glob", intent_to_add=True)
diff_mock.assert_called_once_with(Path("local")) diff_mock.assert_called_once_with(Path("local"))
@ -214,10 +214,11 @@ def test_push(package_ahriman: Package, mocker: MockerFixture) -> None:
commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit") commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
author = "commit author <user@host>"
local = Path("local") local = Path("local")
Sources.push(Path("local"), package_ahriman.remote, "glob") Sources.push(Path("local"), package_ahriman.remote, "glob", commit_author=author)
add_mock.assert_called_once_with(local, "glob") add_mock.assert_called_once_with(local, "glob")
commit_mock.assert_called_once_with(local) commit_mock.assert_called_once_with(local, author=author)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "push", package_ahriman.remote.git_url, package_ahriman.remote.branch, "git", "push", package_ahriman.remote.git_url, package_ahriman.remote.branch,
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=pytest.helpers.anyvar(int))
@ -233,6 +234,21 @@ def test_add(sources: Sources, mocker: MockerFixture) -> None:
local = Path("local") local = Path("local")
sources.add(local, "pattern1", "pattern2") sources.add(local, "pattern1", "pattern2")
glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")]) glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")])
check_output_mock.assert_called_once_with(
"git", "add", "1", "2", "1", "2", cwd=local, logger=pytest.helpers.anyvar(int)
)
def test_add_intent_to_add(sources: Sources, mocker: MockerFixture) -> None:
"""
must add files to git with --intent-to-add flag
"""
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("local/1"), Path("local/2")])
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
sources.add(local, "pattern1", "pattern2", intent_to_add=True)
glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")])
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "add", "--intent-to-add", "1", "2", "1", "2", cwd=local, logger=pytest.helpers.anyvar(int) "git", "add", "--intent-to-add", "1", "2", "1", "2", cwd=local, logger=pytest.helpers.anyvar(int)
) )
@ -256,14 +272,30 @@ def test_commit(sources: Sources, mocker: MockerFixture) -> None:
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local") local = Path("local")
commit_message = "Commit message" message = "Commit message"
sources.commit(local, commit_message=commit_message) sources.commit(local, message=message)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--allow-empty", "--message", commit_message, cwd=local, logger=pytest.helpers.anyvar(int) "git", "commit", "--allow-empty", "--message", message, cwd=local, logger=pytest.helpers.anyvar(int)
) )
def test_commit_autogenerated(sources: Sources, mocker: MockerFixture) -> None: def test_commit_author(sources: Sources, mocker: MockerFixture) -> None:
"""
must commit changes with commit author
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
message = "Commit message"
author = "commit author <user@host>"
sources.commit(Path("local"), message=message, author=author)
check_output_mock.assert_called_once_with(
"git", "commit", "--allow-empty", "--message", message, "--author", author,
cwd=local, logger=pytest.helpers.anyvar(int)
)
def test_commit_autogenerated_message(sources: Sources, mocker: MockerFixture) -> None:
""" """
must commit changes with autogenerated commit message must commit changes with autogenerated commit message
""" """

View File

@ -17,17 +17,14 @@ def test_package_update(package_ahriman: Package, mocker: MockerFixture) -> None
""" """
rmtree_mock = mocker.patch("shutil.rmtree") rmtree_mock = mocker.patch("shutil.rmtree")
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
copytree_mock = mocker.patch("shutil.copytree")
local = Path("local") local = Path("local")
RemotePush.package_update(package_ahriman, local) RemotePush.package_update(package_ahriman, local)
rmtree_mock.assert_has_calls([ rmtree_mock.assert_has_calls([
MockCall(local / package_ahriman.base, ignore_errors=True), MockCall(local / package_ahriman.base, ignore_errors=True),
MockCall(pytest.helpers.anyvar(int), onerror=pytest.helpers.anyvar(int)), # removal of the TemporaryDirectory
MockCall(local / package_ahriman.base / ".git", ignore_errors=True), MockCall(local / package_ahriman.base / ".git", ignore_errors=True),
]) ])
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.remote) fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.remote)
copytree_mock.assert_called_once_with(pytest.helpers.anyvar(int), local / package_ahriman.base)
def test_packages_update(result: Result, package_ahriman: Package, mocker: MockerFixture) -> None: def test_packages_update(result: Result, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -53,7 +50,9 @@ def test_run(configuration: Configuration, result: Result, package_ahriman: Pack
runner.run(result) runner.run(result)
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source) fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source)
push_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source, package_ahriman.base) push_mock.assert_called_once_with(
pytest.helpers.anyvar(int), runner.remote_source, package_ahriman.base, commit_author=runner.commit_author
)
def test_run_failed(configuration: Configuration, result: Result, mocker: MockerFixture) -> None: def test_run_failed(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:

View File

@ -40,6 +40,7 @@ target = gitremote
target = gitremote target = gitremote
[gitremote] [gitremote]
commit_author = "user <user@host>"
push_url = https://github.com/arcan1s/repository.git push_url = https://github.com/arcan1s/repository.git
pull_url = https://github.com/arcan1s/repository.git pull_url = https://github.com/arcan1s/repository.git