Commit Diff


commit - 9c5c5eedac3adb60a57af2032c446ad06765f3a5
commit + ad493afcc0cd483646dbd160cffcad5190f1d139
blob - 16ce520ec71f86b28e92020b684ea0f3352761c0
blob + 18df91b48a1a61d17594b3b5d352285d9ad558dc
--- got/got.c
+++ got/got.c
@@ -96,6 +96,7 @@ __dead static void	usage_backout(void);
 __dead static void	usage_rebase(void);
 __dead static void	usage_histedit(void);
 __dead static void	usage_stage(void);
+__dead static void	usage_unstage(void);
 
 static const struct got_error*		cmd_init(int, char *[]);
 static const struct got_error*		cmd_import(int, char *[]);
@@ -117,6 +118,7 @@ static const struct got_error*		cmd_backout(int, char 
 static const struct got_error*		cmd_rebase(int, char *[]);
 static const struct got_error*		cmd_histedit(int, char *[]);
 static const struct got_error*		cmd_stage(int, char *[]);
+static const struct got_error*		cmd_unstage(int, char *[]);
 
 static struct got_cmd got_commands[] = {
 	{ "init",	cmd_init,	usage_init,	"" },
@@ -139,6 +141,7 @@ static struct got_cmd got_commands[] = {
 	{ "rebase",	cmd_rebase,	usage_rebase,	"rb" },
 	{ "histedit",	cmd_histedit,	usage_histedit,	"he" },
 	{ "stage",	cmd_stage,	usage_stage,	"sg" },
+	{ "unstage",	cmd_unstage,	usage_unstage,	"ug" },
 };
 
 static void
@@ -5345,6 +5348,81 @@ cmd_stage(int argc, char *argv[])
 	} else
 		error = got_worktree_stage(worktree, &paths, print_status,
 		    NULL, repo);
+done:
+	if (repo)
+		got_repo_close(repo);
+	if (worktree)
+		got_worktree_close(worktree);
+	TAILQ_FOREACH(pe, &paths, entry)
+		free((char *)pe->path);
+	got_pathlist_free(&paths);
+	free(cwd);
+	return error;
+}
+
+__dead static void
+usage_unstage(void)
+{
+	fprintf(stderr, "usage: %s unstage [file-path ...]\n",
+	    getprogname());
+	exit(1);
+}
+
+
+static const struct got_error *
+cmd_unstage(int argc, char *argv[])
+{
+	const struct got_error *error = NULL;
+	struct got_repository *repo = NULL;
+	struct got_worktree *worktree = NULL;
+	char *cwd = NULL;
+	struct got_pathlist_head paths;
+	struct got_pathlist_entry *pe;
+	int ch, did_something = 0;
+
+	TAILQ_INIT(&paths);
+
+	while ((ch = getopt(argc, argv, "")) != -1) {
+		switch (ch) {
+		default:
+			usage_unstage();
+			/* NOTREACHED */
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+#ifndef PROFILE
+	if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd "
+	    "unveil", NULL) == -1)
+		err(1, "pledge");
+#endif
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
+		error = got_error_from_errno("getcwd");
+		goto done;
+	}
+
+	error = got_worktree_open(&worktree, cwd);
+	if (error)
+		goto done;
+
+	error = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
+	if (error != NULL)
+		goto done;
+
+	error = apply_unveil(got_repo_get_path(repo), 1,
+	    got_worktree_get_root_path(worktree));
+	if (error)
+		goto done;
+
+	error = get_worktree_paths_from_argv(&paths, argc, argv, worktree);
+	if (error)
+		goto done;
+
+	error = got_worktree_unstage(worktree, &paths, update_progress,
+	    &did_something, repo);
 done:
 	if (repo)
 		got_repo_close(repo);
blob - 371f24f3fab3bd6ac857e1702676f26aa16095f8
blob + 0b3c446ddee4de1ba085b83ea1dad0a327895ce1
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -381,4 +381,12 @@ const struct got_error *got_worktree_get_histedit_scri
 */
 const struct got_error *got_worktree_stage(struct got_worktree *,
     struct got_pathlist_head *, got_worktree_status_cb, void *,
+    struct got_repository *);
+
+/*
+ * Merge staged changes for the specified paths back into the work tree
+ * and mark the paths as non-staged again.
+ */
+const struct got_error *got_worktree_unstage(struct got_worktree *,
+    struct got_pathlist_head *, got_worktree_checkout_cb, void *,
     struct got_repository *);
blob - 2e5c680e8a66dccd6ad8ff1fcd0fab01cf3b70a5
blob + 5c9c914d1aca962740ca379bb0535b43f2f8ace2
--- lib/worktree.c
+++ lib/worktree.c
@@ -5229,3 +5229,116 @@ done:
 		err = unlockerr;
 	return err;
 }
