diff options
Diffstat (limited to 'src/rebase.c')
-rw-r--r-- | src/rebase.c | 1469 |
1 files changed, 0 insertions, 1469 deletions
diff --git a/src/rebase.c b/src/rebase.c deleted file mode 100644 index 4f10c296b..000000000 --- a/src/rebase.c +++ /dev/null @@ -1,1469 +0,0 @@ -/* - * 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 "buffer.h" -#include "repository.h" -#include "posix.h" -#include "filebuf.h" -#include "merge.h" -#include "array.h" -#include "config.h" -#include "annotated_commit.h" -#include "index.h" - -#include <git2/types.h> -#include <git2/annotated_commit.h> -#include <git2/rebase.h> -#include <git2/commit.h> -#include <git2/reset.h> -#include <git2/revwalk.h> -#include <git2/notes.h> - -#define REBASE_APPLY_DIR "rebase-apply" -#define REBASE_MERGE_DIR "rebase-merge" - -#define HEAD_NAME_FILE "head-name" -#define ORIG_HEAD_FILE "orig-head" -#define HEAD_FILE "head" -#define ONTO_FILE "onto" -#define ONTO_NAME_FILE "onto_name" -#define QUIET_FILE "quiet" - -#define MSGNUM_FILE "msgnum" -#define END_FILE "end" -#define CMT_FILE_FMT "cmt.%" PRIuZ -#define CURRENT_FILE "current" -#define REWRITTEN_FILE "rewritten" - -#define ORIG_DETACHED_HEAD "detached HEAD" - -#define NOTES_DEFAULT_REF NULL - -#define REBASE_DIR_MODE 0777 -#define REBASE_FILE_MODE 0666 - -typedef enum { - GIT_REBASE_NONE = 0, - GIT_REBASE_APPLY = 1, - GIT_REBASE_MERGE = 2, - GIT_REBASE_INTERACTIVE = 3, -} git_rebase_t; - -struct git_rebase { - git_repository *repo; - - git_rebase_options options; - - git_rebase_t type; - char *state_path; - - int head_detached : 1, - inmemory : 1, - quiet : 1, - started : 1; - - git_array_t(git_rebase_operation) operations; - size_t current; - - /* Used by in-memory rebase */ - git_index *index; - git_commit *last_commit; - - /* Used by regular (not in-memory) merge-style rebase */ - git_oid orig_head_id; - char *orig_head_name; - - git_oid onto_id; - char *onto_name; -}; - -#define GIT_REBASE_STATE_INIT {0} - -static int rebase_state_type( - git_rebase_t *type_out, - char **path_out, - git_repository *repo) -{ - git_buf path = GIT_BUF_INIT; - git_rebase_t type = GIT_REBASE_NONE; - - if (git_buf_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) - return -1; - - if (git_path_isdir(git_buf_cstr(&path))) { - type = GIT_REBASE_APPLY; - goto done; - } - - git_buf_clear(&path); - if (git_buf_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0) - return -1; - - if (git_path_isdir(git_buf_cstr(&path))) { - type = GIT_REBASE_MERGE; - goto done; - } - -done: - *type_out = type; - - if (type != GIT_REBASE_NONE && path_out) - *path_out = git_buf_detach(&path); - - git_buf_dispose(&path); - - return 0; -} - -GIT_INLINE(int) rebase_readfile( - git_buf *out, - git_buf *state_path, - const char *filename) -{ - size_t state_path_len = state_path->size; - int error; - - git_buf_clear(out); - - if ((error = git_buf_joinpath(state_path, state_path->ptr, filename)) < 0 || - (error = git_futils_readbuffer(out, state_path->ptr)) < 0) - goto done; - - git_buf_rtrim(out); - -done: - git_buf_truncate(state_path, state_path_len); - return error; -} - -GIT_INLINE(int) rebase_readint( - size_t *out, git_buf *asc_out, git_buf *state_path, const char *filename) -{ - int32_t num; - const char *eol; - int error = 0; - - if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) - return error; - - if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { - git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid numeric value", filename); - return -1; - } - - *out = (size_t) num; - - return 0; -} - -GIT_INLINE(int) rebase_readoid( - git_oid *out, git_buf *str_out, git_buf *state_path, const char *filename) -{ - int error; - - if ((error = rebase_readfile(str_out, state_path, filename)) < 0) - return error; - - if (str_out->size != GIT_OID_HEXSZ || git_oid_fromstr(out, str_out->ptr) < 0) { - git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid object ID", filename); - return -1; - } - - return 0; -} - -static git_rebase_operation *rebase_operation_alloc( - git_rebase *rebase, - git_rebase_operation_t type, - git_oid *id, - const char *exec) -{ - git_rebase_operation *operation; - - GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !id, NULL); - GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !!exec, NULL); - - if ((operation = git_array_alloc(rebase->operations)) == NULL) - return NULL; - - operation->type = type; - git_oid_cpy((git_oid *)&operation->id, id); - operation->exec = exec; - - return operation; -} - -static int rebase_open_merge(git_rebase *rebase) -{ - git_buf state_path = GIT_BUF_INIT, buf = GIT_BUF_INIT, cmt = GIT_BUF_INIT; - git_oid id; - git_rebase_operation *operation; - size_t i, msgnum = 0, end; - int error; - - if ((error = git_buf_puts(&state_path, rebase->state_path)) < 0) - goto done; - - /* Read 'msgnum' if it exists (otherwise, let msgnum = 0) */ - if ((error = rebase_readint(&msgnum, &buf, &state_path, MSGNUM_FILE)) < 0 && - error != GIT_ENOTFOUND) - goto done; - - if (msgnum) { - rebase->started = 1; - rebase->current = msgnum - 1; - } - - /* Read 'end' */ - if ((error = rebase_readint(&end, &buf, &state_path, END_FILE)) < 0) - goto done; - - /* Read 'current' if it exists */ - if ((error = rebase_readoid(&id, &buf, &state_path, CURRENT_FILE)) < 0 && - error != GIT_ENOTFOUND) - goto done; - - /* Read cmt.* */ - git_array_init_to_size(rebase->operations, end); - GIT_ERROR_CHECK_ARRAY(rebase->operations); - - for (i = 0; i < end; i++) { - git_buf_clear(&cmt); - - if ((error = git_buf_printf(&cmt, "cmt.%" PRIuZ, (i+1))) < 0 || - (error = rebase_readoid(&id, &buf, &state_path, cmt.ptr)) < 0) - goto done; - - operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); - GIT_ERROR_CHECK_ALLOC(operation); - } - - /* Read 'onto_name' */ - if ((error = rebase_readfile(&buf, &state_path, ONTO_NAME_FILE)) < 0) - goto done; - - rebase->onto_name = git_buf_detach(&buf); - -done: - git_buf_dispose(&cmt); - git_buf_dispose(&state_path); - git_buf_dispose(&buf); - - return error; -} - -static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) -{ - git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); - GIT_ERROR_CHECK_ALLOC(rebase); - - *out = NULL; - - if (rebase_opts) - memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); - else - git_rebase_options_init(&rebase->options, GIT_REBASE_OPTIONS_VERSION); - - if (rebase_opts && rebase_opts->rewrite_notes_ref) { - rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref); - GIT_ERROR_CHECK_ALLOC(rebase->options.rewrite_notes_ref); - } - - *out = rebase; - - return 0; -} - -static int rebase_check_versions(const git_rebase_options *given_opts) -{ - GIT_ERROR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); - - if (given_opts) - GIT_ERROR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); - - return 0; -} - -int git_rebase_open( - git_rebase **out, - git_repository *repo, - const git_rebase_options *given_opts) -{ - git_rebase *rebase; - git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT, - orig_head_id = GIT_BUF_INIT, onto_id = GIT_BUF_INIT; - size_t state_path_len; - int error; - - GIT_ASSERT_ARG(repo); - - if ((error = rebase_check_versions(given_opts)) < 0) - return error; - - if (rebase_alloc(&rebase, given_opts) < 0) - return -1; - - rebase->repo = repo; - - if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0) - goto done; - - if (rebase->type == GIT_REBASE_NONE) { - git_error_set(GIT_ERROR_REBASE, "there is no rebase in progress"); - error = GIT_ENOTFOUND; - goto done; - } - - if ((error = git_buf_puts(&path, rebase->state_path)) < 0) - goto done; - - state_path_len = git_buf_len(&path); - - if ((error = git_buf_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 || - (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0) - goto done; - - git_buf_rtrim(&orig_head_name); - - if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0) - rebase->head_detached = 1; - - git_buf_truncate(&path, state_path_len); - - if ((error = git_buf_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0) - goto done; - - if (!git_path_isfile(path.ptr)) { - /* Previous versions of git.git used 'head' here; support that. */ - git_buf_truncate(&path, state_path_len); - - if ((error = git_buf_joinpath(&path, path.ptr, HEAD_FILE)) < 0) - goto done; - } - - if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0) - goto done; - - git_buf_rtrim(&orig_head_id); - - if ((error = git_oid_fromstr(&rebase->orig_head_id, orig_head_id.ptr)) < 0) - goto done; - - git_buf_truncate(&path, state_path_len); - - if ((error = git_buf_joinpath(&path, path.ptr, ONTO_FILE)) < 0 || - (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0) - goto done; - - git_buf_rtrim(&onto_id); - - if ((error = git_oid_fromstr(&rebase->onto_id, onto_id.ptr)) < 0) - goto done; - - if (!rebase->head_detached) - rebase->orig_head_name = git_buf_detach(&orig_head_name); - - switch (rebase->type) { - case GIT_REBASE_INTERACTIVE: - git_error_set(GIT_ERROR_REBASE, "interactive rebase is not supported"); - error = -1; - break; - case GIT_REBASE_MERGE: - error = rebase_open_merge(rebase); - break; - case GIT_REBASE_APPLY: - git_error_set(GIT_ERROR_REBASE, "patch application rebase is not supported"); - error = -1; - break; - default: - abort(); - } - -done: - if (error == 0) - *out = rebase; - else - git_rebase_free(rebase); - - git_buf_dispose(&path); - git_buf_dispose(&orig_head_name); - git_buf_dispose(&orig_head_id); - git_buf_dispose(&onto_id); - return error; -} - -static int rebase_cleanup(git_rebase *rebase) -{ - if (!rebase || rebase->inmemory) - return 0; - - return git_path_isdir(rebase->state_path) ? - git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) : - 0; -} - -static int rebase_setupfile(git_rebase *rebase, const char *filename, int flags, const char *fmt, ...) -{ - git_buf path = GIT_BUF_INIT, - contents = GIT_BUF_INIT; - va_list ap; - int error; - - va_start(ap, fmt); - git_buf_vprintf(&contents, fmt, ap); - va_end(ap); - - if ((error = git_buf_joinpath(&path, rebase->state_path, filename)) == 0) - error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE); - - git_buf_dispose(&path); - git_buf_dispose(&contents); - - return error; -} - -static const char *rebase_onto_name(const git_annotated_commit *onto) -{ - if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0) - return onto->ref_name + 11; - else if (onto->ref_name) - return onto->ref_name; - else - return onto->id_str; -} - -static int rebase_setupfiles_merge(git_rebase *rebase) -{ - git_buf commit_filename = GIT_BUF_INIT; - char id_str[GIT_OID_HEXSZ]; - git_rebase_operation *operation; - size_t i; - int error = 0; - - if ((error = rebase_setupfile(rebase, END_FILE, 0, "%" PRIuZ "\n", git_array_size(rebase->operations))) < 0 || - (error = rebase_setupfile(rebase, ONTO_NAME_FILE, 0, "%s\n", rebase->onto_name)) < 0) - goto done; - - for (i = 0; i < git_array_size(rebase->operations); i++) { - operation = git_array_get(rebase->operations, i); - - git_buf_clear(&commit_filename); - git_buf_printf(&commit_filename, CMT_FILE_FMT, i+1); - - git_oid_fmt(id_str, &operation->id); - - if ((error = rebase_setupfile(rebase, commit_filename.ptr, 0, - "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0) - goto done; - } - -done: - git_buf_dispose(&commit_filename); - return error; -} - -static int rebase_setupfiles(git_rebase *rebase) -{ - char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ]; - const char *orig_head_name; - - git_oid_fmt(onto, &rebase->onto_id); - git_oid_fmt(orig_head, &rebase->orig_head_id); - - if (p_mkdir(rebase->state_path, REBASE_DIR_MODE) < 0) { - git_error_set(GIT_ERROR_OS, "failed to create rebase directory '%s'", rebase->state_path); - return -1; - } - - orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD : - rebase->orig_head_name; - - if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 || - rebase_setupfile(rebase, HEAD_NAME_FILE, 0, "%s\n", orig_head_name) < 0 || - rebase_setupfile(rebase, ONTO_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 || - rebase_setupfile(rebase, ORIG_HEAD_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 || - rebase_setupfile(rebase, QUIET_FILE, 0, rebase->quiet ? "t\n" : "\n") < 0) - return -1; - - return rebase_setupfiles_merge(rebase); -} - -int git_rebase_options_init(git_rebase_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_rebase_options, GIT_REBASE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_rebase_init_options(git_rebase_options *opts, unsigned int version) -{ - return git_rebase_options_init(opts, version); -} -#endif - -static int rebase_ensure_not_in_progress(git_repository *repo) -{ - int error; - git_rebase_t type; - - if ((error = rebase_state_type(&type, NULL, repo)) < 0) - return error; - - if (type != GIT_REBASE_NONE) { - git_error_set(GIT_ERROR_REBASE, "there is an existing rebase in progress"); - return -1; - } - - return 0; -} - -static int rebase_ensure_not_dirty( - git_repository *repo, - bool check_index, - bool check_workdir, - int fail_with) -{ - git_tree *head = NULL; - git_index *index = NULL; - git_diff *diff = NULL; - int error = 0; - - if (check_index) { - if ((error = git_repository_head_tree(&head, repo)) < 0 || - (error = git_repository_index(&index, repo)) < 0 || - (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - git_error_set(GIT_ERROR_REBASE, "uncommitted changes exist in index"); - error = fail_with; - goto done; - } - - git_diff_free(diff); - diff = NULL; - } - - if (check_workdir) { - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.ignore_submodules = GIT_SUBMODULE_IGNORE_UNTRACKED; - if ((error = git_diff_index_to_workdir(&diff, repo, index, &diff_opts)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - git_error_set(GIT_ERROR_REBASE, "unstaged changes exist in workdir"); - error = fail_with; - goto done; - } - } - -done: - git_diff_free(diff); - git_index_free(index); - git_tree_free(head); - - return error; -} - -static int rebase_init_operations( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - git_revwalk *revwalk = NULL; - git_commit *commit; - git_oid id; - bool merge; - git_rebase_operation *operation; - int error; - - if (!upstream) - upstream = onto; - - if ((error = git_revwalk_new(&revwalk, rebase->repo)) < 0 || - (error = git_revwalk_push(revwalk, git_annotated_commit_id(branch))) < 0 || - (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0) - goto done; - - git_revwalk_sorting(revwalk, GIT_SORT_REVERSE); - - while ((error = git_revwalk_next(&id, revwalk)) == 0) { - if ((error = git_commit_lookup(&commit, repo, &id)) < 0) - goto done; - - merge = (git_commit_parentcount(commit) > 1); - git_commit_free(commit); - - if (merge) - continue; - - operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); - GIT_ERROR_CHECK_ALLOC(operation); - } - - error = 0; - -done: - git_revwalk_free(revwalk); - return error; -} - -static int rebase_init_merge( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - git_reference *head_ref = NULL; - git_commit *onto_commit = NULL; - git_buf reflog = GIT_BUF_INIT; - git_buf state_path = GIT_BUF_INIT; - int error; - - GIT_UNUSED(upstream); - - if ((error = git_buf_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0) - goto done; - - rebase->state_path = git_buf_detach(&state_path); - GIT_ERROR_CHECK_ALLOC(rebase->state_path); - - if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) { - rebase->orig_head_name = git__strdup(branch->ref_name); - GIT_ERROR_CHECK_ALLOC(rebase->orig_head_name); - } else { - rebase->head_detached = 1; - } - - rebase->onto_name = git__strdup(rebase_onto_name(onto)); - GIT_ERROR_CHECK_ALLOC(rebase->onto_name); - - rebase->quiet = rebase->options.quiet; - - git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); - git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); - - if ((error = rebase_setupfiles(rebase)) < 0 || - (error = git_buf_printf(&reflog, - "rebase: checkout %s", rebase_onto_name(onto))) < 0 || - (error = git_commit_lookup( - &onto_commit, repo, git_annotated_commit_id(onto))) < 0 || - (error = git_checkout_tree(repo, - (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || - (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, - git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) - goto done; - -done: - git_reference_free(head_ref); - git_commit_free(onto_commit); - git_buf_dispose(&reflog); - git_buf_dispose(&state_path); - - return error; -} - -static int rebase_init_inmemory( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - GIT_UNUSED(branch); - GIT_UNUSED(upstream); - - return git_commit_lookup( - &rebase->last_commit, repo, git_annotated_commit_id(onto)); -} - -int git_rebase_init( - git_rebase **out, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto, - const git_rebase_options *given_opts) -{ - git_rebase *rebase = NULL; - git_annotated_commit *head_branch = NULL; - git_reference *head_ref = NULL; - bool inmemory = (given_opts && given_opts->inmemory); - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(upstream || onto); - - *out = NULL; - - if (!onto) - onto = upstream; - - if ((error = rebase_check_versions(given_opts)) < 0) - goto done; - - if (!inmemory) { - if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || - (error = rebase_ensure_not_in_progress(repo)) < 0 || - (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0) - goto done; - } - - if (!branch) { - if ((error = git_repository_head(&head_ref, repo)) < 0 || - (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) - goto done; - - branch = head_branch; - } - - if (rebase_alloc(&rebase, given_opts) < 0) - return -1; - - rebase->repo = repo; - rebase->inmemory = inmemory; - rebase->type = GIT_REBASE_MERGE; - - if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) - goto done; - - if (inmemory) - error = rebase_init_inmemory(rebase, repo, branch, upstream, onto); - else - error = rebase_init_merge(rebase, repo, branch ,upstream, onto); - - if (error == 0) - *out = rebase; - -done: - git_reference_free(head_ref); - git_annotated_commit_free(head_branch); - - if (error < 0) { - rebase_cleanup(rebase); - git_rebase_free(rebase); - } - - return error; -} - -static void normalize_checkout_options_for_apply( - git_checkout_options *checkout_opts, - git_rebase *rebase, - git_commit *current_commit) -{ - memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options)); - - if (!checkout_opts->ancestor_label) - checkout_opts->ancestor_label = "ancestor"; - - if (rebase->type == GIT_REBASE_MERGE) { - if (!checkout_opts->our_label) - checkout_opts->our_label = rebase->onto_name; - - if (!checkout_opts->their_label) - checkout_opts->their_label = git_commit_summary(current_commit); - } else { - abort(); - } -} - -GIT_INLINE(int) rebase_movenext(git_rebase *rebase) -{ - size_t next = rebase->started ? rebase->current + 1 : 0; - - if (next == git_array_size(rebase->operations)) - return GIT_ITEROVER; - - rebase->started = 1; - rebase->current = next; - - return 0; -} - -static int rebase_next_merge( - git_rebase_operation **out, - git_rebase *rebase) -{ - git_buf path = GIT_BUF_INIT; - git_commit *current_commit = NULL, *parent_commit = NULL; - git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - git_rebase_operation *operation; - git_checkout_options checkout_opts; - char current_idstr[GIT_OID_HEXSZ]; - unsigned int parent_count; - int error; - - *out = NULL; - - operation = git_array_get(rebase->operations, rebase->current); - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0 || - (error = git_repository_head_tree(&head_tree, rebase->repo)) < 0) - goto done; - - if ((parent_count = git_commit_parentcount(current_commit)) > 1) { - git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); - error = -1; - goto done; - } else if (parent_count) { - if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0) - goto done; - } - - git_oid_fmt(current_idstr, &operation->id); - - normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit); - - if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || - (error = rebase_setupfile(rebase, MSGNUM_FILE, 0, "%" PRIuZ "\n", rebase->current+1)) < 0 || - (error = rebase_setupfile(rebase, CURRENT_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 || - (error = git_merge__check_result(rebase->repo, index)) < 0 || - (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto done; - - *out = operation; - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_tree_free(current_tree); - git_tree_free(head_tree); - git_tree_free(parent_tree); - git_commit_free(parent_commit); - git_commit_free(current_commit); - git_buf_dispose(&path); - - return error; -} - -static int rebase_next_inmemory( - git_rebase_operation **out, - git_rebase *rebase) -{ - git_commit *current_commit = NULL, *parent_commit = NULL; - git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; - git_rebase_operation *operation; - git_index *index = NULL; - unsigned int parent_count; - int error; - - *out = NULL; - - operation = git_array_get(rebase->operations, rebase->current); - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0) - goto done; - - if ((parent_count = git_commit_parentcount(current_commit)) > 1) { - git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); - error = -1; - goto done; - } else if (parent_count) { - if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0) - goto done; - } - - if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) - goto done; - - if (!rebase->index) { - rebase->index = index; - index = NULL; - } else { - if ((error = git_index_read_index(rebase->index, index)) < 0) - goto done; - } - - *out = operation; - -done: - git_commit_free(current_commit); - git_commit_free(parent_commit); - git_tree_free(current_tree); - git_tree_free(head_tree); - git_tree_free(parent_tree); - git_index_free(index); - - return error; -} - -int git_rebase_next( - git_rebase_operation **out, - git_rebase *rebase) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(rebase); - - if ((error = rebase_movenext(rebase)) < 0) - return error; - - if (rebase->inmemory) - error = rebase_next_inmemory(out, rebase); - else if (rebase->type == GIT_REBASE_MERGE) - error = rebase_next_merge(out, rebase); - else - abort(); - - return error; -} - -int git_rebase_inmemory_index( - git_index **out, - git_rebase *rebase) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(rebase); - GIT_ASSERT_ARG(rebase->index); - - GIT_REFCOUNT_INC(rebase->index); - *out = rebase->index; - - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -static int create_signed( - git_oid *out, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - git_tree *tree, - size_t parent_count, - const git_commit **parents) -{ - git_buf commit_content = GIT_BUF_INIT, - commit_signature = GIT_BUF_INIT, - signature_field = GIT_BUF_INIT; - int error; - - git_error_clear(); - - if ((error = git_commit_create_buffer(&commit_content, - rebase->repo, author, committer, message_encoding, - message, tree, parent_count, parents)) < 0) - goto done; - - error = rebase->options.signing_cb(&commit_signature, - &signature_field, commit_content.ptr, - rebase->options.payload); - - if (error) { - if (error != GIT_PASSTHROUGH) - git_error_set_after_callback_function(error, "signing_cb"); - - goto done; - } - - error = git_commit_create_with_signature(out, rebase->repo, - commit_content.ptr, - commit_signature.size > 0 ? commit_signature.ptr : NULL, - signature_field.size > 0 ? signature_field.ptr : NULL); - -done: - git_buf_dispose(&commit_signature); - git_buf_dispose(&signature_field); - git_buf_dispose(&commit_content); - return error; -} -#endif - -static int rebase_commit__create( - git_commit **out, - git_rebase *rebase, - git_index *index, - git_commit *parent_commit, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_rebase_operation *operation; - git_commit *current_commit = NULL, *commit = NULL; - git_tree *parent_tree = NULL, *tree = NULL; - git_oid tree_id, commit_id; - int error; - - operation = git_array_get(rebase->operations, rebase->current); - - if (git_index_has_conflicts(index)) { - git_error_set(GIT_ERROR_REBASE, "conflicts have not been resolved"); - error = GIT_EUNMERGED; - goto done; - } - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || - (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 || - (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) - goto done; - - if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) { - git_error_set(GIT_ERROR_REBASE, "this patch has already been applied"); - error = GIT_EAPPLIED; - goto done; - } - - if (!author) - author = git_commit_author(current_commit); - - if (!message) { - message_encoding = git_commit_message_encoding(current_commit); - message = git_commit_message(current_commit); - } - - git_error_clear(); - error = GIT_PASSTHROUGH; - - if (rebase->options.commit_create_cb) { - error = rebase->options.commit_create_cb(&commit_id, - author, committer, message_encoding, message, - tree, 1, (const git_commit **)&parent_commit, - rebase->options.payload); - - git_error_set_after_callback_function(error, - "commit_create_cb"); - } -#ifndef GIT_DEPRECATE_HARD - else if (rebase->options.signing_cb) { - error = create_signed(&commit_id, rebase, author, - committer, message_encoding, message, tree, - 1, (const git_commit **)&parent_commit); - } -#endif - - if (error == GIT_PASSTHROUGH) - error = git_commit_create(&commit_id, rebase->repo, NULL, - author, committer, message_encoding, message, - tree, 1, (const git_commit **)&parent_commit); - - if (error) - goto done; - - if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) - goto done; - - *out = commit; - -done: - if (error < 0) - git_commit_free(commit); - - git_commit_free(current_commit); - git_tree_free(parent_tree); - git_tree_free(tree); - - return error; -} - -static int rebase_commit_merge( - git_oid *commit_id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_rebase_operation *operation; - git_reference *head = NULL; - git_commit *head_commit = NULL, *commit = NULL; - git_index *index = NULL; - char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; - int error; - - operation = git_array_get(rebase->operations, rebase->current); - GIT_ASSERT(operation); - - if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || - (error = git_repository_head(&head, rebase->repo)) < 0 || - (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)) < 0 || - (error = git_repository_index(&index, rebase->repo)) < 0 || - (error = rebase_commit__create(&commit, rebase, index, head_commit, - author, committer, message_encoding, message)) < 0 || - (error = git_reference__update_for_commit( - rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0) - goto done; - - git_oid_fmt(old_idstr, &operation->id); - git_oid_fmt(new_idstr, git_commit_id(commit)); - - if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, - "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0) - goto done; - - git_oid_cpy(commit_id, git_commit_id(commit)); - -done: - git_index_free(index); - git_reference_free(head); - git_commit_free(head_commit); - git_commit_free(commit); - return error; -} - -static int rebase_commit_inmemory( - git_oid *commit_id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_commit *commit = NULL; - int error = 0; - - GIT_ASSERT_ARG(rebase->index); - GIT_ASSERT_ARG(rebase->last_commit); - GIT_ASSERT_ARG(rebase->current < rebase->operations.size); - - if ((error = rebase_commit__create(&commit, rebase, rebase->index, - rebase->last_commit, author, committer, message_encoding, message)) < 0) - goto done; - - git_commit_free(rebase->last_commit); - rebase->last_commit = commit; - - git_oid_cpy(commit_id, git_commit_id(commit)); - -done: - if (error < 0) - git_commit_free(commit); - - return error; -} - -int git_rebase_commit( - git_oid *id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - int error; - - GIT_ASSERT_ARG(rebase); - GIT_ASSERT_ARG(committer); - - if (rebase->inmemory) - error = rebase_commit_inmemory( - id, rebase, author, committer, message_encoding, message); - else if (rebase->type == GIT_REBASE_MERGE) - error = rebase_commit_merge( - id, rebase, author, committer, message_encoding, message); - else - abort(); - - return error; -} - -int git_rebase_abort(git_rebase *rebase) -{ - git_reference *orig_head_ref = NULL; - git_commit *orig_head_commit = NULL; - int error; - - GIT_ASSERT_ARG(rebase); - - if (rebase->inmemory) - return 0; - - error = rebase->head_detached ? - git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE, - &rebase->orig_head_id, 1, "rebase: aborting") : - git_reference_symbolic_create( - &orig_head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - "rebase: aborting"); - - if (error < 0) - goto done; - - if ((error = git_commit_lookup( - &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 || - (error = git_reset(rebase->repo, (git_object *)orig_head_commit, - GIT_RESET_HARD, &rebase->options.checkout_options)) < 0) - goto done; - - error = rebase_cleanup(rebase); - -done: - git_commit_free(orig_head_commit); - git_reference_free(orig_head_ref); - - return error; -} - -static int notes_ref_lookup(git_buf *out, git_rebase *rebase) -{ - git_config *config = NULL; - int do_rewrite, error; - - if (rebase->options.rewrite_notes_ref) { - git_buf_attach_notowned(out, - rebase->options.rewrite_notes_ref, - strlen(rebase->options.rewrite_notes_ref)); - return 0; - } - - if ((error = git_repository_config(&config, rebase->repo)) < 0 || - (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) { - - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - do_rewrite = 1; - } - - error = do_rewrite ? - git_config_get_string_buf(out, config, "notes.rewriteref") : - GIT_ENOTFOUND; - -done: - git_config_free(config); - return error; -} - -static int rebase_copy_note( - git_rebase *rebase, - const char *notes_ref, - git_oid *from, - git_oid *to, - const git_signature *committer) -{ - git_note *note = NULL; - git_oid note_id; - git_signature *who = NULL; - int error; - - if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if (!committer) { - if((error = git_signature_default(&who, rebase->repo)) < 0) { - if (error != GIT_ENOTFOUND || - (error = git_signature_now(&who, "unknown", "unknown")) < 0) - goto done; - - git_error_clear(); - } - - committer = who; - } - - error = git_note_create(¬e_id, rebase->repo, notes_ref, - git_note_author(note), committer, to, git_note_message(note), 0); - -done: - git_note_free(note); - git_signature_free(who); - - return error; -} - -static int rebase_copy_notes( - git_rebase *rebase, - const git_signature *committer) -{ - git_buf path = GIT_BUF_INIT, rewritten = GIT_BUF_INIT, notes_ref = GIT_BUF_INIT; - char *pair_list, *fromstr, *tostr, *end; - git_oid from, to; - unsigned int linenum = 1; - int error = 0; - - if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if ((error = git_buf_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 || - (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0) - goto done; - - pair_list = rewritten.ptr; - - while (*pair_list) { - fromstr = pair_list; - - if ((end = strchr(fromstr, '\n')) == NULL) - goto on_error; - - pair_list = end+1; - *end = '\0'; - - if ((end = strchr(fromstr, ' ')) == NULL) - goto on_error; - - tostr = end+1; - *end = '\0'; - - if (strlen(fromstr) != GIT_OID_HEXSZ || - strlen(tostr) != GIT_OID_HEXSZ || - git_oid_fromstr(&from, fromstr) < 0 || - git_oid_fromstr(&to, tostr) < 0) - goto on_error; - - if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0) - goto done; - - linenum++; - } - - goto done; - -on_error: - git_error_set(GIT_ERROR_REBASE, "invalid rewritten file at line %d", linenum); - error = -1; - -done: - git_buf_dispose(&rewritten); - git_buf_dispose(&path); - git_buf_dispose(¬es_ref); - - return error; -} - -static int return_to_orig_head(git_rebase *rebase) -{ - git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL; - git_commit *terminal_commit = NULL; - git_buf branch_msg = GIT_BUF_INIT, head_msg = GIT_BUF_INIT; - char onto[GIT_OID_HEXSZ]; - int error = 0; - - git_oid_fmt(onto, &rebase->onto_id); - - if ((error = git_buf_printf(&branch_msg, - "rebase finished: %s onto %.*s", - rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 && - (error = git_buf_printf(&head_msg, - "rebase finished: returning to %s", - rebase->orig_head_name)) == 0 && - (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 && - (error = git_reference_peel((git_object **)&terminal_commit, - terminal_ref, GIT_OBJECT_COMMIT)) == 0 && - (error = git_reference_create_matching(&branch_ref, - rebase->repo, rebase->orig_head_name, - git_commit_id(terminal_commit), 1, - &rebase->orig_head_id, branch_msg.ptr)) == 0) - error = git_reference_symbolic_create(&head_ref, - rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - head_msg.ptr); - - git_buf_dispose(&head_msg); - git_buf_dispose(&branch_msg); - git_commit_free(terminal_commit); - git_reference_free(head_ref); - git_reference_free(branch_ref); - git_reference_free(terminal_ref); - - return error; -} - -int git_rebase_finish( - git_rebase *rebase, - const git_signature *signature) -{ - int error = 0; - - GIT_ASSERT_ARG(rebase); - - if (rebase->inmemory) - return 0; - - if (!rebase->head_detached) - error = return_to_orig_head(rebase); - - if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0) - error = rebase_cleanup(rebase); - - return error; -} - -const char *git_rebase_orig_head_name(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return rebase->orig_head_name; -} - -const git_oid *git_rebase_orig_head_id(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return &rebase->orig_head_id; -} - -const char *git_rebase_onto_name(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return rebase->onto_name; -} - -const git_oid *git_rebase_onto_id(git_rebase *rebase) { - return &rebase->onto_id; -} - -size_t git_rebase_operation_entrycount(git_rebase *rebase) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); - - return git_array_size(rebase->operations); -} - -size_t git_rebase_operation_current(git_rebase *rebase) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); - - return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION; -} - -git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - - return git_array_get(rebase->operations, idx); -} - -void git_rebase_free(git_rebase *rebase) -{ - if (rebase == NULL) - return; - - git_index_free(rebase->index); - git_commit_free(rebase->last_commit); - git__free(rebase->onto_name); - git__free(rebase->orig_head_name); - git__free(rebase->state_path); - git_array_clear(rebase->operations); - git__free((char *)rebase->options.rewrite_notes_ref); - git__free(rebase); -} |