commit ad493afcc0cd483646dbd160cffcad5190f1d139 from: Stefan Sperling date: Sat Aug 03 22:52:07 2019 UTC initial 'got unstage' implementation 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 blob - /dev/null blob + 1377492b96f7e6c3e31976b4e40366a8f951e7e7 (mode 755) --- /dev/null +++ regress/cmdline/unstage.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# +# Copyright (c) 2019 Stefan Sperling +# +# 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