Coverage for src / rhiza / commands / migrate.py: 100%

87 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-12 20:13 +0000

1"""Command for migrating to the new .rhiza folder structure. 

2 

3This module implements the `migrate` command. It helps transition projects to use 

4the new `.rhiza/` folder structure for storing Rhiza state and configuration files, 

5separate from `.github/` which contains GitHub-specific configurations. 

6""" 

7 

8import shutil 

9from pathlib import Path 

10 

11from loguru import logger 

12 

13from rhiza.models import RhizaTemplate 

14 

15 

16def _create_rhiza_directory(target: Path) -> Path: 

17 """Create .rhiza directory if it doesn't exist. 

18 

19 Args: 

20 target: Target repository path. 

21 

22 Returns: 

23 Path to .rhiza directory. 

24 """ 

25 rhiza_dir = target / ".rhiza" 

26 if not rhiza_dir.exists(): 

27 logger.info(f"Creating .rhiza directory at: {rhiza_dir.relative_to(target)}") 

28 rhiza_dir.mkdir(exist_ok=True) 

29 logger.success(f"✓ Created {rhiza_dir.relative_to(target)}") 

30 else: 

31 logger.debug(f".rhiza directory already exists at: {rhiza_dir.relative_to(target)}") 

32 return rhiza_dir 

33 

34 

35def _migrate_template_file(target: Path, rhiza_dir: Path) -> tuple[bool, list[str]]: 

36 """Migrate template.yml from .github to .rhiza. 

37 

38 Args: 

39 target: Target repository path. 

40 rhiza_dir: Path to .rhiza directory. 

41 

42 Returns: 

43 Tuple of (migration_performed, migrations_list). 

44 """ 

45 github_dir = target / ".github" 

46 new_template_file = rhiza_dir / "template.yml" 

47 

48 possible_template_locations = [ 

49 github_dir / "rhiza" / "template.yml", 

50 github_dir / "template.yml", 

51 ] 

52 

53 migrations_performed = [] 

54 template_migrated = False 

55 

56 for old_template_file in possible_template_locations: 

57 if old_template_file.exists(): 

58 if new_template_file.exists(): 

59 logger.info(".rhiza/template.yml already exists") 

60 logger.info(f"Skipping migration of {old_template_file.relative_to(target)}") 

61 logger.info(f"Note: Old file at {old_template_file.relative_to(target)} still exists") 

62 else: 

63 logger.info(f"Found template.yml at: {old_template_file.relative_to(target)}") 

64 logger.info(f"Moving to new location: {new_template_file.relative_to(target)}") 

65 shutil.move(str(old_template_file), str(new_template_file)) 

66 logger.success("✓ Moved template.yml to .rhiza/template.yml") 

67 migrations_performed.append("Moved template.yml to .rhiza/template.yml") 

68 template_migrated = True 

69 break 

70 

71 if not template_migrated: 

72 if new_template_file.exists(): 

73 logger.info(".rhiza/template.yml already exists (no migration needed)") 

74 else: 

75 logger.warning("No existing template.yml file found in .github") 

76 logger.info("You may need to run 'rhiza init' to create a template configuration") 

77 

78 return template_migrated or new_template_file.exists(), migrations_performed 

79 

80 

81def _ensure_rhiza_in_include(template_file: Path) -> None: 

82 """Ensure .rhiza folder is in template.yml include list. 

83 

84 Args: 

85 template_file: Path to template.yml file. 

86 """ 

87 if not template_file.exists(): 

88 logger.debug("No template.yml present in .rhiza; skipping include update") 

89 return 

90 

91 template = RhizaTemplate.from_yaml(template_file) 

92 template_include = template.include or [] 

93 if ".rhiza" not in template_include: 

94 logger.warning("The .rhiza folder is not included in your template.yml") 

95 template_include.append(".rhiza") 

96 logger.info("The .rhiza folder is added to your template.yml to ensure it's included in your repository") 

97 template.include = template_include 

98 template.to_yaml(template_file) 

99 

100 

