mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Simplify masterlist repository interaction code
- Refactor all direct libgit2 usage into GitHelper. - Simplify the masterlist update logic, don't bother trying to merge branches or checkout an up-to-date local branch if the working copy has edits, just delete the branch and checkout a new copy from the remote.
This commit is contained in:
+241
-17
@@ -24,6 +24,9 @@
|
||||
|
||||
#include "api/helpers/git_helper.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "api/helpers/logging.h"
|
||||
@@ -87,15 +90,58 @@ GitHelper::GitData::~GitData() {
|
||||
git_tree_free(tree);
|
||||
git_diff_free(diff);
|
||||
git_buf_free(&buffer);
|
||||
|
||||
// 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];
|
||||
}
|
||||
git_strarray_free(&checkout_options.paths);
|
||||
|
||||
git_libgit2_shutdown();
|
||||
}
|
||||
|
||||
void GitHelper::InitialiseOptions(const std::string& branch,
|
||||
const std::string& filenameToCheckout) {
|
||||
if (logger_) {
|
||||
logger_->debug(
|
||||
"Setting up checkout options uUsing branch {} and filename {}.",
|
||||
branch,
|
||||
filenameToCheckout);
|
||||
}
|
||||
|
||||
data_.checkout_options.checkout_strategy =
|
||||
GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING;
|
||||
|
||||
char** paths = new char*[1];
|
||||
paths[0] = new char[filenameToCheckout.length() + 1];
|
||||
strcpy(paths[0], filenameToCheckout.c_str());
|
||||
|
||||
data_.checkout_options.paths.strings = paths;
|
||||
data_.checkout_options.paths.count = 1;
|
||||
|
||||
// Initialise clone options.
|
||||
data_.clone_options.checkout_opts = data_.checkout_options;
|
||||
data_.clone_options.bare = 0;
|
||||
data_.clone_options.checkout_branch = branch.c_str();
|
||||
}
|
||||
|
||||
void GitHelper::Open(const boost::filesystem::path& repoRoot) {
|
||||
if (logger_) {
|
||||
logger_->info("Attempting to open Git repository at: {}",
|
||||
repoRoot.string());
|
||||
}
|
||||
Call(git_repository_open(&data_.repo, repoRoot.string().c_str()));
|
||||
}
|
||||
|
||||
void GitHelper::SetRemoteUrl(const std::string& remote,
|
||||
const std::string& url) {
|
||||
if (data_.repo == nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot set remote URL for repository that has not been opened.");
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->info("Setting URL for remote {} to {}", remote, url);
|
||||
}
|
||||
|
||||
Call(git_remote_set_url(data_.repo, remote.c_str(), url.c_str()));
|
||||
}
|
||||
|
||||
void GitHelper::Call(int error_code) {
|
||||
if (!error_code)
|
||||
return;
|
||||
@@ -332,7 +378,140 @@ void GitHelper::CheckoutRevision(const std::string& revision) {
|
||||
data_.object = nullptr;
|
||||
}
|
||||
|
||||
std::string GitHelper::GetHeadShortId() {
|
||||
void GitHelper::DeleteBranch(const std::string& branch) {
|
||||
if (data_.repo == nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot delete branch for repository that has not been opened.");
|
||||
} else if (data_.reference != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot delete branch, reference memory already allocated.");
|
||||
}
|
||||
|
||||
Call(git_branch_lookup(
|
||||
&data_.reference, data_.repo, branch.c_str(), GIT_BRANCH_LOCAL));
|
||||
|
||||
int ret = git_branch_is_head(data_.reference);
|
||||
if (ret == 1) {
|
||||
if (logger_) {
|
||||
logger_->debug("Detaching HEAD before deleting branch.");
|
||||
}
|
||||
git_repository_detach_head(data_.repo);
|
||||
} else {
|
||||
Call(ret);
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->debug("Deleting branch.");
|
||||
}
|
||||
|
||||
Call(git_branch_delete(data_.reference));
|
||||
|
||||
git_reference_free(data_.reference);
|
||||
data_.reference = nullptr;
|
||||
}
|
||||
|
||||
bool GitHelper::BranchExists(const std::string& branch) {
|
||||
if (data_.repo == nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence for repository that has not been "
|
||||
"opened.");
|
||||
} else if (data_.reference != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence, reference memory already allocated.");
|
||||
}
|
||||
|
||||
int ret = git_branch_lookup(
|
||||
&data_.reference, data_.repo, branch.c_str(), GIT_BRANCH_LOCAL);
|
||||
if (ret != GIT_ENOTFOUND) {
|
||||
// Handle other errors from preceding branch lookup.
|
||||
Call(ret);
|
||||
|
||||
git_reference_free(data_.reference);
|
||||
data_.reference = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GitHelper::IsBranchUpToDate(const std::string& branch) {
|
||||
if (data_.repo == nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence for repository that has not been "
|
||||
"opened.");
|
||||
} else if (data_.reference != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence, reference memory already allocated.");
|
||||
} else if (data_.reference2 != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence, reference2 memory already allocated.");
|
||||
}
|
||||
|
||||
Call(git_branch_lookup(
|
||||
&data_.reference, data_.repo, branch.c_str(), GIT_BRANCH_LOCAL));
|
||||
|
||||
// Get remote branch reference.
|
||||
Call(git_branch_lookup(&data_.reference2,
|
||||
data_.repo,
|
||||
("origin/" + branch).c_str(),
|
||||
GIT_BRANCH_REMOTE));
|
||||
|
||||
// Get the branch tips' commit IDs.
|
||||
auto local_commit_id = GetCommitId(data_.reference);
|
||||
auto remote_commit_id = GetCommitId(data_.reference2);
|
||||
|
||||
bool upToDate = memcmp(local_commit_id->id, remote_commit_id->id, 20) == 0;
|
||||
|
||||
// Free the remote branch reference.
|
||||
git_reference_free(data_.reference2);
|
||||
data_.reference2 = nullptr;
|
||||
|
||||
git_reference_free(data_.reference);
|
||||
data_.reference = nullptr;
|
||||
|
||||
return upToDate;
|
||||
}
|
||||
|
||||
bool GitHelper::IsBranchCheckedOut(const std::string& branch) {
|
||||
if (data_.repo == nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence for repository that has not been "
|
||||
"opened.");
|
||||
} else if (data_.reference != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot check branch existence, reference memory already allocated.");
|
||||
}
|
||||
|
||||
Call(git_branch_lookup(
|
||||
&data_.reference, data_.repo, branch.c_str(), GIT_BRANCH_LOCAL));
|
||||
|
||||
bool isCheckedOut = git_branch_is_checked_out(data_.reference);
|
||||
|
||||
git_reference_free(data_.reference);
|
||||
data_.reference = nullptr;
|
||||
|
||||
return isCheckedOut;
|
||||
}
|
||||
|
||||
const git_oid* GitHelper::GetCommitId(git_reference* reference) {
|
||||
if (reference == nullptr)
|
||||
throw GitStateError(
|
||||
"Cannot get the commit ID of a null git_reference pointer.");
|
||||
else if (data_.object != nullptr)
|
||||
throw GitStateError(
|
||||
"Cannot fetch repository updates, object memory already allocated.");
|
||||
|
||||
Call(git_reference_peel(&data_.object, reference, GIT_OBJ_COMMIT));
|
||||
const git_oid* remote_commit_id = git_object_id(data_.object);
|
||||
|
||||
git_object_free(data_.object);
|
||||
data_.object = nullptr;
|
||||
|
||||
return remote_commit_id;
|
||||
}
|
||||
|
||||
std::string GitHelper::GetHeadCommitId(bool shortId) {
|
||||
if (data_.repo == nullptr)
|
||||
throw GitStateError(
|
||||
"Cannot checkout revision for repository that has not been opened.");
|
||||
@@ -350,25 +529,70 @@ std::string GitHelper::GetHeadShortId() {
|
||||
logger_->trace("Getting the Git object for HEAD.");
|
||||
}
|
||||
Call(git_repository_head(&data_.reference, data_.repo));
|
||||
Call(git_reference_peel(&data_.object, data_.reference, GIT_OBJ_COMMIT));
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Generating hex string for Git object ID.");
|
||||
std::string id;
|
||||
if (shortId) {
|
||||
Call(git_reference_peel(&data_.object, data_.reference, GIT_OBJ_COMMIT));
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Generating hex string for Git object ID.");
|
||||
}
|
||||
Call(git_object_short_id(&data_.buffer, data_.object));
|
||||
id = data_.buffer.ptr;
|
||||
|
||||
git_object_free(data_.object);
|
||||
git_buf_free(&data_.buffer);
|
||||
data_.object = nullptr;
|
||||
data_.buffer = {0};
|
||||
} else {
|
||||
auto commit_id = GetCommitId(data_.reference);
|
||||
char c_rev[GIT_OID_HEXSZ + 1];
|
||||
|
||||
id = git_oid_tostr(c_rev, GIT_OID_HEXSZ + 1, commit_id);
|
||||
}
|
||||
Call(git_object_short_id(&data_.buffer, data_.object));
|
||||
string revision = data_.buffer.ptr;
|
||||
|
||||
git_reference_free(data_.reference);
|
||||
git_object_free(data_.object);
|
||||
git_buf_free(&data_.buffer);
|
||||
data_.reference = nullptr;
|
||||
data_.object = nullptr;
|
||||
data_.buffer = {0};
|
||||
|
||||
return revision;
|
||||
return id;
|
||||
}
|
||||
|
||||
GitHelper::GitData& GitHelper::GetData() { return data_; }
|
||||
std::string GitHelper::GetHeadCommitDate() {
|
||||
if (data_.repo == nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot get HEAD commit date for repository that has not been opened.");
|
||||
} else if (data_.commit != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot get HEAD commit date, commit memory already allocated.");
|
||||
} else if (data_.reference != nullptr) {
|
||||
throw GitStateError(
|
||||
"Cannot get HEAD commit date, reference memory already allocated.");
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Getting the Git reference for HEAD.");
|
||||
}
|
||||
Call(git_repository_head(&data_.reference, data_.repo));
|
||||
|
||||
auto commit_id = GetCommitId(data_.reference);
|
||||
|
||||
git_reference_free(data_.reference);
|
||||
data_.reference = nullptr;
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Getting commit for ID.");
|
||||
}
|
||||
|
||||
Call(git_commit_lookup(&data_.commit, data_.repo, commit_id));
|
||||
git_time_t time = git_commit_time(data_.commit);
|
||||
|
||||
git_commit_free(data_.commit);
|
||||
data_.commit = nullptr;
|
||||
|
||||
std::ostringstream out;
|
||||
out << std::put_time(std::gmtime(&time), "%Y-%m-%d");
|
||||
return out.str();
|
||||
}
|
||||
|
||||
bool GitHelper::IsFileDifferent(const boost::filesystem::path& repoRoot,
|
||||
const std::string& filename) {
|
||||
|
||||
@@ -34,6 +34,35 @@
|
||||
namespace loot {
|
||||
class GitHelper {
|
||||
public:
|
||||
GitHelper();
|
||||
~GitHelper();
|
||||
|
||||
void InitialiseOptions(const std::string& branch,
|
||||
const std::string& filenameToCheckout);
|
||||
void Open(const boost::filesystem::path& repoRoot);
|
||||
void SetRemoteUrl(const std::string& remote, const std::string& url);
|
||||
|
||||
static bool IsRepository(const boost::filesystem::path& path);
|
||||
static bool IsFileDifferent(const boost::filesystem::path& repoRoot,
|
||||
const std::string& filename);
|
||||
|
||||
void Clone(const boost::filesystem::path& path, const std::string& url);
|
||||
void Fetch(const std::string& remote);
|
||||
|
||||
void CheckoutNewBranch(const std::string& remote, const std::string& branch);
|
||||
void CheckoutRevision(const std::string& revision);
|
||||
|
||||
// Deletes the branch, detaching HEAD if it's currently set to the branch.
|
||||
void DeleteBranch(const std::string& branch);
|
||||
|
||||
bool BranchExists(const std::string& branch);
|
||||
bool IsBranchUpToDate(const std::string& branch);
|
||||
bool IsBranchCheckedOut(const std::string& branch);
|
||||
|
||||
std::string GetHeadCommitId(bool shortId);
|
||||
std::string GetHeadCommitDate();
|
||||
|
||||
private:
|
||||
struct DiffPayload {
|
||||
bool fileFound;
|
||||
const char* fileToFind;
|
||||
@@ -60,32 +89,18 @@ public:
|
||||
git_clone_options clone_options;
|
||||
};
|
||||
|
||||
GitHelper();
|
||||
~GitHelper();
|
||||
|
||||
void Call(int error_code);
|
||||
|
||||
static bool IsRepository(const boost::filesystem::path& path);
|
||||
static bool IsFileDifferent(const boost::filesystem::path& repoRoot,
|
||||
const std::string& filename);
|
||||
static int DiffFileCallback(const git_diff_delta* delta,
|
||||
float progress,
|
||||
void* payload);
|
||||
|
||||
void Clone(const boost::filesystem::path& path, const std::string& url);
|
||||
void Fetch(const std::string& remote);
|
||||
|
||||
void CheckoutNewBranch(const std::string& remote, const std::string& branch);
|
||||
void CheckoutRevision(const std::string& revision);
|
||||
|
||||
std::string GetHeadShortId();
|
||||
GitData& GetData();
|
||||
|
||||
private:
|
||||
// Removes the read-only flag from some files in git repositories
|
||||
// created by libgit2.
|
||||
void FixRepoPermissions(const boost::filesystem::path& path);
|
||||
|
||||
void Call(int error_code);
|
||||
|
||||
const git_oid* GetCommitId(git_reference* reference);
|
||||
|
||||
GitData data_;
|
||||
std::shared_ptr<spdlog::logger> logger_;
|
||||
};
|
||||
|
||||
+42
-242
@@ -24,9 +24,6 @@
|
||||
|
||||
#include "api/masterlist.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "api/game/game.h"
|
||||
@@ -63,42 +60,10 @@ MasterlistInfo Masterlist::GetInfo(const boost::filesystem::path& path,
|
||||
"\" is not a Git repository.");
|
||||
}
|
||||
|
||||
if (logger) {
|
||||
logger->debug("Existing repository found, attempting to open it.");
|
||||
}
|
||||
git.Call(git_repository_open(&git.GetData().repo,
|
||||
path.parent_path().string().c_str()));
|
||||
git.Open(path.parent_path());
|
||||
|
||||
// Need to get the HEAD object, because the individual file has a different
|
||||
// SHA.
|
||||
if (logger) {
|
||||
logger->info("Getting the Git object for the tree at HEAD.");
|
||||
}
|
||||
git.Call(
|
||||
git_revparse_single(&git.GetData().object, git.GetData().repo, "HEAD"));
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Generating hex string for Git object ID.");
|
||||
}
|
||||
if (shortID) {
|
||||
git.Call(git_object_short_id(&git.GetData().buffer, git.GetData().object));
|
||||
info.revision_id = git.GetData().buffer.ptr;
|
||||
} else {
|
||||
char c_rev[GIT_OID_HEXSZ + 1];
|
||||
info.revision_id = git_oid_tostr(
|
||||
c_rev, GIT_OID_HEXSZ + 1, git_object_id(git.GetData().object));
|
||||
}
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Getting date for Git object.");
|
||||
}
|
||||
const git_oid* oid = git_object_id(git.GetData().object);
|
||||
git.Call(git_commit_lookup(&git.GetData().commit, git.GetData().repo, oid));
|
||||
git_time_t time = git_commit_time(git.GetData().commit);
|
||||
|
||||
std::ostringstream out;
|
||||
out << std::put_time(std::gmtime(&time), "%Y-%m-%d");
|
||||
info.revision_date = out.str();
|
||||
info.revision_id = git.GetHeadCommitId(shortID);
|
||||
info.revision_date = git.GetHeadCommitDate();
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Diffing masterlist HEAD and working copy.");
|
||||
@@ -126,26 +91,12 @@ bool Masterlist::IsLatest(const boost::filesystem::path& path,
|
||||
"\" is not a Git repository.");
|
||||
}
|
||||
|
||||
if (logger) {
|
||||
logger->info("Attempting to open repository.");
|
||||
}
|
||||
git.Call(git_repository_open(&git.GetData().repo,
|
||||
path.parent_path().string().c_str()));
|
||||
git.Open(path.parent_path());
|
||||
|
||||
git.Fetch("origin");
|
||||
|
||||
// Get the remote branch's commit ID.
|
||||
git_oid branchOid;
|
||||
git.Call(git_reference_name_to_id(
|
||||
&branchOid,
|
||||
git.GetData().repo,
|
||||
(string("refs/remotes/origin/") + repoBranch).c_str()));
|
||||
|
||||
// Get HEAD's commit ID.
|
||||
git_oid headOid;
|
||||
git.Call(git_reference_name_to_id(&headOid, git.GetData().repo, "HEAD"));
|
||||
|
||||
return memcmp(branchOid.id, headOid.id, 20) == 0;
|
||||
return git.BranchExists(repoBranch) && git.IsBranchUpToDate(repoBranch) &&
|
||||
git.IsBranchCheckedOut(repoBranch);
|
||||
}
|
||||
|
||||
bool Masterlist::Update(const boost::filesystem::path& path,
|
||||
@@ -159,223 +110,72 @@ bool Masterlist::Update(const boost::filesystem::path& path,
|
||||
if (repoUrl.empty() || repoBranch.empty())
|
||||
throw std::invalid_argument("Repository URL and branch must not be empty.");
|
||||
|
||||
// Initialise checkout options.
|
||||
if (logger) {
|
||||
logger->debug("Setting up checkout options.");
|
||||
logger->debug("Using remote URL {} and branch {}", repoUrl, repoBranch);
|
||||
}
|
||||
char* paths = new char[filename.length() + 1];
|
||||
strcpy(paths, filename.c_str());
|
||||
git.GetData().checkout_options.checkout_strategy =
|
||||
GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING;
|
||||
git.GetData().checkout_options.paths.strings = &paths;
|
||||
git.GetData().checkout_options.paths.count = 1;
|
||||
|
||||
// Initialise clone options.
|
||||
git.GetData().clone_options.checkout_opts = git.GetData().checkout_options;
|
||||
git.GetData().clone_options.bare = 0;
|
||||
git.GetData().clone_options.checkout_branch = repoBranch.c_str();
|
||||
git.InitialiseOptions(repoBranch, filename);
|
||||
|
||||
// Now try to access the repository if it exists, or clone one if it doesn't.
|
||||
if (logger) {
|
||||
logger->trace("Attempting to open the Git repository at: {}",
|
||||
repoPath.string());
|
||||
logger->trace("Checking for Git repository at: {}", repoPath.string());
|
||||
}
|
||||
if (!git.IsRepository(repoPath))
|
||||
if (!GitHelper::IsRepository(repoPath)) {
|
||||
git.Clone(repoPath, repoUrl);
|
||||
else {
|
||||
// Repository exists: check settings are correct, then pull updates.
|
||||
} else {
|
||||
git.Open(repoPath);
|
||||
|
||||
// Open the repository.
|
||||
if (logger) {
|
||||
logger->info("Existing repository found, attempting to open it.");
|
||||
}
|
||||
git.Call(
|
||||
git_repository_open(&git.GetData().repo, repoPath.string().c_str()));
|
||||
|
||||
// Set the remote URL.
|
||||
if (logger) {
|
||||
logger->info("Using remote URL: {}", repoUrl);
|
||||
}
|
||||
git.Call(git_remote_set_url(git.GetData().repo, "origin", repoUrl.c_str()));
|
||||
// Set the remote URL. This assumes a single-URL remote called "origin"
|
||||
// exists.
|
||||
git.SetRemoteUrl("origin", repoUrl);
|
||||
|
||||
// Now fetch updates from the remote.
|
||||
git.Fetch("origin");
|
||||
|
||||
// Check that a local branch with the correct name exists.
|
||||
int ret = git_branch_lookup(&git.GetData().reference,
|
||||
git.GetData().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.
|
||||
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.
|
||||
|
||||
// Check if HEAD points to the desired branch and set it to if not.
|
||||
if (!git_branch_is_head(git.GetData().reference)) {
|
||||
if (logger) {
|
||||
logger->trace("Setting HEAD to follow branch: {}", repoBranch);
|
||||
}
|
||||
git.Call(git_repository_set_head(
|
||||
git.GetData().repo, (string("refs/heads/") + repoBranch).c_str()));
|
||||
}
|
||||
|
||||
// Get remote branch reference.
|
||||
git.Call(git_branch_upstream(&git.GetData().reference2,
|
||||
git.GetData().reference));
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Checking HEAD and remote branch's mergeability.");
|
||||
}
|
||||
git_merge_analysis_t analysis;
|
||||
git_merge_preference_t pref;
|
||||
git.Call(git_annotated_commit_from_ref(&git.GetData().annotated_commit,
|
||||
git.GetData().repo,
|
||||
git.GetData().reference2));
|
||||
git.Call(git_merge_analysis(
|
||||
&analysis,
|
||||
&pref,
|
||||
git.GetData().repo,
|
||||
(const git_annotated_commit**)&git.GetData().annotated_commit,
|
||||
1));
|
||||
|
||||
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.
|
||||
if (logger) {
|
||||
logger->trace(
|
||||
"Local branch cannot be easily merged with remote branch.");
|
||||
}
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Detaching HEAD so that the branch can be recreated.");
|
||||
}
|
||||
git.Call(git_repository_detach_head(git.GetData().repo));
|
||||
|
||||
// Need to free ref before calling git.CheckoutNewBranch()
|
||||
git_reference_free(git.GetData().reference);
|
||||
git.GetData().reference = nullptr;
|
||||
git_reference_free(git.GetData().reference2);
|
||||
git.GetData().reference2 = nullptr;
|
||||
|
||||
git.CheckoutNewBranch("origin", repoBranch);
|
||||
} else {
|
||||
// Get remote branch commit ID.
|
||||
git.Call(git_reference_peel(
|
||||
&git.GetData().object, git.GetData().reference2, GIT_OBJ_COMMIT));
|
||||
const git_oid* remote_commit_id = git_object_id(git.GetData().object);
|
||||
|
||||
git_object_free(git.GetData().object);
|
||||
git.GetData().object = nullptr;
|
||||
git_reference_free(git.GetData().reference2);
|
||||
git.GetData().reference2 = nullptr;
|
||||
|
||||
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.
|
||||
if (logger) {
|
||||
logger->trace(
|
||||
"Local branch is up-to-date with remote branch. Checking to "
|
||||
"see if local and remote branch heads are equal.");
|
||||
}
|
||||
|
||||
// Get local branch commit ID.
|
||||
git.Call(git_reference_peel(
|
||||
&git.GetData().object, git.GetData().reference, GIT_OBJ_COMMIT));
|
||||
const git_oid* local_commit_id = git_object_id(git.GetData().object);
|
||||
|
||||
git_object_free(git.GetData().object);
|
||||
git.GetData().object = 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.
|
||||
if (!updateBranchHead) {
|
||||
if (logger) {
|
||||
logger->trace("Local and remote branch heads are equal.");
|
||||
}
|
||||
if (!GitHelper::IsFileDifferent(repoPath, filename)) {
|
||||
if (logger) {
|
||||
logger->info(
|
||||
"Local branch and masterlist file are already up to date.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else if (logger) {
|
||||
logger->trace("Local branch heads is ahead of remote branch head.");
|
||||
}
|
||||
} else if (logger) {
|
||||
logger->trace("Local branch can be fast-forwarded to remote branch.");
|
||||
}
|
||||
|
||||
if (updateBranchHead) {
|
||||
// The remote branch reference points to a particular
|
||||
// commit. Update the local branch reference to point
|
||||
// to the same commit.
|
||||
if (logger) {
|
||||
logger->trace("Syncing local branch head with remote branch head.");
|
||||
}
|
||||
git.Call(git_reference_set_target(&git.GetData().reference2,
|
||||
git.GetData().reference,
|
||||
remote_commit_id,
|
||||
"Setting branch reference."));
|
||||
|
||||
git_reference_free(git.GetData().reference2);
|
||||
git.GetData().reference2 = nullptr;
|
||||
}
|
||||
|
||||
git_reference_free(git.GetData().reference);
|
||||
git.GetData().reference = nullptr;
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Performing a Git checkout of HEAD.");
|
||||
}
|
||||
git.Call(git_checkout_head(git.GetData().repo,
|
||||
&git.GetData().checkout_options));
|
||||
}
|
||||
if (logger) {
|
||||
logger->debug(
|
||||
"Checking if branch {} is up to date and checked out without edits",
|
||||
repoBranch);
|
||||
}
|
||||
|
||||
if (git.BranchExists(repoBranch)) {
|
||||
if (git.IsBranchUpToDate(repoBranch) &&
|
||||
git.IsBranchCheckedOut(repoBranch) &&
|
||||
!GitHelper::IsFileDifferent(repoPath, filename)) {
|
||||
if (logger) {
|
||||
logger->info(
|
||||
"Local branch and masterlist file are already up to date.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
git.DeleteBranch(repoBranch);
|
||||
}
|
||||
|
||||
// No local branch exists, create and checkout a new one from the remote.
|
||||
git.CheckoutNewBranch("origin", repoBranch);
|
||||
}
|
||||
|
||||
// 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;
|
||||
do {
|
||||
// Get the HEAD revision's short ID.
|
||||
string revision = git.GetHeadShortId();
|
||||
|
||||
// Now try parsing the masterlist.
|
||||
if (logger) {
|
||||
logger->debug("Testing masterlist parsing.");
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
this->Load(path);
|
||||
|
||||
parsingFailed = false;
|
||||
return true;
|
||||
} catch (std::exception& e) {
|
||||
parsingFailed = true;
|
||||
|
||||
// There was an error, roll back one revision.
|
||||
if (logger) {
|
||||
logger->error("Masterlist parsing failed. Masterlist revision {}: {}",
|
||||
revision,
|
||||
git.GetHeadCommitId(true),
|
||||
e.what());
|
||||
}
|
||||
git.CheckoutRevision("HEAD^");
|
||||
}
|
||||
} while (parsingFailed);
|
||||
}
|
||||
|
||||
// This should never be reached as git.CheckoutRevision() will throw if it
|
||||
// tries to go one back from the start of history.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +82,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(GitHelperTest, repoShouldInitialiseAsANullPointer) {
|
||||
EXPECT_EQ(nullptr, git_.GetData().repo);
|
||||
}
|
||||
|
||||
TEST_F(GitHelperTest, destructorShouldCallLibgit2CleanupFunction) {
|
||||
ASSERT_EQ(2, git_libgit2_init());
|
||||
|
||||
@@ -96,15 +92,6 @@ TEST_F(GitHelperTest, destructorShouldCallLibgit2CleanupFunction) {
|
||||
EXPECT_EQ(2, git_libgit2_shutdown());
|
||||
}
|
||||
|
||||
TEST_F(GitHelperTest, callShouldNotThrowIfPassedAZeroValue) {
|
||||
EXPECT_NO_THROW(git_.Call(0));
|
||||
}
|
||||
|
||||
TEST_F(GitHelperTest, callShouldThrowIfPassedANonZeroValue) {
|
||||
EXPECT_THROW(git_.Call(1), std::system_error);
|
||||
EXPECT_THROW(git_.Call(-1), std::system_error);
|
||||
}
|
||||
|
||||
TEST_F(GitHelperTest, isRepositoryShouldReturnTrueForARepositoryRoot) {
|
||||
EXPECT_TRUE(GitHelper::IsRepository(parentRepoRoot));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user