aboutsummaryrefslogblamecommitdiffstats
path: root/libpatch_parse_ed_patch.c
blob: f3dec51b021f4fcaf0238be7e747abeaeda2ee58 (plain) (tree)




















































































































































                                                                                                                   
/* See LICENSE file for copyright and license details. */
#include "common.h"


static size_t
hunk_head(const char *text, size_t len, size_t *first, size_t *last)
{
	size_t off = 1, r;
	off += r = libpatch_get_zu__(&text[off], len - off, first);
	if (!r)
		return 0;
	if (off < len && text[off] == ',') {
		off += 1;
		off += r = libpatch_get_zu__(&text[off], len - off, last);
		if (!r)
			return 0;
	} else {
		*last = *first;
	}
	return off;
}


int
libpatch_parse_ed_patch(const char *text, size_t textlen, size_t *textend,
                        struct libpatch_patch **patch, size_t *patchlen)
{
	const char *patchtext = text;
	size_t len = 0, size = 0, off, line, last, count;
	int in_hunk = 0, can_continue = 0, has_patch = 0;
	size_t *hunks = NULL, *new;
	size_t nhunks = 0, hunks_size = 0;
	struct libpatch_patch *patchcopy = NULL;
	size_t patchcopysize = 0;

	*patch = NULL;
	*patchlen = 0;

	while ((len = libpatch_next_line__(&text, &textlen, len))) {
		if (can_continue) {
			can_continue = 0;
			if (len == 1 && text[0] == 'a') {
				in_hunk = 1;
			} else if (len == 1 && text[0] == 'i') {
				goto garbage; /* TODO */
			} else if (len >= 5 && !strncmp(text, "s/", 2) && text[len - 1] == '/') {
				if (*patchlen < 2 || !IS_LINE((*patch)[*patchlen - 2].type))
					goto garbage;
				/* TODO add support for more weird alternatives */
				if (!(len == 5 && !strncmp(text, "s/.//", len)) &&
				    !(len == 6 && !strncmp(text, "s/^.//", len)) &&
				    !(len == 6 && !strncmp(text, "s/\\.//", len)) &&
				    !(len == 7 && !strncmp(text, "s/^\\.//", len)))
					goto garbage;
				(*patch)[*patchlen - 2].text_offset += 1;
				(*patch)[*patchlen - 2].text_length -= 1;
				APPEND_STRING(len, LIBPATCH_PATCH_SYNTACTICAL);
			} else {
				goto else_if;
			}
			APPEND_STRING(len, LIBPATCH_PATCH_SYNTACTICAL);

		} else else_if: if (in_hunk && len == 1 && text[0] == '.') {
			APPEND_STRING(len, LIBPATCH_PATCH_SYNTACTICAL);
			in_hunk = 0;
			can_continue = 1;

		} else if (in_hunk) {
			APPEND_STRING(0, LIBPATCH_PATCH_FILE2_ONLY);
			(*patch)[*patchlen - 1].file1_lineno = line;

		} else if (len >= 5 && !strncmp(text, "diff ", 5)) {
			if (has_patch && textend)
				break;
			if (libpatch_reverse_hunks__(*patch, *patchlen, hunks, nhunks, &patchcopy, &patchcopysize))
				goto fail;
			nhunks = 0;
			APPEND_STRING(5, LIBPATCH_PATCH_DIFF_LINE);

		} else if (len >= 2 && isdigit(text[0])) {
			off = hunk_head(text, len, &line, &last);
			if (len - off != 1)
				goto garbage;
			if (!line && last)
				goto garbage;
			else if (!line)
				count = 0, line = 1;
			else if (last < line)
				goto garbage;
			else
				count = last - line + 1;

			if (text[off] == 'd') {
				APPEND_NO_TEXT(LIBPATCH_PATCH_HUNK_WITH_DELETION);
				(*patch)[*patchlen - 1].first_line = line;
				(*patch)[*patchlen - 1].line_count = count;
			} else if (text[off] == 'a') {
				APPEND_NO_TEXT(LIBPATCH_PATCH_HUNK);
				in_hunk = 1;
			} else if (text[off] == 'c') {
				APPEND_NO_TEXT(LIBPATCH_PATCH_HUNK_WITH_DELETION);
				(*patch)[*patchlen - 1].first_line = line;
				(*patch)[*patchlen - 1].line_count = count;
				in_hunk = 1;
			} else if (text[off] == 'i') {
				goto garbage; /* TODO */
			} else {
				goto garbage;
			}

			if (nhunks == hunks_size) {
				if (hunks_size > SIZE_MAX / sizeof(*hunks) - 16)
					goto enomem;
				new = realloc(hunks, (hunks_size += 16) * sizeof(*hunks));
				if (!new)
					goto fail;
				hunks = new;
			}
			hunks[nhunks++] = *patchlen - 1;
			has_patch = 1;

		} else {
		garbage:
			APPEND_STRING(0, LIBPATCH_PATCH_GARBAGE);
			if (len == textlen)
				(*patch)[*patchlen - 1].lf_terminated = 0;
		}
	}

	if (libpatch_reverse_hunks__(*patch, *patchlen, hunks, nhunks, &patchcopy, &patchcopysize))
		goto fail;

	if (textend)
		*textend = (size_t)(text - patchtext);

	free(patchcopy);
	free(hunks);
	return 0;

enomem:
	errno = ENOMEM;
fail:
	free(patchcopy);
	free(hunks);
	free(*patch);
	*patch = NULL;
	*patchlen = 0;
	return -1;
}