Refactored some masterlist update code.

Trying to abstract the Git operations away from the update logic, as
there's quite a lot going on.
This commit is contained in:
Oliver Hamlet
2015-10-28 08:05:34 +00:00
parent 3c9edb86d5
commit 1451ad75e5
3 changed files with 288 additions and 239 deletions
+166
View File
@@ -50,6 +50,9 @@ namespace loot {
buf({0}) {
// Init threading system and OpenSSL (for Linux builds).
git_libgit2_init();
checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
clone_options = GIT_CLONE_OPTIONS_INIT;
}
GitHelper::~GitHelper() {
@@ -118,6 +121,12 @@ namespace loot {
tree = nullptr;
diff = nullptr;
buf = {0};
// Also free any path strings in the checkout options.
for (size_t i = 0; i < checkout_options.paths.count; ++i) {
delete[] checkout_options.paths.strings[i];
checkout_options.paths.strings[i] = nullptr;
}
}
bool GitHelper::IsRepository(const boost::filesystem::path& path) {
@@ -146,6 +155,163 @@ namespace loot {
return 0;
}
// Clones a repository and opens it.
void GitHelper::Clone(const boost::filesystem::path& path, const std::string& url) {
if (this->repo != nullptr)
throw error(error::git_error, "Cannot clone repository that has already been opened.");
this->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 tempPath = fs::temp_directory_path() / fs::unique_path();
if (!fs::is_empty(path)) {
// Directory is non-empty. Delete the masterlist file and
// .git folder, then move any remaining files to a temporary
// folder while the repo is cloned, before moving them back.
BOOST_LOG_TRIVIAL(trace) << "Repo path not empty, renaming folder.";
// Clear any read-only flags first.
this->FixRepoPermissions(path);
// Now move to temp path.
fs::rename(path, tempPath);
// Recreate the game folder so that we don't inadvertently
// cause any other errors (everything past LOOT init assumes
// it exists).
fs::create_directory(path);
}
// Perform the clone.
this->Call(git_clone(&this->repo, url.c_str(), path.string().c_str(), &this->clone_options));
if (fs::exists(tempPath)) {
//Move contents back in.
BOOST_LOG_TRIVIAL(trace) << "Repo path wasn't empty, moving previous files back in.";
for (fs::directory_iterator it(tempPath); it != fs::directory_iterator(); ++it) {
if (!fs::exists(path / it->path().filename())) {
//No conflict, OK to move back in.
fs::rename(it->path(), path / it->path().filename());
}
}
//Delete temporary folder.
fs::remove_all(tempPath);
}
}
void GitHelper::Fetch(const std::string& remote) {
if (this->repo == nullptr)
throw error(error::git_error, "Cannot fetch updates for repository that has not been opened.");
BOOST_LOG_TRIVIAL(trace) << "Fetching updates from remote.";
this->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."));
// Get the origin remote.
this->Call(git_remote_lookup(&this->remote, this->repo, remote.c_str()));
// Now fetch any updates.
git_fetch_options fetch_options = GIT_FETCH_OPTIONS_INIT;
this->Call(git_remote_fetch(this->remote, nullptr, &fetch_options, nullptr));
// Log some stats on what was fetched either during update or clone.
const git_transfer_progress * stats = git_remote_stats(this->remote);
BOOST_LOG_TRIVIAL(info) << "Received " << stats->indexed_objects << " of " << stats->total_objects << " objects in " << stats->received_bytes << " bytes.";
git_remote_free(this->remote);
this->remote = nullptr;
}
void GitHelper::CheckoutNewBranch(const std::string& remote, const std::string& branch) {
if (this->repo == nullptr)
throw error(error::git_error, "Cannot fetch updates for repository that has not been opened.");
else if (this->commit != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, commit memory already allocated.");
else if (this->obj != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, object memory already allocated.");
else if (this->ref != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, reference memory already allocated.");
BOOST_LOG_TRIVIAL(trace) << "Looking up commit referred to by the remote branch \"" << branch << "\".";
this->Call(git_revparse_single(&this->obj, this->repo, (remote + "/" + branch).c_str()));
const git_oid * commit_id = git_object_id(this->obj);
// Create a branch.
BOOST_LOG_TRIVIAL(trace) << "Creating the new branch.";
this->Call(git_commit_lookup(&this->commit, this->repo, commit_id));
this->Call(git_branch_create(&this->ref, this->repo, branch.c_str(), this->commit, 0));
// Set upstream.
BOOST_LOG_TRIVIAL(trace) << "Setting the upstream for the new branch.";
this->Call(git_branch_set_upstream(this->ref, (remote + "/" + branch).c_str()));
// Check if HEAD points to the desired branch and set it to if not.
if (!git_branch_is_head(this->ref)) {
BOOST_LOG_TRIVIAL(trace) << "Setting HEAD to follow branch: " << branch;
this->Call(git_repository_set_head(this->repo, (string("refs/heads/") + branch).c_str()));
}
BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD.";
this->Call(git_checkout_head(this->repo, &this->checkout_options));
// Free tree and commit pointers. Reference pointer is still used below.
git_object_free(this->obj);
git_commit_free(this->commit);
git_reference_free(this->ref);
this->commit = nullptr;
this->obj = nullptr;
this->ref = nullptr;
}
void GitHelper::CheckoutRevision(const std::string& revision) {
if (this->repo == nullptr)
throw error(error::git_error, "Cannot checkout revision for repository that has not been opened.");
else if (this->obj != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, object memory already allocated.");
// Get an object ID for 'HEAD^'.
this->Call(git_revparse_single(&this->obj, this->repo, revision.c_str()));
const git_oid * oid = git_object_id(this->obj);
// Detach HEAD to HEAD~1. This will roll back HEAD by one commit each time it is called.
this->Call(git_repository_set_head_detached(this->repo, oid));
// Checkout the new HEAD.
BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD.";
this->Call(git_checkout_head(this->repo, &this->checkout_options));
git_object_free(this->obj);
this->obj = nullptr;
}
std::string GitHelper::GetHeadShortId() {
if (this->repo == nullptr)
throw error(error::git_error, "Cannot checkout revision for repository that has not been opened.");
else if (this->obj != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, object memory already allocated.");
else if (this->ref != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, reference memory already allocated.");
else if (this->buf.ptr != nullptr)
throw error(error::git_error, "Cannot fetch repository updates, buffer memory already allocated.");
BOOST_LOG_TRIVIAL(trace) << "Getting the Git object for HEAD.";
this->Call(git_repository_head(&this->ref, this->repo));
this->Call(git_reference_peel(&this->obj, this->ref, GIT_OBJ_COMMIT));
BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID.";
this->Call(git_object_short_id(&this->buf, this->obj));
string revision = this->buf.ptr;
git_reference_free(this->ref);
git_object_free(this->obj);
git_buf_free(&this->buf);
this->ref = nullptr;
this->obj = nullptr;
this->buf = {0};
return revision;
}
bool IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename) {
GitHelper git;
+20 -4
View File
@@ -43,11 +43,18 @@ namespace loot {
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);
// Clones a repository and opens it. Sets 'repo'.
void Clone(const boost::filesystem::path& path, const std::string& url);
static int diff_file_cb(const git_diff_delta *delta, float progress, void * payload);
// Fetch from remote.
void Fetch(const std::string& remote);
// Create and checkout a new remote-tracking branch.
void CheckoutNewBranch(const std::string& remote, const std::string& branch);
void CheckoutRevision(const std::string& revision);
std::string GetHeadShortId();
git_repository * repo;
git_remote * remote;
@@ -62,12 +69,21 @@ namespace loot {
git_diff * diff;
git_buf buf;
git_checkout_options checkout_options;
git_clone_options clone_options;
struct git_diff_payload {
bool fileFound;
const char * fileToFind;
};
static int diff_file_cb(const git_diff_delta *delta, float progress, void * payload);
private:
std::string errorMessage;
// Removes the read-only flag from some files in git repositories
// created by libgit2.
static void FixRepoPermissions(const boost::filesystem::path& path);
};
bool IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename);
+102 -235
View File
@@ -89,238 +89,147 @@ namespace loot {
return Update(game.MasterlistPath(), game.RepoURL(), game.RepoBranch());
}
bool Masterlist::Update(const boost::filesystem::path& path, const std::string& repoURL, const std::string& 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();
fs::path repoPath = path.parent_path();
string filename = path.filename().string();
if (repoURL.empty() || repoBranch.empty())
if (repoUrl.empty() || repoBranch.empty())
throw error(error::invalid_args, "Repository URL and branch must not be empty.");
// First initialise some stuff that isn't specific to a repository.
// Initialise checkout options.
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;
git.checkout_options.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING;
git.checkout_options.paths.strings = &paths;
git.checkout_options.paths.count = 1;
// Initialise clone options.
git.clone_options.checkout_opts = git.checkout_options;
git.clone_options.bare = 0;
git.clone_options.checkout_branch = repoBranch.c_str();
// 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();
//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);
}
}
BOOST_LOG_TRIVIAL(trace) << "Attempting to open the Git repository at: " << repoPath;
if (!git.IsRepository(repoPath))
git.Clone(repoPath, repoUrl);
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());
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%.")) % repoPath.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()));
git.Call(git_repository_open(&git.repo, repoPath.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.repo, "origin", repoURL.c_str()));
// Reload the remote object.
git_remote_free(git.remote);
git.Call(git_remote_lookup(&git.remote, git.repo, "origin"));
}
// Set the remote URL.
BOOST_LOG_TRIVIAL(info) << "Using remote URL: " << repoUrl;
git.Call(git_remote_set_url(git.repo, "origin", repoUrl.c_str()));
// 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.Fetch("origin");
git_fetch_options fetch_options = GIT_FETCH_OPTIONS_INIT;
git.Call(git_remote_fetch(git.remote, nullptr, &fetch_options, 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());
// Check that a local 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%.")) % repoPath.string()).str());
int ret = git_branch_lookup(&git.ref, git.repo, repoBranch.c_str(), GIT_BRANCH_LOCAL);
if (ret == GIT_ENOTFOUND) {
if (ret == GIT_ENOTFOUND)
// Branch doesn't exist. Create a new branch using the remote branch's latest commit.
git.CheckoutNewBranch("origin", repoBranch);
else {
// The local branch exists. Need to merge the remote branch
// into it.
git.Call(ret); // Handle other errors from preceding branch lookup.
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);
// 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()));
}
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));
// Get remote branch reference.
git.Call(git_branch_upstream(&git.ref2, git.ref));
// 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()));
}
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.";
BOOST_LOG_TRIVIAL(trace) << "Checking HEAD and remote branch's mergeability.";
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.
if ((analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) == 0 && (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) == 0) {
// The local branch can't be easily merged. Best just to delete and recreate it.
BOOST_LOG_TRIVIAL(trace) << "Local branch cannot be easily merged with remote branch.";
// 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);
BOOST_LOG_TRIVIAL(trace) << "Deleting the local branch.";
git.Call(git_branch_delete(git.ref));
// Need to free ref before calling git.CheckoutNewBranch()
git_reference_free(git.ref);
git.ref = nullptr;
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, "Setting branch reference."));
git_reference_free(git.ref2);
git.ref2 = nullptr;
git.CheckoutNewBranch("origin", repoBranch);
}
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.
else {
// Get remote branch commit ID.
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;
git_reference_free(git.ref2);
git.ref2 = 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, "Setting branch reference."));
git_reference_free(git.ref2);
git.ref2 = nullptr;
}
else {
// HEAD matches the remote branch. If the masterlist in
bool updateBranchHead = true;
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 local branch commit ID.
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;
updateBranchHead = local_commit_id->id != remote_commit_id->id;
// 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.";
if (!updateBranchHead) {
BOOST_LOG_TRIVIAL(trace) << "Local and remote branch heads are equal.";
if (!IsFileDifferent(repoPath, filename)) {
BOOST_LOG_TRIVIAL(info) << "Local branch and masterlist file are already up to date.";
return false;
}
} else
BOOST_LOG_TRIVIAL(trace) << "Local branch heads is ahead of remote branch head.";
} else
BOOST_LOG_TRIVIAL(trace) << "Local branch can be fast-forwarded to remote branch.";
BOOST_LOG_TRIVIAL(trace) << "Diffing HEAD and filesystem masterlists.";
if (!IsFileDifferent(repo_path, filename)) {
return false;
}
if (updateBranchHead) {
// The remote branch reference points to a particular
// commit. Update the local branch reference to point
// to the same commit.
BOOST_LOG_TRIVIAL(trace) << "Syncing local branch head with remote branch head.";
git.Call(git_reference_set_target(&git.ref2, git.ref, remote_commit_id, "Setting branch reference."));
git_reference_free(git.ref2);
git.ref2 = nullptr;
}
}
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);
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, &git.checkout_options));
}
}
// 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
@@ -329,39 +238,10 @@ namespace loot {
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());
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%.")) % repoPath.string()).str());
do {
// Get some descriptive info about what was checked out.
string revision, date;
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};
// Get the HEAD revision's short ID.
string revision = git.GetHeadShortId();
//Now try parsing the masterlist.
BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing.";
@@ -372,29 +252,16 @@ namespace loot {
}
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));
// 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("The latest masterlist revision contains a syntax error, LOOT is using the most recent valid revision instead. Syntax errors are usually minor and fixed within hours.").str();
//There was an error, roll back one revision.
BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. Masterlist revision " + string(revision) + ": " + e.what();
git.CheckoutRevision("HEAD^");
}
} while (parsingFailed);