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

Release 1.7.2 #235

Merged
merged 5 commits into from Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.7.1
current_version = 1.7.2
commit = True
tag = True

Expand Down
2 changes: 1 addition & 1 deletion craft_parts/__init__.py
Expand Up @@ -16,7 +16,7 @@

"""Craft a project from several parts."""

__version__ = "1.7.1"
__version__ = "1.7.2"

from . import plugins
from .actions import Action, ActionType
Expand Down
31 changes: 9 additions & 22 deletions craft_parts/executor/part_handler.py
Expand Up @@ -274,7 +274,7 @@ def _run_build(
self._unpack_stage_packages()
self._unpack_stage_snaps()

if not self._plugin.out_of_source_build:
if not update and not self._plugin.out_of_source_build:
_remove(self._part.part_build_dir)

# Copy source from the part source dir to the part build dir
Expand Down Expand Up @@ -309,14 +309,13 @@ def _run_build(
# 2. So collision detection takes organization into account, i.e. we can use
# organization to get around file collisions between parts when staging.
#
# If `update` is true, we give the snapcraft CLI permission to overwrite files
# that already exist. Typically we do NOT want this, so that parts don't
# accidentally clobber e.g. files brought in from stage-packages, but in the
# case of updating build, we want the part to have the ability to organize over
# the files it organized last time around. We can be confident that this won't
# overwrite anything else, because to do so would require changing the
# `organize` keyword, which will make the build step dirty and require a clean
# instead of an update.
# If `update` is true, we give permission to overwrite files that already exist.
# Typically we do NOT want this, so that parts don't accidentally clobber e.g.
# files brought in from stage-packages, but in the case of updating build, we
# want the part to have the ability to organize over the files it organized last
# time around. We can be confident that this won't overwrite anything else,
# because to do so would require changing the `organize` keyword, which will
# make the build step dirty and require a clean instead of an update.
self._organize(overwrite=update)

assets = {
Expand Down Expand Up @@ -583,10 +582,6 @@ def _update_build(

:param step_info: The step information.
"""
self._make_dirs()
self._unpack_stage_packages()
self._unpack_stage_snaps()

if not self._plugin.out_of_source_build:
# Use the local source to update. It's important to use
# file_utils.copy instead of link_or_copy, as the build process
Expand All @@ -603,15 +598,7 @@ def _update_build(

_remove(self._part.part_install_dir)

self._run_step(
step_info=step_info,
scriptlet_name="override-build",
work_dir=self._part.part_build_dir,
stdout=stdout,
stderr=stderr,
)

self._organize(overwrite=True)
self._run_build(step_info, stdout=stdout, stderr=stderr, update=True)

def _reapply_action(
self,
Expand Down
2 changes: 1 addition & 1 deletion craft_parts/lifecycle_manager.py
Expand Up @@ -193,7 +193,7 @@ def clean(
"""
self._executor.clean(initial_step=step, part_names=part_names)

def refresh_packages_list(self) -> None: # pylint: disable=no-self-use
def refresh_packages_list(self) -> None:
"""Update the available packages list.

The list of available packages should be updated before planning the
Expand Down
4 changes: 2 additions & 2 deletions craft_parts/parts.py
Expand Up @@ -67,7 +67,7 @@ class Config:
allow_mutation = False
alias_generator = lambda s: s.replace("_", "-") # noqa: E731

# pylint: disable=no-self-argument,no-self-use
# pylint: disable=no-self-argument
@validator("stage_files", "prime_files", each_item=True)
def validate_relative_path_list(cls, item):
"""Check if the list does not contain empty of absolute paths."""
Expand All @@ -77,7 +77,7 @@ def validate_relative_path_list(cls, item):
), f"{item!r} must be a relative path (cannot start with '/')"
return item

# pylint: enable=no-self-argument,no-self-use
# pylint: enable=no-self-argument

@classmethod
def unmarshal(cls, data: Dict[str, Any]) -> "PartSpec":
Expand Down
33 changes: 26 additions & 7 deletions craft_parts/sources/git_source.py
Expand Up @@ -175,18 +175,39 @@ def _fetch_origin_commit(self):

self._run(command)

def _get_current_branch(self):
"""Get current git branch."""
command = [
self.command,
"-C",
self.part_src_dir,
"branch",
"--show-current",
]

return self._run_output(command)

def _pull_existing(self):
"""Pull from origin, using branch, tag or commit if defined."""
"""Pull data for an existing repository.

For an existing (initialized) local git repository, pull from origin
using branch, tag, or commit if defined.

`git reset --hard` is preferred over `git pull` to avoid
merge and rebase conflicts.

If no branch, tag, or commit is defined, then pull from origin/<current-branch>.
"""
refspec = "HEAD"
if self.source_branch:
refspec = "refs/heads/" + self.source_branch
refspec = "refs/remotes/origin/" + self.source_branch
elif self.source_tag:
refspec = "refs/tags/" + self.source_tag
elif self.source_commit:
refspec = self.source_commit
self._fetch_origin_commit()

reset_spec = refspec if refspec != "HEAD" else "origin/master"
else:
refspec = "refs/remotes/origin/" + self._get_current_branch()

command = [
self.command,
Expand All @@ -200,9 +221,7 @@ def _pull_existing(self):
command.extend(["--recurse-submodules=yes"])
self._run(command)

command = [self.command, "-C", self.part_src_dir, "reset", "--hard"]
if reset_spec:
command.append(reset_spec)
command = [self.command, "-C", self.part_src_dir, "reset", "--hard", refspec]

self._run(command)

Expand Down
6 changes: 6 additions & 0 deletions docs/changelog.rst
Expand Up @@ -2,6 +2,12 @@
Changelog
*********

1.7.2 (2022-06-14)
------------------

- Fix git repository updates
- Fix stage packages removal on build update

1.7.1 (2022-05-21)
------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Expand Up @@ -37,7 +37,7 @@
author = "Canonical Ltd."

# The full version, including alpha/beta/rc tags
release = "1.7.1"
release = "1.7.2"


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -7,7 +7,7 @@ ensure_newline_before_comments = true
line_length = 88

[tool.pylint.messages_control]
disable = "bad-continuation,bad-whitespace,too-many-ancestors,too-few-public-methods,fixme,unspecified-encoding,use-implicit-booleaness-not-comparison"
disable = "too-many-ancestors,too-few-public-methods,fixme,unspecified-encoding,use-implicit-booleaness-not-comparison,unnecessary-lambda-assignment"

[tool.pylint.similarities]
min-similarity-lines=13
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -96,7 +96,7 @@ def is_rtd() -> bool:

setup(
name="craft-parts",
version="1.7.1",
version="1.7.2",
description="Craft parts tooling",
long_description=readme,
author="Canonical Ltd.",
Expand Down
1 change: 0 additions & 1 deletion tests/unit/executor/test_collisions.py
Expand Up @@ -60,7 +60,6 @@ def part3(tmpdir) -> Part:
(p / "a").mkdir(parents=True)
(p / "b").mkdir()
(p / "1").write_text("2")
# (p / "2").write_text("1")
(p / "a" / "2").write_text("")
return part

Expand Down
36 changes: 36 additions & 0 deletions tests/unit/executor/test_part_handler.py
Expand Up @@ -451,6 +451,42 @@ def test_update_build(self):

assert Path("parts/foo/install/foo.txt").read_text() == "change"

def test_update_build_stage_packages(self, new_dir, mocker):
def fake_unpack(**_):
Path("parts/foo/install/hello").touch()

mocker.patch(
"craft_parts.packages.Repository.unpack_stage_packages", new=fake_unpack
)
mocker.patch(
"craft_parts.packages.Repository.fetch_stage_packages",
return_value=["pkg1", "pkg2"],
)

part = Part(
"foo",
{"plugin": "dump", "source": "subdir", "stage-packages": ["pkg"]},
)
info = ProjectInfo(application_name="test", cache_dir=new_dir)
ovmgr = OverlayManager(project_info=info, part_list=[part], base_layer_dir=None)
part_info = PartInfo(info, part)
handler = PartHandler(
part,
part_info=part_info,
part_list=[part],
overlay_manager=ovmgr,
)
handler._make_dirs()
handler.run_action(Action("foo", Step.PULL))
handler.run_action(Action("foo", Step.OVERLAY))
handler.run_action(Action("foo", Step.BUILD))

assert Path("parts/foo/install/hello").exists()

handler.run_action(Action("foo", Step.BUILD, ActionType.UPDATE))

assert Path("parts/foo/install/hello").exists()

@pytest.mark.parametrize("step", [Step.STAGE, Step.PRIME])
def test_update_invalid(self, step):
with pytest.raises(errors.InvalidAction):
Expand Down