diff options
119 files changed, 8323 insertions, 784 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 45ab12193..b46e82515 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,9 +97,9 @@ FILE(GLOB SRC_H include/git2/*.h) # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) ADD_DEFINITIONS(-DWIN32 -D_DEBUG) - FILE(GLOB SRC src/*.c src/transports/*.c src/win32/*.c) + FILE(GLOB SRC src/*.c src/transports/*.c src/xdiff/*.c src/win32/*.c) ELSE() - FILE(GLOB SRC src/*.c src/transports/*.c src/unix/*.c) + FILE(GLOB SRC src/*.c src/transports/*.c src/xdiff/*.c src/unix/*.c) ENDIF () # Compile and link libgit2 diff --git a/examples/Makefile b/examples/Makefile index efb55547b..fe99c75cb 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,13 +1,15 @@ .PHONY: all CC = gcc -CFLAGS = -g -I../include +CFLAGS = -g -I../include -I../src LFLAGS = -L../build -lgit2 -lz +APPS = general showindex diff -all: general showindex +all: $(APPS) % : %.c $(CC) -o $@ $(CFLAGS) $< $(LFLAGS) clean: - $(RM) general showindex + $(RM) $(APPS) + $(RM) -r *.dSYM diff --git a/examples/diff.c b/examples/diff.c new file mode 100644 index 000000000..f80f7029c --- /dev/null +++ b/examples/diff.c @@ -0,0 +1,234 @@ +#include <stdio.h> +#include <git2.h> +#include <stdlib.h> +#include <string.h> + +void check(int error, const char *message) +{ + if (error) { + fprintf(stderr, "%s (%d)\n", message, error); + exit(1); + } +} + +int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree) +{ + int err = 0; + size_t len = strlen(identifier); + git_oid oid; + git_object *obj = NULL; + + /* try to resolve as OID */ + if (git_oid_fromstrn(&oid, identifier, len) == 0) + git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY); + + /* try to resolve as reference */ + if (obj == NULL) { + git_reference *ref, *resolved; + if (git_reference_lookup(&ref, repo, identifier) == 0) { + git_reference_resolve(&resolved, ref); + git_reference_free(ref); + if (resolved) { + git_object_lookup(&obj, repo, git_reference_oid(resolved), GIT_OBJ_ANY); + git_reference_free(resolved); + } + } + } + + if (obj == NULL) + return GIT_ENOTFOUND; + + switch (git_object_type(obj)) { + case GIT_OBJ_TREE: + *tree = (git_tree *)obj; + break; + case GIT_OBJ_COMMIT: + err = git_commit_tree(tree, (git_commit *)obj); + git_object_free(obj); + break; + default: + err = GIT_ENOTFOUND; + } + + return err; +} + +char *colors[] = { + "\033[m", /* reset */ + "\033[1m", /* bold */ + "\033[31m", /* red */ + "\033[32m", /* green */ + "\033[36m" /* cyan */ +}; + +int printer(void *data, char usage, const char *line) +{ + int *last_color = data, color = 0; + + if (*last_color >= 0) { + switch (usage) { + case GIT_DIFF_LINE_ADDITION: color = 3; break; + case GIT_DIFF_LINE_DELETION: color = 2; break; + case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; + case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; + case GIT_DIFF_LINE_FILE_HDR: color = 1; break; + case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; + default: color = 0; + } + if (color != *last_color) { + if (*last_color == 1 || color == 1) + fputs(colors[0], stdout); + fputs(colors[color], stdout); + *last_color = color; + } + } + + fputs(line, stdout); + return 0; +} + +int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) +{ + size_t len = strlen(pattern); + uint16_t strval; + char *endptr = NULL; + if (strncmp(arg, pattern, len)) + return 0; + strval = strtoul(arg + len, &endptr, 0); + if (endptr == arg) + return 0; + *val = strval; + return 1; +} + +int check_str_param(const char *arg, const char *pattern, char **val) +{ + size_t len = strlen(pattern); + if (strncmp(arg, pattern, len)) + return 0; + *val = (char *)(arg + len); + return 1; +} + +void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + char path[GIT_PATH_MAX]; + git_repository *repo = NULL; + git_tree *t1 = NULL, *t2 = NULL; + git_diff_options opts = {0}; + git_diff_list *diff; + int i, color = -1, compact = 0, cached = 0; + char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL; + + /* parse arguments as copied from git-diff */ + + for (i = 1; i < argc; ++i) { + a = argv[i]; + + if (a[0] != '-') { + if (treeish1 == NULL) + treeish1 = a; + else if (treeish2 == NULL) + treeish2 = a; + else + usage("Only one or two tree identifiers can be provided", NULL); + } + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || + !strcmp(a, "--patch")) + compact = 0; + else if (!strcmp(a, "--cached")) + cached = 1; + else if (!strcmp(a, "--name-status")) + compact = 1; + else if (!strcmp(a, "--color")) + color = 0; + else if (!strcmp(a, "--no-color")) + color = -1; + else if (!strcmp(a, "-R")) + opts.flags |= GIT_DIFF_REVERSE; + else if (!strcmp(a, "-a") || !strcmp(a, "--text")) + opts.flags |= GIT_DIFF_FORCE_TEXT; + else if (!strcmp(a, "--ignore-space-at-eol")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + else if (!strcmp(a, "--ignored")) + opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + else if (!strcmp(a, "--untracked")) + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + else if (!check_uint16_param(a, "-U", &opts.context_lines) && + !check_uint16_param(a, "--unified=", &opts.context_lines) && + !check_uint16_param(a, "--inter-hunk-context=", + &opts.interhunk_lines) && + !check_str_param(a, "--src-prefix=", &opts.src_prefix) && + !check_str_param(a, "--dst-prefix=", &opts.dst_prefix)) + usage("Unknown arg", a); + } + + /* open repo */ + + check(git_repository_discover(path, sizeof(path), dir, 0, "/"), + "Could not discover repository"); + check(git_repository_open(&repo, path), + "Could not open repository"); + + if (treeish1) + check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree"); + if (treeish2) + check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree"); + + /* <sha1> <sha2> */ + /* <sha1> --cached */ + /* <sha1> */ + /* --cached */ + /* nothing */ + + if (t1 && t2) + check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Diff"); + else if (t1 && cached) + check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + else if (t1) { + git_diff_list *diff2; + check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + check(git_diff_workdir_to_index(repo, &opts, &diff2), "Diff"); + check(git_diff_merge(diff, diff2), "Merge diffs"); + git_diff_list_free(diff2); + } + else if (cached) { + check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD"); + check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + } + else + check(git_diff_workdir_to_index(repo, &opts, &diff), "Diff"); + + if (color >= 0) + fputs(colors[0], stdout); + + if (compact) + check(git_diff_print_compact(diff, &color, printer), "Displaying diff"); + else + check(git_diff_print_patch(diff, &color, printer), "Displaying diff"); + + if (color >= 0) + fputs(colors[0], stdout); + + git_diff_list_free(diff); + git_tree_free(t1); + git_tree_free(t2); + git_repository_free(repo); + + return 0; +} + diff --git a/include/git2.h b/include/git2.h index 5a55bb284..1711ff8be 100644 --- a/include/git2.h +++ b/include/git2.h @@ -30,6 +30,7 @@ #include "git2/commit.h" #include "git2/tag.h" #include "git2/tree.h" +#include "git2/diff.h" #include "git2/index.h" #include "git2/config.h" diff --git a/include/git2/attr.h b/include/git2/attr.h index 7e8bb9fe8..81d1e517b 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -19,12 +19,12 @@ */ GIT_BEGIN_DECL -#define GIT_ATTR_TRUE git_attr__true -#define GIT_ATTR_FALSE git_attr__false -#define GIT_ATTR_UNSPECIFIED NULL +#define GIT_ATTR_TRUE(attr) ((attr) == git_attr__true) +#define GIT_ATTR_FALSE(attr) ((attr) == git_attr__false) +#define GIT_ATTR_UNSPECIFIED(attr) ((attr) == NULL) -GIT_EXTERN(const char *)git_attr__true; -GIT_EXTERN(const char *)git_attr__false; +GIT_EXTERN(const char *) git_attr__true; +GIT_EXTERN(const char *) git_attr__false; /** diff --git a/include/git2/config.h b/include/git2/config.h index 8a0f58937..acc45b018 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -37,6 +37,19 @@ struct git_config_file { void (*free)(struct git_config_file *); }; +typedef enum { + GIT_CVAR_FALSE = 0, + GIT_CVAR_TRUE = 1, + GIT_CVAR_INT32, + GIT_CVAR_STRING +} git_cvar_t; + +typedef struct { + git_cvar_t cvar_type; + const char *str_match; + int map_value; +} git_cvar_map; + /** * Locate the path to the global configuration file * @@ -301,6 +314,43 @@ GIT_EXTERN(int) git_config_foreach( int (*callback)(const char *var_name, const char *value, void *payload), void *payload); + +/** + * Query the value of a config variable and return it mapped to + * an integer constant. + * + * This is a helper method to easily map different possible values + * to a variable to integer constants that easily identify them. + * + * A mapping array looks as follows: + * + * git_cvar_map autocrlf_mapping[3] = { + * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}, + * {GIT_CVAR_STRING, "default", GIT_AUTO_CRLF_DEFAULT}}; + * + * On any "false" value for the variable (e.g. "false", "FALSE", "no"), the + * mapping will store `GIT_AUTO_CRLF_FALSE` in the `out` parameter. + * + * The same thing applies for any "true" value such as "true", "yes" or "1", storing + * the `GIT_AUTO_CRLF_TRUE` variable. + * + * Otherwise, if the value matches the string "input" (with case insensitive comparison), + * the given constant will be stored in `out`, and likewise for "default". + * + * If not a single match can be made to store in `out`, an error code will be + * returned. + * + * @param cfg config file to get the variables from + * @param name name of the config variable to lookup + * @param maps array of `git_cvar_map` objects specifying the possible mappings + * @param map_n number of mapping objects in `maps` + * @param out place to store the result of the mapping + * @return GIT_SUCCESS on success, error code otherwise + */ +GIT_EXTERN(int) git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/diff.h b/include/git2/diff.h new file mode 100644 index 000000000..ba2e21c27 --- /dev/null +++ b/include/git2/diff.h @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_diff_h__ +#define INCLUDE_git_diff_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" +#include "tree.h" +#include "refs.h" + +/** + * @file git2/diff.h + * @brief Git tree and file differencing routines. + * + * Calculating diffs is generally done in two phases: building a diff list + * then traversing the diff list. This makes is easier to share logic + * across the various types of diffs (tree vs tree, workdir vs index, etc.), + * and also allows you to insert optional diff list post-processing phases, + * such as rename detected, in between the steps. When you are done with a + * diff list object, it must be freed. + * + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +enum { + GIT_DIFF_NORMAL = 0, + GIT_DIFF_REVERSE = (1 << 0), + GIT_DIFF_FORCE_TEXT = (1 << 1), + GIT_DIFF_IGNORE_WHITESPACE = (1 << 2), + GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), + GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), + GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), + GIT_DIFF_PATIENCE = (1 << 6), + GIT_DIFF_INCLUDE_IGNORED = (1 << 7), + GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8) +}; + +/** + * Structure describing options about how the diff should be executed. + * + * Setting all values of the structure to zero will yield the default + * values. Similarly, passing NULL for the options structure will + * give the defaults. The default values are marked below. + * + * @todo Most of the parameters here are not actually supported at this time. + */ +typedef struct { + uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */ + uint16_t context_lines; /**< defaults to 3 */ + uint16_t interhunk_lines; /**< defaults to 3 */ + char *old_prefix; /**< defaults to "a" */ + char *new_prefix; /**< defaults to "b" */ + git_strarray pathspec; /**< defaults to show all paths */ +} git_diff_options; + +/** + * The diff list object that contains all individual file deltas. + */ +typedef struct git_diff_list git_diff_list; + +enum { + GIT_DIFF_FILE_VALID_OID = (1 << 0), + GIT_DIFF_FILE_FREE_PATH = (1 << 1), + GIT_DIFF_FILE_BINARY = (1 << 2), + GIT_DIFF_FILE_NOT_BINARY = (1 << 3), + GIT_DIFF_FILE_FREE_DATA = (1 << 4), + GIT_DIFF_FILE_UNMAP_DATA = (1 << 5) +}; + +/** + * What type of change is described by a git_diff_delta? + */ +typedef enum { + GIT_DELTA_UNMODIFIED = 0, + GIT_DELTA_ADDED = 1, + GIT_DELTA_DELETED = 2, + GIT_DELTA_MODIFIED = 3, + GIT_DELTA_RENAMED = 4, + GIT_DELTA_COPIED = 5, + GIT_DELTA_IGNORED = 6, + GIT_DELTA_UNTRACKED = 7 +} git_delta_t; + +/** + * Description of one side of a diff. + */ +typedef struct { + git_oid oid; + char *path; + uint16_t mode; + git_off_t size; + unsigned int flags; +} git_diff_file; + +/** + * Description of changes to one file. + * + * When iterating over a diff list object, this will generally be passed to + * most callback functions and you can use the contents to understand + * exactly what has changed. + * + * Under some circumstances, not all fields will be filled in, but the code + * generally tries to fill in as much as possible. One example is that the + * "binary" field will not actually look at file contents if you do not + * pass in hunk and/or line callbacks to the diff foreach iteration function. + * It will just use the git attributes for those files. + */ +typedef struct { + git_diff_file old_file; + git_diff_file new_file; + git_delta_t status; + unsigned int similarity; /**< for RENAMED and COPIED, value 0-100 */ + int binary; +} git_diff_delta; + +/** + * When iterating over a diff, callback that will be made per file. + */ +typedef int (*git_diff_file_fn)( + void *cb_data, + git_diff_delta *delta, + float progress); + +/** + * Structure describing a hunk of a diff. + */ +typedef struct { + int old_start; + int old_lines; + int new_start; + int new_lines; +} git_diff_range; + +/** + * When iterating over a diff, callback that will be made per hunk. + */ +typedef int (*git_diff_hunk_fn)( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len); + +/** + * Line origin constants. + * + * These values describe where a line came from and will be passed to + * the git_diff_line_fn when iterating over a diff. There are some + * special origin contants at the end that are used for the text + * output callbacks to demarcate lines that are actually part of + * the file or hunk headers. + */ +enum { + /* these values will be sent to `git_diff_line_fn` along with the line */ + GIT_DIFF_LINE_CONTEXT = ' ', + GIT_DIFF_LINE_ADDITION = '+', + GIT_DIFF_LINE_DELETION = '-', + GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */ + GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */ + /* these values will only be sent to a `git_diff_output_fn` */ + GIT_DIFF_LINE_FILE_HDR = 'F', + GIT_DIFF_LINE_HUNK_HDR = 'H', + GIT_DIFF_LINE_BINARY = 'B' +}; + +/** + * When iterating over a diff, callback that will be made per text diff + * line. + */ +typedef int (*git_diff_line_fn)( + void *cb_data, + git_diff_delta *delta, + char line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char *content, + size_t content_len); + +/** + * When printing a diff, callback that will be made to output each line + * of text. This uses some extra GIT_DIFF_LINE_... constants for output + * of lines of file and hunk headers. + */ +typedef int (*git_diff_output_fn)( + void *cb_data, + char line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char *formatted_output); + + +/** @name Diff List Generator Functions + * + * These are the functions you would use to create (or destroy) a + * git_diff_list from various objects in a repository. + */ +/**@{*/ + +/** + * Deallocate a diff list. + */ +GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); + +/** + * Compute a difference between two tree objects. + * + * @param repo The repository containing the trees. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param old_tree A git_tree object to diff from. + * @param new_tree A git_tree object to diff to. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_tree_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_tree *new_tree, + git_diff_list **diff); + +/** + * Compute a difference between a tree and the index. + * + * @param repo The repository containing the tree and index. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param old_tree A git_tree object to diff from. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_index_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_diff_list **diff); + +/** + * Compute a difference between the working directory and the index. + * + * @param repo The repository. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_workdir_to_index( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_diff_list **diff); + +/** + * Compute a difference between the working directory and a tree. + * + * This returns strictly the differences between the tree and the + * files contained in the working directory, regardless of the state + * of files in the index. There is no direct equivalent in C git. + * + * This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'. Those + * commands diff the tree, the index, and the workdir. To emulate those + * functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`, + * then call `git_diff_merge` on the results. + * + * @param repo The repository containing the tree. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param old_tree A git_tree object to diff from. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_workdir_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_diff_list **diff); + +/** + * Merge one diff list into another. + * + * This merges items from the "from" list into the "onto" list. The + * resulting diff list will have all items that appear in either list. + * If an item appears in both lists, then it will be "merged" to appear + * as if the old version was from the "onto" list and the new version + * is from the "from" list (with the exception that if the item has a + * pending DELETE in the middle, then it will show as deleted). + * + * @param onto Diff to merge into. + * @param from Diff to merge. + */ +GIT_EXTERN(int) git_diff_merge( + git_diff_list *onto, + const git_diff_list *from); + +/**@}*/ + + +/** @name Diff List Processor Functions + * + * These are the functions you apply to a diff list to process it + * or read it in some way. + */ +/**@{*/ + +/** + * Iterate over a diff list issuing callbacks. + * + * If the hunk and/or line callbacks are not NULL, then this will calculate + * text diffs for all files it thinks are not binary. If those are both + * NULL, then this will not bother with the text diffs, so it can be + * efficient. + */ +GIT_EXTERN(int) git_diff_foreach( + git_diff_list *diff, + void *cb_data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_line_fn line_cb); + +/** + * Iterate over a diff generating text output like "git diff --name-status". + */ +GIT_EXTERN(int) git_diff_print_compact( + git_diff_list *diff, + void *cb_data, + git_diff_output_fn print_cb); + +/** + * Iterate over a diff generating text output like "git diff". + * + * This is a super easy way to generate a patch from a diff. + */ +GIT_EXTERN(int) git_diff_print_patch( + git_diff_list *diff, + void *cb_data, + git_diff_output_fn print_cb); + +/**@}*/ + + +/* + * Misc + */ + +/** + * Directly run a text diff on two blobs. + */ +GIT_EXTERN(int) git_diff_blobs( + git_repository *repo, + git_blob *old_blob, + git_blob *new_blob, + git_diff_options *options, + void *cb_data, + git_diff_hunk_fn hunk_cb, + git_diff_line_fn line_cb); + +GIT_END_DECL + +/** @} */ + +#endif diff --git a/include/git2/oid.h b/include/git2/oid.h index ad7086164..712ecb2bb 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -160,6 +160,11 @@ GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, unsigned int le GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str); /** + * Check is an oid is all zeros. + */ +GIT_EXTERN(int) git_oid_iszero(const git_oid *a); + +/** * OID Shortener object */ typedef struct git_oid_shorten git_oid_shorten; diff --git a/include/git2/status.h b/include/git2/status.h index 2a304b82f..5c45dae1e 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -31,7 +31,7 @@ GIT_BEGIN_DECL #define GIT_STATUS_WT_MODIFIED (1 << 4) #define GIT_STATUS_WT_DELETED (1 << 5) -#define GIT_STATUS_IGNORED (1 << 6) +#define GIT_STATUS_IGNORED (1 << 6) /** * Gather file statuses and run a callback for each one. diff --git a/include/git2/tree.h b/include/git2/tree.h index 95e0fdf94..972c3795c 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -313,8 +313,6 @@ enum git_treewalk_mode { */ GIT_EXTERN(int) git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload); -/** @} */ - typedef enum { GIT_STATUS_ADDED = 1, GIT_STATUS_DELETED = 2, @@ -349,5 +347,7 @@ int git_tree_diff(git_tree *old, git_tree *newer, git_tree_diff_cb cb, void *dat int git_tree_diff_index_recursive(git_tree *tree, git_index *index, git_tree_diff_cb cb, void *data); +/** @} */ + GIT_END_DECL #endif diff --git a/packaging/rpm/README b/packaging/rpm/README new file mode 100644 index 000000000..1a6410b16 --- /dev/null +++ b/packaging/rpm/README @@ -0,0 +1,6 @@ +To build RPM pakcages for Fedora, follow these steps: + cp packaging/rpm/libgit2.spec ~/rpmbuild/SPECS + cd ~/rpmbuild/SOURCES + wget https://github.com/downloads/libgit2/libgit2/libgit2-0.16.0.tar.gz + cd ~/rpmbuild/SPECS + rpmbuild -ba libgit2.spec diff --git a/packaging/rpm/libgit2.spec b/packaging/rpm/libgit2.spec new file mode 100644 index 000000000..a6e82b241 --- /dev/null +++ b/packaging/rpm/libgit2.spec @@ -0,0 +1,106 @@ +# +# spec file for package libgit2 +# +# Copyright (c) 2012 Saleem Ansari <tuxdna@gmail.com> +# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2011, Sascha Peilicke <saschpe@gmx.de> +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# +Name: libgit2 +Version: 0.16.0 +Release: 1 +Summary: C git library +License: GPL-2.0 with linking +Group: Development/Libraries/C and C++ +Url: http://libgit2.github.com/ +Source0: https://github.com/downloads/libgit2/libgit2/libgit2-0.16.0.tar.gz +BuildRequires: cmake +BuildRequires: pkgconfig +BuildRoot: %{_tmppath}/%{name}-%{version}-build +%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos_version} +BuildRequires: openssl-devel +%else +BuildRequires: libopenssl-devel +%endif + +%description +libgit2 is a portable, pure C implementation of the Git core methods +provided as a re-entrant linkable library with a solid API, allowing +you to write native speed custom Git applications in any language +with bindings. + +%package -n %{name}-0 +Summary: C git library +Group: System/Libraries + +%description -n %{name}-0 +libgit2 is a portable, pure C implementation of the Git core methods +provided as a re-entrant linkable library with a solid API, allowing +you to write native speed custom Git applications in any language +with bindings. + +%package devel +Summary: C git library +Group: Development/Libraries/C and C++ +Requires: %{name}-0 >= %{version} + +%description devel +This package contains all necessary include files and libraries needed +to compile and develop applications that use libgit2. + +%prep +%setup -q + +%build +cmake . \ + -DCMAKE_C_FLAGS:STRING="%{optflags}" \ + -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \ + -DINSTALL_LIB:PATH=%{_libdir} +make %{?_smp_mflags} + +%install +%make_install + +%post -n %{name}-0 -p /sbin/ldconfig +%postun -n %{name}-0 -p /sbin/ldconfig + +%files -n %{name}-0 +%defattr (-,root,root) +%doc AUTHORS COPYING README.md +%{_libdir}/%{name}.so.* + +%files devel +%defattr (-,root,root) +%doc CONVENTIONS examples +%{_libdir}/%{name}.so +%{_includedir}/git2* +%{_libdir}/pkgconfig/libgit2.pc + +%changelog +* Tue Mar 04 2012 tuxdna@gmail.com +- Update to version 0.16.0 +* Tue Jan 31 2012 jengelh@medozas.de +- Provide pkgconfig symbols +* Thu Oct 27 2011 saschpe@suse.de +- Change license to 'GPL-2.0 with linking', fixes bnc#726789 +* Wed Oct 26 2011 saschpe@suse.de +- Update to version 0.15.0: + * Upstream doesn't provide changes +- Removed outdated %%clean section +* Tue Jan 18 2011 saschpe@gmx.de +- Proper Requires for devel package +* Tue Jan 18 2011 saschpe@gmx.de +- Set BuildRequires to "openssl-devel" also for RHEL and CentOS +* Tue Jan 18 2011 saschpe@gmx.de +- Initial commit (0.0.1) +- Added patch to fix shared library soname diff --git a/src/attr.c b/src/attr.c index a7c65f94c..603498df2 100644 --- a/src/attr.c +++ b/src/attr.c @@ -408,10 +408,9 @@ void git_attr_cache_flush( return; if (repo->attrcache.files) { - const void *GIT_UNUSED(name); git_attr_file *file; - GIT_HASHTABLE_FOREACH(repo->attrcache.files, name, file, + GIT_HASHTABLE_FOREACH_VALUE(repo->attrcache.files, file, git_attr_file__free(file)); git_hashtable_free(repo->attrcache.files); @@ -419,10 +418,9 @@ void git_attr_cache_flush( } if (repo->attrcache.macros) { - const void *GIT_UNUSED(name); git_attr_rule *rule; - GIT_HASHTABLE_FOREACH(repo->attrcache.macros, name, rule, + GIT_HASHTABLE_FOREACH_VALUE(repo->attrcache.macros, rule, git_attr_rule__free(rule)); git_hashtable_free(repo->attrcache.macros); diff --git a/src/attr_file.c b/src/attr_file.c index 7911381ea..3783b5ef3 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -111,7 +111,7 @@ int git_attr_file__from_file( git_repository *repo, const char *path, git_attr_file *file) { int error = GIT_SUCCESS; - git_fbuffer fbuf = GIT_FBUFFER_INIT; + git_buf fbuf = GIT_BUF_INIT; assert(path && file); @@ -120,9 +120,9 @@ int git_attr_file__from_file( if (error == GIT_SUCCESS && (error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS) - error = git_attr_file__from_buffer(repo, fbuf.data, file); + error = git_attr_file__from_buffer(repo, fbuf.ptr, file); - git_futils_freebuffer(&fbuf); + git_buf_free(&fbuf); if (error != GIT_SUCCESS) git__rethrow(error, "Could not open attribute file '%s'", path); @@ -458,12 +458,12 @@ int git_attr_assignment__parse( } assign->name_hash = 5381; - assign->value = GIT_ATTR_TRUE; + assign->value = git_attr__true; assign->is_allocated = 0; /* look for magic name prefixes */ if (*scan == '-') { - assign->value = GIT_ATTR_FALSE; + assign->value = git_attr__false; scan++; } else if (*scan == '!') { assign->value = NULL; /* explicit unspecified state */ @@ -510,7 +510,7 @@ int git_attr_assignment__parse( } /* expand macros (if given a repo with a macro cache) */ - if (repo != NULL && assign->value == GIT_ATTR_TRUE) { + if (repo != NULL && assign->value == git_attr__true) { git_attr_rule *macro = git_hashtable_lookup(repo->attrcache.macros, assign->name); diff --git a/src/blob.c b/src/blob.c index 4065ffa12..b67f8afa5 100644 --- a/src/blob.c +++ b/src/blob.c @@ -11,6 +11,7 @@ #include "common.h" #include "blob.h" +#include "filter.h" const void *git_blob_rawcontent(git_blob *blob) { @@ -24,6 +25,12 @@ size_t git_blob_rawsize(git_blob *blob) return blob->odb_object->raw.len; } +int git_blob__getbuf(git_buf *buffer, git_blob *blob) +{ + return git_buf_set( + buffer, blob->odb_object->raw.data, blob->odb_object->raw.len); +} + void git_blob__free(git_blob *blob) { git_odb_object_free(blob->odb_object); @@ -65,15 +72,100 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b return GIT_SUCCESS; } +static int write_file_stream(git_oid *oid, git_odb *odb, const char *path, git_off_t file_size) +{ + int fd, error; + char buffer[4096]; + git_odb_stream *stream = NULL; + + if ((error = git_odb_open_wstream(&stream, odb, file_size, GIT_OBJ_BLOB)) < GIT_SUCCESS) + return error; + + if ((fd = p_open(path, O_RDONLY)) < 0) { + error = git__throw(GIT_ENOTFOUND, "Failed to create blob. Could not open '%s'", path); + goto cleanup; + } + + while (file_size > 0) { + ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); + + if (read_len < 0) { + error = git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); + p_close(fd); + goto cleanup; + } + + stream->write(stream, buffer, read_len); + file_size -= read_len; + } + + p_close(fd); + error = stream->finalize_write(oid, stream); + +cleanup: + stream->free(stream); + return error; +} + +static int write_file_filtered( + git_oid *oid, + git_odb *odb, + const char *full_path, + git_vector *filters) +{ + int error; + git_buf source = GIT_BUF_INIT; + git_buf dest = GIT_BUF_INIT; + + error = git_futils_readbuffer(&source, full_path); + if (error < GIT_SUCCESS) + return error; + + error = git_filters_apply(&dest, &source, filters); + + /* Free the source as soon as possible. This can be big in memory, + * and we don't want to ODB write to choke */ + git_buf_free(&source); + + if (error == GIT_SUCCESS) { + /* Write the file to disk if it was properly filtered */ + error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); + } + + git_buf_free(&dest); + return GIT_SUCCESS; +} + +static int write_symlink(git_oid *oid, git_odb *odb, const char *path, size_t link_size) +{ + char *link_data; + ssize_t read_len; + int error; + + link_data = git__malloc(link_size); + if (!link_data) + return GIT_ENOMEM; + + read_len = p_readlink(path, link_data, link_size); + + if (read_len != (ssize_t)link_size) { + free(link_data); + return git__throw(GIT_EOSERR, "Failed to create blob. Can't read symlink"); + } + + error = git_odb_write(oid, odb, (void *)link_data, link_size, GIT_OBJ_BLOB); + free(link_data); + return error; +} + int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path) { int error = GIT_SUCCESS; git_buf full_path = GIT_BUF_INIT; git_off_t size; - git_odb_stream *stream = NULL; struct stat st; const char *workdir; - git_odb *odb; + git_odb *odb = NULL; workdir = git_repository_workdir(repo); if (workdir == NULL) @@ -95,63 +187,45 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat if (error < GIT_SUCCESS) goto cleanup; - if ((error = git_odb_open_wstream(&stream, odb, (size_t)size, GIT_OBJ_BLOB)) < GIT_SUCCESS) - goto cleanup; - if (S_ISLNK(st.st_mode)) { - char *link_data; - ssize_t read_len; - - link_data = git__malloc((size_t)size); - if (!link_data) { - error = GIT_ENOMEM; - goto cleanup; - } - - read_len = p_readlink(full_path.ptr, link_data, (size_t)size); - - if (read_len != (ssize_t)size) { - error = git__throw(GIT_EOSERR, "Failed to create blob. Can't read symlink"); - free(link_data); - goto cleanup; - } - - stream->write(stream, link_data, (size_t)size); - free(link_data); - + error = write_symlink(oid, odb, full_path.ptr, (size_t)size); } else { - int fd; - char buffer[2048]; - - if ((fd = p_open(full_path.ptr, O_RDONLY)) < 0) { - error = git__throw(GIT_ENOTFOUND, "Failed to create blob. Could not open '%s'", full_path.ptr); - goto cleanup; - } - - while (size > 0) { - ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); + git_vector write_filters = GIT_VECTOR_INIT; + int filter_count; - if (read_len < 0) { - error = git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); - p_close(fd); - goto cleanup; - } + /* Load the filters for writing this file to the ODB */ + filter_count = git_filters_load(&write_filters, repo, path, GIT_FILTER_TO_ODB); - stream->write(stream, buffer, read_len); - size -= read_len; + if (filter_count < 0) { + /* Negative value means there was a critical error */ + error = filter_count; + goto cleanup; + } else if (filter_count == 0) { + /* No filters need to be applied to the document: we can stream + * directly from disk */ + error = write_file_stream(oid, odb, full_path.ptr, size); + } else { + /* We need to apply one or more filters */ + error = write_file_filtered(oid, odb, full_path.ptr, &write_filters); } - p_close(fd); + git_filters_free(&write_filters); + + /* + * TODO: eventually support streaming filtered files, for files which are bigger + * than a given threshold. This is not a priority because applying a filter in + * streaming mode changes the final size of the blob, and without knowing its + * final size, the blob cannot be written in stream mode to the ODB. + * + * The plan is to do streaming writes to a tempfile on disk and then opening + * streaming that file to the ODB, using `write_file_stream`. + * + * CAREFULLY DESIGNED APIS YO + */ } - error = stream->finalize_write(oid, stream); - cleanup: - if (stream) - stream->free(stream); - git_buf_free(&full_path); - return error; } diff --git a/src/blob.h b/src/blob.h index f810b506b..0305e9473 100644 --- a/src/blob.h +++ b/src/blob.h @@ -19,5 +19,6 @@ struct git_blob { void git_blob__free(git_blob *blob); int git_blob__parse(git_blob *blob, git_odb_object *obj); +int git_blob__getbuf(git_buf *buffer, git_blob *blob); #endif diff --git a/src/buffer.c b/src/buffer.c index 183da7c5f..3098f6d68 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -7,14 +7,17 @@ #include "buffer.h" #include "posix.h" #include <stdarg.h> +#include <ctype.h> /* Used as default value for git_buf->ptr so that people can always * assume ptr is non-NULL and zero terminated even for new git_bufs. */ char git_buf_initbuf[1]; +static char git_buf__oom; + #define ENSURE_SIZE(b, d) \ - if ((ssize_t)(d) > buf->asize && git_buf_grow(b, (d)) < GIT_SUCCESS)\ + if ((d) > buf->asize && git_buf_grow(b, (d)) < GIT_SUCCESS)\ return GIT_ENOMEM; @@ -31,8 +34,10 @@ void git_buf_init(git_buf *buf, size_t initial_size) int git_buf_grow(git_buf *buf, size_t target_size) { int error = git_buf_try_grow(buf, target_size); - if (error != GIT_SUCCESS) - buf->asize = -1; + if (error != GIT_SUCCESS) { + buf->ptr = &git_buf__oom; + } + return error; } @@ -41,17 +46,17 @@ int git_buf_try_grow(git_buf *buf, size_t target_size) char *new_ptr; size_t new_size; - if (buf->asize < 0) + if (buf->ptr == &git_buf__oom) return GIT_ENOMEM; - if (target_size <= (size_t)buf->asize) + if (target_size <= buf->asize) return GIT_SUCCESS; if (buf->asize == 0) { new_size = target_size; new_ptr = NULL; } else { - new_size = (size_t)buf->asize; + new_size = buf->asize; new_ptr = buf->ptr; } @@ -64,7 +69,6 @@ int git_buf_try_grow(git_buf *buf, size_t target_size) new_size = (new_size + 7) & ~7; new_ptr = git__realloc(new_ptr, new_size); - /* if realloc fails, return without modifying the git_buf */ if (!new_ptr) return GIT_ENOMEM; @@ -83,7 +87,7 @@ void git_buf_free(git_buf *buf) { if (!buf) return; - if (buf->ptr != git_buf_initbuf) + if (buf->ptr != git_buf_initbuf && buf->ptr != &git_buf__oom) git__free(buf->ptr); git_buf_init(buf, 0); @@ -98,12 +102,12 @@ void git_buf_clear(git_buf *buf) int git_buf_oom(const git_buf *buf) { - return (buf->asize < 0); + return (buf->ptr == &git_buf__oom); } int git_buf_lasterror(const git_buf *buf) { - return (buf->asize < 0) ? GIT_ENOMEM : GIT_SUCCESS; + return (buf->ptr == &git_buf__oom) ? GIT_ENOMEM : GIT_SUCCESS; } int git_buf_set(git_buf *buf, const char *data, size_t len) @@ -162,11 +166,12 @@ int git_buf_printf(git_buf *buf, const char *format, ...) va_end(arglist); if (len < 0) { - buf->asize = -1; + free(buf->ptr); + buf->ptr = &git_buf__oom; return GIT_ENOMEM; } - if (len + 1 <= buf->asize - buf->size) { + if ((size_t)len + 1 <= buf->asize - buf->size) { buf->size += len; break; } @@ -205,9 +210,9 @@ void git_buf_consume(git_buf *buf, const char *end) } } -void git_buf_truncate(git_buf *buf, ssize_t len) +void git_buf_truncate(git_buf *buf, size_t len) { - if (len >= 0 && len < buf->size) { + if (len < buf->size) { buf->size = len; buf->ptr[buf->size] = '\0'; } @@ -230,7 +235,7 @@ char *git_buf_detach(git_buf *buf) { char *data = buf->ptr; - if (buf->asize <= 0) + if (buf->asize == 0 || buf->ptr == &git_buf__oom) return NULL; git_buf_init(buf, 0); @@ -238,7 +243,7 @@ char *git_buf_detach(git_buf *buf) return data; } -void git_buf_attach(git_buf *buf, char *ptr, ssize_t asize) +void git_buf_attach(git_buf *buf, char *ptr, size_t asize) { git_buf_free(buf); @@ -372,3 +377,22 @@ int git_buf_join( return error; } + +void git_buf_rtrim(git_buf *buf) +{ + while (buf->size > 0) { + if (!isspace(buf->ptr[buf->size - 1])) + break; + + buf->size--; + } + + buf->ptr[buf->size] = '\0'; +} + +int git_buf_cmp(const git_buf *a, const git_buf *b) +{ + int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); + return (result != 0) ? result : + (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; +} diff --git a/src/buffer.h b/src/buffer.h index 3969f461e..3cdd794af 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -11,7 +11,7 @@ typedef struct { char *ptr; - ssize_t asize, size; + size_t asize, size; } git_buf; extern char git_buf_initbuf[]; @@ -47,7 +47,7 @@ int git_buf_try_grow(git_buf *buf, size_t target_size); void git_buf_free(git_buf *buf); void git_buf_swap(git_buf *buf_a, git_buf *buf_b); char *git_buf_detach(git_buf *buf); -void git_buf_attach(git_buf *buf, char *ptr, ssize_t asize); +void git_buf_attach(git_buf *buf, char *ptr, size_t asize); /** * Test if there have been any reallocation failures with this git_buf. @@ -83,7 +83,7 @@ int git_buf_puts(git_buf *buf, const char *string); int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); void git_buf_clear(git_buf *buf); void git_buf_consume(git_buf *buf, const char *end); -void git_buf_truncate(git_buf *buf, ssize_t len); +void git_buf_truncate(git_buf *buf, size_t len); void git_buf_rtruncate_at_char(git_buf *path, char separator); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); @@ -115,4 +115,9 @@ GIT_INLINE(int) git_buf_rfind_next(git_buf *buf, char ch) return idx; } +/* Remove whitespace from the end of the buffer */ +void git_buf_rtrim(git_buf *buf); + +int git_buf_cmp(const git_buf *a, const git_buf *b); + #endif diff --git a/src/cc-compat.h b/src/cc-compat.h index 29cc2ec6a..3df36b61f 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -33,21 +33,7 @@ # define GIT_TYPEOF(x) #endif -#ifdef __cplusplus -# define GIT_UNUSED(x) -#else -# ifdef __GNUC__ -# define GIT_UNUSED(x) x __attribute__ ((__unused__)) -# else -# define GIT_UNUSED(x) x -# endif -#endif - -#if defined(_MSC_VER) -#define GIT_UNUSED_ARG(x) ((void)(x)); /* note trailing ; */ -#else -#define GIT_UNUSED_ARG(x) -#endif +#define GIT_UNUSED(x) ((void)(x)) /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) diff --git a/src/config.c b/src/config.c index 4ff1b2e72..912224158 100644 --- a/src/config.c +++ b/src/config.c @@ -209,23 +209,37 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return file->set(file, name, value); } -/*********** - * Getters - ***********/ +static int parse_bool(int *out, const char *value) +{ + /* A missing value means true */ + if (value == NULL) { + *out = 1; + return GIT_SUCCESS; + } -int git_config_get_int64(git_config *cfg, const char *name, int64_t *out) + if (!strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return GIT_SUCCESS; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off")) { + *out = 0; + return GIT_SUCCESS; + } + + return GIT_EINVALIDTYPE; +} + +static int parse_int64(int64_t *out, const char *value) { - const char *value, *num_end; - int ret; + const char *num_end; int64_t num; - ret = git_config_get_string(cfg, name, &value); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to retrieve value for '%s'", name); - - ret = git__strtol64(&num, value, &num_end, 0); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to convert value for '%s'", name); + if (git__strtol64(&num, value, &num_end, 0) < 0) + return GIT_EINVALIDTYPE; switch (*num_end) { case 'g': @@ -245,38 +259,112 @@ int git_config_get_int64(git_config *cfg, const char *name, int64_t *out) /* check that that there are no more characters after the * given modifier suffix */ if (num_end[1] != '\0') - return git__throw(GIT_EINVALIDTYPE, - "Failed to get value for '%s'. Invalid type suffix", name); + return GIT_EINVALIDTYPE; /* fallthrough */ case '\0': *out = num; - return GIT_SUCCESS; + return 0; default: - return git__throw(GIT_EINVALIDTYPE, - "Failed to get value for '%s'. Value is of invalid type", name); + return GIT_EINVALIDTYPE; } } -int git_config_get_int32(git_config *cfg, const char *name, int32_t *out) +static int parse_int32(int32_t *out, const char *value) { - int64_t tmp_long; - int32_t tmp_int; + int64_t tmp; + int32_t truncate; + + if (parse_int64(&tmp, value) < 0) + return GIT_EINVALIDTYPE; + + truncate = tmp & 0xFFFFFFFF; + if (truncate != tmp) + return GIT_EOVERFLOW; + + *out = truncate; + return 0; +} + +/*********** + * Getters + ***********/ +int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out) +{ + size_t i; + const char *value; + int error; + + error = git_config_get_string(cfg, name, &value); + if (error < GIT_SUCCESS) + return error; + + for (i = 0; i < map_n; ++i) { + git_cvar_map *m = maps + i; + + switch (m->cvar_type) { + case GIT_CVAR_FALSE: + case GIT_CVAR_TRUE: { + int bool_val; + + if (parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->cvar_type) { + *out = m->map_value; + return 0; + } + + break; + } + + case GIT_CVAR_INT32: + if (parse_int32(out, value) == 0) + return 0; + + break; + + case GIT_CVAR_STRING: + if (strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; + } + } + } + + return git__throw(GIT_ENOTFOUND, + "Failed to map the '%s' config variable with a valid value", name); +} + +int git_config_get_int64(git_config *cfg, const char *name, int64_t *out) +{ + const char *value; int ret; - ret = git_config_get_int64(cfg, name, &tmp_long); + ret = git_config_get_string(cfg, name, &value); if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to convert value for '%s'", name); - - tmp_int = tmp_long & 0xFFFFFFFF; - if (tmp_int != tmp_long) - return git__throw(GIT_EOVERFLOW, "Value for '%s' is too large", name); + return git__rethrow(ret, "Failed to retrieve value for '%s'", name); - *out = tmp_int; + if (parse_int64(out, value) < 0) + return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as an integer", value); - return ret; + return GIT_SUCCESS; +} + +int git_config_get_int32(git_config *cfg, const char *name, int32_t *out) +{ + const char *value; + int error; + + error = git_config_get_string(cfg, name, &value); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to get value for %s", name); + + error = parse_int32(out, value); + if (error < GIT_SUCCESS) + return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as a 32-bit integer", value); + + return GIT_SUCCESS; } int git_config_get_bool(git_config *cfg, const char *name, int *out) @@ -288,33 +376,15 @@ int git_config_get_bool(git_config *cfg, const char *name, int *out) if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to get value for %s", name); - /* A missing value means true */ - if (value == NULL) { - *out = 1; + if (parse_bool(out, value) == 0) return GIT_SUCCESS; - } - if (!strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on")) { - *out = 1; - return GIT_SUCCESS; - } - if (!strcasecmp(value, "false") || - !strcasecmp(value, "no") || - !strcasecmp(value, "off")) { - *out = 0; + if (parse_int32(out, value) == 0) { + *out = !!(*out); return GIT_SUCCESS; } - /* Try to parse it as an integer */ - error = git_config_get_int32(cfg, name, out); - if (error == GIT_SUCCESS) - *out = !!(*out); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to get value for %s", name); - return error; + return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as a boolean value", value); } int git_config_get_string(git_config *cfg, const char *name, const char **out) diff --git a/src/config_cache.c b/src/config_cache.c new file mode 100644 index 000000000..5e20847f5 --- /dev/null +++ b/src/config_cache.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "fileops.h" +#include "hashtable.h" +#include "config.h" +#include "git2/config.h" +#include "vector.h" +#include "filter.h" +#include "repository.h" + +struct map_data { + const char *cvar_name; + git_cvar_map *maps; + size_t map_count; + int default_value; +}; + +/* + * core.eol + * Sets the line ending type to use in the working directory for + * files that have the text property set. Alternatives are lf, crlf + * and native, which uses the platform’s native line ending. The default + * value is native. See gitattributes(5) for more information on + * end-of-line conversion. + */ +static git_cvar_map _cvar_map_eol[] = { + {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET}, + {GIT_CVAR_STRING, "lf", GIT_EOL_LF}, + {GIT_CVAR_STRING, "crlf", GIT_EOL_CRLF}, + {GIT_CVAR_STRING, "native", GIT_EOL_NATIVE} +}; + +/* + * core.autocrlf + * Setting this variable to "true" is almost the same as setting + * the text attribute to "auto" on all files except that text files are + * not guaranteed to be normalized: files that contain CRLF in the + * repository will not be touched. Use this setting if you want to have + * CRLF line endings in your working directory even though the repository + * does not have normalized line endings. This variable can be set to input, + * in which case no output conversion is performed. + */ +static git_cvar_map _cvar_map_autocrlf[] = { + {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} +}; + +static struct map_data _cvar_maps[] = { + {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT}, + {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT} +}; + +int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) +{ + *out = repo->cvar_cache[(int)cvar]; + + if (*out == GIT_CVAR_NOT_CACHED) { + struct map_data *data = &_cvar_maps[(int)cvar]; + git_config *config; + int error; + + error = git_repository_config__weakptr(&config, repo); + if (error < GIT_SUCCESS) + return error; + + error = git_config_get_mapped( + config, data->cvar_name, data->maps, data->map_count, out); + + if (error == GIT_ENOTFOUND) + *out = data->default_value; + + else if (error < GIT_SUCCESS) + return error; + + repo->cvar_cache[(int)cvar] = *out; + } + + return GIT_SUCCESS; +} + +void git_repository__cvar_cache_clear(git_repository *repo) +{ + int i; + + for (i = 0; i < GIT_CVAR_CACHE_MAX; ++i) + repo->cvar_cache[i] = GIT_CVAR_NOT_CACHED; +} + diff --git a/src/config_file.c b/src/config_file.c index c9c7d11eb..3c7c593ec 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -73,7 +73,7 @@ typedef struct { git_hashtable *values; struct { - git_fbuffer buffer; + git_buf buffer; char *read_ptr; int line_number; int eof; @@ -125,13 +125,12 @@ static int normalize_name(const char *in, char **out) static void free_vars(git_hashtable *values) { - const char *GIT_UNUSED(_unused) = NULL; cvar_t *var = NULL; if (values == NULL) return; - GIT_HASHTABLE_FOREACH(values, _unused, var, + GIT_HASHTABLE_FOREACH_VALUE(values, var, do { cvar_t *next = CVAR_LIST_NEXT(var); cvar_free(var); @@ -151,6 +150,7 @@ static int config_open(git_config_file *cfg) if (b->values == NULL) return GIT_ENOMEM; + git_buf_init(&b->reader.buffer, 0); error = git_futils_readbuffer(&b->reader.buffer, b->file_path); /* It's fine if the file doesn't exist */ @@ -164,14 +164,14 @@ static int config_open(git_config_file *cfg) if (error < GIT_SUCCESS) goto cleanup; - git_futils_freebuffer(&b->reader.buffer); + git_buf_free(&b->reader.buffer); return GIT_SUCCESS; cleanup: free_vars(b->values); b->values = NULL; - git_futils_freebuffer(&b->reader.buffer); + git_buf_free(&b->reader.buffer); return git__rethrow(error, "Failed to open config"); } @@ -765,7 +765,7 @@ static int skip_bom(diskfile_backend *cfg) { static const char utf8_bom[] = "\xef\xbb\xbf"; - if (cfg->reader.buffer.len < sizeof(utf8_bom)) + if (cfg->reader.buffer.size < sizeof(utf8_bom)) return GIT_SUCCESS; if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) @@ -847,7 +847,7 @@ static int config_parse(diskfile_backend *cfg_file) git_buf buf = GIT_BUF_INIT; /* Initialize the reading position */ - cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; + cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr; cfg_file->reader.eof = 0; /* If the file is empty, there's nothing for us to do */ @@ -976,10 +976,9 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p cfg->reader.read_ptr = NULL; cfg->reader.eof = 1; data_start = NULL; - cfg->reader.buffer.len = 0; - cfg->reader.buffer.data = NULL; + git_buf_clear(&cfg->reader.buffer); } else { - cfg->reader.read_ptr = cfg->reader.buffer.data; + cfg->reader.read_ptr = cfg->reader.buffer.ptr; cfg->reader.eof = 0; data_start = cfg->reader.read_ptr; } @@ -1093,7 +1092,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p /* And then the write out rest of the file */ error = git_filebuf_write(&file, post_start, - cfg->reader.buffer.len - (post_start - data_start)); + cfg->reader.buffer.size - (post_start - data_start)); if (error < GIT_SUCCESS) { git__rethrow(error, "Failed to write the rest of the file"); @@ -1128,7 +1127,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p goto cleanup; } - error = git_filebuf_write(&file, cfg->reader.buffer.data, cfg->reader.buffer.len); + error = git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size); if (error < GIT_SUCCESS) { git__rethrow(error, "Failed to write original config content"); goto cleanup; @@ -1155,7 +1154,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p else error = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); - git_futils_freebuffer(&cfg->reader.buffer); + git_buf_free(&cfg->reader.buffer); return error; } diff --git a/src/crlf.c b/src/crlf.c new file mode 100644 index 000000000..f0ec7b736 --- /dev/null +++ b/src/crlf.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "fileops.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" + +#include "git2/attr.h" + +struct crlf_attrs { + int crlf_action; + int eol; +}; + +struct crlf_filter { + git_filter f; + struct crlf_attrs attrs; +}; + +static int check_crlf(const char *value) +{ + if (GIT_ATTR_TRUE(value)) + return GIT_CRLF_TEXT; + + if (GIT_ATTR_FALSE(value)) + return GIT_CRLF_BINARY; + + if (GIT_ATTR_UNSPECIFIED(value)) + return GIT_CRLF_GUESS; + + if (strcmp(value, "input") == 0) + return GIT_CRLF_INPUT; + + if (strcmp(value, "auto") == 0) + return GIT_CRLF_AUTO; + + return GIT_CRLF_GUESS; +} + +static int check_eol(const char *value) +{ + if (GIT_ATTR_UNSPECIFIED(value)) + return GIT_EOL_UNSET; + + if (strcmp(value, "lf") == 0) + return GIT_EOL_LF; + + if (strcmp(value, "crlf") == 0) + return GIT_EOL_CRLF; + + return GIT_EOL_UNSET; +} + +static int crlf_input_action(struct crlf_attrs *ca) +{ + if (ca->crlf_action == GIT_CRLF_BINARY) + return GIT_CRLF_BINARY; + + if (ca->eol == GIT_EOL_LF) + return GIT_CRLF_INPUT; + + if (ca->eol == GIT_EOL_CRLF) + return GIT_CRLF_CRLF; + + return ca->crlf_action; +} + +static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path) +{ +#define NUM_CONV_ATTRS 3 + + static const char *attr_names[NUM_CONV_ATTRS] = { + "crlf", "eol", "text", + }; + + const char *attr_vals[NUM_CONV_ATTRS]; + int error; + + error = git_attr_get_many(repo, path, NUM_CONV_ATTRS, attr_names, attr_vals); + + if (error == GIT_ENOTFOUND) { + ca->crlf_action = GIT_CRLF_GUESS; + ca->eol = GIT_EOL_UNSET; + return 0; + } + + if (error == GIT_SUCCESS) { + ca->crlf_action = check_crlf(attr_vals[2]); /* text */ + if (ca->crlf_action == GIT_CRLF_GUESS) + ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */ + + ca->eol = check_eol(attr_vals[1]); /* eol */ + return 0; + } + + return error; +} + +static int drop_crlf(git_buf *dest, const git_buf *source) +{ + const char *scan = source->ptr, *next; + const char *scan_end = source->ptr + source->size; + + /* Main scan loop. Find the next carriage return and copy the + * whole chunk up to that point to the destination buffer. + */ + while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) { + /* copy input up to \r */ + if (next > scan) + git_buf_put(dest, scan, next - scan); + + /* Do not drop \r unless it is followed by \n */ + if (*(next + 1) != '\n') + git_buf_putc(dest, '\r'); + + scan = next + 1; + } + + /* If there was no \r, then tell the library to skip this filter */ + if (scan == source->ptr) + return -1; + + /* Copy remaining input into dest */ + git_buf_put(dest, scan, scan_end - scan); + + return git_buf_lasterror(dest); +} + +static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source) +{ + struct crlf_filter *filter = (struct crlf_filter *)self; + + assert(self && dest && source); + + /* Empty file? Nothing to do */ + if (source->size == 0) + return 0; + + /* Heuristics to see if we can skip the conversion. + * Straight from Core Git. + */ + if (filter->attrs.crlf_action == GIT_CRLF_AUTO || + filter->attrs.crlf_action == GIT_CRLF_GUESS) { + + git_text_stats stats; + git_text_gather_stats(&stats, source); + + /* + * We're currently not going to even try to convert stuff + * that has bare CR characters. Does anybody do that crazy + * stuff? + */ + if (stats.cr != stats.crlf) + return -1; + + /* + * And add some heuristics for binary vs text, of course... + */ + if (git_text_is_binary(&stats)) + return -1; + +#if 0 + if (crlf_action == CRLF_GUESS) { + /* + * If the file in the index has any CR in it, do not convert. + * This is the new safer autocrlf handling. + */ + if (has_cr_in_index(path)) + return 0; + } +#endif + + if (!stats.cr) + return -1; + } + + /* Actually drop the carriage returns */ + return drop_crlf(dest, source); +} + +int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +{ + struct crlf_attrs ca; + struct crlf_filter *filter; + int error; + + /* Load gitattributes for the path */ + if ((error = crlf_load_attributes(&ca, repo, path)) < 0) + return error; + + /* + * Use the core Git logic to see if we should perform CRLF for this file + * based on its attributes & the value of `core.auto_crlf` + */ + ca.crlf_action = crlf_input_action(&ca); + + if (ca.crlf_action == GIT_CRLF_BINARY) + return 0; + + if (ca.crlf_action == GIT_CRLF_GUESS) { + int auto_crlf; + + if ((error = git_repository__cvar( + &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < GIT_SUCCESS) + return error; + + if (auto_crlf == GIT_AUTO_CRLF_FALSE) + return 0; + } + + /* If we're good, we create a new filter object and push it + * into the filters array */ + filter = git__malloc(sizeof(struct crlf_filter)); + if (filter == NULL) + return GIT_ENOMEM; + + filter->f.apply = &crlf_apply_to_odb; + filter->f.do_free = NULL; + memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); + + return git_vector_insert(filters, filter); +} + diff --git a/src/diff.c b/src/diff.c new file mode 100644 index 000000000..db339d74e --- /dev/null +++ b/src/diff.c @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "git2/diff.h" +#include "diff.h" +#include "fileops.h" + +static void diff_delta__free(git_diff_delta *delta) +{ + if (!delta) + return; + + if (delta->new_file.flags & GIT_DIFF_FILE_FREE_PATH) { + git__free((char *)delta->new_file.path); + delta->new_file.path = NULL; + } + + if (delta->old_file.flags & GIT_DIFF_FILE_FREE_PATH) { + git__free((char *)delta->old_file.path); + delta->old_file.path = NULL; + } + + git__free(delta); +} + +static git_diff_delta *diff_delta__alloc( + git_diff_list *diff, + git_delta_t status, + const char *path) +{ + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + if (!delta) + return NULL; + + delta->old_file.path = git__strdup(path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + delta->old_file.flags |= GIT_DIFF_FILE_FREE_PATH; + delta->new_file.path = delta->old_file.path; + + if (diff->opts.flags & GIT_DIFF_REVERSE) { + switch (status) { + case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; + case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; + default: break; /* leave other status values alone */ + } + } + delta->status = status; + + return delta; +} + +static git_diff_delta *diff_delta__dup(const git_diff_delta *d) +{ + git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); + if (!delta) + return NULL; + + memcpy(delta, d, sizeof(git_diff_delta)); + + delta->old_file.path = git__strdup(d->old_file.path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + delta->old_file.flags |= GIT_DIFF_FILE_FREE_PATH; + + if (d->new_file.path != d->old_file.path) { + delta->new_file.path = git__strdup(d->new_file.path); + if (delta->new_file.path == NULL) { + git__free(delta->old_file.path); + git__free(delta); + return NULL; + } + delta->new_file.flags |= GIT_DIFF_FILE_FREE_PATH; + } else { + delta->new_file.path = delta->old_file.path; + delta->new_file.flags &= ~GIT_DIFF_FILE_FREE_PATH; + } + + return delta; +} + +static git_diff_delta *diff_delta__merge_like_cgit( + const git_diff_delta *a, const git_diff_delta *b) +{ + git_diff_delta *dup = diff_delta__dup(a); + if (!dup) + return NULL; + + if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0) + return dup; + + git_oid_cpy(&dup->new_file.oid, &b->new_file.oid); + + dup->new_file.mode = b->new_file.mode; + dup->new_file.size = b->new_file.size; + dup->new_file.flags = + (dup->new_file.flags & GIT_DIFF_FILE_FREE_PATH) | + (b->new_file.flags & ~GIT_DIFF_FILE_FREE_PATH); + + /* Emulate C git for merging two diffs (a la 'git diff <sha>'). + * + * When C git does a diff between the work dir and a tree, it actually + * diffs with the index but uses the workdir contents. This emulates + * those choices so we can emulate the type of diff. + */ + if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) { + if (dup->status == GIT_DELTA_DELETED) + /* preserve pending delete info */; + else if (b->status == GIT_DELTA_UNTRACKED || + b->status == GIT_DELTA_IGNORED) + dup->status = b->status; + else + dup->status = GIT_DELTA_UNMODIFIED; + } + else if (dup->status == GIT_DELTA_UNMODIFIED || + b->status == GIT_DELTA_DELETED) + dup->status = b->status; + + return dup; +} + +static int diff_delta__from_one( + git_diff_list *diff, + git_delta_t status, + const git_index_entry *entry) +{ + int error; + git_diff_delta *delta = diff_delta__alloc(diff, status, entry->path); + if (!delta) + return git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); + + /* This fn is just for single-sided diffs */ + assert(status != GIT_DELTA_MODIFIED); + + if (delta->status == GIT_DELTA_DELETED) { + delta->old_file.mode = entry->mode; + delta->old_file.size = entry->file_size; + git_oid_cpy(&delta->old_file.oid, &entry->oid); + } else /* ADDED, IGNORED, UNTRACKED */ { + delta->new_file.mode = entry->mode; + delta->new_file.size = entry->file_size; + git_oid_cpy(&delta->new_file.oid, &entry->oid); + } + + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + if ((error = git_vector_insert(&diff->deltas, delta)) < GIT_SUCCESS) + diff_delta__free(delta); + + return error; +} + +static int diff_delta__from_two( + git_diff_list *diff, + git_delta_t status, + const git_index_entry *old_entry, + const git_index_entry *new_entry, + git_oid *new_oid) +{ + int error; + git_diff_delta *delta; + + if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { + const git_index_entry *temp = old_entry; + old_entry = new_entry; + new_entry = temp; + } + + delta = diff_delta__alloc(diff, status, old_entry->path); + if (!delta) + return git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); + + delta->old_file.mode = old_entry->mode; + git_oid_cpy(&delta->old_file.oid, &old_entry->oid); + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + + delta->new_file.mode = new_entry->mode; + git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); + if (new_oid || !git_oid_iszero(&new_entry->oid)) + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + if ((error = git_vector_insert(&diff->deltas, delta)) < GIT_SUCCESS) + diff_delta__free(delta); + + return error; +} + +#define DIFF_OLD_PREFIX_DEFAULT "a/" +#define DIFF_NEW_PREFIX_DEFAULT "b/" + +static char *diff_strdup_prefix(const char *prefix) +{ + size_t len = strlen(prefix); + char *str = git__malloc(len + 2); + if (str != NULL) { + memcpy(str, prefix, len + 1); + /* append '/' at end if needed */ + if (len > 0 && str[len - 1] != '/') { + str[len] = '/'; + str[len + 1] = '\0'; + } + } + return str; +} + +static int diff_delta__cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(da->old_file.path, db->old_file.path); + return val ? val : ((int)da->status - (int)db->status); +} + +static git_diff_list *git_diff_list_alloc( + git_repository *repo, const git_diff_options *opts) +{ + git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); + if (diff == NULL) + return NULL; + + diff->repo = repo; + + if (opts == NULL) + return diff; + + memcpy(&diff->opts, opts, sizeof(git_diff_options)); + + diff->opts.old_prefix = diff_strdup_prefix( + opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); + diff->opts.new_prefix = diff_strdup_prefix( + opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT); + + if (!diff->opts.old_prefix || !diff->opts.new_prefix) { + git__free(diff); + return NULL; + } + + if (diff->opts.flags & GIT_DIFF_REVERSE) { + char *swap = diff->opts.old_prefix; + diff->opts.old_prefix = diff->opts.new_prefix; + diff->opts.new_prefix = swap; + } + + if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < GIT_SUCCESS) { + git__free(diff->opts.old_prefix); + git__free(diff->opts.new_prefix); + git__free(diff); + return NULL; + } + + /* do something safe with the pathspec strarray */ + + return diff; +} + +void git_diff_list_free(git_diff_list *diff) +{ + git_diff_delta *delta; + unsigned int i; + + if (!diff) + return; + + git_vector_foreach(&diff->deltas, i, delta) { + diff_delta__free(delta); + diff->deltas.contents[i] = NULL; + } + git_vector_free(&diff->deltas); + git__free(diff->opts.old_prefix); + git__free(diff->opts.new_prefix); + git__free(diff); +} + +static int oid_for_workdir_item( + git_repository *repo, + const git_index_entry *item, + git_oid *oid) +{ + int error = GIT_SUCCESS; + git_buf full_path = GIT_BUF_INIT; + + error = git_buf_joinpath( + &full_path, git_repository_workdir(repo), item->path); + if (error != GIT_SUCCESS) + return error; + + /* otherwise calculate OID for file */ + if (S_ISLNK(item->mode)) + error = git_odb__hashlink(oid, full_path.ptr); + else if (!git__is_sizet(item->file_size)) + error = git__throw(GIT_ERROR, "File size overflow for 32-bit systems"); + else { + int fd; + + if ((fd = p_open(full_path.ptr, O_RDONLY)) < 0) + error = git__throw( + GIT_EOSERR, "Could not open '%s'", item->path); + else { + error = git_odb__hashfd( + oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); + p_close(fd); + } + } + + git_buf_free(&full_path); + + return error; +} + +static int maybe_modified( + git_iterator *old_iter, + const git_index_entry *oitem, + git_iterator *new_iter, + const git_index_entry *nitem, + git_diff_list *diff) +{ + int error = GIT_SUCCESS; + git_oid noid, *use_noid = NULL; + + GIT_UNUSED(old_iter); + + /* support "assume unchanged" & "skip worktree" bits */ + if ((oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) != 0 || + (oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) + return GIT_SUCCESS; + + if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) { + error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem); + if (error == GIT_SUCCESS) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); + return error; + } + + if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && + oitem->mode == nitem->mode) + return GIT_SUCCESS; + + if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) { + /* if they files look exactly alike, then we'll assume the same */ + if (oitem->file_size == nitem->file_size && + oitem->ctime.seconds == nitem->ctime.seconds && + oitem->mtime.seconds == nitem->mtime.seconds && + oitem->dev == nitem->dev && + oitem->ino == nitem->ino && + oitem->uid == nitem->uid && + oitem->gid == nitem->gid) + return GIT_SUCCESS; + + /* TODO: check git attributes so we will not have to read the file + * in if it is marked binary. + */ + error = oid_for_workdir_item(diff->repo, nitem, &noid); + if (error != GIT_SUCCESS) + return error; + + if (git_oid_cmp(&oitem->oid, &noid) == 0 && + oitem->mode == nitem->mode) + return GIT_SUCCESS; + + /* store calculated oid so we don't have to recalc later */ + use_noid = &noid; + } + + return diff_delta__from_two( + diff, GIT_DELTA_MODIFIED, oitem, nitem, use_noid); +} + +static int diff_from_iterators( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_iterator *old_iter, + git_iterator *new_iter, + git_diff_list **diff_ptr) +{ + int error; + const git_index_entry *oitem, *nitem; + char *ignore_prefix = NULL; + git_diff_list *diff = git_diff_list_alloc(repo, opts); + if (!diff) { + error = GIT_ENOMEM; + goto cleanup; + } + + diff->old_src = old_iter->type; + diff->new_src = new_iter->type; + + if ((error = git_iterator_current(old_iter, &oitem)) < GIT_SUCCESS || + (error = git_iterator_current(new_iter, &nitem)) < GIT_SUCCESS) + goto cleanup; + + /* run iterators building diffs */ + while (!error && (oitem || nitem)) { + + /* create DELETED records for old items not matched in new */ + if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) { + error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem); + if (error == GIT_SUCCESS) + error = git_iterator_advance(old_iter, &oitem); + continue; + } + + /* create ADDED, TRACKED, or IGNORED records for new items not + * matched in old (and/or descend into directories as needed) + */ + if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) { + int is_ignored; + git_delta_t delta_type = GIT_DELTA_ADDED; + + /* contained in ignored parent directory, so this can be skipped. */ + if (ignore_prefix != NULL && + git__prefixcmp(nitem->path, ignore_prefix) == 0) + { + error = git_iterator_advance(new_iter, &nitem); + continue; + } + + is_ignored = git_iterator_current_is_ignored(new_iter); + + if (S_ISDIR(nitem->mode)) { + if (git__prefixcmp(oitem->path, nitem->path) == 0) { + if (is_ignored) + ignore_prefix = nitem->path; + error = git_iterator_advance_into_directory(new_iter, &nitem); + continue; + } + delta_type = GIT_DELTA_UNTRACKED; + } + else if (is_ignored) + delta_type = GIT_DELTA_IGNORED; + else if (new_iter->type == GIT_ITERATOR_WORKDIR) + delta_type = GIT_DELTA_UNTRACKED; + + error = diff_delta__from_one(diff, delta_type, nitem); + if (error == GIT_SUCCESS) + error = git_iterator_advance(new_iter, &nitem); + continue; + } + + /* otherwise item paths match, so create MODIFIED record + * (or ADDED and DELETED pair if type changed) + */ + assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0); + + error = maybe_modified(old_iter, oitem, new_iter, nitem, diff); + if (error == GIT_SUCCESS) + error = git_iterator_advance(old_iter, &oitem); + if (error == GIT_SUCCESS) + error = git_iterator_advance(new_iter, &nitem); + } + +cleanup: + git_iterator_free(old_iter); + git_iterator_free(new_iter); + + if (error != GIT_SUCCESS) { + git_diff_list_free(diff); + diff = NULL; + } + + *diff_ptr = diff; + + return error; +} + + +int git_diff_tree_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_tree *new_tree, + git_diff_list **diff) +{ + int error; + git_iterator *a = NULL, *b = NULL; + + assert(repo && old_tree && new_tree && diff); + + if ((error = git_iterator_for_tree(repo, old_tree, &a)) < GIT_SUCCESS || + (error = git_iterator_for_tree(repo, new_tree, &b)) < GIT_SUCCESS) + return error; + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_index_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old_tree, + git_diff_list **diff) +{ + int error; + git_iterator *a = NULL, *b = NULL; + + assert(repo && old_tree && diff); + + if ((error = git_iterator_for_tree(repo, old_tree, &a)) < GIT_SUCCESS || + (error = git_iterator_for_index(repo, &b)) < GIT_SUCCESS) + return error; + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_workdir_to_index( + git_repository *repo, + const git_diff_options *opts, + git_diff_list **diff) +{ + int error; + git_iterator *a = NULL, *b = NULL; + + assert(repo && diff); + + if ((error = git_iterator_for_index(repo, &a)) < GIT_SUCCESS || + (error = git_iterator_for_workdir(repo, &b)) < GIT_SUCCESS) + return error; + + return diff_from_iterators(repo, opts, a, b, diff); +} + + +int git_diff_workdir_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old_tree, + git_diff_list **diff) +{ + int error; + git_iterator *a = NULL, *b = NULL; + + assert(repo && old_tree && diff); + + if ((error = git_iterator_for_tree(repo, old_tree, &a)) < GIT_SUCCESS || + (error = git_iterator_for_workdir(repo, &b)) < GIT_SUCCESS) + return error; + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_merge( + git_diff_list *onto, + const git_diff_list *from) +{ + int error; + unsigned int i = 0, j = 0; + git_vector onto_new; + git_diff_delta *delta; + + error = git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp); + if (error < GIT_SUCCESS) + return error; + + while (i < onto->deltas.length || j < from->deltas.length) { + git_diff_delta *o = git_vector_get(&onto->deltas, i); + const git_diff_delta *f = git_vector_get_const(&from->deltas, j); + const char *opath = + !o ? NULL : o->old_file.path ? o->old_file.path : o->new_file.path; + const char *fpath = + !f ? NULL : f->old_file.path ? f->old_file.path : f->new_file.path; + + if (opath && (!fpath || strcmp(opath, fpath) < 0)) { + delta = diff_delta__dup(o); + i++; + } else if (fpath && (!opath || strcmp(opath, fpath) > 0)) { + delta = diff_delta__dup(f); + j++; + } else { + delta = diff_delta__merge_like_cgit(o, f); + i++; + j++; + } + + if (!delta) + error = GIT_ENOMEM; + else + error = git_vector_insert(&onto_new, delta); + + if (error != GIT_SUCCESS) + break; + } + + if (error == GIT_SUCCESS) { + git_vector_swap(&onto->deltas, &onto_new); + onto->new_src = from->new_src; + } + + git_vector_foreach(&onto_new, i, delta) + diff_delta__free(delta); + git_vector_free(&onto_new); + + return error; +} diff --git a/src/diff.h b/src/diff.h new file mode 100644 index 000000000..7d69199ea --- /dev/null +++ b/src/diff.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_h__ +#define INCLUDE_diff_h__ + +#include <stdio.h> +#include "vector.h" +#include "buffer.h" +#include "iterator.h" +#include "repository.h" + +struct git_diff_list { + git_repository *repo; + git_diff_options opts; + git_vector deltas; /* vector of git_diff_file_delta */ + git_iterator_type_t old_src; + git_iterator_type_t new_src; +}; + +#endif + diff --git a/src/diff_output.c b/src/diff_output.c new file mode 100644 index 000000000..2c6bacc81 --- /dev/null +++ b/src/diff_output.c @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "git2/diff.h" +#include "git2/attr.h" +#include "git2/blob.h" +#include "xdiff/xdiff.h" +#include <ctype.h> +#include "diff.h" +#include "map.h" +#include "fileops.h" +#include "filter.h" + +typedef struct { + git_diff_list *diff; + void *cb_data; + git_diff_hunk_fn hunk_cb; + git_diff_line_fn line_cb; + unsigned int index; + git_diff_delta *delta; +} diff_output_info; + +static int read_next_int(const char **str, int *value) +{ + const char *scan = *str; + int v = 0, digits = 0; + /* find next digit */ + for (scan = *str; *scan && !isdigit(*scan); scan++); + /* parse next number */ + for (; isdigit(*scan); scan++, digits++) + v = (v * 10) + (*scan - '0'); + *str = scan; + *value = v; + return (digits > 0) ? GIT_SUCCESS : GIT_ENOTFOUND; +} + +static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) +{ + int err = GIT_SUCCESS; + diff_output_info *info = priv; + + if (len == 1 && info->hunk_cb) { + git_diff_range range = { -1, 0, -1, 0 }; + + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (bufs[0].ptr[0] == '@') { + const char *scan = bufs[0].ptr; + if (!(err = read_next_int(&scan, &range.old_start)) && *scan == ',') + err = read_next_int(&scan, &range.old_lines); + if (!err && + !(err = read_next_int(&scan, &range.new_start)) && *scan == ',') + err = read_next_int(&scan, &range.new_lines); + if (!err && range.old_start >= 0 && range.new_start >= 0) + err = info->hunk_cb( + info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size); + } + } + else if ((len == 2 || len == 3) && info->line_cb) { + int origin; + + /* expect " "/"-"/"+", then data, then maybe newline */ + origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + err = info->line_cb( + info->cb_data, info->delta, origin, bufs[1].ptr, bufs[1].size); + + /* deal with adding and removing newline at EOF */ + if (err == GIT_SUCCESS && len == 3) { + if (origin == GIT_DIFF_LINE_ADDITION) + origin = GIT_DIFF_LINE_ADD_EOFNL; + else + origin = GIT_DIFF_LINE_DEL_EOFNL; + + err = info->line_cb( + info->cb_data, info->delta, origin, bufs[2].ptr, bufs[2].size); + } + } + + return err; +} + +#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) + +static int set_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) +{ + const char *value; + int error = git_attr_get(repo, file->path, "diff", &value); + if (error != GIT_SUCCESS) + return error; + if (GIT_ATTR_FALSE(value)) + file->flags |= GIT_DIFF_FILE_BINARY; + else if (GIT_ATTR_TRUE(value)) + file->flags |= GIT_DIFF_FILE_NOT_BINARY; + /* otherwise leave file->flags alone */ + return error; +} + +static void set_delta_is_binary(git_diff_delta *delta) +{ + if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 || + (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) + delta->binary = 1; + else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 || + (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0) + delta->binary = 0; + /* otherwise leave delta->binary value untouched */ +} + +static int file_is_binary_by_attr( + git_diff_list *diff, + git_diff_delta *delta) +{ + int error, mirror_new; + + delta->binary = -1; + + /* make sure files are conceivably mmap-able */ + if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size || + (git_off_t)((size_t)delta->new_file.size) != delta->new_file.size) + { + delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + delta->new_file.flags |= GIT_DIFF_FILE_BINARY; + delta->binary = 1; + return GIT_SUCCESS; + } + + /* check if user is forcing us to text diff these files */ + if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) { + delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + delta->binary = 0; + return GIT_SUCCESS; + } + + /* check diff attribute +, -, or 0 */ + error = set_file_is_binary_by_attr(diff->repo, &delta->old_file); + if (error != GIT_SUCCESS) + return error; + + mirror_new = (delta->new_file.path == delta->old_file.path || + strcmp(delta->new_file.path, delta->old_file.path) == 0); + if (mirror_new) + delta->new_file.flags &= (delta->old_file.flags & BINARY_DIFF_FLAGS); + else + error = set_file_is_binary_by_attr(diff->repo, &delta->new_file); + + set_delta_is_binary(delta); + + return error; +} + +static int file_is_binary_by_content( + git_diff_list *diff, + git_diff_delta *delta, + git_map *old_data, + git_map *new_data) +{ + git_buf search; + git_text_stats stats; + + GIT_UNUSED(diff); + + if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = old_data->data; + search.size = min(old_data->len, 4000); + + git_text_gather_stats(&stats, &search); + + if (git_text_is_binary(&stats)) + delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + else + delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + } + + if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = new_data->data; + search.size = min(new_data->len, 4000); + + git_text_gather_stats(&stats, &search); + + if (git_text_is_binary(&stats)) + delta->new_file.flags |= GIT_DIFF_FILE_BINARY; + else + delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + } + + set_delta_is_binary(delta); + + /* TODO: if value != NULL, implement diff drivers */ + + return GIT_SUCCESS; +} + +static void setup_xdiff_options( + git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param) +{ + memset(cfg, 0, sizeof(xdemitconf_t)); + memset(param, 0, sizeof(xpparam_t)); + + cfg->ctxlen = + (!opts || !opts->context_lines) ? 3 : opts->context_lines; + cfg->interhunkctxlen = + (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines; + + if (!opts) + return; + + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE) + param->flags |= XDF_WHITESPACE_FLAGS; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) + param->flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) + param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL; +} + +static int get_blob_content( + git_repository *repo, + const git_oid *oid, + git_map *map, + git_blob **blob) +{ + int error; + + if (git_oid_iszero(oid)) + return GIT_SUCCESS; + + if ((error = git_blob_lookup(blob, repo, oid)) == GIT_SUCCESS) { + map->data = (void *)git_blob_rawcontent(*blob); + map->len = git_blob_rawsize(*blob); + } + + return error; +} + +static int get_workdir_content( + git_repository *repo, + git_diff_file *file, + git_map *map) +{ + git_buf full_path = GIT_BUF_INIT; + int error = git_buf_joinpath( + &full_path, git_repository_workdir(repo), file->path); + if (error != GIT_SUCCESS) + return error; + + if (S_ISLNK(file->mode)) { + file->flags |= GIT_DIFF_FILE_FREE_DATA; + file->flags |= GIT_DIFF_FILE_BINARY; + + map->data = git__malloc((size_t)file->size + 1); + if (map->data == NULL) + error = GIT_ENOMEM; + else { + ssize_t read_len = + p_readlink(full_path.ptr, map->data, (size_t)file->size + 1); + if (read_len != (ssize_t)file->size) + error = git__throw( + GIT_EOSERR, "Failed to read symlink %s", file->path); + else + map->len = read_len; + + } + } + else { + error = git_futils_mmap_ro_file(map, full_path.ptr); + file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + } + git_buf_free(&full_path); + return error; +} + +static void release_content(git_diff_file *file, git_map *map, git_blob *blob) +{ + if (blob != NULL) + git_blob_free(blob); + + if (file->flags & GIT_DIFF_FILE_FREE_DATA) { + git__free(map->data); + map->data = NULL; + file->flags &= ~GIT_DIFF_FILE_FREE_DATA; + } + else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) { + git_futils_mmap_free(map); + map->data = NULL; + file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA; + } +} + +int git_diff_foreach( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_line_fn line_cb) +{ + int error = GIT_SUCCESS; + diff_output_info info; + git_diff_delta *delta; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; + + info.diff = diff; + info.cb_data = data; + info.hunk_cb = hunk_cb; + info.line_cb = line_cb; + + setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &info; + + git_vector_foreach(&diff->deltas, info.index, delta) { + git_blob *old_blob = NULL, *new_blob = NULL; + git_map old_data, new_data; + mmfile_t old_xdiff_data, new_xdiff_data; + + if (delta->status == GIT_DELTA_UNMODIFIED) + continue; + + if (delta->status == GIT_DELTA_IGNORED && + (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + continue; + + if (delta->status == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + continue; + + error = file_is_binary_by_attr(diff, delta); + if (error < GIT_SUCCESS) + goto cleanup; + + old_data.data = ""; + old_data.len = 0; + new_data.data = ""; + new_data.len = 0; + + /* TODO: Partial blob reading to defer loading whole blob. + * I.e. I want a blob with just the first 4kb loaded, then + * later on I will read the rest of the blob if needed. + */ + + /* map files */ + if (delta->binary != 1 && + (hunk_cb || line_cb) && + (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (diff->old_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content(diff->repo, &delta->old_file, &old_data); + else + error = get_blob_content( + diff->repo, &delta->old_file.oid, &old_data, &old_blob); + if (error != GIT_SUCCESS) + goto cleanup; + } + + if (delta->binary != 1 && + (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) && + (delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (diff->new_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content(diff->repo, &delta->new_file, &new_data); + else + error = get_blob_content( + diff->repo, &delta->new_file.oid, &new_data, &new_blob); + if (error != GIT_SUCCESS) + goto cleanup; + + if ((delta->new_file.flags | GIT_DIFF_FILE_VALID_OID) == 0) { + error = git_odb_hash( + &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB); + if (error != GIT_SUCCESS) + goto cleanup; + + /* since we did not have the definitive oid, we may have + * incorrect status and need to skip this item. + */ + if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) { + delta->status = GIT_DELTA_UNMODIFIED; + goto cleanup; + } + } + } + + /* if we have not already decided whether file is binary, + * check the first 4K for nul bytes to decide... + */ + if (delta->binary == -1) { + error = file_is_binary_by_content( + diff, delta, &old_data, &new_data); + if (error < GIT_SUCCESS) + goto cleanup; + } + + /* TODO: if ignore_whitespace is set, then we *must* do text + * diffs to tell if a file has really been changed. + */ + + if (file_cb != NULL) { + error = file_cb(data, delta, (float)info.index / diff->deltas.length); + if (error != GIT_SUCCESS) + goto cleanup; + } + + /* don't do hunk and line diffs if file is binary */ + if (delta->binary == 1) + goto cleanup; + + /* nothing to do if we did not get data */ + if (!old_data.len && !new_data.len) + goto cleanup; + + assert(hunk_cb || line_cb); + + info.delta = delta; + old_xdiff_data.ptr = old_data.data; + old_xdiff_data.size = old_data.len; + new_xdiff_data.ptr = new_data.data; + new_xdiff_data.size = new_data.len; + + xdl_diff(&old_xdiff_data, &new_xdiff_data, + &xdiff_params, &xdiff_config, &xdiff_callback); + +cleanup: + release_content(&delta->old_file, &old_data, old_blob); + release_content(&delta->new_file, &new_data, new_blob); + + if (error != GIT_SUCCESS) + break; + } + + return error; +} + + +typedef struct { + git_diff_list *diff; + git_diff_output_fn print_cb; + void *cb_data; + git_buf *buf; +} diff_print_info; + +static char pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (mode & 0100) + /* in git, modes are very regular, so we must have 0100755 mode */ + return '*'; + else + return ' '; +} + +static int print_compact(void *data, git_diff_delta *delta, float progress) +{ + diff_print_info *pi = data; + char code, old_suffix, new_suffix; + + GIT_UNUSED(progress); + + switch (delta->status) { + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + default: code = 0; + } + + if (!code) + return GIT_SUCCESS; + + old_suffix = pick_suffix(delta->old_file.mode); + new_suffix = pick_suffix(delta->new_file.mode); + + git_buf_clear(pi->buf); + + if (delta->old_file.path != delta->new_file.path && + strcmp(delta->old_file.path,delta->new_file.path) != 0) + git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); + else if (delta->old_file.mode != delta->new_file.mode && + delta->old_file.mode != 0 && delta->new_file.mode != 0) + git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, + delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); + else if (old_suffix != ' ') + git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + else + git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); + + if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) + return git_buf_lasterror(pi->buf); + + return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr); +} + +int git_diff_print_compact( + git_diff_list *diff, + void *cb_data, + git_diff_output_fn print_cb) +{ + int error; + git_buf buf = GIT_BUF_INIT; + diff_print_info pi; + + pi.diff = diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &buf; + + error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL); + + git_buf_free(&buf); + + return error; +} + + +static int print_oid_range(diff_print_info *pi, git_diff_delta *delta) +{ + char start_oid[8], end_oid[8]; + + /* TODO: Determine a good actual OID range to print */ + git_oid_to_string(start_oid, sizeof(start_oid), &delta->old_file.oid); + git_oid_to_string(end_oid, sizeof(end_oid), &delta->new_file.oid); + + /* TODO: Match git diff more closely */ + if (delta->old_file.mode == delta->new_file.mode) { + git_buf_printf(pi->buf, "index %s..%s %o\n", + start_oid, end_oid, delta->old_file.mode); + } else { + if (delta->old_file.mode == 0) { + git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); + } else if (delta->new_file.mode == 0) { + git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); + } else { + git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); + git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); + } + git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); + } + + return git_buf_lasterror(pi->buf); +} + +static int print_patch_file(void *data, git_diff_delta *delta, float progress) +{ + int error; + diff_print_info *pi = data; + const char *oldpfx = pi->diff->opts.old_prefix; + const char *oldpath = delta->old_file.path; + const char *newpfx = pi->diff->opts.new_prefix; + const char *newpath = delta->new_file.path; + + GIT_UNUSED(progress); + + git_buf_clear(pi->buf); + git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path); + if ((error = print_oid_range(pi, delta)) < GIT_SUCCESS) + return error; + + if (git_oid_iszero(&delta->old_file.oid)) { + oldpfx = ""; + oldpath = "/dev/null"; + } + if (git_oid_iszero(&delta->new_file.oid)) { + oldpfx = ""; + oldpath = "/dev/null"; + } + + if (delta->binary != 1) { + git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath); + git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); + } + + if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) + return git_buf_lasterror(pi->buf); + + error = pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr); + if (error != GIT_SUCCESS || delta->binary != 1) + return error; + + git_buf_clear(pi->buf); + git_buf_printf( + pi->buf, "Binary files %s%s and %s%s differ\n", + oldpfx, oldpath, newpfx, newpath); + if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) + return git_buf_lasterror(pi->buf); + + return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_BINARY, pi->buf->ptr); +} + +static int print_patch_hunk( + void *data, + git_diff_delta *d, + git_diff_range *r, + const char *header, + size_t header_len) +{ + diff_print_info *pi = data; + + GIT_UNUSED(d); + GIT_UNUSED(r); + + git_buf_clear(pi->buf); + + if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) == GIT_SUCCESS) + return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_HUNK_HDR, pi->buf->ptr); + else + return git_buf_lasterror(pi->buf); +} + +static int print_patch_line( + void *data, + git_diff_delta *delta, + char line_origin, /* GIT_DIFF_LINE value from above */ + const char *content, + size_t content_len) +{ + diff_print_info *pi = data; + + GIT_UNUSED(delta); + + git_buf_clear(pi->buf); + + if (line_origin == GIT_DIFF_LINE_ADDITION || + line_origin == GIT_DIFF_LINE_DELETION || + line_origin == GIT_DIFF_LINE_CONTEXT) + git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content); + else if (content_len > 0) + git_buf_printf(pi->buf, "%.*s", (int)content_len, content); + + if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) + return git_buf_lasterror(pi->buf); + + return pi->print_cb(pi->cb_data, line_origin, pi->buf->ptr); +} + +int git_diff_print_patch( + git_diff_list *diff, + void *cb_data, + git_diff_output_fn print_cb) +{ + int error; + git_buf buf = GIT_BUF_INIT; + diff_print_info pi; + + pi.diff = diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &buf; + + error = git_diff_foreach( + diff, &pi, print_patch_file, print_patch_hunk, print_patch_line); + + git_buf_free(&buf); + + return error; +} + + +int git_diff_blobs( + git_repository *repo, + git_blob *old_blob, + git_blob *new_blob, + git_diff_options *options, + void *cb_data, + git_diff_hunk_fn hunk_cb, + git_diff_line_fn line_cb) +{ + diff_output_info info; + git_diff_delta delta; + mmfile_t old_data, new_data; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; + + assert(repo); + + if (options && (options->flags & GIT_DIFF_REVERSE)) { + git_blob *swap = old_blob; + old_blob = new_blob; + new_blob = swap; + } + + if (old_blob) { + old_data.ptr = (char *)git_blob_rawcontent(old_blob); + old_data.size = git_blob_rawsize(old_blob); + } else { + old_data.ptr = ""; + old_data.size = 0; + } + + if (new_blob) { + new_data.ptr = (char *)git_blob_rawcontent(new_blob); + new_data.size = git_blob_rawsize(new_blob); + } else { + new_data.ptr = ""; + new_data.size = 0; + } + + /* populate a "fake" delta record */ + delta.status = old_data.ptr ? + (new_data.ptr ? GIT_DELTA_MODIFIED : GIT_DELTA_DELETED) : + (new_data.ptr ? GIT_DELTA_ADDED : GIT_DELTA_UNTRACKED); + delta.old_file.mode = 0100644; /* can't know the truth from a blob alone */ + delta.new_file.mode = 0100644; + git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old_blob)); + git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new_blob)); + delta.old_file.path = NULL; + delta.new_file.path = NULL; + delta.similarity = 0; + + info.diff = NULL; + info.delta = δ + info.cb_data = cb_data; + info.hunk_cb = hunk_cb; + info.line_cb = line_cb; + + setup_xdiff_options(options, &xdiff_config, &xdiff_params); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &info; + + xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback); + + return GIT_SUCCESS; +} diff --git a/src/fileops.c b/src/fileops.c index 3241c68b1..856823afb 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -79,10 +79,6 @@ git_off_t git_futils_filesize(git_file fd) return sb.st_size; } -#define GIT_MODE_PERMS_MASK 0777 -#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) -#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) - mode_t git_futils_canonical_mode(mode_t raw_mode) { if (S_ISREG(raw_mode)) @@ -97,87 +93,77 @@ mode_t git_futils_canonical_mode(mode_t raw_mode) return 0; } -int git_futils_readbuffer_updated(git_fbuffer *obj, const char *path, time_t *mtime, int *updated) +int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated) { git_file fd; size_t len; struct stat st; - unsigned char *buff; - assert(obj && path && *path); + assert(buf && path && *path); if (updated != NULL) *updated = 0; - if (p_stat(path, &st) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to stat file %s", path); + if ((fd = p_open(path, O_RDONLY)) < 0) { + return git__throw(GIT_ENOTFOUND, "Failed to read file '%s': %s", path, strerror(errno)); + } - if (S_ISDIR(st.st_mode)) - return git__throw(GIT_ERROR, "Can't read a dir into a buffer"); + if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { + close(fd); + return git__throw(GIT_EOSERR, "Failed to stat file '%s'", path); + } /* * If we were given a time, we only want to read the file if it * has been modified. */ - if (mtime != NULL && *mtime >= st.st_mtime) - return GIT_SUCCESS; + if (mtime != NULL && *mtime >= st.st_mtime) { + close(fd); + return 0; + } if (mtime != NULL) *mtime = st.st_mtime; - if (!git__is_sizet(st.st_size+1)) - return git__throw(GIT_ERROR, "Failed to read file `%s`. An error occured while calculating its size", path); len = (size_t) st.st_size; - if ((fd = p_open(path, O_RDONLY)) < 0) - return git__throw(GIT_EOSERR, "Failed to open %s for reading", path); + git_buf_clear(buf); - if ((buff = git__malloc(len + 1)) == NULL) { - p_close(fd); + if (git_buf_grow(buf, len + 1) < 0) { + close(fd); return GIT_ENOMEM; } - if (p_read(fd, buff, len) < 0) { - p_close(fd); - git__free(buff); - return git__throw(GIT_ERROR, "Failed to read file `%s`", path); + buf->ptr[len] = '\0'; + + while (len > 0) { + ssize_t read_size = p_read(fd, buf->ptr, len); + + if (read_size < 0) { + close(fd); + return git__throw(GIT_EOSERR, "Failed to read from FD"); + } + + len -= read_size; + buf->size += read_size; } - buff[len] = '\0'; p_close(fd); if (mtime != NULL) *mtime = st.st_mtime; + if (updated != NULL) *updated = 1; - obj->data = buff; - obj->len = len; - - return GIT_SUCCESS; -} - -int git_futils_readbuffer(git_fbuffer *obj, const char *path) -{ - return git_futils_readbuffer_updated(obj, path, NULL, NULL); -} - -void git_futils_fbuffer_rtrim(git_fbuffer *obj) -{ - unsigned char *buff = obj->data; - while (obj->len > 0 && isspace(buff[obj->len - 1])) - obj->len--; - buff[obj->len] = '\0'; + return 0; } -void git_futils_freebuffer(git_fbuffer *obj) +int git_futils_readbuffer(git_buf *buf, const char *path) { - assert(obj); - git__free(obj->data); - obj->data = NULL; + return git_futils_readbuffer_updated(buf, path, NULL, NULL); } - int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) { if (git_futils_mkpath2file(to, dirmode) < GIT_SUCCESS) @@ -191,6 +177,18 @@ int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); } +int git_futils_mmap_ro_file(git_map *out, const char *path) +{ + git_file fd = p_open(path, O_RDONLY /* | O_NOATIME */); + git_off_t len = git_futils_filesize(fd); + int result; + if (!git__is_sizet(len)) + return git__throw(GIT_ERROR, "File `%s` too large to mmap", path); + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); + p_close(fd); + return result; +} + void git_futils_mmap_free(git_map *out) { p_munmap(out); diff --git a/src/fileops.h b/src/fileops.h index 4c114026b..ab57b6f38 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -17,17 +17,8 @@ * * Read whole files into an in-memory buffer for processing */ -#define GIT_FBUFFER_INIT {NULL, 0} - -typedef struct { /* file io buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ -} git_fbuffer; - -extern int git_futils_readbuffer(git_fbuffer *obj, const char *path); -extern int git_futils_readbuffer_updated(git_fbuffer *obj, const char *path, time_t *mtime, int *updated); -extern void git_futils_freebuffer(git_fbuffer *obj); -extern void git_futils_fbuffer_rtrim(git_fbuffer *obj); +extern int git_futils_readbuffer(git_buf *obj, const char *path); +extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated); /** * File utils @@ -90,6 +81,10 @@ extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t */ extern git_off_t git_futils_filesize(git_file fd); +#define GIT_MODE_PERMS_MASK 0777 +#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) +#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) + /** * Convert a mode_t from the OS to a legal git mode_t value. */ @@ -118,6 +113,19 @@ extern int git_futils_mmap_ro( size_t len); /** + * Read-only map an entire file. + * + * @param out buffer to populate with the mapping information. + * @param path path to file to be opened. + * @return + * - GIT_SUCCESS on success; + * - GIT_EOSERR on an unspecified OS related error. + */ +extern int git_futils_mmap_ro_file( + git_map *out, + const char *path); + +/** * Release the memory associated with a previous memory mapping. * @param map the mapping description previously configured. */ diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 000000000..f0ee1ad39 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "fileops.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" +#include "git2/config.h" + +/* Tweaked from Core Git. I wonder what we could use this for... */ +void git_text_gather_stats(git_text_stats *stats, const git_buf *text) +{ + size_t i; + + memset(stats, 0, sizeof(*stats)); + + for (i = 0; i < text->size; i++) { + unsigned char c = text->ptr[i]; + + if (c == '\r') { + stats->cr++; + + if (i + 1 < text->size && text->ptr[i + 1] == '\n') + stats->crlf++; + } + + else if (c == '\n') + stats->lf++; + + else if (c == 0x85) + /* Unicode CR+LF */ + stats->crlf++; + + else if (c == 127) + /* DEL */ + stats->nonprintable++; + + else if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) { + switch (c) { + /* BS, HT, ESC and FF */ + case '\b': case '\t': case '\033': case '\014': + stats->printable++; + break; + case 0: + stats->nul++; + /* fall through */ + default: + stats->nonprintable++; + } + } + + else + stats->printable++; + } + + /* If file ends with EOF then don't count this EOF as non-printable. */ + if (text->size >= 1 && text->ptr[text->size - 1] == '\032') + stats->nonprintable--; +} + +/* + * Fresh from Core Git + */ +int git_text_is_binary(git_text_stats *stats) +{ + if (stats->nul) + return 1; + + if ((stats->printable >> 7) < stats->nonprintable) + return 1; + /* + * Other heuristics? Average line length might be relevant, + * as might LF vs CR vs CRLF counts.. + * + * NOTE! It might be normal to have a low ratio of CRLF to LF + * (somebody starts with a LF-only file and edits it with an editor + * that adds CRLF only to lines that are added..). But do we + * want to support CR-only? Probably not. + */ + return 0; +} + +int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode) +{ + int error; + + if (mode == GIT_FILTER_TO_ODB) { + /* Load the CRLF cleanup filter when writing to the ODB */ + error = git_filter_add__crlf_to_odb(filters, repo, path); + if (error < GIT_SUCCESS) + return error; + } else { + return git__throw(GIT_ENOTIMPLEMENTED, + "Worktree filters are not implemented yet"); + } + + return (int)filters->length; +} + +void git_filters_free(git_vector *filters) +{ + size_t i; + git_filter *filter; + + git_vector_foreach(filters, i, filter) { + if (filter->do_free != NULL) + filter->do_free(filter); + else + free(filter); + } + + git_vector_free(filters); +} + +int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) +{ + unsigned int i, src; + git_buf *dbuffer[2]; + + dbuffer[0] = source; + dbuffer[1] = dest; + + src = 0; + + if (source->size == 0) { + git_buf_clear(dest); + return GIT_SUCCESS; + } + + /* Pre-grow the destination buffer to more or less the size + * we expect it to have */ + if (git_buf_grow(dest, source->size) < 0) + return GIT_ENOMEM; + + for (i = 0; i < filters->length; ++i) { + git_filter *filter = git_vector_get(filters, i); + unsigned int dst = 1 - src; + + git_buf_clear(dbuffer[dst]); + + /* Apply the filter from dbuffer[src] to the other buffer; + * if the filtering is canceled by the user mid-filter, + * we skip to the next filter without changing the source + * of the double buffering (so that the text goes through + * cleanly). + */ + if (filter->apply(filter, dbuffer[dst], dbuffer[src]) == 0) + src = dst; + + if (git_buf_oom(dbuffer[dst])) + return GIT_ENOMEM; + } + + /* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */ + if (src != 1) + git_buf_swap(dest, source); + + return GIT_SUCCESS; +} + diff --git a/src/filter.h b/src/filter.h new file mode 100644 index 000000000..5a77f25c6 --- /dev/null +++ b/src/filter.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_filter_h__ +#define INCLUDE_filter_h__ + +#include "common.h" +#include "buffer.h" +#include "git2/odb.h" +#include "git2/repository.h" + +typedef struct git_filter { + int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source); + void (*do_free)(struct git_filter *self); +} git_filter; + +typedef enum { + GIT_FILTER_TO_WORKTREE, + GIT_FILTER_TO_ODB +} git_filter_mode; + +typedef enum { + GIT_CRLF_GUESS = -1, + GIT_CRLF_BINARY = 0, + GIT_CRLF_TEXT, + GIT_CRLF_INPUT, + GIT_CRLF_CRLF, + GIT_CRLF_AUTO, +} git_crlf_t; + +typedef struct { + /* NUL, CR, LF and CRLF counts */ + unsigned int nul, cr, lf, crlf; + + /* These are just approximations! */ + unsigned int printable, nonprintable; +} git_text_stats; + +/* + * FILTER API + */ + +/* + * For any given path in the working directory, fill the `filters` + * array with the relevant filters that need to be applied. + * + * Mode is either `GIT_FILTER_TO_WORKTREE` if you need to load the + * filters that will be used when checking out a file to the working + * directory, or `GIT_FILTER_TO_ODB` for the filters used when writing + * a file to the ODB. + * + * @param filters Vector where to store all the loaded filters + * @param repo Repository object that contains `path` + * @param path Relative path of the file to be filtered + * @param mode Filtering direction (WT->ODB or ODB->WT) + * @return the number of filters loaded for the file (0 if the file + * doesn't need filtering), or a negative error code + */ +extern int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode); + +/* + * Apply one or more filters to a file. + * + * The file must have been loaded as a `git_buf` object. Both the `source` + * and `dest` buffers are owned by the caller and must be freed once + * they are no longer needed. + * + * NOTE: Because of the double-buffering schema, the `source` buffer that contains + * the original file may be tampered once the filtering is complete. Regardless, + * the `dest` buffer will always contain the final result of the filtering + * + * @param dest Buffer to store the result of the filtering + * @param source Buffer containing the document to filter + * @param filters A non-empty vector of filters as supplied by `git_filters_load` + * @return GIT_SUCCESS on success, an error code otherwise + */ +extern int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters); + +/* + * Free the `filters` array generated by `git_filters_load`. + * + * Note that this frees both the array and its contents. The array will + * be clean/reusable after this call. + * + * @param filters A filters array as supplied by `git_filters_load` + */ +extern void git_filters_free(git_vector *filters); + +/* + * Available filters + */ + +/* Strip CRLF, from Worktree to ODB */ +extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); + + +/* + * PLAINTEXT API + */ + +/* + * Gather stats for a piece of text + * + * Fill the `stats` structure with information on the number of + * unreadable characters, carriage returns, etc, so it can be + * used in heuristics. + */ +extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text); + +/* + * Process `git_text_stats` data generated by `git_text_stat` to see + * if it qualifies as a binary file + */ +extern int git_text_is_binary(git_text_stats *stats); + +#endif diff --git a/src/hashtable.h b/src/hashtable.h index f6fbb8585..e09965965 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -65,20 +65,20 @@ GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *va #define git_hashtable_node_at(nodes, pos) ((git_hashtable_node *)(&nodes[pos])) -#define GIT_HASHTABLE_FOREACH(self, pkey, pvalue, code) {\ - git_hashtable *_self = (self);\ - git_hashtable_node *_nodes = _self->nodes;\ - unsigned int _i, _size = _self->size;\ - for (_i = 0; _i < _size; _i ++) {\ - git_hashtable_node *_node = git_hashtable_node_at(_nodes, _i);\ - if (_node->key)\ - {\ - pkey = _node->key;\ - pvalue = _node->value;\ - code;\ - }\ - }\ -} +#define GIT_HASHTABLE__FOREACH(self,block) { \ + unsigned int _c; \ + git_hashtable_node *_n = (self)->nodes; \ + for (_c = (self)->size; _c > 0; _c--, _n++) { \ + if (!_n->key) continue; block } } + +#define GIT_HASHTABLE_FOREACH(self, pkey, pvalue, code)\ + GIT_HASHTABLE__FOREACH(self,{(pkey)=_n->key;(pvalue)=_n->value;code;}) + +#define GIT_HASHTABLE_FOREACH_KEY(self, pkey, code)\ + GIT_HASHTABLE__FOREACH(self,{(pkey)=_n->key;code;}) + +#define GIT_HASHTABLE_FOREACH_VALUE(self, pvalue, code)\ + GIT_HASHTABLE__FOREACH(self,{(pvalue)=_n->value;code;}) #define GIT_HASHTABLE_FOREACH_DELETE() {\ _node->key = NULL; _node->value = NULL; _self->key_count--;\ diff --git a/src/ignore.c b/src/ignore.c index 30f86b822..a3bf0a282 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -11,7 +11,7 @@ static int load_ignore_file( git_repository *repo, const char *path, git_attr_file *ignores) { int error = GIT_SUCCESS; - git_fbuffer fbuf = GIT_FBUFFER_INIT; + git_buf fbuf = GIT_BUF_INIT; git_attr_fnmatch *match = NULL; const char *scan = NULL; char *context = NULL; @@ -28,7 +28,7 @@ static int load_ignore_file( if (error == GIT_SUCCESS) error = git_futils_readbuffer(&fbuf, path); - scan = fbuf.data; + scan = fbuf.ptr; while (error == GIT_SUCCESS && *scan) { if (!match && !(match = git__calloc(1, sizeof(git_attr_fnmatch)))) { @@ -53,7 +53,7 @@ static int load_ignore_file( } } - git_futils_freebuffer(&fbuf); + git_buf_free(&fbuf); git__free(match); git__free(context); diff --git a/src/index.c b/src/index.c index 4dccad527..5ac99de3e 100644 --- a/src/index.c +++ b/src/index.c @@ -216,7 +216,7 @@ void git_index_clear(git_index *index) int git_index_read(git_index *index) { int error = GIT_SUCCESS, updated; - git_fbuffer buffer = GIT_FBUFFER_INIT; + git_buf buffer = GIT_BUF_INIT; time_t mtime; assert(index->index_file_path); @@ -235,12 +235,12 @@ int git_index_read(git_index *index) if (updated) { git_index_clear(index); - error = parse_index(index, buffer.data, buffer.len); + error = parse_index(index, buffer.ptr, buffer.size); if (error == GIT_SUCCESS) index->last_modified = mtime; - git_futils_freebuffer(&buffer); + git_buf_free(&buffer); } if (error < GIT_SUCCESS) diff --git a/src/iterator.c b/src/iterator.c index 8255d4c9a..c026c3c09 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -143,6 +143,16 @@ static void tree_iterator__free(git_iterator *self) git_buf_free(&ti->path); } +static int tree_iterator__reset(git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + while (ti->stack && ti->stack->next) + tree_iterator__pop_frame(ti); + if (ti->stack) + ti->stack->index = 0; + return tree_iterator__expand_tree(ti); +} + int git_iterator_for_tree( git_repository *repo, git_tree *tree, git_iterator **iter) { @@ -155,6 +165,7 @@ int git_iterator_for_tree( ti->base.current = tree_iterator__current; ti->base.at_end = tree_iterator__at_end; ti->base.advance = tree_iterator__advance; + ti->base.reset = tree_iterator__reset; ti->base.free = tree_iterator__free; ti->repo = repo; ti->stack = tree_iterator__alloc_frame(tree); @@ -199,6 +210,13 @@ static int index_iterator__advance( return GIT_SUCCESS; } +static int index_iterator__reset(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + ii->current = 0; + return GIT_SUCCESS; +} + static void index_iterator__free(git_iterator *self) { index_iterator *ii = (index_iterator *)self; @@ -217,6 +235,7 @@ int git_iterator_for_index(git_repository *repo, git_iterator **iter) ii->base.current = index_iterator__current; ii->base.at_end = index_iterator__at_end; ii->base.advance = index_iterator__advance; + ii->base.reset = index_iterator__reset; ii->base.free = index_iterator__free; ii->current = 0; @@ -251,7 +270,7 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame(void) workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); if (wf == NULL) return wf; - if (git_vector_init(&wf->entries, 0, git__strcmp_cb) != GIT_SUCCESS) { + if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != GIT_SUCCESS) { git__free(wf); return NULL; } @@ -261,7 +280,7 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame(void) static void workdir_iterator__free_frame(workdir_iterator_frame *wf) { unsigned int i; - char *path; + git_path_with_stat *path; git_vector_foreach(&wf->entries, i, path) git__free(path); @@ -278,10 +297,7 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) if (wf == NULL) return GIT_ENOMEM; - /* allocate dir entries with extra byte (the "1" param) so we - * can suffix directory names with a "/". - */ - error = git_path_dirload(wi->path.ptr, wi->root_len, 1, &wf->entries); + error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries); if (error < GIT_SUCCESS || wf->entries.length == 0) { workdir_iterator__free_frame(wf); return GIT_ENOTFOUND; @@ -319,7 +335,7 @@ static int workdir_iterator__advance( int error; workdir_iterator *wi = (workdir_iterator *)self; workdir_iterator_frame *wf; - const char *next; + git_path_with_stat *next; if (entry != NULL) *entry = NULL; @@ -330,7 +346,7 @@ static int workdir_iterator__advance( while ((wf = wi->stack) != NULL) { next = git_vector_get(&wf->entries, ++wf->index); if (next != NULL) { - if (strcmp(next, DOT_GIT) == 0) + if (strcmp(next->path, DOT_GIT "/") == 0) continue; /* else found a good entry */ break; @@ -355,6 +371,20 @@ static int workdir_iterator__advance( return error; } +static int workdir_iterator__reset(git_iterator *self) +{ + workdir_iterator *wi = (workdir_iterator *)self; + while (wi->stack != NULL && wi->stack->next != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + git_ignore__pop_dir(&wi->ignores); + } + if (wi->stack) + wi->stack->index = 0; + return GIT_SUCCESS; +} + static void workdir_iterator__free(git_iterator *self) { workdir_iterator *wi = (workdir_iterator *)self; @@ -372,39 +402,35 @@ static void workdir_iterator__free(git_iterator *self) static int workdir_iterator__update_entry(workdir_iterator *wi) { int error; - struct stat st; - char *relpath = git_vector_get(&wi->stack->entries, wi->stack->index); + git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index); - error = git_buf_joinpath( - &wi->path, git_repository_workdir(wi->repo), relpath); + git_buf_truncate(&wi->path, wi->root_len); + error = git_buf_put(&wi->path, ps->path, ps->path_len); if (error < GIT_SUCCESS) return error; memset(&wi->entry, 0, sizeof(wi->entry)); - wi->entry.path = relpath; + wi->entry.path = ps->path; /* skip over .git directory */ - if (strcmp(relpath, DOT_GIT) == 0) + if (strcmp(ps->path, DOT_GIT "/") == 0) return workdir_iterator__advance((git_iterator *)wi, NULL); /* if there is an error processing the entry, treat as ignored */ wi->is_ignored = 1; - if (p_lstat(wi->path.ptr, &st) < 0) - return GIT_SUCCESS; - /* TODO: remove shared code for struct stat conversion with index.c */ - wi->entry.ctime.seconds = (git_time_t)st.st_ctime; - wi->entry.mtime.seconds = (git_time_t)st.st_mtime; - wi->entry.dev = st.st_rdev; - wi->entry.ino = st.st_ino; - wi->entry.mode = git_futils_canonical_mode(st.st_mode); - wi->entry.uid = st.st_uid; - wi->entry.gid = st.st_gid; - wi->entry.file_size = st.st_size; + wi->entry.ctime.seconds = (git_time_t)ps->st.st_ctime; + wi->entry.mtime.seconds = (git_time_t)ps->st.st_mtime; + wi->entry.dev = ps->st.st_rdev; + wi->entry.ino = ps->st.st_ino; + wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); + wi->entry.uid = ps->st.st_uid; + wi->entry.gid = ps->st.st_gid; + wi->entry.file_size = ps->st.st_size; /* if this is a file type we don't handle, treat as ignored */ - if (st.st_mode == 0) + if (wi->entry.mode == 0) return GIT_SUCCESS; /* okay, we are far enough along to look up real ignore rule */ @@ -412,18 +438,10 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) if (error != GIT_SUCCESS) return GIT_SUCCESS; - if (S_ISDIR(st.st_mode)) { - if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) { - /* create submodule entry */ - wi->entry.mode = S_IFGITLINK; - } else { - /* create directory entry that can be advanced into as needed */ - size_t pathlen = strlen(wi->entry.path); - wi->entry.path[pathlen] = '/'; - wi->entry.path[pathlen + 1] = '\0'; - wi->entry.mode = S_IFDIR; - } - } + /* detect submodules */ + if (S_ISDIR(wi->entry.mode) && + git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) + wi->entry.mode = S_IFGITLINK; return GIT_SUCCESS; } @@ -439,11 +457,14 @@ int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) wi->base.current = workdir_iterator__current; wi->base.at_end = workdir_iterator__at_end; wi->base.advance = workdir_iterator__advance; + wi->base.reset = workdir_iterator__reset; wi->base.free = workdir_iterator__free; wi->repo = repo; error = git_buf_sets(&wi->path, git_repository_workdir(repo)); if (error == GIT_SUCCESS) + error = git_path_to_dir(&wi->path); + if (error == GIT_SUCCESS) error = git_ignore__for_path(repo, "", &wi->ignores); if (error != GIT_SUCCESS) { git__free(wi); diff --git a/src/iterator.h b/src/iterator.h index ac30b4ded..aa78c9f29 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -23,6 +23,7 @@ struct git_iterator { int (*current)(git_iterator *, const git_index_entry **); int (*at_end)(git_iterator *); int (*advance)(git_iterator *, const git_index_entry **); + int (*reset)(git_iterator *); void (*free)(git_iterator *); }; @@ -60,6 +61,11 @@ GIT_INLINE(int) git_iterator_advance( return iter->advance(iter, entry); } +GIT_INLINE(int) git_iterator_reset(git_iterator *iter) +{ + return iter->reset(iter); +} + GIT_INLINE(void) git_iterator_free(git_iterator *iter) { iter->free(iter); @@ -393,8 +393,8 @@ static int add_default_backends(git_odb *db, const char *objects_dir, int as_alt static int load_alternates(git_odb *odb, const char *objects_dir) { git_buf alternates_path = GIT_BUF_INIT; + git_buf alternates_buf = GIT_BUF_INIT; char *buffer; - git_fbuffer alternates_buf = GIT_FBUFFER_INIT; const char *alternate; int error; @@ -412,7 +412,7 @@ static int load_alternates(git_odb *odb, const char *objects_dir) return git__throw(GIT_EOSERR, "Failed to add backend. Can't read alternates"); } - buffer = (char *)alternates_buf.data; + buffer = (char *)alternates_buf.ptr; error = GIT_SUCCESS; /* add each alternate as a new backend; one alternate per line */ @@ -433,7 +433,8 @@ static int load_alternates(git_odb *odb, const char *objects_dir) } git_buf_free(&alternates_path); - git_futils_freebuffer(&alternates_buf); + git_buf_free(&alternates_buf); + if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to load alternates"); return error; diff --git a/src/odb_loose.c b/src/odb_loose.c index bb2b7b5f5..f5f6e35ac 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -75,13 +75,13 @@ static int object_file_name(git_buf *name, const char *dir, const git_oid *id) } -static size_t get_binary_object_header(obj_hdr *hdr, git_fbuffer *obj) +static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) { unsigned char c; - unsigned char *data = obj->data; + unsigned char *data = (unsigned char *)obj->ptr; size_t shift, size, used = 0; - if (obj->len == 0) + if (obj->size == 0) return 0; c = data[used++]; @@ -90,7 +90,7 @@ static size_t get_binary_object_header(obj_hdr *hdr, git_fbuffer *obj) size = c & 15; shift = 4; while (c & 0x80) { - if (obj->len <= used) + if (obj->size <= used) return 0; if (sizeof(size_t) * 8 <= shift) return 0; @@ -177,12 +177,12 @@ static void set_stream_output(z_stream *s, void *out, size_t len) } -static int start_inflate(z_stream *s, git_fbuffer *obj, void *out, size_t len) +static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len) { int status; init_stream(s, out, len); - set_stream_input(s, obj->data, obj->len); + set_stream_input(s, obj->ptr, obj->size); if ((status = inflateInit(s)) < Z_OK) return status; @@ -287,7 +287,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) * of loose object data into packs. This format is no longer used, but * we must still read it. */ -static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_fbuffer *obj) +static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) { unsigned char *in, *buf; obj_hdr hdr; @@ -310,8 +310,8 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_fbuffer *obj) if (!buf) return GIT_ENOMEM; - in = ((unsigned char *)obj->data) + used; - len = obj->len - used; + in = ((unsigned char *)obj->ptr) + used; + len = obj->size - used; if (inflate_buffer(in, len, buf, hdr.size)) { git__free(buf); return git__throw(GIT_ERROR, "Failed to inflate loose object. Could not inflate buffer"); @@ -325,7 +325,7 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_fbuffer *obj) return GIT_SUCCESS; } -static int inflate_disk_obj(git_rawobj *out, git_fbuffer *obj) +static int inflate_disk_obj(git_rawobj *out, git_buf *obj) { unsigned char head[64], *buf; z_stream zs; @@ -335,7 +335,7 @@ static int inflate_disk_obj(git_rawobj *out, git_fbuffer *obj) /* * check for a pack-like loose object */ - if (!is_zlib_compressed_data(obj->data)) + if (!is_zlib_compressed_data((unsigned char *)obj->ptr)) return inflate_packlike_loose_disk_obj(out, obj); /* @@ -383,7 +383,7 @@ static int inflate_disk_obj(git_rawobj *out, git_fbuffer *obj) static int read_loose(git_rawobj *out, git_buf *loc) { int error; - git_fbuffer obj = GIT_FBUFFER_INIT; + git_buf obj = GIT_BUF_INIT; assert(out && loc); @@ -398,7 +398,7 @@ static int read_loose(git_rawobj *out, git_buf *loc) return git__throw(GIT_ENOTFOUND, "Failed to read loose object. File not found"); error = inflate_disk_obj(out, &obj); - git_futils_freebuffer(&obj); + git_buf_free(&obj); return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read loose object"); } diff --git a/src/odb_pack.c b/src/odb_pack.c index 9e1004eb8..249144a3a 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -159,9 +159,9 @@ static int pack_entry_find_prefix(struct git_pack_entry *e, * ***********************************************************/ -GIT_INLINE(void) pack_window_free_all(struct pack_backend *GIT_UNUSED(backend), struct git_pack_file *p) +GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p) { - GIT_UNUSED_ARG(backend); + GIT_UNUSED(backend); git_mwindow_free_all(&p->mwf); } @@ -190,6 +190,16 @@ int git_oid_streq(const git_oid *a, const char *str) return git_oid_cmp(a, &id) == 0 ? GIT_SUCCESS : GIT_ERROR; } +int git_oid_iszero(const git_oid *oid_a) +{ + const unsigned char *a = oid_a->id; + unsigned int i; + for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) + if (*a != 0) + return 0; + return 1; +} + typedef short node_index; typedef union { diff --git a/src/path.c b/src/path.c index ec40f4b06..d2c292bf2 100644 --- a/src/path.c +++ b/src/path.c @@ -583,3 +583,48 @@ int git_path_dirload( return GIT_SUCCESS; } +int git_path_with_stat_cmp(const void *a, const void *b) +{ + const git_path_with_stat *psa = a, *psb = b; + return git__strcmp_cb(psa->path, psb->path); +} + +int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents) +{ + int error; + unsigned int i; + git_path_with_stat *ps; + git_buf full = GIT_BUF_INIT; + + if ((error = git_buf_set(&full, path, prefix_len)) != GIT_SUCCESS) + return error; + + if ((error = git_path_dirload(path, prefix_len, + sizeof(git_path_with_stat) + 1, contents)) != GIT_SUCCESS) { + git_buf_free(&full); + return error; + } + + git_vector_foreach(contents, i, ps) { + size_t path_len = strlen((char *)ps); + + memmove(ps->path, ps, path_len + 1); + ps->path_len = path_len; + + git_buf_joinpath(&full, full.ptr, ps->path); + p_lstat(full.ptr, &ps->st); + git_buf_truncate(&full, prefix_len); + + if (S_ISDIR(ps->st.st_mode)) { + ps->path[path_len] = '/'; + ps->path[path_len + 1] = '\0'; + } + } + + git_buf_free(&full); + + return error; +} diff --git a/src/path.h b/src/path.h index abe6c2217..981fdd6a4 100644 --- a/src/path.h +++ b/src/path.h @@ -246,4 +246,26 @@ extern int git_path_dirload( size_t alloc_extra, git_vector *contents); + +typedef struct { + struct stat st; + size_t path_len; + char path[GIT_FLEX_ARRAY]; +} git_path_with_stat; + +extern int git_path_with_stat_cmp(const void *a, const void *b); + +/** + * Load all directory entries along with stat info into a vector. + * + * This is just like git_path_dirload except that each entry in the + * vector is a git_path_with_stat structure that contains both the + * path and the stat info, plus directories will have a / suffixed + * to their path name. + */ +extern int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents); + #endif @@ -41,11 +41,11 @@ static int flush_pkt(git_pkt **out) } /* the rest of the line will be useful for multi_ack */ -static int ack_pkt(git_pkt **out, const char *GIT_UNUSED(line), size_t GIT_UNUSED(len)) +static int ack_pkt(git_pkt **out, const char *line, size_t len) { git_pkt *pkt; - GIT_UNUSED_ARG(line); - GIT_UNUSED_ARG(len); + GIT_UNUSED(line); + GIT_UNUSED(len); pkt = git__malloc(sizeof(git_pkt)); if (pkt == NULL) diff --git a/src/posix.h b/src/posix.h index 0cce1fe34..fb17cba6c 100644 --- a/src/posix.h +++ b/src/posix.h @@ -66,4 +66,6 @@ extern int p_rename(const char *from, const char *to); # include "unix/posix.h" #endif +#define p_readdir_r(d,e,r) readdir_r(d,e,r) + #endif diff --git a/src/reflog.c b/src/reflog.c index 9f5ccd322..6ca9418cf 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -183,7 +183,7 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) { int error; git_buf log_path = GIT_BUF_INIT; - git_fbuffer log_file = GIT_FBUFFER_INIT; + git_buf log_file = GIT_BUF_INIT; git_reflog *log = NULL; *reflog = NULL; @@ -201,7 +201,7 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) goto cleanup; } - if ((error = reflog_parse(log, log_file.data, log_file.len)) < GIT_SUCCESS) + if ((error = reflog_parse(log, log_file.ptr, log_file.size)) < GIT_SUCCESS) git__rethrow(error, "Failed to read reflog"); else *reflog = log; @@ -209,7 +209,7 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) cleanup: if (error != GIT_SUCCESS && log != NULL) git_reflog_free(log); - git_futils_freebuffer(&log_file); + git_buf_free(&log_file); git_buf_free(&log_path); return error; diff --git a/src/refs.c b/src/refs.c index 8e911c1ae..f3388bf53 100644 --- a/src/refs.c +++ b/src/refs.c @@ -32,15 +32,15 @@ struct packref { static const int default_table_size = 32; static int reference_read( - git_fbuffer *file_content, + git_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated); /* loose refs */ -static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content); -static int loose_parse_oid(git_oid *ref, git_fbuffer *file_content); +static int loose_parse_symbolic(git_reference *ref, git_buf *file_content); +static int loose_parse_oid(git_oid *ref, git_buf *file_content); static int loose_lookup(git_reference *ref); static int loose_lookup_to_packfile(struct packref **ref_out, git_repository *repo, const char *name); @@ -105,7 +105,7 @@ static int reference_alloc( reference->name = git__strdup(name); if (reference->name == NULL) { - free(reference); + git__free(reference); return GIT_ENOMEM; } @@ -113,7 +113,7 @@ static int reference_alloc( return GIT_SUCCESS; } -static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated) +static int reference_read(git_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated) { git_buf path = GIT_BUF_INIT; int error = GIT_SUCCESS; @@ -129,15 +129,15 @@ static int reference_read(git_fbuffer *file_content, time_t *mtime, const char * return error; } -static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content) +static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) { const unsigned int header_len = strlen(GIT_SYMREF); const char *refname_start; char *eol; - refname_start = (const char *)file_content->data; + refname_start = (const char *)file_content->ptr; - if (file_content->len < (header_len + 1)) + if (file_content->size < (header_len + 1)) return git__throw(GIT_EOBJCORRUPTED, "Failed to parse loose reference. Object too short"); @@ -165,15 +165,15 @@ static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content) return GIT_SUCCESS; } -static int loose_parse_oid(git_oid *oid, git_fbuffer *file_content) +static int loose_parse_oid(git_oid *oid, git_buf *file_content) { int error; char *buffer; - buffer = (char *)file_content->data; + buffer = (char *)file_content->ptr; /* File format: 40 chars (OID) + newline */ - if (file_content->len < GIT_OID_HEXSZ + 1) + if (file_content->size < GIT_OID_HEXSZ + 1) return git__throw(GIT_EOBJCORRUPTED, "Failed to parse loose reference. Reference too short"); @@ -193,26 +193,26 @@ static int loose_parse_oid(git_oid *oid, git_fbuffer *file_content) static git_rtype loose_guess_rtype(const git_buf *full_path) { - git_fbuffer ref_file = GIT_FBUFFER_INIT; + git_buf ref_file = GIT_BUF_INIT; git_rtype type; type = GIT_REF_INVALID; if (git_futils_readbuffer(&ref_file, full_path->ptr) == GIT_SUCCESS) { - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) type = GIT_REF_SYMBOLIC; else type = GIT_REF_OID; } - git_futils_freebuffer(&ref_file); + git_buf_free(&ref_file); return type; } static int loose_lookup(git_reference *ref) { int error = GIT_SUCCESS, updated; - git_fbuffer ref_file = GIT_FBUFFER_INIT; + git_buf ref_file = GIT_BUF_INIT; if (reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name, &updated) < GIT_SUCCESS) @@ -222,13 +222,13 @@ static int loose_lookup(git_reference *ref) return GIT_SUCCESS; if (ref->flags & GIT_REF_SYMBOLIC) { - free(ref->target.symbolic); + git__free(ref->target.symbolic); ref->target.symbolic = NULL; } ref->flags = 0; - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) { + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { ref->flags |= GIT_REF_SYMBOLIC; error = loose_parse_symbolic(ref, &ref_file); } else { @@ -236,7 +236,7 @@ static int loose_lookup(git_reference *ref) error = loose_parse_oid(&ref->target.oid, &ref_file); } - git_futils_freebuffer(&ref_file); + git_buf_free(&ref_file); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to lookup loose reference"); @@ -250,7 +250,7 @@ static int loose_lookup_to_packfile( const char *name) { int error = GIT_SUCCESS; - git_fbuffer ref_file = GIT_FBUFFER_INIT; + git_buf ref_file = GIT_BUF_INIT; struct packref *ref = NULL; size_t name_len; @@ -273,12 +273,13 @@ static int loose_lookup_to_packfile( ref->flags = GIT_PACKREF_WAS_LOOSE; *ref_out = ref; - git_futils_freebuffer(&ref_file); + git_buf_free(&ref_file); return GIT_SUCCESS; cleanup: - git_futils_freebuffer(&ref_file); - free(ref); + git_buf_free(&ref_file); + git__free(ref); + return git__rethrow(error, "Failed to lookup loose reference"); } @@ -420,14 +421,14 @@ static int packed_parse_oid( return GIT_SUCCESS; cleanup: - free(ref); + git__free(ref); return git__rethrow(error, "Failed to parse OID of packed reference"); } static int packed_load(git_repository *repo) { int error = GIT_SUCCESS, updated; - git_fbuffer packfile = GIT_FBUFFER_INIT; + git_buf packfile = GIT_BUF_INIT; const char *buffer_start, *buffer_end; git_refcache *ref_cache = &repo->references; @@ -468,8 +469,8 @@ static int packed_load(git_repository *repo) git_hashtable_clear(ref_cache->packfile); - buffer_start = (const char *)packfile.data; - buffer_end = (const char *)(buffer_start) + packfile.len; + buffer_start = (const char *)packfile.ptr; + buffer_end = (const char *)(buffer_start) + packfile.size; while (buffer_start < buffer_end && buffer_start[0] == '#') { buffer_start = strchr(buffer_start, '\n'); @@ -495,18 +496,18 @@ static int packed_load(git_repository *repo) error = git_hashtable_insert(ref_cache->packfile, ref->name, ref); if (error < GIT_SUCCESS) { - free(ref); + git__free(ref); goto cleanup; } } - git_futils_freebuffer(&packfile); + git_buf_free(&packfile); return GIT_SUCCESS; cleanup: git_hashtable_free(ref_cache->packfile); ref_cache->packfile = NULL; - git_futils_freebuffer(&packfile); + git_buf_free(&packfile); return git__rethrow(error, "Failed to load packed references"); } @@ -560,12 +561,12 @@ static int _dirent_loose_load(void *data, git_buf *full_path) if (git_hashtable_insert2( repository->references.packfile, ref->name, ref, &old_ref) < GIT_SUCCESS) { - free(ref); + git__free(ref); return GIT_ENOMEM; } if (old_ref != NULL) - free(old_ref); + git__free(old_ref); } return error == GIT_SUCCESS ? @@ -773,9 +774,8 @@ static int packed_write(git_repository *repo) /* Load all the packfile into a vector */ { struct packref *reference; - const void *GIT_UNUSED(_unused); - GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, + GIT_HASHTABLE_FOREACH_VALUE(repo->references.packfile, reference, /* cannot fail: vector already has the right size */ git_vector_insert(&packing_list, reference); ); @@ -929,7 +929,7 @@ static int packed_lookup(git_reference *ref) return GIT_SUCCESS; if (ref->flags & GIT_REF_SYMBOLIC) { - free(ref->target.symbolic); + git__free(ref->target.symbolic); ref->target.symbolic = NULL; } @@ -1513,12 +1513,11 @@ int git_reference_foreach( /* list all the packed references first */ if (list_flags & GIT_REF_PACKED) { const char *ref_name; - void *GIT_UNUSED(_unused); if ((error = packed_load(repo)) < GIT_SUCCESS) return git__rethrow(error, "Failed to list references"); - GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused, + GIT_HASHTABLE_FOREACH_KEY(repo->references.packfile, ref_name, if ((error = callback(ref_name, payload)) < GIT_SUCCESS) return git__throw(error, "Failed to list references. User callback failed"); @@ -1595,12 +1594,10 @@ void git_repository__refcache_free(git_refcache *refs) assert(refs); if (refs->packfile) { - const void *GIT_UNUSED(_unused); struct packref *reference; - GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference, - free(reference); - ); + GIT_HASHTABLE_FOREACH_VALUE( + refs->packfile, reference, git__free(reference)); git_hashtable_free(refs->packfile); } diff --git a/src/remote.c b/src/remote.c index 5b442e934..52b6aacc9 100644 --- a/src/remote.c +++ b/src/remote.c @@ -431,13 +431,13 @@ struct cb_data { regex_t *preg; }; -static int remote_list_cb(const char *name, const char *GIT_UNUSED(value), void *data_) +static int remote_list_cb(const char *name, const char *value, void *data_) { struct cb_data *data = (struct cb_data *)data_; size_t nmatch = 2; regmatch_t pmatch[2]; int error; - GIT_UNUSED_ARG(value); + GIT_UNUSED(value); if (!regexec(data->preg, name, nmatch, pmatch, 0)) { char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so); diff --git a/src/repository.c b/src/repository.c index f394d06fe..1f8306991 100644 --- a/src/repository.c +++ b/src/repository.c @@ -43,6 +43,8 @@ static void drop_config(git_repository *repo) git_config_free(repo->_config); repo->_config = NULL; } + + git_repository__cvar_cache_clear(repo); } static void drop_index(git_repository *repo) @@ -111,6 +113,9 @@ static git_repository *repository_alloc(void) return NULL; } + /* set all the entries in the cvar cache to `unset` */ + git_repository__cvar_cache_clear(repo); + return repo; } @@ -467,7 +472,7 @@ static int retrieve_ceiling_directories_offset( */ static int read_gitfile(git_buf *path_out, const char *file_path, const char *base_path) { - git_fbuffer file; + git_buf file = GIT_BUF_INIT; int error; assert(path_out && file_path); @@ -476,22 +481,22 @@ static int read_gitfile(git_buf *path_out, const char *file_path, const char *ba if (error < GIT_SUCCESS) return error; - if (git__prefixcmp((char *)file.data, GIT_FILE_CONTENT_PREFIX)) { - git_futils_freebuffer(&file); + if (git__prefixcmp((char *)file.ptr, GIT_FILE_CONTENT_PREFIX)) { + git_buf_free(&file); return git__throw(GIT_ENOTFOUND, "Invalid gitfile format `%s`", file_path); } - git_futils_fbuffer_rtrim(&file); + git_buf_rtrim(&file); - if (strlen(GIT_FILE_CONTENT_PREFIX) == file.len) { - git_futils_freebuffer(&file); + if (strlen(GIT_FILE_CONTENT_PREFIX) == file.size) { + git_buf_free(&file); return git__throw(GIT_ENOTFOUND, "No path in git file `%s`", file_path); } error = git_path_prettify_dir(path_out, - ((char *)file.data) + strlen(GIT_FILE_CONTENT_PREFIX), base_path); + ((char *)file.ptr) + strlen(GIT_FILE_CONTENT_PREFIX), base_path); - git_futils_freebuffer(&file); + git_buf_free(&file); if (error == GIT_SUCCESS && git_path_exists(path_out->ptr) == 0) return GIT_SUCCESS; diff --git a/src/repository.h b/src/repository.h index 516fd10be..b5dcc1340 100644 --- a/src/repository.h +++ b/src/repository.h @@ -26,6 +26,49 @@ #define GIT_DIR_MODE 0755 #define GIT_BARE_DIR_MODE 0777 +/** Cvar cache identifiers */ +typedef enum { + GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */ + GIT_CVAR_EOL, /* core.eol */ + GIT_CVAR_CACHE_MAX +} git_cvar_cached; + +/** + * CVAR value enumerations + * + * These are the values that are actually stored in the cvar cache, instead + * of their string equivalents. These values are internal and symbolic; + * make sure that none of them is set to `-1`, since that is the unique + * identifier for "not cached" + */ +typedef enum { + /* The value hasn't been loaded from the cache yet */ + GIT_CVAR_NOT_CACHED = -1, + + /* core.safecrlf: false, 'fail', 'warn' */ + GIT_SAFE_CRLF_FALSE = 0, + GIT_SAFE_CRLF_FAIL = 1, + GIT_SAFE_CRLF_WARN = 2, + + /* core.autocrlf: false, true, 'input; */ + GIT_AUTO_CRLF_FALSE = 0, + GIT_AUTO_CRLF_TRUE = 1, + GIT_AUTO_CRLF_INPUT = 2, + GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, + + /* core.eol: unset, 'crlf', 'lf', 'native' */ + GIT_EOL_UNSET = 0, + GIT_EOL_CRLF = 1, + GIT_EOL_LF = 2, +#ifdef GIT_WIN32 + GIT_EOL_NATIVE = GIT_EOL_CRLF, +#else + GIT_EOL_NATIVE = GIT_EOL_LF, +#endif + GIT_EOL_DEFAULT = GIT_EOL_NATIVE +} git_cvar_value; + +/** Base git object for inheritance */ struct git_object { git_cached_obj cached; git_repository *repo; @@ -46,6 +89,8 @@ struct git_repository { unsigned is_bare:1; unsigned int lru_counter; + + git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; /* fully free the object; internal method, do not @@ -55,8 +100,24 @@ void git_object__free(void *object); int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); +/* + * Weak pointers to repository internals. + * + * The returned pointers do not need to be freed. Do not keep + * permanent references to these (i.e. between API calls), since they may + * become invalidated if the user replaces a repository internal. + */ int git_repository_config__weakptr(git_config **out, git_repository *repo); int git_repository_odb__weakptr(git_odb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); +/* + * CVAR cache + * + * Efficient access to the most used config variables of a repository. + * The cache is cleared everytime the config backend is replaced. + */ +int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar); +void git_repository__cvar_cache_clear(git_repository *repo); + #endif diff --git a/src/revwalk.c b/src/revwalk.c index cd971b5d9..997771f08 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -590,7 +590,6 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) void git_revwalk_free(git_revwalk *walk) { unsigned int i; - const void *GIT_UNUSED(_unused); commit_object *commit; if (walk == NULL) @@ -602,7 +601,7 @@ void git_revwalk_free(git_revwalk *walk) /* if the parent has more than PARENTS_PER_COMMIT parents, * we had to allocate a separate array for those parents. * make sure it's being free'd */ - GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, { + GIT_HASHTABLE_FOREACH_VALUE(walk->commits, commit, { if (commit->out_degree > PARENTS_PER_COMMIT) git__free(commit->parents); }); @@ -669,12 +668,11 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) void git_revwalk_reset(git_revwalk *walk) { - const void *GIT_UNUSED(_unused); commit_object *commit; assert(walk); - GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, + GIT_HASHTABLE_FOREACH_VALUE(walk->commits, commit, commit->seen = 0; commit->in_degree = 0; commit->topo_delay = 0; diff --git a/src/transport.c b/src/transport.c index 4c486e200..4910f2433 100644 --- a/src/transport.c +++ b/src/transport.c @@ -51,9 +51,9 @@ static git_transport_cb transport_find_fn(const char *url) * Public API * **************/ -int git_transport_dummy(git_transport **GIT_UNUSED(transport)) +int git_transport_dummy(git_transport **transport) { - GIT_UNUSED_ARG(transport); + GIT_UNUSED(transport); return git__throw(GIT_ENOTIMPLEMENTED, "This protocol isn't implemented. Sorry"); } diff --git a/src/transports/local.c b/src/transports/local.c index 1dfc8ed2e..eb24db0fd 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -154,7 +154,7 @@ static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *pay * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calulating the heads ourselves. */ -static int local_connect(git_transport *transport, int GIT_UNUSED(direction)) +static int local_connect(git_transport *transport, int direction) { git_repository *repo; int error; @@ -162,7 +162,7 @@ static int local_connect(git_transport *transport, int GIT_UNUSED(direction)) const char *path; git_buf buf = GIT_BUF_INIT; - GIT_UNUSED_ARG(direction); + GIT_UNUSED(direction); /* The repo layer doesn't want the prefix */ if (!git__prefixcmp(transport->url, "file://")) { @@ -194,7 +194,7 @@ static int local_connect(git_transport *transport, int GIT_UNUSED(direction)) return GIT_SUCCESS; } -static int local_close(git_transport *GIT_UNUSED(transport)) +static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; diff --git a/src/unix/posix.h b/src/unix/posix.h index 2b0d85bb5..9973acf30 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -21,6 +21,5 @@ #define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) #define p_mkstemp(p) mkstemp(p) #define p_setenv(n,v,o) setenv(n,v,o) -#define p_readdir_r(d,e,r) readdir_r(d,e,r) #endif diff --git a/src/vector.c b/src/vector.c index e109704ab..7513ea3f0 100644 --- a/src/vector.c +++ b/src/vector.c @@ -220,4 +220,14 @@ void git_vector_clear(git_vector *v) v->sorted = 1; } +void git_vector_swap(git_vector *a, git_vector *b) +{ + git_vector t; + + if (!a || !b || a == b) + return; + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); +} diff --git a/src/vector.h b/src/vector.h index 44635ae14..180edbf7c 100644 --- a/src/vector.h +++ b/src/vector.h @@ -24,6 +24,7 @@ typedef struct git_vector { int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); +void git_vector_swap(git_vector *a, git_vector *b); int git_vector_search(git_vector *v, const void *entry); int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key); @@ -38,6 +39,11 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) return (position < v->length) ? v->contents[position] : NULL; } +GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position) +{ + return (position < v->length) ? v->contents[position] : NULL; +} + GIT_INLINE(void *) git_vector_last(git_vector *v) { return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; diff --git a/src/win32/dir.c b/src/win32/dir.c index 23bc55558..035e2b685 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -58,8 +58,11 @@ git__DIR *git__opendir(const char *dir) return new; } -int git__readdir_r( - git__DIR *d, struct git__dirent *entry, struct git__dirent **result) +int git__readdir_ext( + git__DIR *d, + struct git__dirent *entry, + struct git__dirent **result, + int *is_dir) { if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) return -1; @@ -80,13 +83,16 @@ int git__readdir_r( entry->d_name, GIT_PATH_MAX, NULL, NULL); *result = entry; + if (is_dir != NULL) + *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + return 0; } struct git__dirent *git__readdir(git__DIR *d) { struct git__dirent *result; - if (git__readdir_r(d, &d->entry, &result) < 0) + if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) return NULL; return result; } diff --git a/src/win32/dir.h b/src/win32/dir.h index fc54e2977..c816d79bb 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -24,7 +24,8 @@ typedef struct { extern git__DIR *git__opendir(const char *); extern struct git__dirent *git__readdir(git__DIR *); -extern int git__readdir_r(git__DIR*, struct git__dirent*, struct git__dirent**); +extern int git__readdir_ext( + git__DIR *, struct git__dirent *, struct git__dirent **, int *); extern void git__rewinddir(git__DIR *); extern int git__closedir(git__DIR *); @@ -33,10 +34,9 @@ extern int git__closedir(git__DIR *); # define DIR git__DIR # define opendir git__opendir # define readdir git__readdir +# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) # define rewinddir git__rewinddir # define closedir git__closedir # endif -#define p_readdir_r(d,e,r) git__readdir_r(d,e,r) - #endif /* INCLUDE_dir_h__ */ diff --git a/src/win32/posix.h b/src/win32/posix.h index 8f603657b..60adc9666 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -11,20 +11,20 @@ #include "fnmatch.h" #include "utf-conv.h" -GIT_INLINE(int) p_link(const char *GIT_UNUSED(old), const char *GIT_UNUSED(new)) +GIT_INLINE(int) p_link(const char *old, const char *new) { - GIT_UNUSED_ARG(old) - GIT_UNUSED_ARG(new) + GIT_UNUSED(old); + GIT_UNUSED(new); errno = ENOSYS; return -1; } -GIT_INLINE(int) p_mkdir(const char *path, mode_t GIT_UNUSED(mode)) +GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { wchar_t* buf = gitwin_to_utf16(path); int ret = _wmkdir(buf); - GIT_UNUSED_ARG(mode) + GIT_UNUSED(mode); git__free(buf); return ret; diff --git a/src/win32/pthread.c b/src/win32/pthread.c index cbce639c0..3db536848 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -8,10 +8,10 @@ #include "pthread.h" int pthread_create(pthread_t *GIT_RESTRICT thread, - const pthread_attr_t *GIT_RESTRICT GIT_UNUSED(attr), + const pthread_attr_t *GIT_RESTRICT attr, void *(*start_routine)(void*), void *GIT_RESTRICT arg) { - GIT_UNUSED_ARG(attr); + GIT_UNUSED(attr); *thread = (pthread_t) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL); return *thread ? GIT_SUCCESS : git__throw(GIT_EOSERR, "Failed to create pthread"); } @@ -26,9 +26,9 @@ int pthread_join(pthread_t thread, void **value_ptr) } int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, - const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) + const pthread_mutexattr_t *GIT_RESTRICT mutexattr) { - GIT_UNUSED_ARG(mutexattr); + GIT_UNUSED(mutexattr); InitializeCriticalSection(mutex); return 0; } diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h new file mode 100644 index 000000000..cb8b235b5 --- /dev/null +++ b/src/xdiff/xdiff.h @@ -0,0 +1,135 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XDIFF_H) +#define XDIFF_H + +#ifdef __cplusplus +extern "C" { +#endif /* #ifdef __cplusplus */ + + +#define XDF_NEED_MINIMAL (1 << 1) +#define XDF_IGNORE_WHITESPACE (1 << 2) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) +#define XDF_PATIENCE_DIFF (1 << 5) +#define XDF_HISTOGRAM_DIFF (1 << 6) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) + +#define XDL_PATCH_NORMAL '-' +#define XDL_PATCH_REVERSE '+' +#define XDL_PATCH_MODEMASK ((1 << 8) - 1) +#define XDL_PATCH_IGNOREBSPACE (1 << 8) + +#define XDL_EMIT_FUNCNAMES (1 << 0) +#define XDL_EMIT_COMMON (1 << 1) +#define XDL_EMIT_FUNCCONTEXT (1 << 2) + +#define XDL_MMB_READONLY (1 << 0) + +#define XDL_MMF_ATOMIC (1 << 0) + +#define XDL_BDOP_INS 1 +#define XDL_BDOP_CPY 2 +#define XDL_BDOP_INSB 3 + +/* merge simplification levels */ +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 +#define XDL_MERGE_ZEALOUS_ALNUM 3 + +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR_UNION 3 + +/* merge output styles */ +#define XDL_MERGE_DIFF3 1 + +typedef struct s_mmfile { + char *ptr; + size_t size; +} mmfile_t; + +typedef struct s_mmbuffer { + char *ptr; + size_t size; +} mmbuffer_t; + +typedef struct s_xpparam { + unsigned long flags; +} xpparam_t; + +typedef struct s_xdemitcb { + void *priv; + int (*outf)(void *, mmbuffer_t *, int); +} xdemitcb_t; + +typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); + +typedef struct s_xdemitconf { + long ctxlen; + long interhunkctxlen; + unsigned long flags; + find_func_t find_func; + void *find_func_priv; + void (*emit_func)(void); +} xdemitconf_t; + +typedef struct s_bdiffparam { + long bsize; +} bdiffparam_t; + + +#define xdl_malloc(x) malloc(x) +#define xdl_free(ptr) free(ptr) +#define xdl_realloc(ptr,x) realloc(ptr,x) + +void *xdl_mmfile_first(mmfile_t *mmf, long *size); +long xdl_mmfile_size(mmfile_t *mmf); + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb); + +typedef struct s_xmparam { + xpparam_t xpp; + int marker_size; + int level; + int favor; + int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ +} xmparam_t; + +#define DEFAULT_CONFLICT_MARKER_SIZE 7 + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result); + +#ifdef __cplusplus +} +#endif /* #ifdef __cplusplus */ + +#endif /* #if !defined(XDIFF_H) */ diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c new file mode 100644 index 000000000..75a392275 --- /dev/null +++ b/src/xdiff/xdiffi.c @@ -0,0 +1,572 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + + +#define XDL_MAX_COST_MIN 256 +#define XDL_HEUR_MIN_COST 256 +#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) +#define XDL_SNAKE_CNT 20 +#define XDL_K_HEUR 4 + + + +typedef struct s_xdpsplit { + long i1, i2; + int min_lo, min_hi; +} xdpsplit_t; + + + + +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv); +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); + + + + + +/* + * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. + * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both + * the forward diagonal starting from (off1, off2) and the backward diagonal + * starting from (lim1, lim2). If the K values on the same diagonal crosses + * returns the furthest point of reach. We might end up having to expensive + * cases using this algorithm is full, so a little bit of heuristic is needed + * to cut the search and to return a suboptimal point. + */ +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv) { + long dmin = off1 - lim2, dmax = lim1 - off2; + long fmid = off1 - off2, bmid = lim1 - lim2; + long odd = (fmid - bmid) & 1; + long fmin = fmid, fmax = fmid; + long bmin = bmid, bmax = bmid; + long ec, d, i1, i2, prev1, best, dd, v, k; + + /* + * Set initial diagonal values for both forward and backward path. + */ + kvdf[fmid] = off1; + kvdb[bmid] = lim1; + + for (ec = 1;; ec++) { + int got_snake = 0; + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (fmin > dmin) + kvdf[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + kvdf[++fmax + 1] = -1; + else + --fmax; + + for (d = fmax; d >= fmin; d -= 2) { + if (kvdf[d - 1] >= kvdf[d + 1]) + i1 = kvdf[d - 1] + 1; + else + i1 = kvdf[d + 1]; + prev1 = i1; + i2 = i1 - d; + for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); + if (i1 - prev1 > xenv->snake_cnt) + got_snake = 1; + kvdf[d] = i1; + if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (bmin > dmin) + kvdb[--bmin - 1] = XDL_LINE_MAX; + else + ++bmin; + if (bmax < dmax) + kvdb[++bmax + 1] = XDL_LINE_MAX; + else + --bmax; + + for (d = bmax; d >= bmin; d -= 2) { + if (kvdb[d - 1] < kvdb[d + 1]) + i1 = kvdb[d - 1]; + else + i1 = kvdb[d + 1] - 1; + prev1 = i1; + i2 = i1 - d; + for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); + if (prev1 - i1 > xenv->snake_cnt) + got_snake = 1; + kvdb[d] = i1; + if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + if (need_min) + continue; + + /* + * If the edit cost is above the heuristic trigger and if + * we got a good snake, we sample current diagonals to see + * if some of the, have reached an "interesting" path. Our + * measure is a function of the distance from the diagonal + * corner (i1 + i2) penalized with the distance from the + * mid diagonal itself. If this value is above the current + * edit cost times a magic factor (XDL_K_HEUR) we consider + * it interesting. + */ + if (got_snake && ec > xenv->heur_min) { + for (best = 0, d = fmax; d >= fmin; d -= 2) { + dd = d > fmid ? d - fmid: fmid - d; + i1 = kvdf[d]; + i2 = i1 - d; + v = (i1 - off1) + (i2 - off2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 + xenv->snake_cnt <= i1 && i1 < lim1 && + off2 + xenv->snake_cnt <= i2 && i2 < lim2) { + for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) + if (k == xenv->snake_cnt) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 1; + spl->min_hi = 0; + return ec; + } + + for (best = 0, d = bmax; d >= bmin; d -= 2) { + dd = d > bmid ? d - bmid: bmid - d; + i1 = kvdb[d]; + i2 = i1 - d; + v = (lim1 - i1) + (lim2 - i2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 < i1 && i1 <= lim1 - xenv->snake_cnt && + off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { + for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) + if (k == xenv->snake_cnt - 1) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 0; + spl->min_hi = 1; + return ec; + } + } + + /* + * Enough is enough. We spent too much time here and now we collect + * the furthest reaching path using the (i1 + i2) measure. + */ + if (ec >= xenv->mxcost) { + long fbest, fbest1, bbest, bbest1; + + fbest = fbest1 = -1; + for (d = fmax; d >= fmin; d -= 2) { + i1 = XDL_MIN(kvdf[d], lim1); + i2 = i1 - d; + if (lim2 < i2) + i1 = lim2 + d, i2 = lim2; + if (fbest < i1 + i2) { + fbest = i1 + i2; + fbest1 = i1; + } + } + + bbest = bbest1 = XDL_LINE_MAX; + for (d = bmax; d >= bmin; d -= 2) { + i1 = XDL_MAX(off1, kvdb[d]); + i2 = i1 - d; + if (i2 < off2) + i1 = off2 + d, i2 = off2; + if (i1 + i2 < bbest) { + bbest = i1 + i2; + bbest1 = i1; + } + } + + if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { + spl->i1 = fbest1; + spl->i2 = fbest - fbest1; + spl->min_lo = 1; + spl->min_hi = 0; + } else { + spl->i1 = bbest1; + spl->i2 = bbest - bbest1; + spl->min_lo = 0; + spl->min_hi = 1; + } + return ec; + } + } +} + + +/* + * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling + * the box splitting function. Note that the real job (marking changed lines) + * is done in the two boundary reaching checks. + */ +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { + unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; + + /* + * Shrink the box by walking through each diagonal snake (SW and NE). + */ + for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); + for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); + + /* + * If one dimension is empty, then all records on the other one must + * be obviously changed. + */ + if (off1 == lim1) { + char *rchg2 = dd2->rchg; + long *rindex2 = dd2->rindex; + + for (; off2 < lim2; off2++) + rchg2[rindex2[off2]] = 1; + } else if (off2 == lim2) { + char *rchg1 = dd1->rchg; + long *rindex1 = dd1->rindex; + + for (; off1 < lim1; off1++) + rchg1[rindex1[off1]] = 1; + } else { + xdpsplit_t spl; + spl.i1 = spl.i2 = 0; + + /* + * Divide ... + */ + if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, + need_min, &spl, xenv) < 0) { + + return -1; + } + + /* + * ... et Impera. + */ + if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, + kvdf, kvdb, spl.min_lo, xenv) < 0 || + xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, + kvdf, kvdb, spl.min_hi, xenv) < 0) { + + return -1; + } + } + + return 0; +} + + +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long ndiags; + long *kvd, *kvdf, *kvdb; + xdalgoenv_t xenv; + diffdata_t dd1, dd2; + + if (xpp->flags & XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + + if (xpp->flags & XDF_HISTOGRAM_DIFF) + return xdl_do_histogram_diff(mf1, mf2, xpp, xe); + + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { + + return -1; + } + + /* + * Allocate and setup K vectors to be used by the differential algorithm. + * One is to store the forward path and one to store the backward path. + */ + ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; + if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + + xdl_free_env(xe); + return -1; + } + kvdf = kvd; + kvdb = kvdf + ndiags; + kvdf += xe->xdf2.nreff + 1; + kvdb += xe->xdf2.nreff + 1; + + xenv.mxcost = xdl_bogosqrt(ndiags); + if (xenv.mxcost < XDL_MAX_COST_MIN) + xenv.mxcost = XDL_MAX_COST_MIN; + xenv.snake_cnt = XDL_SNAKE_CNT; + xenv.heur_min = XDL_HEUR_MIN_COST; + + dd1.nrec = xe->xdf1.nreff; + dd1.ha = xe->xdf1.ha; + dd1.rchg = xe->xdf1.rchg; + dd1.rindex = xe->xdf1.rindex; + dd2.nrec = xe->xdf2.nreff; + dd2.ha = xe->xdf2.ha; + dd2.rchg = xe->xdf2.rchg; + dd2.rindex = xe->xdf2.rindex; + + if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, + kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { + + xdl_free(kvd); + xdl_free_env(xe); + return -1; + } + + xdl_free(kvd); + + return 0; +} + + +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { + xdchange_t *xch; + + if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) + return NULL; + + xch->next = xscr; + xch->i1 = i1; + xch->i2 = i2; + xch->chg1 = chg1; + xch->chg2 = chg2; + + return xch; +} + + +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; + char *rchg = xdf->rchg, *rchgo = xdfo->rchg; + xrecord_t **recs = xdf->recs; + + /* + * This is the same of what GNU diff does. Move back and forward + * change groups for a consistent and pretty diff output. This also + * helps in finding joinable change groups and reduce the diff size. + */ + for (ix = ixo = 0;;) { + /* + * Find the first changed line in the to-be-compacted file. + * We need to keep track of both indexes, so if we find a + * changed lines group on the other file, while scanning the + * to-be-compacted file, we need to skip it properly. Note + * that loops that are testing for changed lines on rchg* do + * not need index bounding since the array is prepared with + * a zero at position -1 and N. + */ + for (; ix < nrec && !rchg[ix]; ix++) + while (rchgo[ixo++]); + if (ix == nrec) + break; + + /* + * Record the start of a changed-group in the to-be-compacted file + * and find the end of it, on both to-be-compacted and other file + * indexes (ix and ixo). + */ + ixs = ix; + for (ix++; rchg[ix]; ix++); + for (; rchgo[ixo]; ixo++); + + do { + grpsiz = ix - ixs; + + /* + * If the line before the current change group, is equal to + * the last line of the current change group, shift backward + * the group. + */ + while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha && + xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + + /* + * This change might have joined two change groups, + * so we try to take this scenario in account by moving + * the start index accordingly (and so the other-file + * end-of-group index). + */ + for (; rchg[ixs - 1]; ixs--); + while (rchgo[--ixo]); + } + + /* + * Record the end-of-group position in case we are matched + * with a group of changes in the other file (that is, the + * change record before the end-of-group index in the other + * file is set). + */ + ixref = rchgo[ixo - 1] ? ix: nrec; + + /* + * If the first line of the current change group, is equal to + * the line next of the current change group, shift forward + * the group. + */ + while (ix < nrec && recs[ixs]->ha == recs[ix]->ha && + xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) { + rchg[ixs++] = 0; + rchg[ix++] = 1; + + /* + * This change might have joined two change groups, + * so we try to take this scenario in account by moving + * the start index accordingly (and so the other-file + * end-of-group index). Keep tracking the reference + * index in case we are shifting together with a + * corresponding group of changes in the other file. + */ + for (; rchg[ix]; ix++); + while (rchgo[++ixo]) + ixref = ix; + } + } while (grpsiz != ix - ixs); + + /* + * Try to move back the possibly merged group of changes, to match + * the recorded postion in the other file. + */ + while (ixref < ix) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + while (rchgo[--ixo]); + } + } + + return 0; +} + + +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { + xdchange_t *cscr = NULL, *xch; + char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; + long i1, i2, l1, l2; + + /* + * Trivial. Collects "groups" of changes and creates an edit script. + */ + for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) + if (rchg1[i1 - 1] || rchg2[i2 - 1]) { + for (l1 = i1; rchg1[i1 - 1]; i1--); + for (l2 = i2; rchg2[i2 - 1]; i2--); + + if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { + xdl_free_script(cscr); + return -1; + } + cscr = xch; + } + + *xscr = cscr; + + return 0; +} + + +void xdl_free_script(xdchange_t *xscr) { + xdchange_t *xch; + + while ((xch = xscr) != NULL) { + xscr = xscr->next; + xdl_free(xch); + } +} + + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb) { + xdchange_t *xscr; + xdfenv_t xe; + emit_func_t ef = xecfg->emit_func ? + (emit_func_t)xecfg->emit_func : xdl_emit_diff; + + if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { + + return -1; + } + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + + xdl_free_env(&xe); + return -1; + } + if (xscr) { + if (ef(&xe, xscr, ecb, xecfg) < 0) { + + xdl_free_script(xscr); + xdl_free_env(&xe); + return -1; + } + xdl_free_script(xscr); + } + xdl_free_env(&xe); + + return 0; +} diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h new file mode 100644 index 000000000..7a92ea9c4 --- /dev/null +++ b/src/xdiff/xdiffi.h @@ -0,0 +1,63 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XDIFFI_H) +#define XDIFFI_H + + +typedef struct s_diffdata { + long nrec; + unsigned long const *ha; + long *rindex; + char *rchg; +} diffdata_t; + +typedef struct s_xdalgoenv { + long mxcost; + long snake_cnt; + long heur_min; +} xdalgoenv_t; + +typedef struct s_xdchange { + struct s_xdchange *next; + long i1, i2; + long chg1, chg2; +} xdchange_t; + + + +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); +void xdl_free_script(xdchange_t *xscr); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); +int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); +int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); + +#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c new file mode 100644 index 000000000..8b7d417a1 --- /dev/null +++ b/src/xdiff/xemit.c @@ -0,0 +1,253 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + + + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec); +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb); + + + + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { + + *rec = xdf->recs[ri]->ptr; + + return xdf->recs[ri]->size; +} + + +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { + long size, psize = strlen(pre); + char const *rec; + + size = xdl_get_rec(xdf, ri, &rec); + if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { + + return -1; + } + + return 0; +} + + +/* + * Starting at the passed change atom, find the latest change atom to be included + * inside the differential hunk according to the specified configuration. + */ +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { + xdchange_t *xch, *xchp; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + + for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) + if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) + break; + + return xchp; +} + + +static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) +{ + (void)priv; + + if (len > 0 && + (isalpha((unsigned char)*rec) || /* identifier? */ + *rec == '_' || /* also identifier? */ + *rec == '$')) { /* identifiers from VMS and other esoterico */ + if (len > sz) + len = sz; + while (0 < len && isspace((unsigned char)rec[len - 1])) + len--; + memcpy(buf, rec, len); + return len; + } + return -1; +} + +static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + xdfile_t *xdf = &xe->xdf2; + const char *rchg = xdf->rchg; + long ix; + + (void)xscr; + (void)xecfg; + + for (ix = 0; ix < xdf->nrec; ix++) { + if (rchg[ix]) + continue; + if (xdl_emit_record(xdf, ix, "", ecb)) + return -1; + } + return 0; +} + +struct func_line { + long len; + char buf[80]; +}; + +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, + struct func_line *func_line, long start, long limit) +{ + find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; + long l, size, step = (start > limit) ? -1 : 1; + char *buf, dummy[1]; + + buf = func_line ? func_line->buf : dummy; + size = func_line ? sizeof(func_line->buf) : sizeof(dummy); + + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + const char *rec; + long reclen = xdl_get_rec(&xe->xdf1, l, &rec); + long len = ff(rec, reclen, buf, size, xecfg->find_func_priv); + if (len >= 0) { + if (func_line) + func_line->len = len; + return l; + } + } + return -1; +} + +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + long s1, s2, e1, e2, lctx; + xdchange_t *xch, *xche; + long funclineprev = -1; + struct func_line func_line = { 0 }; + + if (xecfg->flags & XDL_EMIT_COMMON) + return xdl_emit_common(xe, xscr, ecb, xecfg); + + for (xch = xscr; xch; xch = xche->next) { + xche = xdl_get_hunk(xch, xecfg); + + s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); + s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1); + if (fs1 < 0) + fs1 = 0; + if (fs1 < s1) { + s2 -= s1 - fs1; + s1 = fs1; + } + } + + again: + lctx = xecfg->ctxlen; + lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); + lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); + + e1 = xche->i1 + xche->chg1 + lctx; + e2 = xche->i2 + xche->chg2 + lctx; + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fe1 = get_func_line(xe, xecfg, NULL, + xche->i1 + xche->chg1, + xe->xdf1.nrec); + if (fe1 < 0) + fe1 = xe->xdf1.nrec; + if (fe1 > e1) { + e2 += fe1 - e1; + e1 = fe1; + } + + /* + * Overlap with next change? Then include it + * in the current hunk and start over to find + * its new end. + */ + if (xche->next) { + long l = xche->next->i1; + if (l <= e1 || + get_func_line(xe, xecfg, NULL, l, e1) < 0) { + xche = xche->next; + goto again; + } + } + } + + /* + * Emit current hunk header. + */ + + if (xecfg->flags & XDL_EMIT_FUNCNAMES) { + get_func_line(xe, xecfg, &func_line, + s1 - 1, funclineprev); + funclineprev = s1 - 1; + } + if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, + func_line.buf, func_line.len, ecb) < 0) + return -1; + + /* + * Emit pre-context. + */ + for (; s2 < xch->i2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { + /* + * Merge previous with current change atom. + */ + for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + /* + * Removes lines from the first file. + */ + for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) + if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) + return -1; + + /* + * Adds lines from the second file. + */ + for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) + return -1; + + if (xch == xche) + break; + s1 = xch->i1 + xch->chg1; + s2 = xch->i2 + xch->chg2; + } + + /* + * Emit post-context. + */ + for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + } + + return 0; +} diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h new file mode 100644 index 000000000..c2e2e8302 --- /dev/null +++ b/src/xdiff/xemit.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XEMIT_H) +#define XEMIT_H + + +typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + + + +#endif /* #if !defined(XEMIT_H) */ diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c new file mode 100644 index 000000000..5d101754d --- /dev/null +++ b/src/xdiff/xhistogram.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in JGit's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +#define MAX_PTR UINT_MAX +#define MAX_CNT UINT_MAX + +#define LINE_END(n) (line##n + count##n - 1) +#define LINE_END_PTR(n) (*line##n + *count##n - 1) + +struct histindex { + struct record { + unsigned int ptr, cnt; + struct record *next; + } **records, /* an ocurrence */ + **line_map; /* map of line to record chain */ + chastore_t rcha; + unsigned int *next_ptrs; + unsigned int table_bits, + records_size, + line_map_size; + + unsigned int max_chain_length, + key_shift, + ptr_shift; + + unsigned int cnt, + has_common; + + xdfenv_t *env; + xpparam_t const *xpp; +}; + +struct region { + unsigned int begin1, end1; + unsigned int begin2, end2; +}; + +#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) + +#define NEXT_PTR(index, ptr) \ + (index->next_ptrs[(ptr) - index->ptr_shift]) + +#define CNT(index, ptr) \ + ((LINE_MAP(index, ptr))->cnt) + +#define REC(env, s, l) \ + (env->xdf##s.recs[l - 1]) + +static int cmp_recs(xpparam_t const *xpp, + xrecord_t *r1, xrecord_t *r2) +{ + return r1->ha == r2->ha && + xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size, + xpp->flags); +} + +#define CMP_ENV(xpp, env, s1, l1, s2, l2) \ + (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2))) + +#define CMP(i, s1, l1, s2, l2) \ + (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2))) + +#define TABLE_HASH(index, side, line) \ + XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) + +static int scanA(struct histindex *index, unsigned int line1, unsigned int count1) +{ + unsigned int ptr; + unsigned int tbl_idx; + unsigned int chain_len; + struct record **rec_chain, *rec; + + for (ptr = LINE_END(1); line1 <= ptr; ptr--) { + tbl_idx = TABLE_HASH(index, 1, ptr); + rec_chain = index->records + tbl_idx; + rec = *rec_chain; + + chain_len = 0; + while (rec) { + if (CMP(index, 1, rec->ptr, 1, ptr)) { + /* + * ptr is identical to another element. Insert + * it onto the front of the existing element + * chain. + */ + NEXT_PTR(index, ptr) = rec->ptr; + rec->ptr = ptr; + /* cap rec->cnt at MAX_CNT */ + rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); + LINE_MAP(index, ptr) = rec; + goto continue_scan; + } + + rec = rec->next; + chain_len++; + } + + if (chain_len == index->max_chain_length) + return -1; + + /* + * This is the first time we have ever seen this particular + * element in the sequence. Construct a new chain for it. + */ + if (!(rec = xdl_cha_alloc(&index->rcha))) + return -1; + rec->ptr = ptr; + rec->cnt = 1; + rec->next = *rec_chain; + *rec_chain = rec; + LINE_MAP(index, ptr) = rec; + +continue_scan: + ; /* no op */ + } + + return 0; +} + +static int try_lcs( + struct histindex *index, struct region *lcs, unsigned int b_ptr, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + unsigned int b_next = b_ptr + 1; + struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; + unsigned int as, ae, bs, be, np, rc; + int should_break; + + for (; rec; rec = rec->next) { + if (rec->cnt > index->cnt) { + if (!index->has_common) + index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); + continue; + } + + as = rec->ptr; + if (!CMP(index, 1, as, 2, b_ptr)) + continue; + + index->has_common = 1; + for (;;) { + should_break = 0; + np = NEXT_PTR(index, as); + bs = b_ptr; + ae = as; + be = bs; + rc = rec->cnt; + + while (line1 < as && line2 < bs + && CMP(index, 1, as - 1, 2, bs - 1)) { + as--; + bs--; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, as)); + } + while (ae < LINE_END(1) && be < LINE_END(2) + && CMP(index, 1, ae + 1, 2, be + 1)) { + ae++; + be++; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, ae)); + } + + if (b_next <= be) + b_next = be + 1; + if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { + lcs->begin1 = as; + lcs->begin2 = bs; + lcs->end1 = ae; + lcs->end2 = be; + index->cnt = rc; + } + + if (np == 0) + break; + + while (np <= ae) { + np = NEXT_PTR(index, np); + if (np == 0) { + should_break = 1; + break; + } + } + + if (should_break) + break; + + as = np; + } + } + return b_next; +} + +static int find_lcs( + struct histindex *index, struct region *lcs, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + unsigned int b_ptr; + + if (scanA(index, line1, count1)) + return -1; + + index->cnt = index->max_chain_length + 1; + + for (b_ptr = line2; b_ptr <= LINE_END(2); ) + b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2); + + return index->has_common && index->max_chain_length < index->cnt; +} + +static int fall_back_to_classic_diff(struct histindex *index, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + xpp.flags = index->xpp->flags & ~XDF_HISTOGRAM_DIFF; + + return xdl_fall_back_diff(index->env, &xpp, + line1, count1, line2, count2); +} + +static int histogram_diff( + xpparam_t const *xpp, xdfenv_t *env, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + struct histindex index; + struct region lcs; + unsigned int sz; + int result = -1; + + if (count1 <= 0 && count2 <= 0) + return 0; + + if (LINE_END(1) >= MAX_PTR) + return -1; + + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&index, 0, sizeof(index)); + + index.env = env; + index.xpp = xpp; + + index.records = NULL; + index.line_map = NULL; + /* in case of early xdl_cha_free() */ + index.rcha.head = NULL; + + index.table_bits = xdl_hashbits(count1); + sz = index.records_size = 1 << index.table_bits; + sz *= sizeof(struct record *); + if (!(index.records = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.records, 0, sz); + + sz = index.line_map_size = count1; + sz *= sizeof(struct record *); + if (!(index.line_map = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.line_map, 0, sz); + + sz = index.line_map_size; + sz *= sizeof(unsigned int); + if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) + goto cleanup; + memset(index.next_ptrs, 0, sz); + + /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ + if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) + goto cleanup; + + index.ptr_shift = line1; + index.max_chain_length = 64; + + memset(&lcs, 0, sizeof(lcs)); + if (find_lcs(&index, &lcs, line1, count1, line2, count2)) + result = fall_back_to_classic_diff(&index, line1, count1, line2, count2); + else { + if (lcs.begin1 == 0 && lcs.begin2 == 0) { + while (count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while (count2--) + env->xdf2.rchg[line2++ - 1] = 1; + result = 0; + } else { + result = histogram_diff(xpp, env, + line1, lcs.begin1 - line1, + line2, lcs.begin2 - line2); + if (result) + goto cleanup; + result = histogram_diff(xpp, env, + lcs.end1 + 1, LINE_END(1) - lcs.end1, + lcs.end2 + 1, LINE_END(2) - lcs.end2); + if (result) + goto cleanup; + } + } + +cleanup: + xdl_free(index.records); + xdl_free(index.line_map); + xdl_free(index.next_ptrs); + xdl_cha_free(&index.rcha); + + return result; +} + +int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + return histogram_diff(xpp, env, + env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, + env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); +} diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h new file mode 100644 index 000000000..2928d329b --- /dev/null +++ b/src/xdiff/xinclude.h @@ -0,0 +1,46 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XINCLUDE_H) +#define XINCLUDE_H + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#ifdef WIN32 +#else +#include <unistd.h> +#endif + +#include "xmacros.h" +#include "xdiff.h" +#include "xtypes.h" +#include "xutils.h" +#include "xprepare.h" +#include "xdiffi.h" +#include "xemit.h" + + +#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h new file mode 100644 index 000000000..165a895a9 --- /dev/null +++ b/src/xdiff/xmacros.h @@ -0,0 +1,54 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XMACROS_H) +#define XMACROS_H + + + + +#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) +#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) +#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) +#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') +#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) +#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) +#define XDL_MASKBITS(b) ((1UL << (b)) - 1) +#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) +#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) +#define XDL_LE32_PUT(p, v) \ +do { \ + unsigned char *__p = (unsigned char *) (p); \ + *__p++ = (unsigned char) (v); \ + *__p++ = (unsigned char) ((v) >> 8); \ + *__p++ = (unsigned char) ((v) >> 16); \ + *__p = (unsigned char) ((v) >> 24); \ +} while (0) +#define XDL_LE32_GET(p, v) \ +do { \ + unsigned char const *__p = (unsigned char const *) (p); \ + (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ + ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ +} while (0) + + +#endif /* #if !defined(XMACROS_H) */ diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c new file mode 100644 index 000000000..9e13b25ab --- /dev/null +++ b/src/xdiff/xmerge.c @@ -0,0 +1,619 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + * 3 = no conflict, take both. + */ + int mode; + /* + * These point at the respective postimages. E.g. <i1,chg1> is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ + long i1, i2; + long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs; + int size = 0; + + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); +} + +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *name3, + int size, int i, int style, + xdmerge_t *m, char *dest, int marker_size) +{ + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int marker3_size = (name3 ? strlen(name3) + 1 : 0); + + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + marker1_size; + } else { + memset(dest + size, '<', marker_size); + size += marker_size; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1 + marker3_size; + } else { + memset(dest + size, '|', marker_size); + size += marker_size; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, + dest ? dest + size : NULL); + } + + if (!dest) { + size += marker_size + 1; + } else { + memset(dest + size, '=', marker_size); + size += marker_size; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + marker2_size; + } else { + memset(dest + size, '>', marker_size); + size += marker_size; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *ancestor_name, + int favor, + xdmerge_t *m, char *dest, int style, + int marker_size) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, + size, i, style, m, dest, + marker_size); + else if (m->mode & 3) { + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + /* Postimage from side #1 */ + if (m->mode & 1) + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + /* Postimage from side #2 */ + if (m->mode & 2) + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + } else + continue; + i = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* no sense refining a conflict when one side is empty */ + if (m->chg1 == 0 || m->chg2 == 0) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, the changes are identical. */ + xdl_free_env(&xe); + m->mode = 4; + continue; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +static int line_contains_alnum(const char *ptr, long size) +{ + while (size--) + if (isalnum((unsigned char)*(ptr++))) + return 1; + return 0; +} + +static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) +{ + for (; chg; chg--, i++) + if (line_contains_alnum(xe->xdf2.recs[i]->ptr, + xe->xdf2.recs[i]->size)) + return 1; + return 0; +} + +/* + * This function merges m and m->next, marking everything between those hunks + * as conflicting, too. + */ +static void xdl_merge_two_conflicts(xdmerge_t *m) +{ + xdmerge_t *next_m = m->next; + m->chg1 = next_m->i1 + next_m->chg1 - m->i1; + m->chg2 = next_m->i2 + next_m->chg2 - m->i2; + m->next = next_m->next; + free(next_m); +} + +/* + * If there are less than 3 non-conflicting lines between conflicts, + * it appears simpler -- because it takes up less (or as many) lines -- + * if the lines are moved into the conflicts. + */ +static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, + int simplify_if_no_alnum) +{ + int result = 0; + + if (!m) + return result; + for (;;) { + xdmerge_t *next_m = m->next; + int begin, end; + + if (!next_m) + return result; + + begin = m->i1 + m->chg1; + end = next_m->i1; + + if (m->mode != 0 || next_m->mode != 0 || + (end - begin > 3 && + (!simplify_if_no_alnum || + lines_contain_alnum(xe1, begin, end - begin)))) { + m = next_m; + } else { + result++; + xdl_merge_two_conflicts(m); + } + } +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * level == 3: analyze non-identical changes for minimal conflict set, but + * treat hunks not containing any letter or number as conflicting + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdmerge_t *changes, *c; + xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; + int i0, i1, i2, chg0, chg1, chg2; + int level = xmp->level; + int style = xmp->style; + int favor = xmp->favor; + + if (style == XDL_MERGE_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) { + i0 -= off; + i1 -= off; + } + else + i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo < 0) { + chg0 -= ffo; + chg1 -= ffo; + } else + chg2 += ffo; + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i0 = xscr2->i1; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (XDL_MERGE_ZEALOUS <= level && + (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int marker_size = xmp->marker_size; + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, + favor, changes, NULL, style, + marker_size); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, + result->ptr, style, marker_size); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + xpparam_t const *xpp = &xmp->xpp; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, + xmp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c new file mode 100644 index 000000000..fdd7d0263 --- /dev/null +++ b/src/xdiff/xpatience.c @@ -0,0 +1,358 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +/* + * The basic idea of patience diff is to find lines that are unique in + * both files. These are intuitively the ones that we want to see as + * common lines. + * + * The maximal ordered sequence of such line pairs (where ordered means + * that the order in the sequence agrees with the order of the lines in + * both files) naturally defines an initial set of common lines. + * + * Now, the algorithm tries to extend the set of common lines by growing + * the line ranges where the files have identical lines. + * + * Between those common lines, the patience diff algorithm is applied + * recursively, until no unique line pairs can be found; these line ranges + * are handled by the well-known Myers algorithm. + */ + +#define NON_UNIQUE ULONG_MAX + +/* + * This is a hash mapping from line hash to line numbers in the first and + * second file. + */ +struct hashmap { + int nr, alloc; + struct entry { + unsigned long hash; + /* + * 0 = unused entry, 1 = first line, 2 = second, etc. + * line2 is NON_UNIQUE if the line is not unique + * in either the first or the second file. + */ + unsigned long line1, line2; + /* + * "next" & "previous" are used for the longest common + * sequence; + * initially, "next" reflects only the order in file1. + */ + struct entry *next, *previous; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(int line, struct hashmap *map, int pass) +{ + xrecord_t **records = pass == 1 ? + map->env->xdf1.recs : map->env->xdf2.recs; + xrecord_t *record = records[line - 1], *other; + /* + * After xdl_prepare_env() (or more precisely, due to + * xdl_classify_record()), the "ha" member of the records (AKA lines) + * is _not_ the hash anymore, but a linearized version of it. In + * other words, the "ha" member is guaranteed to start with 0 and + * the second record's ha can only be 0 or 1, etc. + * + * So we multiply ha by 2 in the hope that the hashing was + * "unique enough". + */ + int index = (int)((record->ha << 1) % map->alloc); + + while (map->entries[index].line1) { + other = map->env->xdf1.recs[map->entries[index].line1 - 1]; + if (map->entries[index].hash != record->ha || + !xdl_recmatch(record->ptr, record->size, + other->ptr, other->size, + map->xpp->flags)) { + if (++index >= map->alloc) + index = 0; + continue; + } + if (pass == 2) + map->has_matches = 1; + if (pass == 1 || map->entries[index].line2) + map->entries[index].line2 = NON_UNIQUE; + else + map->entries[index].line2 = line; + return; + } + if (pass == 2) + return; + map->entries[index].line1 = line; + map->entries[index].hash = record->ha; + if (!map->first) + map->first = map->entries + index; + if (map->last) { + map->last->next = map->entries + index; + map->entries[index].previous = map->last; + } + map->last = map->entries + index; + map->nr++; +} + +/* + * This function has to be called for each recursion into the inter-hunk + * parts, as previously non-unique lines can become unique when being + * restricted to a smaller part of the files. + * + * It is assumed that env has been prepared using xdl_prepare(). + */ +static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + struct hashmap *result, + int line1, int count1, int line2, int count2) +{ + result->file1 = file1; + result->file2 = file2; + result->xpp = xpp; + result->env = env; + + /* We know exactly how large we want the hash map */ + result->alloc = count1 * 2; + result->entries = (struct entry *) + xdl_malloc(result->alloc * sizeof(struct entry)); + if (!result->entries) + return -1; + memset(result->entries, 0, result->alloc * sizeof(struct entry)); + + /* First, fill with entries from the first file */ + while (count1--) + insert_record(line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(line2++, result, 2); + + return 0; +} + +/* + * Find the longest sequence with a smaller last element (meaning a smaller + * line2, as we construct the sequence with entries ordered by line1). + */ +static int binary_search(struct entry **sequence, int longest, + struct entry *entry) +{ + int left = -1, right = longest; + + while (left + 1 < right) { + int middle = (left + right) / 2; + /* by construction, no two entries can be equal */ + if (sequence[middle]->line2 > entry->line2) + right = middle; + else + left = middle; + } + /* return the index in "sequence", _not_ the sequence length */ + return left; +} + +/* + * The idea is to start with the list of common unique lines sorted by + * the order in file1. For each of these pairs, the longest (partial) + * sequence whose last element's line2 is smaller is determined. + * + * For efficiency, the sequences are kept in a list containing exactly one + * item per sequence length: the sequence with the smallest last + * element (in terms of line2). + */ +static struct entry *find_longest_common_sequence(struct hashmap *map) +{ + struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); + int longest = 0, i; + struct entry *entry; + + for (entry = map->first; entry; entry = entry->next) { + if (!entry->line2 || entry->line2 == NON_UNIQUE) + continue; + i = binary_search(sequence, longest, entry); + entry->previous = i < 0 ? NULL : sequence[i]; + sequence[++i] = entry; + if (i == longest) + longest++; + } + + /* No common unique lines were found */ + if (!longest) { + xdl_free(sequence); + return NULL; + } + + /* Iterate starting at the last element, adjusting the "next" members */ + entry = sequence[longest - 1]; + entry->next = NULL; + while (entry->previous) { + entry->previous->next = entry; + entry = entry->previous; + } + xdl_free(sequence); + return entry; +} + +static int match(struct hashmap *map, int line1, int line2) +{ + xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; + xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; + return xdl_recmatch(record1->ptr, record1->size, + record2->ptr, record2->size, map->xpp->flags); +} + +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2); + +static int walk_common_sequence(struct hashmap *map, struct entry *first, + int line1, int count1, int line2, int count2) +{ + int end1 = line1 + count1, end2 = line2 + count2; + int next1, next2; + + for (;;) { + /* Try to grow the line ranges of common lines */ + if (first) { + next1 = first->line1; + next2 = first->line2; + while (next1 > line1 && next2 > line2 && + match(map, next1 - 1, next2 - 1)) { + next1--; + next2--; + } + } else { + next1 = end1; + next2 = end2; + } + while (line1 < next1 && line2 < next2 && + match(map, line1, line2)) { + line1++; + line2++; + } + + /* Recurse */ + if (next1 > line1 || next2 > line2) { + struct hashmap submap; + + memset(&submap, 0, sizeof(submap)); + if (patience_diff(map->file1, map->file2, + map->xpp, map->env, + line1, next1 - line1, + line2, next2 - line2)) + return -1; + } + + if (!first) + return 0; + + while (first->next && + first->next->line1 == first->line1 + 1 && + first->next->line2 == first->line2 + 1) + first = first->next; + + line1 = first->line1 + 1; + line2 = first->line2 + 1; + + first = first->next; + } +} + +static int fall_back_to_classic_diff(struct hashmap *map, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF; + + return xdl_fall_back_diff(map->env, &xpp, + line1, count1, line2, count2); +} + +/* + * Recursively find the longest common sequence of unique lines, + * and if none was found, ask xdl_do_diff() to do the job. + * + * This function assumes that env was prepared with xdl_prepare_env(). + */ +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct hashmap map; + struct entry *first; + int result = 0; + + /* trivial case: one side is empty */ + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&map, 0, sizeof(map)); + if (fill_hashmap(file1, file2, xpp, env, &map, + line1, count1, line2, count2)) + return -1; + + /* are there any matching lines at all? */ + if (!map.has_matches) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + xdl_free(map.entries); + return 0; + } + + first = find_longest_common_sequence(&map); + if (first) + result = walk_common_sequence(&map, first, + line1, count1, line2, count2); + else + result = fall_back_to_classic_diff(&map, + line1, count1, line2, count2); + + xdl_free(map.entries); + return result; +} + +int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + /* environment is cleaned up in xdl_diff() */ + return patience_diff(file1, file2, xpp, env, + 1, env->xdf1.nrec, 1, env->xdf2.nrec); +} diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c new file mode 100644 index 000000000..e419f4f72 --- /dev/null +++ b/src/xdiff/xprepare.c @@ -0,0 +1,483 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + +#define XDL_KPDIS_RUN 4 +#define XDL_MAX_EQLIMIT 1024 +#define XDL_SIMSCAN_WINDOW 100 +#define XDL_GUESS_NLINES1 256 +#define XDL_GUESS_NLINES2 20 + + +typedef struct s_xdlclass { + struct s_xdlclass *next; + unsigned long ha; + char const *line; + long size; + long idx; + long len1, len2; +} xdlclass_t; + +typedef struct s_xdlclassifier { + unsigned int hbits; + long hsize; + xdlclass_t **rchash; + chastore_t ncha; + xdlclass_t **rcrecs; + long alloc; + long count; + long flags; +} xdlclassifier_t; + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); +static void xdl_free_classifier(xdlclassifier_t *cf); +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec); +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf); +static void xdl_free_ctx(xdfile_t *xdf); +static int xdl_clean_mmatch(char const *dis, long i, long s, long e); +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { + cf->flags = flags; + + cf->hbits = xdl_hashbits((unsigned int) size); + cf->hsize = 1 << cf->hbits; + + if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { + + return -1; + } + if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { + + xdl_cha_free(&cf->ncha); + return -1; + } + memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); + + cf->alloc = size; + if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { + + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); + return -1; + } + + cf->count = 0; + + return 0; +} + + +static void xdl_free_classifier(xdlclassifier_t *cf) { + + xdl_free(cf->rcrecs); + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); +} + + +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec) { + long hi; + char const *line; + xdlclass_t *rcrec; + xdlclass_t **rcrecs; + + line = rec->ptr; + hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); + for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) + if (rcrec->ha == rec->ha && + xdl_recmatch(rcrec->line, rcrec->size, + rec->ptr, rec->size, cf->flags)) + break; + + if (!rcrec) { + if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { + + return -1; + } + rcrec->idx = cf->count++; + if (cf->count > cf->alloc) { + cf->alloc *= 2; + if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { + + return -1; + } + cf->rcrecs = rcrecs; + } + cf->rcrecs[rcrec->idx] = rcrec; + rcrec->line = line; + rcrec->size = rec->size; + rcrec->ha = rec->ha; + rcrec->len1 = rcrec->len2 = 0; + rcrec->next = cf->rchash[hi]; + cf->rchash[hi] = rcrec; + } + + (pass == 1) ? rcrec->len1++ : rcrec->len2++; + + rec->ha = (unsigned long) rcrec->idx; + + hi = (long) XDL_HASHLONG(rec->ha, hbits); + rec->next = rhash[hi]; + rhash[hi] = rec; + + return 0; +} + + +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf) { + unsigned int hbits; + long nrec, hsize, bsize; + unsigned long hav; + char const *blk, *cur, *top, *prev; + xrecord_t *crec; + xrecord_t **recs, **rrecs; + xrecord_t **rhash; + unsigned long *ha; + char *rchg; + long *rindex; + + ha = NULL; + rindex = NULL; + rchg = NULL; + rhash = NULL; + recs = NULL; + + if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) + goto abort; + if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) + goto abort; + + if (xpp->flags & XDF_HISTOGRAM_DIFF) + hbits = hsize = 0; + else { + hbits = xdl_hashbits((unsigned int) narec); + hsize = 1 << hbits; + if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) + goto abort; + memset(rhash, 0, hsize * sizeof(xrecord_t *)); + } + + nrec = 0; + if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { + for (top = blk + bsize; cur < top; ) { + prev = cur; + hav = xdl_hash_record(&cur, top, xpp->flags); + if (nrec >= narec) { + narec *= 2; + if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) + goto abort; + recs = rrecs; + } + if (!(crec = xdl_cha_alloc(&xdf->rcha))) + goto abort; + crec->ptr = prev; + crec->size = (long) (cur - prev); + crec->ha = hav; + recs[nrec++] = crec; + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) + goto abort; + } + } + + if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) + goto abort; + memset(rchg, 0, (nrec + 2) * sizeof(char)); + + if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long)))) + goto abort; + if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long)))) + goto abort; + + xdf->nrec = nrec; + xdf->recs = recs; + xdf->hbits = hbits; + xdf->rhash = rhash; + xdf->rchg = rchg + 1; + xdf->rindex = rindex; + xdf->nreff = 0; + xdf->ha = ha; + xdf->dstart = 0; + xdf->dend = nrec - 1; + + return 0; + +abort: + xdl_free(ha); + xdl_free(rindex); + xdl_free(rchg); + xdl_free(rhash); + xdl_free(recs); + xdl_cha_free(&xdf->rcha); + return -1; +} + + +static void xdl_free_ctx(xdfile_t *xdf) { + + xdl_free(xdf->rhash); + xdl_free(xdf->rindex); + xdl_free(xdf->rchg - 1); + xdl_free(xdf->ha); + xdl_free(xdf->recs); + xdl_cha_free(&xdf->rcha); +} + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long enl1, enl2, sample; + xdlclassifier_t cf; + + memset(&cf, 0, sizeof(cf)); + + /* + * For histogram diff, we can afford a smaller sample size and + * thus a poorer estimate of the number of lines, as the hash + * table (rhash) won't be filled up/grown. The number of lines + * (nrecs) will be updated correctly anyway by + * xdl_prepare_ctx(). + */ + sample = xpp->flags & XDF_HISTOGRAM_DIFF ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1; + + enl1 = xdl_guess_lines(mf1, sample) + 1; + enl2 = xdl_guess_lines(mf2, sample) + 1; + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) { + + return -1; + } + + if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { + + xdl_free_classifier(&cf); + return -1; + } + if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + if (!(xpp->flags & XDF_PATIENCE_DIFF) && + !(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); + return -1; + } + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF)) + xdl_free_classifier(&cf); + + return 0; +} + + +void xdl_free_env(xdfenv_t *xe) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); +} + + +static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { + long r, rdis0, rpdis0, rdis1, rpdis1; + + /* + * Limits the window the is examined during the similar-lines + * scan. The loops below stops when dis[i - r] == 1 (line that + * has no match), but there are corner cases where the loop + * proceed all the way to the extremities by causing huge + * performance penalties in case of big files. + */ + if (i - s > XDL_SIMSCAN_WINDOW) + s = i - XDL_SIMSCAN_WINDOW; + if (e - i > XDL_SIMSCAN_WINDOW) + e = i + XDL_SIMSCAN_WINDOW; + + /* + * Scans the lines before 'i' to find a run of lines that either + * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). + * Note that we always call this function with dis[i] > 1, so the + * current line (i) is already a multimatch line. + */ + for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { + if (!dis[i - r]) + rdis0++; + else if (dis[i - r] == 2) + rpdis0++; + else + break; + } + /* + * If the run before the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + * We want to discard multimatch lines only when they appear in the + * middle of runs with nomatch lines (dis[j] == 0). + */ + if (rdis0 == 0) + return 0; + for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { + if (!dis[i + r]) + rdis1++; + else if (dis[i + r] == 2) + rpdis1++; + else + break; + } + /* + * If the run after the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + */ + if (rdis1 == 0) + return 0; + rdis1 += rdis0; + rpdis1 += rpdis0; + + return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); +} + + +/* + * Try to reduce the problem complexity, discard records that have no + * matches on the other file. Also, lines that have multiple matches + * might be potentially discarded if they happear in a run of discardable. + */ +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + long i, nm, nreff, mlim; + xrecord_t **recs; + xdlclass_t *rcrec; + char *dis, *dis1, *dis2; + + if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { + + return -1; + } + memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); + dis1 = dis; + dis2 = dis1 + xdf1->nrec + 1; + + if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len2 : 0; + dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len1 : 0; + dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; + i <= xdf1->dend; i++, recs++) { + if (dis1[i] == 1 || + (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { + xdf1->rindex[nreff] = i; + xdf1->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf1->rchg[i] = 1; + } + xdf1->nreff = nreff; + + for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; + i <= xdf2->dend; i++, recs++) { + if (dis2[i] == 1 || + (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { + xdf2->rindex[nreff] = i; + xdf2->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf2->rchg[i] = 1; + } + xdf2->nreff = nreff; + + xdl_free(dis); + + return 0; +} + + +/* + * Early trim initial and terminal matching records. + */ +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { + long i, lim; + xrecord_t **recs1, **recs2; + + recs1 = xdf1->recs; + recs2 = xdf2->recs; + for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; + i++, recs1++, recs2++) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dstart = xdf2->dstart = i; + + recs1 = xdf1->recs + xdf1->nrec - 1; + recs2 = xdf2->recs + xdf2->nrec - 1; + for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dend = xdf1->nrec - i - 1; + xdf2->dend = xdf2->nrec - i - 1; + + return 0; +} + + +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + + if (xdl_trim_ends(xdf1, xdf2) < 0 || + xdl_cleanup_records(cf, xdf1, xdf2) < 0) { + + return -1; + } + + return 0; +} diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h new file mode 100644 index 000000000..8fb06a537 --- /dev/null +++ b/src/xdiff/xprepare.h @@ -0,0 +1,34 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XPREPARE_H) +#define XPREPARE_H + + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +void xdl_free_env(xdfenv_t *xe); + + + +#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h new file mode 100644 index 000000000..2511aef8d --- /dev/null +++ b/src/xdiff/xtypes.h @@ -0,0 +1,67 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XTYPES_H) +#define XTYPES_H + + + +typedef struct s_chanode { + struct s_chanode *next; + long icurr; +} chanode_t; + +typedef struct s_chastore { + chanode_t *head, *tail; + long isize, nsize; + chanode_t *ancur; + chanode_t *sncur; + long scurr; +} chastore_t; + +typedef struct s_xrecord { + struct s_xrecord *next; + char const *ptr; + long size; + unsigned long ha; +} xrecord_t; + +typedef struct s_xdfile { + chastore_t rcha; + long nrec; + unsigned int hbits; + xrecord_t **rhash; + long dstart, dend; + xrecord_t **recs; + char *rchg; + long *rindex; + long nreff; + unsigned long *ha; +} xdfile_t; + +typedef struct s_xdfenv { + xdfile_t xdf1, xdf2; +} xdfenv_t; + + + +#endif /* #if !defined(XTYPES_H) */ diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c new file mode 100644 index 000000000..9dea04bba --- /dev/null +++ b/src/xdiff/xutils.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + + + +long xdl_bogosqrt(long n) { + long i; + + /* + * Classical integer square root approximation using shifts. + */ + for (i = 1; n > 0; n >>= 2) + i <<= 1; + + return i; +} + + +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb) { + int i = 2; + mmbuffer_t mb[3]; + + mb[0].ptr = (char *) pre; + mb[0].size = psize; + mb[1].ptr = (char *) rec; + mb[1].size = size; + if (size > 0 && rec[size - 1] != '\n') { + mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; + mb[2].size = strlen(mb[2].ptr); + i++; + } + if (ecb->outf(ecb->priv, mb, i) < 0) { + + return -1; + } + + return 0; +} + +void *xdl_mmfile_first(mmfile_t *mmf, long *size) +{ + *size = mmf->size; + return mmf->ptr; +} + + +long xdl_mmfile_size(mmfile_t *mmf) +{ + return mmf->size; +} + + +int xdl_cha_init(chastore_t *cha, long isize, long icount) { + + cha->head = cha->tail = NULL; + cha->isize = isize; + cha->nsize = icount * isize; + cha->ancur = cha->sncur = NULL; + cha->scurr = 0; + + return 0; +} + + +void xdl_cha_free(chastore_t *cha) { + chanode_t *cur, *tmp; + + for (cur = cha->head; (tmp = cur) != NULL;) { + cur = cur->next; + xdl_free(tmp); + } +} + + +void *xdl_cha_alloc(chastore_t *cha) { + chanode_t *ancur; + void *data; + + if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { + if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { + + return NULL; + } + ancur->icurr = 0; + ancur->next = NULL; + if (cha->tail) + cha->tail->next = ancur; + if (!cha->head) + cha->head = ancur; + cha->tail = ancur; + cha->ancur = ancur; + } + + data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; + ancur->icurr += cha->isize; + + return data; +} + + +void *xdl_cha_first(chastore_t *cha) { + chanode_t *sncur; + + if (!(cha->sncur = sncur = cha->head)) + return NULL; + + cha->scurr = 0; + + return (char *) sncur + sizeof(chanode_t) + cha->scurr; +} + + +void *xdl_cha_next(chastore_t *cha) { + chanode_t *sncur; + + if (!(sncur = cha->sncur)) + return NULL; + cha->scurr += cha->isize; + if (cha->scurr == sncur->icurr) { + if (!(sncur = cha->sncur = sncur->next)) + return NULL; + cha->scurr = 0; + } + + return (char *) sncur + sizeof(chanode_t) + cha->scurr; +} + + +long xdl_guess_lines(mmfile_t *mf, long sample) { + long nl = 0, size, tsize = 0; + char const *data, *cur, *top; + + if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { + for (top = data + size; nl < sample && cur < top; ) { + nl++; + if (!(cur = memchr(cur, '\n', top - cur))) + cur = top; + else + cur++; + } + tsize += (long) (cur - data); + } + + if (nl && tsize) + nl = xdl_mmfile_size(mf) / (tsize / nl); + + return nl + 1; +} + +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) +{ + int i1, i2; + + if (s1 == s2 && !memcmp(l1, l2, s1)) + return 1; + if (!(flags & XDF_WHITESPACE_FLAGS)) + return 0; + + i1 = 0; + i2 = 0; + + /* + * -w matches everything that matches with -b, and -b in turn + * matches everything that matches with --ignore-space-at-eol. + * + * Each flavor of ignoring needs different logic to skip whitespaces + * while we have both sides to compare. + */ + if (flags & XDF_IGNORE_WHITESPACE) { + goto skip_ws; + while (i1 < s1 && i2 < s2) { + if (l1[i1++] != l2[i2++]) + return 0; + skip_ws: + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + } + } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + while (i1 < s1 && i2 < s2) { + if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { + /* Skip matching spaces and try again */ + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + continue; + } + if (l1[i1++] != l2[i2++]) + return 0; + } + } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { + while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++]) + ; /* keep going */ + } + + /* + * After running out of one side, the remaining side must have + * nothing but whitespace for the lines to match. Note that + * ignore-whitespace-at-eol case may break out of the loop + * while there still are characters remaining on both lines. + */ + if (i1 < s1) { + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + if (s1 != i1) + return 0; + } + if (i2 < s2) { + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + return (s2 == i2); + } + return 1; +} + +static unsigned long xdl_hash_record_with_whitespace(char const **data, + char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + for (; ptr < top && *ptr != '\n'; ptr++) { + if (XDL_ISSPACE(*ptr)) { + const char *ptr2 = ptr; + int at_eol; + while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) + && ptr[1] != '\n') + ptr++; + at_eol = (top <= ptr + 1 || ptr[1] == '\n'); + if (flags & XDF_IGNORE_WHITESPACE) + ; /* already handled */ + else if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && !at_eol) { + ha += (ha << 5); + ha ^= (unsigned long) ' '; + } + else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL + && !at_eol) { + while (ptr2 != ptr + 1) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr2; + ptr2++; + } + } + continue; + } + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + + +unsigned long xdl_hash_record(char const **data, char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + if (flags & XDF_WHITESPACE_FLAGS) + return xdl_hash_record_with_whitespace(data, top, flags); + + for (; ptr < top && *ptr != '\n'; ptr++) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + + +unsigned int xdl_hashbits(unsigned int size) { + unsigned int val = 1, bits = 0; + + for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); + return bits ? bits: 1; +} + + +int xdl_num_out(char *out, long val) { + char *ptr, *str = out; + char buf[32]; + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + if (val < 0) { + *--ptr = '-'; + val = -val; + } + for (; val && ptr > buf; val /= 10) + *--ptr = "0123456789"[val % 10]; + if (*ptr) + for (; *ptr; ptr++, str++) + *str = *ptr; + else + *str++ = '0'; + *str = '\0'; + + return str - out; +} + + +long xdl_atol(char const *str, char const **next) { + long val, base; + char const *top; + + for (top = str; XDL_ISDIGIT(*top); top++); + if (next) + *next = top; + for (val = 0, base = 1, top--; top >= str; top--, base *= 10) + val += base * (long)(*top - '0'); + return val; +} + + +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb) { + int nb = 0; + mmbuffer_t mb; + char buf[128]; + + memcpy(buf, "@@ -", 4); + nb += 4; + + nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); + + if (c1 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c1); + } + + memcpy(buf + nb, " +", 2); + nb += 2; + + nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); + + if (c2 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c2); + } + + memcpy(buf + nb, " @@", 3); + nb += 3; + if (func && funclen) { + buf[nb++] = ' '; + if (funclen > (long)sizeof(buf) - nb - 1) + funclen = (long)sizeof(buf) - nb - 1; + memcpy(buf + nb, func, funclen); + nb += funclen; + } + buf[nb++] = '\n'; + + mb.ptr = buf; + mb.size = nb; + if (ecb->outf(ecb->priv, &mb, 1) < 0) + return -1; + + return 0; +} + +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2) +{ + /* + * This probably does not work outside Git, since + * we have a very simple mmfile structure. + * + * Note: ideally, we would reuse the prepared environment, but + * the libxdiff interface does not (yet) allow for diffing only + * ranges of lines instead of the whole files. + */ + mmfile_t subfile1, subfile2; + xdfenv_t env; + + subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + + diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + + diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) + return -1; + + memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h new file mode 100644 index 000000000..714719a89 --- /dev/null +++ b/src/xdiff/xutils.h @@ -0,0 +1,49 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XUTILS_H) +#define XUTILS_H + + + +long xdl_bogosqrt(long n); +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb); +int xdl_cha_init(chastore_t *cha, long isize, long icount); +void xdl_cha_free(chastore_t *cha); +void *xdl_cha_alloc(chastore_t *cha); +void *xdl_cha_first(chastore_t *cha); +void *xdl_cha_next(chastore_t *cha); +long xdl_guess_lines(mmfile_t *mf, long sample); +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); +unsigned long xdl_hash_record(char const **data, char const *top, long flags); +unsigned int xdl_hashbits(unsigned int size); +int xdl_num_out(char *out, long val); +long xdl_atol(char const *str, char const **next); +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb); +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2); + + + +#endif /* #if !defined(XUTILS_H) */ diff --git a/tests-clar/attr/attr_expect.h b/tests-clar/attr/attr_expect.h new file mode 100644 index 000000000..bea562457 --- /dev/null +++ b/tests-clar/attr/attr_expect.h @@ -0,0 +1,42 @@ +#ifndef __CLAR_TEST_ATTR_EXPECT__ +#define __CLAR_TEST_ATTR_EXPECT__ + +enum attr_expect_t { + EXPECT_FALSE, + EXPECT_TRUE, + EXPECT_UNDEFINED, + EXPECT_STRING +}; + +struct attr_expected { + const char *path; + const char *attr; + enum attr_expect_t expected; + const char *expected_str; +}; + +static inline void attr_check_expected( + enum attr_expect_t expected, + const char *expected_str, + const char *value) +{ + switch (expected) { + case EXPECT_TRUE: + cl_assert(GIT_ATTR_TRUE(value)); + break; + + case EXPECT_FALSE: + cl_assert(GIT_ATTR_FALSE(value)); + break; + + case EXPECT_UNDEFINED: + cl_assert(GIT_ATTR_UNSPECIFIED(value)); + break; + + case EXPECT_STRING: + cl_assert_strequal(expected_str, value); + break; + } +} + +#endif diff --git a/tests-clar/attr/file.c b/tests-clar/attr/file.c index af50cd38e..132b906cd 100644 --- a/tests-clar/attr/file.c +++ b/tests-clar/attr/file.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "attr_file.h" +#include "attr_expect.h" #define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X))) #define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y))) @@ -25,7 +26,7 @@ void test_attr_file__simple_read(void) assign = get_assign(rule, 0); cl_assert(assign != NULL); cl_assert_strequal("binary", assign->name); - cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(assign->value)); cl_assert(!assign->is_allocated); git_attr_file__free(file); @@ -54,7 +55,7 @@ void test_attr_file__match_variants(void) assign = get_assign(rule,0); cl_assert_strequal("attr0", assign->name); cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); - cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(assign->value)); cl_assert(!assign->is_allocated); rule = get_rule(1); @@ -83,7 +84,7 @@ void test_attr_file__match_variants(void) cl_assert(rule->assigns.length == 1); assign = get_assign(rule,0); cl_assert_strequal("attr7", assign->name); - cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(assign->value)); rule = get_rule(8); cl_assert_strequal("pat8 with spaces", rule->match.pattern); @@ -102,8 +103,8 @@ static void check_one_assign( int assign_idx, const char *pattern, const char *name, - const char *value, - int is_allocated) + enum attr_expect_t expected, + const char *expected_str) { git_attr_rule *rule = get_rule(rule_idx); git_attr_assignment *assign = get_assign(rule, assign_idx); @@ -112,11 +113,8 @@ static void check_one_assign( cl_assert(rule->assigns.length == 1); cl_assert_strequal(name, assign->name); cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); - cl_assert(assign->is_allocated == is_allocated); - if (is_allocated) - cl_assert_strequal(value, assign->value); - else - cl_assert(assign->value == value); + + attr_check_expected(expected, expected_str, assign->value); } void test_attr_file__assign_variants(void) @@ -130,14 +128,14 @@ void test_attr_file__assign_variants(void) cl_assert_strequal(cl_fixture("attr/attr2"), file->path); cl_assert(file->rules.length == 11); - check_one_assign(file, 0, 0, "pat0", "simple", GIT_ATTR_TRUE, 0); - check_one_assign(file, 1, 0, "pat1", "neg", GIT_ATTR_FALSE, 0); - check_one_assign(file, 2, 0, "*", "notundef", GIT_ATTR_TRUE, 0); - check_one_assign(file, 3, 0, "pat2", "notundef", NULL, 0); - check_one_assign(file, 4, 0, "pat3", "assigned", "test-value", 1); - check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", "value-with-more-chars", 1); - check_one_assign(file, 6, 0, "pat5", "empty", GIT_ATTR_TRUE, 0); - check_one_assign(file, 7, 0, "pat6", "negempty", GIT_ATTR_FALSE, 0); + check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL); + check_one_assign(file, 1, 0, "pat1", "neg", EXPECT_FALSE, NULL); + check_one_assign(file, 2, 0, "*", "notundef", EXPECT_TRUE, NULL); + check_one_assign(file, 3, 0, "pat2", "notundef", EXPECT_UNDEFINED, NULL); + check_one_assign(file, 4, 0, "pat3", "assigned", EXPECT_STRING, "test-value"); + check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars"); + check_one_assign(file, 6, 0, "pat5", "empty", EXPECT_TRUE, NULL); + check_one_assign(file, 7, 0, "pat6", "negempty", EXPECT_FALSE, NULL); rule = get_rule(8); cl_assert_strequal("pat7", rule->match.pattern); @@ -148,11 +146,11 @@ void test_attr_file__assign_variants(void) assign = git_attr_rule__lookup_assignment(rule, "multiple"); cl_assert(assign); cl_assert_strequal("multiple", assign->name); - cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "single"); cl_assert(assign); cl_assert_strequal("single", assign->name); - cl_assert(assign->value == GIT_ATTR_FALSE); + cl_assert(GIT_ATTR_FALSE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "values"); cl_assert(assign); cl_assert_strequal("values", assign->name); @@ -174,13 +172,13 @@ void test_attr_file__assign_variants(void) assign = git_attr_rule__lookup_assignment(rule, "again"); cl_assert(assign); cl_assert_strequal("again", assign->name); - cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "another"); cl_assert(assign); cl_assert_strequal("another", assign->name); cl_assert_strequal("12321", assign->value); - check_one_assign(file, 10, 0, "pat9", "at-eof", GIT_ATTR_FALSE, 0); + check_one_assign(file, 10, 0, "pat9", "at-eof", EXPECT_FALSE, NULL); git_attr_file__free(file); } @@ -204,10 +202,10 @@ void test_attr_file__check_attr_examples(void) cl_assert_strequal("java", assign->value); assign = git_attr_rule__lookup_assignment(rule, "crlf"); cl_assert_strequal("crlf", assign->name); - cl_assert(GIT_ATTR_FALSE == assign->value); + cl_assert(GIT_ATTR_FALSE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "myAttr"); cl_assert_strequal("myAttr", assign->name); - cl_assert(GIT_ATTR_TRUE == assign->value); + cl_assert(GIT_ATTR_TRUE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "missing"); cl_assert(assign == NULL); diff --git a/tests-clar/attr/lookup.c b/tests-clar/attr/lookup.c index 9462bbe7f..19396182e 100644 --- a/tests-clar/attr/lookup.c +++ b/tests-clar/attr/lookup.c @@ -1,6 +1,8 @@ #include "clar_libgit2.h" #include "attr_file.h" +#include "attr_expect.h" + void test_attr_lookup__simple(void) { git_attr_file *file; @@ -18,7 +20,7 @@ void test_attr_lookup__simple(void) cl_assert(!path.is_dir); cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); - cl_assert(value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(value)); cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); cl_assert(!value); @@ -26,36 +28,23 @@ void test_attr_lookup__simple(void) git_attr_file__free(file); } -typedef struct { - const char *path; - const char *attr; - const char *expected; - int use_strcmp; - int force_dir; -} test_case; - -static void run_test_cases(git_attr_file *file, test_case *cases) +static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int force_dir) { git_attr_path path; const char *value = NULL; - test_case *c; + struct attr_expected *c; int error; for (c = cases; c->path != NULL; c++) { cl_git_pass(git_attr_path__init(&path, c->path, NULL)); - if (c->force_dir) + if (force_dir) path.is_dir = 1; error = git_attr_file__lookup_one(file,&path,c->attr,&value); - if (error != GIT_SUCCESS) - fprintf(stderr, "failure with %s %s %s\n", c->path, c->attr, c->expected); cl_git_pass(error); - if (c->use_strcmp) - cl_assert_strequal(c->expected, value); - else - cl_assert(c->expected == value); + attr_check_expected(c->expected, c->expected_str, value); } } @@ -63,74 +52,79 @@ void test_attr_lookup__match_variants(void) { git_attr_file *file; git_attr_path path; - test_case cases[] = { + + struct attr_expected dir_cases[] = { + { "pat2", "attr2", EXPECT_TRUE, NULL }, + { "/testing/for/pat2", "attr2", EXPECT_TRUE, NULL }, + { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, + { "/fun/fun/fun/pat4.dir", "attr4", EXPECT_TRUE, NULL }, + { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, + { NULL, NULL, 0, NULL } + }; + + struct attr_expected cases[] = { /* pat0 -> simple match */ - { "pat0", "attr0", GIT_ATTR_TRUE, 0, 0 }, - { "/testing/for/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 }, - { "relative/to/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 }, - { "this-contains-pat0-inside", "attr0", NULL, 0, 0 }, - { "this-aint-right", "attr0", NULL, 0, 0 }, - { "/this/pat0/dont/match", "attr0", NULL, 0, 0 }, + { "pat0", "attr0", EXPECT_TRUE, NULL }, + { "/testing/for/pat0", "attr0", EXPECT_TRUE, NULL }, + { "relative/to/pat0", "attr0", EXPECT_TRUE, NULL }, + { "this-contains-pat0-inside", "attr0", EXPECT_UNDEFINED, NULL }, + { "this-aint-right", "attr0", EXPECT_UNDEFINED, NULL }, + { "/this/pat0/dont/match", "attr0", EXPECT_UNDEFINED, NULL }, /* negative match */ - { "pat0", "attr1", GIT_ATTR_TRUE, 0, 0 }, - { "pat1", "attr1", NULL, 0, 0 }, - { "/testing/for/pat1", "attr1", NULL, 0, 0 }, - { "/testing/for/pat0", "attr1", GIT_ATTR_TRUE, 0, 0 }, - { "/testing/for/pat1/inside", "attr1", GIT_ATTR_TRUE, 0, 0 }, - { "misc", "attr1", GIT_ATTR_TRUE, 0, 0 }, + { "pat0", "attr1", EXPECT_TRUE, NULL }, + { "pat1", "attr1", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat1", "attr1", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat0", "attr1", EXPECT_TRUE, NULL }, + { "/testing/for/pat1/inside", "attr1", EXPECT_TRUE, NULL }, + { "misc", "attr1", EXPECT_TRUE, NULL }, /* dir match */ - { "pat2", "attr2", NULL, 0, 0 }, - { "pat2", "attr2", GIT_ATTR_TRUE, 0, 1 }, - { "/testing/for/pat2", "attr2", NULL, 0, 0 }, - { "/testing/for/pat2", "attr2", GIT_ATTR_TRUE, 0, 1 }, - { "/not/pat2/yousee", "attr2", NULL, 0, 0 }, - { "/not/pat2/yousee", "attr2", NULL, 0, 1 }, + { "pat2", "attr2", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat2", "attr2", EXPECT_UNDEFINED, NULL }, + { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, /* path match */ - { "pat3file", "attr3", NULL, 0, 0 }, - { "/pat3dir/pat3file", "attr3", NULL, 0, 0 }, - { "pat3dir/pat3file", "attr3", GIT_ATTR_TRUE, 0, 0 }, + { "pat3file", "attr3", EXPECT_UNDEFINED, NULL }, + { "/pat3dir/pat3file", "attr3", EXPECT_UNDEFINED, NULL }, + { "pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, /* pattern* match */ - { "pat4.txt", "attr4", GIT_ATTR_TRUE, 0, 0 }, - { "/fun/fun/fun/pat4.c", "attr4", GIT_ATTR_TRUE, 0, 0 }, - { "pat4.", "attr4", GIT_ATTR_TRUE, 0, 0 }, - { "pat4", "attr4", NULL, 0, 0 }, - { "/fun/fun/fun/pat4.dir", "attr4", GIT_ATTR_TRUE, 0, 1 }, + { "pat4.txt", "attr4", EXPECT_TRUE, NULL }, + { "/fun/fun/fun/pat4.c", "attr4", EXPECT_TRUE, NULL }, + { "pat4.", "attr4", EXPECT_TRUE, NULL }, + { "pat4", "attr4", EXPECT_UNDEFINED, NULL }, /* *pattern match */ - { "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 }, - { "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 1 }, - { "/this/is/ok.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 }, - { "/this/is/bad.pat5/yousee.txt", "attr5", NULL, 0, 0 }, - { "foo.pat5", "attr100", NULL, 0, 0 }, + { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, + { "/this/is/ok.pat5", "attr5", EXPECT_TRUE, NULL }, + { "/this/is/bad.pat5/yousee.txt", "attr5", EXPECT_UNDEFINED, NULL }, + { "foo.pat5", "attr100", EXPECT_UNDEFINED, NULL }, /* glob match with slashes */ - { "foo.pat6", "attr6", NULL, 0, 0 }, - { "pat6/pat6/foobar.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 }, - { "pat6/pat6/.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 }, - { "pat6/pat6/extra/foobar.pat6", "attr6", NULL, 0, 0 }, - { "/prefix/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 }, - { "/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 }, + { "foo.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, + { "pat6/pat6/.pat6", "attr6", EXPECT_TRUE, NULL }, + { "pat6/pat6/extra/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "/prefix/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, /* complex pattern */ - { "pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 }, - { "pat7e__x", "attr7", GIT_ATTR_TRUE, 0, 0 }, - { "pat7b/1y", "attr7", NULL, 0, 0 }, /* ? does not match / */ - { "pat7e_x", "attr7", NULL, 0, 0 }, - { "pat7aaaa", "attr7", NULL, 0, 0 }, - { "pat7zzzz", "attr7", NULL, 0, 0 }, - { "/this/can/be/anything/pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 }, - { "but/it/still/must/match/pat7aaaa", "attr7", NULL, 0, 0 }, - { "pat7aaay.fail", "attr7", NULL, 0, 0 }, + { "pat7a12z", "attr7", EXPECT_TRUE, NULL }, + { "pat7e__x", "attr7", EXPECT_TRUE, NULL }, + { "pat7b/1y", "attr7", EXPECT_UNDEFINED, NULL }, /* ? does not match / */ + { "pat7e_x", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7zzzz", "attr7", EXPECT_UNDEFINED, NULL }, + { "/this/can/be/anything/pat7a12z", "attr7", EXPECT_TRUE, NULL }, + { "but/it/still/must/match/pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7aaay.fail", "attr7", EXPECT_UNDEFINED, NULL }, /* pattern with spaces */ - { "pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 }, - { "/gotta love/pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 }, - { "failing pat8 with spaces", "attr8", NULL, 0, 0 }, - { "spaces", "attr8", NULL, 0, 0 }, + { "pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, + { "/gotta love/pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, + { "failing pat8 with spaces", "attr8", EXPECT_UNDEFINED, NULL }, + { "spaces", "attr8", EXPECT_UNDEFINED, NULL }, /* pattern at eof */ - { "pat9", "attr9", GIT_ATTR_TRUE, 0, 0 }, - { "/eof/pat9", "attr9", GIT_ATTR_TRUE, 0, 0 }, - { "pat", "attr9", NULL, 0, 0 }, - { "at9", "attr9", NULL, 0, 0 }, - { "pat9.fail", "attr9", NULL, 0, 0 }, + { "pat9", "attr9", EXPECT_TRUE, NULL }, + { "/eof/pat9", "attr9", EXPECT_TRUE, NULL }, + { "pat", "attr9", EXPECT_UNDEFINED, NULL }, + { "at9", "attr9", EXPECT_UNDEFINED, NULL }, + { "pat9.fail", "attr9", EXPECT_UNDEFINED, NULL }, /* sentinel at end */ - { NULL, NULL, NULL, 0, 0 } + { NULL, NULL, 0, NULL } }; cl_git_pass(git_attr_file__new(&file)); @@ -141,7 +135,8 @@ void test_attr_lookup__match_variants(void) cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL)); cl_assert_strequal("pat0", path.basename); - run_test_cases(file, cases); + run_test_cases(file, cases, 0); + run_test_cases(file, dir_cases, 1); git_attr_file__free(file); } @@ -149,54 +144,54 @@ void test_attr_lookup__match_variants(void) void test_attr_lookup__assign_variants(void) { git_attr_file *file; - test_case cases[] = { + struct attr_expected cases[] = { /* pat0 -> simple assign */ - { "pat0", "simple", GIT_ATTR_TRUE, 0, 0 }, - { "/testing/pat0", "simple", GIT_ATTR_TRUE, 0, 0 }, - { "pat0", "fail", NULL, 0, 0 }, - { "/testing/pat0", "fail", NULL, 0, 0 }, + { "pat0", "simple", EXPECT_TRUE, NULL }, + { "/testing/pat0", "simple", EXPECT_TRUE, NULL }, + { "pat0", "fail", EXPECT_UNDEFINED, NULL }, + { "/testing/pat0", "fail", EXPECT_UNDEFINED, NULL }, /* negative assign */ - { "pat1", "neg", GIT_ATTR_FALSE, 0, 0 }, - { "/testing/pat1", "neg", GIT_ATTR_FALSE, 0, 0 }, - { "pat1", "fail", NULL, 0, 0 }, - { "/testing/pat1", "fail", NULL, 0, 0 }, + { "pat1", "neg", EXPECT_FALSE, NULL }, + { "/testing/pat1", "neg", EXPECT_FALSE, NULL }, + { "pat1", "fail", EXPECT_UNDEFINED, NULL }, + { "/testing/pat1", "fail", EXPECT_UNDEFINED, NULL }, /* forced undef */ - { "pat1", "notundef", GIT_ATTR_TRUE, 0, 0 }, - { "pat2", "notundef", NULL, 0, 0 }, - { "/lead/in/pat1", "notundef", GIT_ATTR_TRUE, 0, 0 }, - { "/lead/in/pat2", "notundef", NULL, 0, 0 }, + { "pat1", "notundef", EXPECT_TRUE, NULL }, + { "pat2", "notundef", EXPECT_UNDEFINED, NULL }, + { "/lead/in/pat1", "notundef", EXPECT_TRUE, NULL }, + { "/lead/in/pat2", "notundef", EXPECT_UNDEFINED, NULL }, /* assign value */ - { "pat3", "assigned", "test-value", 1, 0 }, - { "pat3", "notassigned", NULL, 0, 0 }, + { "pat3", "assigned", EXPECT_STRING, "test-value" }, + { "pat3", "notassigned", EXPECT_UNDEFINED, NULL }, /* assign value */ - { "pat4", "rule-with-more-chars", "value-with-more-chars", 1, 0 }, - { "pat4", "notassigned-rule-with-more-chars", NULL, 0, 0 }, + { "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars" }, + { "pat4", "notassigned-rule-with-more-chars", EXPECT_UNDEFINED, NULL }, /* empty assignments */ - { "pat5", "empty", GIT_ATTR_TRUE, 0, 0 }, - { "pat6", "negempty", GIT_ATTR_FALSE, 0, 0 }, + { "pat5", "empty", EXPECT_TRUE, NULL }, + { "pat6", "negempty", EXPECT_FALSE, NULL }, /* multiple assignment */ - { "pat7", "multiple", GIT_ATTR_TRUE, 0, 0 }, - { "pat7", "single", GIT_ATTR_FALSE, 0, 0 }, - { "pat7", "values", "1", 1, 0 }, - { "pat7", "also", "a-really-long-value/*", 1, 0 }, - { "pat7", "happy", "yes!", 1, 0 }, - { "pat8", "again", GIT_ATTR_TRUE, 0, 0 }, - { "pat8", "another", "12321", 1, 0 }, + { "pat7", "multiple", EXPECT_TRUE, NULL }, + { "pat7", "single", EXPECT_FALSE, NULL }, + { "pat7", "values", EXPECT_STRING, "1" }, + { "pat7", "also", EXPECT_STRING, "a-really-long-value/*" }, + { "pat7", "happy", EXPECT_STRING, "yes!" }, + { "pat8", "again", EXPECT_TRUE, NULL }, + { "pat8", "another", EXPECT_STRING, "12321" }, /* bad assignment */ - { "patbad0", "simple", NULL, 0, 0 }, - { "patbad0", "notundef", GIT_ATTR_TRUE, 0, 0 }, - { "patbad1", "simple", NULL, 0, 0 }, + { "patbad0", "simple", EXPECT_UNDEFINED, NULL }, + { "patbad0", "notundef", EXPECT_TRUE, NULL }, + { "patbad1", "simple", EXPECT_UNDEFINED, NULL }, /* eof assignment */ - { "pat9", "at-eof", GIT_ATTR_FALSE, 0, 0 }, + { "pat9", "at-eof", EXPECT_FALSE, NULL }, /* sentinel at end */ - { NULL, NULL, NULL, 0, 0 } + { NULL, NULL, 0, NULL } }; cl_git_pass(git_attr_file__new(&file)); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), file)); cl_assert(file->rules.length == 11); - run_test_cases(file, cases); + run_test_cases(file, cases, 0); git_attr_file__free(file); } @@ -204,34 +199,34 @@ void test_attr_lookup__assign_variants(void) void test_attr_lookup__check_attr_examples(void) { git_attr_file *file; - test_case cases[] = { - { "foo.java", "diff", "java", 1, 0 }, - { "foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, - { "foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 }, - { "foo.java", "other", NULL, 0, 0 }, - { "/prefix/dir/foo.java", "diff", "java", 1, 0 }, - { "/prefix/dir/foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, - { "/prefix/dir/foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 }, - { "/prefix/dir/foo.java", "other", NULL, 0, 0 }, - { "NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, - { "NoMyAttr.java", "myAttr", NULL, 0, 0 }, - { "NoMyAttr.java", "other", NULL, 0, 0 }, - { "/prefix/dir/NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, - { "/prefix/dir/NoMyAttr.java", "myAttr", NULL, 0, 0 }, - { "/prefix/dir/NoMyAttr.java", "other", NULL, 0, 0 }, - { "README", "caveat", "unspecified", 1, 0 }, - { "/specific/path/README", "caveat", "unspecified", 1, 0 }, - { "README", "missing", NULL, 0, 0 }, - { "/specific/path/README", "missing", NULL, 0, 0 }, + struct attr_expected cases[] = { + { "foo.java", "diff", EXPECT_STRING, "java" }, + { "foo.java", "crlf", EXPECT_FALSE, NULL }, + { "foo.java", "myAttr", EXPECT_TRUE, NULL }, + { "foo.java", "other", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/foo.java", "diff", EXPECT_STRING, "java" }, + { "/prefix/dir/foo.java", "crlf", EXPECT_FALSE, NULL }, + { "/prefix/dir/foo.java", "myAttr", EXPECT_TRUE, NULL }, + { "/prefix/dir/foo.java", "other", EXPECT_UNDEFINED, NULL }, + { "NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, + { "NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, + { "NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, + { "/prefix/dir/NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, + { "README", "caveat", EXPECT_STRING, "unspecified" }, + { "/specific/path/README", "caveat", EXPECT_STRING, "unspecified" }, + { "README", "missing", EXPECT_UNDEFINED, NULL }, + { "/specific/path/README", "missing", EXPECT_UNDEFINED, NULL }, /* sentinel at end */ - { NULL, NULL, NULL, 0, 0 } + { NULL, NULL, 0, NULL } }; cl_git_pass(git_attr_file__new(&file)); cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), file)); cl_assert(file->rules.length == 3); - run_test_cases(file, cases); + run_test_cases(file, cases, 0); git_attr_file__free(file); } @@ -239,24 +234,24 @@ void test_attr_lookup__check_attr_examples(void) void test_attr_lookup__from_buffer(void) { git_attr_file *file; - test_case cases[] = { - { "abc", "foo", GIT_ATTR_TRUE, 0, 0 }, - { "abc", "bar", GIT_ATTR_TRUE, 0, 0 }, - { "abc", "baz", GIT_ATTR_TRUE, 0, 0 }, - { "aaa", "foo", GIT_ATTR_TRUE, 0, 0 }, - { "aaa", "bar", NULL, 0, 0 }, - { "aaa", "baz", GIT_ATTR_TRUE, 0, 0 }, - { "qqq", "foo", NULL, 0, 0 }, - { "qqq", "bar", NULL, 0, 0 }, - { "qqq", "baz", GIT_ATTR_TRUE, 0, 0 }, - { NULL, NULL, NULL, 0, 0 } + struct attr_expected cases[] = { + { "abc", "foo", EXPECT_TRUE, NULL }, + { "abc", "bar", EXPECT_TRUE, NULL }, + { "abc", "baz", EXPECT_TRUE, NULL }, + { "aaa", "foo", EXPECT_TRUE, NULL }, + { "aaa", "bar", EXPECT_UNDEFINED, NULL }, + { "aaa", "baz", EXPECT_TRUE, NULL }, + { "qqq", "foo", EXPECT_UNDEFINED, NULL }, + { "qqq", "bar", EXPECT_UNDEFINED, NULL }, + { "qqq", "baz", EXPECT_TRUE, NULL }, + { NULL, NULL, 0, NULL } }; cl_git_pass(git_attr_file__new(&file)); cl_git_pass(git_attr_file__from_buffer(NULL, "a* foo\nabc bar\n* baz", file)); cl_assert(file->rules.length == 3); - run_test_cases(file, cases); + run_test_cases(file, cases, 0); git_attr_file__free(file); } diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c index 7a716042a..4de4afaa7 100644 --- a/tests-clar/attr/repo.c +++ b/tests-clar/attr/repo.c @@ -3,6 +3,8 @@ #include "git2/attr.h" #include "attr.h" +#include "attr_expect.h" + static git_repository *g_repo = NULL; void test_attr_repo__initialize(void) @@ -27,68 +29,47 @@ void test_attr_repo__cleanup(void) void test_attr_repo__get_one(void) { - const char *value; - struct { - const char *file; - const char *attr; - const char *expected; - } test_cases[] = { - { "root_test1", "repoattr", GIT_ATTR_TRUE }, - { "root_test1", "rootattr", GIT_ATTR_TRUE }, - { "root_test1", "missingattr", NULL }, - { "root_test1", "subattr", NULL }, - { "root_test1", "negattr", NULL }, - { "root_test2", "repoattr", GIT_ATTR_TRUE }, - { "root_test2", "rootattr", GIT_ATTR_FALSE }, - { "root_test2", "missingattr", NULL }, - { "root_test2", "multiattr", GIT_ATTR_FALSE }, - { "root_test3", "repoattr", GIT_ATTR_TRUE }, - { "root_test3", "rootattr", NULL }, - { "root_test3", "multiattr", "3" }, - { "root_test3", "multi2", NULL }, - { "sub/subdir_test1", "repoattr", GIT_ATTR_TRUE }, - { "sub/subdir_test1", "rootattr", GIT_ATTR_TRUE }, - { "sub/subdir_test1", "missingattr", NULL }, - { "sub/subdir_test1", "subattr", "yes" }, - { "sub/subdir_test1", "negattr", GIT_ATTR_FALSE }, - { "sub/subdir_test1", "another", NULL }, - { "sub/subdir_test2.txt", "repoattr", GIT_ATTR_TRUE }, - { "sub/subdir_test2.txt", "rootattr", GIT_ATTR_TRUE }, - { "sub/subdir_test2.txt", "missingattr", NULL }, - { "sub/subdir_test2.txt", "subattr", "yes" }, - { "sub/subdir_test2.txt", "negattr", GIT_ATTR_FALSE }, - { "sub/subdir_test2.txt", "another", "zero" }, - { "sub/subdir_test2.txt", "reposub", GIT_ATTR_TRUE }, - { "sub/sub/subdir.txt", "another", "one" }, - { "sub/sub/subdir.txt", "reposubsub", GIT_ATTR_TRUE }, - { "sub/sub/subdir.txt", "reposub", NULL }, - { "does-not-exist", "foo", "yes" }, - { "sub/deep/file", "deepdeep", GIT_ATTR_TRUE }, - { NULL, NULL, NULL } + struct attr_expected test_cases[] = { + { "root_test1", "repoattr", EXPECT_TRUE, NULL }, + { "root_test1", "rootattr", EXPECT_TRUE, NULL }, + { "root_test1", "missingattr", EXPECT_UNDEFINED, NULL }, + { "root_test1", "subattr", EXPECT_UNDEFINED, NULL }, + { "root_test1", "negattr", EXPECT_UNDEFINED, NULL }, + { "root_test2", "repoattr", EXPECT_TRUE, NULL }, + { "root_test2", "rootattr", EXPECT_FALSE, NULL }, + { "root_test2", "missingattr", EXPECT_UNDEFINED, NULL }, + { "root_test2", "multiattr", EXPECT_FALSE, NULL }, + { "root_test3", "repoattr", EXPECT_TRUE, NULL }, + { "root_test3", "rootattr", EXPECT_UNDEFINED, NULL }, + { "root_test3", "multiattr", EXPECT_STRING, "3" }, + { "root_test3", "multi2", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" }, + { "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL }, + { "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" }, + { "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL }, + { "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" }, + { "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL }, + { "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" }, + { "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL }, + { "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL }, + { "does-not-exist", "foo", EXPECT_STRING, "yes" }, + { "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL }, + { "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" }, + { "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL }, + { NULL, NULL, 0, NULL } }, *scan; - for (scan = test_cases; scan->file != NULL; scan++) { - git_buf b = GIT_BUF_INIT; - - git_buf_printf(&b, "%s:%s == expect %s", - scan->file, scan->attr, scan->expected); - - cl_must_pass_( - git_attr_get(g_repo, scan->file, scan->attr, &value) == GIT_SUCCESS, - b.ptr); - - git_buf_printf(&b, ", got %s", value); - - if (scan->expected == NULL || - scan->expected == GIT_ATTR_TRUE || - scan->expected == GIT_ATTR_FALSE) - { - cl_assert_(scan->expected == value, b.ptr); - } else { - cl_assert_strequal(scan->expected, value); - } - - git_buf_free(&b); + for (scan = test_cases; scan->path != NULL; scan++) { + const char *value; + cl_git_pass(git_attr_get(g_repo, scan->path, scan->attr, &value)); + attr_check_expected(scan->expected, scan->expected_str, value); } cl_git_pass(git_attr_cache__is_cached(g_repo, ".git/info/attributes")); @@ -103,34 +84,33 @@ void test_attr_repo__get_many(void) cl_git_pass(git_attr_get_many(g_repo, "root_test1", 4, names, values)); - cl_assert(values[0] == GIT_ATTR_TRUE); - cl_assert(values[1] == GIT_ATTR_TRUE); - cl_assert(values[2] == NULL); - cl_assert(values[3] == NULL); + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); cl_git_pass(git_attr_get_many(g_repo, "root_test2", 4, names, values)); - cl_assert(values[0] == GIT_ATTR_TRUE); - cl_assert(values[1] == GIT_ATTR_FALSE); - cl_assert(values[2] == NULL); - cl_assert(values[3] == NULL); + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_FALSE(values[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); cl_git_pass(git_attr_get_many(g_repo, "sub/subdir_test1", 4, names, values)); - cl_assert(values[0] == GIT_ATTR_TRUE); - cl_assert(values[1] == GIT_ATTR_TRUE); - cl_assert(values[2] == NULL); + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); cl_assert_strequal("yes", values[3]); - } static int count_attrs( - const char *GIT_UNUSED(name), - const char *GIT_UNUSED(value), + const char *name, + const char *value, void *payload) { - GIT_UNUSED_ARG(name); - GIT_UNUSED_ARG(value); + GIT_UNUSED(name); + GIT_UNUSED(value); *((int *)payload) += 1; @@ -161,19 +141,19 @@ void test_attr_repo__manpage_example(void) const char *value; cl_git_pass(git_attr_get(g_repo, "sub/abc", "foo", &value)); - cl_assert(value == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(value)); cl_git_pass(git_attr_get(g_repo, "sub/abc", "bar", &value)); - cl_assert(value == NULL); + cl_assert(GIT_ATTR_UNSPECIFIED(value)); cl_git_pass(git_attr_get(g_repo, "sub/abc", "baz", &value)); - cl_assert(value == GIT_ATTR_FALSE); + cl_assert(GIT_ATTR_FALSE(value)); cl_git_pass(git_attr_get(g_repo, "sub/abc", "merge", &value)); cl_assert_strequal("filfre", value); cl_git_pass(git_attr_get(g_repo, "sub/abc", "frotz", &value)); - cl_assert(value == NULL); + cl_assert(GIT_ATTR_UNSPECIFIED(value)); } void test_attr_repo__macros(void) @@ -185,24 +165,24 @@ void test_attr_repo__macros(void) cl_git_pass(git_attr_get_many(g_repo, "binfile", 5, names, values)); - cl_assert(values[0] == GIT_ATTR_TRUE); - cl_assert(values[1] == GIT_ATTR_TRUE); - cl_assert(values[2] == GIT_ATTR_FALSE); - cl_assert(values[3] == GIT_ATTR_FALSE); - cl_assert(values[4] == NULL); + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_FALSE(values[2])); + cl_assert(GIT_ATTR_FALSE(values[3])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[4])); cl_git_pass(git_attr_get_many(g_repo, "macro_test", 5, names2, values)); - cl_assert(values[0] == GIT_ATTR_TRUE); - cl_assert(values[1] == GIT_ATTR_TRUE); - cl_assert(values[2] == GIT_ATTR_FALSE); - cl_assert(values[3] == NULL); + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_FALSE(values[2])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); cl_assert_strequal("77", values[4]); cl_git_pass(git_attr_get_many(g_repo, "macro_test", 3, names3, values)); - cl_assert(values[0] == GIT_ATTR_TRUE); - cl_assert(values[1] == GIT_ATTR_FALSE); + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_FALSE(values[1])); cl_assert_strequal("answer", values[2]); } @@ -215,9 +195,9 @@ void test_attr_repo__bad_macros(void) cl_git_pass(git_attr_get_many(g_repo, "macro_bad", 6, names, values)); /* these three just confirm that the "mymacro" rule ran */ - cl_assert(values[0] == NULL); - cl_assert(values[1] == GIT_ATTR_TRUE); - cl_assert(values[2] == GIT_ATTR_FALSE); + cl_assert(GIT_ATTR_UNSPECIFIED(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_FALSE(values[2])); /* file contains: * # let's try some malicious macro defs @@ -241,7 +221,8 @@ void test_attr_repo__bad_macros(void) * so summary results should be: * -firstmacro secondmacro="hahaha" thirdmacro */ - cl_assert(values[3] == GIT_ATTR_FALSE); + cl_assert(GIT_ATTR_FALSE(values[3])); cl_assert_strequal("hahaha", values[4]); - cl_assert(values[5] == GIT_ATTR_TRUE); + cl_assert(GIT_ATTR_TRUE(values[5])); } + diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c index eea8bc87d..22db56f0c 100644 --- a/tests-clar/clar_helpers.c +++ b/tests-clar/clar_helpers.c @@ -27,3 +27,64 @@ void cl_git_mkfile(const char *filename, const char *content) cl_must_pass(p_close(fd)); } + +void cl_git_append2file(const char *filename, const char *new_content) +{ + int fd = p_open(filename, O_WRONLY | O_APPEND | O_CREAT); + cl_assert(fd != 0); + if (!new_content) + new_content = "\n"; + cl_must_pass(p_write(fd, new_content, strlen(new_content))); + cl_must_pass(p_close(fd)); + cl_must_pass(p_chmod(filename, 0644)); +} + +static const char *_cl_sandbox = NULL; +static git_repository *_cl_repo = NULL; + +git_repository *cl_git_sandbox_init(const char *sandbox) +{ + /* Copy the whole sandbox folder from our fixtures to our test sandbox + * area. After this it can be accessed with `./sandbox` + */ + cl_fixture_sandbox(sandbox); + _cl_sandbox = sandbox; + + p_chdir(sandbox); + + /* Rename `sandbox/.gitted` to `sandbox/.git` which must be done since + * we cannot store a folder named `.git` inside the fixtures folder of + * our libgit2 repo. + */ + cl_git_pass(p_rename(".gitted", ".git")); + + /* If we have `gitattributes`, rename to `.gitattributes`. This may + * be necessary if we don't want the attributes to be applied in the + * libgit2 repo, but just during testing. + */ + if (p_access("gitattributes", F_OK) == 0) + cl_git_pass(p_rename("gitattributes", ".gitattributes")); + + /* As with `gitattributes`, we may need `gitignore` just for testing. */ + if (p_access("gitignore", F_OK) == 0) + cl_git_pass(p_rename("gitignore", ".gitignore")); + + p_chdir(".."); + + /* Now open the sandbox repository and make it available for tests */ + cl_git_pass(git_repository_open(&_cl_repo, sandbox)); + + return _cl_repo; +} + +void cl_git_sandbox_cleanup(void) +{ + if (_cl_repo) { + git_repository_free(_cl_repo); + _cl_repo = NULL; + } + if (_cl_sandbox) { + cl_fixture_cleanup(_cl_sandbox); + _cl_sandbox = NULL; + } +} diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index 73ef66844..5c034a385 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -38,10 +38,13 @@ GIT_INLINE(void) cl_assert_strequal_internal( if (!match) { char buf[4096]; snprintf(buf, 4096, "'%s' != '%s'", a, b); - clar__assert(0, file, line, buf, err, 1); + clar__assert(0, file, line, err, buf, 1); } } +#define cl_assert_intequal(a,b) \ + do { if ((a) != (b)) { char buf[128]; snprintf(buf,128,"%d != %d",(a),(b)); clar__assert(0,__FILE__,__LINE__,#a " != " #b,buf,1); } } while (0) + /* * Some utility macros for building long strings */ @@ -53,5 +56,11 @@ GIT_INLINE(void) cl_assert_strequal_internal( /* Write the contents of a buffer to disk */ void cl_git_mkfile(const char *filename, const char *content); +void cl_git_append2file(const char *filename, const char *new_content); + +/* Git sandbox setup helpers */ + +git_repository *cl_git_sandbox_init(const char *sandbox); +void cl_git_sandbox_cleanup(void); #endif diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index bccdc1289..9a411f0df 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -12,10 +12,12 @@ void test_config_multivar__cleanup(void) cl_fixture_cleanup("config"); } -static int mv_read_cb(const char *name, const char *GIT_UNUSED(value), void *data) +static int mv_read_cb(const char *name, const char *value, void *data) { int *n = (int *) data; + GIT_UNUSED(value); + if (!strcmp(name, _name)) (*n)++; @@ -35,10 +37,12 @@ void test_config_multivar__foreach(void) git_config_free(cfg); } -static int cb(const char *GIT_UNUSED(val), void *data) +static int cb(const char *val, void *data) { int *n = (int *) data; + GIT_UNUSED(val); + (*n)++; return GIT_SUCCESS; @@ -119,6 +123,8 @@ void test_config_multivar__replace(void) n = 0; cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); cl_assert(n == 2); + + git_config_free(cfg); } void test_config_multivar__replace_multiple(void) @@ -141,4 +147,5 @@ void test_config_multivar__replace_multiple(void) cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); cl_assert(n == 2); + git_config_free(cfg); } diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c index 740cd8578..870525b36 100644 --- a/tests-clar/core/buffer.c +++ b/tests-clar/core/buffer.c @@ -218,8 +218,8 @@ check_buf_append( const char* data_a, const char* data_b, const char* expected_data, - ssize_t expected_size, - ssize_t expected_asize) + size_t expected_size, + size_t expected_asize) { git_buf tgt = GIT_BUF_INIT; @@ -371,8 +371,8 @@ void test_core_buffer__7(void) git_buf_attach(&a, b, 0); cl_assert_strequal(fun, a.ptr); - cl_assert(a.size == (ssize_t)strlen(fun)); - cl_assert(a.asize == (ssize_t)strlen(fun) + 1); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); git_buf_free(&a); @@ -380,8 +380,8 @@ void test_core_buffer__7(void) git_buf_attach(&a, b, strlen(fun) + 1); cl_assert_strequal(fun, a.ptr); - cl_assert(a.size == (ssize_t)strlen(fun)); - cl_assert(a.asize == (ssize_t)strlen(fun) + 1); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); git_buf_free(&a); } diff --git a/tests-clar/core/dirent.c b/tests-clar/core/dirent.c index edd04471e..9c366bf97 100644 --- a/tests-clar/core/dirent.c +++ b/tests-clar/core/dirent.c @@ -88,10 +88,10 @@ static int one_entry(void *state, git_buf *path) return GIT_ERROR; } -static int dont_call_me(void *GIT_UNUSED(state), git_buf *GIT_UNUSED(path)) +static int dont_call_me(void *state, git_buf *path) { - GIT_UNUSED_ARG(state) - GIT_UNUSED_ARG(path) + GIT_UNUSED(state); + GIT_UNUSED(path); return GIT_ERROR; } diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c index 3ff5d7daf..c07362f1d 100644 --- a/tests-clar/core/path.c +++ b/tests-clar/core/path.c @@ -243,7 +243,7 @@ void test_core_path__07_path_to_dir(void) void test_core_path__08_self_join(void) { git_buf path = GIT_BUF_INIT; - ssize_t asize = 0; + size_t asize = 0; asize = path.asize; cl_git_pass(git_buf_sets(&path, "/foo")); diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c new file mode 100644 index 000000000..bdfe8baaf --- /dev/null +++ b/tests-clar/diff/blob.c @@ -0,0 +1,91 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_blob__initialize(void) +{ + g_repo = cl_git_sandbox_init("attr"); +} + +void test_diff_blob__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_blob__0(void) +{ + git_blob *a, *b, *c, *d; + git_oid a_oid, b_oid, c_oid, d_oid; + git_diff_options opts = {0}; + diff_expects exp; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); + + /* tests/resources/attr/root_test2 */ + cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); + cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); + + /* tests/resources/attr/root_test3 */ + cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16)); + cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 8)); + + /* tests/resources/attr/root_test4.txt */ + cl_git_pass(git_oid_fromstrn(&d_oid, "fe773770c5a6", 12)); + cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &d_oid, 6)); + + /* Doing the equivalent of a `git diff -U1` on these files */ + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_blobs( + g_repo, a, b, &opts, &exp, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.hunks == 1); + cl_assert(exp.lines == 6); + cl_assert(exp.line_ctxt == 1); + cl_assert(exp.line_adds == 5); + cl_assert(exp.line_dels == 0); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_blobs( + g_repo, b, c, &opts, &exp, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.hunks == 1); + cl_assert(exp.lines == 15); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 9); + cl_assert(exp.line_dels == 3); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_blobs( + g_repo, a, c, &opts, &exp, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.hunks == 1); + cl_assert(exp.lines == 13); + cl_assert(exp.line_ctxt == 0); + cl_assert(exp.line_adds == 12); + cl_assert(exp.line_dels == 1); + + opts.context_lines = 1; + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_blobs( + g_repo, c, d, &opts, &exp, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.hunks == 2); + cl_assert(exp.lines == 14); + cl_assert(exp.line_ctxt == 4); + cl_assert(exp.line_adds == 6); + cl_assert(exp.line_dels == 4); + + git_blob_free(a); + git_blob_free(b); + git_blob_free(c); + git_blob_free(d); +} + diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index b2dbe9ee7..d8eca7d9b 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -20,3 +20,69 @@ git_tree *resolve_commit_oid_to_tree( git_object_free(obj); return tree; } + +int diff_file_fn( + void *cb_data, + git_diff_delta *delta, + float progress) +{ + diff_expects *e = cb_data; + (void)progress; + e->files++; + switch (delta->status) { + case GIT_DELTA_ADDED: e->file_adds++; break; + case GIT_DELTA_DELETED: e->file_dels++; break; + case GIT_DELTA_MODIFIED: e->file_mods++; break; + case GIT_DELTA_IGNORED: e->file_ignored++; break; + case GIT_DELTA_UNTRACKED: e->file_untracked++; break; + default: break; + } + return 0; +} + +int diff_hunk_fn( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len) +{ + diff_expects *e = cb_data; + (void)delta; + (void)header; + (void)header_len; + e->hunks++; + e->hunk_old_lines += range->old_lines; + e->hunk_new_lines += range->new_lines; + return 0; +} + +int diff_line_fn( + void *cb_data, + git_diff_delta *delta, + char line_origin, + const char *content, + size_t content_len) +{ + diff_expects *e = cb_data; + (void)delta; + (void)content; + (void)content_len; + e->lines++; + switch (line_origin) { + case GIT_DIFF_LINE_CONTEXT: + e->line_ctxt++; + break; + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_ADD_EOFNL: + e->line_adds++; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_DEL_EOFNL: + e->line_dels++; + break; + default: + break; + } + return 0; +} diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h index a75dd912c..010d156fa 100644 --- a/tests-clar/diff/diff_helpers.h +++ b/tests-clar/diff/diff_helpers.h @@ -1,4 +1,43 @@ #include "fileops.h" +#include "git2/diff.h" extern git_tree *resolve_commit_oid_to_tree( git_repository *repo, const char *partial_oid); + +typedef struct { + int files; + int file_adds; + int file_dels; + int file_mods; + int file_ignored; + int file_untracked; + + int hunks; + int hunk_new_lines; + int hunk_old_lines; + + int lines; + int line_ctxt; + int line_adds; + int line_dels; +} diff_expects; + +extern int diff_file_fn( + void *cb_data, + git_diff_delta *delta, + float progress); + +extern int diff_hunk_fn( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len); + +extern int diff_line_fn( + void *cb_data, + git_diff_delta *delta, + char line_origin, + const char *content, + size_t content_len); + diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c new file mode 100644 index 000000000..171815df5 --- /dev/null +++ b/tests-clar/diff/index.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_index__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_index__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 26a125ee1bf + * - git diff -U1 --cached 26a125ee1bf + * - mv .git .gitted + */ + cl_assert(exp.files == 8); + cl_assert(exp.file_adds == 3); + cl_assert(exp.file_dels == 2); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 8); + + cl_assert(exp.lines == 11); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 6); + cl_assert(exp.line_dels == 2); + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 0017bd4ab1ec3 + * - git diff -U1 --cached 0017bd4ab1ec3 + * - mv .git .gitted + */ + cl_assert(exp.files == 12); + cl_assert(exp.file_adds == 7); + cl_assert(exp.file_dels == 2); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 12); + + cl_assert(exp.lines == 16); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 11); + cl_assert(exp.line_dels == 2); + + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index d6b548ed0..3953fd83f 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -2,37 +2,6 @@ #include "diff_helpers.h" #include "iterator.h" -static git_repository *g_repo = NULL; -static const char *g_sandbox = NULL; - -static void setup_sandbox(const char *sandbox) -{ - cl_fixture_sandbox(sandbox); - g_sandbox = sandbox; - - p_chdir(sandbox); - cl_git_pass(p_rename(".gitted", ".git")); - if (p_access("gitattributes", F_OK) == 0) - cl_git_pass(p_rename("gitattributes", ".gitattributes")); - if (p_access("gitignore", F_OK) == 0) - cl_git_pass(p_rename("gitignore", ".gitignore")); - p_chdir(".."); - - cl_git_pass(git_repository_open(&g_repo, sandbox)); -} - -static void cleanup_sandbox(void) -{ - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - if (g_sandbox) { - cl_fixture_cleanup(g_sandbox); - g_sandbox = NULL; - } -} - void test_diff_iterator__initialize(void) { /* since we are doing tests with different sandboxes, defer setup @@ -44,7 +13,7 @@ void test_diff_iterator__initialize(void) void test_diff_iterator__cleanup(void) { - cleanup_sandbox(); + cl_git_sandbox_cleanup(); } @@ -60,11 +29,10 @@ static void tree_iterator_test( git_iterator *i; const git_index_entry *entry; int count = 0; + git_repository *repo = cl_git_sandbox_init(sandbox); - setup_sandbox(sandbox); - - cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish)); - cl_git_pass(git_iterator_for_tree(g_repo, t, &i)); + cl_assert(t = resolve_commit_oid_to_tree(repo, treeish)); + cl_git_pass(git_iterator_for_tree(repo, t, &i)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -183,10 +151,9 @@ static void index_iterator_test( git_iterator *i; const git_index_entry *entry; int count = 0; + git_repository *repo = cl_git_sandbox_init(sandbox); - setup_sandbox(sandbox); - - cl_git_pass(git_iterator_for_index(g_repo, &i)); + cl_git_pass(git_iterator_for_index(repo, &i)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -238,9 +205,9 @@ static const char *expected_index_oids_0[] = { "5819a185d77b03325aaf87cafc771db36f6ddca7", "ff69f8639ce2e6010b3f33a74160aad98b48da2b", "45141a79a77842c59a63229403220a4e4be74e3d", - "45141a79a77842c59a63229403220a4e4be74e3d", - "45141a79a77842c59a63229403220a4e4be74e3d", - "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "fe773770c5a6cc7185580c9204b1ff18a33ff3fc", "99eae476896f4907224978b88e5ecaa6c5bb67a9", "3e42ffc54a663f9401cc25843d6c0e71a33e4249", "e563cf4758f0d646f1b14b76016aa17fa9e549a4", @@ -303,10 +270,9 @@ static void workdir_iterator_test( git_iterator *i; const git_index_entry *entry; int count = 0, count_all = 0; + git_repository *repo = cl_git_sandbox_init(sandbox); - setup_sandbox(sandbox); - - cl_git_pass(git_iterator_for_workdir(g_repo, &i)); + cl_git_pass(git_iterator_for_workdir(repo, &i)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -338,7 +304,7 @@ static void workdir_iterator_test( void test_diff_iterator__workdir_0(void) { - workdir_iterator_test("attr", 24, 2, NULL, "ign"); + workdir_iterator_test("attr", 25, 2, NULL, "ign"); } static const char *status_paths[] = { @@ -351,10 +317,10 @@ static const char *status_paths[] = { "staged_delete_modified_file", "staged_new_file", "staged_new_file_modified_file", + "subdir.txt", "subdir/current_file", "subdir/modified_file", "subdir/new_file", - "subdir.txt", NULL }; diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c new file mode 100644 index 000000000..91e1343cc --- /dev/null +++ b/tests-clar/diff/tree.c @@ -0,0 +1,170 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_tree__initialize(void) +{ + g_repo = cl_git_sandbox_init("attr"); +} + +void test_diff_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_tree__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "605812a"; + const char *b_commit = "370fe9ec22"; + const char *c_commit = "f5b0af1fb4f5c"; + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_tree *c = resolve_commit_oid_to_tree(g_repo, c_commit); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 5); + cl_assert(exp.file_adds == 2); + cl_assert(exp.file_dels == 1); + cl_assert(exp.file_mods == 2); + + cl_assert(exp.hunks == 5); + + cl_assert(exp.lines == 7 + 24 + 1 + 6 + 6); + cl_assert(exp.line_ctxt == 1); + cl_assert(exp.line_adds == 24 + 1 + 5 + 5); + cl_assert(exp.line_dels == 7 + 1); + + git_diff_list_free(diff); + diff = NULL; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 2); + cl_assert(exp.file_adds == 0); + cl_assert(exp.file_dels == 0); + cl_assert(exp.file_mods == 2); + + cl_assert(exp.hunks == 2); + + cl_assert(exp.lines == 8 + 15); + cl_assert(exp.line_ctxt == 1); + cl_assert(exp.line_adds == 1); + cl_assert(exp.line_dels == 7 + 14); + + git_diff_list_free(diff); + + git_tree_free(a); + git_tree_free(b); + git_tree_free(c); +} + +void test_diff_tree__options(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "6bab5c79cd5140d0"; + const char *b_commit = "605812ab7fe421fdd"; + const char *c_commit = "f5b0af1fb4f5"; + const char *d_commit = "a97cc019851"; + + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_tree *c = resolve_commit_oid_to_tree(g_repo, c_commit); + git_tree *d = resolve_commit_oid_to_tree(g_repo, d_commit); + + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects actual; + int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 }; + git_diff_options test_options[] = { + /* a vs b tests */ + { GIT_DIFF_NORMAL, 1, 1, NULL, NULL, {0} }, + { GIT_DIFF_NORMAL, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_REVERSE, 2, 1, NULL, NULL, {0} }, + { GIT_DIFF_FORCE_TEXT, 2, 1, NULL, NULL, {0} }, + /* c vs d tests */ + { GIT_DIFF_NORMAL, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE_EOL, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1, 1, NULL, NULL, {0} }, + }; + /* to generate these values: + * - cd to tests/resources/attr, + * - mv .gitted .git + * - git diff [options] 6bab5c79cd5140d0 605812ab7fe421fdd + * - mv .git .gitted + */ + diff_expects test_expects[] = { + /* a vs b tests */ + { 5, 3, 0, 2, 0, 0, 4, 0, 0, 51, 2, 46, 3 }, + { 5, 3, 0, 2, 0, 0, 4, 0, 0, 53, 4, 46, 3 }, + { 5, 0, 3, 2, 0, 0, 4, 0, 0, 52, 3, 3, 46 }, + { 5, 3, 0, 2, 0, 0, 5, 0, 0, 54, 3, 48, 3 }, + /* c vs d tests */ + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 22, 9, 10, 3 }, + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 19, 12, 7, 0 }, + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 18, 11, 0, 7 }, + { 0 }, + }; + diff_expects *expected; + int i; + + cl_assert(a); + cl_assert(b); + + for (i = 0; test_expects[i].files > 0; i++) { + memset(&actual, 0, sizeof(actual)); /* clear accumulator */ + opts = test_options[i]; + + if (test_ab_or_cd[i] == 0) + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + else + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, d, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &actual, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + expected = &test_expects[i]; + cl_assert_intequal(actual.files, expected->files); + cl_assert_intequal(actual.file_adds, expected->file_adds); + cl_assert_intequal(actual.file_dels, expected->file_dels); + cl_assert_intequal(actual.file_mods, expected->file_mods); + cl_assert_intequal(actual.hunks, expected->hunks); + cl_assert_intequal(actual.lines, expected->lines); + cl_assert_intequal(actual.line_ctxt, expected->line_ctxt); + cl_assert_intequal(actual.line_adds, expected->line_adds); + cl_assert_intequal(actual.line_dels, expected->line_dels); + + git_diff_list_free(diff); + diff = NULL; + } + + git_tree_free(a); + git_tree_free(b); + git_tree_free(c); + git_tree_free(d); +} diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c new file mode 100644 index 000000000..9fefdbb03 --- /dev/null +++ b/tests-clar/diff/workdir.c @@ -0,0 +1,228 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_workdir__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_workdir__to_index(void) +{ + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status + * - git diff + * - mv .git .gitted + */ + cl_assert_intequal(12, exp.files); + cl_assert_intequal(0, exp.file_adds); + cl_assert_intequal(4, exp.file_dels); + cl_assert_intequal(4, exp.file_mods); + cl_assert_intequal(1, exp.file_ignored); + cl_assert_intequal(3, exp.file_untracked); + + cl_assert_intequal(8, exp.hunks); + + cl_assert_intequal(14, exp.lines); + cl_assert_intequal(5, exp.line_ctxt); + cl_assert_intequal(4, exp.line_adds); + cl_assert_intequal(5, exp.line_dels); + + git_diff_list_free(diff); +} + +void test_diff_workdir__to_tree(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_list *diff2 = NULL; + diff_expects exp; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + memset(&exp, 0, sizeof(exp)); + + /* You can't really generate the equivalent of git_diff_workdir_to_tree() + * using C git. It really wants to interpose the index into the diff. + * + * To validate the following results with command line git, I ran the + * following: + * - git ls-tree 26a125 + * - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths + * The results are documented at the bottom of this file in the + * long comment entitled "PREPARATION OF TEST DATA". + */ + cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 13); + cl_assert(exp.file_adds == 0); + cl_assert(exp.file_dels == 4); + cl_assert(exp.file_mods == 4); + cl_assert(exp.file_ignored == 1); + cl_assert(exp.file_untracked == 4); + + /* Since there is no git diff equivalent, let's just assume that the + * text diffs produced by git_diff_foreach are accurate here. We will + * do more apples-to-apples test comparison below. + */ + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + /* This is a compatible emulation of "git diff <sha>" which looks like + * a workdir to tree diff (even though it is not really). This is what + * you would get from "git diff --name-status 26a125ee1bf" + */ + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_list_free(diff2); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 14); + cl_assert(exp.file_adds == 2); + cl_assert(exp.file_dels == 5); + cl_assert(exp.file_mods == 4); + cl_assert(exp.file_ignored == 1); + cl_assert(exp.file_untracked == 2); + + cl_assert(exp.hunks == 11); + + cl_assert(exp.lines == 17); + cl_assert(exp.line_ctxt == 4); + cl_assert(exp.line_adds == 8); + cl_assert(exp.line_dels == 5); + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + /* Again, emulating "git diff <sha>" for testing purposes using + * "git diff --name-status 0017bd4ab1ec3" instead. + */ + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_list_free(diff2); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 15); + cl_assert(exp.file_adds == 5); + cl_assert(exp.file_dels == 4); + cl_assert(exp.file_mods == 3); + cl_assert(exp.file_ignored == 1); + cl_assert(exp.file_untracked == 2); + + cl_assert(exp.hunks == 12); + + cl_assert(exp.lines == 19); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 12); + cl_assert(exp.line_dels == 4); + + git_diff_list_free(diff); + + git_tree_free(a); + git_tree_free(b); +} + +/* PREPARATION OF TEST DATA + * + * Since there is no command line equivalent of git_diff_workdir_to_tree, + * it was a bit of a pain to confirm that I was getting the expected + * results in the first part of this tests. Here is what I ended up + * doing to set my expectation for the file counts and results: + * + * Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows: + * + * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file + * B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted + * C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file + * D 32504b727382542f9f089e24fddac5e78533e96c staged_changes + * E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted + * F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file + * G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted + * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file + * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file + * J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file + * K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file + * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt + * + * -------- + * + * find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths + * + * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file + * M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file + * C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file + * N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file + * D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes + * F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file + * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file + * O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file + * P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file + * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file + * K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file + * Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file + * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt + * + * -------- + * + * A - current_file (UNMODIFIED) -> not in results + * B D file_deleted + * M I ignored_file (IGNORED) + * C M modified_file + * N U new_file (UNTRACKED) + * D M staged_changes + * E D staged_changes_file_deleted + * F M staged_changes_modified_file + * G D staged_delete_file_deleted + * H - staged_delete_modified_file (UNMODIFIED) -> not in results + * O U staged_new_file + * P U staged_new_file_modified_file + * I - subdir/current_file (UNMODIFIED) -> not in results + * J D subdir/deleted_file + * K M subdir/modified_file + * Q U subdir/new_file + * L - subdir.txt (UNMODIFIED) -> not in results + * + * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR + */ diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c new file mode 100644 index 000000000..0b87b2b46 --- /dev/null +++ b/tests-clar/object/blob/filter.c @@ -0,0 +1,125 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "filter.h" + +static git_repository *g_repo = NULL; +#define NUM_TEST_OBJECTS 6 +static git_oid g_oids[NUM_TEST_OBJECTS]; +static const char *g_raw[NUM_TEST_OBJECTS] = { + "", + "foo\nbar\n", + "foo\rbar\r", + "foo\r\nbar\r\n", + "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", + "123\n\000\001\002\003\004abc\255\254\253\r\n" +}; +static int g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17 }; +static git_text_stats g_stats[NUM_TEST_OBJECTS] = { + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 2, 0, 6, 0 }, + { 0, 2, 0, 0, 6, 0 }, + { 0, 2, 2, 2, 6, 0 }, + { 0, 4, 4, 1, 31, 0 }, + { 1, 1, 2, 1, 9, 5 } +}; +static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { + { "", 0, 0 }, + { "foo\nbar\n", 0, 8 }, + { "foo\rbar\r", 0, 8 }, + { "foo\nbar\n", 0, 8 }, + { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, + { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 } +}; + +void test_object_blob_filter__initialize(void) +{ + int i; + + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(p_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_git_pass(git_repository_open(&g_repo, "empty_standard_repo")); + + for (i = 0; i < NUM_TEST_OBJECTS; i++) { + size_t len = (g_len[i] < 0) ? strlen(g_raw[i]) : (size_t)g_len[i]; + g_len[i] = (int)len; + + cl_git_pass( + git_blob_create_frombuffer(&g_oids[i], g_repo, g_raw[i], len) + ); + } +} + +void test_object_blob_filter__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + cl_fixture_cleanup("empty_standard_repo"); +} + +void test_object_blob_filter__unfiltered(void) +{ + int i; + git_blob *blob; + + for (i = 0; i < NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); + cl_assert((size_t)g_len[i] == git_blob_rawsize(blob)); + cl_assert(memcmp(git_blob_rawcontent(blob), g_raw[i], g_len[i]) == 0); + git_blob_free(blob); + } +} + +void test_object_blob_filter__stats(void) +{ + int i; + git_blob *blob; + git_buf buf = GIT_BUF_INIT; + git_text_stats stats; + + for (i = 0; i < NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); + cl_git_pass(git_blob__getbuf(&buf, blob)); + git_text_gather_stats(&stats, &buf); + cl_assert(memcmp(&g_stats[i], &stats, sizeof(stats)) == 0); + git_blob_free(blob); + } + + git_buf_free(&buf); +} + +void test_object_blob_filter__to_odb(void) +{ + git_vector filters = GIT_VECTOR_INIT; + git_config *cfg; + int i; + git_blob *blob; + git_buf orig = GIT_BUF_INIT, out = GIT_BUF_INIT; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_assert(cfg); + + git_attr_cache_flush(g_repo); + cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n"); + + cl_assert(git_filters_load( + &filters, g_repo, "filename.txt", GIT_FILTER_TO_ODB) > 0); + cl_assert(filters.length == 1); + + for (i = 0; i < NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); + cl_git_pass(git_blob__getbuf(&orig, blob)); + + cl_git_pass(git_filters_apply(&out, &orig, &filters)); + cl_assert(git_buf_cmp(&out, &g_crlf_filtered[i]) == 0); + + git_blob_free(blob); + } + + git_filters_free(&filters); + git_buf_free(&orig); + git_buf_free(&out); + git_config_free(cfg); +} + diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c index 67aecba31..99cb9e8b8 100644 --- a/tests-clar/status/ignore.c +++ b/tests-clar/status/ignore.c @@ -7,21 +7,12 @@ static git_repository *g_repo = NULL; void test_status_ignore__initialize(void) { - /* Before each test, instantiate the attr repo from the fixtures and - * rename the .gitted to .git so it is a repo with a working dir. Also - * rename gitignore to .gitignore. - */ - cl_fixture_sandbox("attr"); - cl_git_pass(p_rename("attr/.gitted", "attr/.git")); - cl_git_pass(p_rename("attr/gitignore", "attr/.gitignore")); - cl_git_pass(git_repository_open(&g_repo, "attr/.git")); + g_repo = cl_git_sandbox_init("attr"); } void test_status_ignore__cleanup(void) { - git_repository_free(g_repo); - g_repo = NULL; - cl_fixture_cleanup("attr"); + cl_git_sandbox_cleanup(); } void test_status_ignore__0(void) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index d8e62a94d..132ec1fc1 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -5,12 +5,6 @@ /** - * Test fixtures - */ -static git_repository *_repository = NULL; - - -/** * Auxiliary methods */ static int @@ -37,48 +31,27 @@ exit: } static int -cb_status__count(const char *GIT_UNUSED(p), unsigned int GIT_UNUSED(s), void *payload) +cb_status__count(const char *p, unsigned int s, void *payload) { volatile int *count = (int *)payload; - GIT_UNUSED_ARG(p); - GIT_UNUSED_ARG(s); + GIT_UNUSED(p); + GIT_UNUSED(s); - *count++; + (*count)++; return GIT_SUCCESS; } - /** * Initializer * - * This method is called once before starting each - * test, and will load the required fixtures + * Not all of the tests in this file use the same fixtures, so we allow each + * test to load their fixture at the top of the test function. */ void test_status_worktree__initialize(void) { - /* - * Sandbox the `status/` repository from our Fixtures. - * This will copy the whole folder to our sandbox, - * so now it can be accessed with `./status` - */ - cl_fixture_sandbox("status"); - - /* - * Rename `status/.gitted` to `status/.git` - * We do this because we cannot store a folder named `.git` - * inside the fixtures folder in our libgit2 repo. - */ - cl_git_pass( - p_rename("status/.gitted", "status/.git") - ); - - /* - * Open the sandboxed "status" repository - */ - cl_git_pass(git_repository_open(&_repository, "status/.git")); } /** @@ -89,10 +62,7 @@ void test_status_worktree__initialize(void) */ void test_status_worktree__cleanup(void) { - git_repository_free(_repository); - _repository = NULL; - - cl_fixture_cleanup("status"); + cl_git_sandbox_cleanup(); } /** @@ -101,6 +71,7 @@ void test_status_worktree__cleanup(void) void test_status_worktree__whole_repository(void) { struct status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); memset(&counts, 0x0, sizeof(struct status_entry_counts)); counts.expected_entry_count = entry_count0; @@ -108,7 +79,7 @@ void test_status_worktree__whole_repository(void) counts.expected_statuses = entry_statuses0; cl_git_pass( - git_status_foreach(_repository, cb_status__normal, &counts) + git_status_foreach(repo, cb_status__normal, &counts) ); cl_assert(counts.entry_count == counts.expected_entry_count); @@ -119,8 +90,10 @@ void test_status_worktree__whole_repository(void) void test_status_worktree__empty_repository(void) { int count = 0; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); - git_status_foreach(_repository, cb_status__count, &count); cl_assert(count == 0); } @@ -128,10 +101,11 @@ void test_status_worktree__single_file(void) { int i; unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); for (i = 0; i < (int)entry_count0; i++) { cl_git_pass( - git_status_file(&status_flags, _repository, entry_paths0[i]) + git_status_file(&status_flags, repo, entry_paths0[i]) ); cl_assert(entry_statuses0[i] == status_flags); } @@ -140,15 +114,22 @@ void test_status_worktree__single_file(void) void test_status_worktree__ignores(void) { int i, ignored; + git_repository *repo = cl_git_sandbox_init("status"); for (i = 0; i < (int)entry_count0; i++) { - cl_git_pass(git_status_should_ignore(_repository, entry_paths0[i], &ignored)); + cl_git_pass( + git_status_should_ignore(repo, entry_paths0[i], &ignored) + ); cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED)); } - cl_git_pass(git_status_should_ignore(_repository, "nonexistent_file", &ignored)); + cl_git_pass( + git_status_should_ignore(repo, "nonexistent_file", &ignored) + ); cl_assert(!ignored); - cl_git_pass(git_status_should_ignore(_repository, "ignored_nonexistent_file", &ignored)); + cl_git_pass( + git_status_should_ignore(repo, "ignored_nonexistent_file", &ignored) + ); cl_assert(ignored); } diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index Binary files differindex c52747e0b..19fa99d5b 100644 --- a/tests/resources/attr/.gitted/index +++ b/tests/resources/attr/.gitted/index diff --git a/tests/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD index f518a465a..68fcff2c5 100644 --- a/tests/resources/attr/.gitted/logs/HEAD +++ b/tests/resources/attr/.gitted/logs/HEAD @@ -1,3 +1,6 @@ 0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data 6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates 605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests +a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer <arrbee@arrbee.com> 1327611749 -0800 commit: Updating files so we can do diffs +370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer <arrbee@arrbee.com> 1327621027 -0800 commit: Updating test data +f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer <arrbee@arrbee.com> 1328653313 -0800 commit: Some whitespace only changes for testing purposes diff --git a/tests/resources/attr/.gitted/logs/refs/heads/master b/tests/resources/attr/.gitted/logs/refs/heads/master index f518a465a..68fcff2c5 100644 --- a/tests/resources/attr/.gitted/logs/refs/heads/master +++ b/tests/resources/attr/.gitted/logs/refs/heads/master @@ -1,3 +1,6 @@ 0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data 6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates 605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests +a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer <arrbee@arrbee.com> 1327611749 -0800 commit: Updating files so we can do diffs +370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer <arrbee@arrbee.com> 1327621027 -0800 commit: Updating test data +f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer <arrbee@arrbee.com> 1328653313 -0800 commit: Some whitespace only changes for testing purposes diff --git a/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e b/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e Binary files differnew file mode 100644 index 000000000..edcf7520c --- /dev/null +++ b/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e diff --git a/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Binary files differnew file mode 100644 index 000000000..9c37c5946 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a diff --git a/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 Binary files differnew file mode 100644 index 000000000..c74add826 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 diff --git a/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d b/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d new file mode 100644 index 000000000..eb1e8d0c5 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d @@ -0,0 +1,2 @@ +x
@WŶ +|k 9n$}g:;51e4\k_]ރ٭hDk'~
\ No newline at end of file diff --git a/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd b/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd Binary files differnew file mode 100644 index 000000000..e832241c9 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd diff --git a/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 Binary files differnew file mode 100644 index 000000000..a80265cac --- /dev/null +++ b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 diff --git a/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 Binary files differnew file mode 100644 index 000000000..efa62f912 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 diff --git a/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba b/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba new file mode 100644 index 000000000..1a7ec0c55 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba @@ -0,0 +1,2 @@ +xQj0DS[hc;PJ( $qޠ_3oIK+BtI|LgƈŐR4'=qFN64 +J1FrzW[rV6-i7.eVW;X,
mwl|]ṬMɢdRwC[W9sj~Wy
\ No newline at end of file diff --git a/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 b/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 new file mode 100644 index 000000000..589f9ad31 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 @@ -0,0 +1,2 @@ +x5A +0D]SεouJ~L0ͯ)xcfp]OOΊcB6!뢘ó{,U<Cj[E--&#)~=;;{.e"3A
\ No newline at end of file diff --git a/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 b/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 new file mode 100644 index 000000000..21faeb8a2 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 @@ -0,0 +1,2 @@ +xN[j1̷O4RbPJ +=;N
A?y 1y~7Z(2be8uJanF.H"UD_HIsvZwL=0TZG_UbKo̮}cv?h<aoԵ_EK
\ No newline at end of file diff --git a/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc b/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc new file mode 100644 index 000000000..e6fcbc0b3 --- /dev/null +++ b/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc @@ -0,0 +1 @@ +x5A09xBAGvդTУ坝<#1UT釛*MWlOCR2dѵC.TIlQH/mY۬UN[߬B@t8~}}R#č#kAdD_=-H
\ No newline at end of file diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master index 0516af2d2..7f8bbe3e7 100644 --- a/tests/resources/attr/.gitted/refs/heads/master +++ b/tests/resources/attr/.gitted/refs/heads/master @@ -1 +1 @@ -a5d76cad53f66f1312bd995909a5bab3c0820770 +a97cc019851d401a4f1d091cb91a15890a0dd1ba diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes index c0c2a56d0..e038983ec 100644 --- a/tests/resources/attr/gitattributes +++ b/tests/resources/attr/gitattributes @@ -23,3 +23,7 @@ macro* macro2 macro2 macro2 [attr]thirdmacro secondmacro=hahaha macro_bad firstmacro secondmacro thirdmacro + +# another test that Peff found was failing +[attr]notest !test + diff --git a/tests/resources/attr/root_test2 b/tests/resources/attr/root_test2 index 45141a79a..4d713dc48 100644 --- a/tests/resources/attr/root_test2 +++ b/tests/resources/attr/root_test2 @@ -1 +1,6 @@ Hello from the root + +Some additional lines + +Down here below + diff --git a/tests/resources/attr/root_test3 b/tests/resources/attr/root_test3 index 45141a79a..108bb4e7f 100644 --- a/tests/resources/attr/root_test3 +++ b/tests/resources/attr/root_test3 @@ -1 +1,19 @@ -Hello from the root +Some additional lines + + + Down here below the other lines + + +With even more at the end + + +And lots of good stuff + + +Anywhere you want + + +Don't you think + + + diff --git a/tests/resources/attr/root_test4.txt b/tests/resources/attr/root_test4.txt index fb5067b1a..fe773770c 100644 --- a/tests/resources/attr/root_test4.txt +++ b/tests/resources/attr/root_test4.txt @@ -1 +1,14 @@ -Hello again +Here is some stuff at the start + +This should go in one hunk + +Some additional lines + +Down here below the other lines + +With even more at the end + +Followed by a second hunk of stuff + +That happens down here + diff --git a/tests/resources/attr/sub/sub/.gitattributes b/tests/resources/attr/sub/sub/.gitattributes new file mode 100644 index 000000000..55225e4d6 --- /dev/null +++ b/tests/resources/attr/sub/sub/.gitattributes @@ -0,0 +1,3 @@ +d/* test=a/b/d/* +d/yes notest + diff --git a/tests/resources/status/.gitted/index b/tests/resources/status/.gitted/index Binary files differindex d793791c9..9a383ec0c 100644 --- a/tests/resources/status/.gitted/index +++ b/tests/resources/status/.gitted/index diff --git a/tests/t00-core.c b/tests/t00-core.c index 58f048af6..7f142ba21 100644 --- a/tests/t00-core.c +++ b/tests/t00-core.c @@ -424,10 +424,10 @@ static walk_data empty = { GIT_BUF_INIT }; -static int dont_call_me(void *GIT_UNUSED(state), git_buf *GIT_UNUSED(path)) +static int dont_call_me(void *state, git_buf *path) { - GIT_UNUSED_ARG(state) - GIT_UNUSED_ARG(path) + GIT_UNUSED(state); + GIT_UNUSED(path); return GIT_ERROR; } diff --git a/tests/t07-hashtable.c b/tests/t07-hashtable.c index 41d52af19..6beaeac68 100644 --- a/tests/t07-hashtable.c +++ b/tests/t07-hashtable.c @@ -154,7 +154,6 @@ BEGIN_TEST(tableit0, "iterate through all the contents of the table") const int objects_n = 32; int i; table_item *objects, *ob; - const void *GIT_UNUSED(_unused); git_hashtable *table = NULL; @@ -170,9 +169,7 @@ BEGIN_TEST(tableit0, "iterate through all the contents of the table") must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i]))); } - GIT_HASHTABLE_FOREACH(table, _unused, ob, - ob->visited = 1; - ); + GIT_HASHTABLE_FOREACH_VALUE(table, ob, ob->visited = 1); /* make sure all nodes have been visited */ for (i = 0; i < objects_n; ++i) diff --git a/tests/t18-status.c b/tests/t18-status.c index 270aa7b46..2b90ac6f4 100644 --- a/tests/t18-status.c +++ b/tests/t18-status.c @@ -156,14 +156,14 @@ BEGIN_TEST(statuscb0, "test retrieving status for worktree of repository") git_futils_rmdir_r(TEMP_REPO_FOLDER, 1); END_TEST -static int status_cb1(const char *GIT_UNUSED(path), unsigned int GIT_UNUSED(status_flags), void *payload) +static int status_cb1(const char *path, unsigned int status_flags, void *payload) { int *count = (int *)payload;; - GIT_UNUSED_ARG(path); - GIT_UNUSED_ARG(status_flags); + GIT_UNUSED(path); + GIT_UNUSED(status_flags); - (void) *count++; + (*count)++; return GIT_SUCCESS; } diff --git a/tests/test_helpers.c b/tests/test_helpers.c index 42c8031cd..837358453 100644 --- a/tests/test_helpers.c +++ b/tests/test_helpers.c @@ -182,7 +182,7 @@ int cmp_objects(git_rawobj *o, object_data *d) int copy_file(const char *src, const char *dst) { - git_fbuffer source_buf; + git_buf source_buf = GIT_BUF_INIT; git_file dst_fd; int error = GIT_ERROR; @@ -193,10 +193,10 @@ int copy_file(const char *src, const char *dst) if (dst_fd < 0) goto cleanup; - error = p_write(dst_fd, source_buf.data, source_buf.len); + error = p_write(dst_fd, source_buf.ptr, source_buf.size); cleanup: - git_futils_freebuffer(&source_buf); + git_buf_free(&source_buf); p_close(dst_fd); return error; @@ -204,22 +204,23 @@ cleanup: int cmp_files(const char *a, const char *b) { - git_fbuffer buf_a, buf_b; + git_buf buf_a = GIT_BUF_INIT; + git_buf buf_b = GIT_BUF_INIT; int error = GIT_ERROR; if (git_futils_readbuffer(&buf_a, a) < GIT_SUCCESS) return GIT_ERROR; if (git_futils_readbuffer(&buf_b, b) < GIT_SUCCESS) { - git_futils_freebuffer(&buf_a); + git_buf_free(&buf_a); return GIT_ERROR; } - if (buf_a.len == buf_b.len && !memcmp(buf_a.data, buf_b.data, buf_a.len)) + if (buf_a.size == buf_b.size && !memcmp(buf_a.ptr, buf_b.ptr, buf_a.size)) error = GIT_SUCCESS; - git_futils_freebuffer(&buf_a); - git_futils_freebuffer(&buf_b); + git_buf_free(&buf_a); + git_buf_free(&buf_b); return error; } diff --git a/tests/test_main.c b/tests/test_main.c index 732d25a9d..50256e97c 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -70,12 +70,12 @@ int __cdecl #else int #endif -main(int GIT_UNUSED(argc), char *GIT_UNUSED(argv[])) +main(int argc, char *argv[]) { unsigned int i, failures; - GIT_UNUSED_ARG(argc); - GIT_UNUSED_ARG(argv); + GIT_UNUSED(argc); + GIT_UNUSED(argv); git_threads_init(); |
