commit 46e6bdd46c729dfc7e89384a8df8f9153ada0557 from: Omar Polo via: Thomas Adam date: Tue Mar 22 17:50:36 2022 UTC refactor apply_patch to support renaming files add two helper functions (schedule_add, schedule_del) and move the guts of apply_patch into a new function `patch_file'. This simplifies apply_patch and makes easier to figure out what happens. Then, drop GOT_ERR_PATCH_PATHS_DIFFER since we allow the to rename files. commit - 122e16119c68804932abae9efbce7cad48a85a24 commit + 46e6bdd46c729dfc7e89384a8df8f9153ada0557 blob - d89b541d0dedd769040cf62df12ad6582091b1d0 blob + 4f71ec07169550c3193e28bb9ff1ffb746d4f3e1 --- lib/patch.c +++ lib/patch.c @@ -380,6 +380,44 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, long * } } return NULL; +} + +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 * @@ -399,8 +437,6 @@ patch_file(struct got_patch *p, const char *path, FILE h = STAILQ_FIRST(&p->head); if (h == NULL || STAILQ_NEXT(h, entries) != NULL) return got_error(GOT_ERR_PATCH_MALFORMED); - if (p->nop) - return NULL; for (i = 0; i < h->len; ++i) { if (fprintf(tmp, "%s", h->lines[i]+1) < 0) return got_error_from_errno("fprintf"); @@ -460,15 +496,7 @@ patch_file(struct got_patch *p, const char *path, FILE } } - - if (p->new == NULL) { - struct stat sb; - - if (fstat(fileno(orig), &sb) == -1) - err = got_error_from_errno("fstat"); - else if (sb.st_size != copypos) - err = got_error(GOT_ERR_PATCH_DONT_APPLY); - } else if (!p->nop && !feof(orig)) + if (!feof(orig)) err = copy(tmp, orig, copypos, -1); done: @@ -478,123 +506,35 @@ 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_cancel_cb cancel_cb, - void *cancel_arg) + got_worktree_checkout_cb add_cb, void *add_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; - TAILQ_INIT(&oldpaths); - TAILQ_INIT(&newpaths); - - err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath, - &oldpaths, worktree); + err = got_worktree_resolve_path(&oldpath, worktree, + p->old != NULL ? p->old : p->new); if (err) goto done; - err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath, - &newpaths, worktree); + err = got_worktree_resolve_path(&newpath, worktree, + p->new != NULL ? p->new : p->old); 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) + 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); goto done; + } if (asprintf(&template, "%s/got-patch", got_worktree_get_root_path(worktree)) == -1) { @@ -602,49 +542,38 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } - if (!p->nop) - err = got_opentemp_named(&tmppath, &tmp, template); + err = got_opentemp_named(&tmppath, &tmp, template); if (err) goto done; err = patch_file(p, oldpath, tmp); if (err) goto done; - if (p->nop) - goto done; - - if (p->old != NULL && p->new == NULL) { - err = got_worktree_schedule_delete(worktree, &oldpaths, - 0, NULL, delete_cb, delete_arg, repo, 0, 0); - goto done; - } - if (rename(tmppath, newpath) == -1) { err = got_error_from_errno3("rename", tmppath, newpath); goto done; } + file_renamed = p->old != NULL && strcmp(p->old, p->new); if (file_renamed) { - err = got_worktree_schedule_delete(worktree, &oldpaths, - 0, NULL, delete_cb, delete_arg, repo, 0, 0); + err = schedule_del(oldpath, worktree, repo, delete_cb, + delete_arg); if (err == NULL) - err = got_worktree_schedule_add(worktree, &newpaths, - add_cb, add_arg, repo, 1); + err = schedule_add(newpath, worktree, repo, + add_cb, add_arg); } else if (p->old == NULL) - err = got_worktree_schedule_add(worktree, &newpaths, - add_cb, add_arg, repo, 1); + err = schedule_add(newpath, worktree, repo, add_cb, + add_arg); else printf("M %s\n", oldpath); /* XXX */ done: - if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL)) + if (err != 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; blob - 41f290c529e09c20b73b0a6752c3351664a9559d blob + 3c32d4b102c5f846f81ff306d31409388f56973a --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -723,7 +723,6 @@ EOF test_done $testroot $ret return 1 fi - rm $testroot/wt/eta cat < $testroot/wt/patch --- alpha @@ -768,205 +767,7 @@ 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_patch_nop() { - local testroot=`test_init patch_nop` - - got checkout $testroot/repo $testroot/wt > /dev/null - ret=$? - if [ $ret -ne 0 ]; then - test_done $testroot $ret - return 1 - fi - - cat < $testroot/wt/patch ---- alpha -+++ alpha -@@ -1 +1 @@ --alpha -+cafe alpha ---- beta -+++ /dev/null -@@ -1 +0,0 @@ --beta ---- gamma/delta -+++ gamma/delta.new -@@ -1 +1 @@ --delta -+delta updated and renamed! -EOF - - (cd $testroot/wt && got patch -n patch) - ret=$? - if [ $ret -ne 0 ]; then - test_done $testroot $ret - return 1 - fi - - # remove the patch to avoid the ? entry - rm $testroot/wt/patch - - (cd $testroot/wt && got status) > $testroot/stdout - ret=$? - if [ $ret -ne 0 ]; then - test_done $testroot $ret - return 1 - fi - - 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 - fi - test_done $testroot $ret -} - test_parseargs "$@" run_test test_patch_simple_add_file run_test test_patch_simple_rm_file @@ -980,5 +781,3 @@ 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 -run_test test_patch_nop