Commit Diff


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 <<EOF > $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 <<EOF > $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