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

89 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-02 07:04 +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 dataclasses 

9import shutil 

10from pathlib import Path 

11 

12from loguru import logger 

13 

14from rhiza.models import RhizaTemplate 

15 

16 

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

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

19 

20 Args: 

21 target: Target repository path. 

22 

23 Returns: 

24 Path to .rhiza directory. 

25 """ 

26 rhiza_dir = target / ".rhiza" 

27 if not rhiza_dir.exists(): 

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

29 rhiza_dir.mkdir(exist_ok=True) 

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

31 else: 

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

33 return rhiza_dir 

34 

35 

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

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

38 

39 Args: 

40 target: Target repository path. 

41 rhiza_dir: Path to .rhiza directory. 

42 

43 Returns: 

44 Tuple of (migration_performed, migrations_list). 

45 """ 

46 github_dir = target / ".github" 

47 new_template_file = rhiza_dir / "template.yml" 

48 

49 possible_template_locations = [ 

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

51 github_dir / "template.yml", 

52 ] 

53 

54 migrations_performed = [] 

55 template_migrated = False 

56 

57 for old_template_file in possible_template_locations: 

58 if old_template_file.exists(): 

59 if new_template_file.exists(): 

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

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

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

63 else: 

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

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

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

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

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

69 template_migrated = True 

70 break 

71 

72 if not template_migrated: 

73 if new_template_file.exists(): 

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

75 else: 

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

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

78 

79 return template_migrated or new_template_file.exists(), migrations_performed 

80 

81 

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

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

84 

85 Args: 

86 template_file: Path to template.yml file. 

87 """ 

88 if not template_file.exists(): 

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

90 return 

91 

92 template = RhizaTemplate.from_yaml(template_file) 

93 template_include = template.include or [] 

94 if ".rhiza" not in template_include: 

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

96 template_include.append(".rhiza") 

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

98 template = dataclasses.replace(template, include=template_include) 

99 template.to_yaml(template_file) 

100 

101 

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

103 """Migrate .rhiza.history to .rhiza/history. 

104 

105 Args: 

106 target: Target repository path. 

107 rhiza_dir: Path to .rhiza directory. 

108 

109 Returns: 

110 List of migrations performed. 

111 """ 

112 old_history_file = target / ".rhiza.history" 

113 new_history_file = rhiza_dir / "history" 

114 migrations_performed = [] 

115 

116 if old_history_file.exists(): 

117 if new_history_file.exists(): 

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

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

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

121 else: 

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

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

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

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

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

127 else: 

128 if new_history_file.exists(): 

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

130 else: 

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

132 

133 return migrations_performed 

134 

135 

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

137 """Print migration summary. 

138 

139 Args: 

140 migrations_performed: List of migrations performed. 

141 """ 

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

143 

144 if migrations_performed: 

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

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

147 for migration in migrations_performed: 

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

149 else: 

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

151 

152 logger.info( 

153 "\nNext steps:\n" 

154 " 1. Review changes:\n" 

155 " git status\n" 

156 " git diff\n\n" 

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

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

159 " 3. Commit the migration:\n" 

160 " git add .\n" 

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

162 ) 

163 

164 

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

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

167 

168 This command performs the following actions: 

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

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

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

172 4. Provides instructions for next steps 

173 

174 The `.rhiza/` folder will contain: 

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

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

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

178 

179 Args: 

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

181 """ 

182 logger.warning("⚠️ The 'migrate' command is deprecated and will be removed in a future release.") 

183 target = target.resolve() 

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

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

186 

187 # Create .rhiza directory 

188 rhiza_dir = _create_rhiza_directory(target) 

189 

190 # Migrate template file 

191 template_exists, template_migrations = _migrate_template_file(target, rhiza_dir) 

192 

193 # Ensure .rhiza is in include list 

194 if template_exists: 

195 _ensure_rhiza_in_include(rhiza_dir / "template.yml") 

196 

197 # Migrate history file 

198 history_migrations = _migrate_history_file(target, rhiza_dir) 

199 

200 # Print summary 

201 all_migrations = template_migrations + history_migrations 

202 _print_migration_summary(all_migrations)