Coverage for src / moai_adk / core / git / manager.py: 34.78%

46 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-20 20:52 +0900

1""" 

2Git repository management built on GitPython. 

3 

4SPEC: .moai/specs/SPEC-CORE-GIT-001/spec.md 

5""" 

6 

7from pathlib import Path 

8 

9from git import InvalidGitRepositoryError, Repo 

10 

11from moai_adk.core.git.conflict_detector import GitConflictDetector 

12 

13 

14class GitManager: 

15 """Manage interactions with a Git repository.""" 

16 

17 def __init__(self, repo_path: str | Path = "."): 

18 """ 

19 Initialize the GitManager. 

20 

21 Args: 

22 repo_path: Path to the Git repository (default: current directory) 

23 

24 Raises: 

25 InvalidGitRepositoryError: Raised when the path is not a Git repository. 

26 """ 

27 self.repo = Repo(repo_path) 

28 self.git = self.repo.git 

29 self.repo_path = Path(repo_path).resolve() 

30 self.conflict_detector = GitConflictDetector(self.repo_path) 

31 

32 def is_repo(self) -> bool: 

33 """ 

34 Check whether the path points to a Git repository. 

35 

36 Returns: 

37 True when the location is a Git repository, otherwise False. 

38 

39 Examples: 

40 >>> manager = GitManager("/path/to/repo") 

41 >>> manager.is_repo() 

42 True 

43 """ 

44 try: 

45 _ = self.repo.git_dir 

46 return True 

47 except (InvalidGitRepositoryError, Exception): 

48 return False 

49 

50 def current_branch(self) -> str: 

51 """ 

52 Return the active branch name. 

53 

54 Returns: 

55 Name of the currently checked-out branch. 

56 

57 Examples: 

58 >>> manager = GitManager() 

59 >>> manager.current_branch() 

60 'main' 

61 """ 

62 return self.repo.active_branch.name 

63 

64 def is_dirty(self) -> bool: 

65 """ 

66 Check whether the working tree has uncommitted changes. 

67 

68 Returns: 

69 True when the worktree is dirty, otherwise False. 

70 

71 Examples: 

72 >>> manager = GitManager() 

73 >>> manager.is_dirty() 

74 False 

75 """ 

76 return self.repo.is_dirty() 

77 

78 def create_branch(self, branch_name: str, from_branch: str | None = None) -> None: 

79 """ 

80 Create and switch to a new branch. 

81 

82 Args: 

83 branch_name: Name of the branch to create. 

84 from_branch: Base branch (default: current branch). 

85 

86 Examples: 

87 >>> manager = GitManager() 

88 >>> manager.create_branch("feature/SPEC-AUTH-001") 

89 >>> manager.current_branch() 

90 'feature/SPEC-AUTH-001' 

91 """ 

92 if from_branch: 

93 self.git.checkout("-b", branch_name, from_branch) 

94 else: 

95 self.git.checkout("-b", branch_name) 

96 

97 def commit(self, message: str, files: list[str] | None = None) -> None: 

98 """ 

99 Stage files and create a commit. 

100 

101 Args: 

102 message: Commit message. 

103 files: Optional list of files to commit (default: all changes). 

104 

105 Examples: 

106 >>> manager = GitManager() 

107 >>> manager.commit("feat: add authentication", files=["auth.py"]) 

108 """ 

109 if files: 

110 self.repo.index.add(files) 

111 else: 

112 self.git.add(A=True) 

113 

114 self.repo.index.commit(message) 

115 

116 def push(self, branch: str | None = None, set_upstream: bool = False) -> None: 

117 """ 

118 Push commits to the remote repository. 

119 

120 Args: 

121 branch: Branch to push (default: current branch). 

122 set_upstream: Whether to set the upstream tracking branch. 

123 

124 Examples: 

125 >>> manager = GitManager() 

126 >>> manager.push(set_upstream=True) 

127 """ 

128 if set_upstream: 

129 target_branch = branch or self.current_branch() 

130 self.git.push("--set-upstream", "origin", target_branch) 

131 else: 

132 self.git.push() 

133 

134 def check_merge_conflicts( 

135 self, feature_branch: str, base_branch: str 

136 ) -> dict: 

137 """ 

138 Check if merge is possible without conflicts. 

139 

140 Args: 

141 feature_branch: Feature branch to merge from 

142 base_branch: Base branch to merge into 

143 

144 Returns: 

145 Dictionary with merge status and conflict information 

146 

147 Examples: 

148 >>> manager = GitManager() 

149 >>> result = manager.check_merge_conflicts("feature/auth", "develop") 

150 >>> if result["can_merge"]: 

151 ... print("Ready to merge") 

152 ... else: 

153 ... print(f"Conflicts: {result['conflicts']}") 

154 """ 

155 return self.conflict_detector.can_merge(feature_branch, base_branch) 

156 

157 def has_merge_conflicts(self, feature_branch: str, base_branch: str) -> bool: 

158 """ 

159 Quick check if merge would have conflicts. 

160 

161 Args: 

162 feature_branch: Feature branch to merge from 

163 base_branch: Base branch to merge into 

164 

165 Returns: 

166 True if conflicts exist, False otherwise 

167 

168 Examples: 

169 >>> manager = GitManager() 

170 >>> if manager.has_merge_conflicts("feature/auth", "develop"): 

171 ... print("Conflicts detected") 

172 """ 

173 result = self.conflict_detector.can_merge(feature_branch, base_branch) 

174 return not result.get("can_merge", False) 

175 

176 def get_conflict_summary(self, feature_branch: str, base_branch: str) -> str: 

177 """ 

178 Get human-readable summary of merge conflicts. 

179 

180 Args: 

181 feature_branch: Feature branch to merge from 

182 base_branch: Base branch to merge into 

183 

184 Returns: 

185 String summary of conflicts for user presentation 

186 

187 Examples: 

188 >>> manager = GitManager() 

189 >>> summary = manager.get_conflict_summary("feature/auth", "develop") 

190 >>> print(summary) 

191 """ 

192 result = self.conflict_detector.can_merge(feature_branch, base_branch) 

193 conflicts = result.get("conflicts", []) 

194 return self.conflict_detector.summarize_conflicts(conflicts) 

195 

196 def auto_resolve_safe_conflicts(self) -> bool: 

197 """ 

198 Auto-resolve safe config file conflicts. 

199 

200 Returns: 

201 True if auto-resolution succeeded, False otherwise 

202 

203 Examples: 

204 >>> manager = GitManager() 

205 >>> if manager.auto_resolve_safe_conflicts(): 

206 ... print("Safe conflicts resolved automatically") 

207 """ 

208 return self.conflict_detector.auto_resolve_safe() 

209 

210 def abort_merge(self) -> None: 

211 """ 

212 Abort an in-progress merge and clean up state. 

213 

214 Examples: 

215 >>> manager = GitManager() 

216 >>> manager.abort_merge() 

217 """ 

218 self.conflict_detector.cleanup_merge_state()