commit 2ec1f75bbb4d6fb8f39613e5012392bae851aa8b from: Stefan Sperling date: Tue Mar 26 09:35:33 2019 UTC add a basic implementation of 'got rm' commit - 049da17d24a611282abd3553f6f43d75609a7fab commit + 2ec1f75bbb4d6fb8f39613e5012392bae851aa8b blob - 7bf08a3c2ec5f97189abca71a3b19ca7b24a3923 blob + 2944724c3dc3ffbb7c73c41c14ebf1f26a6d0d28 --- got/got.1 +++ got/got.1 @@ -286,7 +286,18 @@ Delete the reference with the specified name from the .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 @@ -78,6 +78,7 @@ __dead static void usage_tree(void); __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 *[]); @@ -88,6 +89,7 @@ static const struct got_error* cmd_tree(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, @@ -108,6 +110,8 @@ static struct cmd got_commands[] = { "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 @@ -996,7 +1000,8 @@ print_diff(void *arg, unsigned char status, const char 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) { @@ -1005,28 +1010,31 @@ print_diff(void *arg, unsigned char status, const char 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); @@ -1893,6 +1901,76 @@ done: 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 @@ -80,6 +80,8 @@ #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; @@ -150,6 +152,8 @@ static const struct got_error { { 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 @@ -139,3 +139,12 @@ const struct got_error *got_worktree_resolve_path(char */ 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 @@ -42,6 +42,7 @@ #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; @@ -59,6 +60,8 @@ got_fileindex_entry_update(struct got_fileindex_entry 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; @@ -90,6 +93,12 @@ got_fileindex_entry_update(struct got_fileindex_entry 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, @@ -137,6 +146,12 @@ got_fileindex_entry_has_commit(struct got_fileindex_en 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 @@ -141,3 +141,6 @@ const struct got_error *got_fileindex_diff_dir(struct 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 @@ -967,7 +967,10 @@ get_file_status(unsigned char *status, struct stat *sb 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)); @@ -986,7 +989,10 @@ get_file_status(unsigned char *status, struct stat *sb 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; } @@ -1424,13 +1430,17 @@ status_old(void *arg, struct got_fileindex_entry *ie, { 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 * @@ -1511,7 +1521,7 @@ got_worktree_status(struct got_worktree *worktree, con } 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) { @@ -1682,3 +1692,114 @@ done: } 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 @@ -1,4 +1,4 @@ -REGRESS_TARGETS=checkout update status log add +REGRESS_TARGETS=checkout update status log add rm NOOBJ=Yes checkout: @@ -16,4 +16,8 @@ log: add: ./add.sh +rm: + ./rm.sh + + .include blob - bdf87dc616a765027d46ff0a82ddf9c161e79ff8 blob + 84bbf2e8d42e2b4bfd8d1ad97db33cc0361a41d7 --- regress/cmdline/status.sh +++ regress/cmdline/status.sh @@ -27,6 +27,7 @@ function test_status_basic { 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 @@ -34,6 +35,7 @@ function test_status_basic { (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 @@ -0,0 +1,88 @@ +#!/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_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