diff --git a/plugins/vcs/Algorithm.h b/plugins/vcs/Algorithm.h index 8c40953726..2e8ea076a5 100644 --- a/plugins/vcs/Algorithm.h +++ b/plugins/vcs/Algorithm.h @@ -11,6 +11,7 @@ #include "VersionControlLib.h" #include "command/ExecutionFailure.h" #include "wxutil/dialog/MessageBox.h" +#include "ui/CommitDialog.h" #include namespace vcs @@ -122,6 +123,52 @@ inline RemoteStatus analyseRemoteStatus(const std::shared_ptr& repos RequiredMergeStrategy::MergeMap }; } +inline void tryToFinishMerge(const std::shared_ptr& repository) +{ + auto mapPath = repository->getRepositoryRelativePath(GlobalMapModule().getMapName()); + + auto index = repository->getIndex(); + + if (index->hasConflicts()) + { + // Remove the conflict state from the map + if (!mapPath.empty() && index->fileIsConflicted(mapPath)) + { + index->resolveByUsingOurs(mapPath); + index->write(); + } + + // If the index still has conflicts, notify the user + if (index->hasConflicts()) + { + wxutil::Messagebox::Show(_("Conflicts"), + _("There are still unresolved conflicts in the repository.\nPlease use your Git client to resolve them and try again."), + ::ui::IDialog::MessageType::MESSAGE_CONFIRM); + return; + } + } + + auto head = repository->getHead(); + if (!head) throw git::GitException("Cannot resolve repository HEAD"); + + auto upstream = head->getUpstream(); + if (!upstream) throw git::GitException("Cannot resolve upstream ref from HEAD"); + + // We need the commit metadata to be valid + git::CommitMetadata metadata; + + metadata.name = repository->getConfigValue("user.name"); + metadata.email = repository->getConfigValue("user.email"); + + metadata = ui::CommitDialog::RunDialog(metadata); + + if (metadata.isValid()) + { + repository->createCommit(metadata, upstream); + repository->cleanupState(); + } +} + inline void syncWithRemote(const std::shared_ptr& repository) { if (repository->mergeIsInProgress()) @@ -206,7 +253,7 @@ inline void syncWithRemote(const std::shared_ptr& repository) error = git_merge(repository->_get(), mergeHeads.data(), mergeHeads.size(), &mergeOptions, &checkoutOptions); GitException::ThrowOnError(error); - // At this point, check if the loaded map is affected by the merge + // Check if the loaded map is affected by the merge if (status.strategy == RequiredMergeStrategy::MergeMap) { auto mergeBase = repository->findMergeBase(*repository->getHead(), *upstream); @@ -224,22 +271,11 @@ inline void syncWithRemote(const std::shared_ptr& repository) throw GitException(ex.what()); } - // TODO: save this state and continue to commit and cleanup later + // Wait until the user finishes or aborts the merge return; } - auto index = repository->getIndex(); - - if (index->hasConflicts()) - { - // Handle conflicts -notify user? - } - else - { - //create_merge_commit(repository->_get(), index, &mergeOptions); - } - - git_repository_state_cleanup(repository->_get()); + tryToFinishMerge(repository); } catch (const GitException& ex) { diff --git a/plugins/vcs/Index.cpp b/plugins/vcs/Index.cpp index d9f345107c..376c262330 100644 --- a/plugins/vcs/Index.cpp +++ b/plugins/vcs/Index.cpp @@ -66,6 +66,22 @@ void Index::write() GitException::ThrowOnError(error); } +void Index::resolveByUsingOurs(const std::string& relativePath) +{ + auto error = git_index_add_bypath(_index, relativePath.c_str()); + GitException::ThrowOnError(error); +} + +bool Index::fileIsConflicted(const std::string& relativePath) +{ + const git_index_entry* ancestor = nullptr; + const git_index_entry* ours = nullptr; + const git_index_entry* theirs = nullptr; + + auto error = git_index_conflict_get(&ancestor, &ours, &theirs, _index, relativePath.c_str()); + return error == 0; +} + bool Index::hasConflicts() { return git_index_has_conflicts(_index); diff --git a/plugins/vcs/Index.h b/plugins/vcs/Index.h index 4aadbc9691..7ee738ba19 100644 --- a/plugins/vcs/Index.h +++ b/plugins/vcs/Index.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace vcs @@ -37,6 +38,10 @@ class Index final std::shared_ptr writeTree(Repository& repository); bool hasConflicts(); + + bool fileIsConflicted(const std::string& relativePath); + + void resolveByUsingOurs(const std::string& relativePath); }; } diff --git a/plugins/vcs/Repository.cpp b/plugins/vcs/Repository.cpp index b2432f2881..4f0ddf176e 100644 --- a/plugins/vcs/Repository.cpp +++ b/plugins/vcs/Repository.cpp @@ -306,6 +306,11 @@ std::shared_ptr Repository::getTreeByRevision(const std::string& revision) } void Repository::createCommit(const CommitMetadata& metadata) +{ + createCommit(metadata, Reference::Ptr()); +} + +void Repository::createCommit(const CommitMetadata& metadata, const Reference::Ptr& additionalParent) { auto head = getHead(); auto index = getIndex(); @@ -322,24 +327,38 @@ void Repository::createCommit(const CommitMetadata& metadata) auto tree = index->writeTree(*this); git_oid headOid; - git_reference_name_to_id(&headOid, _repository, head->getName().c_str()); + error = git_reference_name_to_id(&headOid, _repository, head->getName().c_str()); + GitException::ThrowOnError(error); + auto parentCommit = Commit::LookupFromOid(_repository, &headOid); std::vector parentCommits; parentCommits.push_back(parentCommit->_get()); + // Check if we have an additional parent + if (additionalParent) + { + git_oid parentOid; + auto error = git_reference_name_to_id(&parentOid, _repository, additionalParent->getName().c_str()); + GitException::ThrowOnError(error); + + auto additionalParentCommit = Commit::LookupFromOid(_repository, &parentOid); + + parentCommits.push_back(additionalParentCommit->_get()); + } + git_oid commitOid; error = git_commit_create(&commitOid, _repository, head->getName().c_str(), signature, signature, nullptr, metadata.message.c_str(), - tree->_get(), + tree->_get(), parentCommits.size(), parentCommits.data()); GitException::ThrowOnError(error); index->write(); - rMessage() << "Commit created with " << Reference::OidToString(&commitOid) << std::endl; + rMessage() << "Commit created: " << Reference::OidToString(&commitOid) << std::endl; } std::string Repository::getConfigValue(const std::string& key) @@ -368,6 +387,12 @@ std::string Repository::getConfigValue(const std::string& key) } } +void Repository::cleanupState() +{ + auto error = git_repository_state_cleanup(_repository); + GitException::ThrowOnError(error); +} + bool Repository::isReadyForMerge() { auto state = git_repository_state(_repository); diff --git a/plugins/vcs/Repository.h b/plugins/vcs/Repository.h index 10478e0996..c6d15fe36e 100644 --- a/plugins/vcs/Repository.h +++ b/plugins/vcs/Repository.h @@ -81,10 +81,17 @@ class Repository final std::shared_ptr getTreeByRevision(const std::string& revision); + // Create a commit with the current repository HEAD as only parent void createCommit(const CommitMetadata& metadata); + // Create a commit with the currrent repository HEAD and the given ref as parent + // It's valid to pass an empty additionalParent if no second parent is needed + void createCommit(const CommitMetadata& metadata, const Reference::Ptr& additionalParent); + std::string getConfigValue(const std::string& key); + void cleanupState(); + // Creates a new instance of this repository, not sharing any libgit2 handles with the original std::shared_ptr clone(); diff --git a/plugins/vcs/ui/VcsStatus.cpp b/plugins/vcs/ui/VcsStatus.cpp index 29fc723a92..dfbcd4066a 100644 --- a/plugins/vcs/ui/VcsStatus.cpp +++ b/plugins/vcs/ui/VcsStatus.cpp @@ -162,6 +162,54 @@ void VcsStatus::onMapEvent(IMap::MapEvent ev) analyseRemoteStatus(_repository); } } + + // Only when saving: check whether a merge is to be completed + if (ev == IMap::MapSaved && _repository && _repository->mergeIsInProgress()) + { + auto mapPath = _repository->getRepositoryRelativePath(GlobalMapModule().getMapName()); + auto index = _repository->getIndex(); + + // Remove the conflict state of the map file after save + if (!mapPath.empty() && index->fileIsConflicted(mapPath)) + { + index->resolveByUsingOurs(mapPath); + index->write(); + } + + if (wxutil::Messagebox::Show(_("Complete Merge Operation?"), + _("Map has been saved. Do you want to complete the ongoing merge operation using this state?"), + ::ui::IDialog::MessageType::MESSAGE_ASK) == ::ui::IDialog::RESULT_YES) + { + try + { + tryToFinishMerge(_repository); + } + catch (git::GitException& ex) + { + wxutil::Messagebox::ShowError(ex.what()); + } + } + } + + if (ev == IMap::MapMergeOperationAborted && _repository && _repository->mergeIsInProgress()) + { + // Ask the user whether to cancel the git merge status + if (wxutil::Messagebox::Show(_("Cancel Merge Operation?"), + _("You've aborted the map merge. Do you want to abort the ongoing git merge operation too?\n" + "This will reset the repository to the state it had before the merge was started."), + ::ui::IDialog::MessageType::MESSAGE_ASK) == ::ui::IDialog::RESULT_YES) + { + // TODO + } + } + else if (ev == IMap::MapMergeOperationFinished && _repository && _repository->mergeIsInProgress()) + { + wxutil::Messagebox::Show(_("Save the map when done merging"), + _("Now that the merge is finished, please save the map such that the git merge operation can be completed."), + ::ui::IDialog::MessageType::MESSAGE_CONFIRM); + + // Now wait for the merge to complete (MapSaved event) + } } void VcsStatus::startFetchTask()