feat(git): integrate Git Credential Manager

This commit is contained in:
2026-06-18 20:09:58 -05:00
parent 17028fafdd
commit 90937265dd
3 changed files with 58 additions and 0 deletions

View File

@@ -434,6 +434,57 @@ bool GitManager::captureGit(RepositoryView& repository, const std::vector<std::s
return true; return true;
} }
bool GitManager::prepareCredentials(RepositoryView& repository, std::string& error) {
if (repository.credentials_checked) return true;
bool uses_https = false;
for (const std::string& remote_name : repository.remotes) {
git_remote* remote = nullptr;
if (git_remote_lookup(&remote, repository.repo, remote_name.c_str()) == 0) {
const char* url = git_remote_url(remote);
const std::string remote_url = url ? url : "";
uses_https = uses_https || remote_url.starts_with("https://") || remote_url.starts_with("http://");
git_remote_free(remote);
}
}
if (!uses_https) {
repository.credentials_checked = true;
return true;
}
std::string helper;
std::string helper_error;
const bool has_helper = captureGit(repository, {"config", "--get-all", "credential.helper"},
helper, helper_error) && !helper.empty();
std::string gcm_version;
std::string gcm_error;
const bool gcm_available = captureGit(repository, {"credential-manager", "--version"},
gcm_version, gcm_error);
if (has_helper) {
if (helper.find("manager") != std::string::npos && !gcm_available) {
error = "Git Credential Manager is configured but unavailable. Reinstall Git for Windows with GCM enabled.";
return false;
}
repository.credentials_checked = true;
return true;
}
if (!gcm_available) {
error = "This HTTPS remote needs authentication, but no credential helper is configured. "
"Install Git Credential Manager or configure credential.helper in Git.";
return false;
}
std::string configure_output;
if (!captureGit(repository, {"config", "--local", "credential.helper", "manager"},
configure_output, error))
return false;
repository.credentials_checked = true;
return true;
}
bool GitManager::applyPatch(RepositoryView& repository, const std::string& patch, bool cached, bool GitManager::applyPatch(RepositoryView& repository, const std::string& patch, bool cached,
bool reverse, std::string& error) { bool reverse, std::string& error) {
const std::filesystem::path patch_path = std::filesystem::temp_directory_path() / const std::filesystem::path patch_path = std::filesystem::temp_directory_path() /
@@ -455,6 +506,7 @@ bool GitManager::applyPatch(RepositoryView& repository, const std::string& patch
} }
bool GitManager::fetch(RepositoryView& repository, const std::string& remote, std::string& error) { bool GitManager::fetch(RepositoryView& repository, const std::string& remote, std::string& error) {
if (!prepareCredentials(repository, error)) return false;
const std::vector<std::string> args = remote.empty() const std::vector<std::string> args = remote.empty()
? std::vector<std::string>{"fetch", "--all", "--prune"} ? std::vector<std::string>{"fetch", "--all", "--prune"}
: std::vector<std::string>{"fetch", remote, "--prune"}; : std::vector<std::string>{"fetch", remote, "--prune"};
@@ -462,6 +514,7 @@ bool GitManager::fetch(RepositoryView& repository, const std::string& remote, st
} }
bool GitManager::pull(RepositoryView& repository, int mode, std::string& error) { bool GitManager::pull(RepositoryView& repository, int mode, std::string& error) {
if (!prepareCredentials(repository, error)) return false;
if (mode == 0) return fetch(repository, {}, error); if (mode == 0) return fetch(repository, {}, error);
std::vector<std::string> args{"pull"}; std::vector<std::string> args{"pull"};
if (mode == 1) args.push_back("--ff"); if (mode == 1) args.push_back("--ff");
@@ -471,6 +524,7 @@ bool GitManager::pull(RepositoryView& repository, int mode, std::string& error)
} }
bool GitManager::push(RepositoryView& repository, std::string& error) { bool GitManager::push(RepositoryView& repository, std::string& error) {
if (!prepareCredentials(repository, error)) return false;
if (runGit(repository, {"push"}, "Push complete", error)) return true; if (runGit(repository, {"push"}, "Push complete", error)) return true;
if (repository.remotes.empty()) return false; if (repository.remotes.empty()) return false;
const std::string remote = std::find(repository.remotes.begin(), repository.remotes.end(), "origin") != const std::string remote = std::find(repository.remotes.begin(), repository.remotes.end(), "origin") !=
@@ -546,6 +600,7 @@ bool GitManager::createTag(RepositoryView& repository, const std::string& name,
bool GitManager::addRemote(RepositoryView& repository, const std::string& name, bool GitManager::addRemote(RepositoryView& repository, const std::string& name,
const std::string& url, std::string& error) { const std::string& url, std::string& error) {
repository.credentials_checked = false;
return runGit(repository, {"remote", "add", name, url}, "Remote added", error); return runGit(repository, {"remote", "add", name, url}, "Remote added", error);
} }

View File

@@ -54,6 +54,7 @@ private:
void computeGraphLanes(RepositoryView& repository); void computeGraphLanes(RepositoryView& repository);
bool loadRepositoryData(RepositoryView& repository, std::string& error); bool loadRepositoryData(RepositoryView& repository, std::string& error);
void loadWorkingTree(RepositoryView& repository); void loadWorkingTree(RepositoryView& repository);
bool prepareCredentials(RepositoryView& repository, std::string& error);
bool runGit(RepositoryView& repository, const std::vector<std::string>& arguments, bool runGit(RepositoryView& repository, const std::vector<std::string>& arguments,
const char* success_message, std::string& message, bool reload = true); const char* success_message, std::string& message, bool reload = true);
}; };

View File

@@ -47,6 +47,7 @@ struct RepositoryView {
git_repository* repo = nullptr; git_repository* repo = nullptr;
git_revwalk* commit_walk = nullptr; git_revwalk* commit_walk = nullptr;
bool history_exhausted = false; bool history_exhausted = false;
bool credentials_checked = false;
std::string path; std::string path;
std::string name = "New Tab"; std::string name = "New Tab";
std::string branch = "detached"; std::string branch = "detached";
@@ -70,5 +71,6 @@ struct RepositoryView {
commit_walk = nullptr; commit_walk = nullptr;
if (repo) git_repository_free(repo); if (repo) git_repository_free(repo);
repo = nullptr; repo = nullptr;
credentials_checked = false;
} }
}; };