commit 10e55613cb8d8b5d1829a732694d6259647d7821 from: Omar Polo via: Thomas Adam date: Tue Mar 22 17:51:45 2022 UTC check file status before applying the patch Don't allow `got patch' to delete files that are not known, or add files that are already known and to edit files that are known, not obstructed and without conflicts. commit - 46e6bdd46c729dfc7e89384a8df8f9153ada0557 commit + 10e55613cb8d8b5d1829a732694d6259647d7821 blob - 7cb9fdf4c904187bc1843d43438daa6488dc9351 blob + 909c1b2c7ed3e00761357cb9ab0aaa679878483b --- got/got.c +++ got/got.c @@ -7247,7 +7247,7 @@ cmd_patch(int argc, char *argv[]) err(1, "pledge"); #endif - error = got_patch(patchfd, worktree, repo, nop, &print_remove_status, + error = got_patch(patchfd, worktree, repo, &print_remove_status, NULL, &add_progress, NULL, check_cancelled, NULL); done: blob - 5f28ffc619f1b4e32ba37ff8e5361c636009ba74 blob + 448bb17e65d75d8d5ba94bd577f5cde4fef566bf --- include/got_patch.h +++ include/got_patch.h @@ -21,6 +21,6 @@ * The patch file descriptor *must* be seekable. */ const struct got_error * -got_patch(int, struct got_worktree *, struct got_repository *, int, +got_patch(int, struct got_worktree *, struct got_repository *, got_worktree_delete_cb, void *, got_worktree_checkout_cb, void *, got_cancel_cb, void *); blob - 4f71ec07169550c3193e28bb9ff1ffb746d4f3e1 blob + 7cc802afd65fee4f4cb23b39ba22943ec3808115 --- lib/patch.c +++ lib/patch.c @@ -383,44 +383,6 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, long * } static const struct got_error * -schedule_add(const char *path, struct got_worktree *worktree, - struct got_repository *repo, got_worktree_checkout_cb add_cb, - void *add_arg) -{ - static const struct got_error *err = NULL; - struct got_pathlist_head paths; - struct got_pathlist_entry *pe; - - TAILQ_INIT(&paths); - - err = got_pathlist_insert(&pe, &paths, path, NULL); - if (err == NULL) - err = got_worktree_schedule_add(worktree, &paths, - add_cb, add_arg, repo, 1); - got_pathlist_free(&paths); - return err; -} - -static const struct got_error * -schedule_del(const char *path, struct got_worktree *worktree, - struct got_repository *repo, got_worktree_delete_cb delete_cb, - void *delete_arg) -{ - static const struct got_error *err = NULL; - struct got_pathlist_head paths; - struct got_pathlist_entry *pe; - - TAILQ_INIT(&paths); - - err = got_pathlist_insert(&pe, &paths, path, NULL); - if (err == NULL) - err = got_worktree_schedule_delete(worktree, &paths, - 0, NULL, delete_cb, delete_arg, repo, 0, 0); - got_pathlist_free(&paths); - return err; -} - -static const struct got_error * patch_file(struct got_patch *p, const char *path, FILE *tmp) { const struct got_error *err = NULL; @@ -506,33 +468,131 @@ done: } static const struct got_error * +build_pathlist(const char *p, char **path, struct got_pathlist_head *head, + struct got_worktree *worktree) +{ + const struct got_error *err; + struct got_pathlist_entry *pe; + + err = got_worktree_resolve_path(path, worktree, p); + if (err == NULL) + err = got_pathlist_insert(&pe, head, *path, NULL); + return err; +} + +static const struct got_error * +can_rm(void *arg, unsigned char status, unsigned char staged_status, + const char *path, struct got_object_id *blob_id, + struct got_object_id *staged_blob_id, struct got_object_id *commit_id, + int dirfd, const char *de_name) +{ + if (status == GOT_STATUS_NONEXISTENT) + return got_error_set_errno(ENOENT, path); + if (status != GOT_STATUS_NO_CHANGE && + status != GOT_STATUS_ADD && + status != GOT_STATUS_MODIFY && + status != GOT_STATUS_MODE_CHANGE) + return got_error_path(path, GOT_ERR_FILE_STATUS); + if (staged_status == GOT_STATUS_DELETE) + return got_error_path(path, GOT_ERR_FILE_STATUS); + return NULL; +} + +static const struct got_error * +can_add(void *arg, unsigned char status, unsigned char staged_status, + const char *path, struct got_object_id *blob_id, + struct got_object_id *staged_blob_id, struct got_object_id *commit_id, + int dirfd, const char *de_name) +{ + if (status != GOT_STATUS_NONEXISTENT) + return got_error_path(path, GOT_ERR_FILE_STATUS); + return NULL; +} + +static const struct got_error * +can_edit(void *arg, unsigned char status, unsigned char staged_status, + const char *path, struct got_object_id *blob_id, + struct got_object_id *staged_blob_id, struct got_object_id *commit_id, + int dirfd, const char *de_name) +{ + if (status == GOT_STATUS_NONEXISTENT) + return got_error_set_errno(ENOENT, path); + if (status != GOT_STATUS_NO_CHANGE && + status != GOT_STATUS_ADD && + status != GOT_STATUS_MODIFY) + return got_error_path(path, GOT_ERR_FILE_STATUS); + if (staged_status == GOT_STATUS_DELETE) + return got_error_path(path, GOT_ERR_FILE_STATUS); + return NULL; +} + +static const struct got_error * +check_file_status(struct got_patch *p, int file_renamed, + struct got_worktree *worktree, struct got_repository *repo, + struct got_pathlist_head *old, struct got_pathlist_head *new, + got_cancel_cb cancel_cb, void *cancel_arg) +{ + static const struct got_error *err; + + if (p->old != NULL && p->new == NULL) + return got_worktree_status(worktree, old, repo, 0, + can_rm, NULL, cancel_cb, cancel_arg); + else if (file_renamed) { + err = got_worktree_status(worktree, old, repo, 0, + can_rm, NULL, cancel_cb, cancel_arg); + if (err) + return err; + return got_worktree_status(worktree, new, repo, 0, + can_add, NULL, cancel_cb, cancel_arg); + } else if (p->old == NULL) + return got_worktree_status(worktree, new, repo, 0, + can_add, NULL, cancel_cb, cancel_arg); + else + return got_worktree_status(worktree, new, repo, 0, + can_edit, NULL, cancel_cb, cancel_arg); +} + +static const struct got_error * apply_patch(struct got_worktree *worktree, struct got_repository *repo, struct got_patch *p, got_worktree_delete_cb delete_cb, void *delete_arg, - got_worktree_checkout_cb add_cb, void *add_arg) + got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb, + void *cancel_arg) { const struct got_error *err = NULL; + struct got_pathlist_head oldpaths, newpaths; int file_renamed = 0; char *oldpath = NULL, *newpath = NULL; char *tmppath = NULL, *template = NULL; FILE *tmp = NULL; - err = got_worktree_resolve_path(&oldpath, worktree, - p->old != NULL ? p->old : p->new); + TAILQ_INIT(&oldpaths); + TAILQ_INIT(&newpaths); + + err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath, + &oldpaths, worktree); if (err) goto done; - err = got_worktree_resolve_path(&newpath, worktree, - p->new != NULL ? p->new : p->old); + err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath, + &newpaths, worktree); if (err) goto done; + if (p->old != NULL && p->new != NULL && strcmp(p->old, p->new)) + file_renamed = 1; + + err = check_file_status(p, file_renamed, worktree, repo, &oldpaths, + &newpaths, cancel_cb, cancel_arg); + if (err) + goto done; + if (p->old != NULL && p->new == NULL) { /* * special case: delete a file. don't try to match * the lines but just schedule the removal. */ - err = schedule_del(p->old, worktree, repo, delete_cb, - delete_arg); + err = got_worktree_schedule_delete(worktree, &oldpaths, + 0, NULL, delete_cb, delete_arg, repo, 0, 0); goto done; } @@ -554,26 +614,27 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } - file_renamed = p->old != NULL && strcmp(p->old, p->new); if (file_renamed) { - err = schedule_del(oldpath, worktree, repo, delete_cb, - delete_arg); + err = got_worktree_schedule_delete(worktree, &oldpaths, + 0, NULL, delete_cb, delete_arg, repo, 0, 0); if (err == NULL) - err = schedule_add(newpath, worktree, repo, - add_cb, add_arg); + err = got_worktree_schedule_add(worktree, &newpaths, + add_cb, add_arg, repo, 1); } else if (p->old == NULL) - err = schedule_add(newpath, worktree, repo, add_cb, - add_arg); + err = got_worktree_schedule_add(worktree, &newpaths, + add_cb, add_arg, repo, 1); else printf("M %s\n", oldpath); /* XXX */ done: - if (err != NULL && (file_renamed || p->old == NULL)) + if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL)) unlink(newpath); free(template); if (tmppath != NULL) unlink(tmppath); free(tmppath); + got_pathlist_free(&oldpaths); + got_pathlist_free(&newpaths); free(oldpath); free(newpath); return err; @@ -581,7 +642,7 @@ done: const struct got_error * got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo, - int nop, got_worktree_delete_cb delete_cb, void *delete_arg, + got_worktree_delete_cb delete_cb, void *delete_arg, got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb, void *cancel_arg) { blob - 3c32d4b102c5f846f81ff306d31409388f56973a blob + 9a300a816a79b7eacef07263e7bd7cbce706ae25 --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -723,6 +723,7 @@ EOF test_done $testroot $ret return 1 fi + rm $testroot/wt/eta cat < $testroot/wt/patch --- alpha @@ -767,7 +768,152 @@ EOF fi test_done $testroot $ret } + +test_patch_illegal_status() { + local testroot=`test_init patch_illegal_status` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + # edit an non-existent and unknown file + cat < $testroot/wt/patch +--- iota ++++ iota +@@ -1 +1 @@ +- iota ++ IOTA +EOF + (cd $testroot/wt && got patch patch) > /dev/null \ + 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "edited a missing file" >&2 + test_done $testroot $ret + return 1 + fi + + echo "got: iota: No such file or directory" \ + > $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 $ret + return 1 + fi + + # create iota and re-try + echo iota > $testroot/wt/iota + + (cd $testroot/wt && got patch patch) > /dev/null \ + 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "patched an unknown file" >&2 + test_done $testroot $ret + return 1 + fi + + echo "got: iota: file has unexpected status" \ + > $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 $ret + return 1 + fi + + rm $testroot/wt/iota + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + # edit obstructed file + rm $testroot/wt/alpha + mkdir $testroot/wt/alpha + cat < $testroot/wt/patch +--- alpha ++++ alpha +@@ -1 +1,2 @@ + alpha ++was edited +EOF + + (cd $testroot/wt && got patch patch) > /dev/null \ + 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "edited a missing file" >&2 + test_done $testroot $ret + return 1 + fi + + echo "got: alpha: file has unexpected status" \ + > $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 $ret + return 1 + fi + + # delete an unknown file + cat < $testroot/wt/patch +--- iota ++++ /dev/null +@@ -1 +0,0 @@ +-iota +EOF + + (cd $testroot/wt && got patch patch) > /dev/null \ + 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "deleted a missing file?" >&2 + test_done $testroot $ret + return 1 + fi + + echo "got: iota: No such file or directory" \ + > $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done $testroot $ret + return 1 + fi + + # try again with iota in place but still not registered + echo iota > $testroot/wt/iota + (cd $testroot/wt && got patch patch) > /dev/null \ + 2> $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + echo "deleted an unversioned file?" >&2 + test_done $testroot $ret + return 1 + fi + + echo "got: iota: file has unexpected status" \ + > $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret=$? + if [ $ret -eq 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr + fi + test_done $testroot $ret +} + test_parseargs "$@" run_test test_patch_simple_add_file run_test test_patch_simple_rm_file @@ -781,3 +927,4 @@ run_test test_patch_malformed run_test test_patch_no_patch run_test test_patch_equals_for_context run_test test_patch_rename +run_test test_patch_illegal_status