aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-01-09 22:04:24 +0100
committerMattias Andrée <maandree@kth.se>2024-01-09 22:04:24 +0100
commitae850b1ac755f471beac4dbfef4654fe3fbaaae9 (patch)
tree319e7f7f1b99cd1ee75e100f0a29b0f7c827ccea
downloadlibpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.gz
libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.bz2
libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.xz
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--.gitignore14
-rw-r--r--LICENSE15
-rw-r--r--Makefile111
-rw-r--r--README1
-rw-r--r--TODO10
-rw-r--r--common.h54
-rw-r--r--config.mk8
-rw-r--r--libpatch.h1233
-rw-r--r--libpatch_append_to_patch__.c28
-rw-r--r--libpatch_check_file.c15
-rw-r--r--libpatch_create_timestamp.c58
-rw-r--r--libpatch_create_timestamp_fd.c6
-rw-r--r--libpatch_create_timestamp_frac_string.c21
-rw-r--r--libpatch_create_timestamp_now.c73
-rw-r--r--libpatch_create_timestamp_secs.c6
-rw-r--r--libpatch_create_timestamp_stat.c6
-rw-r--r--libpatch_create_timestamp_tm.c6
-rw-r--r--libpatch_create_timestamp_ts.c6
-rw-r--r--libpatch_create_timestamp_tv.c6
-rw-r--r--libpatch_fd_diff2_printer__.c38
-rw-r--r--libpatch_format_copied_patch.c109
-rw-r--r--libpatch_format_ed_alternative_patch.c40
-rw-r--r--libpatch_format_ed_patch.c58
-rw-r--r--libpatch_format_normal_patch.c104
-rw-r--r--libpatch_format_patch.c27
-rw-r--r--libpatch_format_rcs_patch.c48
-rw-r--r--libpatch_format_unified_patch.c86
-rw-r--r--libpatch_get_label_format.c22
-rw-r--r--libpatch_get_zu__.c17
-rw-r--r--libpatch_guess_format.c121
-rw-r--r--libpatch_is_devnull.c24
-rw-r--r--libpatch_next_hunk.c31
-rw-r--r--libpatch_next_line__.c19
-rw-r--r--libpatch_parse_copied_patch.c153
-rw-r--r--libpatch_parse_ed_patch.c149
-rw-r--r--libpatch_parse_normal_patch.c140
-rw-r--r--libpatch_parse_patch.c26
-rw-r--r--libpatch_parse_rcs_patch.c76
-rw-r--r--libpatch_parse_timestamp.c47
-rw-r--r--libpatch_parse_unified_patch.c111
-rw-r--r--libpatch_plain_diff2_printer__.c81
-rw-r--r--libpatch_plain_fd_diff2_printer.c15
-rw-r--r--libpatch_plain_stream_diff2_printer.c15
-rw-r--r--libpatch_previous_hunk.c34
-rw-r--r--libpatch_reverse_diff2.c15
-rw-r--r--libpatch_reverse_hunks__.c38
-rw-r--r--libpatch_rewrite_diff2.c195
-rw-r--r--libpatch_stream_diff2_printer__.c39
-rw-r--r--libpatch_style_requires_text.c22
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
52 files changed, 3595 insertions, 0 deletions
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 <maandree@kth.se>
+
+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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#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 <sys/stat.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+
+/**
+ * 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 <newline>,
+ * “\” 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 <newline>,
+ * “\” 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 <newline>,
+ * “\” 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 <newline>-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 <newline>
+ */
+ unsigned lf_terminated;
+
+ /**
+ * The number of lines in the file
+ */
+ size_t nlines;
+
+ /**
+ * Each line in the file, these are not <newline> 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 <newline> 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 <newline>
+ *
+ * @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 <sys/timex.h>
+#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 <sys/sysmacros.h>
+
+
+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 = :