commit - 9c5c5eedac3adb60a57af2032c446ad06765f3a5
commit + ad493afcc0cd483646dbd160cffcad5190f1d139
blob - 16ce520ec71f86b28e92020b684ea0f3352761c0
blob + 18df91b48a1a61d17594b3b5d352285d9ad558dc
--- got/got.c
+++ got/got.c
__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 *[]);
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, "" },
{ "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
} 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
*/
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
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
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:
stage:
./stage.sh
+unstage:
+ ./unstage.sh
+
.include <bsd.regress.mk>
blob - /dev/null
blob + 1377492b96f7e6c3e31976b4e40366a8f951e7e7 (mode 755)
--- /dev/null
+++ regress/cmdline/unstage.sh
+#!/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