diff --git a/src/backend/helpers/git_helper.cpp b/src/backend/helpers/git_helper.cpp index 6c661e12..81fdb7fc 100644 --- a/src/backend/helpers/git_helper.cpp +++ b/src/backend/helpers/git_helper.cpp @@ -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; diff --git a/src/backend/helpers/git_helper.h b/src/backend/helpers/git_helper.h index e94f4df1..37c23ab5 100644 --- a/src/backend/helpers/git_helper.h +++ b/src/backend/helpers/git_helper.h @@ -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); diff --git a/src/backend/masterlist.cpp b/src/backend/masterlist.cpp index 238f8d64..f145530b 100644 --- a/src/backend/masterlist.cpp +++ b/src/backend/masterlist.cpp @@ -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);