Download raw body.
removing unversioned files with 'got rm'
We have long been very careful about removing files that are not tracked
under version control since there is a real risk of data loss if new files
are removed before being committed (i.e. copied to the repository).
In a recent project, where I tidy up some legacy Java code, there are many
generated files which get in my way and I am too lazy to write 'ant clean'
targets for them. So I finally have an unfortunate need for deleting such
files from a work tree with 'got rm'. Removing them by hand with rm or with
find -delete, or by generating arguments for 'rm' from the output of
'got status -s ?' are all worse options than this.
To avoid accidental deletion of files I did not want to tie this feature
into the usual -f (force) option. Rather, deletion of unversioned files
needs to be requested via the status code option: got rm -s ? ...
Which is safe enough to avoid accidents, I hope.
Please do not use this to implement 'make clean' in your Makefiles.
ok?
M cvg/cvg.c | 1+ 1-
M got/got.1 | 7+ 0-
M got/got.c | 5+ 2-
M include/got_worktree.h | 1+ 1-
M lib/worktree.c | 40+ 19-
M regress/cmdline/rm.sh | 115+ 0-
6 files changed, 169 insertions(+), 23 deletions(-)
commit - d982353621b77ce15a0c2304c8dd4ab4bf58d377
commit + e58cd6c07e37081d096b425a39c9f4b74c1688d2
blob - 01cb4e7ea65057c71ac726267aea2f5cf2e25801
blob + 63826144eaeb4656eb58f5a3124aefdf31707773
--- cvg/cvg.c
+++ cvg/cvg.c
@@ -6777,7 +6777,7 @@ cmd_remove(int argc, char *argv[])
error = got_worktree_schedule_delete(worktree, &paths,
delete_local_mods, status_codes, print_remove_status, NULL,
- repo, keep_on_disk, ignore_missing_paths);
+ repo, keep_on_disk, ignore_missing_paths, 0);
done:
if (repo) {
const struct got_error *close_err = got_repo_close(repo);
blob - aaf85dac9d67ac0abd3101fa829a22473cdf9c93
blob + 6b0e0497e4f97647326e69d2e6d30cb9e0046992
--- got/got.1
+++ got/got.1
@@ -1950,7 +1950,14 @@ The following status codes may be specified:
.Fl f
option)
.It ! Ta versioned file expected on disk but missing
+.It ? Ta unversioned file (deletion is permanent!)
.El
+.Pp
+The
+.Fl s\ ?
+option in combination with
+.Fl k
+results in a no-op since the unversioned files will be kept on disk.
.El
.Tg pa
.It Xo
blob - f57ee6d3b40565d80708f683462754d670794ab1
blob + 5f8225f16e839e72a106efef0b3877245ccfc081
--- got/got.c
+++ got/got.c
@@ -8441,7 +8441,7 @@ cmd_remove(int argc, char *argv[])
struct got_pathlist_head paths;
struct got_pathlist_entry *pe;
int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0, i;
- int ignore_missing_paths = 0;
+ int ignore_missing_paths = 0, delete_unversioned = 0;
int *pack_fds = NULL;
RB_INIT(&paths);
@@ -8473,6 +8473,9 @@ cmd_remove(int argc, char *argv[])
case GOT_STATUS_MISSING:
ignore_missing_paths = 1;
break;
+ case GOT_STATUS_UNVERSIONED:
+ delete_unversioned = 1;
+ break;
default:
errx(1, "invalid status code '%c'",
optarg[i]);
@@ -8554,7 +8557,7 @@ cmd_remove(int argc, char *argv[])
error = got_worktree_schedule_delete(worktree, &paths,
delete_local_mods, status_codes, print_remove_status, NULL,
- repo, keep_on_disk, ignore_missing_paths);
+ repo, keep_on_disk, ignore_missing_paths, delete_unversioned);
done:
if (repo) {
const struct got_error *close_err = got_repo_close(repo);
blob - 667a2af518d6c6a7baff55df5072d8a0d95e6810
blob + f875c7a075d9aa4b99c1585c45c322658131589f
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -238,7 +238,7 @@ const struct got_error *got_worktree_schedule_add(stru
const struct got_error *
got_worktree_schedule_delete(struct got_worktree *,
struct got_pathlist_head *, int, const char *,
- got_worktree_delete_cb, void *, struct got_repository *, int, int);
+ got_worktree_delete_cb, void *, struct got_repository *, int, int, int);
/* A callback function which is used to select or reject a patch. */
typedef const struct got_error *(*got_worktree_patch_cb)(int *, void *,
blob - 2f2919d37662afe63851b5b51815d5022a69051e
blob + 47e5edd5d36cc81b08f7c69c522d9c1f99d9c543
--- lib/worktree.c
+++ lib/worktree.c
@@ -4554,6 +4554,7 @@ struct schedule_deletion_args {
int delete_local_mods;
int keep_on_disk;
int ignore_missing_paths;
+ int delete_unversioned;
const char *status_path;
size_t status_path_len;
const char *status_codes;
@@ -4577,25 +4578,35 @@ schedule_for_deletion(void *arg, unsigned char status,
return got_error_set_errno(ENOENT, relpath);
}
- ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath));
- if (ie == NULL)
- return got_error_path(relpath, GOT_ERR_FILE_STATUS);
-
- staged_status = get_staged_status(ie);
- if (staged_status != GOT_STATUS_NO_CHANGE) {
- if (staged_status == GOT_STATUS_DELETE)
+ if (status == GOT_STATUS_UNVERSIONED) {
+ if (a->keep_on_disk)
return NULL;
- return got_error_path(relpath, GOT_ERR_FILE_STAGED);
+ if (!a->delete_unversioned)
+ return got_error_path(relpath, GOT_ERR_FILE_STATUS);
+ } else {
+ ie = got_fileindex_entry_get(a->fileindex, relpath,
+ strlen(relpath));
+ if (ie == NULL)
+ return got_error_path(relpath, GOT_ERR_FILE_STATUS);
+
+ staged_status = get_staged_status(ie);
+ if (staged_status != GOT_STATUS_NO_CHANGE) {
+ if (staged_status == GOT_STATUS_DELETE)
+ return NULL;
+ return got_error_path(relpath, GOT_ERR_FILE_STAGED);
+ }
}
if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path,
relpath) == -1)
return got_error_from_errno("asprintf");
- err = get_file_status(&status, &sb, ie, ondisk_path, dirfd, de_name,
- a->repo);
- if (err)
- goto done;
+ if (ie) {
+ err = get_file_status(&status, &sb, ie, ondisk_path, dirfd, de_name,
+ a->repo);
+ if (err)
+ goto done;
+ }
if (a->status_codes) {
size_t ncodes = strlen(a->status_codes);
@@ -4610,7 +4621,8 @@ schedule_for_deletion(void *arg, unsigned char status,
return NULL;
}
if (a->status_codes[i] != GOT_STATUS_MODIFY &&
- a->status_codes[i] != GOT_STATUS_MISSING) {
+ a->status_codes[i] != GOT_STATUS_MISSING &&
+ a->status_codes[i] != GOT_STATUS_UNVERSIONED) {
static char msg[64];
snprintf(msg, sizeof(msg),
"invalid status code '%c'", a->status_codes[i]);
@@ -4630,8 +4642,13 @@ schedule_for_deletion(void *arg, unsigned char status,
err = got_error_set_errno(ENOENT, relpath);
goto done;
}
+ if (status == GOT_STATUS_UNVERSIONED && !a->delete_unversioned) {
+ err = got_error_path(relpath, GOT_ERR_FILE_STATUS);
+ goto done;
+ }
if (status != GOT_STATUS_MODIFY &&
- status != GOT_STATUS_MISSING) {
+ status != GOT_STATUS_MISSING &&
+ status != GOT_STATUS_UNVERSIONED) {
err = got_error_path(relpath, GOT_ERR_FILE_STATUS);
goto done;
}
@@ -4675,10 +4692,12 @@ schedule_for_deletion(void *arg, unsigned char status,
strlen(ondisk_path), root_len) != 0);
}
- if (got_fileindex_entry_has_blob(ie))
- got_fileindex_entry_mark_deleted_from_disk(ie);
- else
- got_fileindex_entry_remove(a->fileindex, ie);
+ if (ie) {
+ if (got_fileindex_entry_has_blob(ie))
+ got_fileindex_entry_mark_deleted_from_disk(ie);
+ else
+ got_fileindex_entry_remove(a->fileindex, ie);
+ }
done:
free(ondisk_path);
if (err)
@@ -4694,7 +4713,8 @@ got_worktree_schedule_delete(struct got_worktree *work
struct got_pathlist_head *paths, int delete_local_mods,
const char *status_codes,
got_worktree_delete_cb progress_cb, void *progress_arg,
- struct got_repository *repo, int keep_on_disk, int ignore_missing_paths)
+ struct got_repository *repo, int keep_on_disk, int ignore_missing_paths,
+ int delete_unversioned)
{
struct got_fileindex *fileindex = NULL;
char *fileindex_path = NULL;
@@ -4719,6 +4739,7 @@ got_worktree_schedule_delete(struct got_worktree *work
sda.delete_local_mods = delete_local_mods;
sda.keep_on_disk = keep_on_disk;
sda.ignore_missing_paths = ignore_missing_paths;
+ sda.delete_unversioned = delete_unversioned;
sda.status_codes = status_codes;
RB_FOREACH(pe, got_pathlist_head, paths) {
blob - 8ee0c9db46392914b731af78dcd15c1cc3684308
blob + a53c0b627756932fc184cc81e76141e96b3ee4f6
--- regress/cmdline/rm.sh
+++ regress/cmdline/rm.sh
@@ -614,6 +614,121 @@ test_rm_status_code() {
return 1
fi
+ (cd $testroot/wt && got revert -R . > /dev/null)
+
+ # Test deletion of unversioned files via "-s ?".
+ touch $testroot/wt/unversioned
+ touch $testroot/wt/epsilon/unversioned
+
+ echo '? epsilon/unversioned' > $testroot/stdout.expected
+ echo '? unversioned' >> $testroot/stdout.expected
+ (cd $testroot/wt && got status > $testroot/stdout)
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ # Unversioned files should never be removed without -s ?.
+ for flag in "" "-k" "-f" "-R"; do
+ (cd $testroot/wt && got rm $flag unversioned \
+ >$testroot/stdout 2>$testroot/stderr)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ if [ -n "$flag" -a "$flag" = "-k" ]; then
+ echo -n > $testroot/stderr.expected
+ else
+ echo "got: unversioned: file has unexpected status" \
+ > $testroot/stderr.expected
+ fi
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo '? epsilon/unversioned' > $testroot/stdout.expected
+ echo '? unversioned' >> $testroot/stdout.expected
+ (cd $testroot/wt && got status > $testroot/stdout)
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "1"
+ return 1
+ fi
+ done
+
+ # -k takes priority over -s ?, such that files are kept on disk.
+ (cd $testroot/wt && got rm -k -s ? unversioned \
+ >$testroot/stdout 2>$testroot/stderr)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo -n > $testroot/stderr.expected
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ # Remove all unversioned files from the work tree with -R -s ?
+ (cd $testroot/wt && got rm -R -s ? . \
+ >$testroot/stdout 2>$testroot/stderr)
+
+ echo "D epsilon/unversioned" > $testroot/stdout.expected
+ echo "D unversioned" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo -n > $testroot/stderr.expected
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ if [ -e "$testroot/wt/unversioned" ]; then
+ echo "file $testroot/wt/unversioned still exists on disk" &>2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ if [ -e "$testroot/wt/epsilon/unversioned" ]; then
+ echo "file $testroot/wt/epsilon/unversioned still exists on disk" &>2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
test_done "$testroot" "$ret"
}
removing unversioned files with 'got rm'