diff --git a/include/git2/diff.h b/include/git2/diff.h new file mode 100644 index 000000000..1d3a8d408 --- /dev/null +++ b/include/git2/diff.h @@ -0,0 +1,103 @@ +/* + * 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. + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +typedef int (*git_diff_file_fn)( + void *cb_data, + const git_oid *old, + const char *old_path, + int old_mode, + const git_oid *new, /* hashed object if from work tree */ + const char *new_path, + int new_mode); + +typedef int (*git_diff_hunk_fn)( + void *cb_data, + int old_start, + int old_lines, + int new_start, + int new_lines); + +#define GIT_DIFF_LINE_CONTEXT 0 +#define GIT_DIFF_LINE_ADDITION 1 +#define GIT_DIFF_LINE_DELETION 2 + +typedef int (*git_diff_line_fn)( + void *cb_data, + int origin, /* GIT_DIFF_LINE value from above */ + const char *content, + size_t content_len); + +typedef struct { + int context_lines; + int interhunk_lines; + int ignore_whitespace; + + git_diff_file_fn file_cb; + git_diff_hunk_fn hunk_cb; + git_diff_line_fn line_cb; + void *cb_data; +} git_diff_opts; + + +GIT_EXTERN(int) git_diff_blobs( + git_repository *repo, + git_blob *old, + git_blob *new, + git_diff_opts *options); + +GIT_EXTERN(int) git_diff_trees( + git_repository *repo, + git_tree *old, + git_tree *new, + git_diff_opts *options); + +GIT_EXTERN(int) git_diff_index( + git_repository *repo, + git_tree *old, + git_diff_opts *options); + +/* pass NULL for the git_tree to diff workdir against index */ +GIT_EXTERN(int) git_diff_workdir( + git_repository *repo, + git_tree *old, + git_diff_opts *options); + +GIT_EXTERN(int) git_diff_workdir_file( + git_repository *repo, + git_blob *old, + const char *path, + git_diff_opts *options); + +/* pass git_objects to diff against or NULL for index. + * can handle: blob->blob, tree->index, tree->tree + * it will be an error if object types don't match + */ +/* pass git_object to diff WT against or NULL for index + * can handle: index->wt, tree->wt, blob->wt with path + */ + +GIT_END_DECL + +/** @} */ + +#endif diff --git a/src/diff.c b/src/diff.c new file mode 100644 index 000000000..7128b7c76 --- /dev/null +++ b/src/diff.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009-2011 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 "xdiff/xdiff.h" +#include "blob.h" +#include + +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; + git_diff_opts *opts = priv; + + if (len == 1) { + int ostart = -1, olen = 0, nstart = -1, nlen = 0; + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (opts->hunk_cb && bufs[0].ptr[0] == '@') { + const char *scan = bufs[0].ptr; + if (!(err = read_next_int(&scan, &ostart)) && *scan == ',') + err = read_next_int(&scan, &olen); + if (!err && !(err = read_next_int(&scan, &nstart)) && *scan == ',') + err = read_next_int(&scan, &nlen); + if (!err && ostart >= 0 && nstart >= 0) + err = opts->hunk_cb( + opts->cb_data, ostart, olen, nstart, nlen); + } + } + else if (len == 2 || len == 3) { + 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; + + if (opts->line_cb) + err = opts->line_cb( + opts->cb_data, origin, bufs[1].ptr, bufs[1].size); + } + + return err; +} + +int git_diff_blobs( + git_repository *repo, + git_blob *old_blob, + git_blob *new_blob, + git_diff_opts *options) +{ + mmfile_t old, new; + xpparam_t params; + xdemitconf_t cfg; + xdemitcb_t callback; + + assert(repo && old_blob && new_blob && options); + + old.ptr = (char *)git_blob_rawcontent(old_blob); + old.size = git_blob_rawsize(old_blob); + + new.ptr = (char *)git_blob_rawcontent(new_blob); + new.size = git_blob_rawsize(new_blob); + + memset(¶ms, 0, sizeof(params)); + + memset(&cfg, 0, sizeof(cfg)); + cfg.ctxlen = options->context_lines || 3; + cfg.interhunkctxlen = options->interhunk_lines || 3; + if (options->ignore_whitespace) + cfg.flags |= XDF_WHITESPACE_FLAGS; + + memset(&callback, 0, sizeof(callback)); + callback.outf = diff_output_cb; + callback.priv = options; + + if (options->file_cb) + options->file_cb( + options->cb_data, + git_object_id((const git_object *)old_blob), NULL, 010644, + git_object_id((const git_object *)new_blob), NULL, 010644); + + xdl_diff(&old, &new, ¶ms, &cfg, &callback); + + return GIT_SUCCESS; +} + diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h index 526ccb344..2928d329b 100644 --- a/src/xdiff/xinclude.h +++ b/src/xdiff/xinclude.h @@ -26,10 +26,14 @@ #include #include #include -#include #include #include +#ifdef WIN32 +#else +#include +#endif + #include "xmacros.h" #include "xdiff.h" #include "xtypes.h" diff --git a/tests-clay/diff/blob.c b/tests-clay/diff/blob.c new file mode 100644 index 000000000..2fb3e7740 --- /dev/null +++ b/tests-clay/diff/blob.c @@ -0,0 +1,181 @@ +#include "clay_libgit2.h" +#include "fileops.h" +#include "git2/diff.h" + +static git_repository *g_repo = NULL; + +void test_diff_blob__initialize(void) +{ + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + cl_git_pass(p_rename("attr/gitattributes", "attr/.gitattributes")); + cl_git_pass(git_repository_open(&g_repo, "attr/.git")); +} + +void test_diff_blob__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + cl_fixture_cleanup("attr"); +} + +typedef struct { + int files; + int hunks; + int hunk_new_lines; + int hunk_old_lines; + int lines; + int line_ctxt; + int line_adds; + int line_dels; +} diff_expects; + +static void log(const char *str, int n) +{ + FILE *fp = fopen("/Users/rb/tmp/diff.log", "a"); + if (n > 0) + fprintf(fp, "%.*s", n, str); + else + fputs(str, fp); + fclose(fp); +} + +static int diff_file_fn( + void *cb_data, + const git_oid *old, + const char *old_path, + int old_mode, + const git_oid *new, + const char *new_path, + int new_mode) +{ + diff_expects *e = cb_data; + e->files++; + log("-- file --\n", 0); + return 0; +} + +static int diff_hunk_fn( + void *cb_data, + int old_start, + int old_lines, + int new_start, + int new_lines) +{ + diff_expects *e = cb_data; + e->hunks++; + e->hunk_old_lines += old_lines; + e->hunk_new_lines += new_lines; + log("-- hunk --\n", 0); + return 0; +} + +static int diff_line_fn( + void *cb_data, + int origin, + const char *content, + size_t content_len) +{ + diff_expects *e = cb_data; + e->lines++; + switch (origin) { + case GIT_DIFF_LINE_CONTEXT: + log("[ ]", 3); + e->line_ctxt++; + break; + case GIT_DIFF_LINE_ADDITION: + log("[+]", 3); + e->line_adds++; + break; + case GIT_DIFF_LINE_DELETION: + log("[-]", 3); + e->line_dels++; + break; + default: + cl_assert("Unknown diff line origin" == 0); + } + log(content, content_len); + return 0; +} + +void test_diff_blob__0(void) +{ + int err; + git_blob *a, *b, *c, *d; + git_oid a_oid, b_oid, c_oid, d_oid; + git_diff_opts opts; + 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 `diff -U 2` on these files */ + + opts.context_lines = 2; + opts.interhunk_lines = 0; + opts.ignore_whitespace = 0; + opts.file_cb = diff_file_fn; + opts.hunk_cb = diff_hunk_fn; + opts.line_cb = diff_line_fn; + opts.cb_data = &exp; + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_blobs(g_repo, a, b, &opts)); + + cl_assert(exp.files == 1); + 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)); + + cl_assert(exp.files == 1); + 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)); + + cl_assert(exp.files == 1); + 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 = 2; + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_blobs(g_repo, c, d, &opts)); + + cl_assert(exp.files == 1); + cl_assert(exp.hunks == 2); + cl_assert(exp.lines == 16); + cl_assert(exp.line_ctxt == 6); + cl_assert(exp.line_adds == 6); + cl_assert(exp.line_dels == 4); + + git_blob_free(a); + git_blob_free(b); + git_blob_free(c); +} + diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index index c52747e0b..f35d3005e 100644 Binary files a/tests/resources/attr/.gitted/index and b/tests/resources/attr/.gitted/index differ diff --git a/tests/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD index f518a465a..6d096351c 100644 Binary files a/tests/resources/attr/.gitted/logs/HEAD and b/tests/resources/attr/.gitted/logs/HEAD differ diff --git a/tests/resources/attr/.gitted/logs/refs/heads/master b/tests/resources/attr/.gitted/logs/refs/heads/master index f518a465a..6d096351c 100644 Binary files a/tests/resources/attr/.gitted/logs/refs/heads/master and b/tests/resources/attr/.gitted/logs/refs/heads/master differ diff --git a/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a new file mode 100644 index 000000000..9c37c5946 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a differ diff --git a/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 new file mode 100644 index 000000000..c74add826 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 differ 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 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d differ diff --git a/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 new file mode 100644 index 000000000..a80265cac Binary files /dev/null and b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 differ diff --git a/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 new file mode 100644 index 000000000..efa62f912 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 differ 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 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 differ 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 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 differ 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 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc differ diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master index 0516af2d2..9eca42413 100644 Binary files a/tests/resources/attr/.gitted/refs/heads/master and b/tests/resources/attr/.gitted/refs/heads/master differ diff --git a/tests/resources/attr/root_test2 b/tests/resources/attr/root_test2 index 45141a79a..4d713dc48 100644 Binary files a/tests/resources/attr/root_test2 and b/tests/resources/attr/root_test2 differ diff --git a/tests/resources/attr/root_test3 b/tests/resources/attr/root_test3 index 45141a79a..c96bbb2c2 100644 Binary files a/tests/resources/attr/root_test3 and b/tests/resources/attr/root_test3 differ diff --git a/tests/resources/attr/root_test4.txt b/tests/resources/attr/root_test4.txt index fb5067b1a..fe773770c 100644 Binary files a/tests/resources/attr/root_test4.txt and b/tests/resources/attr/root_test4.txt differ