diff --git a/plugins/vcs/Algorithm.h b/plugins/vcs/Algorithm.h index 9693701df6..1cebccca2f 100644 --- a/plugins/vcs/Algorithm.h +++ b/plugins/vcs/Algorithm.h @@ -2,6 +2,7 @@ #include "imap.h" #include "i18n.h" +#include "itextstream.h" #include "Repository.h" #include "GitException.h" #include "Diff.h" @@ -106,18 +107,35 @@ inline RemoteStatus analyseRemoteStatus(const std::shared_ptr& repos RequiredMergeStrategy::MergeMap }; } -inline void mergeRemoteChanges(const std::shared_ptr& repository) +inline void syncWithRemote(const std::shared_ptr& repository) { + if (GlobalMapModule().isModified()) + { + throw git::GitException(_("The map file has unsaved changes, please save before merging.")); + } + auto mapPath = repository->getRepositoryRelativePath(GlobalMapModule().getMapName()); - auto mapFileHasUncommittedChanges = !mapPath.empty() && repository->fileHasUncommittedChanges(mapPath); + /*auto mapFileHasUncommittedChanges = !mapPath.empty() && repository->fileHasUncommittedChanges(mapPath); if (mapFileHasUncommittedChanges) { throw git::GitException(_("The map file has uncommitted changes, cannot merge yet.")); - } + }*/ RemoteStatus status = analyseRemoteStatus(repository); + if (status.strategy == RequiredMergeStrategy::NoMergeRequired) + { + rMessage() << "No merge required." << std::endl; + return; + } + + if (status.strategy == RequiredMergeStrategy::JustPush) + { + repository->pushToTrackedRemote(); + return; + } + if (!repository->isReadyForMerge()) { throw git::GitException(_("Repository is not ready for a merge at this point")); diff --git a/plugins/vcs/GitModule.cpp b/plugins/vcs/GitModule.cpp index f45cb636a0..dccc5e505c 100644 --- a/plugins/vcs/GitModule.cpp +++ b/plugins/vcs/GitModule.cpp @@ -11,6 +11,7 @@ #include "Repository.h" #include "Remote.h" #include "ui/VcsStatus.h" +#include "command/ExecutionFailure.h" namespace vcs { @@ -85,7 +86,14 @@ void GitModule::fetch(const cmd::ArgumentList& args) return; } - _repository->fetchFromTrackedRemote(); + try + { + _repository->fetchFromTrackedRemote(); + } + catch (const git::GitException& ex) + { + throw cmd::ExecutionFailure(ex.what()); + } } void GitModule::createPreferencePage() diff --git a/plugins/vcs/Remote.h b/plugins/vcs/Remote.h index c95484f314..7ada2ebac1 100644 --- a/plugins/vcs/Remote.h +++ b/plugins/vcs/Remote.h @@ -3,6 +3,7 @@ #include #include "Repository.h" #include "CredentialManager.h" +#include "GitException.h" #include #include @@ -33,8 +34,7 @@ class Remote final { if (!_remote) { - rError() << "Not a valid remote" << std::endl; - return; + throw GitException("Not a valid remote"); } auto url = wxURI(git_remote_url(_remote)); @@ -42,23 +42,10 @@ class Remote final git_fetch_options options; git_fetch_options_init(&options, GIT_FETCH_OPTIONS_VERSION); - // Create the git:scheme://server string to query the credential manager - auto credentialResource = fmt::format("git:{0}://{1}", url.GetScheme().ToStdString(), url.GetServer().ToStdString()); - - auto userAndPass = CredentialManager::RetrievePassword(credentialResource); + auto credentials = getCredentialsForRemote(url); - if (!userAndPass.first.empty() && !userAndPass.second.empty()) + if (credentials != nullptr) { - rMessage() << "Found credentials for resource " << credentialResource << " in the credential store" << std::endl; - - git_credential* credentials = nullptr; - - if (git_credential_userpass_plaintext_new(&credentials, userAndPass.first.c_str(), userAndPass.second.c_str()) < 0) - { - rError() << "Unable to create credentials" << std::endl; - return; - } - options.callbacks.credentials = AcquireCredentials; options.callbacks.payload = credentials; } @@ -67,26 +54,47 @@ class Remote final rMessage() << "Fetching from remote " << remoteName << std::endl; - if (git_remote_fetch(_remote, nullptr, &options, "fetch") < 0) - { - const auto* error = git_error_last(); + auto error = git_remote_fetch(_remote, nullptr, &options, "fetch"); + GitException::ThrowOnError(error); - rError() << "Fetch error " << error->message << std::endl; - return; + rMessage() << "Fetch complete" << std::endl; + } + + void push(const Reference& ref) + { + git_push_options options = GIT_PUSH_OPTIONS_INIT; + auto refName = ref.getName(); + char* refNamePtr = refName.data(); + + const git_strarray refspecs = { + &refNamePtr, + 1 + }; + + auto url = wxURI(git_remote_url(_remote)); + auto credentials = getCredentialsForRemote(url); + + if (credentials != nullptr) + { + options.callbacks.credentials = AcquireCredentials; + options.callbacks.payload = credentials; } - rMessage() << "Fetch complete" << std::endl; + auto remoteName = git_remote_name(_remote); + rMessage() << "Pushing to remote " << remoteName << std::endl; + + auto error = git_remote_push(_remote, &refspecs, &options); + GitException::ThrowOnError(error); + + rMessage() << "Push complete" << std::endl; } static Ptr CreateFromName(Repository& repository, const std::string& name) { git_remote* remote; + auto error = git_remote_lookup(&remote, repository._get(), name.c_str()); - if (git_remote_lookup(&remote, repository._get(), name.c_str()) < 0) - { - rWarning() << "Failed to look up the remote " << name << std::endl; - return Ptr(); - } + GitException::ThrowOnError(error); return std::make_shared(remote); } @@ -97,6 +105,28 @@ class Remote final *out = reinterpret_cast(payload); return 0; } + + git_credential* getCredentialsForRemote(const wxURI& url) + { + // Create the git:scheme://server string to query the credential manager + auto credentialResource = fmt::format("git:{0}://{1}", url.GetScheme().ToStdString(), url.GetServer().ToStdString()); + + auto userAndPass = CredentialManager::RetrievePassword(credentialResource); + + if (!userAndPass.first.empty() && !userAndPass.second.empty()) + { + rMessage() << "Found credentials for resource " << credentialResource << " in the credential store" << std::endl; + + git_credential* credentials = nullptr; + + auto error = git_credential_userpass_plaintext_new(&credentials, userAndPass.first.c_str(), userAndPass.second.c_str()); + GitException::ThrowOnError(error); + + return credentials; + } + + return nullptr; + } }; } diff --git a/plugins/vcs/Repository.cpp b/plugins/vcs/Repository.cpp index 6e7fa65849..d8a5a5c5d5 100644 --- a/plugins/vcs/Repository.cpp +++ b/plugins/vcs/Repository.cpp @@ -107,14 +107,13 @@ std::string Repository::getUpstreamRemoteName(const Reference& reference) return upstreamRemote; } -void Repository::fetchFromTrackedRemote() +Remote::Ptr Repository::getTrackedRemote() { auto head = getHead(); if (!head) { - rWarning() << "Could not retrieve HEAD reference from repository" << std::endl; - return; + throw GitException("Could not retrieve HEAD reference from repository"); } auto trackedBranch = head->getUpstream(); @@ -123,8 +122,7 @@ void Repository::fetchFromTrackedRemote() if (!trackedBranch) { - rWarning() << "No tracked remote branch configured, cannot fetch" << std::endl; - return; + throw GitException("No tracked remote branch configured"); } auto remoteName = getUpstreamRemoteName(*head); @@ -134,13 +132,24 @@ void Repository::fetchFromTrackedRemote() if (!remote) { - rWarning() << "Cannot fetch from remote 'origin'" << std::endl; - return; + throw GitException("Failed to get the named remote: " + remoteName); } + return remote; +} + +void Repository::fetchFromTrackedRemote() +{ + auto remote = getTrackedRemote(); remote->fetch(); } +void Repository::pushToTrackedRemote() +{ + auto remote = getTrackedRemote(); + remote->push(*getHead()); // getHead will succeed because getTrackedRemote did +} + RefSyncStatus Repository::getSyncStatusOfBranch(const Reference& reference) { RefSyncStatus status; diff --git a/plugins/vcs/Repository.h b/plugins/vcs/Repository.h index 4e66612aed..3b2287ab72 100644 --- a/plugins/vcs/Repository.h +++ b/plugins/vcs/Repository.h @@ -51,6 +51,9 @@ class Repository final // Performs a fetch from the remote the current branch is tracking void fetchFromTrackedRemote(); + // Pushes the current head to its tracked remote branch + void pushToTrackedRemote(); + bool isUpToDateWithRemote(); bool fileIsIndexed(const std::string& relativePath); bool fileHasUncommittedChanges(const std::string& relativePath); @@ -74,6 +77,8 @@ class Repository final git_repository* _get(); private: + std::shared_ptr getTrackedRemote(); + unsigned int getFileStatus(const std::string& relativePath); }; diff --git a/plugins/vcs/ui/VcsStatus.cpp b/plugins/vcs/ui/VcsStatus.cpp index 3794093b3e..64d0e999ae 100644 --- a/plugins/vcs/ui/VcsStatus.cpp +++ b/plugins/vcs/ui/VcsStatus.cpp @@ -91,8 +91,8 @@ void VcsStatus::createPopupMenu() )); _popupMenu->addItem(std::make_shared( - new wxMenuItem(nullptr, wxID_ANY, _("Integrate Changes from Server"), ""), - [this]() { if (_repository) { mergeRemoteChanges(_repository); } }, + new wxMenuItem(nullptr, wxID_ANY, _("Sync Changes with Server"), ""), + [this]() { performSync(_repository); }, [this]() { return !_fetchInProgress; } )); } @@ -228,6 +228,19 @@ void VcsStatus::performFetch(std::shared_ptr repository) _fetchInProgress = false; } +void VcsStatus::performSync(std::shared_ptr repository) +{ + try + { + repository->pushToTrackedRemote(); + setRemoteStatus(git::analyseRemoteStatus(repository)); + } + catch (git::GitException& ex) + { + setRemoteStatus(git::RemoteStatus{ 0, 0, ex.what() }); + } +} + void VcsStatus::setMapFileStatus(const std::string& status) { GlobalUserInterface().dispatch([this, status]() { _mapStatus->SetLabel(status); }); diff --git a/plugins/vcs/ui/VcsStatus.h b/plugins/vcs/ui/VcsStatus.h index 40f26a6095..be505aa50a 100644 --- a/plugins/vcs/ui/VcsStatus.h +++ b/plugins/vcs/ui/VcsStatus.h @@ -57,6 +57,7 @@ class VcsStatus final : void onIntervalReached(wxTimerEvent& ev); void onIdle(wxIdleEvent& ev); void performFetch(std::shared_ptr repository); + void performSync(std::shared_ptr repository); void performMapFileStatusCheck(std::shared_ptr repository); void updateMapFileStatus(); void onMapEvent(IMap::MapEvent ev);