commit - 805253d5155091691f7cf36e54134cc87b2ea91a
commit + e9ce266e31923cc339954b331d273d9bba543f6f
blob - 8b431936ed43a5398c391d18637ae6dbc0f5348f
blob + 8dfd844f3da472b6ed040a62acaf85403cbc07ea
--- got/Makefile
+++ got/Makefile
diff_myers.c diff_output.c diff_output_plain.c \
diff_output_unidiff.c diff_output_edscript.c \
diff_patience.c send.c deltify.c pack_create.c dial.c \
- bloom.c murmurhash2.c ratelimit.c
+ bloom.c murmurhash2.c ratelimit.c patch.c
MAN = ${PROG}.1 got-worktree.5 git-repository.5 got.conf.5
blob - eaafde8411ebca559ac74b4970f3a9ee755e26b9
blob + 61596d1cfc534598b2516ffa737754cfb667abb1
--- got/got.1
+++ got/got.1
.It ! Ta versioned file expected on disk but missing
.El
.El
+.Tg pa
+.It Cm patch Op Ar patchfile
+.Dl Pq alias: Cm pa
+Apply changes from
+.Ar patchfile
+.Pq or standard input
+and record the state of the affected files afterwards.
+The content of
+.Ar patchfile
+must be an unified diff.
+If
+.Ar patchfile
+contains more than one patch,
+.Nm
+.Cm patch
+will try to apply them all.
+.Pp
+Show the status of each affected file, using the following status codes:
+.Bl -column XYZ description
+.It M Ta modified file
+.It D Ta deleted file
+.It A Ta added file
+.El
+.Pp
+If a change does not match at its exact line number,
+.Nm
+.Cm patch
+applies it somewhere else in the file if it can find a good spot before
+giving up.
.Tg rv
.It Cm revert Oo Fl p Oc Oo Fl F Ar response-script Oc Oo Fl R Oc Ar path ...
.Dl Pq alias: Cm rv
blob - 2c02ca33857bec5cf55e156d4e9cb8783514c705
blob + 3e66e251220b096cd82830703d4481ddfc399307
--- got/got.c
+++ got/got.c
#include "got_opentemp.h"
#include "got_gotconfig.h"
#include "got_dial.h"
+#include "got_patch.h"
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
__dead static void usage_tag(void);
__dead static void usage_add(void);
__dead static void usage_remove(void);
+__dead static void usage_patch(void);
__dead static void usage_revert(void);
__dead static void usage_commit(void);
__dead static void usage_send(void);
static const struct got_error* cmd_tag(int, char *[]);
static const struct got_error* cmd_add(int, char *[]);
static const struct got_error* cmd_remove(int, char *[]);
+static const struct got_error* cmd_patch(int, char *[]);
static const struct got_error* cmd_revert(int, char *[]);
static const struct got_error* cmd_commit(int, char *[]);
static const struct got_error* cmd_send(int, char *[]);
{ "tag", cmd_tag, usage_tag, "" },
{ "add", cmd_add, usage_add, "" },
{ "remove", cmd_remove, usage_remove, "rm" },
+ { "patch", cmd_patch, usage_patch, "pa" },
{ "revert", cmd_revert, usage_revert, "rv" },
{ "commit", cmd_commit, usage_commit, "ci" },
{ "send", cmd_send, usage_send, "se" },
TAILQ_FOREACH(pe, &paths, entry)
free((char *)pe->path);
got_pathlist_free(&paths);
+ free(cwd);
+ return error;
+}
+
+__dead static void
+usage_patch(void)
+{
+ fprintf(stderr, "usage: %s patch [patchfile]\n",
+ getprogname());
+ exit(1);
+}
+
+static const struct got_error *
+patch_from_stdin(int *patchfd)
+{
+ const struct got_error *err = NULL;
+ ssize_t r;
+ char *path, buf[BUFSIZ];
+ sig_t sighup, sigint, sigquit;
+
+ err = got_opentemp_named_fd(&path, patchfd,
+ GOT_TMPDIR_STR "/got-patch");
+ if (err)
+ return err;
+ unlink(path);
+ free(path);
+
+ sighup = signal(SIGHUP, SIG_DFL);
+ sigint = signal(SIGINT, SIG_DFL);
+ sigquit = signal(SIGQUIT, SIG_DFL);
+
+ for (;;) {
+ r = read(0, buf, sizeof(buf));
+ if (r == -1) {
+ err = got_error_from_errno("read");
+ break;
+ }
+ if (r == 0)
+ break;
+ if (write(*patchfd, buf, r) == -1) {
+ err = got_error_from_errno("write");
+ break;
+ }
+ }
+
+ signal(SIGHUP, sighup);
+ signal(SIGINT, sigint);
+ signal(SIGQUIT, sigquit);
+
+ if (err != NULL)
+ close(*patchfd);
+ return NULL;
+}
+
+static const struct got_error *
+cmd_patch(int argc, char *argv[])
+{
+ const struct got_error *error = NULL, *close_error = NULL;
+ struct got_worktree *worktree = NULL;
+ struct got_repository *repo = NULL;
+ char *cwd = NULL;
+ int ch;
+ int patchfd;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ usage_patch();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ error = patch_from_stdin(&patchfd);
+ if (error)
+ return error;
+ } else if (argc == 1) {
+ patchfd = open(argv[0], O_RDONLY);
+ if (patchfd == -1) {
+ error = got_error_from_errno2("open", argv[0]);
+ return error;
+ }
+ } else
+ usage_patch();
+
+ if ((cwd = getcwd(NULL, 0)) == NULL) {
+ error = got_error_from_errno("getcwd");
+ goto done;
+ }
+
+ error = got_worktree_open(&worktree, cwd);
+ if (error != NULL)
+ goto done;
+
+ const char *repo_path = got_worktree_get_repo_path(worktree);
+ error = got_repo_open(&repo, repo_path, NULL);
+ if (error != NULL)
+ goto done;
+
+ error = apply_unveil(got_repo_get_path(repo), 0,
+ worktree ? got_worktree_get_root_path(worktree) : NULL);
+ if (error != NULL)
+ goto done;
+
+#ifndef PROFILE
+ if (pledge("stdio rpath wpath cpath proc exec sendfd flock",
+ NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ error = got_patch(patchfd, worktree, repo, &print_remove_status,
+ &add_progress);
+
+done:
+ if (repo) {
+ close_error = got_repo_close(repo);
+ if (error == NULL)
+ error = close_error;
+ }
+ if (worktree != NULL) {
+ close_error = got_worktree_close(worktree);
+ if (error == NULL)
+ error = close_error;
+ }
free(cwd);
return error;
}
return NULL;
}
-
static const struct got_error *
cmd_revert(int argc, char *argv[])
{
blob - bfdc8fac28522667c8ec28af0e4485c8e46a75a3
blob + 64f2cb93558b933d2ffdcc0da7dedecf78d8ee52
--- include/got_error.h
+++ include/got_error.h
#define GOT_ERR_MERGE_BUSY 144
#define GOT_ERR_MERGE_PATH 145
#define GOT_ERR_FILE_BINARY 146
+#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
static const struct got_error {
int code;
{ GOT_ERR_MERGE_PATH, "cannot merge branch which contains "
"changes outside of this work tree's path prefix" },
{ GOT_ERR_FILE_BINARY, "found a binary file instead of text" },
+ { 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 - /dev/null
blob + 3f56d45c54c3ff202d4e7db59288e3ec6717ed78 (mode 644)
--- /dev/null
+++ include/got_patch.h
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Apply the (already opened) patch to the repository and register the
+ * status of the added and removed files.
+ *
+ * The patch file descriptor *must* be seekable.
+ */
+const struct got_error *
+got_patch(int, struct got_worktree *, struct got_repository *,
+ got_worktree_delete_cb, got_worktree_checkout_cb);
blob - 274e89878290befef48084afc0ae191cd5c36b16
blob + fef20e3a85c35f0faa4743d896a34ac04f0a4397
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
#define GOT_PROG_READ_PACK got-read-pack
#define GOT_PROG_READ_GITCONFIG got-read-gitconfig
#define GOT_PROG_READ_GOTCONFIG got-read-gotconfig
+#define GOT_PROG_READ_PATCH got-read-patch
#define GOT_PROG_FETCH_PACK got-fetch-pack
#define GOT_PROG_INDEX_PACK got-index-pack
#define GOT_PROG_SEND_PACK got-send-pack
GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_GITCONFIG)
#define GOT_PATH_PROG_READ_GOTCONFIG \
GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_GOTCONFIG)
+#define GOT_PATH_PROG_READ_PATCH \
+ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_PATCH)
#define GOT_PATH_PROG_FETCH_PACK \
GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_FETCH_PACK)
#define GOT_PATH_PROG_SEND_PACK \
GOT_IMSG_RAW_DELTA_OUTFD,
GOT_IMSG_RAW_DELTA_REQUEST,
GOT_IMSG_RAW_DELTA,
+
+ /* Messages related to patch files. */
+ GOT_IMSG_PATCH_FILE,
+ GOT_IMSG_PATCH_HUNK,
+ GOT_IMSG_PATCH_DONE,
+ GOT_IMSG_PATCH_LINE,
+ GOT_IMSG_PATCH,
+ GOT_IMSG_PATCH_EOF,
};
/* Structure for GOT_IMSG_ERROR. */
int nremotes; /* This many GOT_IMSG_GITCONFIG_REMOTE messages follow. */
};
+/*
+ * Structure for GOT_IMSG_PATCH data.
+ */
+struct got_imsg_patch {
+ char old[PATH_MAX];
+ char new[PATH_MAX];
+};
+
+/*
+ * Structure for GOT_IMSG_PATCH_HUNK data.
+ */
+struct got_imsg_patch_hunk {
+ long oldfrom;
+ long oldlines;
+ long newfrom;
+ long newlines;
+};
+
struct got_remote_repo;
struct got_pack;
struct got_packidx;
blob - /dev/null
blob + 84226a57dca7da7a69187a76d804e8ceda7558ba (mode 644)
--- /dev/null
+++ lib/patch.c
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Apply patches.
+ *
+ * Things that are still missing:
+ * + "No final newline" handling
+ *
+ * Things that we may want to support:
+ * + support indented patches?
+ * + support other kinds of patches?
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <limits.h>
+#include <sha1.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_path.h"
+#include "got_reference.h"
+#include "got_cancel.h"
+#include "got_worktree.h"
+#include "got_opentemp.h"
+#include "got_patch.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_privsep.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+struct got_patch_hunk {
+ STAILQ_ENTRY(got_patch_hunk) entries;
+ long old_from;
+ long old_lines;
+ long new_from;
+ long new_lines;
+ size_t len;
+ size_t cap;
+ char **lines;
+};
+
+struct got_patch {
+ char *old;
+ char *new;
+ STAILQ_HEAD(, got_patch_hunk) head;
+};
+
+static const struct got_error *
+send_patch(struct imsgbuf *ibuf, int fd)
+{
+ const struct got_error *err = NULL;
+
+ if (imsg_compose(ibuf, GOT_IMSG_PATCH_FILE, 0, 0, fd,
+ NULL, 0) == -1) {
+ err = got_error_from_errno(
+ "imsg_compose GOT_IMSG_PATCH_FILE");
+ close(fd);
+ return err;
+ }
+
+ if (imsg_flush(ibuf) == -1) {
+ err = got_error_from_errno("imsg_flush");
+ imsg_clear(ibuf);
+ }
+
+ return err;
+}
+
+static void
+patch_free(struct got_patch *p)
+{
+ struct got_patch_hunk *h;
+ size_t i;
+
+ while (!STAILQ_EMPTY(&p->head)) {
+ h = STAILQ_FIRST(&p->head);
+ STAILQ_REMOVE_HEAD(&p->head, entries);
+
+ for (i = 0; i < h->len; ++i)
+ free(h->lines[i]);
+ free(h->lines);
+ free(h);
+ }
+
+ free(p->new);
+ free(p->old);
+}
+
+static const struct got_error *
+pushline(struct got_patch_hunk *h, const char *line)
+{
+ void *t;
+ size_t newcap;
+
+ if (h->len == h->cap) {
+ if ((newcap = h->cap * 1.5) == 0)
+ newcap = 16;
+ t = recallocarray(h->lines, h->cap, newcap,
+ sizeof(h->lines[0]));
+ if (t == NULL)
+ return got_error_from_errno("recallocarray");
+ h->lines = t;
+ h->cap = newcap;
+ }
+
+ if ((t = strdup(line)) == NULL)
+ return got_error_from_errno("strdup");
+
+ h->lines[h->len++] = t;
+ return NULL;
+}
+
+static const struct got_error *
+recv_patch(struct imsgbuf *ibuf, int *done, struct got_patch *p)
+{
+ const struct got_error *err = NULL;
+ struct imsg imsg;
+ struct got_imsg_patch_hunk hdr;
+ struct got_imsg_patch patch;
+ struct got_patch_hunk *h = NULL;
+ size_t datalen;
+
+ memset(p, 0, sizeof(*p));
+ STAILQ_INIT(&p->head);
+
+ err = got_privsep_recv_imsg(&imsg, ibuf, 0);
+ if (err)
+ return err;
+ if (imsg.hdr.type == GOT_IMSG_PATCH_EOF) {
+ *done = 1;
+ goto done;
+ }
+ if (imsg.hdr.type != GOT_IMSG_PATCH) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+ datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(patch)) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ memcpy(&patch, imsg.data, sizeof(patch));
+ if (*patch.old != '\0' && (p->old = strdup(patch.old)) == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+ if (*patch.new != '\0' && (p->new = strdup(patch.new)) == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+
+ imsg_free(&imsg);
+
+ for (;;) {
+ char *t;
+
+ err = got_privsep_recv_imsg(&imsg, ibuf, 0);
+ if (err)
+ return err;
+
+ switch (imsg.hdr.type) {
+ case GOT_IMSG_PATCH_DONE:
+ goto done;
+ case GOT_IMSG_PATCH_HUNK:
+ datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(hdr)) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ memcpy(&hdr, imsg.data, sizeof(hdr));
+ if ((h = calloc(1, sizeof(*h))) == NULL) {
+ err = got_error_from_errno("calloc");
+ goto done;
+ }
+ h->old_from = hdr.oldfrom;
+ h->old_lines = hdr.oldlines;
+ h->new_from = hdr.newfrom;
+ h->new_lines = hdr.newlines;
+ STAILQ_INSERT_TAIL(&p->head, h, entries);
+ break;
+ case GOT_IMSG_PATCH_LINE:
+ if (h == NULL) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+ datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ t = imsg.data;
+ /* at least one char plus newline */
+ if (datalen < 2 || t[datalen-1] != '\0') {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+ if (*t != ' ' && *t != '-' && *t != '+') {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+ err = pushline(h, t);
+ if (err)
+ goto done;
+ break;
+ default:
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+
+ imsg_free(&imsg);
+ }
+
+done:
+ imsg_free(&imsg);
+ return err;
+}
+
+/*
+ * Copy data from orig starting at copypos until pos into tmp.
+ * If pos is -1, copy until EOF.
+ */
+static const struct got_error *
+copy(FILE *tmp, FILE *orig, off_t copypos, off_t pos)
+{
+ char buf[BUFSIZ];
+ size_t len, r, w;
+
+ if (fseek(orig, copypos, SEEK_SET) == -1)
+ return got_error_from_errno("fseek");
+
+ while (pos == -1 || copypos < pos) {
+ len = sizeof(buf);
+ if (pos > 0)
+ len = MIN(len, (size_t)pos - copypos);
+ r = fread(buf, 1, len, orig);
+ if (r != len && ferror(orig))
+ return got_error_from_errno("fread");
+ w = fwrite(buf, 1, r, tmp);
+ if (w != r)
+ return got_error_from_errno("fwrite");
+ copypos += len;
+ if (r != len && feof(orig)) {
+ if (pos == -1)
+ return NULL;
+ return got_error(GOT_ERR_PATCH_DONT_APPLY);
+ }
+ }
+ return NULL;
+}
+
+static const struct got_error *
+locate_hunk(FILE *orig, struct got_patch_hunk *h, long *lineno)
+{
+ const struct got_error *err = NULL;
+ char *line = NULL;
+ char mode = *h->lines[0];
+ size_t linesize = 0;
+ ssize_t linelen;
+ off_t match = -1;
+ long match_lineno = -1;
+
+ for (;;) {
+ linelen = getline(&line, &linesize, orig);
+ if (linelen == -1) {
+ if (ferror(orig))
+ err = got_error_from_errno("getline");
+ else if (match == -1)
+ err = got_error(GOT_ERR_PATCH_DONT_APPLY);
+ break;
+ }
+ (*lineno)++;
+
+ if ((mode == ' ' && !strcmp(h->lines[0]+1, line)) ||
+ (mode == '-' && !strcmp(h->lines[0]+1, line)) ||
+ (mode == '+' && *lineno == h->old_from)) {
+ match = ftello(orig);
+ if (match == -1) {
+ err = got_error_from_errno("ftello");
+ break;
+ }
+ match -= linelen;
+ match_lineno = (*lineno)-1;
+ }
+
+ if (*lineno >= h->old_from && match != -1)
+ break;
+ }
+
+ if (err == NULL) {
+ *lineno = match_lineno;
+ if (fseek(orig, match, SEEK_SET) == -1)
+ err = got_error_from_errno("fseek");
+ }
+
+ free(line);
+ return err;
+}
+
+static const struct got_error *
+test_hunk(FILE *orig, struct got_patch_hunk *h)
+{
+ const struct got_error *err = NULL;
+ char *line = NULL;
+ size_t linesize = 0, i = 0;
+ ssize_t linelen;
+
+ for (i = 0; i < h->len; ++i) {
+ switch (*h->lines[i]) {
+ case '+':
+ continue;
+ case ' ':
+ case '-':
+ linelen = getline(&line, &linesize, orig);
+ if (linelen == -1) {
+ if (ferror(orig))
+ err = got_error_from_errno("getline");
+ else
+ err = got_error(
+ GOT_ERR_PATCH_DONT_APPLY);
+ goto done;
+ }
+ if (strcmp(h->lines[i]+1, line)) {
+ err = got_error(GOT_ERR_PATCH_DONT_APPLY);
+ goto done;
+ }
+ break;
+ }
+ }
+
+done:
+ free(line);
+ return err;
+}
+
+static const struct got_error *
+apply_hunk(FILE *tmp, struct got_patch_hunk *h, long *lineno)
+{
+ size_t i = 0;
+
+ for (i = 0; i < h->len; ++i) {
+ switch (*h->lines[i]) {
+ case ' ':
+ if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
+ return got_error_from_errno("fprintf");
+ /* fallthrough */
+ case '-':
+ (*lineno)++;
+ break;
+ case '+':
+ if (fprintf(tmp, "%s", h->lines[i]+1) < 0)
+ return got_error_from_errno("fprintf");
+ break;
+ }
+ }
+ return NULL;
+}
+
+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,
+ got_worktree_checkout_cb add_cb)
+{
+ const struct got_error *err = NULL;
+ struct got_pathlist_head paths;
+ struct got_pathlist_entry *pe;
+ char *path = NULL, *tmppath = 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);
+
+ if (p->old == NULL && p->new == NULL)
+ return got_error(GOT_ERR_PATCH_MALFORMED);
+
+ 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 (p->old != NULL && p->new == NULL) {
+ /*
+ * special case: delete a file. don't try to match
+ * the lines but just schedule the removal.
+ */
+ err = got_worktree_schedule_delete(worktree, &paths,
+ 0, NULL, delete_cb, NULL, 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;
+ }
+
+ err = got_opentemp_named(&tmppath, &tmp,
+ got_worktree_get_root_path(worktree));
+ 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;
+ }
+ for (i = 0; i < h->len; ++i) {
+ if (fprintf(tmp, "%s", h->lines[i]+1) < 0) {
+ err = got_error_from_errno("fprintf");
+ goto done;
+ }
+ }
+ goto rename;
+ }
+
+ if ((orig = fopen(path, "r")) == NULL) {
+ err = got_error_from_errno2("fopen", path);
+ goto done;
+ }
+
+ copypos = 0;
+ STAILQ_FOREACH(h, &p->head, entries) {
+ tryagain:
+ err = locate_hunk(orig, h, &lineno);
+ if (err != NULL)
+ goto done;
+ if ((pos = ftello(orig)) == -1) {
+ err = got_error_from_errno("ftello");
+ goto done;
+ }
+ err = copy(tmp, orig, copypos, pos);
+ if (err != NULL)
+ goto done;
+ copypos = pos;
+
+ err = test_hunk(orig, h);
+ if (err != NULL && err->code == GOT_ERR_PATCH_DONT_APPLY) {
+ /*
+ * try to apply the hunk again starting the search
+ * after the previous partial match.
+ */
+ if (fseek(orig, pos, SEEK_SET) == -1) {
+ err = got_error_from_errno("fseek");
+ goto done;
+ }
+ linelen = getline(&line, &linesize, orig);
+ if (linelen == -1) {
+ err = got_error_from_errno("getline");
+ goto done;
+ }
+ lineno++;
+ goto tryagain;
+ }
+ if (err != NULL)
+ goto done;
+
+ err = apply_hunk(tmp, h, &lineno);
+ if (err != NULL)
+ goto done;
+
+ copypos = ftello(orig);
+ if (copypos == -1) {
+ err = got_error_from_errno("ftello");
+ goto done;
+ }
+ }
+
+ if (!feof(orig)) {
+ err = copy(tmp, orig, copypos, -1);
+ if (err)
+ goto done;
+ }
+
+rename:
+ if (rename(tmppath, path) == -1) {
+ err = got_error_from_errno3("rename", tmppath, path);
+ goto done;
+ }
+
+ if (p->old == NULL)
+ err = got_worktree_schedule_add(worktree, &paths,
+ add_cb, NULL, repo, 1);
+ else
+ printf("M %s\n", path); /* XXX */
+done:
+ 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);
+ return err;
+}
+
+const struct got_error *
+got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo,
+ got_worktree_delete_cb delete_cb, got_worktree_checkout_cb add_cb)
+{
+ const struct got_error *err = NULL;
+ struct imsgbuf *ibuf;
+ int imsg_fds[2] = {-1, -1};
+ int done = 0;
+ pid_t pid;
+
+ ibuf = calloc(1, sizeof(*ibuf));
+ if (ibuf == NULL) {
+ err = got_error_from_errno("calloc");
+ goto done;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
+ err = got_error_from_errno("socketpair");
+ goto done;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ err = got_error_from_errno("fork");
+ goto done;
+ } else if (pid == 0) {
+ got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PATCH,
+ NULL);
+ /* not reached */
+ }
+
+ if (close(imsg_fds[1]) == -1) {
+ err = got_error_from_errno("close");
+ goto done;
+ }
+ imsg_fds[1] = -1;
+ imsg_init(ibuf, imsg_fds[0]);
+
+ err = send_patch(ibuf, fd);
+ fd = -1;
+ if (err)
+ goto done;
+
+ while (!done && err == NULL) {
+ struct got_patch p;
+
+ err = recv_patch(ibuf, &done, &p);
+ if (err || done)
+ break;
+
+ err = apply_patch(worktree, repo, &p, delete_cb, add_cb);
+ patch_free(&p);
+ if (err)
+ break;
+ }
+
+done:
+ if (fd != -1 && close(fd) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (ibuf != NULL)
+ imsg_clear(ibuf);
+ if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ return err;
+}
blob - 4cbc40e6f66cdf7a166db6612c052858ffaba1b6
blob + 67b0e54997c29a12feaca8d74bc968633b1711b4
--- lib/privsep.c
+++ lib/privsep.c
GOT_PATH_PROG_READ_TAG,
GOT_PATH_PROG_READ_GITCONFIG,
GOT_PATH_PROG_READ_GOTCONFIG,
+ GOT_PATH_PROG_READ_PATCH,
GOT_PATH_PROG_FETCH_PACK,
GOT_PATH_PROG_INDEX_PACK,
GOT_PATH_PROG_SEND_PACK,
blob - 3783b56689f6ab58fbacbd8f0f990a7154d90f61
blob + cfd4876a2dfa135816bb51fb862396c0cd6a4331
--- libexec/Makefile
+++ libexec/Makefile
SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \
got-read-tag got-fetch-pack got-index-pack got-read-pack \
- got-read-gitconfig got-read-gotconfig got-send-pack
+ got-read-gitconfig got-read-gotconfig got-send-pack \
+ got-read-patch
.include <bsd.subdir.mk>
blob - /dev/null
blob + 9eddbae60cbd3e82dc3178ffebc9903391caa40c (mode 644)
--- /dev/null
+++ libexec/got-read-patch/Makefile
+.PATH:${.CURDIR}/../../lib
+
+.include "../../got-version.mk"
+
+PROG= got-read-patch
+SRCS= got-read-patch.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+LDADD = -lz -lutil
+DPADD = ${LIBZ} ${LIBUTIL}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + ed5eb50b17c3c73f369b043bdf1ea72f54f5ff88 (mode 644)
--- /dev/null
+++ libexec/got-read-patch/got-read-patch.c
+/*
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <paths.h>
+#include <sha1.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "got_error.h"
+#include "got_object.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_privsep.h"
+
+struct imsgbuf ibuf;
+
+static const struct got_error *
+send_patch(const char *oldname, const char *newname)
+{
+ struct got_imsg_patch p;
+
+ memset(&p, 0, sizeof(p));
+
+ if (oldname != NULL)
+ strlcpy(p.old, oldname, sizeof(p.old));
+ if (newname != NULL)
+ strlcpy(p.new, newname, sizeof(p.new));
+
+ if (imsg_compose(&ibuf, GOT_IMSG_PATCH, 0, 0, -1,
+ &p, sizeof(p)) == -1)
+ return got_error_from_errno("imsg_compose GOT_IMSG_PATCH");
+ return NULL;
+}
+
+static const struct got_error *
+send_patch_done(void)
+{
+ if (imsg_compose(&ibuf, GOT_IMSG_PATCH_DONE, 0, 0, -1,
+ NULL, 0) == -1)
+ return got_error_from_errno("imsg_compose GOT_IMSG_PATCH_EOF");
+ if (imsg_flush(&ibuf) == -1)
+ return got_error_from_errno("imsg_flush");
+ return NULL;
+}
+
+/* based on fetchname from usr.bin/patch/util.c */
+static const struct got_error *
+filename(const char *at, char **name, int strip)
+{
+ char *fullname, *t;
+ int l, tab;
+
+ *name = NULL;
+ if (*at == '\0')
+ return NULL;
+
+ while (isspace((unsigned char)*at))
+ at++;
+
+ /* files can be created or removed by diffing against /dev/null */
+ if (!strncmp(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL)-1))
+ return NULL;
+
+ t = strdup(at);
+ if (t == NULL)
+ return got_error_from_errno("strdup");
+ *name = fullname = t;
+ tab = strchr(t, '\t') != NULL;
+
+ /* strip off path components and NUL-terminate */
+ for (l = strip;
+ *t != '\0' && ((tab && *t != '\t') || !isspace((unsigned char)*t));
+ ++t) {
+ if (t[0] == '/' && t[1] != '/' && t[1] != '\0')
+ if (--l >= 0)
+ *name = t+1;
+ }
+ *t = '\0';
+
+ *name = strdup(*name);
+ free(fullname);
+ if (*name == NULL)
+ return got_error_from_errno("strdup");
+ return NULL;
+}
+
+static const struct got_error *
+find_patch(FILE *fp)
+{
+ const struct got_error *err = NULL;
+ char *old = NULL, *new = NULL;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t linelen;
+ int create, git = 0;
+
+ while ((linelen = getline(&line, &linesize, fp)) != -1) {
+ /*
+ * Ignore the Index name like GNU and larry' patch,
+ * we don't have to follow POSIX.
+ */
+
+ if (git && !strncmp(line, "--- a/", 6)) {
+ free(old);
+ err = filename(line+6, &old, 0);
+ } else if (!strncmp(line, "--- ", 4)) {
+ free(old);
+ err = filename(line+4, &old, 0);
+ } else if (git && !strncmp(line, "+++ b/", 6)) {
+ free(new);
+ err = filename(line+6, &new, 0);
+ } else if (!strncmp(line, "+++ ", 4)) {
+ free(new);
+ err = filename(line+4, &new, 0);
+ } else if (!strncmp(line, "diff --git a/", 13))
+ git = 1;
+
+ if (err)
+ break;
+
+ if (!strncmp(line, "@@ -", 4)) {
+ create = !strncmp(line+4, "0,0", 3);
+ if ((old == NULL && new == NULL) ||
+ (!create && old == NULL))
+ err = got_error(GOT_ERR_PATCH_MALFORMED);
+ else
+ err = send_patch(old, new);
+
+ free(old);
+ free(new);
+
+ if (err)
+ break;
+
+ /* rewind to previous line */
+ if (fseek(fp, linelen * -1, SEEK_CUR) == -1)
+ err = got_error_from_errno("fseek");
+ break;
+ }
+ }
+
+ free(line);
+ if (ferror(fp) && err == NULL)
+ err = got_error_from_errno("getline");
+ if (feof(fp) && err == NULL)
+ err = got_error(GOT_ERR_NO_PATCH);
+ return err;
+}
+
+static const struct got_error *
+strtolnum(char **str, long *n)
+{
+ char *p, c;
+ const char *errstr;
+
+ for (p = *str; isdigit((unsigned char)*p); ++p)
+ /* nop */;
+
+ c = *p;
+ *p = '\0';
+
+ *n = strtonum(*str, 0, LONG_MAX, &errstr);
+ if (errstr != NULL)
+ return got_error(GOT_ERR_PATCH_MALFORMED);
+
+ *p = c;
+ *str = p;
+ return NULL;
+}
+
+static const struct got_error *
+parse_hdr(char *s, int *ok, struct got_imsg_patch_hunk *hdr)
+{
+ static const struct got_error *err = NULL;
+
+ *ok = 1;
+ if (strncmp(s, "@@ -", 4)) {
+ *ok = 0;
+ return NULL;
+ }
+
+ s += 4;
+ if (!*s)
+ return NULL;
+ err = strtolnum(&s, &hdr->oldfrom);
+ if (err)
+ return err;
+ if (*s == ',') {
+ s++;
+ err = strtolnum(&s, &hdr->oldlines);
+ if (err)
+ return err;
+ } else
+ hdr->oldlines = 1;
+
+ if (*s == ' ')
+ s++;
+
+ if (*s != '+' || !*++s)
+ return got_error(GOT_ERR_PATCH_MALFORMED);
+ err = strtolnum(&s, &hdr->newfrom);
+ if (err)
+ return err;
+ if (*s == ',') {
+ s++;
+ err = strtolnum(&s, &hdr->newlines);
+ if (err)
+ return err;
+ } else
+ hdr->newlines = 1;
+
+ if (*s == ' ')
+ s++;
+
+ if (*s != '@')
+ return got_error(GOT_ERR_PATCH_MALFORMED);
+
+ if (hdr->oldfrom >= LONG_MAX - hdr->oldlines ||
+ hdr->newfrom >= LONG_MAX - hdr->newlines ||
+ /* not so sure about this one */
+ hdr->oldlines >= LONG_MAX - hdr->newlines - 1)
+ return got_error(GOT_ERR_PATCH_MALFORMED);
+
+ if (hdr->oldlines == 0) {
+ /* larry says to "do append rather than insert"; I don't
+ * quite get it, but i trust him.
+ */
+ hdr->oldfrom++;
+ }
+
+ if (imsg_compose(&ibuf, GOT_IMSG_PATCH_HUNK, 0, 0, -1,
+ hdr, sizeof(*hdr)) == -1)
+ return got_error_from_errno(
+ "imsg_compose GOT_IMSG_PATCH_HUNK");
+ return NULL;
+}
+
+static const struct got_error *
+send_line(const char *line)
+{
+ static const struct got_error *err = NULL;
+ char *p = NULL;
+
+ if (*line != '+' && *line != '-' && *line != ' ') {
+ if (asprintf(&p, " %s", line) == -1)
+ return got_error_from_errno("asprintf");
+ line = p;
+ }
+
+ if (imsg_compose(&ibuf, GOT_IMSG_PATCH_LINE, 0, 0, -1,
+ line, strlen(line)+1) == -1)
+ err = got_error_from_errno(
+ "imsg_compose GOT_IMSG_PATCH_LINE");
+
+ free(p);
+ return err;
+}
+
+static const struct got_error *
+parse_hunk(FILE *fp, int *ok)
+{
+ static const struct got_error *err = NULL;
+ struct got_imsg_patch_hunk hdr;
+ char *line = NULL, ch;
+ size_t linesize = 0;
+ ssize_t linelen;
+ long leftold, leftnew;
+
+ linelen = getline(&line, &linesize, fp);
+ if (linelen == -1) {
+ *ok = 0;
+ goto done;
+ }
+
+ err = parse_hdr(line, ok, &hdr);
+ if (err)
+ goto done;
+ if (!*ok) {
+ if (fseek(fp, linelen * -1, SEEK_CUR) == -1)
+ err = got_error_from_errno("fseek");
+ goto done;
+ }
+
+ leftold = hdr.oldlines;
+ leftnew = hdr.newlines;
+
+ while (leftold > 0 || leftnew > 0) {
+ linelen = getline(&line, &linesize, fp);
+ if (linelen == -1) {
+ if (ferror(fp)) {
+ err = got_error_from_errno("getline");
+ goto done;
+ }
+
+ /* trailing newlines may be chopped */
+ if (leftold < 3 && leftnew < 3) {
+ *ok = 0;
+ break;
+ }
+
+ err = got_error(GOT_ERR_PATCH_TRUNCATED);
+ goto done;
+ }
+
+ /* usr.bin/patch allows '=' as context char */
+ if (*line == '=')
+ *line = ' ';
+
+ ch = *line;
+ if (ch == '\t' || ch == '\n')
+ ch = ' '; /* the space got eaten */
+
+ switch (ch) {
+ case '-':
+ leftold--;
+ break;
+ case ' ':
+ leftold--;
+ leftnew--;
+ break;
+ case '+':
+ leftnew--;
+ break;
+ default:
+ err = got_error(GOT_ERR_PATCH_MALFORMED);
+ goto done;
+ }
+
+ if (leftold < 0 || leftnew < 0) {
+ err = got_error(GOT_ERR_PATCH_MALFORMED);
+ goto done;
+ }
+
+ err = send_line(line);
+ if (err)
+ goto done;
+ }
+
+done:
+ free(line);
+ return err;
+}
+
+static const struct got_error *
+read_patch(struct imsgbuf *ibuf, int fd)
+{
+ const struct got_error *err = NULL;
+ FILE *fp;
+ int ok, patch_found = 0;
+
+ if ((fp = fdopen(fd, "r")) == NULL) {
+ err = got_error_from_errno("fdopen");
+ close(fd);
+ return err;
+ }
+
+ while (!feof(fp)) {
+ err = find_patch(fp);
+ if (err)
+ goto done;
+
+ patch_found = 1;
+ for (;;) {
+ err = parse_hunk(fp, &ok);
+ if (err)
+ goto done;
+ if (!ok) {
+ err = send_patch_done();
+ if (err)
+ goto done;
+ break;
+ }
+ }
+ }
+
+done:
+ fclose(fp);
+
+ /* ignore trailing gibberish */
+ if (err != NULL && err->code == GOT_ERR_NO_PATCH && patch_found)
+ err = NULL;
+
+ return err;
+}
+
+int
+main(int argc, char **argv)
+{
+ const struct got_error *err = NULL;
+ struct imsg imsg;
+#if 0
+ static int attached;
+ while (!attached)
+ sleep(1);
+#endif
+
+ imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
+#ifndef PROFILE
+ /* revoke access to most system calls */
+ if (pledge("stdio recvfd", NULL) == -1) {
+ err = got_error_from_errno("pledge");
+ got_privsep_send_error(&ibuf, err);
+ return 1;
+ }
+#endif
+
+ err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
+ if (err)
+ goto done;
+ if (imsg.hdr.type != GOT_IMSG_PATCH_FILE || imsg.fd == -1) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+
+ err = read_patch(&ibuf, imsg.fd);
+ if (err)
+ goto done;
+ if (imsg_compose(&ibuf, GOT_IMSG_PATCH_EOF, 0, 0, -1,
+ NULL, 0) == -1) {
+ err = got_error_from_errno("imsg_compose GOT_IMSG_PATCH_EOF");
+ goto done;
+ }
+ err = got_privsep_flush_imsg(&ibuf);
+done:
+ imsg_free(&imsg);
+ if (err != NULL) {
+ got_privsep_send_error(&ibuf, err);
+ err = NULL;
+ }
+ if (close(GOT_IMSG_FD_CHILD) == -1 && err == NULL)
+ err = got_error_from_errno("close");
+ if (err && err->code != GOT_ERR_PRIVSEP_PIPE)
+ fprintf(stderr, "%s: %s\n", getprogname(), err->msg);
+ return err ? 1 : 0;
+}
blob - 54055c09da65df95bc8676121ad774abaed5f07c
blob + a1b33c05a7dbcb845170a3d4eabcc5a6cbc68802
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
REGRESS_TARGETS=checkout update status log add rm diff blame branch tag \
ref commit revert cherrypick backout rebase import histedit \
- integrate merge stage unstage cat clone fetch tree pack cleanup
+ integrate merge stage unstage cat clone fetch tree patch pack \
+ cleanup
NOOBJ=Yes
GOT_TEST_ROOT=/tmp
tree:
./tree.sh -q -r "$(GOT_TEST_ROOT)"
+patch:
+ ./patch.sh -q -r "$(GOT_TEST_ROOT)"
+
pack:
./pack.sh -q -r "$(GOT_TEST_ROOT)"
blob - /dev/null
blob + cb9ff81d665f65b182c6a4ecb6bd3b4185b0ad6d (mode 755)
--- /dev/null
+++ regress/cmdline/patch.sh
+#!/bin/sh
+#
+# Copyright (c) 2022 Omar Polo <op@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+. ./common.sh
+
+test_patch_simple_add_file() {
+ local testroot=`test_init patch_simple_add_file`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- /dev/null
++++ eta
+@@ -0,0 +1 @@
++eta
+EOF
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ echo "A eta" > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ echo eta > $testroot/wt/eta.expected
+ cmp -s $testroot/wt/eta.expected $testroot/wt/eta
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/eta.expected $testroot/wt/eta
+ fi
+ test_done $testroot $ret
+}
+
+test_patch_simple_rm_file() {
+ local testroot=`test_init patch_simple_rm_file`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ /dev/null
+@@ -1 +0,0 @@
+-alpha
+EOF
+
+ echo "D alpha" > $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ if [ -f $testroot/wt/alpha ]; then
+ ret=1
+ echo "alpha still exists!"
+ fi
+ test_done $testroot $ret
+}
+
+test_patch_simple_edit_file() {
+ local testroot=`test_init patch_simple_edit_file`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1 @@
+-alpha
++alpha is my favourite character
+EOF
+
+ echo "M alpha" > $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ echo 'alpha is my favourite character' > $testroot/wt/alpha.expected
+ cmp -s $testroot/wt/alpha.expected $testroot/wt/alpha
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/alpha.expected $testroot/wt/alpha
+ fi
+ test_done $testroot $ret
+}
+
+test_patch_prepend_line() {
+ local testroot=`test_init patch_prepend_line`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1,2 @@
++hatsuseno
+ alpha
+EOF
+
+ echo "M alpha" > $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ echo hatsuseno > $testroot/wt/alpha.expected
+ echo alpha >> $testroot/wt/alpha.expected
+ cmp -s $testroot/wt/alpha.expected $testroot/wt/alpha
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/alpha.expected $testroot/wt/alpha
+ fi
+ test_done $testroot $ret
+}
+
+test_patch_replace_line() {
+ local testroot=`test_init patch_replace_line`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ jot 10 > $testroot/wt/numbers
+ (cd $testroot/wt/ && got add numbers && got ci -m 'add numbers') \
+ >/dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- numbers
++++ numbers
+@@ -3,7 +3,7 @@
+ 3
+ 4
+ 5
+-6
++foo
+ 7
+ 8
+ 9
+EOF
+
+ echo "M numbers" > $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ jot 10 | sed 's/6/foo/' > $testroot/wt/numbers.expected
+ cmp -s $testroot/wt/numbers.expected $testroot/wt/numbers
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/numbers.expected $testroot/wt/numbers
+ fi
+ test_done $testroot $ret
+}
+
+test_patch_multiple_hunks() {
+ local testroot=`test_init patch_replace_multiple_lines`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ jot 100 > $testroot/wt/numbers
+ (cd $testroot/wt/ && got add numbers && got ci -m 'add numbers') \
+ >/dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- numbers
++++ numbers
+@@ -3,7 +3,7 @@
+ 3
+ 4
+ 5
+-6
++foo
+ 7
+ 8
+ 9
+@@ -57,7 +57,7 @@
+ 57
+ 58
+ 59
+-60
++foo foo
+ 61
+ 62
+ 63
+@@ -98,3 +98,6 @@
+ 98
+ 99
+ 100
++101
++102
++...
+EOF
+
+ echo "M numbers" > $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ jot 100 | sed -e 's/^6$/foo/' -e 's/^60$/foo foo/' \
+ > $testroot/wt/numbers.expected
+ echo "101" >> $testroot/wt/numbers.expected
+ echo "102" >> $testroot/wt/numbers.expected
+ echo "..." >> $testroot/wt/numbers.expected
+
+ cmp -s $testroot/wt/numbers.expected $testroot/wt/numbers
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/numbers.expected $testroot/wt/numbers
+ fi
+ test_done $testroot $ret
+}
+
+test_patch_multiple_files() {
+ local testroot=`test_init patch_multiple_files`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- alpha Mon Mar 7 19:02:07 2022
++++ alpha Mon Mar 7 19:01:53 2022
+@@ -1 +1,3 @@
++new
+ alpha
++available
+--- beta Mon Mar 7 19:02:11 2022
++++ beta Mon Mar 7 19:01:46 2022
+@@ -1 +1,3 @@
+ beta
++was
++improved
+--- gamma/delta Mon Mar 7 19:02:17 2022
++++ gamma/delta Mon Mar 7 19:01:37 2022
+@@ -1 +1 @@
+-delta
++delta new
+EOF
+
+ echo "M alpha" > $testroot/stdout.expected
+ echo "M beta" >> $testroot/stdout.expected
+ echo "M gamma/delta" >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testrot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ printf 'new\nalpha\navailable\n' > $testroot/wt/alpha.expected
+ printf 'beta\nwas\nimproved\n' > $testroot/wt/beta.expected
+ printf 'delta new\n' > $testroot/wt/gamma/delta.expected
+
+ for f in alpha beta gamma/delta; do
+ cmp -s $testroot/wt/$f.expected $testroot/wt/$f
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/$f.expected $testroot/wt/$f
+ test_done $testroot $ret
+ return 1
+ fi
+ done
+
+ test_done $testroot 0
+}
+
+test_patch_dont_apply() {
+ local testroot=`test_init patch_dont_apply`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1,2 @@
++hatsuseno
+ alpha something
+EOF
+
+ echo -n > $testroot/stdout.expected
+ echo "got: patch doesn't apply" > $testroot/stderr.expected
+
+ (cd $testroot/wt && got patch patch) \
+ > $testroot/stdout \
+ 2> $testroot/stderr
+ ret=$?
+ if [ $ret == 0 ]; then # should fail
+ test_done $testroot 1
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done $testroot $ret
+ return 1
+ fi
+
+ test_done $testroot $ret
+}
+
+test_patch_malformed() {
+ local testroot=`test_init patch_malformed`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ # missing "@@"
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1,2
++hatsuseno
+ alpha
+EOF
+
+ echo -n > $testroot/stdout.expected
+ echo "got: malformed patch" > $testroot/stderr.expected
+
+ (cd $testroot/wt && got patch patch) \
+ > $testroot/stdout \
+ 2> $testroot/stderr
+ ret=$?
+ if [ $ret == 0 ]; then
+ echo "got managed to apply an invalid patch"
+ test_done $testroot 1
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done $testroot $ret
+ return 1
+ fi
+
+ # wrong first character
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1,2 @@
++hatsuseno
+alpha
+EOF
+
+ (cd $testroot/wt && got patch patch) \
+ > $testroot/stdout \
+ 2> $testroot/stderr
+ ret=$?
+ if [ $ret == 0 ]; then
+ echo "got managed to apply an invalid patch"
+ test_done $testroot 1
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done $testroot $ret
+ return 1
+ fi
+
+ test_done $testroot $ret
+}
+
+test_patch_no_patch() {
+ local testroot=`test_init patch_no_patch`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+hello world!
+...
+
+some other nonsense
+...
+
+there's no patch in here!
+EOF
+
+ echo -n > $testroot/stdout.expected
+ echo "got: no patch found" > $testroot/stderr.expected
+
+ (cd $testroot/wt && got patch patch) \
+ > $testroot/stdout \
+ 2> $testroot/stderr
+ ret=$?
+ if [ $ret == 0 ]; then # should fail
+ test_done $testroot 1
+ return 1
+ fi
+
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done $testroot $ret
+ return 1
+ fi
+
+ test_done $testroot $ret
+}
+
+test_patch_equals_for_context() {
+ local testroot=`test_init patch_prepend_line`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cat <<EOF > $testroot/wt/patch
+--- alpha
++++ alpha
+@@ -1 +1,2 @@
++hatsuseno
+=alpha
+EOF
+
+ echo "M alpha" > $testroot/stdout.expected
+
+ (cd $testroot/wt && got patch patch) > $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ test_done $testroot $ret
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done $testroot $ret
+ return 1
+ fi
+
+ echo hatsuseno > $testroot/wt/alpha.expected
+ echo alpha >> $testroot/wt/alpha.expected
+ cmp -s $testroot/wt/alpha.expected $testroot/wt/alpha
+ ret=$?
+ if [ $ret != 0 ]; then
+ diff -u $testroot/wt/alpha.expected $testroot/wt/alpha
+ fi
+ test_done $testroot $ret
+}
+
+test_parseargs "$@"
+run_test test_patch_simple_add_file
+run_test test_patch_simple_rm_file
+run_test test_patch_simple_edit_file
+run_test test_patch_prepend_line
+run_test test_patch_replace_line
+run_test test_patch_multiple_hunks
+run_test test_patch_multiple_files
+run_test test_patch_dont_apply
+run_test test_patch_malformed
+run_test test_patch_no_patch
+run_test test_patch_equals_for_context