mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user