From ae850b1ac755f471beac4dbfef4654fe3fbaaae9 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Tue, 9 Jan 2024 22:04:24 +0100 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 14 + LICENSE | 15 + Makefile | 111 +++ README | 1 + TODO | 10 + common.h | 54 ++ config.mk | 8 + libpatch.h | 1233 +++++++++++++++++++++++++++++++ libpatch_append_to_patch__.c | 28 + libpatch_check_file.c | 15 + libpatch_create_timestamp.c | 58 ++ libpatch_create_timestamp_fd.c | 6 + libpatch_create_timestamp_frac_string.c | 21 + libpatch_create_timestamp_now.c | 73 ++ libpatch_create_timestamp_secs.c | 6 + libpatch_create_timestamp_stat.c | 6 + libpatch_create_timestamp_tm.c | 6 + libpatch_create_timestamp_ts.c | 6 + libpatch_create_timestamp_tv.c | 6 + libpatch_fd_diff2_printer__.c | 38 + libpatch_format_copied_patch.c | 109 +++ libpatch_format_ed_alternative_patch.c | 40 + libpatch_format_ed_patch.c | 58 ++ libpatch_format_normal_patch.c | 104 +++ libpatch_format_patch.c | 27 + libpatch_format_rcs_patch.c | 48 ++ libpatch_format_unified_patch.c | 86 +++ libpatch_get_label_format.c | 22 + libpatch_get_zu__.c | 17 + libpatch_guess_format.c | 121 +++ libpatch_is_devnull.c | 24 + libpatch_next_hunk.c | 31 + libpatch_next_line__.c | 19 + libpatch_parse_copied_patch.c | 153 ++++ libpatch_parse_ed_patch.c | 149 ++++ libpatch_parse_normal_patch.c | 140 ++++ libpatch_parse_patch.c | 26 + libpatch_parse_rcs_patch.c | 76 ++ libpatch_parse_timestamp.c | 47 ++ libpatch_parse_unified_patch.c | 111 +++ libpatch_plain_diff2_printer__.c | 81 ++ libpatch_plain_fd_diff2_printer.c | 15 + libpatch_plain_stream_diff2_printer.c | 15 + libpatch_previous_hunk.c | 34 + libpatch_reverse_diff2.c | 15 + libpatch_reverse_hunks__.c | 38 + libpatch_rewrite_diff2.c | 195 +++++ libpatch_stream_diff2_printer__.c | 39 + libpatch_style_requires_text.c | 22 + mk/linux.mk | 6 + mk/macos.mk | 6 + mk/windows.mk | 6 + 52 files changed, 3595 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 common.h create mode 100644 config.mk create mode 100644 libpatch.h create mode 100644 libpatch_append_to_patch__.c create mode 100644 libpatch_check_file.c create mode 100644 libpatch_create_timestamp.c create mode 100644 libpatch_create_timestamp_fd.c create mode 100644 libpatch_create_timestamp_frac_string.c create mode 100644 libpatch_create_timestamp_now.c create mode 100644 libpatch_create_timestamp_secs.c create mode 100644 libpatch_create_timestamp_stat.c create mode 100644 libpatch_create_timestamp_tm.c create mode 100644 libpatch_create_timestamp_ts.c create mode 100644 libpatch_create_timestamp_tv.c create mode 100644 libpatch_fd_diff2_printer__.c create mode 100644 libpatch_format_copied_patch.c create mode 100644 libpatch_format_ed_alternative_patch.c create mode 100644 libpatch_format_ed_patch.c create mode 100644 libpatch_format_normal_patch.c create mode 100644 libpatch_format_patch.c create mode 100644 libpatch_format_rcs_patch.c create mode 100644 libpatch_format_unified_patch.c create mode 100644 libpatch_get_label_format.c create mode 100644 libpatch_get_zu__.c create mode 100644 libpatch_guess_format.c create mode 100644 libpatch_is_devnull.c create mode 100644 libpatch_next_hunk.c create mode 100644 libpatch_next_line__.c create mode 100644 libpatch_parse_copied_patch.c create mode 100644 libpatch_parse_ed_patch.c create mode 100644 libpatch_parse_normal_patch.c create mode 100644 libpatch_parse_patch.c create mode 100644 libpatch_parse_rcs_patch.c create mode 100644 libpatch_parse_timestamp.c create mode 100644 libpatch_parse_unified_patch.c create mode 100644 libpatch_plain_diff2_printer__.c create mode 100644 libpatch_plain_fd_diff2_printer.c create mode 100644 libpatch_plain_stream_diff2_printer.c create mode 100644 libpatch_previous_hunk.c create mode 100644 libpatch_reverse_diff2.c create mode 100644 libpatch_reverse_hunks__.c create mode 100644 libpatch_rewrite_diff2.c create mode 100644 libpatch_stream_diff2_printer__.c create mode 100644 libpatch_style_requires_text.c create mode 100644 mk/linux.mk create mode 100644 mk/macos.mk create mode 100644 mk/windows.mk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a071ed4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fccd785 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2024 Mattias Andrée + +Permission to use, copy, modify, and/or 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..38d51dc --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OS = linux +# Linux: linux +# Mac OS: macos +# Windows: windows +include mk/$(OS).mk + + +LIB_MAJOR = 1 +LIB_MINOR = 0 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR) +LIB_NAME = patch + + +OBJ =\ + libpatch_append_to_patch__.o\ + libpatch_check_file.o\ + libpatch_create_timestamp.o\ + libpatch_create_timestamp_fd.o\ + libpatch_create_timestamp_frac_string.o\ + libpatch_create_timestamp_secs.o\ + libpatch_create_timestamp_stat.o\ + libpatch_create_timestamp_tm.o\ + libpatch_create_timestamp_ts.o\ + libpatch_create_timestamp_tv.o\ + libpatch_create_timestamp_now.o\ + libpatch_fd_diff2_printer__.o\ + libpatch_format_copied_patch.o\ + libpatch_format_ed_alternative_patch.o\ + libpatch_format_ed_patch.o\ + libpatch_format_normal_patch.o\ + libpatch_format_patch.o\ + libpatch_format_rcs_patch.o\ + libpatch_format_unified_patch.o\ + libpatch_get_label_format.o\ + libpatch_get_zu__.o\ + libpatch_guess_format.o\ + libpatch_is_devnull.o\ + libpatch_next_hunk.o\ + libpatch_next_line__.o\ + libpatch_parse_copied_patch.o\ + libpatch_parse_ed_patch.o\ + libpatch_parse_normal_patch.o\ + libpatch_parse_patch.o\ + libpatch_parse_rcs_patch.o\ + libpatch_parse_timestamp.o\ + libpatch_parse_unified_patch.o\ + libpatch_plain_diff2_printer__.o\ + libpatch_plain_fd_diff2_printer.o\ + libpatch_plain_stream_diff2_printer.o\ + libpatch_previous_hunk.o\ + libpatch_reverse_diff2.o\ + libpatch_reverse_hunks__.o\ + libpatch_rewrite_diff2.o\ + libpatch_stream_diff2_printer__.o\ + libpatch_style_requires_text.o + +HDR =\ + libpatch.h\ + common.h + +LOBJ = $(OBJ:.o=.lo) + + +all: libpatch.a libpatch.$(LIBEXT) +$(OBJ): $(HDR) +$(LOBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +.c.lo: + $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +libpatch.a: $(OBJ) + @rm -f -- $@ + $(AR) rc $@ $(OBJ) + $(AR) ts $@ > /dev/null + +libpatch.$(LIBEXT): $(LOBJ) + $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS) + +install: libpatch.a libpatch.$(LIBEXT) + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + cp -- libpatch.a "$(DESTDIR)$(PREFIX)/lib/" + cp -- libpatch.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBMINOREXT)" + $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBMINOREXT)" + ln -sf -- libpatch.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBMAJOREXT)" + ln -sf -- libpatch.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBEXT)" + cp -- libpatch.h "$(DESTDIR)$(PREFIX)/include/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libpatch.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBMAJOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBMINOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libpatch.$(LIBEXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/libpatch.h" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib + -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) + +.SUFFIXES: +.SUFFIXES: .lo .o .c + +.PHONY: all install uninstall clean diff --git a/README b/README new file mode 100644 index 0000000..072b7ea --- /dev/null +++ b/README @@ -0,0 +1 @@ +A library for parsing and formatting patch files diff --git a/TODO b/TODO new file mode 100644 index 0000000..bef94c1 --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ +Add tests +Add libpatch_patch_to_diff2 (should be able to store text pointers) +Add libpatch_reverse_patch +Add libpatch_reformat_{normal,copied,unified,ed,rcs}_patch +Add libpatch_reformat_patch +Add patch validation +Add patchset parsing +Add diff3 +Add man pages +Add git diff diff --git a/common.h b/common.h new file mode 100644 index 0000000..21e9ad8 --- /dev/null +++ b/common.h @@ -0,0 +1,54 @@ +/* See LICENSE file for copyright and license details. */ +#include "libpatch.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) + +#define IS_LINE(T) ((T) >= LIBPATCH_PATCH_CONTEXT && (T) <= LIBPATCH_PATCH_POSTCHANGE) + + +struct printer_internals { + void (*put)(struct libpatch_diff2_printer *this, const char *text, size_t len); + union { + FILE *stream; + int fd; + } f; +}; + + +void libpatch_plain_diff2_printer__(struct libpatch_diff2_printer *printer); +struct libpatch_diff2_printer *libpatch_stream_diff2_printer__(FILE *output); +struct libpatch_diff2_printer *libpatch_fd_diff2_printer__(int output); + +size_t libpatch_next_line__(const char **text, size_t *textlen, size_t lastlen); + +int libpatch_reverse_hunks__(struct libpatch_patch *patch, size_t patchlen, + const size_t *hunks, size_t nhunks, + struct libpatch_patch **patchcopy, size_t *patchcopysize); + +size_t libpatch_get_zu__(const char *text, size_t len, size_t *out); + +int libpatch_append_to_patch__(struct libpatch_patch **patch, size_t *patchlen, size_t *size, + size_t stroff, size_t strlen, int type); + +#define APPEND_STRING(OFF, TYPE)\ + do {\ + if (libpatch_append_to_patch__(patch, patchlen, &size,\ + (size_t)(&text[OFF] - patchtext),\ + len - (size_t)(OFF), (TYPE)))\ + goto fail;\ + } while (0) + +#define APPEND_OPTSTRING(OFF, TYPE)\ + APPEND_STRING((OFF) + ((OFF) < len && text[OFF] == ' '), (TYPE)) + +#define APPEND_NO_TEXT(TYPE)\ + APPEND_STRING(len, TYPE) diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..f4adf12 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = +LDFLAGS = diff --git a/libpatch.h b/libpatch.h new file mode 100644 index 0000000..ee7c1c9 --- /dev/null +++ b/libpatch.h @@ -0,0 +1,1233 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBPATCH_H +#define LIBPATCH_H + +#include +#include +#include +#include +#include +#include + + +/** + * Patch file format + */ +enum libpatch_style { + /** + * Style could not be determined + * + * Always invalid as function input + * (EINVAL error) + */ + LIBPATCH_STYLE_GARBAGE, + + /** + * “Normal” format + * + * See “Diff Default Output Format” + * in diff(1p) for details + * + * If nonstandard syntax is enabled, context + * will be printed with a “ ”-prefix + * + * As a common extension to the format, + * if a file does not end with a , + * “\” followed by some text will be printed + * after that line is included in the patch + */ + LIBPATCH_STYLE_NORMAL, + + /** + * “Copied” format + * + * See “Diff -c or -C Output Format” + * in diff(1p) for details + * + * As a common extension to the format, + * if a file does not end with a , + * “\” followed by some text will be printed + * after that line is included in the patch + */ + LIBPATCH_STYLE_COPIED, + + /** + * “Unified” format + * + * See “Diff -u or -U Output Format” + * in diff(1p) for details + * + * As a common extension to the format, + * if a file does not end with a , + * “\” followed by some text will be printed + * after that line is included in the patch + */ + LIBPATCH_STYLE_UNIFIED, + + /** + * ed(1) format + * + * See “Diff -e Output Format” + * in diff(1p) for details + */ + LIBPATCH_STYLE_ED, + + /** + * Alternative ed(1) format + * + * See “Diff -f Output Format” + * in diff(1p) for details + * + * Diff scripts in this format are usually + * ambiguous and can therefore not be parsed + */ + LIBPATCH_STYLE_ED_ALTERNATIVE, + + /** + * RCS format + * + * This is a non-POSIX format + */ + LIBPATCH_STYLE_RCS +}; + + +/** + * How a file label shall be formatted + */ +enum libpatch_label_format { + /** + * An error occured + */ + LIBPATCH_LABEL_ERROR = -1, + + /** + * The patch format does not support file labels + */ + LIBPATCH_LABEL_NONE, + + /** + * The patch format file label shall + * just be the filepath + */ + LIBPATCH_LABEL_FILE_ONLY, + + /** + * The patch format file label shall + * be the filepath followed by a regular + * space, followed by the file's mtime + */ + LIBPATCH_LABEL_FILE_SPACE_MTIME, + + /** + * The patch format file label shall + * be the filepath followed by a regular + * space, followed by the file's mtime + */ + LIBPATCH_LABEL_FILE_TAB_MTIME +}; + + +/** + * Partial text comparision + */ +struct libpatch_diff2 { + /** + * Which files are changed, or if + * the text included as context? + */ + enum { + /** + * Text appears in both file + */ + LIBPATCH_DIFF2_SKIP = 0x00, + + /** + * Text only appears in file 1 (removed) + */ + LIBPATCH_DIFF2_FILE1_ONLY = 0x01, + + /** + * Text only appears in file 2 (added) + */ + LIBPATCH_DIFF2_FILE2_ONLY = 0x02, + + /** + * Text is different between file 1 and file 2 + */ + LIBPATCH_DIFF2_TWO_FILE_CHANGE = 0x03, + + /** + * Same as `LIBPATCH_DIFF2_SKIP` except + * the text shall be included as context + * for a change + */ + LIBPATCH_DIFF2_CONTEXT = 1 << 2 + } change : CHAR_BIT; + + /** + * The number of consecutive units of + * text that `.change` apply to + */ + unsigned int repetition : sizeof(int) * CHAR_BIT - CHAR_BIT; +}; + + +/** + * Specifications for how a diff string shall be organised + */ +struct libpatch_diff2_spec { + /** + * The number of units of text that shall + * be included as context before for a change + */ + size_t context_before; + + /** + * If there are up to (inclusive) this number + * of units of texts between two changes, the + * two changes shall be included as one hunk + * + * If `.context_after + .context_before > .context_between`, + * `.context_after + .context_before` is effective + * used for `.context_between` + */ + size_t context_between; + + /** + * The number of units of text that shall + * be included as context after for a change + */ + size_t context_after; + + /** + * Whether to include all unchanged code as + * context. This is the same as setting + * `.context_before` and `.context_after` + * to `SIZE_MAX`, except that if there are + * no changes, the entire file is still used + * as context + */ + unsigned full_context : 1; + + /** + * If 0, keep each `LIBPATCH_DIFF2_FILE1_ONLY` + * and `LIBPATCH_DIFF2_FILE2_ONLY`. If 1, + * replace each pair of `LIBPATCH_DIFF2_FILE1_ONLY` + * and `LIBPATCH_DIFF2_FILE2_ONLY`, that appear + * in a consecutive group of changes, with a + * `LIBPATCH_DIFF2_TWO_FILE_CHANGE` + * + * Has no effect if `.two_file_change_order` is + * `LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES` or + * `LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES_INTERLEAVE` + */ + unsigned keep_split_changes : 1; + + enum { + /** + * Text that only appear in file 1 + * shall be included in the patch before + * any text that appears in file 2 + * (that is not indentical in file 1) + */ + LIBPATCH_DIFF2_FILE1_ONLY_FIRST, + + /** + * Text that only appear in file 2 + * shall be included in the patch before + * any text that appears in file 1 + * (that is not indentical in file 2) + */ + LIBPATCH_DIFF2_FILE2_ONLY_FIRST + } single_file_change_order : 1; + + enum { + /** + * Each `LIBPATCH_DIFF2_TWO_FILE_CHANGE` shall + * be rewritten as `LIBPATCH_DIFF2_FILE1_ONLY` + * and `LIBPATCH_DIFF2_FILE2_ONLY` (doubling the + * number of reported changes). Each group of + * `LIBPATCH_DIFF2_FILE1_ONLY` and + * `LIBPATCH_DIFF2_FILE2_ONLY` shall be sorted + * according to `.single_file_change_order` + */ + LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES, + + /** + * Same as `LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES_INTERLEAVE` + * except that, to the extent possible, there shall be + * and alternation between `LIBPATCH_DIFF2_FILE1_ONLY` + * and `LIBPATCH_DIFF2_FILE2_ONLY` + */ + LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES_INTERLEAVE, + + /** + * In consecutive changes, all + * `LIBPATCH_DIFF2_TWO_FILE_CHANGE`s shall + * be put in the front + */ + LIBPATCH_DIFF2_TWO_FILE_CHANGES_BEFORE_SINGLE_FILE, + + /** + * In consecutive changes, all + * `LIBPATCH_DIFF2_TWO_FILE_CHANGE`s shall + * be put after all of whichever shall appear + * first of `LIBPATCH_DIFF2_FILE1_ONLY` and + * `LIBPATCH_DIFF2_FILE2_ONLY`, but before + * the other ones + */ + LIBPATCH_DIFF2_TWO_FILE_CHANGES_BETWEEN_SINGLE_FILE, + + /** + * In consecutive changes, all + * `LIBPATCH_DIFF2_TWO_FILE_CHANGE`s shall + * be put in the back + */ + LIBPATCH_DIFF2_TWO_FILE_CHANGES_AFTER_SINGLE_FILE + } two_file_change_order : 3; +}; + + +/** + * A line from a patch file + */ +struct libpatch_patch { + /** + * What the line represents + */ + enum { + /** + * The line does not mean anything and + * should be ignored if at an appropriate + * location + */ + LIBPATCH_PATCH_GARBAGE, + + /** + * The line is simply require by the + * patch format and can be ignored + */ + LIBPATCH_PATCH_SYNTACTICAL, + + /** + * Line describing what files are compared + * and how (used in multi-file patchsets) + * + * The line's non-syntactical text is the + * command line arguments after "diff" + */ + LIBPATCH_PATCH_DIFF_LINE, + + /** + * The line's non-syntactical text is the first + * file's label (see `libpatch_get_label_format`) + */ + LIBPATCH_PATCH_FILE1_LABEL, + + /** + * The line's non-syntactical text is the second + * file's label (see `libpatch_get_label_format`) + */ + LIBPATCH_PATCH_FILE2_LABEL, + + /** + * Beginning of a hunk, `.first_line` and + * `.line_count` are set to indicate + * required deletion + * + * The line's non-syntactical text is context + * for the hunk intended for human reading + */ + LIBPATCH_PATCH_HUNK_WITH_DELETION, + + /** + * Beginning of a hunk + * + * The line's non-syntactical text is context + * for the hunk intended for human reading + */ + LIBPATCH_PATCH_HUNK, + + /** + * Beginning of text deletions in a hunk + * + * The line's non-syntactical text is context + * for the hunk intended for human reading + */ + LIBPATCH_PATCH_HUNK_FILE1, + + /** + * Beginning of text insertions in a hunk + * + * The line's non-syntactical text is context + * for the hunk intended for human reading + */ + LIBPATCH_PATCH_HUNK_FILE2, + + /** + * The line's non-syntactical text is + * context for an hunk + */ + LIBPATCH_PATCH_CONTEXT, + + /** + * The line's non-syntactical text is + * context for text deletion + */ + LIBPATCH_PATCH_FILE1_CONTEXT, + + /** + * The line's non-syntactical text is + * context for text insertion + */ + LIBPATCH_PATCH_FILE2_CONTEXT, + + /** + * The line's non-syntactical text is + * a line of text to delete + */ + LIBPATCH_PATCH_FILE1_ONLY, + + /** + * The line's non-syntactical text is + * a line of text to insert + */ + LIBPATCH_PATCH_FILE2_ONLY, + + /** + * The line's non-syntactical text is + * a line of text to delete for which + * there also is an insertion + */ + LIBPATCH_PATCH_PRECHANGE, + + /** + * The line's non-syntactical text is + * a line of text to insertion for which + * there also is an deletion + */ + LIBPATCH_PATCH_POSTCHANGE + } type : 7; + + /** + * Usually 1, but if the text line shall + * not be -terminated, it is 0 + */ + unsigned lf_terminated : 1; + + /** + * The byte in the patch file where + * the line's non-syntactical text begins + */ + size_t text_offset; + + /** + * The length of the line's non-syntactical text + */ + size_t text_length; + + /** + * The first line (counted from 1) to + * delete from the first line, or 0 if + * not applicable + */ + size_t first_line; + + /** + * The number of lines to delete from + * the first file + */ + size_t line_count; + + /** + * The line number (counted from 1) in the + * first file, or 0 if not applicable + */ + size_t file1_lineno; + + /** + * The line number (counted from 1) in the + * second file, or 0 if not applicable + */ + size_t file2_lineno; +}; + + +/** + * A line of text + */ +struct libpatch_line { + /** + * The number of bytes in the text + */ + size_t len; + + /** + * The text + */ + char text[]; +}; + + +/** + * Description of a compared file + */ +struct libpatch_file { + /** + * The file's label (see `libpatch_get_label_format`) + */ + const char *label; + + /** + * Whether the file ends with a + */ + unsigned lf_terminated; + + /** + * The number of lines in the file + */ + size_t nlines; + + /** + * Each line in the file, these are not terminated + */ + struct libpatch_line **lines; +}; + + +/** + * How a diff should be printed + */ +struct libpatch_diff2_printer { + /* TODO doc */ + void (*put_end_of_change)(struct libpatch_diff2_printer *this, const char *text); + void (*put_syntactical_escape)(struct libpatch_diff2_printer *this, const char *text); + void (*put_syntactical_garbage)(struct libpatch_diff2_printer *this, const char *text); + void (*put_hunk_head_prefix)(struct libpatch_diff2_printer *this, const char *text, int file /* 0 if both */); + void (*put_hunk_head_suffix)(struct libpatch_diff2_printer *this, const char *text, int file /* 0 if both */); + void (*put_hunk_start)(struct libpatch_diff2_printer *this, const char *prefix, size_t value, int file /* 1 or 2 */); + void (*put_hunk_end)(struct libpatch_diff2_printer *this, const char *prefix, size_t value, int file /* 1 or 2 */); + void (*put_hunk_length)(struct libpatch_diff2_printer *this, const char *prefix, size_t value, int file /* 1 or 2 */); + void (*put_hunk_operation)(struct libpatch_diff2_printer *this, const char *text, int file1, int file2); + void (*put_label_prefix)(struct libpatch_diff2_printer *this, const char *prefix, int file /* 1 or 2 */); + void (*put_label)(struct libpatch_diff2_printer *this, const char *label, int file /* 1 or 2 */); + void (*put_context_prefix)(struct libpatch_diff2_printer *this, const char *prefix, + int file1, int file2, size_t lineno); + void (*put_change_prefix)(struct libpatch_diff2_printer *this, const char *prefix, + int file /* 1 or 2 */, int two_file_change, size_t lineno); + + /** + * Output text from a compared file + * + * Fill be preceeded by one `.put_context_prefix` or + * one `.put_change_prefix`, even if the prefix text + * is empty, followed by any number of `.put_whitespace` + * + * @param this The container of this member function + * @param line The text to output + */ + void (*put_line)(struct libpatch_diff2_printer *this, struct libpatch_line *line); + + /** + * Called any time syntactical whitespace shall be + * output + * + * @param this The container of this member function + * @param text The text to output + */ + void (*put_whitespace)(struct libpatch_diff2_printer *this, const char *text); + + /** + * Called any time should be output + * + * @param this The container of this member function + */ + void (*put_newline)(struct libpatch_diff2_printer *this); + + /** + * Output a text that is used to mark that a line + * is not properly terminated with a + * + * @param this The container of this member function + * @param text The text to output + */ + void (*put_no_newline)(struct libpatch_diff2_printer *this, const char *text); + + /** + * If 1, patches with “unified” format shall will + * use text from the second file for context + * + * If 0, patches with “unified” format shall will + * use text from the first file for context + */ + unsigned use_file2_when_common : 1; + + /** + * If 0, the output shall be as portable as possible + * (changes that cannot be describe in standard format + * or would safely enough be interpreted as garbage + * and is very rare will still be included.) + * + * If 1, patches in “normal” format can include + * context, if 0, such context would be printed + * as changes (that don't actually change anything) + */ + unsigned use_nonstandard_syntax : 1; + + /** + * If an error is encountered, the errno(3) value is stored here + */ + int error; + + /** + * User-defined data (implementation-definied if set by the library) + */ + void *user_data; +}; + + +/** + * Create a `struct libpatch_diff2_printer` to + * print a patch file that is parsable by patch(1) + * (that is, without any colours or other additions + * to aid human reading) + * + * @param output The file stream to print to + * @return The printer, `NULL` on failure; deallocate with free(3) + * @throws ENOMEM Failed to allocate enough memory + */ +struct libpatch_diff2_printer *libpatch_plain_stream_diff2_printer(FILE *output); + +/** + * Create a `struct libpatch_diff2_printer` to + * print a patch file that is parsable by patch(1) + * (that is, without any colours or other additions + * to aid human reading) + * + * @param output The file descriptor to print to + * @return The printer, `NULL` on failure; deallocate with free(3) + * @throws ENOMEM Failed to allocate enough memory + */ +struct libpatch_diff2_printer *libpatch_plain_fd_diff2_printer(int output); + +/** + * Create file timestamp for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param tm The time to use in the timestamp + * @param subseconds The subseconds in the timestamp + * @param subseconds_decimals The number of decimals in the subseconds + * @param frac The number of subsecond decimals to include in the + * timestamp (of allowed), at most (the function may choose + * to include less) + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + */ +size_t libpatch_create_timestamp(char *buf, size_t bufsize, const struct tm *tm, + uintmax_t subseconds, unsigned subseconds_decimals, + unsigned frac, int zone, enum libpatch_style style); + +/** + * Create file timestamp for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param tm The time to use in the timestamp + * @param frac `NULL` or the string of subsecond decimals (any non-digit + * character is interpreted as a string termination) + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + */ +size_t libpatch_create_timestamp_frac_string(char *buf, size_t bufsize, const struct tm *tm, + const char *frac, int zone, enum libpatch_style style); + +/** + * Create file timestamp for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param tm The time to use in the timestamp + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + */ +inline size_t +libpatch_create_timestamp_tm(char *buf, size_t bufsize, const struct tm *tm, + int zone, enum libpatch_style style) +{ + return libpatch_create_timestamp(buf, bufsize, tm, 0, 0, 0, zone, style); +} + +/** + * Create file timestamp for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param ts The time to use in the timestamp + * @param frac The number of subsecond decimals to include in the + * timestamp (of allowed), at most (the function may choose + * to include less) + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from localtime(3) + */ +inline size_t +libpatch_create_timestamp_ts(char *buf, size_t bufsize, const struct timespec *ts, + unsigned frac, int zone, enum libpatch_style style) +{ + struct tm *tm, tmbuf; + tm = localtime_r(&ts->tv_sec, &tmbuf); + return tm ? libpatch_create_timestamp(buf, bufsize, tm, (uintmax_t)ts->tv_nsec, 9, frac, zone, style) : 0; +} + +/** + * Create file timestamp for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param tv The time to use in the timestamp + * @param frac The number of subsecond decimals to include in the + * timestamp (of allowed), at most (the function may choose + * to include less) + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from localtime(3) + */ +inline size_t +libpatch_create_timestamp_tv(char *buf, size_t bufsize, const struct timeval *tv, + unsigned frac, int zone, enum libpatch_style style) +{ + struct tm *tm, tmbuf; + tm = localtime_r(&tv->tv_sec, &tmbuf); + return tm ? libpatch_create_timestamp(buf, bufsize, tm, (uintmax_t)tv->tv_usec, 6, frac, zone, style) : 0; +} + +/** + * Create file timestamp for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param secs The time to use in the timestamp + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from localtime(3) + */ +inline size_t +libpatch_create_timestamp_secs(char *buf, size_t bufsize, time_t secs, + int zone, enum libpatch_style style) +{ + struct tm *tm, tmbuf; + tm = localtime_r(&secs, &tmbuf); + return tm ? libpatch_create_timestamp(buf, bufsize, tm, 0, 0, 0, zone, style) : 0; +} + +/** + * Create file timestamp, from file status, for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param st The file's status (mtime will be used) + * @param frac The number of subsecond decimals to include in the + * timestamp (of allowed), at most (the function may choose + * to include less) + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from localtime(3) + */ +inline size_t +libpatch_create_timestamp_stat(char *buf, size_t bufsize, const struct stat *st, + unsigned frac, int zone, enum libpatch_style style) +{ +#ifdef st_mtime /* = st_mtim.tv_sec, infers existance of st->st_mtim */ + return libpatch_create_timestamp_ts(buf, bufsize, &st->st_mtim, frac, zone, style); +#else + return libpatch_create_timestamp_secs(buf, bufsize, &st->st_mtime, zone, style); +#endif +} + +/** + * Create file timestamp, from a file descriptor, for diff header + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param fd The file descriptor (mtime will be used) + * @param frac The number of subsecond decimals to include in the + * timestamp (of allowed), at most (the function may choose + * to include less) + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from localtime(3) or fstat(3) + */ +inline size_t +libpatch_create_timestamp_fd(char *buf, size_t bufsize, int fd, + unsigned frac, int zone, enum libpatch_style style) +{ + struct stat st; + return fstat(fd, &st) ? 0 : libpatch_create_timestamp_stat(buf, bufsize, &st, frac, zone, style); +} + +/** + * Create file timestamp, from the current time, for diff header + * + * According to POSIX, diff(1) shall (not explicitly a strict requirement) + * use the current time in the timestamp if the file is standard input. + * GNU diff uses the current time where the "-" operand is specified. + * On Linux, pipes have timestamps so you would automatically get the + * current time if standard input is a pipe. + * + * @param buf Output buffer for the time stamp; will be NUL-terminated + * provided that the function returns a value that is no + * greater than `bufsize` (may or may not be NUL-terminated + * otherwise) + * @param bufsize The size of `buf` + * @param frac The number of subsecond decimals to include in the + * timestamp (of allowed), at most (the function may choose + * to include less); -1 for autoselection + * @param zone Whether to include the timezone (ignored unless optional) + * @param style The diff style the timestamp shall be generated for + * @return The length of the time stamp, including terminal NUL byte, 0 on + * failure. This value will exceed `bufsize` if `buf` is to small. + * It is possible that if `bufsize` is too small, an overly large + * value is returned. + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from localtime(3) + */ +size_t libpatch_create_timestamp_now(char *buf, size_t bufsize, signed frac, int zone, + enum libpatch_style style); + +/** + * Parse a timestamp from a diff header + * + * @param text The timestamp to parse (the mtime for a file) + * @param time_out Output parameter for the timestamp (second-resolution); + * `time_out->tm_isdst` and `time_out->tm_zone` will be undefined + * @param frac_out Output parameter for the pointer to where in `text` + * the fractions of the second start. If there are not + * fractions of the second in the timestamp, `NULL` will + * be stored in `*frac_out`. + * @param has_zone_out Output parameter for whether the timestamp contained a + * time zone + * @param style The diff style the timestamp is assumed to use + * @return Pointer to pass the last parsed character in `text`, or + * `NULL` on failure + * @throws EINVAL `style` is not a style that uses a diff header with timestamps + * @throws Any error from strptime(3) + */ +const char *libpatch_parse_timestamp(const char *text, struct tm *time_out, const char **frac_out, + int *has_zone_out, enum libpatch_style style); + +/** + * Get how a file label shall be formatted + * + * @param style The diff style + * @return How file labels shall be formatted, + * `LIBPATCH_LABEL_ERROR` on failure + * @throws EINVAL `style` is not a recognised style + */ +enum libpatch_label_format libpatch_get_label_format(enum libpatch_style style); + +/** + * Rewrite a diff string + * + * @param diff The output string, will be updated + * @param difflen The string length, may be updated + * @param spec Specification for desired diff string + * @return 0 on success, -1 on failure + * @throws ENOMEM Failed to allocate enough memory + * + * Update to `*diff` may be incomplete on failure (causing corruption) + */ +int libpatch_rewrite_diff2(struct libpatch_diff2 **diff, size_t *difflen, const struct libpatch_diff2_spec *spec); + +/** + * Check what properties a file's content must have + * + * Support for non-text files may be an extension of the standard + * + * @param style The diff style + * @param multifile Whether the created patchset may contain multiple diff scripts + * @return Bitmask of text properties the diff style requires: + * * 0x01: File must be LF-terminated + * (subject to additional bits in future version) + * -1 on failure; if 0, there no restrictions on the file + * @throws EINVAL `style` is not a recognised style + */ +int libpatch_style_requires_text(enum libpatch_style style, int multifile); +#define LIBPATCH_STYLE_REQUIRES_LF_TERMINATION 0x01 + +/** + * Check that a diff style supports a file's content + * + * @param file The file + * @param style The diff style + * @param multifile Whether the created patchset may contain multiple diff scripts + * @return 0 if the file is supported, positive if otherwise, + * and -1 on failure; if the value is positive, the + * bits as returned by `libpatch_style_requires_text` + * that denote a property that is not satisfied as set + * (bits for satisfied properties are cleared) + * @throws EINVAL `style` is not a recognised style + */ +int libpatch_check_file(struct libpatch_file *file, enum libpatch_style style, int multifile); + +/** + * Check if a file descriptor refers to /dev/null + * + * Since diff(1) can be used to find whether a file + * is identical or not to another file, diff(1) is + * often piped to /dev/null to discard the diff output + * (piping to /dev/zero has the same effect, but is + * not common practice, and could be useful for + * benchmarking). Therefore it can be a good idea to + * check if standard out is /dev/null, and if so, + * check whether the files are identical or not, + * rather than doing the expensive work of creating + * a patch file. + * + * @param fd The file descriptor + * @return 0 if the file descriptor is not /dev/null, + * 1 if the file descriptor is /dev/null, + * -1 on failure + * @throws Any error for fstat(3) or stat(3) + */ +int libpatch_is_devnull(int fd); + +/** + * Get the next hunk in a patch + * + * @param diff The diff string for the patch + * @param difflen The length of `diff` + * @param position The current position in `diff`, will be updated, start at 0 + * @param ai The current position in the first file, will be updated, start at 0 + * @param bi The current position in the second file, will be updated, start at 0 + * @param an_out Output parameter for the number of lines in the first file within + * the hunk; before calling again, add this value to `*ai` + * @param bn_out Output parameter for the number of lines in the second file within + * the hunk; before calling again, add this value to `*bi` + * @param skip_context If 0, include `LIBPATCH_DIFF2_CONTEXT` lines, exclude them otherwise + * @return The number of elements in `diff` that the hunk uses, + * 0 if there are no more hunks in the patch; + * before calling again, add this value to `*position` + */ +size_t libpatch_next_hunk(struct libpatch_diff2 *diff, size_t difflen, size_t *position, + size_t *ai, size_t *bi, size_t *an_out, size_t *bn_out, int skip_context); + +/** + * Get the previous hunk in a patch + * + * @param diff The diff string for the patch + * @param position The position in `diff` where the current hunk ends, will be + * updated; the initial value shall be the number of elements in `diff` + * @param ai The current position in the first file, will be updated; + * the initial value shall be the number of lines in the first file + * @param bi The current position in the second file, will be updated; + * the initial value shall be the number of lines in the second file + * @param an_out Output parameter for the number of lines in the first file within + * the hunk (do _not_ use this value to modify `*ai`) + * @param bn_out Output parameter for the number of lines in the second file within + * the hunk (do _not_ use this value to modify `*bi`) + * @param skip_context If 0, include `LIBPATCH_DIFF2_CONTEXT` lines, exclude them otherwise + * @return The number of elements in `diff` that the hunk uses, + * 0 if there are no more hunks in the patch; + * (do _not_ use this value to modify `*position`) + */ +size_t libpatch_previous_hunk(struct libpatch_diff2 *diff, size_t *position, + size_t *ai, size_t *bi, size_t *an_out, size_t *bn_out, int skip_context); + +/** + * Print a patch in “unified” format + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @return 0 on success, -1 on failure (always successful in the current implementation) + */ +int libpatch_format_unified_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2); + +/** + * Print a patch in “copied” format + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @return 0 on success, -1 on failure (always successful in the current implementation) + */ +int libpatch_format_copied_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2); + +/** + * Print a patch in “normal” format + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @return 0 on success, -1 on failure (always successful in the current implementation) + */ +int libpatch_format_normal_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2); + +/** + * Print a patch in “alternative ed” format + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_format_ed_alternative_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2); + +/** + * Print a patch in ed(1) format + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_format_ed_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2); + +/** + * Print a patch in RCS format + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_format_rcs_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2); + +/** + * Print a patch + * + * @param printer Patch printer + * @param diff The diff string for the patch + * @param difflen The number of elements in `diff` + * @param file1 The first file + * @param file2 The second file + * @param style The format the patch file should use + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + * @throws EINVAL `style` is not a recognised style + */ +int libpatch_format_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2, enum libpatch_style style); + +/** + * Parse a patch file in “unified” format + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @parma textend_out Unless `NULL`, parsing will at the end of the first patch + * in the file and the number of bytes included in the patch + * is stored to `*textend_out` + * @param patch_out Output parameter for the description of patch + * @param patchlen_out Output parameter for the number of eleemnts in `*patch_out` + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_parse_unified_patch(const char *text, size_t textlen, size_t *textend_out, + struct libpatch_patch **patch_out, size_t *patchlen_out); + +/** + * Parse a patch file in “copied” format + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @parma textend_out Unless `NULL`, parsing will at the end of the first patch + * in the file and the number of bytes included in the patch + * is stored to `*textend_out` + * @param patch_out Output parameter for the description of patch + * @param patchlen_out Output parameter for the number of eleemnts in `*patch_out` + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_parse_copied_patch(const char *text, size_t textlen, size_t *textend_out, + struct libpatch_patch **patch_out, size_t *patchlen_out); + +/** + * Parse a patch file in “normal” format + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @parma textend_out Unless `NULL`, parsing will at the end of the first patch + * in the file and the number of bytes included in the patch + * is stored to `*textend_out` + * @param patch_out Output parameter for the description of patch + * @param patchlen_out Output parameter for the number of eleemnts in `*patch_out` + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_parse_normal_patch(const char *text, size_t textlen, size_t *textend_out, + struct libpatch_patch **patch_out, size_t *patchlen_out); + +/** + * Parse a patch file in ed(1) format + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @parma textend_out Unless `NULL`, parsing will at the end of the first patch + * in the file and the number of bytes included in the patch + * is stored to `*textend_out` + * @param patch_out Output parameter for the description of patch + * @param patchlen_out Output parameter for the number of eleemnts in `*patch_out` + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_parse_ed_patch(const char *text, size_t textlen, size_t *textend_out, + struct libpatch_patch **patch_out, size_t *patchlen_out); + +/** + * Parse a patch file in RCS format + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @parma textend_out Unless `NULL`, parsing will at the end of the first patch + * in the file and the number of bytes included in the patch + * is stored to `*textend_out` + * @param patch_out Output parameter for the description of patch + * @param patchlen_out Output parameter for the number of eleemnts in `*patch_out` + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + */ +int libpatch_parse_rcs_patch(const char *text, size_t textlen, size_t *textend_out, + struct libpatch_patch **patch_out, size_t *patchlen_out); + +/** + * Parse a patch file + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @parma textend_out Unless `NULL`, parsing will at the end of the first patch + * in the file and the number of bytes included in the patch + * is stored to `*textend_out` + * @param patch_out Output parameter for the description of patch + * @param patchlen_out Output parameter for the number of eleemnts in `*patch_out` + * @param style The format the patch file is in + * @return 0 on success, -1 on failure + * @throws ENOMEM If enough memory cannot be allocated + * @throws EINVAL `style` is not a recognised style + * @throws EINVAL `style` is LIBPATCH_STYLE_ED_ALTERNATIVE + */ +int libpatch_parse_patch(const char *text, size_t textlen, size_t *textend_out, + struct libpatch_patch **patch_out, size_t *patchlen_out, + enum libpatch_style style); + +/** + * Determine how a patch file is formatted + * + * If the file does not contain any diff content, + * `LIBPATCH_STYLE_GARBAGE` will be returned, but + * it can still contain useful information that + * can be parsed by arbitrary parsing function. + * If `LIBPATCH_STYLE_GARBAGE` is returned, there + * is no point in calling the function again. + * + * @param text The content of the patch file + * @param textlen The size of the patch file + * @param offset Offset for where the function shall start + * reading the patch file to determine the + * format. Shall be 0 on the first. The function + * will update the value so that it is set + * appropriately for calling the function again + * to make a new guess. (This value shall not + * be used as an offset for parsing.) + * @return The style the patch file appears to use, + * or `LIBPATCH_STYLE_GARBAGE` if it couldn't + * be determined + */ +enum libpatch_style libpatch_guess_format(const char *text, size_t textlen, size_t *offset); + +/** + * Reverse the direction of a diff script + * + * @param diff The diff string, will be modified in-place + * @param difflen The number of elements in `diff` + */ +void libpatch_reverse_diff2(struct libpatch_diff2 *diff, size_t difflen); + + +#endif diff --git a/libpatch_append_to_patch__.c b/libpatch_append_to_patch__.c new file mode 100644 index 0000000..cf54c6f --- /dev/null +++ b/libpatch_append_to_patch__.c @@ -0,0 +1,28 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_append_to_patch__(struct libpatch_patch **patch, size_t *patchlen, size_t *size, + size_t stroff, size_t strlen, int type) +{ + void *new; + if (*patchlen < *size) { + if (*size > SIZE_MAX / sizeof(**patch) - 70) { + errno = ENOMEM; + return -1; + } + new = realloc(*patch, (*size + 70) * sizeof(**patch)); + if (!new) + return -1; + *patch = new; + *size += 70; + memset(&(*patch)[*patchlen], 0, 70 * sizeof(**patch)); + } + (*patch)[*patchlen].type = type; + (*patch)[*patchlen].text_offset = stroff; + (*patch)[*patchlen].text_length = strlen; + (*patch)[*patchlen].lf_terminated = 1; + *patchlen += 1; + return 0; +} diff --git a/libpatch_check_file.c b/libpatch_check_file.c new file mode 100644 index 0000000..4f0adca --- /dev/null +++ b/libpatch_check_file.c @@ -0,0 +1,15 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_check_file(struct libpatch_file *file, enum libpatch_style style, int multifile) +{ + int req = libpatch_style_requires_text(style, multifile); + if (req > 0) { + if (req & LIBPATCH_STYLE_REQUIRES_LF_TERMINATION) + if (file->lf_terminated) + req ^= LIBPATCH_STYLE_REQUIRES_LF_TERMINATION; + } + return req; +} diff --git a/libpatch_create_timestamp.c b/libpatch_create_timestamp.c new file mode 100644 index 0000000..54b6d91 --- /dev/null +++ b/libpatch_create_timestamp.c @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +size_t +libpatch_create_timestamp(char *buf, size_t bufsize, const struct tm *tm, + uintmax_t subseconds, unsigned subseconds_decimals, + unsigned frac, int zone, enum libpatch_style style) +{ + size_t guess, len, r; + const char *fmt; + + frac = MIN(frac, subseconds_decimals); + + if (style == LIBPATCH_STYLE_COPIED) { + fmt = "%a %b %e %T %Y"; + guess = sizeof("aaa bbb 00 00:00:00 0000"); + } else if (style == LIBPATCH_STYLE_UNIFIED) { + fmt = "%Y-%m-%d %H:%M:%S"; + zone = 1; + guess = sizeof("0000-00-00 00:00:00"); + } else { + errno = EINVAL; + return 0; + } + + if (zone) + guess += sizeof(" +0000"); + guess += (size_t)frac + (size_t)(frac > 0); + + if (!bufsize) + return guess; + + while (subseconds_decimals != frac) { + subseconds /= 10; + subseconds_decimals -= 1; + } + + len = strftime(buf, bufsize, fmt, tm); + if (!len) + return bufsize < guess ? guess : bufsize + 16U; + + if (frac) { + if (len < bufsize) + snprintf(&buf[len], bufsize - len, ".%0*ju", (int)frac, subseconds); + len += (size_t)frac + (size_t)(frac > 0); + } + + if (zone) { + if (len < bufsize) + return bufsize < len + 7U ? len + 7U : bufsize + 16U; + len += r = strftime(&buf[len], bufsize - len, " %z", tm); + if (!r) + return bufsize < len + 7U ? len + 7U : bufsize + 16U; + } + + return len + 1; +} diff --git a/libpatch_create_timestamp_fd.c b/libpatch_create_timestamp_fd.c new file mode 100644 index 0000000..f729614 --- /dev/null +++ b/libpatch_create_timestamp_fd.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t +libpatch_create_timestamp_fd(char *, size_t, int, unsigned, int, enum libpatch_style); diff --git a/libpatch_create_timestamp_frac_string.c b/libpatch_create_timestamp_frac_string.c new file mode 100644 index 0000000..18b8090 --- /dev/null +++ b/libpatch_create_timestamp_frac_string.c @@ -0,0 +1,21 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +size_t +libpatch_create_timestamp_frac_string(char *buf, size_t bufsize, const struct tm *tm, + const char *frac, int zone, enum libpatch_style style) +{ + uintmax_t subseconds = 0, digit; + unsigned decimals = 0; + if (frac) { + for (;; frac++) { + digit = (uintmax_t)*frac - (uintmax_t)'0'; + if (digit > 9 || subseconds > (UINTMAX_MAX - digit) / 10U) + break; + subseconds = subseconds * 10U + digit; + decimals += 1; + } + } + return libpatch_create_timestamp(buf, bufsize, tm, subseconds, decimals, decimals, zone, style); +} diff --git a/libpatch_create_timestamp_now.c b/libpatch_create_timestamp_now.c new file mode 100644 index 0000000..b0bef09 --- /dev/null +++ b/libpatch_create_timestamp_now.c @@ -0,0 +1,73 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifdef __linux__ +# include +#endif + + +size_t +libpatch_create_timestamp_now(char *buf, size_t bufsize, signed frac, int zone, enum libpatch_style style) +{ + struct timespec ts; +#ifdef __linux__ + struct timex timex; + struct tm tm; + int r; +#endif + + if (frac < 0) { + if (clock_gettime(CLOCK_REALTIME, &ts)) { + frac = 9; + } else { + frac = 0; + while (ts.tv_nsec < 1000000000L) { + ts.tv_nsec *= 10; + frac += 1; + } + } + } + +#ifdef __linux__ + memset(&timex, 0, sizeof(timex)); + if (frac > 6) + timex.status = ADJ_NANO; + r = adjtimex(&timex); + if (r == -1) { + if (errno == ENOSYS) + goto fallback; + return 0; + } + + if (timex.time.tv_sec % (24 * 60 * 60) == 0) { + if (r == TIME_INS) { + timex.time.tv_sec -= 1; + if (!localtime_r(&timex.time.tv_sec, &tm)) + return 0; + tm.tm_sec += 1; + } else if (r == TIME_DEL) { + timex.time.tv_sec += 1; + if (!localtime_r(&timex.time.tv_sec, &tm)) + return 0; + } else { + if (!localtime_r(&timex.time.tv_sec, &tm)) + return 0; + } + } else if (r == TIME_OOP) { + if (!localtime_r(&timex.time.tv_sec, &tm)) + return 0; + tm.tm_sec += 1; + } else { + if (!localtime_r(&timex.time.tv_sec, &tm)) + return 0; + } + + return libpatch_create_timestamp(buf, bufsize, &tm, (uintmax_t)timex.time.tv_usec, + frac > 6 ? 9U : 6U, (unsigned)frac, zone, style); + +fallback: +#endif + + if (clock_gettime(CLOCK_REALTIME, &ts)) + return 0; + return libpatch_create_timestamp_ts(buf, bufsize, &ts, (unsigned)frac, zone, style); +} diff --git a/libpatch_create_timestamp_secs.c b/libpatch_create_timestamp_secs.c new file mode 100644 index 0000000..104fb91 --- /dev/null +++ b/libpatch_create_timestamp_secs.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t +libpatch_create_timestamp_secs(char *, size_t, time_t, int, enum libpatch_style); diff --git a/libpatch_create_timestamp_stat.c b/libpatch_create_timestamp_stat.c new file mode 100644 index 0000000..e47d51b --- /dev/null +++ b/libpatch_create_timestamp_stat.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t +libpatch_create_timestamp_stat(char *, size_t, const struct stat *, unsigned, int, enum libpatch_style); diff --git a/libpatch_create_timestamp_tm.c b/libpatch_create_timestamp_tm.c new file mode 100644 index 0000000..47578c5 --- /dev/null +++ b/libpatch_create_timestamp_tm.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t +libpatch_create_timestamp_tm(char *, size_t, const struct tm *, int, enum libpatch_style); diff --git a/libpatch_create_timestamp_ts.c b/libpatch_create_timestamp_ts.c new file mode 100644 index 0000000..f9c8f60 --- /dev/null +++ b/libpatch_create_timestamp_ts.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t +libpatch_create_timestamp_ts(char *, size_t, const struct timespec *, unsigned, int, enum libpatch_style); diff --git a/libpatch_create_timestamp_tv.c b/libpatch_create_timestamp_tv.c new file mode 100644 index 0000000..2d4bb65 --- /dev/null +++ b/libpatch_create_timestamp_tv.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline size_t +libpatch_create_timestamp_tv(char *, size_t, const struct timeval *, unsigned, int, enum libpatch_style); diff --git a/libpatch_fd_diff2_printer__.c b/libpatch_fd_diff2_printer__.c new file mode 100644 index 0000000..3267b69 --- /dev/null +++ b/libpatch_fd_diff2_printer__.c @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static void +put(struct libpatch_diff2_printer *this, const char *text, size_t len) +{ + size_t off; + ssize_t r; + int fd = ((struct printer_internals *)this->user_data)->f.fd; + for (off = 0; off < len; off += (size_t)r) { + r = write(fd, &text[off], len - off); + if (r < 0) { + if (errno != EINTR) { + this->error = errno; + break; + } + r = 0; + } + } +} + + +struct libpatch_diff2_printer * +libpatch_fd_diff2_printer__(int output) +{ + struct libpatch_diff2_printer *printer; + struct printer_internals *internals; + printer = calloc(1, sizeof(*printer) + sizeof(struct printer_internals)); + if (!printer) + return NULL; + internals = (void *)&((char *)printer)[sizeof(*printer)]; + printer->user_data = internals; + internals->put = put; + internals->f.fd = output; + return printer; + +} diff --git a/libpatch_format_copied_patch.c b/libpatch_format_copied_patch.c new file mode 100644 index 0000000..9f0378d --- /dev/null +++ b/libpatch_format_copied_patch.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static void +print_line(struct libpatch_diff2_printer *printer, const struct libpatch_file *file, size_t lineno) +{ + printer->put_line(printer, file->lines[lineno]); + printer->put_newline(printer); + if (lineno + 1 == file->nlines && !file->lf_terminated) { + printer->put_no_newline(printer, "\\ No newline at end of file"); + printer->put_newline(printer); + } +} + + +static void +print_change(struct libpatch_diff2_printer *printer, int fileno, int both, const struct libpatch_file *file, size_t lineno) +{ + printer->put_change_prefix(printer, both ? "! " : fileno == 1 ? "- " : "+ ", fileno, 0, lineno); + print_line(printer, file, lineno); +} + + +static void +print_context(struct libpatch_diff2_printer *printer, int fileno, const struct libpatch_file *file, size_t lineno) +{ + printer->put_context_prefix(printer, " ", fileno == 1, fileno == 2, lineno); + print_line(printer, file, lineno); +} + + +static void +print_hunk_head(struct libpatch_diff2_printer *printer, const char *prefix, const char *suffix, + size_t lineno, size_t len, int file) +{ + printer->put_hunk_head_prefix(printer, prefix, file); + printer->put_whitespace(printer, " "); + printer->put_hunk_start(printer, "", lineno + (size_t)(len > 0), file); + if (len > 1) + printer->put_hunk_end(printer, ",", lineno + len, file); + printer->put_whitespace(printer, " "); + printer->put_hunk_head_suffix(printer, suffix, file); + printer->put_newline(printer); +} + + +int +libpatch_format_copied_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2) +{ +#define HUNK(FNO, FI, FN)\ + do {\ + for (; i < n; i++) {\ + if (changed_saved) {\ + changed_saved -= 1;\ + } else if (diff[i].repetition & 0x03) {\ + for (changed_saved = 0; changed_saved < n - i;)\ + changed |= diff[i + changed_saved++].repetition;\ + changed_saved -= 1;\ + } else {\ + changed = 0;\ + }\ + for (j = 0; j < diff[i].repetition; j++) {\ + if (diff[i].change == LIBPATCH_DIFF2_FILE##FNO##_ONLY ||\ + diff[i].change == LIBPATCH_DIFF2_TWO_FILE_CHANGE)\ + print_change(printer, FNO, changed == 0x03, file##FNO, FI++);\ + else if (diff[i].change == LIBPATCH_DIFF2_CONTEXT)\ + print_context(printer, FNO, file##FNO, FI++);\ + }\ + }\ + } while (0) + + size_t i, i0, j, n, ai, bi, an, bn, changed_saved = 0; + int changed = 0, have; + + printer->put_label_prefix(printer, "***", 1); + printer->put_whitespace(printer, " "); + if (file1->label) + printer->put_label(printer, file1->label, 1); + printer->put_newline(printer); + + printer->put_label_prefix(printer, "---", 2); + printer->put_whitespace(printer, " "); + if (file2->label) + printer->put_label(printer, file2->label, 2); + printer->put_newline(printer); + + ai = 0; + bi = 0; + for (i = 0; (n = libpatch_next_hunk(diff, difflen, &i, &ai, &bi, &an, &bn, 0));) { + have = 0; + for (n += j = i; j < n; j++) + have |= diff[j].change; + + printer->put_syntactical_garbage(printer, "***************"); + printer->put_newline(printer); + if (have & 1) { + i0 = i; + print_hunk_head(printer, "***", "****", ai, an, 1); + HUNK(1, ai, an); + i = i0; + } + print_hunk_head(printer, "---", "----", bi, bn, 2); + HUNK(2, bi, bn); + } + + return 0; +} diff --git a/libpatch_format_ed_alternative_patch.c b/libpatch_format_ed_alternative_patch.c new file mode 100644 index 0000000..68cd05e --- /dev/null +++ b/libpatch_format_ed_alternative_patch.c @@ -0,0 +1,40 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_format_ed_alternative_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2) +{ + size_t i, j, n, ai, bi, an, bn; + + ai = 0; + bi = 0; + for (i = 0; (n = libpatch_next_hunk(diff, difflen, &i, &ai, &bi, &an, &bn, 1));) { + printer->put_hunk_head_prefix(printer, "", 0); + printer->put_hunk_operation(printer, !bn ? "d" : !an ? "a" : "c", an > 0, bn > 0); + printer->put_hunk_start(printer, "", ai + (an ? 1U : 0U), 1); + if (an > 1) + printer->put_hunk_end(printer, " ", ai + an, 1); + printer->put_hunk_head_suffix(printer, "", 0); + printer->put_newline(printer); + + ai += an; + if (bn) { + for (; i < n; i++) { + for (j = 0; j < diff[i].repetition; j++) { + if (diff[i].change != LIBPATCH_DIFF2_FILE1_ONLY) { + printer->put_change_prefix(printer, "", 2, an > 0, bi); + printer->put_line(printer, file2->lines[bi++]); + printer->put_newline(printer); + } + } + } + printer->put_end_of_change(printer, "."); + printer->put_newline(printer); + } + } + + (void) file1; + return 0; +} diff --git a/libpatch_format_ed_patch.c b/libpatch_format_ed_patch.c new file mode 100644 index 0000000..ba9efcb --- /dev/null +++ b/libpatch_format_ed_patch.c @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_format_ed_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2) +{ + size_t i, j, n, ai, bi, bj, an, bn; + int stopped; + + ai = file1->nlines; + bi = file2->nlines; + for (i = difflen; (n = libpatch_previous_hunk(diff, &i, &ai, &bi, &an, &bn, 1));) { + printer->put_hunk_head_prefix(printer, "", 0); + printer->put_hunk_start(printer, "", ai + (an ? 1U : 0U), 1); + if (an > 1) + printer->put_hunk_end(printer, ",", ai + an, 1); + printer->put_hunk_operation(printer, !bn ? "d" : !an ? "a" : "c", an > 0, bn > 0); + printer->put_hunk_head_suffix(printer, "", 0); + printer->put_newline(printer); + + if (bn) { + bj = bi; + stopped = 0; + for (; i < n; i++) { + for (j = 0; j < diff[i].repetition; j++) { + if (diff[i].change != LIBPATCH_DIFF2_FILE1_ONLY) { + if (stopped) { + stopped = 0; + printer->put_end_of_change(printer, "a"); + printer->put_newline(printer); + } + printer->put_change_prefix(printer, "", 2, an > 0, bj); + if (file2->lines[bj]->len == 1 && file2->lines[bj]->text[0] == '.') { + printer->put_syntactical_escape(printer, "."); + printer->put_line(printer, file2->lines[bj++]); + printer->put_newline(printer); + printer->put_end_of_change(printer, "."); + printer->put_newline(printer); + printer->put_end_of_change(printer, "s/.//"); + printer->put_newline(printer); + stopped = 1; + } else { + printer->put_line(printer, file2->lines[bj++]); + printer->put_newline(printer); + } + } + } + } + if (!stopped) + printer->put_end_of_change(printer, "."); + printer->put_newline(printer); + } + } + + return 0; +} diff --git a/libpatch_format_normal_patch.c b/libpatch_format_normal_patch.c new file mode 100644 index 0000000..daf8e8b --- /dev/null +++ b/libpatch_format_normal_patch.c @@ -0,0 +1,104 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static void +print_line(struct libpatch_diff2_printer *printer, const struct libpatch_file *file, size_t lineno) +{ + printer->put_line(printer, file->lines[lineno]); + printer->put_newline(printer); + if (lineno + 1 == file->nlines && !file->lf_terminated) { + printer->put_no_newline(printer, "\\ No newline at end of file"); + printer->put_newline(printer); + } +} + + +static void +print_change(struct libpatch_diff2_printer *printer, int fileno, const struct libpatch_file *file, size_t lineno) +{ + printer->put_change_prefix(printer, fileno == 1 ? "< " : "> ", fileno, 0, lineno); + print_line(printer, file, lineno); +} + + +static void +print_context(struct libpatch_diff2_printer *printer, int fileno, const struct libpatch_file *file, size_t lineno) +{ + printer->put_context_prefix(printer, " ", fileno == 1, fileno == 2, lineno); + print_line(printer, file, lineno); +} + + +static void +print_change_context(struct libpatch_diff2_printer *printer, int fileno, const struct libpatch_file *file, size_t lineno) +{ + printer->put_context_prefix(printer, fileno == 1 ? "< " : "> ", fileno == 1, fileno == 2, lineno); + print_line(printer, file, lineno); +} + + +int +libpatch_format_normal_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2) +{ + size_t i, i0, j, n, ai, bi, an, bn; + int have, have_a, have_b; + +#define HUNK(FNO, FI, FN)\ + do {\ + for (; i < n; i++) {\ + for (j = 0; j < diff[i].repetition; j++) {\ + if (diff[i].change == LIBPATCH_DIFF2_FILE##FNO##_ONLY ||\ + diff[i].change == LIBPATCH_DIFF2_TWO_FILE_CHANGE)\ + print_change(printer, FNO, file##FNO, FI++);\ + else if (diff[i].change != LIBPATCH_DIFF2_CONTEXT)\ + continue;\ + else if (printer->use_nonstandard_syntax)\ + print_context(printer, FNO, file##FNO, FI++);\ + else\ + print_change_context(printer, FNO, file##FNO, FI++);\ + }\ + }\ + } while (0) + + ai = 0; + bi = 0; + for (i = 0; (n = libpatch_next_hunk(diff, difflen, &i, &ai, &bi, &an, &bn, 0));) { + have = 0; + for (n += j = i; j < n; j++) + have |= diff[j].change; + if (!printer->use_nonstandard_syntax && (have & LIBPATCH_DIFF2_CONTEXT)) { + have_a = have_b = 1; + } else { + have_a = (have & 1); + have_b = (have & 2); + if (!have_a && !have_b) + have_a = have_b = 1; + } + + printer->put_hunk_head_prefix(printer, "", 0); + printer->put_hunk_start(printer, "", ai + (have_a ? 1U : 0U), 1); + if (an > 1) + printer->put_hunk_end(printer, ",", ai + an, 1); + printer->put_hunk_operation(printer, !have_b ? "d" : !have_a ? "a" : "c", have_a, have_b); + printer->put_hunk_start(printer, "", bi + (have_b ? 1U : 0U), 2); + if (bn > 1) + printer->put_hunk_end(printer, ",", bi + bn, 2); + printer->put_hunk_head_suffix(printer, "", 0); + printer->put_newline(printer); + + if (have_a) { + i0 = i; + HUNK(1, ai, an); + if (have_b) { + printer->put_syntactical_garbage(printer, "---"); + printer->put_newline(printer); + } + i = i0; + } + HUNK(2, bi, bn); + } + + return 0; +} diff --git a/libpatch_format_patch.c b/libpatch_format_patch.c new file mode 100644 index 0000000..22df906 --- /dev/null +++ b/libpatch_format_patch.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_format_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2, enum libpatch_style style) +{ + switch (style) { + case LIBPATCH_STYLE_NORMAL: + return libpatch_format_normal_patch(printer, diff, difflen, file1, file2); + case LIBPATCH_STYLE_COPIED: + return libpatch_format_copied_patch(printer, diff, difflen, file1, file2); + case LIBPATCH_STYLE_UNIFIED: + return libpatch_format_unified_patch(printer, diff, difflen, file1, file2); + case LIBPATCH_STYLE_ED: + return libpatch_format_ed_patch(printer, diff, difflen, file1, file2); + case LIBPATCH_STYLE_ED_ALTERNATIVE: + return libpatch_format_ed_alternative_patch(printer, diff, difflen, file1, file2); + case LIBPATCH_STYLE_RCS: + return libpatch_format_rcs_patch(printer, diff, difflen, file1, file2); + default: + case LIBPATCH_STYLE_GARBAGE: + errno = EINVAL; + return -1; + } +} diff --git a/libpatch_format_rcs_patch.c b/libpatch_format_rcs_patch.c new file mode 100644 index 0000000..9d09923 --- /dev/null +++ b/libpatch_format_rcs_patch.c @@ -0,0 +1,48 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_format_rcs_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2) +{ + size_t i, j, n, ai, bi, an, bn; + + ai = 0; + bi = 0; + for (i = 0; (n = libpatch_next_hunk(diff, difflen, &i, &ai, &bi, &an, &bn, 1));) { + if (an) { + printer->put_hunk_head_prefix(printer, "", 0); + printer->put_hunk_operation(printer, "d", 0, 1); + printer->put_hunk_start(printer, " ", ai + 1, 1); + printer->put_hunk_length(printer, " ", an, 1); + printer->put_hunk_head_suffix(printer, "", 0); + printer->put_newline(printer); + } + if (bn) { + printer->put_hunk_head_prefix(printer, "", 0); + printer->put_hunk_operation(printer, "a", 0, 1); + printer->put_hunk_start(printer, " ", ai + an, 1); + printer->put_hunk_length(printer, " ", bn, 2); + printer->put_hunk_head_suffix(printer, "", 0); + printer->put_newline(printer); + } + + ai += an; + if (bn) { + for (n += i; i < n; i++) { + for (j = 0; j < diff[i].repetition; j++) { + if (diff[i].change != LIBPATCH_DIFF2_FILE1_ONLY) { + printer->put_change_prefix(printer, "", 2, 0, bi); + printer->put_line(printer, file2->lines[bi++]); + if (bi < file2->nlines || file2->lf_terminated) + printer->put_newline(printer); + } + } + } + } + } + + (void) file1; + return 0; +} diff --git a/libpatch_format_unified_patch.c b/libpatch_format_unified_patch.c new file mode 100644 index 0000000..1caab9c --- /dev/null +++ b/libpatch_format_unified_patch.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static void +print_line(struct libpatch_diff2_printer *printer, const struct libpatch_file *file, size_t lineno) +{ + printer->put_line(printer, file->lines[lineno]); + printer->put_newline(printer); + if (lineno + 1 == file->nlines && !file->lf_terminated) { + printer->put_no_newline(printer, "\\ No newline at end of file"); + printer->put_newline(printer); + } +} + + +static void +print_change(struct libpatch_diff2_printer *printer, int fileno, const struct libpatch_file *file, size_t lineno) +{ + printer->put_change_prefix(printer, fileno == 1 ? "-" : "+", fileno, 0, lineno); + print_line(printer, file, lineno); +} + + +static void +print_context(struct libpatch_diff2_printer *printer, const struct libpatch_file *file, size_t lineno) +{ + printer->put_context_prefix(printer, " ", 1, 1, lineno); + print_line(printer, file, lineno); +} + + +int +libpatch_format_unified_patch(struct libpatch_diff2_printer *printer, struct libpatch_diff2 *diff, size_t difflen, + const struct libpatch_file *file1, const struct libpatch_file *file2) +{ + size_t i, j, n, ai, bi, an, bn; + + printer->put_label_prefix(printer, "---", 1); + printer->put_whitespace(printer, " "); + if (file1->label) + printer->put_label(printer, file1->label, 1); + printer->put_newline(printer); + + printer->put_label_prefix(printer, "+++", 2); + printer->put_whitespace(printer, " "); + if (file2->label) + printer->put_label(printer, file2->label, 2); + printer->put_newline(printer); + + ai = 0; + bi = 0; + for (i = 0; (n = libpatch_next_hunk(diff, difflen, &i, &ai, &bi, &an, &bn, 0));) { + printer->put_hunk_head_prefix(printer, "@@", 0); + printer->put_whitespace(printer, " "); + printer->put_hunk_start(printer, "-", ai + (size_t)(an > 0), 1); + if (an) + printer->put_hunk_length(printer, ",", an, 1); + printer->put_whitespace(printer, " "); + printer->put_hunk_start(printer, "+", bi + (size_t)(bn > 0), 2); + if (bn) + printer->put_hunk_length(printer, ",", bn, 2); + printer->put_whitespace(printer, " "); + printer->put_hunk_head_suffix(printer, "@@", 0); + printer->put_newline(printer); + + for (n += i; i < n; i++) { + for (j = 0; j < diff[i].repetition; j++) { + if (diff[i].change == LIBPATCH_DIFF2_FILE1_ONLY) { + print_change(printer, 1, file1, ai++); + } else if (diff[i].change == LIBPATCH_DIFF2_FILE2_ONLY) { + print_change(printer, 2, file2, bi++); + } else if (diff[i].change == LIBPATCH_DIFF2_TWO_FILE_CHANGE) { + print_change(printer, 1, file1, ai++); + print_change(printer, 2, file2, bi++); + } else if (printer->use_file2_when_common) { + print_context(printer, file2, bi++), ai++; + } else { + print_context(printer, file1, ai++), bi++; + } + } + } + } + + return 0; +} diff --git a/libpatch_get_label_format.c b/libpatch_get_label_format.c new file mode 100644 index 0000000..a43ab54 --- /dev/null +++ b/libpatch_get_label_format.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +enum libpatch_label_format +libpatch_get_label_format(enum libpatch_style style) +{ + switch (style) { + case LIBPATCH_STYLE_NORMAL: + case LIBPATCH_STYLE_ED: + case LIBPATCH_STYLE_ED_ALTERNATIVE: + case LIBPATCH_STYLE_RCS: + return LIBPATCH_LABEL_NONE; + case LIBPATCH_STYLE_COPIED: + return LIBPATCH_LABEL_FILE_SPACE_MTIME; + case LIBPATCH_STYLE_UNIFIED: + return LIBPATCH_LABEL_FILE_TAB_MTIME; + default: + errno = EINVAL; + return LIBPATCH_LABEL_ERROR; + } +} diff --git a/libpatch_get_zu__.c b/libpatch_get_zu__.c new file mode 100644 index 0000000..050f3d2 --- /dev/null +++ b/libpatch_get_zu__.c @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +size_t +libpatch_get_zu__(const char *text, size_t len, size_t *out) +{ + size_t i = 0, digit; + *out = 0; + for (; i < len; i++) { + digit = (size_t)(text[i] - '0'); + if (digit > 9U) + break; + *out = *out * 10U + digit; + } + return i; +} diff --git a/libpatch_guess_format.c b/libpatch_guess_format.c new file mode 100644 index 0000000..48ce419 --- /dev/null +++ b/libpatch_guess_format.c @@ -0,0 +1,121 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static int +is_unsigned(const char *text, size_t textlen, size_t *off) +{ + if (*off == textlen || !isdigit(text[*off])) + return 0; + while (isdigit(++*off)); + return 1; +} + + +enum libpatch_style +libpatch_guess_format(const char *text, size_t textlen, size_t *offset) +{ + enum libpatch_style guess = LIBPATCH_STYLE_GARBAGE; + size_t off = offset ? *offset : 0; + size_t two_nums; + + while (off < textlen) { + if (text[off] == '+') { + if (textlen - off < 3 || strncmp(&text[off], "+++", 3)) + goto skip_line; + goto unified; + + } else if (text[off] == '*') { + if (textlen - off < 3 || strncmp(&text[off], "***", 3)) + goto skip_line; + goto copied; + + } else if (text[off] == '@') { + if (textlen - off < 3 || strncmp(&text[off], "@@ ", 3)) + goto skip_line; + goto unified; + + } else if (text[off] == 'a') { + off++; + if (!is_unsigned(text, textlen, &off)) + goto skip_line; + if (off != textlen && text[off] == ' ') + off++; + if (is_unsigned(text, textlen, &off)) + goto rcs; + else + goto ed_alternative; + + } else if (text[off] == 'd') { + off++; + if (!is_unsigned(text, textlen, &off)) + goto skip_line; + if (off != textlen && text[off] == ' ') + off++; + if (!is_unsigned(text, textlen, &off)) + goto ed_alternative; + guess = LIBPATCH_STYLE_RCS; + /* could also be LIBPATCH_STYLE_ED_ALTERNATIVE, + * whould cannot be parsed so LIBPATCH_STYLE_RCS + * is preferred if the patch happens to be valid + * in both */ + goto skip_line; + + } else if (text[off] == 'c') { + off++; + if (!is_unsigned(text, textlen, &off)) + goto skip_line; + goto ed_alternative; + + } else if (isdigit(text[off])) { + if (!is_unsigned(text, textlen, &off)) + goto skip_line; + two_nums = (off != textlen && text[off] == ','); + if (two_nums) { + off += 1; + if (!is_unsigned(text, textlen, &off)) + goto skip_line; + } + switch (text[off]) { + case 'i': + goto ed; + case 'a': + if (two_nums) + goto normal; + /* fall through */ + case 'c': + case 'd': + off++; + if (off == textlen || text[off] == '\n') + goto ed; + else if (isdigit(text[off])) + goto normal; + /* fall through */ + default: + goto skip_line; + } + + } else { + skip_line: + while (off != textlen && text[off] != '\n') + off++; + off += (off != textlen); + } + } + + *offset = off; + return guess; + +determined: + while (off != textlen && text[off] != '\n') + off++; + *offset = off += (off != textlen); + return guess; + +normal: guess = LIBPATCH_STYLE_NORMAL; goto determined; +copied: guess = LIBPATCH_STYLE_COPIED; goto determined; +unified: guess = LIBPATCH_STYLE_UNIFIED; goto determined; +ed: guess = LIBPATCH_STYLE_ED; goto determined; +ed_alternative: guess = LIBPATCH_STYLE_ED_ALTERNATIVE; goto determined; +rcs: guess = LIBPATCH_STYLE_RCS; goto determined; +} diff --git a/libpatch_is_devnull.c b/libpatch_is_devnull.c new file mode 100644 index 0000000..3a78ea6 --- /dev/null +++ b/libpatch_is_devnull.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#include + + +int +libpatch_is_devnull(int fd) +{ + struct stat fd_st; +#ifndef __linux__ + struct stat null_st; +#endif + if (fstat(fd, &fd_st)) + return -1; + if (!S_ISCHR(fd_st.st_mode)) + return 0; +#ifdef __linux__ + return fd_st.st_rdev == makedev(1, 3); +#else + if (stat("/dev/null", &null_st)) + return -1; + return fd_st.st_rdev == null_st.st_rdev; +#endif +} diff --git a/libpatch_next_hunk.c b/libpatch_next_hunk.c new file mode 100644 index 0000000..4fcb8f4 --- /dev/null +++ b/libpatch_next_hunk.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +size_t +libpatch_next_hunk(struct libpatch_diff2 *diff, size_t difflen, size_t *position, + size_t *ai, size_t *bi, size_t *an_out, size_t *bn_out, int skip_context) +{ + size_t n; + int mask = skip_context ? 0x03 : ~0; + + while (*position < difflen && !(diff[*position].change & mask)) { + *ai += (size_t)diff[*position].repetition; + *bi += (size_t)diff[*position].repetition; + *position += 1; + } + + *an_out = 0; + *bn_out = 0; + n = 0; + + while (n < difflen - *position && (diff[*position + n].change & mask)) { + if (diff[*position + n].change != LIBPATCH_DIFF2_FILE2_ONLY) + *an_out += (size_t)diff[*position + n].repetition; + if (diff[*position + n].change != LIBPATCH_DIFF2_FILE1_ONLY) + *bn_out += (size_t)diff[*position + n].repetition; + n += 1; + } + + return n; +} diff --git a/libpatch_next_line__.c b/libpatch_next_line__.c new file mode 100644 index 0000000..0762262 --- /dev/null +++ b/libpatch_next_line__.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +size_t +libpatch_next_line__(const char **text, size_t *textlen, size_t lastlen) +{ + size_t len; + + while (lastlen < *textlen && (*text)[lastlen] == '\n') + lastlen += 1; + *text = &(*text)[lastlen]; + *textlen -= lastlen; + + len = 0; + while (len < *textlen && (*text)[len] != '\n') + len += 1; + return len; +} diff --git a/libpatch_parse_copied_patch.c b/libpatch_parse_copied_patch.c new file mode 100644 index 0000000..b6849b8 --- /dev/null +++ b/libpatch_parse_copied_patch.c @@ -0,0 +1,153 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static size_t +hunk_head(const char *text, size_t len, const char *suffix, size_t *first, size_t *last) +{ + size_t off = 4, 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; + } + if (5 < len - off && strncmp(&text[off], suffix, 5)) + return 0; + off += 5; + return off; +} + + +int +libpatch_parse_copied_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, lineno, last, count; + int need_hunk = 0, in_hunk = 0, type, has_patch = 0; + + *patch = NULL; + *patchlen = 0; + + while ((len = libpatch_next_line__(&text, &textlen, len))) { + if (len >= 5 && !strncmp(text, "diff ", 5)) { + if (has_patch && textend) + break; + APPEND_STRING(5, LIBPATCH_PATCH_DIFF_LINE); + need_hunk = 0; + in_hunk = 0; + + } else if (len >= 15 && !strncmp(text, "***************", 15)) { + APPEND_OPTSTRING(15, LIBPATCH_PATCH_HUNK); + need_hunk = 1; + in_hunk = 0; + has_patch = 1; + + } else if (len >= 3 && !strncmp(text, "***", 3)) { + if (!need_hunk) { + APPEND_OPTSTRING(3, LIBPATCH_PATCH_FILE1_LABEL); + (*patch)[*patchlen - 1].lf_terminated = 0; + } else if (need_hunk == 1 && len > 9 && text[3] == ' ') { + off = hunk_head(text, len, " ****", &lineno, &last); + if (!off) + goto garbage; + APPEND_OPTSTRING(off, LIBPATCH_PATCH_HUNK_FILE1); + if (!lineno && last) + goto garbage; + else if (!lineno) + count = 0; + else if (last < lineno) + goto garbage; + else + count = last - lineno + 1; + need_hunk = 2; + in_hunk = lineno > 0 ? 1 : 0; + } else { + goto garbage; + } + + } else if (len >= 3 && !strncmp(text, "---", 3)) { + if (!need_hunk) { + APPEND_OPTSTRING(3, LIBPATCH_PATCH_FILE2_LABEL); + (*patch)[*patchlen - 1].lf_terminated = 0; + has_patch = 1; + } else if (need_hunk == 2 && len > 9 && text[3] == ' ') { + off = hunk_head(text, len, " ----", &lineno, &last); + if (!off) + goto garbage; + APPEND_OPTSTRING(off, LIBPATCH_PATCH_HUNK_FILE2); + count = 0; + if (!lineno && last) + goto garbage; + else if (!lineno) + count = 0; + else if (last < lineno) + goto garbage; + else + count = last - lineno + 1; + need_hunk = 0; + in_hunk = lineno > 0 ? 2 : 0; + } else { + goto garbage; + } + + } else if (len >= 2 && in_hunk == 1 && !strncmp(text, "- ", 2)) { + type = LIBPATCH_PATCH_FILE1_ONLY; + goto file1_line; + } else if (len >= 2 && in_hunk == 1 && !strncmp(text, "! ", 2)) { + type = LIBPATCH_PATCH_PRECHANGE; + goto file1_line; + } else if (len >= 2 && in_hunk == 1 && !strncmp(text, " ", 2)) { + type = LIBPATCH_PATCH_FILE1_CONTEXT; + file1_line: + APPEND_OPTSTRING(2, type); + if (!--count) + in_hunk = 0; + (*patch)[*patchlen - 1].file1_lineno = lineno++; + + } else if (len >= 2 && in_hunk == 2 && !strncmp(text, "+ ", 2)) { + type = LIBPATCH_PATCH_FILE2_ONLY; + goto file2_line; + } else if (len >= 2 && in_hunk == 2 && !strncmp(text, "! ", 2)) { + type = LIBPATCH_PATCH_POSTCHANGE; + goto file2_line; + } else if (len >= 2 && in_hunk == 2 && !strncmp(text, " ", 2)) { + type = LIBPATCH_PATCH_FILE2_CONTEXT; + file2_line: + APPEND_OPTSTRING(2, type); + if (!--count) + in_hunk = 0; + (*patch)[*patchlen - 1].file2_lineno = lineno++; + + } else if (len >= 1 && !strncmp(text, "\\", 1) && *patchlen) { + if (IS_LINE((*patch)[*patchlen - 1].type) || + !(*patch)[*patchlen - 1].lf_terminated) + goto garbage; + (*patch)[*patchlen - 1].lf_terminated = 0; + APPEND_NO_TEXT(LIBPATCH_PATCH_SYNTACTICAL); + + } else { + garbage: + APPEND_STRING(0, LIBPATCH_PATCH_GARBAGE); + if (len == textlen) + (*patch)[*patchlen - 1].lf_terminated = 0; + } + } + + if (textend) + *textend = (size_t)(text - patchtext); + + return 0; + +fail: + free(*patch); + *patch = NULL; + *patchlen = 0; + return -1; +} diff --git a/libpatch_parse_ed_patch.c b/libpatch_parse_ed_patch.c new file mode 100644 index 0000000..f3dec51 --- /dev/null +++ b/libpatch_parse_ed_patch.c @@ -0,0 +1,149 @@ +/* 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; +} diff --git a/libpatch_parse_normal_patch.c b/libpatch_parse_normal_patch.c new file mode 100644 index 0000000..2d84a39 --- /dev/null +++ b/libpatch_parse_normal_patch.c @@ -0,0 +1,140 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static size_t +hunk_head_part(const char *text, size_t len, size_t *first, size_t *last) +{ + size_t off, r; + off = libpatch_get_zu__(&text[off], len - off, first); + if (!off) + 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_normal_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, lineno1, lineno2, count1, count2, last; + int in_hunk = 0, need2, type, has_patch = 0; + char op; + + *patch = NULL; + *patchlen = 0; + + while ((len = libpatch_next_line__(&text, &textlen, len))) { + if (len >= 5 && !strncmp(text, "diff ", 5)) { + if (has_patch && textend) + break; + APPEND_STRING(5, LIBPATCH_PATCH_DIFF_LINE); + in_hunk = 0; + + } else if (len >= 3 && in_hunk == 1 && need2 && !strncmp(text, "---", 3)) { + APPEND_OPTSTRING(3, LIBPATCH_PATCH_HUNK_FILE2); + in_hunk = 2; + + } else if (len >= 3 && in_hunk == 0 && text[1] != ' ') { + off = hunk_head_part(&text[off], len - off, &lineno1, &last); + if (!off || 2 > len - off) + goto garbage; + if (!lineno1 && last) + goto garbage; + else if (!lineno1) + count1 = 0; + else if (last < lineno1) + goto garbage; + else + count1 = last - lineno1 + 1; + + op = text[off++]; + + off = hunk_head_part(&text[off], len - off, &lineno2, &last); + if (!off || 2 > len - off) + goto garbage; + if (!lineno2 && last) + goto garbage; + else if (!lineno2) + count2 = 0; + else if (last < lineno2) + goto garbage; + else + count2 = last - lineno2 + 1; + + if (op == 'a') { + in_hunk = 2; + } else if (op == 'c') { + in_hunk = 1; + need2 = 1; + } else if (op == 'd') { + in_hunk = 1; + need2 = 0; + } else { + goto garbage; + } + + if (in_hunk == 1) + APPEND_OPTSTRING(off, LIBPATCH_PATCH_HUNK_FILE1); + else + APPEND_OPTSTRING(off, LIBPATCH_PATCH_HUNK); + + has_patch = 1; + + } else if (len >= 2 && in_hunk == 1 && !strncmp(text, "< ", 2)) { + type = LIBPATCH_PATCH_FILE1_ONLY; + file1_line: + APPEND_OPTSTRING(2, type); + if (!--count2) + in_hunk = 0; + (*patch)[*patchlen - 1].file1_lineno = lineno1++; + } else if (len >= 2 && in_hunk == 1 && !strncmp(text, " ", 2)) { + type = LIBPATCH_PATCH_FILE1_CONTEXT; + goto file1_line; + + } else if (len >= 2 && in_hunk == 2 && !strncmp(text, "> ", 2)) { + type = LIBPATCH_PATCH_FILE2_ONLY; + file2_line: + APPEND_OPTSTRING(2, type); + if (!--count2) + in_hunk = 0; + (*patch)[*patchlen - 1].file2_lineno = lineno2++; + } else if (len >= 2 && in_hunk == 2 && !strncmp(text, " ", 2)) { + type = LIBPATCH_PATCH_FILE2_CONTEXT; + goto file2_line; + + } else if (len >= 1 && !strncmp(text, "\\", 1) && *patchlen) { + if (IS_LINE((*patch)[*patchlen - 1].type) || + !(*patch)[*patchlen - 1].lf_terminated) + goto garbage; + (*patch)[*patchlen - 1].lf_terminated = 0; + APPEND_NO_TEXT(LIBPATCH_PATCH_SYNTACTICAL); + + } else { + garbage: + APPEND_STRING(0, LIBPATCH_PATCH_GARBAGE); + if (len == textlen) + (*patch)[*patchlen - 1].lf_terminated = 0; + } + } + + if (textend) + *textend = (size_t)(text - patchtext); + + return 0; + +fail: + free(*patch); + *patch = NULL; + *patchlen = 0; + return -1; +} diff --git a/libpatch_parse_patch.c b/libpatch_parse_patch.c new file mode 100644 index 0000000..526b197 --- /dev/null +++ b/libpatch_parse_patch.c @@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_parse_patch(const char *text, size_t textlen, size_t *textend, + struct libpatch_patch **patch, size_t *patchlen, enum libpatch_style style) +{ + switch (style) { + case LIBPATCH_STYLE_NORMAL: + return libpatch_parse_normal_patch(text, textlen, textend, patch, patchlen); + case LIBPATCH_STYLE_COPIED: + return libpatch_parse_copied_patch(text, textlen, textend, patch, patchlen); + case LIBPATCH_STYLE_UNIFIED: + return libpatch_parse_unified_patch(text, textlen, textend, patch, patchlen); + case LIBPATCH_STYLE_ED: + return libpatch_parse_ed_patch(text, textlen, textend, patch, patchlen); + case LIBPATCH_STYLE_RCS: + return libpatch_parse_rcs_patch(text, textlen, textend, patch, patchlen); + default: + case LIBPATCH_STYLE_ED_ALTERNATIVE: + case LIBPATCH_STYLE_GARBAGE: + errno = EINVAL; + return -1; + } +} diff --git a/libpatch_parse_rcs_patch.c b/libpatch_parse_rcs_patch.c new file mode 100644 index 0000000..04dc671 --- /dev/null +++ b/libpatch_parse_rcs_patch.c @@ -0,0 +1,76 @@ +/* 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 *count) +{ + 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, count); + if (!r) + return 0; + } else { + *count = 1; + } + return off; +} + + +int +libpatch_parse_rcs_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, line, count = 0; + int has_patch = 0; + + while ((len = libpatch_next_line__(&text, &textlen, len))) { + if (count) { + count -= 1; + 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; + APPEND_STRING(5, LIBPATCH_PATCH_DIFF_LINE); + + } else if (len >= 2 && text[0] == 'd' && isdigit(text[1])) { + if (hunk_head(text, len, &line, &count) < len) + goto garbage; + APPEND_NO_TEXT(LIBPATCH_PATCH_HUNK_WITH_DELETION); + (*patch)[*patchlen - 1].first_line = line; + (*patch)[*patchlen - 1].line_count = count; + has_patch = 1; + + } else if (len >= 2 && text[0] == 'a' && isdigit(text[1])) { + if (hunk_head(text, len, &line, &count) < len) + goto garbage; + APPEND_NO_TEXT(LIBPATCH_PATCH_HUNK); + line += 1; + has_patch = 1; + + } else { + garbage: + APPEND_STRING(0, LIBPATCH_PATCH_GARBAGE); + if (len == textlen) + (*patch)[*patchlen - 1].lf_terminated = 0; + } + } + + if (textend) + *textend = (size_t)(text - patchtext); + + return 0; + +fail: + free(*patch); + *patch = NULL; + *patchlen = 0; + return -1; +} diff --git a/libpatch_parse_timestamp.c b/libpatch_parse_timestamp.c new file mode 100644 index 0000000..aceb058 --- /dev/null +++ b/libpatch_parse_timestamp.c @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +const char * +libpatch_parse_timestamp(const char *text, struct tm *time_out, const char **frac_out, + int *has_zone_out, enum libpatch_style style) +{ + const char *fmt, *r; + struct tm zone; + + if (style == LIBPATCH_STYLE_COPIED) { + fmt = "%a %b %e %T %Y"; + } else if (style == LIBPATCH_STYLE_UNIFIED) { + fmt = "%Y-%m-%d %H:%M:%S"; + } else { + errno = EINVAL; + return NULL; + } + + text = strptime(text, fmt, time_out); + if (!text) + return NULL; + + if (text[0] == '.') { + *frac_out = ++text; + while (isdigit(*text)) + text++; + } else { + *frac_out = NULL; + } + + time_out->tm_isdst = -1; + time_out->tm_zone = NULL; + + r = strptime(text, " %z", &zone); + if (r) { + text = r; + *has_zone_out = 1; + time_out->tm_gmtoff = zone.tm_gmtoff; + } else { + *has_zone_out = 0; + time_out->tm_gmtoff = 0; + } + + return text; +} diff --git a/libpatch_parse_unified_patch.c b/libpatch_parse_unified_patch.c new file mode 100644 index 0000000..d92d419 --- /dev/null +++ b/libpatch_parse_unified_patch.c @@ -0,0 +1,111 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static size_t +hunk_head_part(const char *text, size_t len, size_t *first, size_t *count) +{ + size_t off, r; + off = libpatch_get_zu__(&text[off], len - off, first); + if (!off) + return 0; + if (off < len && text[off] == ',') { + off += 1; + off += r = libpatch_get_zu__(&text[off], len - off, count); + if (!r) + return 0; + } else if (*count) { + *count = 1; + } + return off; +} + + +int +libpatch_parse_unified_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, r, lineno1, lineno2, count1, count2; + int in_hunk = 0, has_patch = 0; + + *patch = NULL; + *patchlen = 0; + + while ((len = libpatch_next_line__(&text, &textlen, len))) { + if (len >= 5 && !strncmp(text, "diff ", 5)) { + if (has_patch && textend) + break; + APPEND_STRING(5, LIBPATCH_PATCH_DIFF_LINE); + in_hunk = 0; + + } else if (len >= 4 && !strncmp(text, "@@ -", 4)) { + off = 4; + off += r = hunk_head_part(&text[off], len - off, &lineno1, &count1); + if (!r || 2 >= len - off || strncmp(&text[off], " +", 2)) + goto garbage; + off += 2; + off += r = hunk_head_part(&text[off], len - off, &lineno2, &count2); + if (!r || 3 > len - off || strncmp(&text[off], " @@", 3)) + goto garbage; + off += 3; + APPEND_OPTSTRING(off, LIBPATCH_PATCH_HUNK); + in_hunk = ((count1 ? 1 : 0) | (count2 ? 2 : 0)); + has_patch = 1; + + } else if (!in_hunk && len >= 3 && !strncmp(text, "---", 3)) { + APPEND_OPTSTRING(3, LIBPATCH_PATCH_FILE1_LABEL); + (*patch)[*patchlen - 1].lf_terminated = 0; + + } else if (!in_hunk && len >= 3 && !strncmp(text, "+++", 3)) { + APPEND_OPTSTRING(3, LIBPATCH_PATCH_FILE2_LABEL); + (*patch)[*patchlen - 1].lf_terminated = 0; + has_patch = 1; + + } else if ((in_hunk & 1) && len >= 1 && text[0] == '-') { + APPEND_OPTSTRING(1, LIBPATCH_PATCH_FILE1_ONLY); + if (!--count1) + in_hunk ^= 1; + (*patch)[*patchlen - 1].file1_lineno = lineno1++; + + } else if ((in_hunk & 2) && len >= 1 && text[0] == '+') { + APPEND_OPTSTRING(1, LIBPATCH_PATCH_FILE2_ONLY); + if (!--count2) + in_hunk ^= 2; + (*patch)[*patchlen - 1].file2_lineno = lineno2++; + + } else if (in_hunk == 3 && len >= 1 && text[0] == ' ') { + APPEND_OPTSTRING(1, LIBPATCH_PATCH_CONTEXT); + if (!--count1) + in_hunk ^= 1; + if (!--count2) + in_hunk ^= 2; + (*patch)[*patchlen - 1].file1_lineno = lineno1++; + (*patch)[*patchlen - 1].file2_lineno = lineno2++; + + } else if (len >= 1 && !strncmp(text, "\\", 1) && *patchlen) { + if (IS_LINE((*patch)[*patchlen - 1].type) || + !(*patch)[*patchlen - 1].lf_terminated) + goto garbage; + (*patch)[*patchlen - 1].lf_terminated = 0; + APPEND_NO_TEXT(LIBPATCH_PATCH_SYNTACTICAL); + + } else { + garbage: + APPEND_STRING(0, LIBPATCH_PATCH_GARBAGE); + if (len == textlen) + (*patch)[*patchlen - 1].lf_terminated = 0; + } + } + + if (textend) + *textend = (size_t)(text - patchtext); + + return 0; + +fail: + free(*patch); + *patch = NULL; + *patchlen = 0; + return -1; +} diff --git a/libpatch_plain_diff2_printer__.c b/libpatch_plain_diff2_printer__.c new file mode 100644 index 0000000..f345e86 --- /dev/null +++ b/libpatch_plain_diff2_printer__.c @@ -0,0 +1,81 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +#define PUT(THIS, TEXT, LEN)\ + (((struct printer_internals *)(THIS)->user_data)->put((THIS), (TEXT), (LEN))) + + +static void +put_text(struct libpatch_diff2_printer *printer, const char *text) +{ + PUT(printer, text, strlen(text)); +} + +static void +put_text_zu_discard_i(struct libpatch_diff2_printer *printer, const char *text1, size_t text2, int a) +{ + char buf[3 * sizeof(text2) + 1]; + (void) a; + PUT(printer, text1, strlen(text1)); + PUT(printer, buf, (size_t)sprintf(buf, "%zu", text2)); +} + +static void +put_text_discard_i(struct libpatch_diff2_printer *printer, const char *text, int a) +{ + (void) a; + PUT(printer, text, strlen(text)); +} + +static void +put_text_discard_ii(struct libpatch_diff2_printer *printer, const char *text, int a, int b) +{ + (void) a; + (void) b; + PUT(printer, text, strlen(text)); +} + +static void +put_text_discard_iizu(struct libpatch_diff2_printer *printer, const char *text, int a, int b, size_t c) +{ + (void) a; + (void) b; + (void) c; + PUT(printer, text, strlen(text)); +} + +static void +put_newline(struct libpatch_diff2_printer *printer) +{ + PUT(printer, "\n", 1); +} + +static void +put_line(struct libpatch_diff2_printer *printer, struct libpatch_line *line) +{ + PUT(printer, line->text, line->len); +} + + +void +libpatch_plain_diff2_printer__(struct libpatch_diff2_printer *printer) +{ + printer->put_end_of_change = &put_text; + printer->put_syntactical_escape = &put_text; + printer->put_syntactical_garbage = &put_text; + printer->put_hunk_head_prefix = &put_text_discard_i; + printer->put_hunk_head_suffix = &put_text_discard_i; + printer->put_hunk_start = &put_text_zu_discard_i; + printer->put_hunk_end = &put_text_zu_discard_i; + printer->put_hunk_length = &put_text_zu_discard_i; + printer->put_hunk_operation = &put_text_discard_ii; + printer->put_label_prefix = &put_text_discard_i; + printer->put_label = &put_text_discard_i; + printer->put_context_prefix = &put_text_discard_iizu; + printer->put_change_prefix = &put_text_discard_iizu; + printer->put_line = &put_line; + printer->put_whitespace = &put_text; + printer->put_newline = &put_newline; + printer->put_no_newline = &put_text; +} diff --git a/libpatch_plain_fd_diff2_printer.c b/libpatch_plain_fd_diff2_printer.c new file mode 100644 index 0000000..9a33ce0 --- /dev/null +++ b/libpatch_plain_fd_diff2_printer.c @@ -0,0 +1,15 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +struct libpatch_diff2_printer * +libpatch_plain_fd_diff2_printer(int output) +{ + struct libpatch_diff2_printer *printer; + printer = libpatch_fd_diff2_printer__(output); + if (!printer) + return NULL; + libpatch_plain_diff2_printer__(printer); + return printer; + +} diff --git a/libpatch_plain_stream_diff2_printer.c b/libpatch_plain_stream_diff2_printer.c new file mode 100644 index 0000000..443b8ab --- /dev/null +++ b/libpatch_plain_stream_diff2_printer.c @@ -0,0 +1,15 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +struct libpatch_diff2_printer * +libpatch_plain_stream_diff2_printer(FILE *output) +{ + struct libpatch_diff2_printer *printer; + printer = libpatch_stream_diff2_printer__(output); + if (!printer) + return NULL; + libpatch_plain_diff2_printer__(printer); + return printer; + +} diff --git a/libpatch_previous_hunk.c b/libpatch_previous_hunk.c new file mode 100644 index 0000000..19ab964 --- /dev/null +++ b/libpatch_previous_hunk.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +size_t +libpatch_previous_hunk(struct libpatch_diff2 *diff, size_t *position, + size_t *ai, size_t *bi, size_t *an_out, size_t *bn_out, int skip_context) +{ + size_t n; + int mask = skip_context ? 0x03 : ~0; + + while (*position && !(diff[*position - 1].change & mask)) { + *position -= 1; + *ai -= (size_t)diff[*position].repetition; + *bi -= (size_t)diff[*position].repetition; + } + + *an_out = 0; + *bn_out = 0; + n = 0; + + while (*position && (diff[*position - 1].change & mask)) { + *position -= 1; + n += 1; + if (diff[*position].change != LIBPATCH_DIFF2_FILE2_ONLY) + *an_out += (size_t)diff[*position].repetition; + if (diff[*position].change != LIBPATCH_DIFF2_FILE1_ONLY) + *bn_out += (size_t)diff[*position].repetition; + } + + *ai -= *an_out; + *bi -= *bn_out; + return n; +} diff --git a/libpatch_reverse_diff2.c b/libpatch_reverse_diff2.c new file mode 100644 index 0000000..a247efd --- /dev/null +++ b/libpatch_reverse_diff2.c @@ -0,0 +1,15 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libpatch_reverse_diff2(struct libpatch_diff2 *diff, size_t difflen) +{ + size_t i; + for (i = 0; i < difflen; i++) { + if (diff[i].change == 1) + diff[i].change = 2; + else if (diff[i].change == 2) + diff[i].change = 1; + } +} diff --git a/libpatch_reverse_hunks__.c b/libpatch_reverse_hunks__.c new file mode 100644 index 0000000..a22e849 --- /dev/null +++ b/libpatch_reverse_hunks__.c @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_reverse_hunks__(struct libpatch_patch *patch, size_t patchlen, + const size_t *hunks, size_t nhunks, + struct libpatch_patch **patchcopy, size_t *patchcopysize) +{ + size_t i, j, first, count, lines; + void *new; + + if (nhunks < 2) + return 0; + + lines = patchlen - hunks[0]; + if (lines > *patchcopysize) { + new = realloc(*patchcopy, lines * sizeof(**patchcopy)); + if (!new) + return -1; + *patchcopy = new; + *patchcopysize = lines; + } + + j = lines; + for (i = 1; i < nhunks; i++) { + first = hunks[i - 1]; + count = hunks[i] - first; + memcpy(&patchcopy[j -= count], &patch[first], count * sizeof(*patch)); + } + first = hunks[i - 1]; + count = patchlen - first; + memcpy(&patchcopy[j -= count], &patch[first], count * sizeof(*patch)); + + memcpy(&patch[hunks[0]], &patchcopy[0], lines * sizeof(*patch)); + + return 0; +} diff --git a/libpatch_rewrite_diff2.c b/libpatch_rewrite_diff2.c new file mode 100644 index 0000000..5af72ba --- /dev/null +++ b/libpatch_rewrite_diff2.c @@ -0,0 +1,195 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +#define REPETITION_MAX ((1U << (sizeof(int) * CHAR_BIT - CHAR_BIT)) - 1U) + + +static int +allocate(struct libpatch_diff2 **diff, size_t *difflen, size_t *r, size_t *w, int change) +{ +#define GROWTH 32U + + void *new; + size_t size; + + if (*w == *r) { + if (GROWTH > SIZE_MAX / sizeof(**diff) - *difflen) { + errno = ENOMEM; + return -1; + } + size = (*difflen + GROWTH) * sizeof(**diff); + new = realloc(*diff, size); + if (!new) + return -1; + *diff = new; + memmove(&(*diff)[*r + GROWTH], &(*diff)[*r], *difflen - *r); + *difflen += GROWTH; + *r += GROWTH; + } + + (*diff)[*w].change = (unsigned char)change; + (*diff)[*w].repetition = 0; + *w += 1; + return 0; +} + + +static int +putchange(struct libpatch_diff2 **diff, size_t *difflen, size_t *r, size_t *w, int change, size_t count) +{ + if (!count) + return 0; + if (*w && (*diff)[*w - 1].change == change && (*diff)[*w - 1].repetition < REPETITION_MAX) + goto start; + do { + if (allocate(diff, difflen, r, w, change)) + return -1; + start: + if (count > (size_t)(REPETITION_MAX - (*diff)[*w - 1].repetition)) { + count -= (size_t)(REPETITION_MAX - (*diff)[*w - 1].repetition); + (*diff)[*w - 1].repetition = REPETITION_MAX; + } else { + (*diff)[*w - 1].repetition += count; + break; + } + } while (count); + return 0; +} + + +static int +put(struct libpatch_diff2 **diff, size_t *difflen, + const struct libpatch_diff2_spec *spec, size_t *r, size_t *w, + size_t common, size_t onlyfile1, size_t onlyfile2, size_t changed) +{ + int a, b; + size_t an, bn, i; + size_t context, a_context, z_context; + + if (common) { + if (spec->full_context) { + return putchange(diff, difflen, r, w, LIBPATCH_DIFF2_CONTEXT, common); + } else if (*w > 0 && *r < *difflen) { + context = MAX(spec->context_after + spec->context_before, spec->context_between); + if (common <= context) + return putchange(diff, difflen, r, w, LIBPATCH_DIFF2_CONTEXT, common); + common -= a_context = MIN(spec->context_after, common); + common -= z_context = MIN(spec->context_before, common); + return -(putchange(diff, difflen, r, w, LIBPATCH_DIFF2_CONTEXT, a_context) || + putchange(diff, difflen, r, w, LIBPATCH_DIFF2_SKIP, common) || + putchange(diff, difflen, r, w, LIBPATCH_DIFF2_CONTEXT, z_context)); + } else if (*w > 0) { + common -= context = MIN(spec->context_after, common); + return -(putchange(diff, difflen, r, w, LIBPATCH_DIFF2_CONTEXT, context) || + putchange(diff, difflen, r, w, LIBPATCH_DIFF2_SKIP, common)); + } else if (*r < *difflen) { + common -= context = MIN(spec->context_before, common); + return -(putchange(diff, difflen, r, w, LIBPATCH_DIFF2_SKIP, common) || + putchange(diff, difflen, r, w, LIBPATCH_DIFF2_CONTEXT, context)); + } else { + return putchange(diff, difflen, r, w, LIBPATCH_DIFF2_SKIP, common); + } + + } else { + if (!spec->keep_split_changes) { + changed += MIN(onlyfile1, onlyfile2); + onlyfile1 -= changed; + onlyfile2 -= changed; + } + + if (spec->single_file_change_order == LIBPATCH_DIFF2_FILE1_ONLY_FIRST) { + a = LIBPATCH_DIFF2_FILE1_ONLY; + an = onlyfile1; + b = LIBPATCH_DIFF2_FILE2_ONLY; + bn = onlyfile2; + } else { + a = LIBPATCH_DIFF2_FILE2_ONLY; + an = onlyfile2; + b = LIBPATCH_DIFF2_FILE1_ONLY; + bn = onlyfile1; + } + + if (spec->two_file_change_order == LIBPATCH_DIFF2_TWO_FILE_CHANGES_BEFORE_SINGLE_FILE) + if (putchange(diff, difflen, r, w, LIBPATCH_DIFF2_TWO_FILE_CHANGE, changed)) + return -1; + + if (putchange(diff, difflen, r, w, a, an)) + return -1; + + if (spec->two_file_change_order == LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES) { + if (putchange(diff, difflen, r, w, a, changed)) + return -1; + if (putchange(diff, difflen, r, w, b, changed)) + return -1; + } else if (spec->two_file_change_order == LIBPATCH_DIFF2_NO_TWO_FILE_CHANGES_INTERLEAVE) { + for (i = 0; i < changed; i++) { + if (putchange(diff, difflen, r, w, a, 1)) + return -1; + if (putchange(diff, difflen, r, w, b, 1)) + return -1; + } + } + + if (spec->two_file_change_order == LIBPATCH_DIFF2_TWO_FILE_CHANGES_BETWEEN_SINGLE_FILE) + if (putchange(diff, difflen, r, w, LIBPATCH_DIFF2_TWO_FILE_CHANGE, changed)) + return -1; + + if (putchange(diff, difflen, r, w, b, bn)) + return -1; + + if (spec->two_file_change_order == LIBPATCH_DIFF2_TWO_FILE_CHANGES_AFTER_SINGLE_FILE) + if (putchange(diff, difflen, r, w, LIBPATCH_DIFF2_TWO_FILE_CHANGE, changed)) + return -1; + + return 0; + } +} + + +int +libpatch_rewrite_diff2(struct libpatch_diff2 **diff, size_t *difflen, + const struct libpatch_diff2_spec *spec) +{ + size_t r, w = 0; + size_t saved0 = 0, saved1 = 0, saved2 = 0, saved3 = 0; + + for (r = 0; r < *difflen; r++) { + if ((*diff)[r].change == LIBPATCH_DIFF2_SKIP || + (*diff)[r].change == LIBPATCH_DIFF2_CONTEXT) { + if (saved1 || saved2) { + if (put(diff, difflen, spec, &r, &w, saved0, saved1, saved2, saved3)) + return -1; + saved1 = 0; + saved2 = 0; + saved3 = 0; + } + saved0 += (size_t)(*diff)[r].repetition; + } else { + if (saved0) { + if (put(diff, difflen, spec, &r, &w, saved0, saved1, saved2, saved3)) + return -1; + saved0 = 0; + } + if (spec->keep_split_changes) { + if ((*diff)[r].change == LIBPATCH_DIFF2_FILE1_ONLY) + saved1 += (size_t)(*diff)[r].repetition; + else if ((*diff)[r].change == LIBPATCH_DIFF2_FILE2_ONLY) + saved2 += (size_t)(*diff)[r].repetition; + else + saved3 += (size_t)(*diff)[r].repetition; + } else { + if ((*diff)[r].change != LIBPATCH_DIFF2_FILE2_ONLY) + saved1 += (size_t)(*diff)[r].repetition; + if ((*diff)[r].change != LIBPATCH_DIFF2_FILE1_ONLY) + saved2 += (size_t)(*diff)[r].repetition; + } + } + } + + if (put(diff, difflen, spec, &r, &w, saved0, saved1, saved2, saved3)) + return -1; + + *difflen = w; + return 0; +} diff --git a/libpatch_stream_diff2_printer__.c b/libpatch_stream_diff2_printer__.c new file mode 100644 index 0000000..6c6bc14 --- /dev/null +++ b/libpatch_stream_diff2_printer__.c @@ -0,0 +1,39 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static void +put(struct libpatch_diff2_printer *this, const char *text, size_t len) +{ + size_t off, r; + FILE *f = ((struct printer_internals *)this->user_data)->f.stream; + if (ferror(f)) + return; + for (off = 0; off < len; off += r) { + r = fwrite(&text[off], 1, len - off, f); + if (!r) { + if (errno != EINTR) { + this->error = errno; + break; + } + clearerr(f); + } + } +} + + +struct libpatch_diff2_printer * +libpatch_stream_diff2_printer__(FILE *output) +{ + struct libpatch_diff2_printer *printer; + struct printer_internals *internals; + printer = calloc(1, sizeof(*printer) + sizeof(struct printer_internals)); + if (!printer) + return NULL; + internals = (void *)&((char *)printer)[sizeof(*printer)]; + printer->user_data = internals; + internals->put = put; + internals->f.stream = output; + return printer; + +} diff --git a/libpatch_style_requires_text.c b/libpatch_style_requires_text.c new file mode 100644 index 0000000..631f9fb --- /dev/null +++ b/libpatch_style_requires_text.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libpatch_style_requires_text(enum libpatch_style style, int multifile) +{ + switch (style) { + case LIBPATCH_STYLE_NORMAL: + case LIBPATCH_STYLE_COPIED: + case LIBPATCH_STYLE_UNIFIED: + return 0; + case LIBPATCH_STYLE_ED: + case LIBPATCH_STYLE_ED_ALTERNATIVE: + return LIBPATCH_STYLE_REQUIRES_LF_TERMINATION; + case LIBPATCH_STYLE_RCS: + return multifile ? LIBPATCH_STYLE_REQUIRES_LF_TERMINATION : 0; + default: + errno = EINVAL; + return -1; + } +} diff --git a/mk/linux.mk b/mk/linux.mk new file mode 100644 index 0000000..ad58f69 --- /dev/null +++ b/mk/linux.mk @@ -0,0 +1,6 @@ +LIBEXT = so +LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR) +LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR) +LIBMINOREXT = $(LIBEXT).$(LIB_VERSION) + +FIX_INSTALL_NAME = : diff --git a/mk/macos.mk b/mk/macos.mk new file mode 100644 index 0000000..3591657 --- /dev/null +++ b/mk/macos.mk @@ -0,0 +1,6 @@ +LIBEXT = dylib +LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION) +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) + +FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/libpatch.$(LIBMAJOREXT)" diff --git a/mk/windows.mk b/mk/windows.mk new file mode 100644 index 0000000..ed5ec8d --- /dev/null +++ b/mk/windows.mk @@ -0,0 +1,6 @@ +LIBEXT = dll +LIBFLAGS = -shared +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) + +FIX_INSTALL_NAME = : -- cgit v1.2.3-70-g09d2