commit bb2ad8ff07ba26d01c541025d8daaa04723bd280 from: Omar Polo via: Thomas Adam date: Sun Mar 13 21:25:37 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 - 19f1a2acab41c8b9bb0860811443c055567afb5b commit + bb2ad8ff07ba26d01c541025d8daaa04723bd280 blob - b818e8ed1d67281ff8303209c637dc69135ccacf blob + e9c4fd901539661e6bda8434cb62f4033b7b2202 --- include/got_error.h +++ include/got_error.h @@ -167,8 +167,7 @@ #define GOT_ERR_PATCH_MALFORMED 147 #define GOT_ERR_PATCH_TRUNCATED 148 #define GOT_ERR_PATCH_DONT_APPLY 149 -#define GOT_ERR_PATCH_PATHS_DIFFER 150 -#define GOT_ERR_NO_PATCH 151 +#define GOT_ERR_NO_PATCH 150 static const struct got_error { int code; @@ -348,8 +347,6 @@ static const struct got_error { { GOT_ERR_PATCH_MALFORMED, "malformed patch" }, { GOT_ERR_PATCH_TRUNCATED, "patch truncated" }, { GOT_ERR_PATCH_DONT_APPLY, "patch doesn't apply" }, - { GOT_ERR_PATCH_PATHS_DIFFER, "the paths mentioned in the patch " - "are different." }, { GOT_ERR_NO_PATCH, "no patch found" }, }; blob - 0c9a36311c21f50d3aeedf9dbc1502def9d14425 blob + 126ca847c901495d37cbfb18e5a97a53f215178e --- lib/patch.c +++ lib/patch.c @@ -379,69 +379,65 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, long * } 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) +schedule_add(const char *path, struct got_worktree *worktree, + struct got_repository *repo, got_worktree_checkout_cb add_cb, + void *add_arg) { - const struct got_error *err = NULL; + static const struct got_error *err = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; - char *path = NULL, *tmppath = NULL, *template = NULL; - FILE *orig = NULL, *tmp = NULL; - struct got_patch_hunk *h; - size_t i; - long lineno = 0; - off_t copypos, pos; - char *line = NULL; - size_t linesize = 0; - ssize_t linelen; TAILQ_INIT(&paths); - err = got_worktree_resolve_path(&path, worktree, - p->new != NULL ? p->new : p->old); - if (err) - return err; err = got_pathlist_insert(&pe, &paths, path, NULL); - if (err) - goto done; + if (err == NULL) + err = got_worktree_schedule_add(worktree, &paths, + add_cb, add_arg, repo, 1); + got_pathlist_free(&paths); + return 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. - */ +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); - goto done; - } else if (p->old != NULL && strcmp(p->old, p->new)) { - err = got_error(GOT_ERR_PATCH_PATHS_DIFFER); - goto done; - } + got_pathlist_free(&paths); + return err; +} - if (asprintf(&template, "%s/got-patch", - got_worktree_get_root_path(worktree)) == -1) { - err = got_error_from_errno(template); - goto done; - } +static const struct got_error * +patch_file(struct got_patch *p, const char *path, FILE *tmp) +{ + const struct got_error *err = NULL; + struct got_patch_hunk *h; + size_t i; + long lineno = 0; + FILE *orig; + off_t copypos, pos; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; - err = got_opentemp_named(&tmppath, &tmp, template); - if (err) - goto done; - if (p->old == NULL) { /* create */ h = STAILQ_FIRST(&p->head); - if (h == NULL || STAILQ_NEXT(h, entries) != NULL) { - err = got_error(GOT_ERR_PATCH_MALFORMED); - goto done; - } + if (h == NULL || STAILQ_NEXT(h, entries) != NULL) + return got_error(GOT_ERR_PATCH_MALFORMED); for (i = 0; i < h->len; ++i) { - if (fprintf(tmp, "%s", h->lines[i]+1) < 0) { - err = got_error_from_errno("fprintf"); - goto done; - } + if (fprintf(tmp, "%s", h->lines[i]+1) < 0) + return got_error_from_errno("fprintf"); } - goto rename; + return err; } if ((orig = fopen(path, "r")) == NULL) { @@ -451,6 +447,9 @@ apply_patch(struct got_worktree *worktree, struct got_ copypos = 0; STAILQ_FOREACH(h, &p->head, entries) { + if (h->lines == NULL) + break; + tryagain: err = locate_hunk(orig, h, &lineno); if (err != NULL) @@ -496,40 +495,86 @@ apply_patch(struct got_worktree *worktree, struct got_ } } - if (!feof(orig)) { + if (!feof(orig)) err = copy(tmp, orig, copypos, -1); - if (err) - goto done; + +done: + if (orig != NULL) + fclose(orig); + return err; +} + +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) +{ + const struct got_error *err = NULL; + 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); + if (err) + goto done; + + 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) { + /* + * 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; } -rename: - if (rename(tmppath, path) == -1) { - err = got_error_from_errno3("rename", tmppath, path); + if (asprintf(&template, "%s/got-patch", + got_worktree_get_root_path(worktree)) == -1) { + err = got_error_from_errno(template); goto done; } - if (p->old == NULL) - err = got_worktree_schedule_add(worktree, &paths, - add_cb, add_arg, repo, 1); + err = got_opentemp_named(&tmppath, &tmp, template); + if (err) + goto done; + err = patch_file(p, oldpath, tmp); + if (err) + 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 = schedule_del(oldpath, worktree, repo, delete_cb, + delete_arg); + if (err == NULL) + err = schedule_add(newpath, worktree, repo, + add_cb, add_arg); + } else if (p->old == NULL) + err = schedule_add(newpath, worktree, repo, add_cb, + add_arg); else - printf("M %s\n", path); /* XXX */ + printf("M %s\n", oldpath); /* XXX */ + done: + if (err != NULL && (file_renamed || p->old == NULL)) + unlink(newpath); free(template); - if (err != NULL && p->old == NULL && path != NULL) - unlink(path); - if (tmp != NULL) - fclose(tmp); if (tmppath != NULL) unlink(tmppath); free(tmppath); - if (orig != NULL) { - if (p->old == NULL && err != NULL) - unlink(path); - fclose(orig); - } - free(path); - free(line); - got_pathlist_free(&paths); + free(oldpath); + free(newpath); return err; } blob - 7a5ad3671fa62d02f74a21cc4f0edf7be252462a blob + 5916921f260f5a0eaefdbf6036b813fbf3570dd5 --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -623,7 +623,113 @@ EOF fi test_done $testroot $ret } + +test_patch_rename() { + local testroot=`test_init patch_rename` + + 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 ++++ eta +@@ -0,0 +0,0 @@ +EOF + echo 'D alpha' > $testroot/stdout.expected + echo 'A eta' >> $testroot/stdout.expected + + (cd $testroot/wt && got patch patch) > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot $ret + return 1 + fi + + if [ -f $testroot/wt/alpha ]; then + echo "alpha was not removed" >&2 + test_done $testroot 1 + return 1 + fi + if [ ! -f $testroot/wt/eta ]; then + echo "eta was not created" >&2 + test_done $testroot 1 + return 1 + fi + + echo alpha > $testroot/wt/eta.expected + cmp -s $testroot/wt/eta.expected $testroot/wt/eta + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/wt/eta.expected $testroot/wt/eta + test_done $testroot $ret + return 1 + fi + + # revert the changes and try again with a rename + edit + (cd $testroot/wt && got revert alpha eta) > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + cat < $testroot/wt/patch +--- alpha ++++ eta +@@ -1 +1,2 @@ + alpha ++but now is eta +EOF + + (cd $testroot/wt && got patch patch) > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot $ret + return 1 + fi + + if [ -f $testroot/wt/alpha ]; then + echo "alpha was not removed" >&2 + test_done $testroot 1 + return 1 + fi + if [ ! -f $testroot/wt/eta ]; then + echo "eta was not created" >&2 + test_done $testroot 1 + return 1 + fi + + echo alpha > $testroot/wt/eta.expected + echo 'but now is eta' >> $testroot/wt/eta.expected + cmp -s $testroot/wt/eta.expected $testroot/wt/eta + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/wt/eta.expected $testroot/wt/eta + fi + test_done $testroot $ret +} + test_parseargs "$@" run_test test_patch_simple_add_file run_test test_patch_simple_rm_file @@ -636,3 +742,4 @@ run_test test_patch_dont_apply run_test test_patch_malformed run_test test_patch_no_patch run_test test_patch_equals_for_context +run_test test_patch_rename