summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2014-07-21 12:25:03 -0400
committerEdward Thomson <ethomson@microsoft.com>2014-10-26 22:59:32 -0400
commit5ae9d296e3313be59a969cb8eab250c7c8f7307d (patch)
tree53f130e2f3bd49150458e4f32dfcc7135c9a27a0
parentbad4937ea50a8874598171c7ccb3d3443f1daf7b (diff)
downloadlibgit2-5ae9d296e3313be59a969cb8eab250c7c8f7307d.tar.gz
git_rebase_finish: rewrite notes when finishing rebase
-rw-r--r--include/git2/rebase.h13
-rw-r--r--src/rebase.c166
-rw-r--r--tests/rebase/merge.c94
3 files changed, 255 insertions, 18 deletions
diff --git a/include/git2/rebase.h b/include/git2/rebase.h
index e123ae506..d9cf2318e 100644
--- a/include/git2/rebase.h
+++ b/include/git2/rebase.h
@@ -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
diff --git a/src/rebase.c b/src/rebase.c
index 60c3dd02b..3384a14f2 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -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;
}
diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c
index 0d4dca489..fddab8397 100644
--- a/tests/rebase/merge.c
+++ b/tests/rebase/merge.c
@@ -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);
+}
+