Coverage for src/rhiza_task/tasks/lfs.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-10-13 02:27 +0000

1"""The Git LFS tasks: lfs.mk, as tasks. 

2 

3Three of the four are one git subcommand each. The fourth, ``lfs-install``, is 50 lines of 

4platform shell and is **deliberately not ported as written** -- what it did and what it is 

5now differ, so the change is stated here rather than discovered. 

6 

7lfs.mk's ``lfs-install`` has two branches. On Linux it runs ``apt-get install git-lfs``, 

8with ``sudo`` when not root. On macOS it queries the GitHub releases API for the newest 

9git-lfs, downloads the architecture-matched zip into ``.local/tmp``, extracts the binary 

10into ``.local/bin``, and runs ``PATH=$PWD/.local/bin:$PATH git-lfs install``. 

11 

12The macOS branch does not leave a working installation. ``.local/bin`` is not on PATH 

13after the recipe exits, and every other target in the fragment -- and every ``git lfs`` 

14anywhere else -- invokes the bare command, so ``make lfs-install && make lfs-pull`` 

15fails on a machine that had no git-lfs. The one thing the branch achieves that survives is 

16the ``git lfs install`` at the end, which writes the filter and hook configuration into 

17the repository. 

18 

19So this task does that part, and *reports* how to install the binary rather than 

20downloading one. Two reasons beyond the broken branch: a task runner provisioned by 

21``uvx`` should not be shelling out to ``sudo apt-get`` as a side effect of a target 

22someone typed, and pinning a download URL to a release-API shape is a maintenance 

23liability for something ``brew``/``apt``/``winget`` all do properly. 

24 

25Consumers who relied on the apt branch need one line of their own -- in CI, the 

26``setup-git-lfs`` action or the distribution's package; locally, their package manager. 

27""" 

28 

29from __future__ import annotations 

30 

31import sys 

32 

33from ..config import Config 

34from ..spec import Failed, Guard, have, task 

35from ..uv import tool 

36 

37SECTION = "Git LFS" 

38 

39INSTALL_URL = "https://github.com/git-lfs/git-lfs#installing" 

40 

41INSTALL_HINTS = { 

42 "darwin": "brew install git-lfs", 

43 "linux": "sudo apt-get install git-lfs (or your distribution's package)", 

44 "win32": "winget install GitHub.GitLFS", 

45} 

46"""How to get the binary, by :data:`sys.platform`. Reported, never run.""" 

47 

48HAVE_LFS = Guard(tool="git-lfs", reason=f"git-lfs not found; see {INSTALL_URL}") 

49"""``git lfs <cmd>`` needs the ``git-lfs`` binary on PATH; git reports it as an unknown 

50command otherwise, which is a confusing way to learn that a tool is missing.""" 

51 

52 

53def install_hint() -> str: 

54 """Return the platform's install command, for the message a missing binary produces. 

55 

56 Returns: 

57 A command to run, or the project's install page when the platform is unknown. 

58 """ 

59 return INSTALL_HINTS.get(sys.platform, f"see {INSTALL_URL}") 

60 

61 

62def uses_lfs(cfg: Config) -> bool: 

63 """Return whether the repository tracks anything with LFS, for ``list``. 

64 

65 ``git lfs track`` records a pattern as a ``filter=lfs`` attribute in ``.gitattributes``, 

66 so that line is the repository-side evidence of LFS whatever the machine has installed. 

67 A repository about to adopt LFS runs ``lfs-install`` before it has one; that still 

68 works by name, and ``list --all`` still shows it. 

69 

70 Args: 

71 cfg: The resolved config. 

72 

73 Returns: 

74 True when ``.gitattributes`` declares an LFS filter. 

75 """ 

76 attributes = cfg.root / ".gitattributes" 

77 return attributes.is_file() and "filter=lfs" in attributes.read_text(encoding="utf-8", errors="replace") 

78 

79 

80@task("lfs-install", "configure git-lfs for this repository", section=SECTION, applies=uses_lfs) 

81def lfs_install(cfg: Config) -> None: 

82 """Run ``git lfs install``, writing this repository's filter and hook configuration. 

83 

84 Args: 

85 cfg: The resolved config. 

86 

87 Raises: 

88 Failed: When the git-lfs binary is absent. A failure rather than a skip, unlike 

89 every other tool guard in this module's siblings: installing is the one thing 

90 this task exists to do, so it has nothing left to report success about. 

91 """ 

92 if not have("git-lfs"): 

93 print(f"[ERROR] git-lfs not found. Install it with:\n {install_hint()}") 

94 raise Failed(1, "git-lfs is not installed") 

95 tool("git", "lfs", "install", cwd=cfg.root) 

96 

97 

98@task( 

99 "lfs-pull", "download the LFS files for the current branch", section=SECTION, guards=(HAVE_LFS,), applies=uses_lfs 

100) 

101def lfs_pull(cfg: Config) -> None: 

102 """Fetch and check out the LFS objects the working tree points at. 

103 

104 Args: 

105 cfg: The resolved config. 

106 """ 

107 tool("git", "lfs", "pull", cwd=cfg.root) 

108 

109 

110@task("lfs-track", "list the patterns tracked by git-lfs", section=SECTION, guards=(HAVE_LFS,), applies=uses_lfs) 

111def lfs_track(cfg: Config) -> None: 

112 """Show the ``.gitattributes`` patterns routed through LFS. 

113 

114 Args: 

115 cfg: The resolved config. 

116 """ 

117 tool("git", "lfs", "track", cwd=cfg.root) 

118 

119 

120@task("lfs-status", "show the status of LFS files", section=SECTION, guards=(HAVE_LFS,), applies=uses_lfs) 

121def lfs_status(cfg: Config) -> None: 

122 """Show which LFS files are modified, staged, or not yet pushed. 

123 

124 Args: 

125 cfg: The resolved config. 

126 """ 

127 tool("git", "lfs", "status", cwd=cfg.root)