Coverage for repo_ctx/providers/base.py: 82%

1"""Abstract base class for repository providers."""

2 from abc import ABC, abstractmethod

3 from dataclasses import dataclass

4 from typing import Optional, List

5

6

7@dataclass

8 class ProviderProject:

9 """Normalized project representation across providers."""

10 id: str

11 name: str

12 path: str

13 description: Optional[str]

14 default_branch: str

15 web_url: Optional[str]

16

17

18@dataclass

19 class ProviderFile:

20 """Normalized file representation."""

21 path: str

22 content: str

23 size: int

24

25

26 class GitProvider(ABC):

27 """Abstract base class for all repository providers."""

28

29 @abstractmethod

30 async def get_project(self, path: str) -> ProviderProject:

31 """

32 Get project metadata.

33

34 Args:

35 path: Provider-specific project identifier

36 GitLab: "group/project" or "group/subgroup/project"

37 GitHub: "owner/repo"

38 Local: "/absolute/path" or "./relative/path"

39

40 Returns:

41 ProviderProject with normalized metadata

42

43 Raises:

44 ProviderNotFoundError: Project doesn't exist

45 ProviderAuthError: Authentication failed

46 """

47 pass

48

49 @abstractmethod

50 async def get_default_branch(self, project: ProviderProject) -> str:

51 """

52 Get default branch name (main, master, etc.).

53

54 Args:

55 project: Project to query

56

57 Returns:

58 Default branch name

59

60 Raises:

61 ProviderError: Error getting branch information

62 """

63 pass

64

65 @abstractmethod

66 async def get_file_tree(

67 self,

68 project: ProviderProject,

69 ref: str,

70 recursive: bool = True

71 ) -> List[str]:

72 """

73 Get list of all file paths in repository.

74

75 Args:

76 project: Project to query

77 ref: Branch, tag, or commit SHA

78 recursive: Include subdirectories

79

80 Returns:

81 List of file paths relative to repo root

82

83 Raises:

84 ProviderError: Error accessing file tree

85 """

86 pass

87

88 @abstractmethod

89 async def read_file(

90 self,

91 project: ProviderProject,

92 path: str,

93 ref: str

94 ) -> ProviderFile:

95 """

96 Read file contents.

97

98 Args:

99 project: Project to query

100 path: File path relative to repo root

101 ref: Branch, tag, or commit SHA

102

103 Returns:

104 ProviderFile with content and metadata

105

106 Raises:

107 ProviderFileNotFoundError: File doesn't exist at ref

108 """

109 pass

110

111 @abstractmethod

112 async def read_config(

113 self,

114 project: ProviderProject,

115 ref: str

116 ) -> Optional[dict]:

117 """

118 Read repo-ctx configuration file if it exists.

119

120 Searches for configuration files in this order:

121 1. git_context.json (current name)

122 2. .git_context.json

123 3. repo_context.json

124 4. .repo-ctx.json

125

126 Args:

127 project: Project to query

128 ref: Branch, tag, or commit SHA

129

130 Returns:

131 Parsed JSON config or None if not found

132

133 Raises:

134 ProviderError: Error reading configuration

135 """

136 pass

137

138 @abstractmethod

139 async def get_tags(

140 self,

141 project: ProviderProject,

142 limit: int = 5

143 ) -> List[str]:

144 """

145 Get repository tags (most recent first).

146

147 Args:

148 project: Project to query

149 limit: Maximum number of tags to return

150

151 Returns:

152 List of tag names

153

154 Raises:

155 ProviderError: Error accessing tags

156 """

157 pass

158

159 @abstractmethod

160 async def list_projects_in_group(

161 self,

162 group_path: str,

163 include_subgroups: bool = True

164 ) -> List[ProviderProject]:

165 """

166 List all projects in a group/organization.

167

168 Args:

169 group_path: Group identifier

170 GitLab: "group" or "group/subgroup"

171 GitHub: "org"

172 Local: Not supported

173

174 include_subgroups: Include nested groups (GitLab only)

175

176 Returns:

177 List of projects in the group

178

179 Raises:

180 ProviderNotFoundError: Group not found

181 NotImplementedError: Provider doesn't support groups

182

183 Note:

184 Not all providers support groups (e.g., local repos)

185 """

186 pass