diff options
author | Mattias Andrée <maandree@kth.se> | 2024-01-09 22:04:24 +0100 |
---|---|---|
committer | Mattias Andrée <maandree@kth.se> | 2024-01-09 22:04:24 +0100 |
commit | ae850b1ac755f471beac4dbfef4654fe3fbaaae9 (patch) | |
tree | 319e7f7f1b99cd1ee75e100f0a29b0f7c827ccea | |
download | libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.gz libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.bz2 libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.xz |
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
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 @@ -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 @@ -0,0 +1 @@ +A library for parsing and formatting patch files @@ -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 = : |