Commit eaf0d68830cc8fc56c92733a550ea5f6bc15101d

Edward Thomson 2015-03-17T17:53:07

rebase: block rebase_commit with unstaged changes

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69c6991..b7d4aa4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,9 @@ v0.22 + 1
   allow for specifying the expression from the user to be put into the
   reflog.
 
+* `git_rebase_commit` now return `GIT_EUNMERGED` when you attempt to
+  commit with unstaged changes.
+
 ### API additions
 
 * The `git_merge_options` gained a `file_flags` member.
diff --git a/src/rebase.c b/src/rebase.c
index 3bc10f4..f8f4a1b 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -504,33 +504,42 @@ static int rebase_ensure_not_in_progress(git_repository *repo)
 	return 0;
 }
 
-static int rebase_ensure_not_dirty(git_repository *repo)
+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;
 
-	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 (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) {
-		giterr_set(GITERR_REBASE, "Uncommitted changes exist in index");
-		error = -1;
-		goto done;
-	}
+		if (git_diff_num_deltas(diff) > 0) {
+			giterr_set(GITERR_REBASE, "Uncommitted changes exist in index");
+			error = fail_with;
+			goto done;
+		}
 
-	git_diff_free(diff);
-	diff = NULL;
+		git_diff_free(diff);
+		diff = NULL;
+	}
 
-	if ((error = git_diff_index_to_workdir(&diff, repo, index, NULL)) < 0)
-		goto done;
+	if (check_workdir) {
+		if ((error = git_diff_index_to_workdir(&diff, repo, index, NULL)) < 0)
+			goto done;
 
-	if (git_diff_num_deltas(diff) > 0) {
-		giterr_set(GITERR_REBASE, "Unstaged changes exist in workdir");
-		error = -1;
+		if (git_diff_num_deltas(diff) > 0) {
+			giterr_set(GITERR_REBASE, "Unstaged changes exist in workdir");
+			error = fail_with;
+			goto done;
+		}
 	}
 
 done:
@@ -679,7 +688,7 @@ int git_rebase_init(
 	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 ||
+		(error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0 ||
 		(error = git_commit_lookup(
 			&onto_commit, repo, git_annotated_commit_id(onto))) < 0)
 		return error;
@@ -869,11 +878,12 @@ static int rebase_commit_merge(
 
 	if (git_index_has_conflicts(index)) {
 		giterr_set(GITERR_REBASE, "Conflicts have not been resolved");
-		error = GIT_EMERGECONFLICT;
+		error = GIT_EUNMERGED;
 		goto done;
 	}
 
-	if ((error = git_commit_lookup(&current_commit, rebase->repo, &operation->id)) < 0 ||
+	if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 ||
+		(error = git_commit_lookup(&current_commit, rebase->repo, &operation->id)) < 0 ||
 		(error = git_repository_head(&head, rebase->repo)) < 0 ||
 		(error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 0 ||
 		(error = git_commit_tree(&head_tree, head_commit)) < 0 ||
diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c
index e939056..02c3960 100644
--- a/tests/rebase/merge.c
+++ b/tests/rebase/merge.c
@@ -264,6 +264,41 @@ void test_rebase_merge__commit(void)
 	git_rebase_free(rebase);
 }
 
+void test_rebase_merge__blocked_when_dirty(void)
+{
+	git_rebase *rebase;
+	git_reference *branch_ref, *upstream_ref;
+	git_annotated_commit *branch_head, *upstream_head;
+	git_rebase_operation *rebase_operation;
+	git_oid commit_id;
+
+	cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef"));
+	cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master"));
+
+	cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref));
+	cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref));
+
+	cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL));
+
+	/* Allow untracked files */
+	cl_git_pass(git_rebase_next(&rebase_operation, rebase));
+	cl_git_mkfile("rebase/untracked_file.txt", "This is untracked\n");
+	cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature,
+		NULL, NULL));
+
+	/* Do not allow unstaged */
+	cl_git_pass(git_rebase_next(&rebase_operation, rebase));
+	cl_git_mkfile("rebase/veal.txt", "This is an unstaged change\n");
+	cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature,
+		NULL, NULL));
+
+	git_annotated_commit_free(branch_head);
+	git_annotated_commit_free(upstream_head);
+	git_reference_free(branch_ref);
+	git_reference_free(upstream_ref);
+	git_rebase_free(rebase);
+}
+
 void test_rebase_merge__commit_updates_rewritten(void)
 {
 	git_rebase *rebase;