|
22 | 22 | clean_webpage_text, |
23 | 23 | parse_category_config_from_str, |
24 | 24 | parse_channel_config_from_str, |
| 25 | + create_error_embed, |
| 26 | + create_success_embed, |
25 | 27 | ) |
26 | 28 | from ..views.base_views import DeleteExtraObjectsView |
27 | 29 | from ..utils import template_metadata |
@@ -88,70 +90,234 @@ async def synccommands_prefix(self, ctx: commands.Context) -> None: |
88 | 90 | ctx, "❌ Sync Failed", f"Failed to sync commands: {e}" |
89 | 91 | ) |
90 | 92 |
|
| 93 | + def _convert_to_git_style_diff(self, result_msgs: list) -> str: |
| 94 | + """Convert verbose template messages to git-style diff format.""" |
| 95 | + diff_lines = [] |
| 96 | + |
| 97 | + for msg in result_msgs: |
| 98 | + # Skip summary lines and info messages |
| 99 | + if "**" in msg or msg.startswith("ℹ️") or "already exists" in msg: |
| 100 | + continue |
| 101 | + |
| 102 | + # Parse different message types |
| 103 | + if "🔄 Updated channel:" in msg or "🔄 Updated category:" in msg: |
| 104 | + # Extract name: "🔄 Updated channel: moderator-only in Boilerplate" -> "M moderator-only" |
| 105 | + parts = msg.split(": ", 1)[1].split(" in ") |
| 106 | + name = parts[0] if parts else "unknown" |
| 107 | + diff_lines.append(f"M {name}") |
| 108 | + elif "Created channel:" in msg or "Created category:" in msg: |
| 109 | + # Extract name: "Created channel: new-channel in Category" -> "A new-channel" |
| 110 | + parts = msg.split(": ", 1)[1].split(" in ") |
| 111 | + name = parts[0] if parts else "unknown" |
| 112 | + diff_lines.append(f"A {name}") |
| 113 | + elif "Deleted channel:" in msg or "Deleted category:" in msg: |
| 114 | + # Extract name for deletions |
| 115 | + parts = msg.split(": ", 1)[1].split(" in ") |
| 116 | + name = parts[0] if parts else "unknown" |
| 117 | + diff_lines.append(f"D {name}") |
| 118 | + # Skip "Skipped" messages as they indicate no changes |
| 119 | + |
| 120 | + return "\n".join(diff_lines) if diff_lines else "No changes" |
| 121 | + |
91 | 122 | @commands.command(name="git") |
92 | 123 | @commands.has_permissions(administrator=True) |
93 | 124 | async def git_command(self, ctx: commands.Context, *args): |
94 | 125 | """Handle !git clone <url> [-b branch], !git pull, and warn on others.""" |
95 | 126 | if not args: |
96 | | - await ctx.send("❌ Usage: !git clone <url> [-b branch] or !git pull") |
| 127 | + embed = create_error_embed( |
| 128 | + "❌ Invalid Usage", |
| 129 | + "Usage: `!git clone <url> [-b branch]` or `!git pull`" |
| 130 | + ) |
| 131 | + await ctx.send(embed=embed) |
97 | 132 | return |
| 133 | + |
98 | 134 | cmd = args[0] |
99 | 135 | guild_id = ctx.guild.id |
100 | 136 | repo_dir = get_template_repo_dir(guild_id) |
| 137 | + |
101 | 138 | if cmd == "clone": |
102 | 139 | if len(args) < 2: |
103 | | - await ctx.send("❌ Usage: !git clone <url> [-b branch]") |
| 140 | + embed = create_error_embed( |
| 141 | + "❌ Missing Repository URL", |
| 142 | + "Usage: `!git clone <url> [-b branch]`" |
| 143 | + ) |
| 144 | + await ctx.send(embed=embed) |
104 | 145 | return |
| 146 | + |
105 | 147 | url = args[1] |
106 | 148 | branch = "main" |
107 | 149 | if len(args) >= 4 and args[2] == "-b": |
108 | 150 | branch = args[3] |
| 151 | + |
109 | 152 | # Remove existing repo if present |
110 | 153 | if os.path.exists(repo_dir): |
111 | 154 | shutil.rmtree(repo_dir) |
112 | 155 | os.makedirs(repo_dir, exist_ok=True) |
| 156 | + |
113 | 157 | try: |
114 | 158 | result = subprocess.run([ |
115 | 159 | "git", "clone", "-b", branch, url, repo_dir |
116 | 160 | ], capture_output=True, text=True, timeout=60) |
| 161 | + |
117 | 162 | if result.returncode != 0: |
118 | | - await ctx.send(f"❌ git clone failed: {result.stderr}") |
| 163 | + error_embed = create_error_embed( |
| 164 | + "❌ Git Clone Failed", |
| 165 | + f"```\n{result.stderr}\n```" |
| 166 | + ) |
| 167 | + await ctx.send(embed=error_embed) |
119 | 168 | return |
| 169 | + |
120 | 170 | # Save metadata |
121 | 171 | template_metadata.save_metadata(guild_id, { |
122 | 172 | "url": url, |
123 | 173 | "branch": branch, |
124 | 174 | "local_path": repo_dir |
125 | 175 | }) |
126 | | - await ctx.send(f"✅ Cloned template repo from {url} (branch: {branch}) for this server.") |
| 176 | + |
| 177 | + # Always show success |
| 178 | + success_embed = create_success_embed( |
| 179 | + "✅ Repository Cloned", |
| 180 | + f"Template repository cloned successfully\n`{url}` (branch: `{branch}`)" |
| 181 | + ) |
| 182 | + await ctx.send(embed=success_embed) |
| 183 | + |
| 184 | + # Show warnings if any |
| 185 | + if result.stderr.strip(): |
| 186 | + warning_embed = create_embed( |
| 187 | + title="⚠️ Clone Warnings", |
| 188 | + description=f"```\n{result.stderr}\n```", |
| 189 | + color=discord.Color.orange() |
| 190 | + ) |
| 191 | + await ctx.send(embed=warning_embed) |
| 192 | + |
| 193 | + except subprocess.TimeoutExpired: |
| 194 | + timeout_embed = create_error_embed( |
| 195 | + "⏰ Clone Timeout", |
| 196 | + "Git clone operation timed out after 60 seconds." |
| 197 | + ) |
| 198 | + await ctx.send(embed=timeout_embed) |
127 | 199 | except Exception as e: |
128 | | - await ctx.send(f"❌ git clone error: {e}") |
| 200 | + error_embed = create_error_embed( |
| 201 | + "❌ Clone Error", |
| 202 | + f"```\n{str(e)}\n```" |
| 203 | + ) |
| 204 | + await ctx.send(embed=error_embed) |
| 205 | + |
129 | 206 | elif cmd == "pull": |
130 | 207 | meta = template_metadata.load_metadata(guild_id) |
131 | 208 | if not meta or not os.path.exists(meta.get("local_path", "")): |
132 | | - await ctx.send("❌ No template repo found for this server. Run !git clone first.") |
| 209 | + embed = create_error_embed( |
| 210 | + "❌ No Template Repository", |
| 211 | + "Run `!git clone <url>` first to set up a template repository." |
| 212 | + ) |
| 213 | + await ctx.send(embed=embed) |
133 | 214 | return |
| 215 | + |
134 | 216 | try: |
135 | 217 | result = subprocess.run([ |
136 | 218 | "git", "pull" |
137 | 219 | ], cwd=meta["local_path"], capture_output=True, text=True, timeout=60) |
| 220 | + |
138 | 221 | if result.returncode != 0: |
139 | | - await ctx.send(f"❌ git pull failed: {result.stderr}") |
| 222 | + error_embed = create_error_embed( |
| 223 | + "❌ Git Pull Failed", |
| 224 | + f"```\n{result.stderr}\n```" |
| 225 | + ) |
| 226 | + await ctx.send(embed=error_embed) |
140 | 227 | return |
141 | | - pull_msg = f"✅ git pull successful: {result.stdout}" |
142 | | - await ctx.send("🔄 Applying template from local repo after pull...") |
| 228 | + |
| 229 | + # Always show basic success |
| 230 | + git_output = result.stdout.strip() |
| 231 | + if git_output and git_output != "Already up to date.": |
| 232 | + # Show changes |
| 233 | + success_embed = create_success_embed( |
| 234 | + "✅ Repository Updated", |
| 235 | + f"```\n{git_output}\n```" |
| 236 | + ) |
| 237 | + else: |
| 238 | + # Show "already up to date" |
| 239 | + success_embed = create_success_embed( |
| 240 | + "✅ Repository Up To Date", |
| 241 | + "No changes found in remote repository" |
| 242 | + ) |
| 243 | + await ctx.send(embed=success_embed) |
| 244 | + |
| 245 | + # Show git warnings if any |
| 246 | + if result.stderr.strip(): |
| 247 | + warning_embed = create_embed( |
| 248 | + title="⚠️ Git Warnings", |
| 249 | + description=f"```\n{result.stderr}\n```", |
| 250 | + color=discord.Color.orange() |
| 251 | + ) |
| 252 | + await ctx.send(embed=warning_embed) |
| 253 | + |
143 | 254 | try: |
144 | 255 | result_msgs = await self._apply_template_from_dir(ctx.guild, meta["local_path"], ctx=ctx) |
145 | | - for msg in result_msgs: |
146 | | - self.logger.info(f"[git pull apply] {msg}") |
147 | | - await ctx.send(pull_msg + "\n" + "\n".join(result_msgs)) |
| 256 | + |
| 257 | + # Convert to git-style diff |
| 258 | + if result_msgs: |
| 259 | + # Check for warnings/errors first |
| 260 | + warnings = [msg for msg in result_msgs if "warning" in msg.lower() or "error" in msg.lower() or "failed" in msg.lower()] |
| 261 | + |
| 262 | + # Show warnings if any |
| 263 | + if warnings: |
| 264 | + template_warning_embed = create_embed( |
| 265 | + title="⚠️ Template Warnings", |
| 266 | + description=f"```\n{chr(10).join(warnings)}\n```", |
| 267 | + color=discord.Color.orange() |
| 268 | + ) |
| 269 | + await ctx.send(embed=template_warning_embed) |
| 270 | + |
| 271 | + # Convert to git-style diff |
| 272 | + git_diff = self._convert_to_git_style_diff(result_msgs) |
| 273 | + |
| 274 | + if git_diff and git_diff != "No changes": |
| 275 | + template_changes_embed = create_success_embed( |
| 276 | + "✅ Template Applied", |
| 277 | + f"```\n{git_diff}\n```" |
| 278 | + ) |
| 279 | + await ctx.send(embed=template_changes_embed) |
| 280 | + else: |
| 281 | + # No changes |
| 282 | + template_success_embed = create_success_embed( |
| 283 | + "✅ Template Applied", |
| 284 | + "No changes needed" |
| 285 | + ) |
| 286 | + await ctx.send(embed=template_success_embed) |
| 287 | + else: |
| 288 | + # No template results |
| 289 | + template_success_embed = create_success_embed( |
| 290 | + "✅ Template Applied", |
| 291 | + "No output from template processing" |
| 292 | + ) |
| 293 | + await ctx.send(embed=template_success_embed) |
| 294 | + |
148 | 295 | except Exception as e: |
149 | 296 | self.logger.error(f"[git pull apply] Error: {e}", exc_info=True) |
150 | | - await self.send_error(ctx, "❌ Template Error after pull", str(e)) |
| 297 | + error_embed = create_error_embed( |
| 298 | + "❌ Template Application Failed", |
| 299 | + f"```\n{str(e)}\n```" |
| 300 | + ) |
| 301 | + await ctx.send(embed=error_embed) |
| 302 | + |
| 303 | + except subprocess.TimeoutExpired: |
| 304 | + timeout_embed = create_error_embed( |
| 305 | + "⏰ Pull Timeout", |
| 306 | + "Git pull operation timed out after 60 seconds." |
| 307 | + ) |
| 308 | + await ctx.send(embed=timeout_embed) |
151 | 309 | except Exception as e: |
152 | | - await ctx.send(f"❌ git pull error: {e}") |
| 310 | + error_embed = create_error_embed( |
| 311 | + "❌ Pull Error", |
| 312 | + f"```\n{str(e)}\n```" |
| 313 | + ) |
| 314 | + await ctx.send(embed=error_embed) |
153 | 315 | else: |
154 | | - await ctx.send("⚠️ Only 'git clone' and 'git pull' are supported for templates.") |
| 316 | + embed = create_error_embed( |
| 317 | + "⚠️ Unsupported Git Command", |
| 318 | + f"Only `git clone` and `git pull` are supported. You tried: `!git {cmd}`" |
| 319 | + ) |
| 320 | + await ctx.send(embed=embed) |
155 | 321 |
|
156 | 322 | # Patch applytemplate to use local repo if present |
157 | 323 | def _get_template_dir(self, folder=None, guild_id=None): |
|
0 commit comments