Skip to content

Commit

Permalink
fix(remote): fallback to legacy snapcraft if under shallow git clone
Browse files Browse the repository at this point in the history
Git shallow clone is not pushable by design. Fallback to legacy remote
build which has workaround. This only support core20 and core22. Any
other bases and devel will report error.
  • Loading branch information
syu-w committed Dec 14, 2023
1 parent 6300e0e commit 8a70c19
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 4 deletions.
34 changes: 32 additions & 2 deletions snapcraft/commands/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
from snapcraft.errors import MaintenanceBase, SnapcraftError
from snapcraft.legacy_cli import run_legacy
from snapcraft.parts import yaml_utils
from snapcraft.remote import AcceptPublicUploadError, RemoteBuilder, is_repo
from snapcraft.remote import (
AcceptPublicUploadError,
RemoteBuilder,
is_repo,
is_shallow_repo,
)
from snapcraft.utils import confirm_with_user, get_host_architecture, humanize_list

_CONFIRMATION_PROMPT = (
Expand Down Expand Up @@ -193,7 +198,27 @@ def _run_new_or_fallback_remote_build(self, base: str) -> None:
run_legacy()
return

if is_repo(Path().absolute()):
current_path = Path().absolute()

if is_shallow_repo(current_path):
emit.debug("Current git repository is shallow cloned.")
base = self._get_effective_base()
if base in ["core20", "core22"]:
emit.progress(
"Remote build for shallow clones is deprecated "
"and will be removed in core24",
permanent=True,
)
emit.progress("Fallback to legacy remote-build", permanent=True)
run_legacy()
return

raise SnapcraftError(
"remote-build for shallow clones is not supported "
f"for bases newer than core22, current base is {base}"
)

if is_repo(current_path):
emit.debug(
"Running new remote-build because project is in a git repository"
)
Expand Down Expand Up @@ -228,6 +253,11 @@ def _get_project_name(self) -> str:

def _run_new_remote_build(self) -> None:
"""Run new remote-build code."""
if is_shallow_repo(Path().absolute()):
raise SnapcraftError(
"remote-build for shallow clones is not supported "
"for bases newer than core22"
)
emit.progress("Setting up launchpad environment")
remote_builder = RemoteBuilder(
app_name="snapcraft",
Expand Down
3 changes: 2 additions & 1 deletion snapcraft/remote/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
RemoteBuildTimeoutError,
UnsupportedArchitectureError,
)
from .git import GitRepo, is_repo
from .git import GitRepo, is_repo, is_shallow_repo
from .launchpad import LaunchpadClient
from .remote_builder import RemoteBuilder
from .utils import get_build_id, humanize_list, rmtree, validate_architectures
Expand All @@ -35,6 +35,7 @@
"get_build_id",
"humanize_list",
"is_repo",
"is_shallow_repo",
"rmtree",
"validate_architectures",
"AcceptPublicUploadError",
Expand Down
16 changes: 16 additions & 0 deletions snapcraft/remote/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ def is_repo(path: Path) -> bool:
) from error


def is_shallow_repo(path: Path) -> bool:
"""Check if a directory is a shallow cloned git repo.
:param path: filepath to check
:returns: True if path is a git repo.
:raises GitError: if git fails while checking for a repository
"""
if is_repo(path):
repo = pygit2.Repository(path)
return repo.is_shallow

return False


class GitRepo:
"""Git repository class."""

Expand Down
98 changes: 98 additions & 0 deletions tests/unit/commands/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

"""Remote-build command tests."""

