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 /libpatch.h | |
download | libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.gz libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.bz2 libpatch-ae850b1ac755f471beac4dbfef4654fe3fbaaae9.tar.xz |
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to 'libpatch.h')
-rw-r--r-- | libpatch.h | 1233 |
1 files changed, 1233 insertions, 0 deletions
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 |