2 * Copyright (c) 2022 Omar Polo <op@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 * Things that are still missing:
19 * + "No final newline" handling
21 * Things that we may want to support:
22 * + support indented patches?
23 * + support other kinds of patches?
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/socket.h>
41 #include "got_error.h"
42 #include "got_object.h"
44 #include "got_reference.h"
45 #include "got_cancel.h"
46 #include "got_worktree.h"
47 #include "got_opentemp.h"
48 #include "got_patch.h"
50 #include "got_lib_delta.h"
51 #include "got_lib_object.h"
52 #include "got_lib_privsep.h"
54 #define MIN(a, b) ((a) < (b) ? (a) : (b))
56 struct got_patch_hunk {
57 STAILQ_ENTRY(got_patch_hunk) entries;
70 STAILQ_HEAD(, got_patch_hunk) head;
73 static const struct got_error *
74 send_patch(struct imsgbuf *ibuf, int fd)
76 const struct got_error *err = NULL;
78 if (imsg_compose(ibuf, GOT_IMSG_PATCH_FILE, 0, 0, fd,
80 err = got_error_from_errno(
81 "imsg_compose GOT_IMSG_PATCH_FILE");
86 if (imsg_flush(ibuf) == -1) {
87 err = got_error_from_errno("imsg_flush");
95 patch_free(struct got_patch *p)
97 struct got_patch_hunk *h;
100 while (!STAILQ_EMPTY(&p->head)) {
101 h = STAILQ_FIRST(&p->head);
102 STAILQ_REMOVE_HEAD(&p->head, entries);
104 for (i = 0; i < h->len; ++i)
114 static const struct got_error *
115 pushline(struct got_patch_hunk *h, const char *line)
120 if (h->len == h->cap) {
121 if ((newcap = h->cap * 1.5) == 0)
123 t = recallocarray(h->lines, h->cap, newcap,
124 sizeof(h->lines[0]));
126 return got_error_from_errno("recallocarray");
131 if ((t = strdup(line)) == NULL)
132 return got_error_from_errno("strdup");
134 h->lines[h->len++] = t;
138 static const struct got_error *
139 recv_patch(struct imsgbuf *ibuf, int *done, struct got_patch *p)
141 const struct got_error *err = NULL;
143 struct got_imsg_patch_hunk hdr;
144 struct got_imsg_patch patch;
145 struct got_patch_hunk *h = NULL;
148 memset(p, 0, sizeof(*p));
149 STAILQ_INIT(&p->head);
151 err = got_privsep_recv_imsg(&imsg, ibuf, 0);
154 if (imsg.hdr.type == GOT_IMSG_PATCH_EOF) {
158 if (imsg.hdr.type != GOT_IMSG_PATCH) {
159 err = got_error(GOT_ERR_PRIVSEP_MSG);
162 datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
163 if (datalen != sizeof(patch)) {
164 err = got_error(GOT_ERR_PRIVSEP_LEN);
167 memcpy(&patch, imsg.data, sizeof(patch));
168 if (*patch.old != '\0' && (p->old = strdup(patch.old)) == NULL) {
169 err = got_error_from_errno("strdup");
172 if (*patch.new != '\0' && (p->new = strdup(patch.new)) == NULL) {
173 err = got_error_from_errno("strdup");
176 if (p->old == NULL && p->new == NULL) {
177 err = got_error(GOT_ERR_PATCH_MALFORMED);
186 err = got_privsep_recv_imsg(&imsg, ibuf, 0);
190 switch (imsg.hdr.type) {
191 case GOT_IMSG_PATCH_DONE:
193 case GOT_IMSG_PATCH_HUNK:
194 datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
195 if (datalen != sizeof(hdr)) {
196 err = got_error(GOT_ERR_PRIVSEP_LEN);
199 memcpy(&hdr, imsg.data, sizeof(hdr));
200 if ((h = calloc(1, sizeof(*h))) == NULL) {
201 err = got_error_from_errno("calloc");
204 h->old_from = hdr.oldfrom;
205 h->old_lines = hdr.oldlines;
206 h->new_from = hdr.newfrom;
207 h->new_lines = hdr.newlines;
208 STAILQ_INSERT_TAIL(&p->head, h, entries);
210 case GOT_IMSG_PATCH_LINE:
212 err = got_error(GOT_ERR_PRIVSEP_MSG);
215 datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
217 /* at least one char plus newline */
218 if (datalen < 2 || t[datalen-1] != '\0') {
219 err = got_error(GOT_ERR_PRIVSEP_MSG);
222 if (*t != ' ' && *t != '-' && *t != '+') {
223 err = got_error(GOT_ERR_PRIVSEP_MSG);
226 err = pushline(h, t);
231 err = got_error(GOT_ERR_PRIVSEP_MSG);
244 * Copy data from orig starting at copypos until pos into tmp.
245 * If pos is -1, copy until EOF.
247 static const struct got_error *
248 copy(FILE *tmp, FILE *orig, off_t copypos, off_t pos)
253 if (fseek(orig, copypos, SEEK_SET) == -1)
254 return got_error_from_errno("fseek");
256 while (pos == -1 || copypos < pos) {
259 len = MIN(len, (size_t)pos - copypos);
260 r = fread(buf, 1, len, orig);
261 if (r != len && ferror(orig))
262 return got_error_from_errno("fread");
263 w = fwrite(buf, 1, r, tmp);
265 return got_error_from_errno("fwrite");
267 if (r != len && feof(orig)) {
270 return got_error(GOT_ERR_PATCH_DONT_APPLY);
276 static const struct got_error *
277 locate_hunk(FILE *orig, struct got_patch_hunk *h, long *lineno)
279 const struct got_error *err = NULL;
281 char mode = *h->lines[0];
285 long match_lineno = -1;
288 linelen = getline(&line, &linesize, orig);
291 err = got_error_from_errno("getline");
292 else if (match == -1)
293 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
298 if ((mode == ' ' && !strcmp(h->lines[0]+1, line)) ||
299 (mode == '-' && !strcmp(h->lines[0]+1, line)) ||
300 (mode == '+' && *lineno == h->old_from)) {
301 match = ftello(orig);
303 err = got_error_from_errno("ftello");
307 match_lineno = (*lineno)-1;
310 if (*lineno >= h->old_from && match != -1)
315 *lineno = match_lineno;
316 if (fseek(orig, match, SEEK_SET) == -1)
317 err = got_error_from_errno("fseek");
324 static const struct got_error *
325 test_hunk(FILE *orig, struct got_patch_hunk *h)
327 const struct got_error *err = NULL;
329 size_t linesize = 0, i = 0;
332 for (i = 0; i < h->len; ++i) {
333 switch (*h->lines[i]) {
338 linelen = getline(&line, &linesize, orig);
341 err = got_error_from_errno("getline");
344 GOT_ERR_PATCH_DONT_APPLY);
347 if (strcmp(h->lines[i]+1, line)) {
348 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
360 static const struct got_error *
361 apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *lineno)
365 for (i = 0; i < h->len; ++i) {
366 switch (*h->lines[i]) {
368 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
369 return got_error_from_errno("fprintf");
375 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
376 return got_error_from_errno("fprintf");
383 static const struct got_error *
384 patch_file(struct got_patch *p, const char *path, FILE *tmp)
386 const struct got_error *err = NULL;
387 struct got_patch_hunk *h;
396 if (p->old == NULL) { /* create */
397 h = STAILQ_FIRST(&p->head);
398 if (h == NULL || STAILQ_NEXT(h, entries) != NULL)
399 return got_error(GOT_ERR_PATCH_MALFORMED);
400 for (i = 0; i < h->len; ++i) {
401 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
402 return got_error_from_errno("fprintf");
407 if ((orig = fopen(path, "r")) == NULL) {
408 err = got_error_from_errno2("fopen", path);
413 STAILQ_FOREACH(h, &p->head, entries) {
414 if (h->lines == NULL)
418 err = locate_hunk(orig, h, &lineno);
421 if ((pos = ftello(orig)) == -1) {
422 err = got_error_from_errno("ftello");
425 err = copy(tmp, orig, copypos, pos);
430 err = test_hunk(orig, h);
431 if (err != NULL && err->code == GOT_ERR_PATCH_DONT_APPLY) {
433 * try to apply the hunk again starting the search
434 * after the previous partial match.
436 if (fseek(orig, pos, SEEK_SET) == -1) {
437 err = got_error_from_errno("fseek");
440 linelen = getline(&line, &linesize, orig);
442 err = got_error_from_errno("getline");
451 err = apply_hunk(tmp, h, &lineno);
455 copypos = ftello(orig);
457 err = got_error_from_errno("ftello");
463 if (p->new == NULL) {
466 if (fstat(fileno(orig), &sb) == -1)
467 err = got_error_from_errno("fstat");
468 else if (sb.st_size != copypos)
469 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
470 } else if (!feof(orig))
471 err = copy(tmp, orig, copypos, -1);
479 static const struct got_error *
480 build_pathlist(const char *p, char **path, struct got_pathlist_head *head,
481 struct got_worktree *worktree)
483 const struct got_error *err;
484 struct got_pathlist_entry *pe;
486 err = got_worktree_resolve_path(path, worktree, p);
488 err = got_pathlist_insert(&pe, head, *path, NULL);
492 static const struct got_error *
493 can_rm(void *arg, unsigned char status, unsigned char staged_status,
494 const char *path, struct got_object_id *blob_id,
495 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
496 int dirfd, const char *de_name)
498 if (status == GOT_STATUS_NONEXISTENT)
499 return got_error_set_errno(ENOENT, path);
500 if (status != GOT_STATUS_NO_CHANGE &&
501 status != GOT_STATUS_ADD &&
502 status != GOT_STATUS_MODIFY &&
503 status != GOT_STATUS_MODE_CHANGE)
504 return got_error_path(path, GOT_ERR_FILE_STATUS);
505 if (staged_status == GOT_STATUS_DELETE)
506 return got_error_path(path, GOT_ERR_FILE_STATUS);
510 static const struct got_error *
511 can_add(void *arg, unsigned char status, unsigned char staged_status,
512 const char *path, struct got_object_id *blob_id,
513 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
514 int dirfd, const char *de_name)
516 if (status != GOT_STATUS_NONEXISTENT)
517 return got_error_path(path, GOT_ERR_FILE_STATUS);
521 static const struct got_error *
522 can_edit(void *arg, unsigned char status, unsigned char staged_status,
523 const char *path, struct got_object_id *blob_id,
524 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
525 int dirfd, const char *de_name)
527 if (status == GOT_STATUS_NONEXISTENT)
528 return got_error_set_errno(ENOENT, path);
529 if (status != GOT_STATUS_NO_CHANGE &&
530 status != GOT_STATUS_ADD &&
531 status != GOT_STATUS_MODIFY)
532 return got_error_path(path, GOT_ERR_FILE_STATUS);
533 if (staged_status == GOT_STATUS_DELETE)
534 return got_error_path(path, GOT_ERR_FILE_STATUS);
538 static const struct got_error *
539 check_file_status(struct got_patch *p, int file_renamed,
540 struct got_worktree *worktree, struct got_repository *repo,
541 struct got_pathlist_head *old, struct got_pathlist_head *new,
542 got_cancel_cb cancel_cb, void *cancel_arg)
544 static const struct got_error *err;
546 if (p->old != NULL && p->new == NULL)
547 return got_worktree_status(worktree, old, repo, 0,
548 can_rm, NULL, cancel_cb, cancel_arg);
549 else if (file_renamed) {
550 err = got_worktree_status(worktree, old, repo, 0,
551 can_rm, NULL, cancel_cb, cancel_arg);
554 return got_worktree_status(worktree, new, repo, 0,
555 can_add, NULL, cancel_cb, cancel_arg);
556 } else if (p->old == NULL)
557 return got_worktree_status(worktree, new, repo, 0,
558 can_add, NULL, cancel_cb, cancel_arg);
560 return got_worktree_status(worktree, new, repo, 0,
561 can_edit, NULL, cancel_cb, cancel_arg);
564 static const struct got_error *
565 apply_patch(struct got_worktree *worktree, struct got_repository *repo,
566 struct got_patch *p, got_worktree_delete_cb delete_cb, void *delete_arg,
567 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
570 const struct got_error *err = NULL;
571 struct got_pathlist_head oldpaths, newpaths;
572 int file_renamed = 0;
573 char *oldpath = NULL, *newpath = NULL;
574 char *tmppath = NULL, *template = NULL;
577 TAILQ_INIT(&oldpaths);
578 TAILQ_INIT(&newpaths);
580 err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath,
581 &oldpaths, worktree);
585 err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath,
586 &newpaths, worktree);
590 if (p->old != NULL && p->new != NULL && strcmp(p->old, p->new))
593 err = check_file_status(p, file_renamed, worktree, repo, &oldpaths,
594 &newpaths, cancel_cb, cancel_arg);
598 if (asprintf(&template, "%s/got-patch",
599 got_worktree_get_root_path(worktree)) == -1) {
600 err = got_error_from_errno(template);
604 err = got_opentemp_named(&tmppath, &tmp, template);
607 err = patch_file(p, oldpath, tmp);
611 if (p->old != NULL && p->new == NULL) {
612 err = got_worktree_schedule_delete(worktree, &oldpaths,
613 0, NULL, delete_cb, delete_arg, repo, 0, 0);
617 if (rename(tmppath, newpath) == -1) {
618 err = got_error_from_errno3("rename", tmppath, newpath);
623 err = got_worktree_schedule_delete(worktree, &oldpaths,
624 0, NULL, delete_cb, delete_arg, repo, 0, 0);
626 err = got_worktree_schedule_add(worktree, &newpaths,
627 add_cb, add_arg, repo, 1);
628 } else if (p->old == NULL)
629 err = got_worktree_schedule_add(worktree, &newpaths,
630 add_cb, add_arg, repo, 1);
632 printf("M %s\n", oldpath); /* XXX */
635 if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL))
641 got_pathlist_free(&oldpaths);
642 got_pathlist_free(&newpaths);
648 const struct got_error *
649 got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo,
650 got_worktree_delete_cb delete_cb, void *delete_arg,
651 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
654 const struct got_error *err = NULL;
655 struct imsgbuf *ibuf;
656 int imsg_fds[2] = {-1, -1};
660 ibuf = calloc(1, sizeof(*ibuf));
662 err = got_error_from_errno("calloc");
666 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
667 err = got_error_from_errno("socketpair");
673 err = got_error_from_errno("fork");
675 } else if (pid == 0) {
676 got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PATCH,
681 if (close(imsg_fds[1]) == -1) {
682 err = got_error_from_errno("close");
686 imsg_init(ibuf, imsg_fds[0]);
688 err = send_patch(ibuf, fd);
693 while (!done && err == NULL) {
696 err = recv_patch(ibuf, &done, &p);
700 err = apply_patch(worktree, repo, &p, delete_cb, delete_arg,
701 add_cb, add_arg, cancel_cb, cancel_arg);
708 if (fd != -1 && close(fd) == -1 && err == NULL)
709 err = got_error_from_errno("close");
712 if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
713 err = got_error_from_errno("close");
714 if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
715 err = got_error_from_errno("close");