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, off_t *pos, 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)
316 *lineno = match_lineno;
317 if (fseek(orig, match, SEEK_SET) == -1)
318 err = got_error_from_errno("fseek");
325 static const struct got_error *
326 test_hunk(FILE *orig, struct got_patch_hunk *h)
328 const struct got_error *err = NULL;
330 size_t linesize = 0, i = 0;
333 for (i = 0; i < h->len; ++i) {
334 switch (*h->lines[i]) {
339 linelen = getline(&line, &linesize, orig);
342 err = got_error_from_errno("getline");
345 GOT_ERR_PATCH_DONT_APPLY);
348 if (strcmp(h->lines[i]+1, line)) {
349 err = got_error(GOT_ERR_PATCH_DONT_APPLY);
361 static const struct got_error *
362 apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *lineno)
366 for (i = 0; i < h->len; ++i) {
367 switch (*h->lines[i]) {
369 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
370 return got_error_from_errno("fprintf");
376 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
377 return got_error_from_errno("fprintf");
384 static const struct got_error *
385 patch_file(struct got_patch *p, const char *path, FILE *tmp)
387 const struct got_error *err = NULL;
388 struct got_patch_hunk *h;
397 if (p->old == NULL) { /* create */
398 h = STAILQ_FIRST(&p->head);
399 if (h == NULL || STAILQ_NEXT(h, entries) != NULL)
400 return got_error(GOT_ERR_PATCH_MALFORMED);
401 for (i = 0; i < h->len; ++i) {
402 if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
403 return got_error_from_errno("fprintf");
408 if ((orig = fopen(path, "r")) == NULL) {
409 err = got_error_from_errno2("fopen", path);
414 STAILQ_FOREACH(h, &p->head, entries) {
415 if (h->lines == NULL)
419 err = locate_hunk(orig, h, &pos, &lineno);
422 err = copy(tmp, orig, copypos, pos);
427 err = test_hunk(orig, h);
428 if (err != NULL && err->code == GOT_ERR_PATCH_DONT_APPLY) {
430 * try to apply the hunk again starting the search
431 * after the previous partial match.
433 if (fseek(orig, pos, SEEK_SET) == -1) {
434 err = got_error_from_errno("fseek");
437 linelen = getline(&line, &linesize, orig);
439 err = got_error_from_errno("getline");
448 err = apply_hunk(tmp, h, &lineno);
452 copypos = ftello(orig);
454 err = got_error_from_errno("ftello");
460 err = copy(tmp, orig, copypos, -1);
468 static const struct got_error *
469 build_pathlist(const char *p, char **path, struct got_pathlist_head *head,
470 struct got_worktree *worktree)
472 const struct got_error *err;
473 struct got_pathlist_entry *pe;
475 err = got_worktree_resolve_path(path, worktree, p);
477 err = got_pathlist_insert(&pe, head, *path, NULL);
481 static const struct got_error *
482 can_rm(void *arg, unsigned char status, unsigned char staged_status,
483 const char *path, struct got_object_id *blob_id,
484 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
485 int dirfd, const char *de_name)
487 if (status == GOT_STATUS_NONEXISTENT)
488 return got_error_set_errno(ENOENT, path);
489 if (status != GOT_STATUS_NO_CHANGE &&
490 status != GOT_STATUS_ADD &&
491 status != GOT_STATUS_MODIFY &&
492 status != GOT_STATUS_MODE_CHANGE)
493 return got_error_path(path, GOT_ERR_FILE_STATUS);
494 if (staged_status == GOT_STATUS_DELETE)
495 return got_error_path(path, GOT_ERR_FILE_STATUS);
499 static const struct got_error *
500 can_add(void *arg, unsigned char status, unsigned char staged_status,
501 const char *path, struct got_object_id *blob_id,
502 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
503 int dirfd, const char *de_name)
505 if (status != GOT_STATUS_NONEXISTENT)
506 return got_error_path(path, GOT_ERR_FILE_STATUS);
510 static const struct got_error *
511 can_edit(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_set_errno(ENOENT, path);
518 if (status != GOT_STATUS_NO_CHANGE &&
519 status != GOT_STATUS_ADD &&
520 status != GOT_STATUS_MODIFY)
521 return got_error_path(path, GOT_ERR_FILE_STATUS);
522 if (staged_status == GOT_STATUS_DELETE)
523 return got_error_path(path, GOT_ERR_FILE_STATUS);
527 static const struct got_error *
528 check_file_status(struct got_patch *p, int file_renamed,
529 struct got_worktree *worktree, struct got_repository *repo,
530 struct got_pathlist_head *old, struct got_pathlist_head *new,
531 got_cancel_cb cancel_cb, void *cancel_arg)
533 static const struct got_error *err;
535 if (p->old != NULL && p->new == NULL)
536 return got_worktree_status(worktree, old, repo, 0,
537 can_rm, NULL, cancel_cb, cancel_arg);
538 else if (file_renamed) {
539 err = got_worktree_status(worktree, old, repo, 0,
540 can_rm, NULL, cancel_cb, cancel_arg);
543 return got_worktree_status(worktree, new, repo, 0,
544 can_add, NULL, cancel_cb, cancel_arg);
545 } else if (p->old == NULL)
546 return got_worktree_status(worktree, new, repo, 0,
547 can_add, NULL, cancel_cb, cancel_arg);
549 return got_worktree_status(worktree, new, repo, 0,
550 can_edit, NULL, cancel_cb, cancel_arg);
553 static const struct got_error *
554 apply_patch(struct got_worktree *worktree, struct got_repository *repo,
555 struct got_patch *p, got_worktree_delete_cb delete_cb, void *delete_arg,
556 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
559 const struct got_error *err = NULL;
560 struct got_pathlist_head oldpaths, newpaths;
561 int file_renamed = 0;
562 char *oldpath = NULL, *newpath = NULL;
563 char *tmppath = NULL, *template = NULL;
566 TAILQ_INIT(&oldpaths);
567 TAILQ_INIT(&newpaths);
569 err = build_pathlist(p->old != NULL ? p->old : p->new, &oldpath,
570 &oldpaths, worktree);
574 err = build_pathlist(p->new != NULL ? p->new : p->old, &newpath,
575 &newpaths, worktree);
579 if (p->old != NULL && p->new != NULL && strcmp(p->old, p->new))
582 err = check_file_status(p, file_renamed, worktree, repo, &oldpaths,
583 &newpaths, cancel_cb, cancel_arg);
587 if (p->old != NULL && p->new == NULL) {
589 * special case: delete a file. don't try to match
590 * the lines but just schedule the removal.
592 err = got_worktree_schedule_delete(worktree, &oldpaths,
593 0, NULL, delete_cb, delete_arg, repo, 0, 0);
597 if (asprintf(&template, "%s/got-patch",
598 got_worktree_get_root_path(worktree)) == -1) {
599 err = got_error_from_errno(template);
603 err = got_opentemp_named(&tmppath, &tmp, template);
606 err = patch_file(p, oldpath, tmp);
610 if (rename(tmppath, newpath) == -1) {
611 err = got_error_from_errno3("rename", tmppath, newpath);
616 err = got_worktree_schedule_delete(worktree, &oldpaths,
617 0, NULL, delete_cb, delete_arg, repo, 0, 0);
619 err = got_worktree_schedule_add(worktree, &newpaths,
620 add_cb, add_arg, repo, 1);
621 } else if (p->old == NULL)
622 err = got_worktree_schedule_add(worktree, &newpaths,
623 add_cb, add_arg, repo, 1);
625 printf("M %s\n", oldpath); /* XXX */
628 if (err != NULL && newpath != NULL && (file_renamed || p->old == NULL))
634 got_pathlist_free(&oldpaths);
635 got_pathlist_free(&newpaths);
641 const struct got_error *
642 got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo,
643 got_worktree_delete_cb delete_cb, void *delete_arg,
644 got_worktree_checkout_cb add_cb, void *add_arg, got_cancel_cb cancel_cb,
647 const struct got_error *err = NULL;
648 struct imsgbuf *ibuf;
649 int imsg_fds[2] = {-1, -1};
653 ibuf = calloc(1, sizeof(*ibuf));
655 err = got_error_from_errno("calloc");
659 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
660 err = got_error_from_errno("socketpair");
666 err = got_error_from_errno("fork");
668 } else if (pid == 0) {
669 got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PATCH,
674 if (close(imsg_fds[1]) == -1) {
675 err = got_error_from_errno("close");
679 imsg_init(ibuf, imsg_fds[0]);
681 err = send_patch(ibuf, fd);
686 while (!done && err == NULL) {
689 err = recv_patch(ibuf, &done, &p);
693 err = apply_patch(worktree, repo, &p, delete_cb, delete_arg,
694 add_cb, add_arg, cancel_cb, cancel_arg);
701 if (fd != -1 && close(fd) == -1 && err == NULL)
702 err = got_error_from_errno("close");
705 if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
706 err = got_error_from_errno("close");
707 if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
708 err = got_error_from_errno("close");