+
+struct unstage_path_arg {
+	struct got_worktree *worktree;
+	struct got_fileindex *fileindex;
+	struct got_repository *repo;
+	got_worktree_checkout_cb progress_cb;
+	void *progress_arg;
+};
+
+static const struct got_error *
+unstage_path(void *arg, unsigned char status,
+    unsigned char staged_status, const char *relpath,
+    struct got_object_id *blob_id, struct got_object_id *staged_blob_id,
+    struct got_object_id *commit_id)
+{
+	const struct got_error *err = NULL;
+	struct unstage_path_arg *a = arg;
+	struct got_fileindex_entry *ie;
+	struct got_blob_object *blob_base = NULL, *blob_staged = NULL;
+	char *ondisk_path = NULL;
+	int local_changes_subsumed;
+
+	ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath));
+	if (ie == NULL)
+		return got_error_path(relpath, GOT_ERR_BAD_PATH);
+
+	switch (staged_status) {
+	case GOT_STATUS_MODIFY:
+		err = got_object_open_as_blob(&blob_base, a->repo,
+		    blob_id, 8192);
+		if (err)
+			break;
+		/* fall through */
+	case GOT_STATUS_ADD:
+		err = got_object_open_as_blob(&blob_staged, a->repo,
+		    staged_blob_id, 8192);
+		if (err)
+			break;
+
+		if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path,
+		    relpath) == -1) {
+			err= got_error_from_errno("asprintf");
+			break;
+		}
+
+		err = merge_blob(&local_changes_subsumed, a->worktree,
+		    blob_base, ondisk_path, relpath,
+		    got_fileindex_perms_to_st(ie), blob_staged,
+		    commit_id ? commit_id : a->worktree->base_commit_id,
+		    a->repo, a->progress_cb, a->progress_arg);
+		if (err == NULL)
+			got_fileindex_entry_stage_set(ie,
+			    GOT_FILEIDX_STAGE_NONE);
+		break;
+	case GOT_STATUS_DELETE:
+		got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE);
+		err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_DELETE,
+		    relpath);
+		break;
+	}
+
+	free(ondisk_path);
+	if (blob_base)
+		got_object_blob_close(blob_base);
+	if (blob_staged)
+		got_object_blob_close(blob_staged);
+	return err;
+}
+
+const struct got_error *
+got_worktree_unstage(struct got_worktree *worktree,
+    struct got_pathlist_head *paths,
+    got_worktree_checkout_cb progress_cb, void *progress_arg,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL, *sync_err, *unlockerr;
+	struct got_pathlist_entry *pe;
+	struct got_fileindex *fileindex = NULL;
+	char *fileindex_path = NULL;
+	struct unstage_path_arg upa;
+
+	err = lock_worktree(worktree, LOCK_EX);
+	if (err)
+		return err;
+
+	err = open_fileindex(&fileindex, &fileindex_path, worktree);
+	if (err)
+		goto done;
+
+	upa.worktree = worktree;
+	upa.fileindex = fileindex;
+	upa.repo = repo;
+	upa.progress_cb = progress_cb;
+	upa.progress_arg = progress_arg;
+	TAILQ_FOREACH(pe, paths, entry) {
+		err = worktree_status(worktree, pe->path, fileindex, repo,
+		    unstage_path, &upa, NULL, NULL);
+		if (err)
+			goto done;
+	}
+
+	sync_err = sync_fileindex(fileindex, fileindex_path);
+	if (sync_err && err == NULL)
+		err = sync_err;
+done:
+	free(fileindex_path);
+	if (fileindex)
+		got_fileindex_free(fileindex);
+	unlockerr = lock_worktree(worktree, LOCK_SH);
+	if (unlockerr && err == NULL)
+		err = unlockerr;
+	return err;
+}
blob - a8d8fbf800294c759793e7717882f7e8bab9f0dd
blob + e86ea7f544fae06c498afb4bfbe43ed12e0e2818
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
@@ -1,5 +1,5 @@
 REGRESS_TARGETS=checkout update status log add rm diff blame branch commit \
-	revert cherrypick backout rebase import histedit stage
+	revert cherrypick backout rebase import histedit stage unstage
 NOOBJ=Yes
 
 checkout:
@@ -53,4 +53,7 @@ histedit:
 stage:
 	./stage.sh
 
+unstage:
+	./unstage.sh
+
 .include <bsd.regress.mk>
blob - /dev/null
blob + 1377492b96f7e6c3e31976b4e40366a8f951e7e7 (mode 755)
--- /dev/null
+++ regress/cmdline/unstage.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+. ./common.sh
+
+function test_unstage_basic {
+	local testroot=`test_init unstage_basic`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified file" > $testroot/wt/alpha
+	(cd $testroot/wt && got rm beta > /dev/null)
+	echo "new file" > $testroot/wt/foo
+	(cd $testroot/wt && got add foo > /dev/null)
+
+	echo ' M alpha' > $testroot/stdout.expected
+	echo ' D beta' >> $testroot/stdout.expected
+	echo ' A foo' >> $testroot/stdout.expected
+	(cd $testroot/wt && got stage alpha beta foo > /dev/null)
+
+	(cd $testroot/wt && got unstage > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got stage command succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo 'G  alpha' > $testroot/stdout.expected
+	echo 'D  beta' >> $testroot/stdout.expected
+	echo 'G  foo' >> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo 'M  alpha' > $testroot/stdout.expected
+	echo 'D  beta' >> $testroot/stdout.expected
+	echo 'A  foo' >> $testroot/stdout.expected
+	(cd $testroot/wt && got status > $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+run_test test_unstage_basic