Line data Source code
1 : /*!
2 : * \file esys/repo/gitcmdline/git_cmdline.cpp
3 : * \brief System ``git`` CLI backend (sync-capable: clone/fetch/checkout/…)
4 : *
5 : * \cond
6 : * __legal_b__
7 : *
8 : * Copyright (c) 2020-2026 Michel Gillet
9 : * Distributed under the MIT License.
10 : * (See accompanying file LICENSE.txt or
11 : * copy at https://opensource.org/licenses/MIT)
12 : *
13 : * __legal_e__
14 : * \endcond
15 : *
16 : */
17 :
18 : #include "esys/repo/esysrepo_prec.h"
19 : #include "esys/repo/gitcmdline/git.h"
20 :
21 : #include "esys/repo/git/branch.h"
22 : #include "esys/repo/git/commithash.h"
23 :
24 : #include <boost/filesystem.hpp>
25 : #include <boost/process/search_path.hpp>
26 :
27 : #include <cstdio>
28 : #include <cstdlib>
29 : #include <sstream>
30 : #include <string>
31 : #include <vector>
32 :
33 : #ifdef _WIN32
34 : #define ESYSREPO_POPEN _popen
35 : #define ESYSREPO_PCLOSE _pclose
36 : #else
37 : #define ESYSREPO_POPEN popen
38 : #define ESYSREPO_PCLOSE pclose
39 : #endif
40 :
41 : namespace esys::repo::gitcmdline
42 : {
43 :
44 : namespace
45 : {
46 :
47 151 : std::string shell_quote(const std::string &arg)
48 : {
49 : #ifdef _WIN32
50 : // cmd.exe: double % (env expansion) and quote when spaces or shell metacharacters
51 : // appear (notably '|' in git for-each-ref --format).
52 : std::string escaped;
53 : escaped.reserve(arg.size() * 2);
54 : for (char c : arg)
55 : {
56 : if (c == '%') escaped += '%';
57 : escaped += c;
58 : }
59 : const bool need_quote = escaped.find_first_of(" \t\"|&<>^!") != std::string::npos;
60 : if (!need_quote) return escaped;
61 : std::string out = "\"";
62 : for (char c : escaped)
63 : {
64 : // cmd embedded quote is ""
65 : if (c == '"') out += '"';
66 : out += c;
67 : }
68 : out += '"';
69 : return out;
70 : #else
71 151 : if (arg.find_first_of(" \t\"'\\") == std::string::npos) return arg;
72 0 : std::string out = "\"";
73 0 : for (char c : arg)
74 : {
75 0 : if (c == '"') out += '\\';
76 0 : out += c;
77 : }
78 0 : out += '"';
79 0 : return out;
80 : #endif
81 151 : }
82 :
83 18 : std::string trim(std::string s)
84 : {
85 32 : while (!s.empty() && (s.back() == '\n' || s.back() == '\r' || s.back() == ' ' || s.back() == '\t')) s.pop_back();
86 : std::size_t i = 0;
87 18 : while (i < s.size() && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) ++i;
88 18 : return s.substr(i);
89 : }
90 :
91 2 : std::string strip_rev_prefix(std::string rev)
92 : {
93 2 : const std::string heads = "refs/heads/";
94 2 : const std::string tags = "refs/tags/";
95 2 : if (rev.compare(0, heads.size(), heads) == 0) return rev.substr(heads.size());
96 2 : if (rev.compare(0, tags.size(), tags) == 0) return rev.substr(tags.size());
97 4 : return rev;
98 2 : }
99 :
100 3 : std::vector<std::string> split_lines(const std::string &text)
101 : {
102 3 : std::vector<std::string> lines;
103 3 : std::string cur;
104 180 : for (char c : text)
105 : {
106 177 : if (c == '\n')
107 : {
108 3 : if (!cur.empty() && cur.back() == '\r') cur.pop_back();
109 3 : lines.push_back(cur);
110 3 : cur.clear();
111 : }
112 : else
113 351 : cur += c;
114 : }
115 3 : if (!cur.empty())
116 : {
117 0 : if (cur.back() == '\r') cur.pop_back();
118 0 : lines.push_back(cur);
119 : }
120 3 : return lines;
121 3 : }
122 :
123 : } // namespace
124 :
125 : bool Git::s_detect_ssh_agent_done = false;
126 : bool Git::s_ssh_agent_running = false;
127 :
128 0 : std::shared_ptr<GitBase> Git::new_ptr()
129 : {
130 0 : return std::make_shared<Git>();
131 : }
132 :
133 6 : Git::Git()
134 6 : : GitBase()
135 : {
136 6 : }
137 :
138 6 : Git::~Git()
139 : {
140 6 : if (m_open) close();
141 6 : }
142 :
143 69 : std::string Git::find_git_executable()
144 : {
145 69 : if (const char *env = std::getenv("ESYSREPO_GIT_EXE"))
146 : {
147 0 : if (env[0] != '\0' && boost::filesystem::exists(env)) return std::string(env);
148 : }
149 69 : if (const char *env = std::getenv("GIT_EXECUTABLE"))
150 : {
151 0 : if (env[0] != '\0' && boost::filesystem::exists(env)) return std::string(env);
152 : }
153 138 : boost::filesystem::path found = boost::process::search_path("git");
154 : // Prefer the bare name so std::system/cmd.exe does not break on spaces in
155 : // "C:\Program Files\Git\..." (PATH already contains the directory).
156 69 : if (!found.empty()) return "git";
157 69 : return {};
158 69 : }
159 :
160 0 : Result Git::not_implemented() const
161 : {
162 0 : return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED);
163 : }
164 :
165 32 : std::string Git::build_git_command(const std::vector<std::string> &args, const std::string &cwd) const
166 : {
167 32 : const std::string git = find_git_executable();
168 32 : std::ostringstream cmd;
169 64 : cmd << shell_quote(git);
170 57 : if (!cwd.empty()) cmd << " -C " << shell_quote(cwd);
171 126 : for (const auto &arg : args) cmd << ' ' << shell_quote(arg);
172 32 : return cmd.str();
173 32 : }
174 :
175 3 : Result Git::run_git(const std::vector<std::string> &args, const std::string &cwd)
176 : {
177 3 : const std::string git = find_git_executable();
178 3 : if (git.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "git executable not found");
179 :
180 3 : const std::string cmd = build_git_command(args, cwd);
181 6 : debug(1, std::string("[gitcmdline] ") + cmd);
182 3 : cmd_start();
183 3 : const int rc = std::system(cmd.c_str());
184 3 : cmd_end();
185 3 : if (rc != 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, rc, cmd);
186 3 : return ESYSREPO_RESULT(ResultCode::OK);
187 3 : }
188 :
189 10 : void Git::feed_progress_stderr(std::string &pending, const char *data, std::size_t len)
190 : {
191 10 : pending.append(data, len);
192 10 : std::size_t start = 0;
193 2715 : for (std::size_t i = 0; i < pending.size(); ++i)
194 : {
195 2705 : const char c = pending[i];
196 2705 : if (c != '\r' && c != '\n') continue;
197 88 : if (i > start) handle_sideband_progress(pending.substr(start, i - start));
198 44 : start = i + 1;
199 : }
200 10 : if (start > 0) pending.erase(0, start);
201 10 : }
202 :
203 11 : Result Git::run_git_progress(const std::vector<std::string> &args, const std::string &cwd)
204 : {
205 11 : const std::string git = find_git_executable();
206 11 : if (git.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "git executable not found");
207 :
208 : // Merge stderr into the pipe so ``--progress`` lines (CR-updated) are readable.
209 11 : std::string cmd = build_git_command(args, cwd);
210 11 : cmd += " 2>&1";
211 :
212 22 : debug(1, std::string("[gitcmdline] ") + cmd);
213 11 : cmd_start();
214 11 : FILE *pipe = ESYSREPO_POPEN(cmd.c_str(), "r");
215 11 : if (pipe == nullptr)
216 : {
217 0 : cmd_end();
218 0 : return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "failed to start git");
219 : }
220 :
221 11 : std::string pending;
222 11 : char buf[1024];
223 11 : while (true)
224 : {
225 11 : const std::size_t n = std::fread(buf, 1, sizeof(buf), pipe);
226 11 : if (n > 0) feed_progress_stderr(pending, buf, n);
227 11 : if (n < sizeof(buf))
228 : {
229 11 : if (std::feof(pipe)) break;
230 0 : if (std::ferror(pipe)) break;
231 : }
232 : }
233 11 : if (!pending.empty()) handle_sideband_progress(pending);
234 :
235 11 : const int rc = ESYSREPO_PCLOSE(pipe);
236 11 : cmd_end();
237 :
238 11 : git::Progress done;
239 11 : done.set_done(true);
240 11 : done.set_percentage(git::Progress::MAX_PERCENTAGE);
241 11 : handle_transfer_progress(done);
242 :
243 11 : if (rc != 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, rc, cmd);
244 11 : return ESYSREPO_RESULT(ResultCode::OK);
245 22 : }
246 :
247 18 : Result Git::run_git_out(const std::vector<std::string> &args, const std::string &cwd, std::string &output)
248 : {
249 18 : output.clear();
250 18 : const std::string git = find_git_executable();
251 18 : if (git.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "git executable not found");
252 :
253 : // Capture stdout; discard stderr so porcelain/status noise does not pollute parsing.
254 18 : std::string cmd = build_git_command(args, cwd);
255 : #ifdef _WIN32
256 : cmd += " 2>NUL";
257 : #else
258 18 : cmd += " 2>/dev/null";
259 : #endif
260 :
261 36 : debug(1, std::string("[gitcmdline] ") + cmd);
262 18 : cmd_start();
263 18 : FILE *pipe = ESYSREPO_POPEN(cmd.c_str(), "r");
264 18 : if (pipe == nullptr)
265 : {
266 0 : cmd_end();
267 0 : return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "failed to start git");
268 : }
269 : char buf[4096];
270 35 : while (std::fgets(buf, sizeof(buf), pipe) != nullptr) output += buf;
271 18 : const int rc = ESYSREPO_PCLOSE(pipe);
272 18 : cmd_end();
273 18 : if (rc != 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, rc, cmd);
274 18 : return ESYSREPO_RESULT(ResultCode::OK);
275 18 : }
276 :
277 8 : Result Git::open(const std::string &folder)
278 : {
279 8 : if (!is_repo(folder)) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "not a git repository");
280 8 : m_folder = folder;
281 8 : m_open = true;
282 8 : open_time();
283 8 : return ESYSREPO_RESULT(ResultCode::OK);
284 : }
285 :
286 9 : bool Git::is_open()
287 : {
288 9 : return m_open;
289 : }
290 :
291 8 : Result Git::close()
292 : {
293 8 : m_open = false;
294 8 : m_folder.clear();
295 8 : close_time();
296 8 : return ESYSREPO_RESULT(ResultCode::OK);
297 : }
298 :
299 0 : void Git::close_on_error()
300 : {
301 0 : if (m_open) close();
302 0 : }
303 :
304 7 : Result Git::clone(const std::string &url, const std::string &path, const std::string &rev,
305 : const git::CloneOptions &options)
306 : {
307 7 : if (url.empty() || path.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty url or path");
308 :
309 21 : std::vector<std::string> args = {"clone", "--progress"};
310 : // Default CloneOptions: ignore rev (legacy remote-default tip only).
311 7 : if (options.checkout_rev && !rev.empty())
312 : {
313 1 : args.emplace_back("--branch");
314 1 : args.push_back(rev);
315 : }
316 7 : if (options.checkout_rev && options.single_branch) args.emplace_back("--single-branch");
317 7 : args.push_back(url);
318 7 : args.push_back(path);
319 :
320 7 : auto result = run_git_progress(args);
321 7 : if (result.error()) return ESYSREPO_RESULT(result);
322 :
323 7 : return open(path);
324 7 : }
325 :
326 6 : Result Git::fetch(const std::string &remote)
327 : {
328 6 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
329 :
330 12 : std::vector<std::string> args = {"fetch", "--progress"};
331 4 : if (!remote.empty()) args.push_back(remote);
332 4 : return run_git_progress(args, m_folder);
333 4 : }
334 :
335 0 : Result Git::init_bare(const std::string &folder_path)
336 : {
337 0 : if (folder_path.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty path");
338 0 : auto result = run_git({"init", "--bare", folder_path});
339 0 : if (result.error()) return ESYSREPO_RESULT(result);
340 0 : return open(folder_path);
341 0 : }
342 :
343 0 : Result Git::init(const std::string &folder_path)
344 : {
345 0 : if (folder_path.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty path");
346 0 : auto result = run_git({"init", folder_path});
347 0 : if (result.error()) return ESYSREPO_RESULT(result);
348 0 : return open(folder_path);
349 0 : }
350 :
351 0 : Result Git::get_remotes(std::vector<git::Remote> &)
352 : {
353 0 : return not_implemented();
354 : }
355 :
356 0 : Result Git::get_head_branch_remote(git::Branch &, git::Remote &)
357 : {
358 0 : return not_implemented();
359 : }
360 :
361 0 : Result Git::add_remote(const std::string &name, const std::string &url)
362 : {
363 0 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
364 0 : if (name.empty() || url.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty name or url");
365 0 : return run_git({"remote", "add", name, url}, m_folder);
366 : }
367 :
368 3 : Result Git::get_branches(git::Branches &branches, git::BranchType branch_type)
369 : {
370 3 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
371 3 : branches.clear();
372 :
373 3 : if (branch_type == git::BranchType::LOCAL || branch_type == git::BranchType::ALL)
374 : {
375 3 : std::string head_name;
376 15 : auto result = run_git_out({"rev-parse", "--abbrev-ref", "HEAD"}, m_folder, head_name);
377 3 : if (result.error()) return ESYSREPO_RESULT(result);
378 3 : head_name = trim(head_name);
379 3 : if (head_name == "HEAD") head_name.clear(); // detached
380 :
381 3 : std::string out;
382 9 : result = run_git_out({"show-ref", "--heads"}, m_folder, out);
383 : // Empty repo / no heads: not an error for listing.
384 3 : if (result.ok())
385 : {
386 6 : for (const auto &line : split_lines(out))
387 : {
388 : // <hash> <sp> refs/heads/<name>
389 3 : const auto sp = line.find(' ');
390 3 : if (sp == std::string::npos) continue;
391 3 : const std::string ref = trim(line.substr(sp + 1));
392 3 : const std::string heads = "refs/heads/";
393 3 : if (ref.compare(0, heads.size(), heads) != 0) continue;
394 3 : const std::string name = ref.substr(heads.size());
395 :
396 3 : auto branch = std::make_shared<git::Branch>();
397 3 : branch->set_ref_name(ref);
398 3 : branch->set_name(name);
399 3 : branch->set_type(git::BranchType::LOCAL);
400 3 : branch->set_is_head(!head_name.empty() && name == head_name);
401 :
402 3 : std::string remote;
403 3 : std::string merge;
404 3 : std::string cfg_out;
405 12 : if (run_git_out({"config", "--get", "branch." + name + ".remote"}, m_folder, cfg_out).ok())
406 3 : remote = trim(cfg_out);
407 12 : if (run_git_out({"config", "--get", "branch." + name + ".merge"}, m_folder, cfg_out).ok())
408 3 : merge = trim(cfg_out);
409 3 : if (!remote.empty() && !merge.empty())
410 : {
411 3 : branch->set_remote_name(remote);
412 : // Prefer refs/remotes/<remote>/<branch> for merge_analysis tips.
413 3 : const std::string merge_heads = "refs/heads/";
414 3 : std::string short_merge = merge;
415 3 : if (merge.compare(0, merge_heads.size(), merge_heads) == 0)
416 3 : short_merge = merge.substr(merge_heads.size());
417 6 : branch->set_remote_branch("refs/remotes/" + remote + "/" + short_merge);
418 3 : }
419 9 : branches.add(branch);
420 9 : }
421 : }
422 3 : }
423 :
424 3 : if (branch_type == git::BranchType::REMOTE || branch_type == git::BranchType::ALL)
425 : {
426 0 : std::string out;
427 0 : auto result = run_git_out({"show-ref"}, m_folder, out);
428 0 : if (result.ok())
429 : {
430 0 : for (const auto &line : split_lines(out))
431 : {
432 0 : const auto sp = line.find(' ');
433 0 : if (sp == std::string::npos) continue;
434 0 : const std::string ref = trim(line.substr(sp + 1));
435 0 : const std::string remotes = "refs/remotes/";
436 0 : if (ref.compare(0, remotes.size(), remotes) != 0) continue;
437 0 : if (ref.size() >= 5 && ref.compare(ref.size() - 5, 5, "/HEAD") == 0) continue;
438 :
439 0 : auto branch = std::make_shared<git::Branch>();
440 0 : branch->set_ref_name(ref);
441 0 : branch->set_name(ref.substr(remotes.size()));
442 0 : branch->set_type(git::BranchType::REMOTE);
443 0 : branches.add(branch);
444 0 : }
445 : }
446 0 : }
447 :
448 3 : return ESYSREPO_RESULT(ResultCode::OK);
449 : }
450 :
451 1 : Result Git::checkout(const std::string &branch, bool force)
452 : {
453 1 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
454 1 : if (branch.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty branch");
455 :
456 2 : std::vector<std::string> args = {"checkout"};
457 1 : if (force) args.emplace_back("-f");
458 2 : args.push_back(strip_rev_prefix(branch));
459 1 : return run_git(args, m_folder);
460 1 : }
461 :
462 0 : Result Git::reset(const git::CommitHash &, git::ResetType)
463 : {
464 0 : return not_implemented();
465 : }
466 :
467 1 : Result Git::fastforward(const git::CommitHash &commit)
468 : {
469 1 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
470 1 : if (commit.get_hash().empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty commit");
471 : // Matches libgit2 path: move HEAD + worktree to the tip (FF only).
472 4 : return run_git({"merge", "--ff-only", commit.get_hash()}, m_folder);
473 : }
474 :
475 0 : Result Git::get_last_commit(git::CommitHash &)
476 : {
477 0 : return not_implemented();
478 : }
479 :
480 0 : Result Git::get_last_commit(git::Commit &, bool)
481 : {
482 0 : return not_implemented();
483 : }
484 :
485 0 : Result Git::get_parent_commit(const git::CommitHash &, git::CommitHash &, int)
486 : {
487 0 : return not_implemented();
488 : }
489 :
490 1 : Result Git::is_dirty(bool &dirty)
491 : {
492 1 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
493 1 : dirty = false;
494 1 : std::string out;
495 4 : auto result = run_git_out({"status", "--porcelain"}, m_folder, out);
496 1 : if (result.error()) return ESYSREPO_RESULT(result);
497 1 : dirty = !trim(out).empty();
498 1 : return ESYSREPO_RESULT(ResultCode::OK);
499 2 : }
500 :
501 1 : Result Git::is_detached(bool &detached)
502 : {
503 1 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
504 1 : detached = false;
505 : // symbolic-ref fails (nonzero) when HEAD is detached.
506 5 : auto result = run_git({"symbolic-ref", "-q", "HEAD"}, m_folder);
507 1 : detached = result.error();
508 1 : return ESYSREPO_RESULT(ResultCode::OK);
509 1 : }
510 :
511 0 : Result Git::get_status(git::RepoStatus &)
512 : {
513 0 : return not_implemented();
514 : }
515 :
516 0 : Result_t<bool> Git::is_ssh_agent_running(bool log_once)
517 : {
518 0 : detect_ssh_agent(log_once);
519 0 : return ESYSREPO_RESULT_T(ResultCode::OK, s_ssh_agent_running);
520 : }
521 :
522 0 : void Git::detect_ssh_agent(bool /*log_once*/)
523 : {
524 0 : if (s_detect_ssh_agent_done) return;
525 0 : s_detect_ssh_agent_done = true;
526 : #ifdef _WIN32
527 : // Pageant / Windows OpenSSH agent: presence of SSH_AUTH_SOCK or ssh-agent service is enough for CLI git.
528 : const char *sock = std::getenv("SSH_AUTH_SOCK");
529 : s_ssh_agent_running = (sock != nullptr && sock[0] != '\0') || (std::getenv("SSH_AGENT_PID") != nullptr);
530 : #else
531 0 : const char *sock = std::getenv("SSH_AUTH_SOCK");
532 0 : s_ssh_agent_running = (sock != nullptr && sock[0] != '\0');
533 : #endif
534 : }
535 :
536 2 : Result Git::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
537 : std::vector<git::CommitHash> &commits)
538 : {
539 2 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
540 2 : merge_analysis_result = git::MergeAnalysisResult::NOT_SET;
541 2 : commits.clear();
542 2 : if (refs.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty refs");
543 :
544 2 : std::string head;
545 8 : auto result = run_git_out({"rev-parse", "HEAD"}, m_folder, head);
546 2 : if (result.error()) return ESYSREPO_RESULT(result);
547 2 : head = trim(head);
548 :
549 : // Sync only uses the first upstream ref today; analyze that tip.
550 2 : const std::string &ref = refs.front();
551 2 : std::string target;
552 6 : result = run_git_out({"rev-parse", ref}, m_folder, target);
553 2 : if (result.error()) return ESYSREPO_RESULT(result);
554 2 : target = trim(target);
555 :
556 2 : git::CommitHash target_commit;
557 2 : target_commit.set_hash(target);
558 2 : commits.push_back(target_commit);
559 :
560 2 : if (head == target)
561 : {
562 1 : merge_analysis_result = git::MergeAnalysisResult::UP_TO_DATE;
563 1 : return ESYSREPO_RESULT(ResultCode::OK);
564 : }
565 :
566 1 : std::string base;
567 4 : result = run_git_out({"merge-base", "HEAD", ref}, m_folder, base);
568 1 : if (result.error()) return ESYSREPO_RESULT(result);
569 1 : base = trim(base);
570 :
571 1 : if (base == head)
572 1 : merge_analysis_result = git::MergeAnalysisResult::FASTFORWARD;
573 0 : else if (base == target)
574 0 : merge_analysis_result = git::MergeAnalysisResult::UP_TO_DATE;
575 : else
576 0 : merge_analysis_result = git::MergeAnalysisResult::NORMAL;
577 :
578 1 : return ESYSREPO_RESULT(ResultCode::OK);
579 6 : }
580 :
581 0 : Result Git::fetch_all_notes(const std::string &)
582 : {
583 0 : return not_implemented();
584 : }
585 :
586 1 : Result_t<bool> Git::has_branch(const std::string &name, git::BranchType branch_type)
587 : {
588 1 : if (!m_open) return ESYSREPO_RESULT_T(ResultCode::GIT_GENERIC_ERROR, false);
589 1 : git::Branches branches;
590 1 : auto result = get_branches(branches, branch_type);
591 1 : if (result.error()) return ESYSREPO_RESULT_T(result, false);
592 1 : const std::string tip = strip_rev_prefix(name);
593 1 : for (const auto &b : branches.get())
594 : {
595 1 : if (b->get_name() == tip || b->get_name() == name || b->get_ref_name() == name)
596 1 : return ESYSREPO_RESULT_T(ResultCode::OK, true);
597 : }
598 0 : return ESYSREPO_RESULT_T(ResultCode::OK, false);
599 1 : }
600 :
601 0 : Result Git::get_hash(const std::string &revision, std::string &hash, git::BranchType /*branch_type*/)
602 : {
603 0 : if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
604 0 : hash.clear();
605 0 : std::string out;
606 0 : auto result = run_git_out({"rev-parse", revision}, m_folder, out);
607 0 : if (result.error()) return ESYSREPO_RESULT(result);
608 0 : hash = trim(out);
609 0 : return ESYSREPO_RESULT(ResultCode::OK);
610 0 : }
611 :
612 0 : Result Git::walk_commits(std::shared_ptr<git::WalkCommit>)
613 : {
614 0 : return not_implemented();
615 : }
616 :
617 0 : Result Git::diff(const git::CommitHash &, std::shared_ptr<git::Diff>)
618 : {
619 0 : return not_implemented();
620 : }
621 :
622 0 : Result Git::get_ahead_behind(git::AheadBehind &, const git::Branch &)
623 : {
624 0 : return not_implemented();
625 : }
626 :
627 0 : Result Git::get_ahead_behind(git::AheadBehind &, const std::string &, const std::string &)
628 : {
629 0 : return not_implemented();
630 : }
631 :
632 0 : Result Git::add_note(git::NoteId &, const std::string &, const std::string &, bool)
633 : {
634 0 : return not_implemented();
635 : }
636 :
637 0 : Result Git::remove_note(const git::NoteId &)
638 : {
639 0 : return not_implemented();
640 : }
641 :
642 0 : Result Git::remove_note(const git::CommitHash &, const std::string &)
643 : {
644 0 : return not_implemented();
645 : }
646 :
647 0 : Result Git::remove_note_last_commit(const std::string &)
648 : {
649 0 : return not_implemented();
650 : }
651 :
652 0 : Result Git::push(const std::string &, const std::string &, const std::string &)
653 : {
654 0 : return not_implemented();
655 : }
656 :
657 0 : Result Git::push_notes(const std::string &, const std::string &)
658 : {
659 0 : return not_implemented();
660 : }
661 :
662 0 : const std::string &Git::get_version()
663 : {
664 0 : return s_get_version();
665 : }
666 :
667 0 : const std::string &Git::get_lib_name()
668 : {
669 0 : return s_get_lib_name();
670 : }
671 :
672 0 : const std::string &Git::s_get_version()
673 : {
674 0 : static const std::string version = "git-cmdline";
675 0 : return version;
676 : }
677 :
678 0 : const std::string &Git::s_get_lib_name()
679 : {
680 0 : static const std::string name = "gitcmdline";
681 0 : return name;
682 : }
683 :
684 0 : bool Git::do_is_ssh_backend_supported(git::SshBackend backend) const
685 : {
686 0 : return backend == git::SshBackend::Exec;
687 : }
688 :
689 0 : bool Git::do_is_ssh_backend_available(git::SshBackend backend, bool /*force*/)
690 : {
691 0 : if (backend != git::SshBackend::Exec) return false;
692 0 : return !find_git_executable().empty();
693 : }
694 :
695 0 : Result Git::do_set_ssh_backend(git::SshBackend backend)
696 : {
697 0 : if (backend != git::SshBackend::Exec)
698 0 : return ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED);
699 0 : m_ssh_backend = backend;
700 0 : return ESYSREPO_RESULT(ResultCode::OK);
701 : }
702 :
703 0 : Result_t<git::SshBackend> Git::do_get_ssh_backend() const
704 : {
705 0 : return ESYSREPO_RESULT_T(ResultCode::OK, m_ssh_backend);
706 : }
707 :
708 : } // namespace esys::repo::gitcmdline
|