Commit 5ae9d296 by Edward Thomson Committed by Edward Thomson

git_rebase_finish: rewrite notes when finishing rebase

parent bad4937e
......@@ -28,6 +28,15 @@ typedef struct {
* interoperability with other clients.
*/
int quiet;
/**
* Canonical name of the notes reference used to rewrite notes for
* rebased commits when finishing the rebase; if NULL, the contents of
* the coniguration option `notes.rewriteRef` is examined, unless the
* configuration option `notes.rewrite.rebase` is set to false. If
* `notes.rewriteRef` is NULL, notes will not be rewritten.
*/
const char *rewrite_notes_ref;
} git_rebase_options;
#define GIT_REBASE_OPTIONS_VERSION 1
......@@ -130,11 +139,13 @@ GIT_EXTERN(int) git_rebase_abort(
*
* @param repo The repository with the in-progress rebase
* @param signature The identity that is finishing the rebase
* @param opts Options to specify how rebase is finished
* @param Zero on success; -1 on error
*/
GIT_EXTERN(int) git_rebase_finish(
git_repository *repo,
const git_signature *signature);
const git_signature *signature,
const git_rebase_options *opts);
/** @} */
GIT_END_DECL
......
......@@ -12,12 +12,14 @@
#include "filebuf.h"
#include "merge.h"
#include "array.h"
#include "config.h"
#include <git2/types.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"
......@@ -37,6 +39,8 @@
#define ORIG_DETACHED_HEAD "detached HEAD"
#define NOTES_DEFAULT_REF NULL
#define REBASE_DIR_MODE 0777
#define REBASE_FILE_MODE 0666
......@@ -291,7 +295,7 @@ static void rebase_state_free(git_rebase_state *state)
git__free(state->state_path);
}
static int rebase_finish(git_rebase_state *state)
static int rebase_cleanup(git_rebase_state *state)
{
return git_path_isdir(state->state_path) ?
git_futils_rmdir_r(state->state_path, NULL, GIT_RMDIR_REMOVE_FILES) :
......@@ -443,12 +447,46 @@ int git_rebase_init_options(git_rebase_options *opts, unsigned int version)
return 0;
}
static void rebase_normalize_options(
static int rebase_normalize_opts(
git_repository *repo,
git_rebase_options *opts,
const git_rebase_options *given_opts)
{
git_rebase_options default_opts = GIT_REBASE_OPTIONS_INIT;
git_config *config;
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_rebase_options));
memcpy(opts, given_opts, sizeof(git_rebase_options));
else
memcpy(opts, &default_opts, sizeof(git_rebase_options));
if (git_repository_config(&config, repo) < 0)
return -1;
if (given_opts && given_opts->rewrite_notes_ref) {
opts->rewrite_notes_ref = git__strdup(given_opts->rewrite_notes_ref);
GITERR_CHECK_ALLOC(opts->rewrite_notes_ref);
} else if (git_config__get_bool_force(config, "notes.rewrite.rebase", 1)) {
const char *rewrite_ref = git_config__get_string_force(
config, "notes.rewriteref", NOTES_DEFAULT_REF);
if (rewrite_ref) {
opts->rewrite_notes_ref = git__strdup(rewrite_ref);
GITERR_CHECK_ALLOC(opts->rewrite_notes_ref);
}
}
git_config_free(config);
return 0;
}
static void rebase_opts_free(git_rebase_options *opts)
{
if (!opts)
return;
git__free((char *)opts->rewrite_notes_ref);
}
static int rebase_ensure_not_in_progress(git_repository *repo)
......@@ -512,7 +550,7 @@ int git_rebase(
const git_signature *signature,
const git_rebase_options *given_opts)
{
git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
git_rebase_options opts;
git_reference *head_ref = NULL;
git_buf reflog = GIT_BUF_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
......@@ -521,9 +559,9 @@ int git_rebase(
assert(repo && branch && (upstream || onto));
GITERR_CHECK_VERSION(given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options");
rebase_normalize_options(&opts, given_opts);
if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 ||
if ((error = rebase_normalize_opts(repo, &opts, given_opts)) < 0 ||
(error = git_repository__ensure_not_bare(repo, "rebase")) < 0 ||
(error = rebase_ensure_not_in_progress(repo)) < 0 ||
(error = rebase_ensure_not_dirty(repo)) < 0)
goto done;
......@@ -546,6 +584,7 @@ int git_rebase(
done:
git_reference_free(head_ref);
git_buf_free(&reflog);
rebase_opts_free(&opts);
return error;
}
......@@ -818,7 +857,7 @@ int git_rebase_abort(git_repository *repo, const git_signature *signature)
GIT_RESET_HARD, NULL, signature, NULL)) < 0)
goto done;
error = rebase_finish(&state);
error = rebase_cleanup(&state);
done:
git_commit_free(orig_head_commit);
......@@ -828,8 +867,102 @@ done:
return error;
}
int git_rebase_finish(git_repository *repo, const git_signature *signature)
static int rebase_copy_note(
git_repository *repo,
git_oid *from,
git_oid *to,
const git_signature *committer,
const git_rebase_options *opts)
{
git_note *note = NULL;
git_oid note_id;
int error;
if ((error = git_note_read(&note, repo, opts->rewrite_notes_ref, from)) < 0) {
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = 0;
}
goto done;
}
error = git_note_create(&note_id, repo, git_note_author(note),
committer, opts->rewrite_notes_ref, to, git_note_message(note), 0);
done:
git_note_free(note);
return error;
}
static int rebase_copy_notes(
git_repository *repo,
git_rebase_state *state,
const git_signature *committer,
const git_rebase_options *opts)
{
git_buf path = GIT_BUF_INIT, rewritten = GIT_BUF_INIT;
char *pair_list, *fromstr, *tostr, *end;
git_oid from, to;
unsigned int linenum = 1;
int error = 0;
if (!opts->rewrite_notes_ref)
goto done;
if ((error = git_buf_joinpath(&path, state->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(repo, &from, &to, committer, opts)) < 0)
goto done;
linenum++;
}
goto done;
on_error:
giterr_set(GITERR_REBASE, "Invalid rewritten file at line %d", linenum);
error = -1;
done:
git_buf_free(&rewritten);
git_buf_free(&path);
return error;
}
int git_rebase_finish(
git_repository *repo,
const git_signature *signature,
const git_rebase_options *given_opts)
{
git_rebase_options opts;
git_rebase_state state = GIT_REBASE_STATE_INIT;
git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL;
git_commit *terminal_commit = NULL;
......@@ -839,7 +972,8 @@ int git_rebase_finish(git_repository *repo, const git_signature *signature)
assert(repo);
if ((error = rebase_state(&state, repo)) < 0)
if ((error = rebase_normalize_opts(repo, &opts, given_opts)) < 0 ||
(error = rebase_state(&state, repo)) < 0)
goto done;
git_oid_fmt(onto, &state.onto_id);
......@@ -850,18 +984,17 @@ int git_rebase_finish(git_repository *repo, const git_signature *signature)
state.orig_head_name)) < 0 ||
(error = git_repository_head(&terminal_ref, repo)) < 0 ||
(error = git_reference_peel((git_object **)&terminal_commit,
terminal_ref, GIT_OBJ_COMMIT)) < 0)
goto done;
if ((error = git_reference_create_matching(&branch_ref,
terminal_ref, GIT_OBJ_COMMIT)) < 0 ||
(error = git_reference_create_matching(&branch_ref,
repo, state.orig_head_name, git_commit_id(terminal_commit), 1,
&state.orig_head_id, signature, branch_msg.ptr)) < 0 ||
(error = git_reference_symbolic_create(&head_ref,
repo, GIT_HEAD_FILE, state.orig_head_name, 1,
signature, head_msg.ptr)) < 0)
signature, head_msg.ptr)) < 0 ||
(error = rebase_copy_notes(repo, &state, signature, &opts)) < 0)
goto done;
error = rebase_finish(&state);
error = rebase_cleanup(&state);
done:
git_buf_free(&head_msg);
......@@ -871,6 +1004,7 @@ done:
git_reference_free(branch_ref);
git_reference_free(terminal_ref);
rebase_state_free(&state);
rebase_opts_free(&opts);
return error;
}
......
......@@ -330,7 +330,7 @@ void test_rebase_merge__finish(void)
cl_git_fail(error = git_rebase_next(repo, &checkout_opts));
cl_assert_equal_i(GIT_ITEROVER, error);
cl_git_pass(git_rebase_finish(repo, signature));
cl_git_pass(git_rebase_finish(repo, signature, NULL));
cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo));
......@@ -360,3 +360,95 @@ void test_rebase_merge__finish(void)
git_reference_free(upstream_ref);
}
static void test_copy_note(
const git_rebase_options *opts,
bool should_exist)
{
git_reference *branch_ref, *upstream_ref;
git_merge_head *branch_head, *upstream_head;
git_commit *branch_commit;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
git_oid note_id, commit_id;
git_note *note = NULL;
int error;
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy"));
cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal"));
cl_git_pass(git_merge_head_from_ref(&branch_head, repo, branch_ref));
cl_git_pass(git_merge_head_from_ref(&upstream_head, repo, upstream_ref));
cl_git_pass(git_reference_peel((git_object **)&branch_commit,
branch_ref, GIT_OBJ_COMMIT));
/* Add a note to a commit */
cl_git_pass(git_note_create(&note_id, repo,
git_commit_author(branch_commit), git_commit_committer(branch_commit),
"refs/notes/test", git_commit_id(branch_commit),
"This is a commit note.", 0));
cl_git_pass(git_rebase(repo, branch_head, upstream_head, NULL, signature, opts));
cl_git_pass(git_rebase_next(repo, &checkout_opts));
cl_git_pass(git_rebase_commit(&commit_id, repo, NULL, signature,
NULL, NULL));
cl_git_pass(git_rebase_finish(repo, signature, opts));
cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo));
if (should_exist) {
cl_git_pass(git_note_read(&note, repo, "refs/notes/test", &commit_id));
cl_assert_equal_s("This is a commit note.", git_note_message(note));
} else {
cl_git_fail(error =
git_note_read(&note, repo, "refs/notes/test", &commit_id));
cl_assert_equal_i(GIT_ENOTFOUND, error);
}
git_note_free(note);
git_commit_free(branch_commit);
git_merge_head_free(branch_head);
git_merge_head_free(upstream_head);
git_reference_free(branch_ref);
git_reference_free(upstream_ref);
}
void test_rebase_merge__copy_notes_off_by_default(void)
{
test_copy_note(NULL, 0);
}
void test_rebase_merge__copy_notes_specified_in_options(void)
{
git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
opts.rewrite_notes_ref = "refs/notes/test";
test_copy_note(&opts, 1);
}
void test_rebase_merge__copy_notes_specified_in_config(void)
{
git_config *config;
cl_git_pass(git_repository_config(&config, repo));
cl_git_pass(git_config_set_string(config,
"notes.rewriteRef", "refs/notes/test"));
test_copy_note(NULL, 1);
}
void test_rebase_merge__copy_notes_disabled_in_config(void)
{
git_config *config;
cl_git_pass(git_repository_config(&config, repo));
cl_git_pass(git_config_set_bool(config, "notes.rewrite.rebase", 0));
cl_git_pass(git_config_set_string(config,
"notes.rewriteRef", "refs/notes/test"));
test_copy_note(NULL, 0);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment