Commit Diff


commit - 8cdd231889a848b735f84ed6772eab46c2512db9
commit + af179be739cacd6576fdf9596ac7e61b714ee367
blob - b08ea7c98c8952d224400390c47972f2e057ee4e
blob + df29e65f90431310315474962e5c738ec544f38d
--- lib/worktree.c
+++ lib/worktree.c
@@ -4700,6 +4700,7 @@ struct revert_file_args {
 	void *patch_arg;
 	struct got_repository *repo;
 	int unlink_added_files;
+	struct got_pathlist_head *added_files_to_unlink;
 };
 
 static const struct got_error *
@@ -5002,16 +5003,33 @@ revert_file(void *arg, unsigned char status, unsigned 
 			goto done;
 		got_fileindex_entry_remove(a->fileindex, ie);
 		if (a->unlink_added_files) {
-			if (asprintf(&ondisk_path, "%s/%s",
-			    got_worktree_get_root_path(a->worktree),
-			    relpath) == -1) {
-				err = got_error_from_errno("asprintf");
-				goto done;
+			int do_unlink = a->added_files_to_unlink ? 0 : 1;
+
+			if (a->added_files_to_unlink) {
+				struct got_pathlist_entry *pe;
+
+				TAILQ_FOREACH(pe, a->added_files_to_unlink,
+				    entry) {
+					if (got_path_cmp(pe->path, relpath,
+					    pe->path_len, strlen(relpath)))
+						continue;
+					do_unlink = 1;
+					break;
+				}
 			}
-			if (unlink(ondisk_path) == -1) {
-				err = got_error_from_errno2("unlink",
-				    ondisk_path);
-				break;
+
+			if (do_unlink) {
+				if (asprintf(&ondisk_path, "%s/%s",
+				    got_worktree_get_root_path(a->worktree),
+				    relpath) == -1) {
+					err = got_error_from_errno("asprintf");
+					goto done;
+				}
+				if (unlink(ondisk_path) == -1) {
+					err = got_error_from_errno2("unlink",
+					    ondisk_path);
+					break;
+				}
 			}
 		}
 		break;
@@ -7327,7 +7345,134 @@ done:
 		err = unlockerr;
 	return err;
 }
+
+static const struct got_error *
+get_paths_changed_between_commits(struct got_pathlist_head *paths,
+    struct got_object_id *id1, struct got_object_id *id2,
+    struct got_repository *repo)
+{
+	const struct got_error		*err;
+	struct got_commit_object	*commit1 = NULL, *commit2 = NULL;
+	struct got_tree_object		*tree1 = NULL, *tree2 = NULL;
+
+	if (id1) {
+		err = got_object_open_as_commit(&commit1, repo, id1);
+		if (err)
+			goto done;
+
+		err = got_object_open_as_tree(&tree1, repo,
+		    got_object_commit_get_tree_id(commit1));
+		if (err)
+			goto done;
+	}
 
+	if (id2) {
+		err = got_object_open_as_commit(&commit2, repo, id2);
+		if (err)
+			goto done;
+
+		err = got_object_open_as_tree(&tree2, repo,
+		    got_object_commit_get_tree_id(commit2));
+		if (err)
+			goto done;
+	}
+
+	err = got_diff_tree(tree1, tree2, NULL, NULL, -1, -1, "", "", repo,
+	    got_diff_tree_collect_changed_paths, paths, 0);
+	if (err)
+		goto done;
+done:
+	if (commit1)
+		got_object_commit_close(commit1);
+	if (commit2)
+		got_object_commit_close(commit2);
+	if (tree1)
+		got_object_tree_close(tree1);
+	if (tree2)
+		got_object_tree_close(tree2);
+	return err;
+}
+
+static const struct got_error *
+get_paths_added_between_commits(struct got_pathlist_head *added_paths,
+    struct got_object_id *id1, struct got_object_id *id2,
+    const char *path_prefix, struct got_repository *repo)
+{
+	const struct got_error *err;
+	struct got_pathlist_head merged_paths;
+	struct got_pathlist_entry *pe;
+	char *abspath = NULL, *wt_path = NULL;
+
+	TAILQ_INIT(&merged_paths);
+
+	err = get_paths_changed_between_commits(&merged_paths, id1, id2, repo);
+	if (err)
+		goto done;
+
+	TAILQ_FOREACH(pe, &merged_paths, entry) {
+		struct got_diff_changed_path *change = pe->data;
+
+		if (change->status != GOT_STATUS_ADD)
+			continue;
+
+		if (got_path_is_root_dir(path_prefix)) {
+			wt_path = strdup(pe->path);
+			if (wt_path == NULL) {
+				err = got_error_from_errno("strdup");
+				goto done;
+			}
+		} else {
+			if (asprintf(&abspath, "/%s", pe->path) == -1) {
+				err = got_error_from_errno("asprintf");
+				goto done;
+			}
+
+			err = got_path_skip_common_ancestor(&wt_path,
+			    path_prefix, abspath);
+			if (err)
+				goto done;
+			free(abspath);
+			abspath = NULL;
+		}
+
+		err = got_pathlist_append(added_paths, wt_path, NULL);
+		if (err)
+			goto done;
+		wt_path = NULL;
+	}
+
+done:
+	got_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_ALL);
+	free(abspath);
+	free(wt_path);
+	return err;
+}
+
+static const struct got_error *
+get_paths_added_in_commit(struct got_pathlist_head *added_paths,
+    struct got_object_id *id, const char *path_prefix,
+    struct got_repository *repo)
+{
+	const struct got_error		*err;
+	struct got_commit_object	*commit = NULL;
+	struct got_object_qid		*pid;
+
+	err = got_object_open_as_commit(&commit, repo, id);
+	if (err)
+		goto done;
+
+	pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
+
+	err = get_paths_added_between_commits(added_paths,
+	    pid ? &pid->id : NULL, id, path_prefix, repo);
+	if (err)
+		goto done;
+done:
+	if (commit)
+		got_object_commit_close(commit);
+	return err;
+}
+
 const struct got_error *
 got_worktree_rebase_abort(struct got_worktree *worktree,
     struct got_fileindex *fileindex, struct got_repository *repo,
@@ -7337,15 +7482,44 @@ got_worktree_rebase_abort(struct got_worktree *worktre
 	const struct got_error *err, *unlockerr, *sync_err;
 	struct got_reference *resolved = NULL;
 	struct got_object_id *commit_id = NULL;
+	struct got_object_id *merged_commit_id = NULL;
 	struct got_commit_object *commit = NULL;
 	char *fileindex_path = NULL;
+	char *commit_ref_name = NULL;
+	struct got_reference *commit_ref = NULL;
 	struct revert_file_args rfa;
 	struct got_object_id *tree_id = NULL;
+	struct got_pathlist_head added_paths;
 
+	TAILQ_INIT(&added_paths);
+
 	err = lock_worktree(worktree, LOCK_EX);
 	if (err)
 		return err;
+
+	err = get_rebase_commit_ref_name(&commit_ref_name, worktree);
+	if (err)
+		goto done;
+
+	err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
+	if (err)
+		goto done;
+
+	err = got_ref_resolve(&merged_commit_id, repo, commit_ref);
+	if (err)
+		goto done;
 
+	/*
+	 * Determine which files in added status can be safely removed
+	 * from disk while reverting changes in the work tree.
+	 * We want to avoid deleting unrelated files which were added by
+	 * the user for conflict resolution purposes.
+	 */
+	err = get_paths_added_in_commit(&added_paths, merged_commit_id,
+	    got_worktree_get_path_prefix(worktree), repo);
+	if (err)
+		goto done;
+
 	err = got_ref_open(&resolved, repo,
 	    got_ref_get_symref_target(new_base_branch), 0);
 	if (err)
@@ -7393,7 +7567,8 @@ got_worktree_rebase_abort(struct got_worktree *worktre
 	rfa.patch_cb = NULL;
 	rfa.patch_arg = NULL;
 	rfa.repo = repo;
-	rfa.unlink_added_files = 0;
+	rfa.unlink_added_files = 1;
+	rfa.added_files_to_unlink = &added_paths;
 	err = worktree_status(worktree, "", fileindex, repo,
 	    revert_file, &rfa, NULL, NULL, 1, 0);
 	if (err)
@@ -7406,14 +7581,19 @@ sync:
 	if (sync_err && err == NULL)
 		err = sync_err;
 done:
+	got_pathlist_free(&added_paths, GOT_PATHLIST_FREE_PATH);
 	got_ref_close(resolved);
 	free(tree_id);
 	free(commit_id);
+	free(merged_commit_id);
 	if (commit)
 		got_object_commit_close(commit);
 	if (fileindex)
 		got_fileindex_free(fileindex);
 	free(fileindex_path);
+	free(commit_ref_name);
+	if (commit_ref)
+		got_ref_close(commit_ref);
 
 	unlockerr = lock_worktree(worktree, LOCK_SH);
 	if (unlockerr && err == NULL)
@@ -7709,14 +7889,49 @@ got_worktree_histedit_abort(struct got_worktree *workt
 	const struct got_error *err, *unlockerr, *sync_err;
 	struct got_reference *resolved = NULL;
 	char *fileindex_path = NULL;
+	struct got_object_id *merged_commit_id = NULL;
 	struct got_commit_object *commit = NULL;
+	char *commit_ref_name = NULL;
+	struct got_reference *commit_ref = NULL;
 	struct got_object_id *tree_id = NULL;
 	struct revert_file_args rfa;
+	struct got_pathlist_head added_paths;
 
+	TAILQ_INIT(&added_paths);
+
 	err = lock_worktree(worktree, LOCK_EX);
 	if (err)
 		return err;
 
+	err = get_histedit_commit_ref_name(&commit_ref_name, worktree);
+	if (err)
+		goto done;
+
+	err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
+	if (err) {
+		if (err->code != GOT_ERR_NOT_REF)
+			goto done;
+		/* Can happen on early abort due to invalid histedit script. */
+		commit_ref = NULL;
+	}
+
+	if (commit_ref) {
+		err = got_ref_resolve(&merged_commit_id, repo, commit_ref);
+		if (err)
+			goto done;
+
+		/*
+		 * Determine which files in added status can be safely removed
+		 * from disk while reverting changes in the work tree.
+		 * We want to avoid deleting unrelated files added by the
+		 * user during conflict resolution or during histedit -e.
+		 */
+		err = get_paths_added_in_commit(&added_paths, merged_commit_id,
+		    got_worktree_get_path_prefix(worktree), repo);
+		if (err)
+			goto done;
+	}
+
 	err = got_ref_open(&resolved, repo,
 	    got_ref_get_symref_target(branch), 0);
 	if (err)
@@ -7755,7 +7970,8 @@ got_worktree_histedit_abort(struct got_worktree *workt
 	rfa.patch_cb = NULL;
 	rfa.patch_arg = NULL;
 	rfa.repo = repo;
-	rfa.unlink_added_files = 0;
+	rfa.unlink_added_files = 1;
+	rfa.added_files_to_unlink = &added_paths;
 	err = worktree_status(worktree, "", fileindex, repo,
 	    revert_file, &rfa, NULL, NULL, 1, 0);
 	if (err)
@@ -7768,9 +7984,14 @@ sync:
 	if (sync_err && err == NULL)
 		err = sync_err;
 done:
-	got_ref_close(resolved);
+	if (resolved)
+		got_ref_close(resolved);
+	if (commit_ref)
+		got_ref_close(commit_ref);
+	free(merged_commit_id);
 	free(tree_id);
 	free(fileindex_path);
+	free(commit_ref_name);
 
 	unlockerr = lock_worktree(worktree, LOCK_SH);
 	if (unlockerr && err == NULL)
@@ -8448,12 +8669,42 @@ got_worktree_merge_abort(struct got_worktree *worktree
     got_worktree_checkout_cb progress_cb, void *progress_arg)
 {
 	const struct got_error *err, *unlockerr, *sync_err;
-	struct got_object_id *commit_id = NULL;
 	struct got_commit_object *commit = NULL;
 	char *fileindex_path = NULL;
 	struct revert_file_args rfa;
+	char *commit_ref_name = NULL;
+	struct got_reference *commit_ref = NULL;
+	struct got_object_id *merged_commit_id = NULL;
 	struct got_object_id *tree_id = NULL;
+	struct got_pathlist_head added_paths;
+
+	TAILQ_INIT(&added_paths);
+
+	err = get_merge_commit_ref_name(&commit_ref_name, worktree);
+	if (err)
+		goto done;
+
+	err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
+	if (err)
+		goto done;
+
+	err = got_ref_resolve(&merged_commit_id, repo, commit_ref);
+	if (err)
+		goto done;
+
+	/*
+	 * Determine which files in added status can be safely removed
+	 * from disk while reverting changes in the work tree.
+	 * We want to avoid deleting unrelated files which were added by
+	 * the user for conflict resolution purposes.
+	 */
+	err = get_paths_added_between_commits(&added_paths,
+	    got_worktree_get_base_commit_id(worktree), merged_commit_id,
+	    got_worktree_get_path_prefix(worktree), repo);
+	if (err)
+		goto done;
 
+
 	err = got_object_open_as_commit(&commit, repo,
 	    worktree->base_commit_id);
 	if (err)
@@ -8480,6 +8731,7 @@ got_worktree_merge_abort(struct got_worktree *worktree
 	rfa.patch_arg = NULL;
 	rfa.repo = repo;
 	rfa.unlink_added_files = 1;
+	rfa.added_files_to_unlink = &added_paths;
 	err = worktree_status(worktree, "", fileindex, repo,
 	    revert_file, &rfa, NULL, NULL, 1, 0);
 	if (err)
@@ -8493,12 +8745,15 @@ sync:
 		err = sync_err;
 done:
 	free(tree_id);
-	free(commit_id);
+	free(merged_commit_id);
 	if (commit)
 		got_object_commit_close(commit);
 	if (fileindex)
 		got_fileindex_free(fileindex);
 	free(fileindex_path);
+	if (commit_ref)
+		got_ref_close(commit_ref);
+	free(commit_ref_name);
 
 	unlockerr = lock_worktree(worktree, LOCK_SH);
 	if (unlockerr && err == NULL)
blob - efdeafbe917d9f2c945e958629fc06b7bd9af2ba
blob + 6080151a8a3e5c278b52bd1288edfdf4aa4383fb
--- regress/cmdline/histedit.sh
+++ regress/cmdline/histedit.sh
@@ -902,20 +902,15 @@ test_histedit_abort() {
 		fi
 	done
 
-	echo "new file on master" > $testroot/content.expected
-	cat $testroot/wt/epsilon/new > $testroot/content
-	cmp -s $testroot/content.expected $testroot/content
-	ret=$?
-	if [ $ret -ne 0 ]; then
-		diff -u $testroot/content.expected $testroot/content
-		test_done "$testroot" "$ret"
+	if [ -e $testroot/wt/epsilon/new ]; then
+		echo "removed file new still exists on disk" >&2
+		test_done "$testroot" "1"
 		return 1
 	fi
 
 	(cd $testroot/wt && got status > $testroot/stdout)
 
-	echo "?  epsilon/new" > $testroot/stdout.expected
-	echo "?  unversioned-file" >> $testroot/stdout.expected
+	echo "?  unversioned-file" > $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
blob - 876eced1d6b84caf0d92277204c67399f2876875
blob + 9093dd5b9fb524b14d2d17d4fe754da0ec8ef9f4
--- regress/cmdline/merge.sh
+++ regress/cmdline/merge.sh
@@ -672,10 +672,15 @@ test_merge_abort() {
 		test_done "$testroot" "$ret"
 		return 1
 	fi
+
+	# unrelated added file added during conflict resolution
+	touch $testroot/wt/added-file
+	(cd $testroot/wt && got add added-file > /dev/null)
 
 	(cd $testroot/wt && got status > $testroot/stdout)
 
-	echo "C  alpha" > $testroot/stdout.expected
+	echo "A  added-file" > $testroot/stdout.expected
+	echo "C  alpha" >> $testroot/stdout.expected
 	echo "D  beta" >> $testroot/stdout.expected
 	echo "A  epsilon/new" >> $testroot/stdout.expected
 	echo "M  gamma/delta" >> $testroot/stdout.expected
@@ -697,11 +702,13 @@ test_merge_abort() {
 		return 1
 	fi
 
-	echo "R  alpha" > $testroot/stdout.expected
+	echo "R  added-file" > $testroot/stdout.expected
+	echo "R  alpha" >> $testroot/stdout.expected
 	echo "R  beta" >> $testroot/stdout.expected
 	echo "R  epsilon/new" >> $testroot/stdout.expected
 	echo "R  gamma/delta" >> $testroot/stdout.expected
 	echo "R  symlink" >> $testroot/stdout.expected
+	echo "G  added-file" >> $testroot/stdout.expected
 	echo "Merge of refs/heads/newbranch aborted" \
 		>> $testroot/stdout.expected
 
@@ -757,7 +764,8 @@ test_merge_abort() {
 
 	(cd $testroot/wt && got status > $testroot/stdout)
 
-	echo "?  unversioned-file" > $testroot/stdout.expected
+	echo "?  added-file" > $testroot/stdout.expected
+	echo "?  unversioned-file" >> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
blob - bb762cd05b066923e9538c4bdf31982707dba134
blob + 1d339f654c90ee75c99eac9cc5a7f39e8b6275ce
--- regress/cmdline/rebase.sh
+++ regress/cmdline/rebase.sh
@@ -419,7 +419,10 @@ test_rebase_abort() {
 	local short_orig_commit1=`trim_obj_id 28 $orig_commit1`
 
 	echo "modified alpha on branch" > $testroot/repo/alpha
-	git_commit $testroot/repo -m "committing to alpha on newbranch"
+	echo "new file on branch" > $testroot/repo/epsilon/new
+	(cd $testroot/repo && git add epsilon/new)
+	git_commit $testroot/repo \
+		-m "changing alpha and adding new on newbranch"
 	local orig_commit2=`git_show_head $testroot/repo`
 	local short_orig_commit2=`trim_obj_id 28 $orig_commit2`
 
@@ -450,10 +453,12 @@ test_rebase_abort() {
 		>> $testroot/stdout.expected
 	echo ": committing to beta on newbranch" >> $testroot/stdout.expected
 	echo "C  alpha" >> $testroot/stdout.expected
+	echo "A  epsilon/new" >> $testroot/stdout.expected
 	echo "Files with new merge conflicts: 1" >> $testroot/stdout.expected
 	echo -n "$short_orig_commit2 -> merge conflict" \
 		>> $testroot/stdout.expected
-	echo ": committing to alpha on newbranch" >> $testroot/stdout.expected
+	echo ": changing alpha and adding new on newbranch" \
+		>> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
@@ -490,9 +495,15 @@ test_rebase_abort() {
 		return 1
 	fi
 
+	# unrelated file in work tree added during conflict resolution
+	touch $testroot/wt/added-file
+	(cd $testroot/wt && got add added-file > /dev/null)
+
 	(cd $testroot/wt && got status > $testroot/stdout)
 
-	echo "C  alpha" > $testroot/stdout.expected
+	echo "A  added-file" > $testroot/stdout.expected
+	echo "C  alpha" >> $testroot/stdout.expected
+	echo "A  epsilon/new" >> $testroot/stdout.expected
 	echo "?  unversioned-file" >> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
@@ -508,7 +519,10 @@ test_rebase_abort() {
 
 	echo "Switching work tree to refs/heads/master" \
 		> $testroot/stdout.expected
+	echo 'R  added-file' >> $testroot/stdout.expected
 	echo 'R  alpha' >> $testroot/stdout.expected
+	echo 'R  epsilon/new' >> $testroot/stdout.expected
+	echo 'G  added-file' >> $testroot/stdout.expected
 	echo 'U  beta' >> $testroot/stdout.expected
 	echo "Rebase of refs/heads/newbranch aborted" \
 		>> $testroot/stdout.expected
@@ -556,7 +570,8 @@ test_rebase_abort() {
 	fi
 
 	(cd $testroot/wt && got status > $testroot/stdout)
-	echo "?  unversioned-file" > $testroot/stdout.expected
+	echo "?  added-file" > $testroot/stdout.expected
+	echo "?  unversioned-file" >> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then