Commit f28bae0c380467409515ffd25247f3204dcc4019

Edward Thomson 2016-02-15T17:16:00

rebase: persist a single in-memory index When performing an in-memory rebase, keep a single index for the duration, so that callers have the expected index lifecycle and do not hold on to an index that is free'd out from under them.

diff --git a/include/git2/rebase.h b/include/git2/rebase.h
index 4fda1fd..ece8b36 100644
--- a/include/git2/rebase.h
+++ b/include/git2/rebase.h
@@ -142,12 +142,6 @@ typedef struct {
 	 * be populated for operations of type `GIT_REBASE_OPERATION_EXEC`.
 	 */
 	const char *exec;
-
-	/**
-	 * The index that is the result of an operation.
-	 * This is set only for in-memory rebases.
-	 */
-	git_index *index;
 } git_rebase_operation;
 
 /**
@@ -248,6 +242,21 @@ GIT_EXTERN(int) git_rebase_next(
 	git_rebase *rebase);
 
 /**
+ * Gets the index produced by the last operation, which is the result
+ * of `git_rebase_next` and which will be committed by the next
+ * invocation of `git_rebase_commit`.  This is useful for resolving
+ * conflicts in an in-memory rebase before committing them.  You must
+ * call `git_index_free` when you are finished with this.
+ *
+ * This is only applicable for in-memory rebases; for rebases within
+ * a working directory, the changes were applied to the repository's
+ * index.
+ */
+GIT_EXTERN(int) git_rebase_inmemory_index(
+	git_index **index,
+	git_rebase *rebase);
+
+/**
  * Commits the current patch.  You must have resolved any conflicts that
  * were introduced during the patch application from the `git_rebase_next`
  * invocation.
diff --git a/src/rebase.c b/src/rebase.c
index 52a6df0..b9d1d4f 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -71,8 +71,8 @@ struct git_rebase {
 	size_t current;
 
 	/* Used by in-memory rebase */
+	git_index *index;
 	git_commit *last_commit;
-	git_index *last_index;
 
 	/* Used by regular (not in-memory) merge-style rebase */
 	git_oid orig_head_id;