101def _migrate_history_file(target: Path, rhiza_dir: Path) -> list[str]: 

102 """Migrate .rhiza.history to .rhiza/history. 

103 

104 Args: 

105 target: Target repository path. 

106 rhiza_dir: Path to .rhiza directory. 

107 

108 Returns: 

109 List of migrations performed. 

110 """ 

111 old_history_file = target / ".rhiza.history" 

112 new_history_file = rhiza_dir / "history" 

113 migrations_performed = [] 

114 

115 if old_history_file.exists(): 

116 if new_history_file.exists(): 

117 logger.info(".rhiza/history already exists") 

118 logger.info(f"Skipping migration of {old_history_file.relative_to(target)}") 

119 logger.info(f"Note: Old file at {old_history_file.relative_to(target)} still exists") 

120 else: 

121 logger.info("Found existing .rhiza.history file") 

122 logger.info(f"Moving to new location: {new_history_file.relative_to(target)}") 

123 shutil.move(str(old_history_file), str(new_history_file)) 

124 logger.success("✓ Moved history file to .rhiza/history") 

125 migrations_performed.append("Moved history tracking to .rhiza/history") 

126 else: 

127 if new_history_file.exists(): 

128 logger.debug(".rhiza/history already exists (no migration needed)") 

129 else: 

130 logger.debug("No existing .rhiza.history file to migrate") 

131 

132 return migrations_performed 

133 

134 

135def _print_migration_summary(migrations_performed: list[str]) -> None: 

136 """Print migration summary. 

137 

138 Args: 

139 migrations_performed: List of migrations performed. 

140 """ 

141 logger.success("✓ Migration completed successfully") 

142 

143 if migrations_performed: 

144 logger.info("\nMigration Summary:") 

145 logger.info(" - Created .rhiza/ folder") 

146 for migration in migrations_performed: 

147 logger.info(f" - {migration}") 

148 else: 

149 logger.info("\nNo files needed migration (already using .rhiza structure)") 

150 

151 logger.info( 

152 "\nNext steps:\n" 

153 " 1. Review changes:\n" 

154 " git status\n" 

155 " git diff\n\n" 

156 " 2. Update other commands to use new .rhiza/ location\n" 

157 " (Future rhiza versions will automatically use .rhiza/)\n\n" 

158 " 3. Commit the migration:\n" 

159 " git add .\n" 

160 ' git commit -m "chore: migrate to .rhiza folder structure"\n' 

161 ) 

162 

163 

164def migrate(target: Path) -> None: 

165 """Migrate project to use the new .rhiza folder structure. 

166 

167 This command performs the following actions: 

168 1. Creates the `.rhiza/` directory in the project root 

169 2. Moves template.yml from `.github/rhiza/` or `.github/` to `.rhiza/template.yml` 

170 3. Moves `.rhiza.history` to `.rhiza/history` if it exists 

171 4. Provides instructions for next steps 

172 

173 The `.rhiza/` folder will contain: 

174 - `template.yml` - Template configuration (replaces `.github/rhiza/template.yml`) 

175 - `history` - List of files managed by Rhiza templates (replaces `.rhiza.history`) 

176 - Future: Additional state, cache, or metadata files 

177 

178 Args: 

179 target (Path): Path to the target repository. 

180 """ 

181 target = target.resolve() 

182 logger.info(f"Migrating Rhiza structure in: {target}") 

183 logger.info("This will create the .rhiza folder and migrate configuration files") 

184 

185 # Create .rhiza directory 

186 rhiza_dir = _create_rhiza_directory(target) 

187 

188 # Migrate template file 

189 template_exists, template_migrations = _migrate_template_file(target, rhiza_dir) 

190 

191 # Ensure .rhiza is in include list 

192 if template_exists: 

193 _ensure_rhiza_in_include(rhiza_dir / "template.yml") 

194 

195 # Migrate history file 

196 history_migrations = _migrate_history_file(target, rhiza_dir) 

197 

198 # Print summary 

199 all_migrations = template_migrations + history_migrations 

200 _print_migration_summary(all_migrations)