@bniladridas/cursor 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#include "services/file_service.h"
|
|
2
|
+
#include "utils/validation.h"
|
|
3
|
+
#include <filesystem>
|
|
4
|
+
#include <fstream>
|
|
5
|
+
#include <sstream>
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cctype>
|
|
9
|
+
#include <regex>
|
|
10
|
+
|
|
11
|
+
namespace Services {
|
|
12
|
+
static const std::size_t MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB limit
|
|
13
|
+
|
|
14
|
+
std::string FileService::read_file(const std::string &filename) {
|
|
15
|
+
try {
|
|
16
|
+
if (!file_exists(filename)) {
|
|
17
|
+
return "Error: File '" + filename + "' does not exist";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
|
21
|
+
if (!file.is_open()) {
|
|
22
|
+
return "Error: Could not open file '" + filename + "'";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
auto size = file.tellg();
|
|
26
|
+
if (size > static_cast<std::streampos>(MAX_FILE_SIZE)) {
|
|
27
|
+
return "Error: File too large to read (" + std::to_string(size) +
|
|
28
|
+
" bytes)";
|
|
29
|
+
}
|
|
30
|
+
file.seekg(0);
|
|
31
|
+
|
|
32
|
+
std::ostringstream buffer;
|
|
33
|
+
buffer << file.rdbuf();
|
|
34
|
+
return buffer.str();
|
|
35
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
36
|
+
return std::string("Error reading file: ") + e.what();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
std::string FileService::read_file_range(const std::string &filename,
|
|
41
|
+
int start_line, int line_count) {
|
|
42
|
+
try {
|
|
43
|
+
if (!file_exists(filename)) {
|
|
44
|
+
return "Error: File '" + filename + "' does not exist";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
std::ifstream file(filename);
|
|
48
|
+
if (!file.is_open()) {
|
|
49
|
+
return "Error: Could not open file '" + filename + "'";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
std::vector<std::string> lines;
|
|
53
|
+
std::string line;
|
|
54
|
+
while (std::getline(file, line)) {
|
|
55
|
+
lines.push_back(line);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (start_line < 0 || start_line >= static_cast<int>(lines.size())) {
|
|
59
|
+
return "Error: Start line out of range";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
int end_line =
|
|
63
|
+
(line_count == -1)
|
|
64
|
+
? static_cast<int>(lines.size())
|
|
65
|
+
: std::min(start_line + line_count, static_cast<int>(lines.size()));
|
|
66
|
+
|
|
67
|
+
std::ostringstream result;
|
|
68
|
+
for (int i = start_line; i < end_line; ++i) {
|
|
69
|
+
result << lines[i];
|
|
70
|
+
if (i < end_line - 1)
|
|
71
|
+
result << "\n";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result.str();
|
|
75
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
76
|
+
return std::string("Error reading file range: ") + e.what();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
std::string FileService::write_file(const std::string &filename,
|
|
81
|
+
const std::string &content) {
|
|
82
|
+
try {
|
|
83
|
+
if (content.size() > MAX_FILE_SIZE) {
|
|
84
|
+
return "Error: Content too large to write";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
std::filesystem::path file_path(filename);
|
|
88
|
+
if (file_path.has_parent_path()) {
|
|
89
|
+
std::filesystem::create_directories(file_path.parent_path());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
std::ofstream file(filename, std::ios::binary);
|
|
93
|
+
if (!file.is_open()) {
|
|
94
|
+
return "Error: Could not create/write file '" + filename + "'";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
file << content;
|
|
98
|
+
if (!file) {
|
|
99
|
+
return "Error: Write operation failed";
|
|
100
|
+
}
|
|
101
|
+
return "File '" + filename + "' written successfully (" +
|
|
102
|
+
std::to_string(content.length()) + " bytes)";
|
|
103
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
104
|
+
return std::string("Error writing file: ") + e.what();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
FileEditResult FileService::replace_text_in_file(const std::string &filename,
|
|
109
|
+
const std::string &old_text,
|
|
110
|
+
const std::string &new_text,
|
|
111
|
+
int expected_replacements) {
|
|
112
|
+
FileEditResult result = {false, "", 0};
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
if (!file_exists(filename)) {
|
|
116
|
+
result.message = "Error: File '" + filename + "' does not exist";
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Read file content
|
|
121
|
+
std::ifstream file(filename);
|
|
122
|
+
if (!file.is_open()) {
|
|
123
|
+
result.message = "Error: Could not open file '" + filename + "'";
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
std::ostringstream buffer;
|
|
128
|
+
buffer << file.rdbuf();
|
|
129
|
+
std::string content = buffer.str();
|
|
130
|
+
file.close();
|
|
131
|
+
|
|
132
|
+
// Count occurrences
|
|
133
|
+
size_t pos = 0;
|
|
134
|
+
int count = 0;
|
|
135
|
+
while ((pos = content.find(old_text, pos)) != std::string::npos) {
|
|
136
|
+
count++;
|
|
137
|
+
pos += old_text.length();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (count == 0) {
|
|
141
|
+
result.message = "Error: Text to replace not found in file";
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (expected_replacements > 0 && count != expected_replacements) {
|
|
146
|
+
result.message = "Error: Expected " +
|
|
147
|
+
std::to_string(expected_replacements) +
|
|
148
|
+
" replacements but found " + std::to_string(count);
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Perform replacements
|
|
153
|
+
pos = 0;
|
|
154
|
+
while ((pos = content.find(old_text, pos)) != std::string::npos) {
|
|
155
|
+
content.replace(pos, old_text.length(), new_text);
|
|
156
|
+
pos += new_text.length();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Write back to file
|
|
160
|
+
std::ofstream out_file(filename);
|
|
161
|
+
if (!out_file.is_open()) {
|
|
162
|
+
result.message = "Error: Could not write to file '" + filename + "'";
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
out_file << content;
|
|
167
|
+
if (!out_file) {
|
|
168
|
+
result.message = "Error: Write operation failed";
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
result.success = true;
|
|
173
|
+
result.replacements_made = count;
|
|
174
|
+
result.message =
|
|
175
|
+
"Successfully replaced " + std::to_string(count) + " occurrence(s)";
|
|
176
|
+
return result;
|
|
177
|
+
|
|
178
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
179
|
+
result.message = std::string("Error during text replacement: ") + e.what();
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
std::vector<FileSearchResult>
|
|
185
|
+
FileService::search_in_file(const std::string &filename,
|
|
186
|
+
const std::string &pattern, bool case_sensitive) {
|
|
187
|
+
std::vector<FileSearchResult> results;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
if (!file_exists(filename) || !is_text_file(filename)) {
|
|
191
|
+
return results;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
std::ifstream file(filename);
|
|
195
|
+
if (!file.is_open()) {
|
|
196
|
+
return results;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
std::regex regex_pattern;
|
|
200
|
+
if (case_sensitive) {
|
|
201
|
+
regex_pattern = std::regex(pattern);
|
|
202
|
+
} else {
|
|
203
|
+
regex_pattern = std::regex(pattern, std::regex_constants::icase);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
std::string line;
|
|
207
|
+
int line_number = 1;
|
|
208
|
+
|
|
209
|
+
while (std::getline(file, line)) {
|
|
210
|
+
std::smatch match;
|
|
211
|
+
if (std::regex_search(line, match, regex_pattern)) {
|
|
212
|
+
FileSearchResult result;
|
|
213
|
+
result.file_path = filename;
|
|
214
|
+
result.line_number = line_number;
|
|
215
|
+
result.line_content = line;
|
|
216
|
+
result.match_context = line; // For now, context is the same as line
|
|
217
|
+
results.push_back(result);
|
|
218
|
+
}
|
|
219
|
+
line_number++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
223
|
+
// Log error but return partial results
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return results;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
std::vector<FileSearchResult> FileService::search_in_directory(
|
|
230
|
+
const std::string &directory, const std::string &pattern,
|
|
231
|
+
const std::string &file_filter, bool case_sensitive) {
|
|
232
|
+
std::vector<FileSearchResult> all_results;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
if (!std::filesystem::exists(directory) ||
|
|
236
|
+
!std::filesystem::is_directory(directory)) {
|
|
237
|
+
return all_results;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Simple file filter matching (supports * wildcard)
|
|
241
|
+
auto matches_filter = [&file_filter](const std::string &filename) {
|
|
242
|
+
if (file_filter == "*")
|
|
243
|
+
return true;
|
|
244
|
+
|
|
245
|
+
// Simple wildcard matching
|
|
246
|
+
if (file_filter.find('*') != std::string::npos) {
|
|
247
|
+
std::string pattern_regex = file_filter;
|
|
248
|
+
std::replace(pattern_regex.begin(), pattern_regex.end(), '*', '.');
|
|
249
|
+
pattern_regex = ".*" + pattern_regex + ".*";
|
|
250
|
+
try {
|
|
251
|
+
std::regex filter_regex(pattern_regex, std::regex_constants::icase);
|
|
252
|
+
return std::regex_match(filename, filter_regex);
|
|
253
|
+
} catch (...) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return filename.find(file_filter) != std::string::npos;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
for (const auto &entry :
|
|
262
|
+
std::filesystem::recursive_directory_iterator(directory)) {
|
|
263
|
+
if (entry.is_regular_file()) {
|
|
264
|
+
std::string filepath = entry.path().string();
|
|
265
|
+
std::string filename = entry.path().filename().string();
|
|
266
|
+
|
|
267
|
+
if (matches_filter(filename) && is_text_file(filepath)) {
|
|
268
|
+
auto file_results = search_in_file(filepath, pattern, case_sensitive);
|
|
269
|
+
all_results.insert(all_results.end(), file_results.begin(),
|
|
270
|
+
file_results.end());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
276
|
+
// Log error but return partial results
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return all_results;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bool FileService::file_exists(const std::string &filename) {
|
|
283
|
+
try {
|
|
284
|
+
return std::filesystem::exists(filename);
|
|
285
|
+
} catch (...) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
std::string FileService::get_file_extension(const std::string &filename) {
|
|
291
|
+
std::filesystem::path file_path(filename);
|
|
292
|
+
return file_path.extension().string();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
bool FileService::is_text_file(const std::string &filename) {
|
|
296
|
+
std::string ext = get_file_extension(filename);
|
|
297
|
+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
|
298
|
+
|
|
299
|
+
// Common text file extensions
|
|
300
|
+
static const std::vector<std::string> text_extensions =
|
|
301
|
+
Utils::Validator::get_text_extensions();
|
|
302
|
+
|
|
303
|
+
return std::find(text_extensions.begin(), text_extensions.end(), ext) !=
|
|
304
|
+
text_extensions.end();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
std::string FileService::get_relative_path(const std::string &filepath,
|
|
308
|
+
const std::string &base_path) {
|
|
309
|
+
try {
|
|
310
|
+
std::filesystem::path file_path = std::filesystem::absolute(filepath);
|
|
311
|
+
std::filesystem::path base = std::filesystem::absolute(base_path);
|
|
312
|
+
return std::filesystem::relative(file_path, base).string();
|
|
313
|
+
} catch (...) {
|
|
314
|
+
return filepath;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
bool FileService::is_within_directory(const std::string &filepath,
|
|
319
|
+
const std::string &base_directory) {
|
|
320
|
+
try {
|
|
321
|
+
std::filesystem::path file_path = std::filesystem::absolute(filepath);
|
|
322
|
+
std::filesystem::path base = std::filesystem::absolute(base_directory);
|
|
323
|
+
|
|
324
|
+
auto file_it = file_path.begin();
|
|
325
|
+
auto base_it = base.begin();
|
|
326
|
+
|
|
327
|
+
while (base_it != base.end() && file_it != file_path.end()) {
|
|
328
|
+
if (*base_it != *file_it) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
++base_it;
|
|
332
|
+
++file_it;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return base_it == base.end();
|
|
336
|
+
} catch (...) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
std::string FileService::normalize_path(const std::string &path) {
|
|
342
|
+
try {
|
|
343
|
+
return std::filesystem::absolute(path).string();
|
|
344
|
+
} catch (...) {
|
|
345
|
+
return path;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
} // namespace Services
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#include "services/git_service.h"
|
|
2
|
+
#include "utils/platform.h"
|
|
3
|
+
#include "utils/validation.h"
|
|
4
|
+
#include <filesystem>
|
|
5
|
+
#include <fstream>
|
|
6
|
+
#include <regex>
|
|
7
|
+
#include <sstream>
|
|
8
|
+
|
|
9
|
+
namespace Services {
|
|
10
|
+
|
|
11
|
+
GitService::GitService() {}
|
|
12
|
+
|
|
13
|
+
bool GitService::is_git_repository(const std::string &path) {
|
|
14
|
+
std::filesystem::path git_dir = std::filesystem::path(path) / ".git";
|
|
15
|
+
return std::filesystem::exists(git_dir);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
std::string GitService::get_git_log(const std::string &path, int days) {
|
|
19
|
+
if (!is_git_repository(path)) {
|
|
20
|
+
return "Error: Not a git repository";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
std::string command = "cd \"" + path + "\" && git log --oneline --since=\"" +
|
|
24
|
+
std::to_string(days) + " days ago\"";
|
|
25
|
+
|
|
26
|
+
auto validation = Utils::Validator::validate_command_safe(command);
|
|
27
|
+
if (!validation.warnings.empty()) {
|
|
28
|
+
return "Error: Command validation failed";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
FILE *pipe = Utils::Platform::open_process(command, "r");
|
|
32
|
+
if (!pipe) {
|
|
33
|
+
return "Error: Failed to execute git command";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
std::string result;
|
|
37
|
+
char buffer[128];
|
|
38
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
39
|
+
result += buffer;
|
|
40
|
+
}
|
|
41
|
+
Utils::Platform::close_process(pipe);
|
|
42
|
+
|
|
43
|
+
return result.empty()
|
|
44
|
+
? "No commits found in the last " + std::to_string(days) + " days"
|
|
45
|
+
: result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
std::string GitService::get_git_status(const std::string &path) {
|
|
49
|
+
if (!is_git_repository(path)) {
|
|
50
|
+
return "Error: Not a git repository";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::string command = "cd \"" + path + "\" && git status --porcelain";
|
|
54
|
+
|
|
55
|
+
FILE *pipe = Utils::Platform::open_process(command, "r");
|
|
56
|
+
if (!pipe) {
|
|
57
|
+
return "Error: Failed to execute git status";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
std::string result;
|
|
61
|
+
char buffer[128];
|
|
62
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
63
|
+
result += buffer;
|
|
64
|
+
}
|
|
65
|
+
Utils::Platform::close_process(pipe);
|
|
66
|
+
|
|
67
|
+
return result.empty() ? "Working directory clean" : result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
std::vector<std::string> GitService::get_changed_files(const std::string &path,
|
|
71
|
+
int days) {
|
|
72
|
+
std::vector<std::string> files;
|
|
73
|
+
|
|
74
|
+
if (!is_git_repository(path)) {
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
std::string command =
|
|
79
|
+
"cd \"" + path + "\" && git log --name-only --pretty=format: --since=\"" +
|
|
80
|
+
std::to_string(days) + " days ago\" | sort | uniq";
|
|
81
|
+
|
|
82
|
+
FILE *pipe = Utils::Platform::open_process(command, "r");
|
|
83
|
+
if (!pipe) {
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
char buffer[256];
|
|
88
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
89
|
+
std::string file(buffer);
|
|
90
|
+
// Remove newline
|
|
91
|
+
if (!file.empty() && file.back() == '\n') {
|
|
92
|
+
file.pop_back();
|
|
93
|
+
}
|
|
94
|
+
if (!file.empty()) {
|
|
95
|
+
files.push_back(file);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
Utils::Platform::close_process(pipe);
|
|
99
|
+
|
|
100
|
+
return files;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
std::string GitService::analyze_repository(const std::string &path) {
|
|
104
|
+
if (!is_git_repository(path)) {
|
|
105
|
+
return "Error: Not a git repository";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
std::ostringstream analysis;
|
|
109
|
+
analysis << "Git Repository Analysis:\n\n";
|
|
110
|
+
|
|
111
|
+
// Get basic info
|
|
112
|
+
std::string branch_cmd = "cd \"" + path + "\" && git branch --show-current";
|
|
113
|
+
FILE *pipe = Utils::Platform::open_process(branch_cmd, "r");
|
|
114
|
+
if (pipe) {
|
|
115
|
+
char buffer[128];
|
|
116
|
+
if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
117
|
+
std::string branch(buffer);
|
|
118
|
+
if (!branch.empty() && branch.back() == '\n') {
|
|
119
|
+
branch.pop_back();
|
|
120
|
+
}
|
|
121
|
+
analysis << "Current branch: " << branch << "\n";
|
|
122
|
+
}
|
|
123
|
+
Utils::Platform::close_process(pipe);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get commit count
|
|
127
|
+
std::string count_cmd = "cd \"" + path + "\" && git rev-list --count HEAD";
|
|
128
|
+
pipe = Utils::Platform::open_process(count_cmd, "r");
|
|
129
|
+
if (pipe) {
|
|
130
|
+
char buffer[128];
|
|
131
|
+
if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
132
|
+
analysis << "Total commits: " << buffer;
|
|
133
|
+
}
|
|
134
|
+
Utils::Platform::close_process(pipe);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get recent activity
|
|
138
|
+
analysis << "\nRecent commits (last 7 days):\n";
|
|
139
|
+
analysis << get_git_log(path, 7);
|
|
140
|
+
|
|
141
|
+
// Get status
|
|
142
|
+
analysis << "\nWorking directory status:\n";
|
|
143
|
+
analysis << get_git_status(path);
|
|
144
|
+
|
|
145
|
+
return analysis.str();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
} // namespace Services
|