yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
version_manager.cc
Go to the documentation of this file.
2
3#include <array>
4#include <chrono>
5#include <cstdio>
6#include <filesystem>
7#include <iomanip>
8#include <iostream>
9#include <memory>
10#include <sstream>
11
12#include "absl/strings/str_format.h"
13#include "util/log.h"
14
15namespace yaze {
16namespace core {
17
18namespace fs = std::filesystem;
19
21 : project_(project) {}
22
24 const fs::path project_dir = fs::path(project_->filepath).parent_path();
25 const fs::path repo(project_->git_repository);
26 if (repo.empty())
27 return project_dir;
28 if (repo.is_relative() && !project_dir.empty())
29 return project_dir / repo;
30 return repo;
31}
32
34 if (!project_ || project_->git_repository.empty())
35 return false;
36
37 // Resolve against the project directory, not the process working directory;
38 // RunCommand() runs git from the project directory.
39 std::error_code ec;
40 return fs::exists(RepositoryRoot() / ".git", ec);
41}
42
44 if (!project_)
45 return false;
46
47 if (project_->git_repository.empty()) {
48 std::error_code ec;
49 if (fs::exists(RepositoryRoot() / ".git", ec)) {
51 }
52 }
53 return IsGitInitialized();
54}
55
57 if (!project_)
58 return absl::InvalidArgumentError("No project context");
59
60 // Ensure git repository path is set (default to project root)
61 if (project_->git_repository.empty()) {
62 project_->git_repository = "."; // Default to current dir
63 }
64
65 if (IsGitInitialized())
66 return absl::OkStatus();
67
68 return GitInit();
69}
70
71absl::StatusOr<SnapshotResult> VersionManager::CreateSnapshot(
72 const std::string& message) {
73 if (!project_)
74 return absl::InvalidArgumentError("No project context");
75
76 SnapshotResult result;
77 result.success = false;
78
79 // 1. Ensure Git is ready
80 if (!IsGitInitialized()) {
81 auto status = InitializeGit();
82 if (!status.ok())
83 return status;
84 }
85
86 // 2. Commit Code
87 auto add_status = GitAddAll();
88 if (!add_status.ok())
89 return add_status;
90
91 auto commit_status = GitCommit(message);
92 if (!commit_status.ok()) {
93 // It's possible there were no changes to commit, which is fine.
94 // We continue to ROM backup.
95 LOG_INFO("VersionManager", "Git commit skipped (no changes?)");
96 }
97
99
100 // 3. Backup ROM Artifact
101 // Generate timestamp
102 auto now = std::chrono::system_clock::now();
103 auto time_t = std::chrono::system_clock::to_time_t(now);
104 std::stringstream ss;
105 ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
106 std::string timestamp = ss.str();
107
108 auto backup_status = BackupRomArtifact(timestamp);
109 if (backup_status.ok()) {
110 result.rom_backup_path = "backups/snapshots/rom_" + timestamp + ".sfc";
111 } else {
112 LOG_ERROR("VersionManager", "Failed to backup ROM: %s",
113 backup_status.ToString().c_str());
114 }
115
116 result.success = true;
117 result.message = "Snapshot created successfully.";
118
119 // Update project build hash
121
122 return result;
123}
124
126 return GitRevParseHead();
127}
128
129std::vector<std::string> VersionManager::GetHistory(int limit) const {
130 std::string cmd =
131 absl::StrFormat("git log -n %d --pretty=format:\"%%h %%s\"", limit);
132 auto output = RunCommandOutput(cmd);
133 if (!output.ok())
134 return {};
135
136 // Split lines (basic)
137 std::vector<std::string> history;
138 std::istringstream stream(*output);
139 std::string line;
140 while (std::getline(stream, line)) {
141 if (!line.empty())
142 history.push_back(line);
143 }
144 return history;
145}
146
147// ============================================================================
148// Git Implementation
149// ============================================================================
150
152 return RunCommand("git init");
153}
154
156 // We specifically want to avoid adding the ROMs if they aren't ignored.
157 // But assuming user has .gitignore setup properly for Roms/*.sfc
158 return RunCommand("git add .");
159}
160
161absl::Status VersionManager::GitCommit(const std::string& message) {
162 std::string sanitized_msg = message;
163 // Basic sanitization for shell command (replace quotes)
164 std::replace(sanitized_msg.begin(), sanitized_msg.end(), '"', '\'');
165
166 return RunCommand(absl::StrFormat("git commit -m \"%s\"", sanitized_msg));
167}
168
170 auto res = RunCommandOutput("git rev-parse --short HEAD");
171 if (res.ok()) {
172 // Trim newline
173 std::string hash = *res;
174 hash.erase(hash.find_last_not_of(" \n\r\t") + 1);
175 return hash;
176 }
177 return "";
178}
179
180// ============================================================================
181// ROM Backup Implementation
182// ============================================================================
183
185 const std::string& timestamp_str) {
186 if (project_->output_folder.empty() || project_->build_target.empty()) {
187 return absl::FailedPreconditionError(
188 "Project output folder or build target not defined");
189 }
190
191 fs::path rom_path(project_->build_target);
192
193 // Resolve relative paths against project directory
194 fs::path project_dir = fs::path(project_->filepath).parent_path();
195 fs::path abs_rom_path = project_dir / rom_path;
196
197 if (!fs::exists(abs_rom_path)) {
198 return absl::NotFoundError(
199 absl::StrFormat("Built ROM not found at %s", abs_rom_path.string()));
200 }
201
202 // Determine backup location
203 fs::path backup_dir = project_dir / "backups" / "snapshots";
204 std::error_code ec;
205 fs::create_directories(backup_dir, ec);
206 if (ec)
207 return absl::InternalError("Could not create backup directory");
208
209 std::string filename = absl::StrFormat("rom_%s.sfc", timestamp_str);
210 fs::path backup_path = backup_dir / filename;
211
212 // Copy file
213 fs::copy_file(abs_rom_path, backup_path, fs::copy_options::overwrite_existing,
214 ec);
215 if (ec) {
216 return absl::InternalError(
217 absl::StrFormat("Failed to copy ROM: %s", ec.message()));
218 }
219
220 return absl::OkStatus();
221}
222
223// ============================================================================
224// System Helpers
225// ============================================================================
226
227absl::Status VersionManager::RunCommand(const std::string& cmd) {
228#if defined(YAZE_IOS)
229 (void)cmd;
230 return absl::FailedPreconditionError(
231 "Command execution is not available on iOS");
232#else
233 // Execute command in project root
234 std::string full_cmd;
235
236 fs::path project_dir = fs::path(project_->filepath).parent_path();
237 if (!project_dir.empty()) {
238 full_cmd = absl::StrFormat("cd \"%s\" && %s", project_dir.string(), cmd);
239 } else {
240 full_cmd = cmd;
241 }
242
243 int ret = std::system(full_cmd.c_str());
244 if (ret != 0) {
245 return absl::InternalError(
246 absl::StrFormat("Command failed: %s (Exit code %d)", cmd, ret));
247 }
248 return absl::OkStatus();
249#endif
250}
251
252absl::StatusOr<std::string> VersionManager::RunCommandOutput(
253 const std::string& cmd) const {
254#if defined(YAZE_IOS)
255 (void)cmd;
256 return absl::FailedPreconditionError(
257 "Command execution is not available on iOS");
258#else
259 std::string full_cmd;
260 fs::path project_dir = fs::path(project_->filepath).parent_path();
261 if (!project_dir.empty()) {
262 full_cmd = absl::StrFormat("cd \"%s\" && %s", project_dir.string(), cmd);
263 } else {
264 full_cmd = cmd;
265 }
266
267 std::array<char, 128> buffer;
268 std::string result;
269#ifdef _WIN32
270 std::unique_ptr<FILE, decltype(&_pclose)> pipe(_popen(full_cmd.c_str(), "r"),
271 _pclose);
272#else
273 std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(full_cmd.c_str(), "r"),
274 pclose);
275#endif
276 if (!pipe) {
277 return absl::InternalError("popen() failed!");
278 }
279 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
280 result += buffer.data();
281 }
282 return result;
283#endif
284}
285
286} // namespace core
287} // namespace yaze
absl::Status BackupRomArtifact(const std::string &timestamp_str)
absl::Status GitCommit(const std::string &message)
std::vector< std::string > GetHistory(int limit=10) const
project::YazeProject * project_
VersionManager(project::YazeProject *project)
absl::StatusOr< std::string > RunCommandOutput(const std::string &cmd) const
std::string GetCurrentHash() const
absl::StatusOr< SnapshotResult > CreateSnapshot(const std::string &message)
absl::Status RunCommand(const std::string &cmd)
std::string GitRevParseHead() const
std::filesystem::path RepositoryRoot() const
#define LOG_ERROR(category, format,...)
Definition log.h:140
#define LOG_INFO(category, format,...)
Definition log.h:136
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
std::string git_repository
Definition project.h:374
std::string output_folder
Definition project.h:367
std::string last_build_hash
Definition project.h:376