mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Refactored git code.
The masterlist-specific code in has been moved into masterlist.cpp, and the more general git-related code has been moved into a GitHelper class. Although it's a helper, there are a few loot::error exceptions thrown in its code, as it provides an error code that distinguishes git related errors from others, so is useful. There is still a lot of git code in masterlist.cpp. Not all of this will be moved, but there may be some bits that can be.
This commit is contained in:
+2
-1
@@ -93,11 +93,11 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata/conditional_metadata.c
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/masterlist.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/plugin/plugin.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/plugin/plugin_loader.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/git_helper.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/helpers.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/language.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/version.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/graph.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/git.cpp"
|
||||
"${CMAKE_BINARY_DIR}/generated/globals.cpp")
|
||||
|
||||
set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_grammar.h"
|
||||
@@ -117,6 +117,7 @@ set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_grammar.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/masterlist.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/plugin/plugin.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/plugin/plugin_loader.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/git_helper.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/helpers.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/language.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers/streams.h"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,185 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2012-2015 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT is free software: you can redistribute
|
||||
it and/or modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
LOOT is distributed in the hope that it will
|
||||
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with LOOT. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "git_helper.h"
|
||||
#include "../error.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace lc = boost::locale;
|
||||
|
||||
namespace loot {
|
||||
GitHelper::GitHelper() :
|
||||
repo(nullptr),
|
||||
remote(nullptr),
|
||||
cfg(nullptr),
|
||||
obj(nullptr),
|
||||
commit(nullptr),
|
||||
ref(nullptr),
|
||||
ref2(nullptr),
|
||||
sig(nullptr),
|
||||
blob(nullptr),
|
||||
annotated_commit(nullptr),
|
||||
tree(nullptr),
|
||||
diff(nullptr),
|
||||
buf({0}) {
|
||||
// Init threading system and OpenSSL (for Linux builds).
|
||||
git_libgit2_init();
|
||||
}
|
||||
|
||||
GitHelper::~GitHelper() {
|
||||
string path;
|
||||
if (repo != nullptr)
|
||||
path = git_repository_path(repo);
|
||||
|
||||
Free();
|
||||
|
||||
if (!path.empty()) {
|
||||
try {
|
||||
FixRepoPermissions(path);
|
||||
}
|
||||
catch (exception&) {}
|
||||
}
|
||||
|
||||
git_libgit2_shutdown();
|
||||
}
|
||||
|
||||
void GitHelper::Call(int error_code) {
|
||||
if (!error_code)
|
||||
return;
|
||||
|
||||
const git_error * last_error = giterr_last();
|
||||
std::string gitError;
|
||||
if (last_error == nullptr)
|
||||
gitError = to_string(error_code) + ".";
|
||||
else
|
||||
gitError = to_string(error_code) + "; " + last_error->message;
|
||||
giterr_clear();
|
||||
|
||||
if (errorMessage.empty())
|
||||
errorMessage = (boost::format(lc::translate("Git operation failed. Error: %1%")) % gitError).str();
|
||||
|
||||
BOOST_LOG_TRIVIAL(error) << "Git operation failed. Error: " << gitError;
|
||||
throw loot::error(loot::error::git_error, errorMessage);
|
||||
}
|
||||
|
||||
void GitHelper::SetErrorMessage(const std::string& message) {
|
||||
errorMessage = message;
|
||||
}
|
||||
|
||||
void GitHelper::Free() {
|
||||
git_commit_free(commit);
|
||||
git_object_free(obj);
|
||||
git_config_free(cfg);
|
||||
git_remote_free(remote);
|
||||
git_repository_free(repo);
|
||||
git_reference_free(ref);
|
||||
git_reference_free(ref2);
|
||||
git_signature_free(sig);
|
||||
git_blob_free(blob);
|
||||
git_annotated_commit_free(annotated_commit);
|
||||
git_tree_free(tree);
|
||||
git_diff_free(diff);
|
||||
git_buf_free(&buf);
|
||||
|
||||
commit = nullptr;
|
||||
obj = nullptr;
|
||||
cfg = nullptr;
|
||||
remote = nullptr;
|
||||
repo = nullptr;
|
||||
ref = nullptr;
|
||||
ref2 = nullptr;
|
||||
sig = nullptr;
|
||||
blob = nullptr;
|
||||
annotated_commit = nullptr;
|
||||
tree = nullptr;
|
||||
diff = nullptr;
|
||||
buf = {0};
|
||||
}
|
||||
|
||||
bool GitHelper::IsRepository(const boost::filesystem::path& path) {
|
||||
return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0;
|
||||
}
|
||||
|
||||
// Removes the read-only flag from some files in git repositories created by libgit2.
|
||||
void GitHelper::FixRepoPermissions(const boost::filesystem::path& path) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << path;
|
||||
for (fs::recursive_directory_iterator it(path); it != fs::recursive_directory_iterator(); ++it) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path();
|
||||
fs::permissions(it->path(), fs::add_perms | fs::owner_write | fs::group_write | fs::others_write);
|
||||
}
|
||||
}
|
||||
|
||||
int GitHelper::diff_file_cb(const git_diff_delta *delta, float progress, void * payload) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Checking diff for: " << delta->old_file.path;
|
||||
git_diff_payload * gdp = (git_diff_payload*)payload;
|
||||
if (strcmp(delta->old_file.path, gdp->fileToFind) == 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Edited masterlist found.";
|
||||
gdp->fileFound = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename) {
|
||||
GitHelper git;
|
||||
|
||||
if (!git.IsRepository(repoRoot)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: Git repository missing.";
|
||||
throw error(error::ok, lc::translate("Unknown: Git repository missing"));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it.";
|
||||
git.Call(git_repository_open(&git.repo, repoRoot.string().c_str()));
|
||||
|
||||
// Perform a git diff, then iterate the deltas to see if one exists for the masterlist.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Getting the tree for the HEAD revision.";
|
||||
git.Call(git_revparse_single(&git.obj, git.repo, "HEAD^{tree}"));
|
||||
git.Call(git_tree_lookup(&git.tree, git.repo, git_object_id(git.obj)));
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Performing git diff.";
|
||||
git.Call(git_diff_tree_to_workdir_with_index(&git.diff, git.repo, git.tree, NULL));
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Iterating over git diff deltas.";
|
||||
GitHelper::git_diff_payload payload;
|
||||
payload.fileFound = false;
|
||||
payload.fileToFind = filename.c_str();
|
||||
git.Call(git_diff_foreach(git.diff, &git.diff_file_cb, NULL, NULL, &payload));
|
||||
|
||||
// Clean up memory
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr;
|
||||
git_tree_free(git.tree);
|
||||
git.tree = nullptr;
|
||||
git_diff_free(git.diff);
|
||||
git.diff = nullptr;
|
||||
|
||||
return payload.fileFound;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2012-2015 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT is free software: you can redistribute
|
||||
it and/or modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
LOOT is distributed in the hope that it will
|
||||
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with LOOT. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __LOOT_GIT_HELPER__
|
||||
#define __LOOT_GIT_HELPER__
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <git2.h>
|
||||
|
||||
namespace loot {
|
||||
class GitHelper {
|
||||
public:
|
||||
GitHelper();
|
||||
~GitHelper();
|
||||
|
||||
void Call(int error_code);
|
||||
void SetErrorMessage(const std::string& message);
|
||||
void Free();
|
||||
|
||||
static bool IsRepository(const boost::filesystem::path& path);
|
||||
|
||||
// Removes the read-only flag from some files in git repositories
|
||||
// created by libgit2.
|
||||
static void FixRepoPermissions(const boost::filesystem::path& path);
|
||||
|
||||
static int diff_file_cb(const git_diff_delta *delta, float progress, void * payload);
|
||||
|
||||
git_repository * repo;
|
||||
git_remote * remote;
|
||||
git_config * cfg;
|
||||
git_object * obj;
|
||||
git_commit * commit;
|
||||
git_reference * ref;
|
||||
git_reference * ref2;
|
||||
git_signature * sig;
|
||||
git_blob * blob;
|
||||
git_annotated_commit * annotated_commit;
|
||||
git_tree * tree;
|
||||
git_diff * diff;
|
||||
git_buf buf;
|
||||
|
||||
struct git_diff_payload {
|
||||
bool fileFound;
|
||||
const char * fileToFind;
|
||||
};
|
||||
private:
|
||||
std::string errorMessage;
|
||||
};
|
||||
|
||||
bool IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename);
|
||||
}
|
||||
#endif
|
||||
@@ -23,9 +23,12 @@
|
||||
*/
|
||||
|
||||
#include "masterlist.h"
|
||||
#include "helpers/git_helper.h"
|
||||
#include "game/game.h"
|
||||
#include "error.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
@@ -61,4 +64,379 @@ namespace loot {
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
void Masterlist::GetGitInfo(const boost::filesystem::path& path, bool shortID) {
|
||||
// Compare HEAD and working copy, and get revision info.
|
||||
GitHelper git;
|
||||
git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in %1%.")) % path.parent_path().string()).str());
|
||||
|
||||
if (!fs::exists(path)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: No masterlist present.";
|
||||
throw error(error::ok, lc::translate("N/A: No masterlist present"));
|
||||
}
|
||||
else if (!git.IsRepository(path.parent_path())) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: Git repository missing.";
|
||||
throw error(error::ok, lc::translate("Unknown: Git repository missing"));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it.";
|
||||
git.Call(git_repository_open(&git.repo, path.parent_path().string().c_str()));
|
||||
|
||||
//Need to get the HEAD object, because the individual file has a different SHA.
|
||||
BOOST_LOG_TRIVIAL(info) << "Getting the Git object for the tree at HEAD.";
|
||||
git.Call(git_revparse_single(&git.obj, git.repo, "HEAD"));
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID.";
|
||||
if (shortID) {
|
||||
git.Call(git_object_short_id(&git.buf, git.obj));
|
||||
revision = git.buf.ptr;
|
||||
}
|
||||
else {
|
||||
char c_rev[GIT_OID_HEXSZ + 1];
|
||||
revision = git_oid_tostr(c_rev, GIT_OID_HEXSZ + 1, git_object_id(git.obj));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Getting date for Git object.";
|
||||
const git_oid * oid = git_object_id(git.obj);
|
||||
git.Call(git_commit_lookup(&git.commit, git.repo, oid));
|
||||
git_time_t time = git_commit_time(git.commit);
|
||||
boost::locale::date_time dateTime(time);
|
||||
stringstream out;
|
||||
out << boost::locale::as::ftime("%Y-%m-%d") << dateTime;
|
||||
date = out.str();
|
||||
|
||||
// Free object memory.
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Diffing masterlist HEAD and working copy.";
|
||||
if (IsFileDifferent(path.parent_path(), path.filename().string())) {
|
||||
revision += string(" ") + lc::translate("(edited)").str();
|
||||
date += string(" ") + lc::translate("(edited)").str();
|
||||
}
|
||||
}
|
||||
|
||||
bool Masterlist::Update(const Game& game) {
|
||||
return Update(game.MasterlistPath(), game.RepoURL(), game.RepoBranch());
|
||||
}
|
||||
|
||||
bool Masterlist::Update(const boost::filesystem::path& path, const std::string& repoURL, const std::string& repoBranch) {
|
||||
GitHelper git;
|
||||
fs::path repo_path = path.parent_path();
|
||||
string filename = path.filename().string();
|
||||
|
||||
// First initialise some stuff that isn't specific to a repository.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Creating a reflog signature to use.";
|
||||
// Create a reflog signature for any changes.
|
||||
git.Call(git_signature_new(&git.sig, "LOOT", "loot@placeholder.net", 0, 0));
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Setting up checkout options.";
|
||||
char * paths = new char[filename.length() + 1];
|
||||
strcpy(paths, filename.c_str());
|
||||
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
|
||||
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING;
|
||||
checkout_opts.paths.strings = &paths;
|
||||
checkout_opts.paths.count = 1;
|
||||
|
||||
// Now try to access the repository if it exists, or clone one if it doesn't.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Attempting to open the Git repository at: " << repo_path;
|
||||
if (!git.IsRepository(repo_path)) {
|
||||
git.SetErrorMessage(lc::translate("An error occurred while trying to clone the remote masterlist repository."));
|
||||
// Clone the remote repository.
|
||||
BOOST_LOG_TRIVIAL(info) << "Repository doesn't exist, cloning the remote repository.";
|
||||
|
||||
fs::path temp_path = repo_path.string() + ".temp";
|
||||
if (!fs::is_empty(repo_path)) {
|
||||
// Clear any read-only flags first.
|
||||
git.FixRepoPermissions(repo_path);
|
||||
// Now, libgit2 doesn't support cloning into non-empty folders. Rename the folder
|
||||
|
||||
// temporarily, and move its contents back in afterwards, skipping any that then conflict.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Repo path not empty, renaming folder.";
|
||||
// If the temp path already exists, it needs to be deleted.
|
||||
if (fs::exists(temp_path)) {
|
||||
git.FixRepoPermissions(temp_path);
|
||||
fs::remove_all(temp_path);
|
||||
}
|
||||
// There's no point moving the .git folder, so delete that.
|
||||
fs::remove_all(repo_path / ".git");
|
||||
// Now move to temp path.
|
||||
fs::rename(repo_path, temp_path);
|
||||
// Recreate the game folder so that we don't inadvertently cause any other errors (everything past LOOT init assumes it exists).
|
||||
fs::create_directory(repo_path);
|
||||
}
|
||||
|
||||
//First set up clone options.
|
||||
|
||||
git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT;
|
||||
clone_options.checkout_opts = checkout_opts;
|
||||
clone_options.bare = 0;
|
||||
clone_options.checkout_branch = repoBranch.c_str();
|
||||
clone_options.signature = git.sig;
|
||||
|
||||
//Now perform the clone.
|
||||
git.Call(git_clone(&git.repo, repoURL.c_str(), repo_path.string().c_str(), &clone_options));
|
||||
|
||||
if (fs::exists(temp_path)) {
|
||||
//Move contents back in.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Repo path wasn't empty, moving previous files back in.";
|
||||
for (fs::directory_iterator it(temp_path); it != fs::directory_iterator(); ++it) {
|
||||
if (!fs::exists(repo_path / it->path().filename())) {
|
||||
//No conflict, OK to move back in.
|
||||
fs::rename(it->path(), repo_path / it->path().filename());
|
||||
}
|
||||
}
|
||||
//Delete temporary folder.
|
||||
git.FixRepoPermissions(temp_path);
|
||||
fs::remove_all(temp_path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Repository exists: check settings are correct, then pull updates.
|
||||
git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to access the local masterlist repository. If this error happens again, try deleting the \".git\" folder in %1%.")) % repo_path.string()).str());
|
||||
|
||||
// Open the repository.
|
||||
BOOST_LOG_TRIVIAL(info) << "Existing repository found, attempting to open it.";
|
||||
git.Call(git_repository_open(&git.repo, repo_path.string().c_str()));
|
||||
|
||||
// Check that the repository's remote settings match LOOT's.
|
||||
BOOST_LOG_TRIVIAL(info) << "Checking to see if remote URL matches URL in settings.";
|
||||
git.Call(git_remote_lookup(&git.remote, git.repo, "origin"));
|
||||
const char * url = git_remote_url(git.remote);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Remote URL given: " << repoURL;
|
||||
BOOST_LOG_TRIVIAL(info) << "Remote URL in repository settings: " << url;
|
||||
if (url != repoURL) {
|
||||
BOOST_LOG_TRIVIAL(info) << "URLs do not match, setting repository URL to URL in settings.";
|
||||
// The URLs don't match. Change the remote URL to match the one LOOT has.
|
||||
git.Call(git_remote_set_url(git.remote, repoURL.c_str()));
|
||||
|
||||
// Now save change.
|
||||
git.Call(git_remote_save(git.remote));
|
||||
}
|
||||
|
||||
// Now fetch updates from the remote.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Fetching updates from remote.";
|
||||
git.SetErrorMessage(lc::translate("An error occurred while trying to update the masterlist. This could be due to a server-side error. Try again in a few minutes."));
|
||||
|
||||
git.Call(git_remote_fetch(git.remote, nullptr, git.sig, nullptr));
|
||||
|
||||
// Print some stats on what was fetched either during update or clone.
|
||||
const git_transfer_progress * stats = git_remote_stats(git.remote);
|
||||
BOOST_LOG_TRIVIAL(info) << "Received " << stats->indexed_objects << " of " << stats->total_objects << " objects in " << stats->received_bytes << " bytes.";
|
||||
|
||||
// Check that a branch with the correct name exists.
|
||||
git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to access the local masterlist repository. If this error happens again, try deleting the \".git\" folder in %1%.")) % repo_path.string()).str());
|
||||
int ret = git_branch_lookup(&git.ref, git.repo, repoBranch.c_str(), GIT_BRANCH_LOCAL);
|
||||
if (ret == GIT_ENOTFOUND) {
|
||||
// Branch doesn't exist. Create a new branch using the remote branch's latest commit.
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Looking up commit referred to by the remote branch \"" << repoBranch << "\".";
|
||||
git.Call(git_revparse_single(&git.obj, git.repo, (string("origin/") + repoBranch).c_str()));
|
||||
const git_oid * commit_id = git_object_id(git.obj);
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Creating the new branch.";
|
||||
// Create a branch.
|
||||
git.Call(git_commit_lookup(&git.commit, git.repo, commit_id));
|
||||
git.Call(git_branch_create(&git.ref, git.repo, repoBranch.c_str(), git.commit, 0, git.sig, NULL));
|
||||
|
||||
// Set upstream. Don't really know if this is necessary or not.
|
||||
git.Call(git_branch_set_upstream(git.ref, (string("origin/") + repoBranch).c_str()));
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Setting the upstream for the new branch.";
|
||||
// Free tree and commit pointers. Reference pointer is still used below.
|
||||
git_object_free(git.obj);
|
||||
git_commit_free(git.commit);
|
||||
git.commit = nullptr;
|
||||
git.obj = nullptr;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Done creating the new branch.";
|
||||
}
|
||||
else if (ret != 0)
|
||||
git.Call(ret); // Handle other errors.
|
||||
|
||||
// Check if HEAD points to the desired branch and set it to if not.
|
||||
if (!git_branch_is_head(git.ref)) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Setting HEAD to follow branch: " << repoBranch;
|
||||
git.Call(git_repository_set_head(git.repo, (string("refs/heads/") + repoBranch).c_str(), git.sig, (string("Updated HEAD to ") + repoBranch).c_str()));
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
/* The branch did exist, and is now pointed at by HEAD.
|
||||
Need to merge the remote branch into it. Just do a fast-forward merge because
|
||||
that's all that should be necessary as the local repo shouldn't get changed by
|
||||
the user.
|
||||
*/
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Checking that local and remote branches can be merged by fast-forward.";
|
||||
git_merge_analysis_t analysis;
|
||||
git_merge_preference_t pref;
|
||||
git.Call(git_reference_lookup(&git.ref2, git.repo, (string("refs/remotes/origin/") + repoBranch).c_str()));
|
||||
git.Call(git_annotated_commit_from_ref(&git.annotated_commit, git.repo, git.ref2));
|
||||
git.Call(git_merge_analysis(&analysis, &pref, git.repo, (const git_annotated_commit **)&git.annotated_commit, 1));
|
||||
|
||||
if ((analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) != 0) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Local branch can be fast-forwarded to remote branch.";
|
||||
// The remote branch reference points to a particular commit. We just want to
|
||||
// update the local branch reference to point to the same commit.
|
||||
|
||||
// Get the commit object ID.
|
||||
git.Call(git_reference_peel(&git.obj, git.ref2, GIT_OBJ_COMMIT));
|
||||
const git_oid * commit_id = git_object_id(git.obj);
|
||||
git_reference_free(git.ref2);
|
||||
git.ref2 = nullptr;
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr;
|
||||
|
||||
// Set the reference target.
|
||||
git.Call(git_reference_set_target(&git.ref2, git.ref, commit_id, git.sig, "Setting branch reference."));
|
||||
|
||||
git_reference_free(git.ref2);
|
||||
git.ref2 = nullptr;
|
||||
}
|
||||
else if ((analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0) {
|
||||
// No merge is required, but HEAD might be ahead of the remote branch. Check
|
||||
// to see if that's the case, and move HEAD back to match the remote branch
|
||||
// if so.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Local branch is up-to-date with remote branch.";
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Checking to see if local and remote branch heads are equal.";
|
||||
// Get the local branch and remote branch head commit IDs.
|
||||
|
||||
// Local branch.
|
||||
git.Call(git_reference_peel(&git.obj, git.ref, GIT_OBJ_COMMIT));
|
||||
const git_oid * local_commit_id = git_object_id(git.obj);
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr;
|
||||
|
||||
// Remote branch.
|
||||
git.Call(git_reference_peel(&git.obj, git.ref2, GIT_OBJ_COMMIT));
|
||||
const git_oid * remote_commit_id = git_object_id(git.obj);
|
||||
git_reference_free(git.ref2);
|
||||
git.ref2 = nullptr;
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr;
|
||||
|
||||
if (local_commit_id->id != remote_commit_id->id) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Branch heads are not equal, updating local HEAD.";
|
||||
// Commit IDs don't match, update HEAD, and continue with normal update procedure.
|
||||
git.Call(git_reference_set_target(&git.ref2, git.ref, remote_commit_id, git.sig, "Setting branch reference."));
|
||||
git_reference_free(git.ref2);
|
||||
git.ref2 = nullptr;
|
||||
}
|
||||
else {
|
||||
// HEAD matches the remote branch. If the masterlist in
|
||||
// HEAD also matches the masterlist file, no further
|
||||
// action needs to be taken. Otherwise, a checkout
|
||||
// must be performed and the checked-out file parsed.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Branch heads are equal.";
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Diffing HEAD and filesystem masterlists.";
|
||||
if (!IsFileDifferent(repo_path, filename)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The local repository can't be easily merged. It's best just to delete and re-clone it.
|
||||
git.FixRepoPermissions(repo_path / ".git");
|
||||
git.Free();
|
||||
fs::remove_all(repo_path / ".git");
|
||||
return this->Update(path, repoURL, repoBranch);
|
||||
}
|
||||
}
|
||||
|
||||
// Free branch pointer.
|
||||
git_reference_free(git.ref);
|
||||
git.ref = nullptr;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD.";
|
||||
git.Call(git_checkout_head(git.repo, &checkout_opts));
|
||||
}
|
||||
|
||||
// Now whether the repository was cloned or updated, the working directory contains
|
||||
// the latest masterlist. Try parsing it: on failure, detach the HEAD back one commit
|
||||
// and try again.
|
||||
|
||||
bool parsingFailed = false;
|
||||
std::string parsingError;
|
||||
git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to read information on the updated masterlist. If this error happens again, try deleting the \".git\" folder in %1%.")) % repo_path.string()).str());
|
||||
do {
|
||||
// Get some descriptive info about what was checked out.
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Getting the Git object for HEAD.";
|
||||
git.Call(git_repository_head(&git.ref, git.repo));
|
||||
git.Call(git_reference_peel(&git.obj, git.ref, GIT_OBJ_COMMIT));
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID.";
|
||||
git.Call(git_object_short_id(&git.buf, git.obj));
|
||||
revision = git.buf.ptr;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Getting date for Git object.";
|
||||
const git_oid * oid = git_object_id(git.obj);
|
||||
git.Call(git_commit_lookup(&git.commit, git.repo, oid));
|
||||
git_time_t time = git_commit_time(git.commit);
|
||||
// Now convert into a nice text format.
|
||||
boost::locale::date_time dateTime(time);
|
||||
stringstream out;
|
||||
out << boost::locale::as::ftime("%Y-%m-%d") << dateTime;
|
||||
date = out.str();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Set HEAD to commit (date): " << revision << " (" << date << ").";
|
||||
BOOST_LOG_TRIVIAL(trace) << "Freeing pointers.";
|
||||
git_reference_free(git.ref);
|
||||
git_object_free(git.obj);
|
||||
git_buf_free(&git.buf);
|
||||
git_commit_free(git.commit);
|
||||
git.ref = nullptr;
|
||||
git.obj = nullptr;
|
||||
git.commit = nullptr;
|
||||
git.buf = {0};
|
||||
|
||||
//Now try parsing the masterlist.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing.";
|
||||
try {
|
||||
this->MetadataList::Load(path);
|
||||
|
||||
for (auto &plugin : plugins) {
|
||||
plugin.ParseAllConditions();
|
||||
}
|
||||
for (auto &plugin : regexPlugins) {
|
||||
plugin.ParseAllConditions();
|
||||
}
|
||||
for (auto &message : messages) {
|
||||
message.ParseCondition();
|
||||
}
|
||||
|
||||
parsingFailed = false;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
parsingFailed = true;
|
||||
|
||||
//Roll back one revision if there's an error.
|
||||
BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. Masterlist revision " + string(revision) + ": " + e.what();
|
||||
|
||||
// Get an object ID for 'HEAD~1'.
|
||||
git.Call(git_revparse_single(&git.obj, git.repo, "HEAD~1"));
|
||||
const git_oid * oid = git_object_id(git.obj);
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr;
|
||||
|
||||
// Detach HEAD to HEAD~1. This will roll back HEAD by one commit each time it is called.
|
||||
git.Call(git_repository_set_head_detached(git.repo, oid, git.sig, "Updated HEAD to HEAD~1"));
|
||||
|
||||
// Checkout the new HEAD.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD.";
|
||||
git.Call(git_checkout_head(git.repo, &checkout_opts));
|
||||
|
||||
if (parsingError.empty())
|
||||
parsingError = boost::locale::translate("Masterlist revision").str() + " " + string(revision) + ": " + e.what() + ". " + boost::locale::translate("Rolled back to the previous revision.").str();
|
||||
}
|
||||
} while (parsingFailed);
|
||||
|
||||
if (!parsingError.empty())
|
||||
throw error(error::ok, parsingError); //Throw an OK because the process still completed in a successful state.
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user