|
|
|
|
@@ -16,6 +16,70 @@
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
std::string trim(std::string value)
|
|
|
|
|
{
|
|
|
|
|
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char character)
|
|
|
|
|
{ return std::isspace(character) != 0; });
|
|
|
|
|
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char character)
|
|
|
|
|
{ return std::isspace(character) != 0; }).base();
|
|
|
|
|
if (first >= last)
|
|
|
|
|
return {};
|
|
|
|
|
return std::string(first, last);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool startsWith(std::string_view text, std::string_view prefix)
|
|
|
|
|
{
|
|
|
|
|
return text.size() >= prefix.size() && text.substr(0, prefix.size()) == prefix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool parseCheckoutReflog(std::string_view subject, std::string &from, std::string &to)
|
|
|
|
|
{
|
|
|
|
|
constexpr std::string_view prefix = "checkout: moving from ";
|
|
|
|
|
if (!startsWith(subject, prefix))
|
|
|
|
|
return false;
|
|
|
|
|
const size_t separator = subject.find(" to ", prefix.size());
|
|
|
|
|
if (separator == std::string_view::npos)
|
|
|
|
|
return false;
|
|
|
|
|
from = trim(std::string(subject.substr(prefix.size(), separator - prefix.size())));
|
|
|
|
|
to = trim(std::string(subject.substr(separator + 4)));
|
|
|
|
|
return !from.empty() && !to.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string commitSummaryFromReflog(std::string_view subject)
|
|
|
|
|
{
|
|
|
|
|
const size_t colon = subject.find(':');
|
|
|
|
|
return colon == std::string_view::npos ? std::string{} : trim(std::string(subject.substr(colon + 1)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ToolbarHistoryAction unavailableAction(const char *tooltip)
|
|
|
|
|
{
|
|
|
|
|
ToolbarHistoryAction action;
|
|
|
|
|
action.tooltip = tooltip;
|
|
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ToolbarHistoryAction makeCheckoutAction(const std::string &from, const std::string &to, const char *verb)
|
|
|
|
|
{
|
|
|
|
|
ToolbarHistoryAction action;
|
|
|
|
|
action.kind = ToolbarHistoryActionKind::checkout;
|
|
|
|
|
action.available = true;
|
|
|
|
|
action.source = from;
|
|
|
|
|
action.target = to;
|
|
|
|
|
action.tooltip = std::string(verb) + " Checkout from \"" + from + "\" to \"" + to + "\"";
|
|
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ToolbarHistoryAction makeCommitAction(std::string summary, const char *verb)
|
|
|
|
|
{
|
|
|
|
|
ToolbarHistoryAction action;
|
|
|
|
|
action.kind = ToolbarHistoryActionKind::commit;
|
|
|
|
|
action.available = true;
|
|
|
|
|
action.summary = std::move(summary);
|
|
|
|
|
action.tooltip = action.summary.empty() ? std::string(verb) + " Last Commit"
|
|
|
|
|
: std::string(verb) + " Commit \"" + action.summary + "\"";
|
|
|
|
|
return action;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void addBadge(RepositoryView &repository, const git_oid *oid, RefBadge badge)
|
|
|
|
|
{
|
|
|
|
|
if (!oid)
|
|
|
|
|
@@ -406,6 +470,50 @@ void GitManager::loadWorkingTree(RepositoryView &repository)
|
|
|
|
|
git_status_list_free(list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GitManager::loadToolbarHistoryActions(RepositoryView &repository)
|
|
|
|
|
{
|
|
|
|
|
repository.undo_action = unavailableAction("No recent action found to undo");
|
|
|
|
|
repository.redo_action = unavailableAction("No recent undo found to redo");
|
|
|
|
|
|
|
|
|
|
std::string output;
|
|
|
|
|
std::string error;
|
|
|
|
|
if (!captureGit(repository, {"reflog", "--format=%gs", "-n", "2", "HEAD"}, output, error))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> entries;
|
|
|
|
|
std::istringstream stream(output);
|
|
|
|
|
std::string line;
|
|
|
|
|
while (std::getline(stream, line))
|
|
|
|
|
{
|
|
|
|
|
if (!line.empty() && line.back() == '\r')
|
|
|
|
|
line.pop_back();
|
|
|
|
|
if (!line.empty())
|
|
|
|
|
entries.push_back(std::move(line));
|
|
|
|
|
}
|
|
|
|
|
if (entries.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
std::string from;
|
|
|
|
|
std::string to;
|
|
|
|
|
if (parseCheckoutReflog(entries[0], from, to))
|
|
|
|
|
{
|
|
|
|
|
repository.undo_action = makeCheckoutAction(from, to, "Undo");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startsWith(entries[0], "commit"))
|
|
|
|
|
{
|
|
|
|
|
repository.undo_action = makeCommitAction(commitSummaryFromReflog(entries[0]), "Undo");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startsWith(entries[0], "reset: moving to HEAD~1") && entries.size() > 1 &&
|
|
|
|
|
startsWith(entries[1], "commit"))
|
|
|
|
|
{
|
|
|
|
|
repository.redo_action = makeCommitAction(commitSummaryFromReflog(entries[1]), "Redo");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GitManager::loadRepositoryData(RepositoryView &repository, std::string &error)
|
|
|
|
|
{
|
|
|
|
|
repository.local_branches.clear();
|
|
|
|
|
@@ -431,6 +539,7 @@ bool GitManager::loadRepositoryData(RepositoryView &repository, std::string &err
|
|
|
|
|
repository.name = path.filename().string();
|
|
|
|
|
if (repository.name.empty())
|
|
|
|
|
repository.name = path.parent_path().filename().string();
|
|
|
|
|
loadToolbarHistoryActions(repository);
|
|
|
|
|
|
|
|
|
|
git_reference *head = nullptr;
|
|
|
|
|
if (git_repository_head(&head, repository.repo) == 0)
|
|
|
|
|
@@ -902,12 +1011,74 @@ bool GitManager::popStash(RepositoryView &repository, std::string &error)
|
|
|
|
|
|
|
|
|
|
bool GitManager::undoCommit(RepositoryView &repository, std::string &error)
|
|
|
|
|
{
|
|
|
|
|
return runGit(repository, {"reset", "--soft", "HEAD~1"}, "Last commit moved back to staging", error);
|
|
|
|
|
if (!repository.undo_action.available)
|
|
|
|
|
{
|
|
|
|
|
error = repository.undo_action.tooltip.empty() ? "No recent action found to undo" : repository.undo_action.tooltip;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> arguments;
|
|
|
|
|
std::string success_message;
|
|
|
|
|
ToolbarHistoryAction redo_action = unavailableAction("No recent undo found to redo");
|
|
|
|
|
switch (repository.undo_action.kind)
|
|
|
|
|
{
|
|
|
|
|
case ToolbarHistoryActionKind::checkout:
|
|
|
|
|
arguments = {"checkout", repository.undo_action.source};
|
|
|
|
|
success_message = "Checked out " + repository.undo_action.source;
|
|
|
|
|
redo_action = makeCheckoutAction(repository.undo_action.source, repository.undo_action.target, "Redo");
|
|
|
|
|
break;
|
|
|
|
|
case ToolbarHistoryActionKind::commit:
|
|
|
|
|
arguments = {"reset", "--soft", "HEAD~1"};
|
|
|
|
|
success_message = "Last commit moved back to staging";
|
|
|
|
|
redo_action = makeCommitAction(repository.undo_action.summary, "Redo");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
error = "No supported action is available to undo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string command_output;
|
|
|
|
|
if (!captureGit(repository, arguments, command_output, error))
|
|
|
|
|
return false;
|
|
|
|
|
if (!loadRepositoryData(repository, error))
|
|
|
|
|
return false;
|
|
|
|
|
repository.redo_action = std::move(redo_action);
|
|
|
|
|
error = success_message;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GitManager::redoCommit(RepositoryView &repository, std::string &error)
|
|
|
|
|
{
|
|
|
|
|
return runGit(repository, {"reset", "--soft", "HEAD@{1}"}, "Commit restored from reflog", error);
|
|
|
|
|
if (!repository.redo_action.available)
|
|
|
|
|
{
|
|
|
|
|
error = repository.redo_action.tooltip.empty() ? "No recent undo found to redo" : repository.redo_action.tooltip;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> arguments;
|
|
|
|
|
std::string success_message;
|
|
|
|
|
switch (repository.redo_action.kind)
|
|
|
|
|
{
|
|
|
|
|
case ToolbarHistoryActionKind::checkout:
|
|
|
|
|
arguments = {"checkout", repository.redo_action.target};
|
|
|
|
|
success_message = "Checked out " + repository.redo_action.target;
|
|
|
|
|
break;
|
|
|
|
|
case ToolbarHistoryActionKind::commit:
|
|
|
|
|
arguments = {"reset", "--soft", "HEAD@{1}"};
|
|
|
|
|
success_message = "Commit restored from reflog";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
error = "No supported action is available to redo";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string command_output;
|
|
|
|
|
if (!captureGit(repository, arguments, command_output, error))
|
|
|
|
|
return false;
|
|
|
|
|
if (!loadRepositoryData(repository, error))
|
|
|
|
|
return false;
|
|
|
|
|
error = success_message;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GitManager::stageAll(RepositoryView &repository, std::string &error)
|
|
|
|
|
|