commit - 95edb37e3f3bec85f054ff6fc44a9eca557e6080
commit + a129376b6f8adc074c4b53a4f78195ca32b78b1a
blob - 265c9850b620a3a3ca13d48618052cfb435aadff
blob + 785da132116932e9208b30119726b3a7885b8e0a
--- got/got.1
+++ got/got.1
.It Fl f
Perform the operation even if the file contains uncommitted modifications.
.El
+.It Cm revert Ar file-path
+Revert any uncommited changes in the file at the specified path.
+File contents will be overwritten with those contained in the
+work tree's base commit. There is no way to bring discarded
+changes back after
+.Cm got revert !
+.Pp
+If the file was added with
+.Cm got add
+it will become an unversioned file again.
+If the file was deleted with
+.Cm got rm
+it will be restored.
.El
.Sh EXIT STATUS
.Ex -std got
blob - 4a3c791020dff8d53212a4d0fac32cd6b5925676
blob + e083023d4b4d640205a1f9554968d287c356fdc3
--- got/got.c
+++ got/got.c
__dead static void usage_ref(void);
__dead static void usage_add(void);
__dead static void usage_rm(void);
+__dead static void usage_revert(void);
static const struct got_error* cmd_checkout(int, char *[]);
static const struct got_error* cmd_update(int, char *[]);
static const struct got_error* cmd_ref(int, char *[]);
static const struct got_error* cmd_add(int, char *[]);
static const struct got_error* cmd_rm(int, char *[]);
+static const struct got_error* cmd_revert(int, char *[]);
static struct cmd got_commands[] = {
{ "checkout", cmd_checkout, usage_checkout,
"add a new file to version control" },
{ "rm", cmd_rm, usage_rm,
"remove a versioned file" },
+ { "revert", cmd_revert, usage_revert,
+ "revert uncommitted changes" },
};
int
error = got_worktree_schedule_delete(worktree, path, delete_local_mods,
print_status, NULL, repo);
+ if (error)
+ goto done;
+done:
+ if (repo)
+ got_repo_close(repo);
+ if (worktree)
+ got_worktree_close(worktree);
+ free(path);
+ free(cwd);
+ return error;
+}
+
+__dead static void
+usage_revert(void)
+{
+ fprintf(stderr, "usage: %s revert file-path\n", getprogname());
+ exit(1);
+}
+
+static void
+revert_progress(void *arg, unsigned char status, const char *path)
+{
+ while (path[0] == '/')
+ path++;
+ printf("%c %s\n", status, path);
+}
+
+static const struct got_error *
+cmd_revert(int argc, char *argv[])
+{
+ const struct got_error *error = NULL;
+ struct got_worktree *worktree = NULL;
+ struct got_repository *repo = NULL;
+ char *cwd = NULL, *path = NULL;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ usage_revert();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage_revert();
+
+ path = realpath(argv[0], NULL);
+ if (path == NULL) {
+ error = got_error_from_errno();
+ goto done;
+ }
+
+ cwd = getcwd(NULL, 0);
+ if (cwd == NULL) {
+ error = got_error_from_errno();
+ 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 = got_worktree_revert(worktree, path,
+ revert_progress, NULL, repo);
+ if (error)
+ goto done;
done:
if (repo)
got_repo_close(repo);
blob - 2bd95039582b9f7349099a9e19cd046c5028db04
blob + 6baad612e9f1ecbad710071e93668cb9e6da2c5b
--- include/got_object.h
+++ include/got_object.h
const struct got_tree_entries *got_object_tree_get_entries(
struct got_tree_object *);
+/* Find a particular entry in a tree. */
+const struct got_tree_entry *got_object_tree_find_entry(
+ struct got_tree_object *, const char *);
+
/*
* Compare two trees and indicate whether the entry at the specified path
* differs between them. The path must not be the root path "/"; the function
blob - d6cae67b59be7b305f805fc6a0b6590bff63815c
blob + d49c5f89501111450660c1051f14f61b0ce3dd09
--- include/got_worktree.h
+++ include/got_worktree.h
#define GOT_STATUS_MISSING '!'
#define GOT_STATUS_UNVERSIONED '?'
#define GOT_STATUS_OBSTRUCTED '~'
+#define GOT_STATUS_REVERT 'R'
/*
* Attempt to initialize a new work tree on disk.
* void * argument, and the path of each checked out file.
*/
const struct got_error *got_worktree_checkout_files(struct got_worktree *,
- struct got_repository *, got_worktree_checkout_cb progress, void *,
+ struct got_repository *, got_worktree_checkout_cb, void *,
got_worktree_cancel_cb, void *);
/* A callback function which is invoked to report a path's status. */
const struct got_error *
got_worktree_schedule_delete(struct got_worktree *, const char *, int,
got_worktree_status_cb, void *, struct got_repository *);
+
+/*
+ * Revert a file at the specified path such that it matches its
+ * original state in the worktree's base commit.
+ */
+const struct got_error *got_worktree_revert(struct got_worktree *,
+ const char *, got_worktree_checkout_cb, void *, struct got_repository *);
blob - 911aa0e314aa74b4cbc6a5f1f749500f7cd6eb3e
blob + eb8980676a00c3a6d06cb7825b9821b9828b23f0
--- lib/object.c
+++ lib/object.c
return te;
}
return NULL;
+}
+
+const struct got_tree_entry *
+got_object_tree_find_entry(struct got_tree_object *tree, const char *name)
+{
+ return find_entry_by_name(tree, name, strlen(name));
}
const struct got_error *
blob - 487e120c8cd12a0a3b7d5ec4f6272064cb3a5f26
blob + 917cd5d8b0d2657829ef7fa395ed2320226cffaa
--- lib/path.c
+++ lib/path.c
got_path_is_root_dir(const char *path)
{
return (path[0] == '/' && path[1] == '\0');
+}
+
+int
+got_path_is_current_dir(const char *path)
+{
+ return (path[0] == '.' && path[1] == '\0');
}
int
blob - 694339afe5680f6668ebb29a16409b9056d82593
blob + 106aa6ffdc6f810cb3b0515b4e20cb1a875123ed
--- lib/worktree.c
+++ lib/worktree.c
install_blob(struct got_worktree *worktree, const char *ondisk_path,
const char *path, uint16_t te_mode, uint16_t st_mode,
struct got_blob_object *blob, int restoring_missing_file,
- struct got_repository *repo, got_worktree_checkout_cb progress_cb,
- void *progress_arg)
+ int reverting_versioned_file, struct got_repository *repo,
+ got_worktree_checkout_cb progress_cb, void *progress_arg)
{
const struct got_error *err = NULL;
int fd = -1;
if (restoring_missing_file)
(*progress_cb)(progress_arg, GOT_STATUS_MISSING, path);
+ else if (reverting_versioned_file)
+ (*progress_cb)(progress_arg, GOT_STATUS_REVERT, path);
else
(*progress_cb)(progress_arg,
update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, path);
goto done;
} else {
err = install_blob(worktree, ondisk_path, path, te->mode,
- sb.st_mode, blob, status == GOT_STATUS_MISSING, repo,
- progress_cb, progress_arg);
+ sb.st_mode, blob, status == GOT_STATUS_MISSING, 0,
+ repo, progress_cb, progress_arg);
if (err)
goto done;
err = update_blob_fileindex_entry(worktree, fileindex, ie,
err = unlockerr;
return err;
}
+
+const struct got_error *
+got_worktree_revert(struct got_worktree *worktree,
+ const char *ondisk_path,
+ got_worktree_checkout_cb progress_cb, void *progress_arg,
+ struct got_repository *repo)
+{
+ struct got_fileindex *fileindex = NULL;
+ struct got_fileindex_entry *ie = NULL;
+ char *relpath, *fileindex_path = NULL, *new_fileindex_path = NULL;
+ char *tree_path = NULL, *parent_path, *te_name;
+ FILE *index = NULL, *new_index = NULL;
+ const struct got_error *err = NULL, *unlockerr = NULL;
+ struct got_tree_object *tree = NULL;
+ struct got_object_id id, *tree_id = NULL;
+ const struct got_tree_entry *te;
+ struct got_blob_object *blob = NULL;
+ unsigned char status;
+ struct stat sb;
+
+ err = lock_worktree(worktree, LOCK_EX);
+ if (err)
+ return err;
+
+ err = got_path_skip_common_ancestor(&relpath,
+ got_worktree_get_root_path(worktree), ondisk_path);
+ if (err)
+ goto done;
+
+ fileindex = got_fileindex_alloc();
+ if (fileindex == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+ if (asprintf(&fileindex_path, "%s/%s/%s", worktree->root_path,
+ GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FILE_INDEX) == -1) {
+ err = got_error_from_errno();
+ fileindex_path = NULL;
+ goto done;
+ }
+
+ index = fopen(fileindex_path, "rb");
+ if (index == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+ err = got_fileindex_read(fileindex, index);
+ if (err)
+ goto done;
+
+ ie = got_fileindex_entry_get(fileindex, relpath);
+ if (ie == NULL) {
+ err = got_error(GOT_ERR_BAD_PATH);
+ goto done;
+ }
+
+ /* Construct in-repository path of tree which contains this blob. */
+ err = got_path_dirname(&parent_path, ie->path);
+ if (err) {
+ if (err->code != GOT_ERR_BAD_PATH)
+ goto done;
+ parent_path = "/";
+ }
+ if (got_path_is_root_dir(worktree->path_prefix)) {
+ tree_path = strdup(parent_path);
+ if (tree_path == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ } else {
+ if (got_path_is_root_dir(parent_path)) {
+ tree_path = strdup(worktree->path_prefix);
+ if (tree_path == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ } else {
+ if (asprintf(&tree_path, "%s/%s",
+ worktree->path_prefix, parent_path) == -1) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ }
+ }
+
+ err = got_object_id_by_path(&tree_id, repo, worktree->base_commit_id,
+ tree_path);
+ if (err)
+ goto done;
+
+ err = got_object_open_as_tree(&tree, repo, tree_id);
+ if (err)
+ goto done;
+
+ te_name = basename(ie->path);
+ if (te_name == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+ err = get_file_status(&status, &sb, ie, ondisk_path, repo);
+ if (err)
+ goto done;
+
+ te = got_object_tree_find_entry(tree, te_name);
+ if (te == NULL && status != GOT_STATUS_ADD) {
+ err = got_error(GOT_ERR_NO_TREE_ENTRY);
+ goto done;
+ }
+
+ switch (status) {
+ case GOT_STATUS_ADD:
+ (*progress_cb)(progress_arg, GOT_STATUS_REVERT, ie->path);
+ got_fileindex_entry_remove(fileindex, ie);
+ break;
+ case GOT_STATUS_DELETE:
+ case GOT_STATUS_MODIFY:
+ case GOT_STATUS_CONFLICT:
+ case GOT_STATUS_MISSING:
+ memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
+ err = got_object_open_as_blob(&blob, repo, &id, 8192);
+ if (err)
+ goto done;
+ err = install_blob(worktree, ondisk_path, ie->path,
+ te->mode, sb.st_mode, blob, 0, 1, repo, progress_cb,
+ progress_arg);
+ if (err)
+ goto done;
+ if (status == GOT_STATUS_DELETE) {
+ err = update_blob_fileindex_entry(worktree,
+ fileindex, ie, ondisk_path, ie->path, blob, 1);
+ if (err)
+ goto done;
+ }
+ break;
+ default:
+ goto done;
+ }
+
+ err = got_opentemp_named(&new_fileindex_path, &new_index,
+ fileindex_path);
+ if (err)
+ goto done;
+
+ err = got_fileindex_write(fileindex, new_index);
+ if (err)
+ goto done;
+
+ if (rename(new_fileindex_path, fileindex_path) != 0) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+ free(new_fileindex_path);
+ new_fileindex_path = NULL;
+done:
+ free(relpath);
+ free(tree_path);
+ if (blob)
+ got_object_blob_close(blob);
+ if (tree)
+ got_object_tree_close(tree);
+ free(tree_id);
+ if (index) {
+ if (fclose(index) != 0 && err == NULL)
+ err = got_error_from_errno();
+ }
+ if (new_fileindex_path) {
+ if (unlink(new_fileindex_path) != 0 && err == NULL)
+ err = got_error_from_errno();
+ free(new_fileindex_path);
+ }
+ if (fileindex)
+ got_fileindex_free(fileindex);
+ unlockerr = lock_worktree(worktree, LOCK_SH);
+ if (unlockerr && err == NULL)
+ err = unlockerr;
+ return err;
+}
blob - /dev/null
blob + 4c59fbdebed9af30db2c55f99c3b569c552e8c31 (mode 755)
--- /dev/null
+++ regress/cmdline/revert.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_revert_basic {
+ local testroot=`test_init revert_basic`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha" > $testroot/wt/alpha
+
+ echo 'R alpha' > $testroot/stdout.expected
+
+ (cd $testroot/wt && got revert alpha > $testroot/stdout)
+
+ cmp $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 "alpha" > $testroot/content.expected
+ cat $testroot/wt/alpha > $testroot/content
+
+ cmp $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ fi
+ test_done "$testroot" "$ret"
+
+}
+
+function test_revert_rm {
+ local testroot=`test_init revert_rm`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got rm beta >/dev/null)
+
+ echo 'R beta' > $testroot/stdout.expected
+
+ (cd $testroot/wt && got revert beta > $testroot/stdout)
+
+ cmp $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 "beta" > $testroot/content.expected
+ cat $testroot/wt/beta > $testroot/content
+
+ cmp $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_revert_add {
+ local testroot=`test_init revert_add`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "new file" > $testroot/wt/new
+ (cd $testroot/wt && got add new >/dev/null)
+
+ echo 'R new' > $testroot/stdout.expected
+
+ (cd $testroot/wt && got revert new > $testroot/stdout)
+
+ cmp $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 "new file" > $testroot/content.expected
+ cat $testroot/wt/new > $testroot/content
+
+ cmp $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo '? new' > $testroot/stdout.expected
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ cmp $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_revert_basic
+run_test test_revert_rm
+run_test test_revert_add