@@ -856,10 +856,13 @@ static int rebase_next_inmemory(
 		(error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0)
 		goto done;
 
-	git_index_free(rebase->last_index);
-	rebase->last_index = index;
-	operation->index = index;
-	index = NULL;
+	if (!rebase->index) {
+		rebase->index = index;
+		index = NULL;
+	} else {
+		if ((error = git_index_read_index(rebase->index, index)) < 0)
+			goto done;
+	}
 
 	*out = operation;
 
@@ -895,6 +898,18 @@ int git_rebase_next(
 	return error;
 }
 
+int git_rebase_inmemory_index(
+	git_index **out,
+	git_rebase *rebase)
+{
+	assert(out && rebase && rebase->index);
+
+	GIT_REFCOUNT_INC(rebase->index);
+	*out = rebase->index;
+
+	return 0;
+}
+
 static int rebase_commit__create(
 	git_commit **out,
 	git_rebase *rebase,
@@ -1018,17 +1033,13 @@ static int rebase_commit_inmemory(
 	operation = git_array_get(rebase->operations, rebase->current);
 
 	assert(operation);
-	assert(operation->index);
+	assert(rebase->index);
 	assert(rebase->last_commit);
 
-	if ((error = rebase_commit__create(&commit, rebase, operation->index,
+	if ((error = rebase_commit__create(&commit, rebase, rebase->index,
 		rebase->last_commit, author, committer, message_encoding, message)) < 0)
 		goto done;
 
-	git_index_free(rebase->last_index);
-	operation->index = NULL;
-	rebase->last_index = NULL;
-
 	git_commit_free(rebase->last_commit);
 	rebase->last_commit = commit;
 
@@ -1313,7 +1324,7 @@ void git_rebase_free(git_rebase *rebase)
 	if (rebase == NULL)
 		return;
 
-	git_index_free(rebase->last_index);
+	git_index_free(rebase->index);
 	git_commit_free(rebase->last_commit);
 	git__free(rebase->onto_name);
 	git__free(rebase->orig_head_name);
diff --git a/tests/rebase/inmemory.c b/tests/rebase/inmemory.c
index c7942d6..d5d89c7 100644
--- a/tests/rebase/inmemory.c
+++ b/tests/rebase/inmemory.c
@@ -54,7 +54,7 @@ void test_rebase_inmemory__can_resolve_conflicts(void)
 	git_status_list *status_list;
 	git_oid pick_id, commit_id, expected_commit_id;
 	git_signature *signature;
-	git_index *repo_index;
+	git_index *rebase_index, *repo_index;
 	git_index_entry resolution = {{0}};
 	git_rebase_options opts = GIT_REBASE_OPTIONS_INIT;
 
@@ -86,7 +86,8 @@ void test_rebase_inmemory__can_resolve_conflicts(void)
 	cl_assert_equal_i(0, git_status_list_entrycount(status_list));
 
 	/* but that the index returned from rebase does have conflicts */
-	cl_assert(git_index_has_conflicts(rebase_operation->index));
+	cl_git_pass(git_rebase_inmemory_index(&rebase_index, rebase));
+	cl_assert(git_index_has_conflicts(rebase_index));
 
 	cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
 
@@ -94,8 +95,8 @@ void test_rebase_inmemory__can_resolve_conflicts(void)
 	resolution.path = "asparagus.txt";
 	resolution.mode = GIT_FILEMODE_BLOB;
 	git_oid_fromstr(&resolution.id, "414dfc71ead79c07acd4ea47fecf91f289afc4b9");
-	cl_git_pass(git_index_conflict_remove(rebase_operation->index, "asparagus.txt"));
-	cl_git_pass(git_index_add(rebase_operation->index, &resolution));
+	cl_git_pass(git_index_conflict_remove(rebase_index, "asparagus.txt"));
+	cl_git_pass(git_index_add(rebase_index, &resolution));
 
 	/* and finally create a commit for the resolved rebase operation */
 	cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL));
@@ -110,5 +111,6 @@ void test_rebase_inmemory__can_resolve_conflicts(void)
 	git_reference_free(branch_ref);
 	git_reference_free(upstream_ref);
 	git_index_free(repo_index);
+	git_index_free(rebase_index);
 	git_rebase_free(rebase);
 }
diff --git a/tests/rebase/iterator.c b/tests/rebase/iterator.c
index e761d39..db57b0a 100644
--- a/tests/rebase/iterator.c
+++ b/tests/rebase/iterator.c
@@ -13,7 +13,8 @@ void test_rebase_iterator__initialize(void)
 {
 	repo = cl_git_sandbox_init("rebase");
 	cl_git_pass(git_repository_index(&_index, repo));
-	cl_git_pass(git_signature_now(&signature, "Rebaser", "rebaser@rebaser.rb"));
+	cl_git_pass(git_signature_new(&signature, "Rebaser",
+		"rebaser@rebaser.rb", 1405694510, 0));
 }
 
 void test_rebase_iterator__cleanup(void)
@@ -53,7 +54,7 @@ void test_iterator(bool inmemory)
 	git_reference *branch_ref, *upstream_ref;
 	git_annotated_commit *branch_head, *upstream_head;
 	git_rebase_operation *rebase_operation;
-	git_oid commit_id;
+	git_oid commit_id, expected_id;
 	int error;
 
 	opts.inmemory = inmemory;
@@ -77,16 +78,25 @@ void test_iterator(bool inmemory)
 		NULL, NULL));
 	test_operations(rebase, 0);
 
+	git_oid_fromstr(&expected_id, "776e4c48922799f903f03f5f6e51da8b01e4cce0");
+	cl_assert_equal_oid(&expected_id, &commit_id);
+
 	cl_git_pass(git_rebase_next(&rebase_operation, rebase));
 	cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature,
 		NULL, NULL));
 	test_operations(rebase, 1);
 
+	git_oid_fromstr(&expected_id, "ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a");
+	cl_assert_equal_oid(&expected_id, &commit_id);
+
 	cl_git_pass(git_rebase_next(&rebase_operation, rebase));
 	cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature,
 		NULL, NULL));
 	test_operations(rebase, 2);
 
+	git_oid_fromstr(&expected_id, "948b12fe18b84f756223a61bece4c307787cd5d4");
+	cl_assert_equal_oid(&expected_id, &commit_id);
+
 	if (!inmemory) {
 		git_rebase_free(rebase);
 		cl_git_pass(git_rebase_open(&rebase, repo, NULL));
@@ -97,11 +107,17 @@ void test_iterator(bool inmemory)
 		NULL, NULL));
 	test_operations(rebase, 3);
 
+	git_oid_fromstr(&expected_id, "d9d5d59d72c9968687f9462578d79878cd80e781");
+	cl_assert_equal_oid(&expected_id, &commit_id);
+
 	cl_git_pass(git_rebase_next(&rebase_operation, rebase));
 	cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature,
 		NULL, NULL));
 	test_operations(rebase, 4);
 
+	git_oid_fromstr(&expected_id, "9cf383c0a125d89e742c5dec58ed277dd07588b3");
+	cl_assert_equal_oid(&expected_id, &commit_id);
+
 	cl_git_fail(error = git_rebase_next(&rebase_operation, rebase));
 	cl_assert_equal_i(GIT_ITEROVER, error);
 	test_operations(rebase, 4);