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 <chrono>
4#include <filesystem>
5#include <iomanip>
6#include <sstream>
7#include <array>
8#include <cstdio>
9#include <memory>
10#include <iostream>
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
23 if (!project_ || project_->git_repository.empty()) return false;
24
25 fs::path git_dir = fs::path(project_->git_repository) / ".git";
26 return fs::exists(git_dir);
27}
28
30 if (!project_) return absl::InvalidArgumentError("No project context");
31
32 // Ensure git repository path is set (default to project root)
33 if (project_->git_repository.empty()) {
34 project_->git_repository = "."; // Default to current dir
35 }
36
37 if (IsGitInitialized()) return absl::OkStatus();
38
39 return GitInit();
40}
41
42absl::StatusOr<SnapshotResult> VersionManager::CreateSnapshot(const std::string& message) {
43 if (!project_) return absl::InvalidArgumentError("No project context");
44
45 SnapshotResult result;
46 result.success = false;
47
48 // 1. Ensure Git is ready
49 if (!IsGitInitialized()) {
50 auto status = InitializeGit();
51 if (!status.ok()) return status;
52 }
53
54 // 2. Commit Code
55 auto add_status = GitAddAll();
56 if (!add_status.ok()) return add_status;
57
58 auto commit_status = GitCommit(message);
59 if (!commit_status.ok()) {
60 // It's possible there were no changes to commit, which is fine.
61 // We continue to ROM backup.
62 LOG_INFO("VersionManager", "Git commit skipped (no changes?)");
63 }
64
66
67 // 3. Backup ROM Artifact
68 // Generate timestamp
69 auto now = std::chrono::system_clock::now();
70 auto time_t = std::chrono::system_clock::to_time_t(now);
71 std::stringstream ss;
72 ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
73 std::string timestamp = ss.str();
74
75 auto backup_status = BackupRomArtifact(timestamp);
76 if (backup_status.ok()) {
77 result.rom_backup_path = "backups/snapshots/rom_" + timestamp + ".sfc";
78 } else {
79 LOG_ERROR("VersionManager", "Failed to backup ROM: %s", backup_status.ToString().c_str());
80 }
81
82 result.success = true;
83 result.message = "Snapshot created successfully.";
84
85 // Update project build hash
87
88 return result;
89}
90
91std::string VersionManager::GetCurrentHash() const {
92 return GitRevParseHead();
93}
94
95std::vector<std::string> VersionManager::GetHistory(int limit) const {
96 std::string cmd = absl::StrFormat("git log -n %d --pretty=format:\"%%h %%s\"", limit);
97 auto output = RunCommandOutput(cmd);
98 if (!output.ok()) return {};
99
100 // Split lines (basic)
101 std::vector<std::string> history;
102 std::istringstream stream(*output);
103 std::string line;
104 while (std::getline(stream, line)) {
105 if (!line.empty()) history.push_back(line);
106 }
107 return history;
108}
109
110// ============================================================================
111// Git Implementation
112// ============================================================================
113
115 return RunCommand("git init");
116}
117
119 // We specifically want to avoid adding the ROMs if they aren't ignored.
120 // But assuming user has .gitignore setup properly for Roms/*.sfc
121 return RunCommand("git add .");
122}
123
124absl::Status VersionManager::GitCommit(const std::string& message) {
125 std::string sanitized_msg = message;
126 // Basic sanitization for shell command (replace quotes)
127 std::replace(sanitized_msg.begin(), sanitized_msg.end(), '"', '\'');
128
129 return RunCommand(absl::StrFormat("git commit -m \"%s\"", sanitized_msg));
130}
131
133 auto res = RunCommandOutput("git rev-parse --short HEAD");
134 if (res.ok()) {
135 // Trim newline
136 std::string hash = *res;
137 hash.erase(hash.find_last_not_of(" \n\r\t") + 1);
138 return hash;
139 }
140 return "";
141}
142
143// ============================================================================
144// ROM Backup Implementation
145// ============================================================================
146
147absl::Status VersionManager::BackupRomArtifact(const std::string& timestamp_str) {
148 if (project_->output_folder.empty() || project_->build_target.empty()) {
149 return absl::FailedPreconditionError("Project output folder or build target not defined");
150 }
151
152 fs::path rom_path(project_->build_target);
153
154 // Resolve relative paths against project directory
155 fs::path project_dir = fs::path(project_->filepath).parent_path();
156 fs::path abs_rom_path = project_dir / rom_path;
157
158 if (!fs::exists(abs_rom_path)) {
159 return absl::NotFoundError(absl::StrFormat("Built ROM not found at %s", abs_rom_path.string()));
160 }
161
162 // Determine backup location
163 fs::path backup_dir = project_dir / "backups" / "snapshots";
164 std::error_code ec;
165 fs::create_directories(backup_dir, ec);
166 if (ec) return absl::InternalError("Could not create backup directory");
167
168 std::string filename = absl::StrFormat("rom_%s.sfc", timestamp_str);
169 fs::path backup_path = backup_dir / filename;
170
171 // Copy file
172 fs::copy_file(abs_rom_path, backup_path, fs::copy_options::overwrite_existing, ec);
173 if (ec) {
174 return absl::InternalError(absl::StrFormat("Failed to copy ROM: %s", ec.message()));
175 }
176
177 return absl::OkStatus();
178}
179
180// ============================================================================
181// System Helpers
182// ============================================================================
183
184absl::Status VersionManager::RunCommand(const std::string& cmd) {
185 // Execute command in project root
186 std::string full_cmd;
187
188 fs::path project_dir = fs::path(project_->filepath).parent_path();
189 if (!project_dir.empty()) {
190 full_cmd = absl::StrFormat("cd \"%s\" && %s", project_dir.string(), cmd);
191 } else {
192 full_cmd = cmd;
193 }
194
195 int ret = std::system(full_cmd.c_str());
196 if (ret != 0) {
197 return absl::InternalError(absl::StrFormat("Command failed: %s (Exit code %d)", cmd, ret));
198 }
199 return absl::OkStatus();
200}
201
202absl::StatusOr<std::string> VersionManager::RunCommandOutput(const std::string& cmd) const {
203 std::string full_cmd;
204 fs::path project_dir = fs::path(project_->filepath).parent_path();
205 if (!project_dir.empty()) {
206 full_cmd = absl::StrFormat("cd \"%s\" && %s", project_dir.string(), cmd);
207 } else {
208 full_cmd = cmd;
209 }
210
211 std::array<char, 128> buffer;
212 std::string result;
213 std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(full_cmd.c_str(), "r"), pclose);
214 if (!pipe) {
215 return absl::InternalError("popen() failed!");
216 }
217 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
218 result += buffer.data();
219 }
220 return result;
221}
222
223} // namespace core
224} // 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
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_INFO(category, format,...)
Definition log.h:105
Modern project structure with comprehensive settings consolidation.
Definition project.h:84
std::string git_repository
Definition project.h:123
std::string output_folder
Definition project.h:116
std::string last_build_hash
Definition project.h:125