commit - 049da17d24a611282abd3553f6f43d75609a7fab
commit + 2ec1f75bbb4d6fb8f39613e5012392bae851aa8b
blob - 7bf08a3c2ec5f97189abca71a3b19ca7b24a3923
blob + 2944724c3dc3ffbb7c73c41c14ebf1f26a6d0d28
--- got/got.1
+++ got/got.1
.It Cm add Ar file-path
Schedule an unversioned file in a work tree for addition to the
repository in the next commit.
+.It Cm rm Ar file-path
+Remove a versioned file from a work tree and schedule it for deletion
+from the repository in the next commit.
+.Pp
+The options for
+.Cm got rm
+are as follows:
+.Bl -tag -width Ds
+.It Fl f
+Perform the operation even if the file contains uncommitted modifications.
.El
+.El
.Sh EXIT STATUS
.Ex -std got
.Sh EXAMPLES
blob - ec9e9c4a494ee6e79c2a52d3afaa7e13809f6a1d
blob + 9580988002d014eeb77fc0ac4b4948cf7d4ab414
--- got/got.c
+++ got/got.c
__dead static void usage_status(void);
__dead static void usage_ref(void);
__dead static void usage_add(void);
+__dead static void usage_rm(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_status(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 struct cmd got_commands[] = {
{ "checkout", cmd_checkout, usage_checkout,
"manage references in repository" },
{ "add", cmd_add, usage_add,
"add a new file to version control" },
+ { "rm", cmd_rm, usage_rm,
+ "remove a versioned file" },
};
int
char *abspath = NULL;
struct stat sb;
- if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_ADD)
+ if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_ADD &&
+ status != GOT_STATUS_DELETE)
return NULL;
if (!a->header_shown) {
a->header_shown = 1;
}
- if (status == GOT_STATUS_MODIFY) {
+ if (status == GOT_STATUS_MODIFY || status == GOT_STATUS_DELETE) {
err = got_object_open_as_blob(&blob1, a->repo, id, 8192);
if (err)
goto done;
}
- if (asprintf(&abspath, "%s/%s",
- got_worktree_get_root_path(a->worktree), path) == -1) {
- err = got_error_from_errno();
- goto done;
- }
+ if (status == GOT_STATUS_MODIFY || status == GOT_STATUS_ADD) {
+ if (asprintf(&abspath, "%s/%s",
+ got_worktree_get_root_path(a->worktree), path) == -1) {
+ err = got_error_from_errno();
+ goto done;
+ }
- f2 = fopen(abspath, "r");
- if (f2 == NULL) {
- err = got_error_from_errno();
- goto done;
- }
- if (lstat(abspath, &sb) == -1) {
- err = got_error_from_errno();
- goto done;
- }
+ f2 = fopen(abspath, "r");
+ if (f2 == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ if (lstat(abspath, &sb) == -1) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ } else
+ sb.st_size = 0;
err = got_diff_blob_file(blob1, f2, sb.st_size, path, a->diff_context,
stdout);
got_worktree_close(worktree);
free(path);
free(relpath);
+ free(cwd);
+ return error;
+}
+
+__dead static void
+usage_rm(void)
+{
+ fprintf(stderr, "usage: %s rm [-f] file-path\n", getprogname());
+ exit(1);
+}
+
+static const struct got_error *
+cmd_rm(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, delete_local_mods = 0;
+
+ while ((ch = getopt(argc, argv, "f")) != -1) {
+ switch (ch) {
+ case 'f':
+ delete_local_mods = 1;
+ break;
+ default:
+ usage_add();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage_rm();
+
+ 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(NULL, 0, got_worktree_get_root_path(worktree));
+ if (error)
+ goto done;
+
+ 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;
}
blob - 67326beff75d161b6b1590cd639b5563c4349e5a
blob + f60e3a768c9e7b2d3dc759b950409b01577ef15e
--- include/got_error.h
+++ include/got_error.h
#define GOT_ERR_LOCKFILE_TIMEOUT 64
#define GOT_ERR_BAD_REF_NAME 65
#define GOT_ERR_WORKTREE_REPO 66
+#define GOT_ERR_FILE_MODIFIED 67
+#define GOT_ERR_FILE_STATUS 68
static const struct got_error {
int code;
{ GOT_ERR_LOCKFILE_TIMEOUT,"lockfile timeout" },
{ GOT_ERR_BAD_REF_NAME, "bad reference name" },
{ GOT_ERR_WORKTREE_REPO,"cannot create worktree inside a git repository" },
+ { GOT_ERR_FILE_MODIFIED,"file contains modifications" },
+ { GOT_ERR_FILE_STATUS, "file has unexpected status" },
};
/*
blob - 6c64d84a67bcf518f83f376fa4908c701fd84b6f
blob + 51e30c84ed559444e681b1e0d16454d4bf167cee
--- include/got_worktree.h
+++ include/got_worktree.h
*/
const struct got_error *got_worktree_schedule_add(char **,
struct got_worktree *, const char *);
+
+/*
+ * Remove a file from disk and schedule it to be deleted in the next commit.
+ * Don't allow deleting files with uncommitted modifications, unless the
+ * parameter 'delete_local_mods' is set.
+ */
+const struct got_error *
+got_worktree_schedule_delete(struct got_worktree *, const char *, int,
+ got_worktree_status_cb, void *, struct got_repository *);
blob - 35307267948d55390cbdb4637114ef5fb2dae80d
blob + 3421739139d8ff2c8f0b5051debfd6d9755fd579
--- lib/fileindex.c
+++ lib/fileindex.c
#define GOT_FILEIDX_F_NOT_FLUSHED 0x00010000
#define GOT_FILEIDX_F_NO_BLOB 0x00020000
#define GOT_FILEIDX_F_NO_COMMIT 0x00040000
+#define GOT_FILEIDX_F_NO_FILE_ON_DISK 0x00080000
struct got_fileindex {
struct got_fileindex_tree entries;
if (lstat(ondisk_path, &sb) != 0)
return got_error_from_errno();
+ entry->flags &= ~GOT_FILEIDX_F_NO_FILE_ON_DISK;
+
if (update_timestamps) {
entry->ctime_sec = sb.st_ctime;
entry->ctime_nsec = sb.st_ctimensec;
return NULL;
}
+void
+got_fileindex_entry_mark_deleted_from_disk(struct got_fileindex_entry *entry)
+{
+ entry->flags |= GOT_FILEIDX_F_NO_FILE_ON_DISK;
+}
+
const struct got_error *
got_fileindex_entry_alloc(struct got_fileindex_entry **entry,
const char *ondisk_path, const char *relpath, uint8_t *blob_sha1,
return (ie->flags & GOT_FILEIDX_F_NO_COMMIT) == 0;
}
+int
+got_fileindex_entry_has_file_on_disk(struct got_fileindex_entry *ie)
+{
+ return (ie->flags & GOT_FILEIDX_F_NO_FILE_ON_DISK) == 0;
+}
+
static const struct got_error *
add_entry(struct got_fileindex *fileindex, struct got_fileindex_entry *entry)
{
blob - 98ab532f88824e32453c1ec38d506d903df29a8f
blob + 982945591293b88f08ae619ce9593f1eeb8bc22a
--- lib/got_lib_fileindex.h
+++ lib/got_lib_fileindex.h
int got_fileindex_entry_has_blob(struct got_fileindex_entry *);
int got_fileindex_entry_has_commit(struct got_fileindex_entry *);
+int got_fileindex_entry_has_file_on_disk(struct got_fileindex_entry *);
+
+void got_fileindex_entry_mark_deleted_from_disk(struct got_fileindex_entry *);
blob - bd919fbf217da75c8cea5d5941bca91798510925
blob + 9b86570baa1094b9fc829a3d2d4e98e1faf72521
--- lib/worktree.c
+++ lib/worktree.c
if (lstat(abspath, sb) == -1) {
if (errno == ENOENT) {
if (ie) {
- *status = GOT_STATUS_MISSING;
+ if (got_fileindex_entry_has_file_on_disk(ie))
+ *status = GOT_STATUS_MISSING;
+ else
+ *status = GOT_STATUS_DELETE;
sb->st_mode =
((ie->mode >> GOT_FILEIDX_MODE_PERMS_SHIFT)
& (S_IRWXU | S_IRWXG | S_IRWXO));
if (ie == NULL)
return NULL;
- if (!got_fileindex_entry_has_blob(ie)) {
+ if (!got_fileindex_entry_has_file_on_disk(ie)) {
+ *status = GOT_STATUS_DELETE;
+ return NULL;
+ } else if (!got_fileindex_entry_has_blob(ie)) {
*status = GOT_STATUS_ADD;
return NULL;
}
{
struct diff_dir_cb_arg *a = arg;
struct got_object_id id;
+ unsigned char status;
if (!got_path_is_child(parent_path, a->status_path, a->status_path_len))
return NULL;
memcpy(id.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
- return (*a->status_cb)(a->status_arg, GOT_STATUS_MISSING, ie->path,
- &id);
+ if (got_fileindex_entry_has_file_on_disk(ie))
+ status = GOT_STATUS_MISSING;
+ else
+ status = GOT_STATUS_DELETE;
+ return (*a->status_cb)(a->status_arg, status, ie->path, &id);
}
static const struct got_error *
}
workdir = opendir(ondisk_path);
if (workdir == NULL) {
- if (errno == ENOTDIR) {
+ if (errno == ENOTDIR || errno == ENOENT) {
struct got_fileindex_entry *ie;
ie = got_fileindex_entry_get(fileindex, path);
if (ie == NULL) {
}
return err;
}
+
+const struct got_error *
+got_worktree_schedule_delete(struct got_worktree *worktree,
+ const char *ondisk_path, int delete_local_mods,
+ got_worktree_status_cb status_cb, void *status_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;
+ FILE *index = NULL, *new_index = NULL;
+ const struct got_error *err = NULL, *unlockerr = 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;
+ }
+
+ err = get_file_status(&status, &sb, ie, ondisk_path, repo);
+ if (err)
+ goto done;
+
+ if (status != GOT_STATUS_NO_CHANGE) {
+ if (status != GOT_STATUS_MODIFY) {
+ err = got_error(GOT_ERR_FILE_STATUS);
+ goto done;
+ }
+ if (!delete_local_mods) {
+ err = got_error(GOT_ERR_FILE_MODIFIED);
+ goto done;
+ }
+ }
+
+ if (unlink(ondisk_path) != 0) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+ got_fileindex_entry_mark_deleted_from_disk(ie);
+
+ 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;
+
+ err = report_file_status(ie, ondisk_path, status_cb, status_arg, repo);
+done:
+ free(relpath);
+ 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 - fb63dcc0bfd3e3a0ceefa1d427f83a507225e6df
blob + 3692fa08e31570f8c6294c0cc7725dbadb4d573d
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
-REGRESS_TARGETS=checkout update status log add
+REGRESS_TARGETS=checkout update status log add rm
NOOBJ=Yes
checkout:
add:
./add.sh
+rm:
+ ./rm.sh
+
+
.include <bsd.regress.mk>
blob - bdf87dc616a765027d46ff0a82ddf9c161e79ff8
blob + 84bbf2e8d42e2b4bfd8d1ad97db33cc0361a41d7
--- regress/cmdline/status.sh
+++ regress/cmdline/status.sh
fi
echo "modified alpha" > $testroot/wt/alpha
+ (cd $testroot/wt && got rm beta >/dev/null)
echo "unversioned file" > $testroot/wt/foo
rm $testroot/wt/epsilon/zeta
touch $testroot/wt/beta
(cd $testroot/wt && got add new >/dev/null)
echo 'M alpha' > $testroot/stdout.expected
+ echo 'D beta' >> $testroot/stdout.expected
echo '! epsilon/zeta' >> $testroot/stdout.expected
echo '? foo' >> $testroot/stdout.expected
echo 'A new' >> $testroot/stdout.expected
blob - /dev/null
blob + 8753e23b6747409f7a3892319ef732fac8c6a45c (mode 755)
--- /dev/null
+++ regress/cmdline/rm.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_rm_basic {
+ local testroot=`test_init rm_basic`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo 'D beta' > $testroot/stdout.expected
+ (cd $testroot/wt && got rm beta > $testroot/stdout)
+
+ cmp $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+
+ if [ -e $testroot/wt/beta ]; then
+ echo "removed file beta still exists on disk" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
+function test_rm_with_local_mods {
+ local testroot=`test_init rm_with_local_mods`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified beta" > $testroot/wt/beta
+ echo 'got: file contains modifications' > $testroot/stderr.expected
+ (cd $testroot/wt && got rm beta 2>$testroot/stderr)
+
+ cmp $testroot/stderr.expected $testroot/stderr
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo 'D beta' > $testroot/stdout.expected
+ (cd $testroot/wt && got rm -f beta > $testroot/stdout)
+
+ cmp $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+
+ if [ -e $testroot/wt/beta ]; then
+ echo "removed file beta still exists on disk" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
+run_test test_rm_basic
+run_test test_rm_with_local_mods