libgit2/src/cherrypick.c
Patrick Steinhardt 84f56cb05a repository: rename path_repository and path_gitlink
The `path_repository` variable is actually confusing to think
about, as it is not always clear what the repository actually is.
It may either be the path to the folder containing worktree and
.git directory, the path to .git itself, a worktree or something
entirely different. Actually, the intent of the variable is to
hold the path to the gitdir, which is either the .git directory
or the bare repository.

Rename the variable to `gitdir` to avoid confusion. While at it,
also rename `path_gitlink` to `gitlink` to improve consistency.
2017-02-13 11:07:52 +01:00

230 lines
6.4 KiB
C

/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "repository.h"
#include "filebuf.h"
#include "merge.h"
#include "vector.h"
#include "index.h"
#include "git2/types.h"
#include "git2/merge.h"
#include "git2/cherrypick.h"
#include "git2/commit.h"
#include "git2/sys/commit.h"
#define GIT_CHERRYPICK_FILE_MODE 0666
static int write_cherrypick_head(
git_repository *repo,
const char *commit_oidstr)
{
git_filebuf file = GIT_FILEBUF_INIT;
git_buf file_path = GIT_BUF_INIT;
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file);
if (error < 0)
git_filebuf_cleanup(&file);
git_buf_free(&file_path);
return error;
}
static int write_merge_msg(
git_repository *repo,
const char *commit_msg)
{
git_filebuf file = GIT_FILEBUF_INIT;
git_buf file_path = GIT_BUF_INIT;
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
(error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
goto cleanup;
error = git_filebuf_commit(&file);
cleanup:
if (error < 0)
git_filebuf_cleanup(&file);
git_buf_free(&file_path);
return error;
}
static int cherrypick_normalize_opts(
git_repository *repo,
git_cherrypick_options *opts,
const git_cherrypick_options *given,
const char *their_label)
{
int error = 0;
unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
GIT_CHECKOUT_ALLOW_CONFLICTS;
GIT_UNUSED(repo);
if (given != NULL)
memcpy(opts, given, sizeof(git_cherrypick_options));
else {
git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT;
memcpy(opts, &default_opts, sizeof(git_cherrypick_options));
}
if (!opts->checkout_opts.checkout_strategy)
opts->checkout_opts.checkout_strategy = default_checkout_strategy;
if (!opts->checkout_opts.our_label)
opts->checkout_opts.our_label = "HEAD";
if (!opts->checkout_opts.their_label)
opts->checkout_opts.their_label = their_label;
return error;
}
static int cherrypick_state_cleanup(git_repository *repo)
{
const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE };
return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
}
static int cherrypick_seterr(git_commit *commit, const char *fmt)
{
char commit_oidstr[GIT_OID_HEXSZ + 1];
giterr_set(GITERR_CHERRYPICK, fmt,
git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
return -1;
}
int git_cherrypick_commit(
git_index **out,
git_repository *repo,
git_commit *cherrypick_commit,
git_commit *our_commit,
unsigned int mainline,
const git_merge_options *merge_opts)
{
git_commit *parent_commit = NULL;
git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL;
int parent = 0, error = 0;
assert(out && repo && cherrypick_commit && our_commit);
if (git_commit_parentcount(cherrypick_commit) > 1) {
if (!mainline)
return cherrypick_seterr(cherrypick_commit,
"mainline branch is not specified but %s is a merge commit");
parent = mainline;
} else {
if (mainline)
return cherrypick_seterr(cherrypick_commit,
"mainline branch specified but %s is not a merge commit");
parent = git_commit_parentcount(cherrypick_commit);
}
if (parent &&
((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 ||
(error = git_commit_tree(&parent_tree, parent_commit)) < 0))
goto done;
if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 ||
(error = git_commit_tree(&our_tree, our_commit)) < 0)
goto done;
error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts);
done:
git_tree_free(parent_tree);
git_tree_free(our_tree);
git_tree_free(cherrypick_tree);
git_commit_free(parent_commit);
return error;
}
int git_cherrypick(
git_repository *repo,
git_commit *commit,
const git_cherrypick_options *given_opts)
{
git_cherrypick_options opts;
git_reference *our_ref = NULL;
git_commit *our_commit = NULL;
char commit_oidstr[GIT_OID_HEXSZ + 1];
const char *commit_msg, *commit_summary;
git_buf their_label = GIT_BUF_INIT;
git_index *index = NULL;
git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
int error = 0;
assert(repo && commit);
GITERR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options");
if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0)
return error;
if ((commit_msg = git_commit_message(commit)) == NULL ||
(commit_summary = git_commit_summary(commit)) == NULL) {
error = -1;
goto on_error;
}
git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit));
if ((error = write_merge_msg(repo, commit_msg)) < 0 ||
(error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 ||
(error = cherrypick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
(error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 ||
(error = write_cherrypick_head(repo, commit_oidstr)) < 0 ||
(error = git_repository_head(&our_ref, repo)) < 0 ||
(error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
(error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
(error = git_merge__check_result(repo, index)) < 0 ||
(error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 ||
(error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 ||
(error = git_indexwriter_commit(&indexwriter)) < 0)
goto on_error;
goto done;
on_error:
cherrypick_state_cleanup(repo);
done:
git_indexwriter_cleanup(&indexwriter);
git_index_free(index);
git_commit_free(our_commit);
git_reference_free(our_ref);
git_buf_free(&their_label);
return error;
}
int git_cherrypick_init_options(
git_cherrypick_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT);
return 0;
}