165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
"""Tests for ``src/git_utils.py``."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from src import git_utils
|
|
from src.git_utils import (
|
|
find_git_root,
|
|
get_repo_remote_hash,
|
|
normalize_git_remote_url,
|
|
should_include_git_instructions,
|
|
)
|
|
|
|
|
|
class FindGitRootTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
find_git_root.cache_clear()
|
|
|
|
def test_finds_git_directory(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
(Path(tmp) / '.git').mkdir()
|
|
self.assertEqual(find_git_root(tmp), str(Path(tmp).resolve()))
|
|
|
|
def test_walks_up_from_subdirectory(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
(Path(tmp) / '.git').mkdir()
|
|
sub = Path(tmp) / 'a' / 'b' / 'c'
|
|
sub.mkdir(parents=True)
|
|
self.assertEqual(find_git_root(str(sub)), str(Path(tmp).resolve()))
|
|
|
|
def test_finds_when_git_is_a_file(self) -> None:
|
|
# Worktrees and submodules use a .git file containing 'gitdir: ...'
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
(Path(tmp) / '.git').write_text('gitdir: /elsewhere/.git/worktrees/x')
|
|
self.assertEqual(find_git_root(tmp), str(Path(tmp).resolve()))
|
|
|
|
def test_returns_none_when_not_in_repo(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
self.assertIsNone(find_git_root(tmp))
|
|
|
|
def test_memoizes(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
(Path(tmp) / '.git').mkdir()
|
|
first = find_git_root(tmp)
|
|
# Now remove .git — second lookup should still hit cache
|
|
(Path(tmp) / '.git').rmdir()
|
|
self.assertEqual(find_git_root(tmp), first)
|
|
|
|
|
|
class NormalizeGitRemoteUrlTest(unittest.TestCase):
|
|
def test_ssh_url(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('git@github.com:org/repo.git'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_ssh_url_no_dot_git_suffix(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('git@github.com:org/repo'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_https_url(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('https://github.com/org/repo.git'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_https_with_user(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('https://user@github.com/org/repo.git'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_ssh_protocol_url(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('ssh://git@github.com/org/repo.git'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_lowercases_result(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('git@GitHub.com:Org/Repo.git'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_ccr_proxy_legacy_assumes_github(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('http://x@127.0.0.1:8080/git/org/repo'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
def test_ccr_proxy_ghe_uses_first_segment_as_host(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url(
|
|
'http://x@127.0.0.1:8080/git/ghe.example.com/org/repo',
|
|
),
|
|
'ghe.example.com/org/repo',
|
|
)
|
|
|
|
def test_returns_none_on_garbage(self) -> None:
|
|
self.assertIsNone(normalize_git_remote_url(''))
|
|
self.assertIsNone(normalize_git_remote_url(' '))
|
|
self.assertIsNone(normalize_git_remote_url('not-a-url'))
|
|
|
|
def test_localhost_alias(self) -> None:
|
|
self.assertEqual(
|
|
normalize_git_remote_url('http://localhost:8080/git/org/repo'),
|
|
'github.com/org/repo',
|
|
)
|
|
|
|
|
|
class GetRepoRemoteHashTest(unittest.TestCase):
|
|
def test_hashes_normalized_url(self) -> None:
|
|
url = 'git@github.com:Org/Repo.git'
|
|
normalized = 'github.com/org/repo'
|
|
expected = hashlib.sha256(normalized.encode('utf-8')).hexdigest()[:16]
|
|
self.assertEqual(get_repo_remote_hash(url), expected)
|
|
|
|
def test_returns_none_for_empty(self) -> None:
|
|
self.assertIsNone(get_repo_remote_hash(None))
|
|
self.assertIsNone(get_repo_remote_hash(''))
|
|
|
|
def test_returns_none_for_unparseable(self) -> None:
|
|
self.assertIsNone(get_repo_remote_hash('not-a-url'))
|
|
|
|
|
|
class ShouldIncludeGitInstructionsTest(unittest.TestCase):
|
|
def test_default_true_when_nothing_set(self) -> None:
|
|
self.assertTrue(should_include_git_instructions(env={}))
|
|
|
|
def test_env_truthy_disables(self) -> None:
|
|
for value in ('1', 'true', 'yes', 'on'):
|
|
self.assertFalse(
|
|
should_include_git_instructions(
|
|
settings_value=True,
|
|
env={'CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS': value},
|
|
),
|
|
)
|
|
|
|
def test_env_falsy_overrides_settings(self) -> None:
|
|
# Settings says off, but env explicitly says don't disable → on
|
|
self.assertTrue(
|
|
should_include_git_instructions(
|
|
settings_value=False,
|
|
env={'CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS': '0'},
|
|
),
|
|
)
|
|
|
|
def test_settings_value_used_when_env_unset(self) -> None:
|
|
self.assertFalse(
|
|
should_include_git_instructions(settings_value=False, env={}),
|
|
)
|
|
self.assertTrue(
|
|
should_include_git_instructions(settings_value=True, env={}),
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|