[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/QuantStack/git2cpp/0.0.12/src/subcommand/merge_subcommand.cpp [Back]  [Original]

#include "merge_subcommand.hpp"

#include 
#include 

#include 
#include 
#include 

#include "../utils/input_output.hpp"
#include "../wrapper/status_wrapper.hpp"

merge_subcommand::merge_subcommand(const libgit2_object&, CLI::App& app)
{
    auto* sub = app.add_subcommand("merge", "Join two or more development histories together");

    sub->add_option("", m_branches_to_merge, "Branch(es) to merge");
    // sub->add_flag("--no-ff", m_no_ff, "Create a merge commit in all cases, even when the merge could
    // instead be resolved as a fast-forward."); sub->add_flag("--commit", m_commit, "Perform the merge and
    // commit the result. This option can be used to override --no-commit.");
    sub->add_flag(
        "--no-commit",
        m_no_commit,
        "With --no-commit perform the merge and stop just before creating a merge commit, to give the user a chance to inspect and further tweak the merge result before committing. \nNote that fast-forward updates do not create a merge commit and therefore there is no way to stop those merges with --no-commit. Thus, if you want to ensure your branch is not changed or updated by the merge command, use --no-ff with --no-commit."
    );
    sub->add_flag(
        "--abort",
        m_abort,
        "Abort the current conflict resolution process, and try to reconstruct the pre-merge state. If an autostash entry is present, apply it to the worktree.\nIf there were uncommitted worktree changes present when the merge started, git merge --abort will in some cases be unable to reconstruct these changes. It is therefore recommended to always commit or stash your changes before running git merge.\ngit merge --abort is equivalent to git reset --merge when MERGE_HEAD is present unless MERGE_AUTOSTASH is also present in which case git merge --abort applies the stash entry to the worktree whereas git reset --merge will save the stashed changes in the stash list."
    );
    sub->add_flag(
        "--quit",
        m_quit,
        "Forget about the current merge in progress. Leave the index and the working tree as-is. If MERGE_AUTOSTASH is present, the stash entry will be saved to the stash list."
    );
    sub->add_flag(
        "--continue",
        m_continue,
        "After a git merge stops due to conflicts you can conclude the merge by running git merge --continue"
    );  //  (see "HOW TO RESOLVE CONFLICTS" section below).

    sub->callback(
        [this]()
        {
            this->run();
        }
    );
}

namespace
{
    annotated_commit_list_wrapper
    resolve_mergeheads(const repository_wrapper& repo, const std::vector& oid_list)
    {
        std::vector commits_to_merge;
        commits_to_merge.reserve(oid_list.size());

        for (const auto& id : oid_list)
        {
            std::optional commit = repo.find_annotated_commit(id);
            if (commit.has_value())
            {
                commits_to_merge.push_back(std::move(commit).value());
            }
        }
        return annotated_commit_list_wrapper(std::move(commits_to_merge));
    }

    void perform_fastforward(repository_wrapper& repo, const git_oid& target_oid, int is_unborn)
    {
        const git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT;

        auto lambda_get_target_ref = [](auto repo, auto is_unborn)
        {
            if (!is_unborn)
            {
                return repo->head();
            }
            else
            {
                return repo->find_reference("HEAD");
            }
        };
        reference_wrapper target_ref = lambda_get_target_ref(&repo, is_unborn);

        object_wrapper target = repo.find_object(target_oid, GIT_OBJECT_COMMIT);

        repo.checkout_tree(target, ff_checkout_options);

        target_ref.write_new_ref(target_oid);
    }

    // This function is used as a callback in git_repository_mergehead_foreach and therefore its type must be
    // git_repository_mergehead_foreach_cb.
    int populate_list(const git_oid* oid, void* payload)
    {
        auto* l = reinterpret_cast(payload);
        l->push_back(*oid);
        return 0;
    }
}

annotated_commit_list_wrapper merge_subcommand::resolve_heads(const repository_wrapper& repo)
{
    std::vector commits_to_merge;
    commits_to_merge.reserve(m_branches_to_merge.size());

    for (const auto branch_name : m_branches_to_merge)
    {
        std::optional commit = repo.resolve_local_ref(branch_name);
        if (commit.has_value())
        {
            commits_to_merge.push_back(std::move(commit).value());
        }
    }
    return annotated_commit_list_wrapper(std::move(commits_to_merge));
}

void merge_subcommand::create_merge_commit(
    repository_wrapper& repo,
    const index_wrapper& index,
    const std::vector& branches_to_merge,
    const annotated_commit_list_wrapper& commits_to_merge,
    size_t num_commits_to_merge
)
{
    auto head_ref = repo.head();
    auto merge_ref = repo.find_reference_dwim(branches_to_merge.front());
    auto merge_commit = repo.resolve_local_ref(branches_to_merge.front()).value();

    std::vector parents_list;
    parents_list.reserve(num_commits_to_merge + 1);
    parents_list.push_back(std::move(head_ref.peel()));
    for (size_t i = 0; i < num_commits_to_merge; ++i)
    {
        parents_list.push_back(repo.find_commit(commits_to_merge[i].oid()));
    }
    auto parents = commit_list_wrapper(std::move(parents_list));

    auto author_committer_sign = signature_wrapper::get_default_signature_from_env(repo);
    std::string author_name;
    author_name = author_committer_sign.first.name();
    std::string author_email;
    author_email = author_committer_sign.first.email();
    auto author_committer_sign_now = signature_wrapper::signature_now(
        author_name,
        author_email,
        author_name,
        author_email
    );

    // TODO: add a prompt to edit the merge message
    std::string msg_target = merge_ref ? merge_ref->short_name() : git_oid_tostr_s(&(merge_commit.oid()));
    msg_target = "\'" + msg_target + "\'";
    std::string msg = merge_ref ? "Merge branch " : "Merge commit ";
    msg.append(msg_target);

    repo.create_commit(author_committer_sign_now, msg, std::optional(std::move(parents)));

    repo.state_cleanup();
}

void merge_subcommand::run()
{
    auto directory = get_current_git_path();
    auto repo = repository_wrapper::open(directory);
    auto state = repo.state();
    index_wrapper index = repo.make_index();
    stream_colour_fn yellow = termcolor::yellow;

    if (state == GIT_REPOSITORY_STATE_MERGE)
    {
        if (m_abort)
        {
            // git merge --abort is equivalent to git reset --merge when MERGE_HEAD is present
            // unless MERGE_AUTOSTASH is also present in which case git merge --abort applies
            // the stash entry to the worktree whereas git reset --merge will save the stashed
            // changes in the stash list.

            if (m_quit | m_continue)
            {
                std::cout 

Web Proxy Viewer  |  New URL  |  Original Page