Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ jobs:

- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
files: junit.xml

build:
needs: check
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ markers = [
"slow: mark test as slow",
]
addopts = "-m 'not integration'"
filterwarnings = ["error"]

[dependency-groups]
dev = [
Expand Down
10 changes: 5 additions & 5 deletions src/mygit.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class RemoteState:
@dataclass
class _RemoteLsResult:
remote: pygit2.Remote
refs: list[dict]
refs: list
error: str | None


Expand All @@ -127,7 +127,7 @@ def fetch_states(requests: list[RemoteStateRequest]) -> Iterator[RemoteState]:

def ls(remote: pygit2.Remote) -> _RemoteLsResult:
try:
refs = remote.ls_remotes()
refs = remote.list_heads()
error = None
except pygit2.GitError as exception:
refs = []
Expand All @@ -148,9 +148,9 @@ def ls(remote: pygit2.Remote) -> _RemoteLsResult:
else:
branch_ref = f"refs/heads/{branch}"
for ref in ls_result.refs:
is_head = ref["name"] == "HEAD" and ref["symref_target"] == branch_ref
if is_head or ref["name"] == branch_ref:
yield RemoteState(url, branch, str(ref["oid"]), None)
is_head = ref.name == "HEAD" and ref.symref_target == branch_ref
if is_head or ref.name == branch_ref:
yield RemoteState(url, branch, str(ref.oid), None)
break


Expand Down
5 changes: 4 additions & 1 deletion src/snapjaw.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,15 @@ def get_addon_states(config: Config, addons_dir: str) -> list[AddonState]:
processed += 1
print(f"{processed}/{total_addons}", end="\r")
comment = None
addon_dir = os.path.join(addons_dir, addon.name)
if state.error is not None:
status = AddonStatus.Error
comment = state.error
elif not os.path.isdir(addon_dir):
status = AddonStatus.Missing
elif state.head_commit_hex is None:
status = AddonStatus.Unknown
elif addon.checksum is None or not signature.validate(os.path.join(addons_dir, addon.name), addon.checksum):
elif addon.checksum is None or not signature.validate(addon_dir, addon.checksum):
status = AddonStatus.Modified
elif state.head_commit_hex == addon.commit:
status = AddonStatus.UpToDate
Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ def _mock_remote(name, url, refs=None, error=None):
remote.name = name
remote.url = url
if error:
remote.ls_remotes.side_effect = pygit2.GitError(error)
remote.list_heads.side_effect = pygit2.GitError(error)
else:
remote.ls_remotes.return_value = refs or []
remote.list_heads.return_value = refs or []
return remote

return _mock_remote
Expand Down
6 changes: 3 additions & 3 deletions tests/test_mygit.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_success_returns_commit_hash(self, mock_remote, mock_pygit2_repo, fetch_
remote = mock_remote(
"abc123",
"https://github.com/test/repo.git",
refs=[{"name": "refs/heads/master", "symref_target": "", "oid": "deadbeef"}],
refs=[SimpleNamespace(name="refs/heads/master", symref_target="", oid="deadbeef")],
)
mock_pygit2_repo.remotes.__iter__ = MagicMock(return_value=iter([remote]))

Expand All @@ -92,7 +92,7 @@ def test_head_symref_resolves_branch(self, mock_remote, mock_pygit2_repo, fetch_
remote = mock_remote(
"abc123",
"https://github.com/test/repo.git",
refs=[{"name": "HEAD", "symref_target": "refs/heads/main", "oid": "cafebabe"}],
refs=[SimpleNamespace(name="HEAD", symref_target="refs/heads/main", oid="cafebabe")],
)
mock_pygit2_repo.remotes.__iter__ = MagicMock(return_value=iter([remote]))

Expand Down Expand Up @@ -137,7 +137,7 @@ def test_branch_not_found_in_refs(self, mock_remote, mock_pygit2_repo, fetch_sta
remote = mock_remote(
"abc123",
"https://github.com/test/repo.git",
refs=[{"name": "refs/heads/other-branch", "symref_target": "", "oid": "deadbeef"}],
refs=[SimpleNamespace(name="refs/heads/other-branch", symref_target="", oid="deadbeef")],
)
mock_pygit2_repo.remotes.__iter__ = MagicMock(return_value=iter([remote]))

Expand Down
26 changes: 26 additions & 0 deletions tests/test_snapjaw_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def test_error(self, tmp_path, monkeypatch, make_addon, capsys):

def test_unknown(self, tmp_path, monkeypatch, make_addon, capsys):
"""No commit and no error sets Unknown status."""
addon_dir = tmp_path / "TestAddon"
addon_dir.mkdir()

addon = make_addon()
config = Config(addons_by_key={"testaddon": addon})

Expand Down Expand Up @@ -154,6 +157,29 @@ def test_missing(self, tmp_path, monkeypatch, make_addon, capsys):
assert states[0].addon == "MyMissingAddon"
capsys.readouterr()

def test_missing_directory_with_remote_state(self, tmp_path, monkeypatch, make_addon, capsys):
"""Addon in config with valid remote state but missing directory should be Missing, not crash.

Reproduces https://github.com/.../issues/47: when an addon directory is renamed
(e.g. adding '---' suffix), signature.validate raises ValueError instead of
gracefully reporting the addon as missing.
"""
# Addon is in config with a checksum, but its directory does NOT exist on disk
addon = make_addon(checksum="sig|2")
config = Config(addons_by_key={"testaddon": addon})

# Remote returns a valid state (so the code enters the signature.validate branch)
monkeypatch.setattr(
"snapjaw.mygit.fetch_states",
lambda reqs: iter([RemoteState("https://github.com/test/test.git", "master", "abc123", None)]),
)
# Do NOT mock signature.validate — use the real one to reproduce the ValueError

states = get_addon_states(config, str(tmp_path))
assert len(states) == 1
assert states[0].status == AddonStatus.Missing
capsys.readouterr()

def test_multiple_addons_different_statuses(self, tmp_path, monkeypatch, make_addon, capsys):
"""Multiple addons with different statuses are correctly detected."""
# Create addon directories with content
Expand Down