import os
import shutil
import subprocess
import sys
from pathlib import Path
from unittest.mock import ANY, call
Expand Down Expand Up @@ -523,6 +526,101 @@ def test_run_in_repo_newer_than_core22(
emitter.assert_debug("Running new remote-build because base is newer than core22")


@pytest.mark.parametrize(
"create_snapcraft_yaml", LEGACY_BASES | {"core22"}, indirect=True
)
@pytest.mark.usefixtures("create_snapcraft_yaml", "mock_confirm", "mock_argv")
def test_run_in_shallow_repo(emitter, mock_run_legacy, new_dir):
"""core22 and older bases run new remote-build if in a git repo."""
root_path = Path(new_dir)
git_normal_path = root_path / "normal"
git_normal_path.mkdir()
git_shallow_path = root_path / "shallow"

shutil.move(root_path / "snap", git_normal_path)

repo_normal = GitRepo(git_normal_path)
(repo_normal.path / "1").write_text("1")
repo_normal.add_all()
repo_normal.commit("1")

(repo_normal.path / "2").write_text("2")
repo_normal.add_all()
repo_normal.commit("2")

(repo_normal.path / "3").write_text("3")
repo_normal.add_all()
repo_normal.commit("3")

# pygit2 does not support shallow cloning, so we use git directly
subprocess.run(
[
"git",
"clone",
"--depth",
"1",
"file://" + str(git_normal_path.absolute()),
str(git_shallow_path.absolute()),
],
check=True,
)

os.chdir(git_shallow_path)
cli.run()

mock_run_legacy.assert_called_once()
emitter.assert_debug("Current git repository is shallow cloned.")
emitter.assert_progress("Fallback to legacy remote-build", permanent=True)


@pytest.mark.parametrize("create_snapcraft_yaml", {"devel"}, indirect=True)
@pytest.mark.usefixtures(
"create_snapcraft_yaml", "mock_confirm", "mock_argv", "use_new_remote_build"
)
def test_run_in_shallow_repo_unsupported(emitter, new_dir, mock_remote_builder):
"""core22 and older bases run new remote-build if in a git repo."""
root_path = Path(new_dir)
git_normal_path = root_path / "normal"
git_normal_path.mkdir()
git_shallow_path = root_path / "shallow"

shutil.move(root_path / "snap", git_normal_path)

repo_normal = GitRepo(git_normal_path)
(repo_normal.path / "1").write_text("1")
repo_normal.add_all()
repo_normal.commit("1")

(repo_normal.path / "2").write_text("2")
repo_normal.add_all()
repo_normal.commit("2")

(repo_normal.path / "3").write_text("3")
repo_normal.add_all()
repo_normal.commit("3")

# pygit2 does not support shallow cloning, so we use git directly
subprocess.run(
[
"git",
"clone",
"--depth",
"1",
"file://" + str(git_normal_path.absolute()),
str(git_shallow_path.absolute()),
],
check=True,
)

os.chdir(git_shallow_path)

# no exception because run() catches it
ret = cli.run()
assert ret != 0

mock_remote_builder.assert_not_called()


######################
# Architecture tests #
######################
Expand Down
39 changes: 38 additions & 1 deletion tests/unit/remote/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
"""Tests for the pygit2 wrapper class."""

import re
import subprocess
from pathlib import Path
from unittest.mock import ANY

import pygit2
import pytest

from snapcraft.remote import GitError, GitRepo, is_repo
from snapcraft.remote import GitError, GitRepo, is_repo, is_shallow_repo


def test_is_repo(new_dir):
Expand All @@ -39,6 +40,42 @@ def test_is_not_repo(new_dir):
assert not is_repo(new_dir)


def test_is_shallow_repo(new_dir):
"""Check if directory is a shallow cloned repo."""
root_path = Path(new_dir)
git_normal_path = root_path / "normal"
git_normal_path.mkdir()
git_shallow_path = root_path / "shallow"

repo_normal = GitRepo(git_normal_path)
(repo_normal.path / "1").write_text("1")
repo_normal.add_all()
repo_normal.commit("1")

(repo_normal.path / "2").write_text("2")
repo_normal.add_all()
repo_normal.commit("2")

(repo_normal.path / "3").write_text("3")
repo_normal.add_all()
repo_normal.commit("3")

# pygit2 does not support shallow cloning, so we use git directly
subprocess.run(
[
"git",
"clone",
"--depth",
"1",
"file://" + str(git_normal_path.absolute()),
str(git_shallow_path.absolute()),
],
check=True,
)

assert is_shallow_repo(git_shallow_path)


def test_is_repo_path_only(new_dir):
"""Only look at the path for a repo."""
Path("parent-repo/not-a-repo/child-repo").mkdir(parents=True)
Expand Down

0 comments on commit 8a70c19

Please sign in to comment.