summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/diff.c43
-rw-r--r--include/git2/diff.h101
-rw-r--r--include/git2/oid.h5
-rw-r--r--include/git2/status.h2
-rw-r--r--src/diff.c1347
-rw-r--r--src/diff.h10
-rw-r--r--src/diff_output.c722
-rw-r--r--src/fileops.c13
-rw-r--r--src/fileops.h17
-rw-r--r--src/iterator.c97
-rw-r--r--src/iterator.h6
-rw-r--r--src/oid.c10
-rw-r--r--src/path.c43
-rw-r--r--src/path.h22
-rw-r--r--src/posix.h2
-rw-r--r--src/status.c2
-rw-r--r--src/unix/posix.h1
-rw-r--r--src/vector.c10
-rw-r--r--src/vector.h6
-rw-r--r--src/win32/dir.c12
-rw-r--r--src/win32/dir.h6
-rw-r--r--src/xdiff/xdiff.h4
-rw-r--r--tests-clar/clar_libgit2.h5
-rw-r--r--tests-clar/diff/diff_helpers.c14
-rw-r--r--tests-clar/diff/diff_helpers.h2
-rw-r--r--tests-clar/diff/iterator.c4
-rw-r--r--tests-clar/diff/tree.c46
-rw-r--r--tests-clar/diff/workdir.c230
-rw-r--r--tests-clar/status/status_data.h2
-rw-r--r--tests-clar/status/worktree.c2
-rw-r--r--tests/t18-status.c6
31 files changed, 1705 insertions, 1087 deletions
diff --git a/examples/diff.c b/examples/diff.c
index 5eb0f3179..f80f7029c 100644
--- a/examples/diff.c
+++ b/examples/diff.c
@@ -116,7 +116,7 @@ void usage(const char *message, const char *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");
+ fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
exit(1);
}
@@ -127,7 +127,7 @@ int main(int argc, char *argv[])
git_tree *t1 = NULL, *t2 = NULL;
git_diff_options opts = {0};
git_diff_list *diff;
- int i, color = -1, compact = 0;
+ int i, color = -1, compact = 0, cached = 0;
char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL;
/* parse arguments as copied from git-diff */
@@ -146,6 +146,8 @@ int main(int argc, char *argv[])
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"))
@@ -162,6 +164,10 @@ int main(int argc, char *argv[])
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=",
@@ -171,9 +177,6 @@ int main(int argc, char *argv[])
usage("Unknown arg", a);
}
- if (!treeish1)
- usage("Must provide at least one tree identifier (for now)", NULL);
-
/* open repo */
check(git_repository_discover(path, sizeof(path), dir, 0, "/"),
@@ -181,20 +184,40 @@ int main(int argc, char *argv[])
check(git_repository_open(&repo, path),
"Could not open repository");
- check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
+ 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");
- if (!treeish2)
- check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Generating diff");
+ /* <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_tree_to_tree(repo, &opts, t1, t2, &diff), "Generating diff");
+ 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 summary");
+ check(git_diff_print_compact(diff, &color, printer), "Displaying diff");
else
check(git_diff_print_patch(diff, &color, printer), "Displaying diff");
diff --git a/include/git2/diff.h b/include/git2/diff.h
index e9ef5c356..413de8d98 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -37,7 +37,9 @@ enum {
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_PATIENCE = (1 << 6),
+ GIT_DIFF_INCLUDE_IGNORED = (1 << 7),
+ GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8)
};
/**
@@ -63,6 +65,26 @@ typedef struct {
*/
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)
+};
+
+/**
+ * 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.
*
@@ -77,17 +99,11 @@ typedef struct git_diff_list git_diff_list;
* It will just use the git attributes for those files.
*/
typedef struct {
+ git_diff_file old;
+ git_diff_file new;
git_status_t status; /**< value from tree.h */
- unsigned int old_attr;
- unsigned int new_attr;
- git_oid old_oid;
- git_oid new_oid;
- git_blob *old_blob;
- git_blob *new_blob;
- const char *path;
- const char *new_path; /**< NULL unless status is RENAMED or COPIED */
- int similarity; /**< for RENAMED and COPIED, value from 0 to 100 */
- int binary; /**< files in diff are binary? */
+ unsigned int similarity; /**< for RENAMED and COPIED, value from 0 to 100 */
+ int binary;
} git_diff_delta;
/**
@@ -170,7 +186,18 @@ typedef int (*git_diff_output_fn)(
/**@{*/
/**
+ * 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 A git_tree object to diff from.
+ * @param new 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,
@@ -181,7 +208,11 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
/**
* Compute a difference between a tree and the index.
- * @todo NOT IMPLEMENTED
+ *
+ * @param repo The repository containing the tree and index.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ * @param old 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,
@@ -190,28 +221,56 @@ GIT_EXTERN(int) git_diff_index_to_tree(
git_diff_list **diff);
/**
- * Compute a difference between the working directory and a tree.
- * @todo NOT IMPLEMENTED
+ * 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_tree(
+GIT_EXTERN(int) git_diff_workdir_to_index(
git_repository *repo,
const git_diff_options *opts, /**< can be NULL for defaults */
- git_tree *old,
git_diff_list **diff);
/**
- * Compute a difference between the working directory and the index.
- * @todo NOT IMPLEMENTED
+ * 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 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_index(
+GIT_EXTERN(int) git_diff_workdir_to_tree(
git_repository *repo,
const git_diff_options *opts, /**< can be NULL for defaults */
+ git_tree *old,
git_diff_list **diff);
/**
- * Deallocate a diff list.
+ * 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(void) git_diff_list_free(git_diff_list *diff);
+GIT_EXTERN(int) git_diff_merge(
+ git_diff_list *onto,
+ const git_diff_list *from);
/**@}*/
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..31823c6c5 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_WT_IGNORED (1 << 6)
/**
* Gather file statuses and run a callback for each one.
diff --git a/src/diff.c b/src/diff.c
index cfa34c138..9e4105571 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1,194 +1,203 @@
/*
- * Copyright (C) 2009-2011 the libgit2 contributors
+ * 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 "xdiff/xdiff.h"
-#include "blob.h"
-#include "ignore.h"
-#include <ctype.h>
+#include "fileops.h"
-static void file_delta_free(git_diff_delta *delta)
+static void diff_delta__free(git_diff_delta *delta)
{
if (!delta)
return;
- if (delta->new_path != delta->path) {
- git__free((char *)delta->new_path);
- delta->new_path = NULL;
+ if (delta->new.flags & GIT_DIFF_FILE_FREE_PATH) {
+ git__free((char *)delta->new.path);
+ delta->new.path = NULL;
}
- git__free((char *)delta->path);
- delta->path = NULL;
+ if (delta->old.flags & GIT_DIFF_FILE_FREE_PATH) {
+ git__free((char *)delta->old.path);
+ delta->old.path = NULL;
+ }
git__free(delta);
}
-static int file_delta_new__from_one(
+static git_diff_delta *diff_delta__alloc(
git_diff_list *diff,
- git_status_t status,
- mode_t attr,
- const git_oid *oid,
- const char *path)
+ git_status_t status,
+ const char *path)
{
- int error;
git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
-
- /* This fn is just for single-sided diffs */
- assert(status == GIT_STATUS_ADDED || status == GIT_STATUS_DELETED);
-
if (!delta)
- return git__rethrow(GIT_ENOMEM, "Could not allocate diff record");
+ return NULL;
- if ((delta->path = git__strdup(path)) == NULL) {
+ delta->old.path = git__strdup(path);
+ if (delta->old.path == NULL) {
git__free(delta);
- return git__rethrow(GIT_ENOMEM, "Could not allocate diff record path");
+ return NULL;
}
+ delta->old.flags |= GIT_DIFF_FILE_FREE_PATH;
+ delta->new.path = delta->old.path;
- if (diff->opts.flags & GIT_DIFF_REVERSE)
- status = (status == GIT_STATUS_ADDED) ?
- GIT_STATUS_DELETED : GIT_STATUS_ADDED;
-
+ if (diff->opts.flags & GIT_DIFF_REVERSE) {
+ switch (status) {
+ case GIT_STATUS_ADDED: status = GIT_STATUS_DELETED; break;
+ case GIT_STATUS_DELETED: status = GIT_STATUS_ADDED; break;
+ default: break; /* leave other status values alone */
+ }
+ }
delta->status = status;
- if (status == GIT_STATUS_ADDED) {
- delta->new_attr = attr;
- if (oid != NULL)
- git_oid_cpy(&delta->new_oid, oid);
- } else {
- delta->old_attr = attr;
- if (oid != NULL)
- git_oid_cpy(&delta->old_oid, oid);
+ 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.path = git__strdup(d->old.path);
+ if (delta->old.path == NULL) {
+ git__free(delta);
+ return NULL;
}
+ delta->old.flags |= GIT_DIFF_FILE_FREE_PATH;
- if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS)
- file_delta_free(delta);
+ if (d->new.path != d->old.path) {
+ delta->new.path = git__strdup(d->new.path);
+ if (delta->new.path == NULL) {
+ git__free(delta->old.path);
+ git__free(delta);
+ return NULL;
+ }
+ delta->new.flags |= GIT_DIFF_FILE_FREE_PATH;
+ } else {
+ delta->new.path = delta->old.path;
+ delta->new.flags &= ~GIT_DIFF_FILE_FREE_PATH;
+ }
- return error;
+ return delta;
}
-static int file_delta_new__from_tree_diff(
- git_diff_list *diff,
- const git_tree_diff_data *tdiff)
+static git_diff_delta *diff_delta__merge_like_cgit(
+ const git_diff_delta *a, const git_diff_delta *b)
{
- int error;
- git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
+ git_diff_delta *dup = diff_delta__dup(a);
+ if (!dup)
+ return NULL;
- if (!delta)
- return git__rethrow(GIT_ENOMEM, "Could not allocate diff record");
+ if (git_oid_cmp(&dup->new.oid, &b->new.oid) == 0)
+ return dup;
- if ((diff->opts.flags & GIT_DIFF_REVERSE) == 0) {
- delta->status = tdiff->status;
- delta->old_attr = tdiff->old_attr;
- delta->new_attr = tdiff->new_attr;
- delta->old_oid = tdiff->old_oid;
- delta->new_oid = tdiff->new_oid;
- } else {
- /* reverse the polarity of the neutron flow */
- switch (tdiff->status) {
- case GIT_STATUS_ADDED: delta->status = GIT_STATUS_DELETED; break;
- case GIT_STATUS_DELETED: delta->status = GIT_STATUS_ADDED; break;
- default: delta->status = tdiff->status;
- }
- delta->old_attr = tdiff->new_attr;
- delta->new_attr = tdiff->old_attr;
- delta->old_oid = tdiff->new_oid;
- delta->new_oid = tdiff->old_oid;
- }
+ git_oid_cpy(&dup->new.oid, &b->new.oid);
- delta->path = git__strdup(diff->pfx.ptr);
- if (delta->path == NULL) {
- git__free(delta);
- return git__rethrow(GIT_ENOMEM, "Could not allocate diff record path");
- }
+ dup->new.mode = b->new.mode;
+ dup->new.size = b->new.size;
+ dup->new.flags =
+ (dup->new.flags & GIT_DIFF_FILE_FREE_PATH) |
+ (b->new.flags & ~GIT_DIFF_FILE_FREE_PATH);
- if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS)
- file_delta_free(delta);
+ /* 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.oid, &dup->new.oid) == 0) {
+ if (dup->status == GIT_STATUS_DELETED)
+ /* preserve pending delete info */;
+ else if (b->status == GIT_STATUS_UNTRACKED ||
+ b->status == GIT_STATUS_IGNORED)
+ dup->status = b->status;
+ else
+ dup->status = GIT_STATUS_UNMODIFIED;
+ }
+ else if (dup->status == GIT_STATUS_UNMODIFIED ||
+ b->status == GIT_STATUS_DELETED)
+ dup->status = b->status;
- return error;
+ return dup;
}
-static int create_diff_for_tree_entry(const char *root, git_tree_entry *entry, void *data)
+static int diff_delta__from_one(
+ git_diff_list *diff,
+ git_status_t status,
+ const git_index_entry *entry)
{
int error;
- git_diff_list *diff = data;
- ssize_t pfx_len = diff->pfx.size;
+ git_diff_delta *delta = diff_delta__alloc(diff, status, entry->path);
+ if (!delta)
+ return git__rethrow(GIT_ENOMEM, "Could not allocate diff record");
- if (S_ISDIR(git_tree_entry_attributes(entry)))
- return GIT_SUCCESS;
+ /* This fn is just for single-sided diffs */
+ assert(status != GIT_STATUS_MODIFIED);
- /* join pfx, root, and entry->filename into one */
- if ((error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, root)) ||
- (error = git_buf_joinpath(
- &diff->pfx, diff->pfx.ptr, git_tree_entry_name(entry))))
- return error;
+ if (delta->status == GIT_STATUS_DELETED) {
+ delta->old.mode = entry->mode;
+ delta->old.size = entry->file_size;
+ git_oid_cpy(&delta->old.oid, &entry->oid);
+ } else /* ADDED, IGNORED, UNTRACKED */ {
+ delta->new.mode = entry->mode;
+ delta->new.size = entry->file_size;
+ git_oid_cpy(&delta->new.oid, &entry->oid);
+ }
- error = file_delta_new__from_one(
- diff, diff->status, git_tree_entry_attributes(entry),
- git_tree_entry_id(entry), diff->pfx.ptr);
+ delta->old.flags |= GIT_DIFF_FILE_VALID_OID;
+ delta->new.flags |= GIT_DIFF_FILE_VALID_OID;
- git_buf_truncate(&diff->pfx, pfx_len);
+ if ((error = git_vector_insert(&diff->deltas, delta)) < GIT_SUCCESS)
+ diff_delta__free(delta);
return error;
}
-static int tree_to_tree_diff_cb(const git_tree_diff_data *tdiff, void *data)
+static int diff_delta__from_two(
+ git_diff_list *diff,
+ git_status_t status,
+ const git_index_entry *old,
+ const git_index_entry *new,
+ git_oid *new_oid)
{
int error;
- git_diff_list *diff = data;
- ssize_t pfx_len = diff->pfx.size;
+ git_diff_delta *delta;
- error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, tdiff->path);
- if (error < GIT_SUCCESS)
- return error;
+ if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
+ const git_index_entry *temp = old;
+ old = new;
+ new = temp;
+ }
- /* there are 4 tree related cases:
- * - diff tree to tree, which just means we recurse
- * - tree was deleted
- * - tree was added
- * - tree became non-tree or vice versa, which git_tree_diff
- * will already have converted into two calls: an addition
- * and a deletion (thank you, git_tree_diff!)
- * otherwise, this is a blob-to-blob diff
- */
- if (S_ISDIR(tdiff->old_attr) && S_ISDIR(tdiff->new_attr)) {
- git_tree *old = NULL, *new = NULL;
-
- if (!(error = git_tree_lookup(&old, diff->repo, &tdiff->old_oid)) &&
- !(error = git_tree_lookup(&new, diff->repo, &tdiff->new_oid)))
- error = git_tree_diff(old, new, tree_to_tree_diff_cb, diff);
-
- git_tree_free(old);
- git_tree_free(new);
- } else if (S_ISDIR(tdiff->old_attr) || S_ISDIR(tdiff->new_attr)) {
- git_tree *tree = NULL;
- int added_dir = S_ISDIR(tdiff->new_attr);
- const git_oid *oid = added_dir ? &tdiff->new_oid : &tdiff->old_oid;
- diff->status = added_dir ? GIT_STATUS_ADDED : GIT_STATUS_DELETED;
-
- if (!(error = git_tree_lookup(&tree, diff->repo, oid)))
- error = git_tree_walk(
- tree, create_diff_for_tree_entry, GIT_TREEWALK_POST, diff);
- git_tree_free(tree);
- } else
- error = file_delta_new__from_tree_diff(diff, tdiff);
-
- git_buf_truncate(&diff->pfx, pfx_len);
+ delta = diff_delta__alloc(diff, status, old->path);
+ if (!delta)
+ return git__rethrow(GIT_ENOMEM, "Could not allocate diff record");
+
+ delta->old.mode = old->mode;
+ git_oid_cpy(&delta->old.oid, &old->oid);
+ delta->old.flags |= GIT_DIFF_FILE_VALID_OID;
+
+ delta->new.mode = new->mode;
+ git_oid_cpy(&delta->new.oid, new_oid ? new_oid : &new->oid);
+ if (new_oid || !git_oid_iszero(&new->oid))
+ delta->new.flags |= GIT_DIFF_FILE_VALID_OID;
+
+ if ((error = git_vector_insert(&diff->deltas, delta)) < GIT_SUCCESS)
+ diff_delta__free(delta);
return error;
}
-static char *git_diff_src_prefix_default = "a/";
-static char *git_diff_dst_prefix_default = "b/";
-#define PREFIX_IS_DEFAULT(A) \
- ((A) == git_diff_src_prefix_default || (A) == git_diff_dst_prefix_default)
+#define DIFF_SRC_PREFIX_DEFAULT "a/"
+#define DIFF_DST_PREFIX_DEFAULT "b/"
-static char *copy_prefix(const char *prefix)
+static char *diff_strdup_prefix(const char *prefix)
{
size_t len = strlen(prefix);
char *str = git__malloc(len + 2);
@@ -203,6 +212,13 @@ static char *copy_prefix(const char *prefix)
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.path, db->old.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)
{
@@ -211,27 +227,35 @@ static git_diff_list *git_diff_list_alloc(
return NULL;
diff->repo = repo;
- git_buf_init(&diff->pfx, 0);
if (opts == NULL)
return diff;
memcpy(&diff->opts, opts, sizeof(git_diff_options));
- diff->opts.src_prefix = (opts->src_prefix == NULL) ?
- git_diff_src_prefix_default : copy_prefix(opts->src_prefix);
- diff->opts.dst_prefix = (opts->dst_prefix == NULL) ?
- git_diff_dst_prefix_default : copy_prefix(opts->dst_prefix);
+ diff->opts.src_prefix = diff_strdup_prefix(
+ opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT);
+ diff->opts.dst_prefix = diff_strdup_prefix(
+ opts->dst_prefix ? opts->dst_prefix : DIFF_DST_PREFIX_DEFAULT);
+
if (!diff->opts.src_prefix || !diff->opts.dst_prefix) {
git__free(diff);
return NULL;
}
+
if (diff->opts.flags & GIT_DIFF_REVERSE) {
char *swap = diff->opts.src_prefix;
diff->opts.src_prefix = diff->opts.dst_prefix;
diff->opts.dst_prefix = swap;
}
+ if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < GIT_SUCCESS) {
+ git__free(diff->opts.src_prefix);
+ git__free(diff->opts.dst_prefix);
+ git__free(diff);
+ return NULL;
+ }
+
/* do something safe with the pathspec strarray */
return diff;
@@ -245,930 +269,329 @@ void git_diff_list_free(git_diff_list *diff)
if (!diff)
return;
- git_buf_free(&diff->pfx);
- git_vector_foreach(&diff->files, i, delta) {
- file_delta_free(delta);
- diff->files.contents[i] = NULL;
- }
- git_vector_free(&diff->files);
- if (!PREFIX_IS_DEFAULT(diff->opts.src_prefix)) {
- git__free(diff->opts.src_prefix);
- diff->opts.src_prefix = NULL;
- }
- if (!PREFIX_IS_DEFAULT(diff->opts.dst_prefix)) {
- git__free(diff->opts.dst_prefix);
- diff->opts.dst_prefix = NULL;
+ 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.src_prefix);
+ git__free(diff->opts.dst_prefix);
git__free(diff);
}
-int git_diff_tree_to_tree(
+static int oid_for_workdir_item(
git_repository *repo,
- const git_diff_options *opts,
- git_tree *old,
- git_tree *new,
- git_diff_list **diff_ptr)
+ const git_index_entry *item,
+ git_oid *oid)
{
- int error;
- git_diff_list *diff = git_diff_list_alloc(repo, opts);
- if (!diff)
- return GIT_ENOMEM;
-
- error = git_tree_diff(old, new, tree_to_tree_diff_cb, diff);
- if (error == GIT_SUCCESS) {
- git_buf_free(&diff->pfx); /* don't need this anymore */
- *diff_ptr = diff;
- } else
- git_diff_list_free(diff);
-
- return error;
-}
-
-typedef struct {
- git_diff_list *diff;
- git_index *index;
- unsigned int index_pos;
- git_ignores *ignores;
-} diff_callback_info;
-
-static int add_new_index_deltas(
- diff_callback_info *info,
- git_status_t status,
- const char *stop_path)
-{
- int error;
- git_index_entry *idx_entry = git_index_get(info->index, info->index_pos);
-
- while (idx_entry != NULL &&
- (stop_path == NULL || strcmp(idx_entry->path, stop_path) < 0))
- {
- error = file_delta_new__from_one(
- info->diff, status, idx_entry->mode,
- &idx_entry->oid, idx_entry->path);
- if (error < GIT_SUCCESS)
- return error;
-
- idx_entry = git_index_get(info->index, ++info->index_pos);
- }
-
- return GIT_SUCCESS;
-}
-
-static int diff_index_to_tree_cb(const char *root, git_tree_entry *tree_entry, void *data)
-{
- int error;
- diff_callback_info *info = data;
- git_index_entry *idx_entry;
-
- /* TODO: submodule support for GIT_OBJ_COMMITs in tree */
- if (git_tree_entry_type(tree_entry) != GIT_OBJ_BLOB)
- return GIT_SUCCESS;
-
- error = git_buf_joinpath(&info->diff->pfx, root, git_tree_entry_name(tree_entry));
- if (error < GIT_SUCCESS)
- return error;
+ int error = GIT_SUCCESS;
+ git_buf full_path = GIT_BUF_INIT;
- /* create add deltas for index entries that are not in the tree */
- error = add_new_index_deltas(info, GIT_STATUS_ADDED, info->diff->pfx.ptr);
- if (error < GIT_SUCCESS)
+ error = git_buf_joinpath(
+ &full_path, git_repository_workdir(repo), item->path);
+ if (error != GIT_SUCCESS)
return error;
- /* create delete delta for tree entries that are not in the index */
- idx_entry = git_index_get(info->index, info->index_pos);
- if (idx_entry == NULL || strcmp(idx_entry->path, info->diff->pfx.ptr) > 0) {
- return file_delta_new__from_one(
- info->diff, GIT_STATUS_DELETED, git_tree_entry_attributes(tree_entry),
- git_tree_entry_id(tree_entry), info->diff->pfx.ptr);
- }
-
- /* create modified delta for non-matching tree & index entries */
- info->index_pos++;
-
- if (git_oid_cmp(&idx_entry->oid, git_tree_entry_id(tree_entry)) ||
- idx_entry->mode != git_tree_entry_attributes(tree_entry))
- {
- git_tree_diff_data tdiff;
- tdiff.old_attr = git_tree_entry_attributes(tree_entry);
- tdiff.new_attr = idx_entry->mode;
- tdiff.status = GIT_STATUS_MODIFIED;
- tdiff.path = idx_entry->path;
- git_oid_cpy(&tdiff.old_oid, git_tree_entry_id(tree_entry));
- git_oid_cpy(&tdiff.new_oid, &idx_entry->oid);
-
- error = file_delta_new__from_tree_diff(info->diff, &tdiff);
- }
-
- return error;
-
-}
-
-int git_diff_index_to_tree(
- git_repository *repo,
- const git_diff_options *opts,
- git_tree *old,
- git_diff_list **diff_ptr)
-{
- int error;
- diff_callback_info info = {0};
-
- if ((info.diff = git_diff_list_alloc(repo, opts)) == NULL)
- return GIT_ENOMEM;
-
- if ((error = git_repository_index(&info.index, repo)) == GIT_SUCCESS) {
- error = git_tree_walk(
- old, diff_index_to_tree_cb, GIT_TREEWALK_POST, &info);
- if (error == GIT_SUCCESS)
- error = add_new_index_deltas(&info, GIT_STATUS_ADDED, NULL);
- git_index_free(info.index);
+ /* 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(&info.diff->pfx);
- if (error != GIT_SUCCESS)
- git_diff_list_free(info.diff);
- else
- *diff_ptr = info.diff;
+ git_buf_free(&full_path);
return error;
}
-typedef struct {
- struct stat st;
- mode_t mode;
- char path[GIT_FLEX_ARRAY];
-} workdir_entry;
-
-#define MODE_PERMS_MASK 0777
-
-/* TODO: need equiv of core git's "trust_executable_bit" flag? */
-#define CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
-#define MODE_TYPE(MODE) ((MODE) & ~MODE_PERMS_MASK)
-
-static mode_t canonical_mode(mode_t raw_mode)
-{
- if (S_ISREG(raw_mode))
- return S_IFREG | CANONICAL_PERMS(raw_mode);
- else if (S_ISLNK(raw_mode))
- return S_IFLNK;
- else if (S_ISDIR(raw_mode))
- return S_IFDIR;
- else if (S_ISGITLINK(raw_mode))
- return S_IFGITLINK;
- else
- return 0;
-}
-
-static int diff_workdir_insert(void *data, git_buf *dir)
-{
- workdir_entry *wd_entry = git__malloc(sizeof(workdir_entry) + dir->size + 2);
- if (wd_entry == NULL)
- return GIT_ENOMEM;
- if (p_lstat(dir->ptr, &wd_entry->st) < 0) {
- git__free(wd_entry);
- return GIT_EOSERR;
- }
- git_buf_copy_cstr(wd_entry->path, dir->size + 1, dir);
- wd_entry->mode = canonical_mode(wd_entry->st.st_mode);
- /* suffix directories with / to mimic tree/index sort order */
- if (S_ISDIR(wd_entry->st.st_mode)) {
- wd_entry->path[dir->size] = '/';
- wd_entry->path[dir->size+1] = '\0';
- }
-
- return git_vector_insert((git_vector *)data, wd_entry);
-}
-
-static int diff_workdir_walk(
- const char *dir,
- diff_callback_info *info,
- int (*cb)(diff_callback_info *, workdir_entry *))
+static int maybe_modified(
+ git_iterator *old,
+ const git_index_entry *oitem,
+ git_iterator *new,
+ const git_index_entry *nitem,
+ git_diff_list *diff)
{
int error = GIT_SUCCESS;
- git_vector files = GIT_VECTOR_INIT;
- git_buf buf = GIT_BUF_INIT;
- unsigned int i;
- workdir_entry *wd_entry;
- git_ignores ignores = {0}, *old_ignores = info->ignores;
-
- if (!dir)
- dir = git_repository_workdir(info->diff->repo);
-
- if ((error = git_vector_init(&files, 0, git__strcmp_cb)) < GIT_SUCCESS ||
- (error = git_buf_sets(&buf, dir)) < GIT_SUCCESS ||
- (error = git_path_direach(&buf, diff_workdir_insert, &files)) < GIT_SUCCESS ||
- (error = git_ignore__for_path(info->diff->repo, dir, &ignores)) < GIT_SUCCESS)
- goto cleanup;
-
- git_vector_sort(&files);
- info->ignores = old_ignores;
+ git_oid noid, *use_noid = NULL;
- git_vector_foreach(&files, i, wd_entry) {
- if ((error = cb(info, wd_entry)) < GIT_SUCCESS)
- goto cleanup;
- }
-
-cleanup:
- git_vector_foreach(&files, i, wd_entry)
- git__free(wd_entry);
- info->ignores = old_ignores;
- git_ignore__free(&ignores);
- git_vector_free(&files);
- git_buf_free(&buf);
-
- return error;
-}
-
-static int found_new_workdir_entry(
- diff_callback_info *info, workdir_entry *wd_entry)
-{
- int error;
- int ignored = 0;
- git_status_t status;
+ GIT_UNUSED_ARG(old);
- /* skip file types that are not trackable */
- if (wd_entry->mode == 0)
+ /* 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;
- error = git_ignore__lookup(info->ignores, wd_entry->path, &ignored);
- if (error < GIT_SUCCESS)
- return error;
- status = ignored ? GIT_STATUS_IGNORED : GIT_STATUS_UNTRACKED;
-
- return file_delta_new__from_one(
- info->diff, status, wd_entry->mode, NULL, wd_entry->path);
-}
-
-static int diff_workdir_to_index_cb(
- diff_callback_info *info, workdir_entry *wd_entry)
-{
- int error, modified;
- git_index_entry *idx_entry;
- git_oid new_oid;
-
- /* Store index entries that precede this workdir entry */
- error = add_new_index_deltas(info, GIT_STATUS_DELETED, wd_entry->path);
- if (error < GIT_SUCCESS)
+ if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) {
+ error = diff_delta__from_one(diff, GIT_STATUS_DELETED, oitem);
+ if (error == GIT_SUCCESS)
+ error = diff_delta__from_one(diff, GIT_STATUS_ADDED, nitem);
return error;
-
- /* Process workdir entries that are not in the index.
- * These might be untracked, ignored, or special (dirs, etc).
- */
- idx_entry = git_index_get(info->index, info->index_pos);
- if (idx_entry == NULL || strcmp(idx_entry->path, wd_entry->path) > 0) {
- git_buf dotgit = GIT_BUF_INIT;
- int contains_dotgit;
-
- if (!S_ISDIR(wd_entry->mode))
- return found_new_workdir_entry(info, wd_entry);
-
- error = git_buf_joinpath(&dotgit, wd_entry->path, DOT_GIT);
- if (error < GIT_SUCCESS)
- return error;
- contains_dotgit = (git_path_exists(dotgit.ptr) == GIT_SUCCESS);
- git_buf_free(&dotgit);
-
- if (contains_dotgit)
- /* TODO: deal with submodule or embedded repo */
- return GIT_SUCCESS;
- else if (git__prefixcmp(idx_entry->path, wd_entry->path) == GIT_SUCCESS)
- /* there are entries in the directory in the index already,
- * so recurse into it.
- */
- return diff_workdir_walk(wd_entry->path, info, diff_workdir_to_index_cb);
- else
- /* TODO: this is not the same behavior as core git.
- *
- * I don't recurse into the directory once I know that no files
- * in it are being tracked. But core git does and only adds an
- * entry if there are non-directory entries contained under the
- * dir (although, interestingly, it only shows the dir, not the
- * individual entries).
- */
- return found_new_workdir_entry(info, wd_entry);
- }
-
- /* create modified delta for non-matching tree & index entries */
- info->index_pos++;
-
- /* check for symlink/blob changes and split into add/del pair */
- if (MODE_TYPE(wd_entry->mode) != MODE_TYPE(idx_entry->mode)) {
- error = file_delta_new__from_one(
- info->diff, GIT_STATUS_DELETED,
- idx_entry->mode, &idx_entry->oid, idx_entry->path);
- if (error < GIT_SUCCESS)
- return error;
-
- /* because of trailing slash, cannot have non-dir to dir transform */
- assert(!S_ISDIR(wd_entry->mode));
-
- return file_delta_new__from_one(
- info->diff, GIT_STATUS_ADDED,
- wd_entry->mode, NULL, wd_entry->path);
}
- /* mode or size changed, so git blob has definitely changed */
- if (wd_entry->mode != idx_entry->mode ||
- wd_entry->st.st_size != idx_entry->file_size)
- {
- modified = 1;
- memset(&new_oid, 0, sizeof(new_oid));
- }
+ if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
+ oitem->mode == nitem->mode)
+ return GIT_SUCCESS;
- /* all other things are indicators there might be a change, so get oid */
- if (!modified &&
- ((git_time_t)wd_entry->st.st_ctime != idx_entry->ctime.seconds ||
- (git_time_t)wd_entry->st.st_mtime != idx_entry->mtime.seconds ||
- (unsigned int)wd_entry->st.st_dev != idx_entry->dev ||
- (unsigned int)wd_entry->st.st_ino != idx_entry->ino ||
- /* TODO: need TRUST_UID_GID configs */
- (unsigned int)wd_entry->st.st_uid != idx_entry->uid ||
- (unsigned int)wd_entry->st.st_gid != idx_entry->gid))
- {
- /* calculate oid to confirm change */
- if (S_ISLNK(wd_entry->st.st_mode))
- error = git_odb__hashlink(&new_oid, wd_entry->path);
- else {
- int fd;
- if ((fd = p_open(wd_entry->path, O_RDONLY)) < 0)
- error = git__throw(
- GIT_EOSERR, "Could not open '%s'", wd_entry->path);
- else {
- error = git_odb__hashfd(
- &new_oid, fd, wd_entry->st.st_size, GIT_OBJ_BLOB);
- p_close(fd);
- }
- }
+ if (git_oid_iszero(&nitem->oid) && new->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;
- if (error < 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;
- modified = (git_oid_cmp(&new_oid, &idx_entry->oid) != 0);
- }
-
- /* TODO: check index flags for forced ignore changes */
-
- if (modified) {
- git_tree_diff_data tdiff;
-
- tdiff.old_attr = idx_entry->mode;
- tdiff.new_attr = wd_entry->mode;
- tdiff.status = GIT_STATUS_MODIFIED;
- tdiff.path = wd_entry->path;
- git_oid_cpy(&tdiff.old_oid, &idx_entry->oid);
- git_oid_cpy(&tdiff.new_oid, &new_oid);
+ if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
+ oitem->mode == nitem->mode)
+ return GIT_SUCCESS;
- error = file_delta_new__from_tree_diff(info->diff, &tdiff);
+ /* store calculated oid so we don't have to recalc later */
+ use_noid = &noid;
}
- return error;
+ return diff_delta__from_two(
+ diff, GIT_STATUS_MODIFIED, oitem, nitem, use_noid);
}
-int git_diff_workdir_to_index(
+static int diff_from_iterators(
git_repository *repo,
- const git_diff_options *opts,
- git_diff_list **diff)
+ const git_diff_options *opts, /**< can be NULL for defaults */
+ git_iterator *old,
+ git_iterator *new,
+ git_diff_list **diff_ptr)
{
int error;
- diff_callback_info info = {0};
-
- if ((info.diff = git_diff_list_alloc(repo, opts)) == NULL)
- return GIT_ENOMEM;
-
- if ((error = git_repository_index(&info.index, repo)) == GIT_SUCCESS) {
- error = diff_workdir_walk(NULL, &info, diff_workdir_to_index_cb);
- if (error == GIT_SUCCESS)
- error = add_new_index_deltas(&info, GIT_STATUS_DELETED, NULL);
- git_index_free(info.index);
+ 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;
}
- git_buf_free(&info.diff->pfx);
-
- if (error != GIT_SUCCESS)
- git_diff_list_free(info.diff);
- else
- *diff = info.diff;
- return error;
-}
+ diff->old_src = old->type;
+ diff->new_src = new->type;
-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;
+ if ((error = git_iterator_current(old, &oitem)) < GIT_SUCCESS ||
+ (error = git_iterator_current(new, &nitem)) < GIT_SUCCESS)
+ goto cleanup;
-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;
-}
+ /* run iterators building diffs */
+ while (!error && (oitem || nitem)) {
-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);
+ /* 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_STATUS_DELETED, oitem);
+ if (error == GIT_SUCCESS)
+ error = git_iterator_advance(old, &oitem);
+ continue;
}
- }
-
- return err;
-}
-
-static int set_file_is_binary(
- git_repository *repo,
- git_diff_delta *file,
- mmfile_t *old,
- mmfile_t *new)
-{
- int error;
- const char *value;
-
- /* check diff attribute +, -, or 0 */
- error = git_attr_get(repo, file->path, "diff", &value);
- if (error != GIT_SUCCESS)
- return error;
-
- if (value == GIT_ATTR_TRUE) {
- file->binary = 0;
- return GIT_SUCCESS;
- }
- if (value == GIT_ATTR_FALSE) {
- file->binary = 1;
- return GIT_SUCCESS;
- }
-
- /* TODO: if value != NULL, implement diff drivers */
- /* TODO: check if NUL byte appears in first bit */
- GIT_UNUSED_ARG(old);
- GIT_UNUSED_ARG(new);
- file->binary = 0;
- 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;
-}
+ /* 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_status_t use_status = GIT_STATUS_ADDED;
-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->files, info.index, delta) {
- mmfile_t old_data, new_data;
-
- /* map files */
- if (hunk_cb || line_cb) {
- /* 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.
- */
-
- if (delta->status == GIT_STATUS_DELETED ||
- delta->status == GIT_STATUS_MODIFIED)
+ /* contained in ignored parent directory, so this can be skipped. */
+ if (ignore_prefix != NULL &&
+ git__prefixcmp(nitem->path, ignore_prefix) == 0)
{
- error = git_blob_lookup(
- &delta->old_blob, diff->repo, &delta->old_oid);
- old_data.ptr = (char *)git_blob_rawcontent(delta->old_blob);
- old_data.size = git_blob_rawsize(delta->old_blob);
- } else {
- delta->old_blob = NULL;
- old_data.ptr = "";
- old_data.size = 0;
+ error = git_iterator_advance(new, &nitem);
+ continue;
}
- if (delta->status == GIT_STATUS_ADDED ||
- delta->status == GIT_STATUS_MODIFIED)
- {
- error = git_blob_lookup(
- &delta->new_blob, diff->repo, &delta->new_oid);
- new_data.ptr = (char *)git_blob_rawcontent(delta->new_blob);
- new_data.size = git_blob_rawsize(delta->new_blob);
- } else {
- delta->new_blob = NULL;
- new_data.ptr = "";
- new_data.size = 0;
+ is_ignored = git_iterator_current_is_ignored(new);
+
+ 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, &nitem);
+ continue;
+ }
+ use_status = GIT_STATUS_UNTRACKED;
}
+ else if (is_ignored)
+ use_status = GIT_STATUS_IGNORED;
+ else if (new->type == GIT_ITERATOR_WORKDIR)
+ use_status = GIT_STATUS_UNTRACKED;
+
+ error = diff_delta__from_one(diff, use_status, nitem);
+ if (error == GIT_SUCCESS)
+ error = git_iterator_advance(new, &nitem);
+ continue;
}
- if (diff->opts.flags & GIT_DIFF_FORCE_TEXT)
- delta->binary = 0;
- else if ((error = set_file_is_binary(
- diff->repo, delta, &old_data, &new_data)) < GIT_SUCCESS)
- break;
-
- /* TODO: if ignore_whitespace is set, then we *must* do text
- * diffs to tell if a file has really been changed.
+ /* 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);
- if (file_cb != NULL) {
- error = file_cb(data, delta, (float)info.index / diff->files.length);
- if (error != GIT_SUCCESS)
- break;
- }
-
- /* don't do hunk and line diffs if file is binary */
- if (delta->binary)
- continue;
-
- /* nothing to do if we did not get a blob */
- if (!delta->old_blob && !delta->new_blob)
- continue;
-
- assert(hunk_cb || line_cb);
-
- info.delta = delta;
-
- xdl_diff(&old_data, &new_data,
- &xdiff_params, &xdiff_config, &xdiff_callback);
-
- git_blob_free(delta->old_blob);
- delta->old_blob = NULL;
-
- git_blob_free(delta->new_blob);
- delta->new_blob = NULL;
+ error = maybe_modified(old, oitem, new, nitem, diff);
+ if (error == GIT_SUCCESS)
+ error = git_iterator_advance(old, &oitem);
+ if (error == GIT_SUCCESS)
+ error = git_iterator_advance(new, &nitem);
}
- 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)
- /* modes in git are not very flexible, so if this bit is set,
- * we must be dealwith with a 100755 type of file.
- */
- return '*';
- else
- return ' ';
-}
+cleanup:
+ git_iterator_free(old);
+ git_iterator_free(new);
-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_ARG(progress);
-
- switch (delta->status) {
- case GIT_STATUS_ADDED: code = 'A'; break;
- case GIT_STATUS_DELETED: code = 'D'; break;
- case GIT_STATUS_MODIFIED: code = 'M'; break;
- case GIT_STATUS_RENAMED: code = 'R'; break;
- case GIT_STATUS_COPIED: code = 'C'; break;
- case GIT_STATUS_IGNORED: code = 'I'; break;
- case GIT_STATUS_UNTRACKED: code = '?'; break;
- default: code = 0;
+ if (error != GIT_SUCCESS) {
+ git_diff_list_free(diff);
+ diff = NULL;
}
- if (!code)
- return GIT_SUCCESS;
-
- old_suffix = pick_suffix(delta->old_attr);
- new_suffix = pick_suffix(delta->new_attr);
+ *diff_ptr = diff;
- git_buf_clear(pi->buf);
-
- if (delta->new_path != NULL)
- git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
- delta->path, old_suffix, delta->new_path, new_suffix);
- else if (delta->old_attr != delta->new_attr &&
- delta->old_attr != 0 && delta->new_attr != 0)
- git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,
- delta->path, new_suffix, delta->old_attr, delta->new_attr);
- else if (old_suffix != ' ')
- git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->path, old_suffix);
- else
- git_buf_printf(pi->buf, "%c\t%s\n", code, delta->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);
+ return error;
}
-int git_diff_print_compact(
- git_diff_list *diff,
- void *cb_data,
- git_diff_output_fn print_cb)
+
+int git_diff_tree_to_tree(
+ git_repository *repo,
+ const git_diff_options *opts, /**< can be NULL for defaults */
+ git_tree *old,
+ git_tree *new,
+ git_diff_list **diff)
{
int error;
- git_buf buf = GIT_BUF_INIT;
- diff_print_info pi;
+ git_iterator *a = NULL, *b = NULL;
- pi.diff = diff;
- pi.print_cb = print_cb;
- pi.cb_data = cb_data;
- pi.buf = &buf;
+ assert(repo && old && new && diff);
- error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL);
-
- git_buf_free(&buf);
+ if ((error = git_iterator_for_tree(repo, old, &a)) < GIT_SUCCESS ||
+ (error = git_iterator_for_tree(repo, new, &b)) < GIT_SUCCESS)
+ return error;
- return error;
+ return diff_from_iterators(repo, opts, a, b, diff);
}
-static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
+int git_diff_index_to_tree(
+ git_repository *repo,
+ const git_diff_options *opts,
+ git_tree *old,
+ git_diff_list **diff)
{
- char start_oid[8], end_oid[8];
+ int error;
+ git_iterator *a = NULL, *b = NULL;
- /* TODO: Determine a good actual OID range to print */
- git_oid_to_string(start_oid, sizeof(start_oid), &delta->old_oid);
- git_oid_to_string(end_oid, sizeof(end_oid), &delta->new_oid);
+ assert(repo && old && diff);
- /* TODO: Match git diff more closely */
- if (delta->old_attr == delta->new_attr) {
- git_buf_printf(pi->buf, "index %s..%s %o\n",
- start_oid, end_oid, delta->old_attr);
- } else {
- if (delta->old_attr == 0) {
- git_buf_printf(pi->buf, "new file mode %o\n", delta->new_attr);
- } else if (delta->new_attr == 0) {
- git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_attr);
- } else {
- git_buf_printf(pi->buf, "old mode %o\n", delta->old_attr);
- git_buf_printf(pi->buf, "new mode %o\n", delta->new_attr);
- }
- git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);
- }
+ if ((error = git_iterator_for_tree(repo, old, &a)) < GIT_SUCCESS ||
+ (error = git_iterator_for_index(repo, &b)) < GIT_SUCCESS)
+ return error;
- return git_buf_lasterror(pi->buf);
+ return diff_from_iterators(repo, opts, a, b, diff);
}
-static int print_patch_file(void *data, git_diff_delta *delta, float progress)
+int git_diff_workdir_to_index(
+ git_repository *repo,
+ const git_diff_options *opts,
+ git_diff_list **diff)
{
int error;
- diff_print_info *pi = data;
- const char *oldpfx = pi->diff->opts.src_prefix;
- const char *oldpath = delta->path;
- const char *newpfx = pi->diff->opts.dst_prefix;
- const char *newpath = delta->new_path ? delta->new_path : delta->path;
-
- GIT_UNUSED_ARG(progress);
-
- git_buf_clear(pi->buf);
- git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->path, newpfx, newpath);
- if ((error = print_oid_range(pi, delta)) < GIT_SUCCESS)
- return error;
-
- if (delta->old_blob == NULL) {
- oldpfx = "";
- oldpath = "/dev/null";
- }
- if (delta->new_blob == NULL) {
- oldpfx = "";
- oldpath = "/dev/null";
- }
-
- if (!delta->binary) {
- git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
- git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
- }
+ git_iterator *a = NULL, *b = NULL;
- if (git_buf_lasterror(pi->buf) != GIT_SUCCESS)
- return git_buf_lasterror(pi->buf);
+ assert(repo && diff);
- error = pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr);
- if (error != GIT_SUCCESS || !delta->binary)
+ if ((error = git_iterator_for_index(repo, &a)) < GIT_SUCCESS ||
+ (error = git_iterator_for_workdir(repo, &b)) < GIT_SUCCESS)
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);
+ return diff_from_iterators(repo, opts, a, b, diff);
}
-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_ARG(d);
- GIT_UNUSED_ARG(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)
+int git_diff_workdir_to_tree(
+ git_repository *repo,
+ const git_diff_options *opts,
+ git_tree *old,
+ git_diff_list **diff)
{
- diff_print_info *pi = data;
-
- GIT_UNUSED_ARG(delta);
-
- git_buf_clear(pi->buf);
+ int error;
+ git_iterator *a = NULL, *b = NULL;
- 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);
+ assert(repo && old && diff);
- if (git_buf_lasterror(pi->buf) != GIT_SUCCESS)
- return git_buf_lasterror(pi->buf);
+ if ((error = git_iterator_for_tree(repo, old, &a)) < GIT_SUCCESS ||
+ (error = git_iterator_for_workdir(repo, &b)) < GIT_SUCCESS)
+ return error;
- return pi->print_cb(pi->cb_data, line_origin, pi->buf->ptr);
+ return diff_from_iterators(repo, opts, a, b, diff);
}
-int git_diff_print_patch(
- git_diff_list *diff,
- void *cb_data,
- git_diff_output_fn print_cb)
+int git_diff_merge(
+ git_diff_list *onto,
+ const git_diff_list *from)
{
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;
+ unsigned int i = 0, j = 0;
+ git_vector onto_new;
+ git_diff_delta *delta;
- error = git_diff_foreach(
- diff, &pi, print_patch_file, print_patch_hunk, print_patch_line);
+ error = git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp);
+ if (error < GIT_SUCCESS)
+ return error;
- git_buf_free(&buf);
+ 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.path ? o->old.path : o->new.path;
+ const char *fpath = !f ? NULL : f->old.path ? f->old.path : f->new.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++;
+ }
- return error;
-}
+ if (!delta)
+ error = GIT_ENOMEM;
+ else
+ error = git_vector_insert(&onto_new, delta);
-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, new;
- 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 (error != GIT_SUCCESS)
+ break;
}
- if (old_blob) {
- old.ptr = (char *)git_blob_rawcontent(old_blob);
- old.size = git_blob_rawsize(old_blob);
- } else {
- old.ptr = "";
- old.size = 0;
+ if (error == GIT_SUCCESS) {
+ git_vector_swap(&onto->deltas, &onto_new);
+ onto->new_src = from->new_src;
}
- if (new_blob) {
- new.ptr = (char *)git_blob_rawcontent(new_blob);
- new.size = git_blob_rawsize(new_blob);
- } else {
- new.ptr = "";
- new.size = 0;
- }
+ git_vector_foreach(&onto_new, i, delta)
+ diff_delta__free(delta);
+ git_vector_free(&onto_new);
- /* populate a "fake" delta record */
- delta.status = old.ptr ?
- (new.ptr ? GIT_STATUS_MODIFIED : GIT_STATUS_DELETED) :
- (new.ptr ? GIT_STATUS_ADDED : GIT_STATUS_UNTRACKED);
- delta.old_attr = 0100644; /* can't know the truth from a blob alone */
- delta.new_attr = 0100644;
- git_oid_cpy(&delta.old_oid, git_object_id((const git_object *)old_blob));
- git_oid_cpy(&delta.new_oid, git_object_id((const git_object *)new_blob));
- delta.old_blob = old_blob;
- delta.new_blob = new_blob;
- delta.path = NULL;
- delta.new_path = NULL;
- delta.similarity = 0;
- delta.binary = 0;
-
- info.diff = NULL;
- info.delta = &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, &new, &xdiff_params, &xdiff_config, &xdiff_callback);
-
- return GIT_SUCCESS;
+ return error;
}
diff --git a/src/diff.h b/src/diff.h
index b0f1ebbe8..7d69199ea 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -10,15 +10,15 @@
#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 files; /* vector of git_diff_file_delta */
-
- /* the following are just used while processing the diff list */
- git_buf pfx;
- git_status_t status;
+ 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..ac60e9822
--- /dev/null
+++ b/src/diff_output.c
@@ -0,0 +1,722 @@
+/*
+ * 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"
+
+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 (value == GIT_ATTR_FALSE)
+ file->flags |= GIT_DIFF_FILE_BINARY;
+ else if (value == GIT_ATTR_TRUE)
+ 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.flags & GIT_DIFF_FILE_BINARY) != 0 ||
+ (delta->new.flags & GIT_DIFF_FILE_BINARY) != 0)
+ delta->binary = 1;
+ else if ((delta->old.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 ||
+ (delta->new.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.size) != delta->old.size ||
+ (git_off_t)((size_t)delta->new.size) != delta->new.size)
+ {
+ delta->old.flags |= GIT_DIFF_FILE_BINARY;
+ delta->new.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.flags |= GIT_DIFF_FILE_NOT_BINARY;
+ delta->new.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);
+ if (error != GIT_SUCCESS)
+ return error;
+
+ mirror_new = (delta->new.path == delta->old.path ||
+ strcmp(delta->new.path, delta->old.path) == 0);
+ if (mirror_new)
+ delta->new.flags &= (delta->old.flags & BINARY_DIFF_FLAGS);
+ else
+ error = set_file_is_binary_by_attr(diff->repo, &delta->new);
+
+ 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_UNUSED_ARG(diff);
+
+ if ((delta->old.flags & BINARY_DIFF_FLAGS) == 0) {
+ size_t search_len = min(old_data->len, 4000);
+ if (strnlen(old_data->data, search_len) != search_len)
+ delta->old.flags |= GIT_DIFF_FILE_BINARY;
+ else
+ delta->old.flags |= GIT_DIFF_FILE_NOT_BINARY;
+ }
+
+ if ((delta->new.flags & BINARY_DIFF_FLAGS) == 0) {
+ size_t search_len = min(new_data->len, 4000);
+ if (strnlen(new_data->data, search_len) != search_len)
+ delta->new.flags |= GIT_DIFF_FILE_BINARY;
+ else
+ delta->new.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;
+
+ if (delta->status == GIT_STATUS_UNMODIFIED)
+ continue;
+
+ if (delta->status == GIT_STATUS_IGNORED &&
+ (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ continue;
+
+ if (delta->status == GIT_STATUS_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_STATUS_DELETED ||
+ delta->status == GIT_STATUS_MODIFIED))
+ {
+ if (diff->old_src == GIT_ITERATOR_WORKDIR)
+ error = get_workdir_content(diff->repo, &delta->old, &old_data);
+ else
+ error = get_blob_content(
+ diff->repo, &delta->old.oid, &old_data, &old_blob);
+ if (error != GIT_SUCCESS)
+ goto cleanup;
+ }
+
+ if (delta->binary != 1 &&
+ (hunk_cb || line_cb || git_oid_iszero(&delta->new.oid)) &&
+ (delta->status == GIT_STATUS_ADDED ||
+ delta->status == GIT_STATUS_MODIFIED))
+ {
+ if (diff->new_src == GIT_ITERATOR_WORKDIR)
+ error = get_workdir_content(diff->repo, &delta->new, &new_data);
+ else
+ error = get_blob_content(
+ diff->repo, &delta->new.oid, &new_data, &new_blob);
+ if (error != GIT_SUCCESS)
+ goto cleanup;
+
+ if ((delta->new.flags | GIT_DIFF_FILE_VALID_OID) == 0) {
+ error = git_odb_hash(
+ &delta->new.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.oid, &delta->new.oid) == 0) {
+ delta->status = GIT_STATUS_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;
+
+ xdl_diff((mmfile_t *)&old_data, (mmfile_t *)&new_data,
+ &xdiff_params, &xdiff_config, &xdiff_callback);
+
+cleanup:
+ release_content(&delta->old, &old_data, old_blob);
+ release_content(&delta->new, &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_ARG(progress);
+
+ switch (delta->status) {
+ case GIT_STATUS_ADDED: code = 'A'; break;
+ case GIT_STATUS_DELETED: code = 'D'; break;
+ case GIT_STATUS_MODIFIED: code = 'M'; break;
+ case GIT_STATUS_RENAMED: code = 'R'; break;
+ case GIT_STATUS_COPIED: code = 'C'; break;
+ case GIT_STATUS_IGNORED: code = 'I'; break;
+ case GIT_STATUS_UNTRACKED: code = '?'; break;
+ default: code = 0;
+ }
+
+ if (!code)
+ return GIT_SUCCESS;
+
+ old_suffix = pick_suffix(delta->old.mode);
+ new_suffix = pick_suffix(delta->new.mode);
+
+ git_buf_clear(pi->buf);
+
+ if (delta->old.path != delta->new.path &&
+ strcmp(delta->old.path,delta->new.path) != 0)
+ git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
+ delta->old.path, old_suffix, delta->new.path, new_suffix);
+ else if (delta->old.mode != delta->new.mode &&
+ delta->old.mode != 0 && delta->new.mode != 0)
+ git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,
+ delta->old.path, new_suffix, delta->old.mode, delta->new.mode);
+ else if (old_suffix != ' ')
+ git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old.path, old_suffix);
+ else
+ git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old.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.oid);
+ git_oid_to_string(end_oid, sizeof(end_oid), &delta->new.oid);
+
+ /* TODO: Match git diff more closely */
+ if (delta->old.mode == delta->new.mode) {
+ git_buf_printf(pi->buf, "index %s..%s %o\n",
+ start_oid, end_oid, delta->old.mode);
+ } else {
+ if (delta->old.mode == 0) {
+ git_buf_printf(pi->buf, "new file mode %o\n", delta->new.mode);
+ } else if (delta->new.mode == 0) {
+ git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old.mode);
+ } else {
+ git_buf_printf(pi->buf, "old mode %o\n", delta->old.mode);
+ git_buf_printf(pi->buf, "new mode %o\n", delta->new.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.src_prefix;
+ const char *oldpath = delta->old.path;
+ const char *newpfx = pi->diff->opts.dst_prefix;
+ const char *newpath = delta->new.path;
+
+ GIT_UNUSED_ARG(progress);
+
+ git_buf_clear(pi->buf);
+ git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old.path, newpfx, delta->new.path);
+ if ((error = print_oid_range(pi, delta)) < GIT_SUCCESS)
+ return error;
+
+ if (git_oid_iszero(&delta->old.oid)) {
+ oldpfx = "";
+ oldpath = "/dev/null";
+ }
+ if (git_oid_iszero(&delta->new.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_ARG(d);
+ GIT_UNUSED_ARG(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_ARG(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, new;
+ 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.ptr = (char *)git_blob_rawcontent(old_blob);
+ old.size = git_blob_rawsize(old_blob);
+ } else {
+ old.ptr = "";
+ old.size = 0;
+ }
+
+ if (new_blob) {
+ new.ptr = (char *)git_blob_rawcontent(new_blob);
+ new.size = git_blob_rawsize(new_blob);
+ } else {
+ new.ptr = "";
+ new.size = 0;
+ }
+
+ /* populate a "fake" delta record */
+ delta.status = old.ptr ?
+ (new.ptr ? GIT_STATUS_MODIFIED : GIT_STATUS_DELETED) :
+ (new.ptr ? GIT_STATUS_ADDED : GIT_STATUS_UNTRACKED);
+ delta.old.mode = 0100644; /* can't know the truth from a blob alone */
+ delta.new.mode = 0100644;
+ git_oid_cpy(&delta.old.oid, git_object_id((const git_object *)old_blob));
+ git_oid_cpy(&delta.new.oid, git_object_id((const git_object *)new_blob));
+ delta.old.path = NULL;
+ delta.new.path = NULL;
+ delta.similarity = 0;
+
+ info.diff = NULL;
+ info.delta = &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, &new, &xdiff_params, &xdiff_config, &xdiff_callback);
+
+ return GIT_SUCCESS;
+}
diff --git a/src/fileops.c b/src/fileops.c
index d2b4af51e..6e45ff8a8 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))
@@ -181,6 +177,15 @@ 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 */);
+ size_t len = git_futils_filesize(fd);
+ int result = git_futils_mmap_ro(out, fd, 0, 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 43ef21521..ab57b6f38 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -81,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.
*/
@@ -109,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/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);
diff --git a/src/oid.c b/src/oid.c
index 92d8d1e89..a1f010927 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -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..ceae2abcf 100644
--- a/src/path.c
+++ b/src/path.c
@@ -583,3 +583,46 @@ 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';
+ }
+ }
+
+ 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
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/status.c b/src/status.c
index 106e5005d..76ae83138 100644
--- a/src/status.c
+++ b/src/status.c
@@ -131,7 +131,7 @@ static int status_entry_update_ignore(struct status_entry *e, git_ignores *ignor
if ((error = git_ignore__lookup(ignores, path, &ignored)) == GIT_SUCCESS &&
ignored)
e->status_flags =
- (e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED;
+ (e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_WT_IGNORED;
return error;
}
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/xdiff/xdiff.h b/src/xdiff/xdiff.h
index e18b4d2f6..cb8b235b5 100644
--- a/src/xdiff/xdiff.h
+++ b/src/xdiff/xdiff.h
@@ -69,12 +69,12 @@ extern "C" {
typedef struct s_mmfile {
char *ptr;
- long size;
+ size_t size;
} mmfile_t;
typedef struct s_mmbuffer {
char *ptr;
- long size;
+ size_t size;
} mmbuffer_t;
typedef struct s_xpparam {
diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h
index fd5c16a03..43dc4e846 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
*/
diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c
index cd5a0f9af..67a4f1c51 100644
--- a/tests-clar/diff/diff_helpers.c
+++ b/tests-clar/diff/diff_helpers.c
@@ -29,12 +29,14 @@ int diff_file_fn(
diff_expects *e = cb_data;
(void)progress;
e->files++;
- if (delta->old_attr == 0)
- e->file_adds++;
- else if (delta->new_attr == 0)
- e->file_dels++;
- else
- e->file_mods++;
+ switch (delta->status) {
+ case GIT_STATUS_ADDED: e->file_adds++; break;
+ case GIT_STATUS_DELETED: e->file_dels++; break;
+ case GIT_STATUS_MODIFIED: e->file_mods++; break;
+ case GIT_STATUS_IGNORED: e->file_ignored++; break;
+ case GIT_STATUS_UNTRACKED: e->file_untracked++; break;
+ default: break;
+ }
return 0;
}
diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h
index 035c000f5..010d156fa 100644
--- a/tests-clar/diff/diff_helpers.h
+++ b/tests-clar/diff/diff_helpers.h
@@ -9,6 +9,8 @@ typedef struct {
int file_adds;
int file_dels;
int file_mods;
+ int file_ignored;
+ int file_untracked;
int hunks;
int hunk_new_lines;
diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c
index 185a37a53..1ad126ca8 100644
--- a/tests-clar/diff/iterator.c
+++ b/tests-clar/diff/iterator.c
@@ -338,7 +338,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 +351,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
index eb4092af8..f5fdfba16 100644
--- a/tests-clar/diff/tree.c
+++ b/tests-clar/diff/tree.c
@@ -100,7 +100,7 @@ void test_diff_tree__options(void)
git_diff_options opts = {0};
git_diff_list *diff = NULL;
- diff_expects exp;
+ 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 */
@@ -123,25 +123,26 @@ void test_diff_tree__options(void)
*/
diff_expects test_expects[] = {
/* a vs b tests */
- { 5, 3, 0, 2, 4, 0, 0, 51, 2, 46, 3 },
- { 5, 3, 0, 2, 4, 0, 0, 53, 4, 46, 3 },
- { 5, 0, 3, 2, 4, 0, 0, 52, 3, 3, 46 },
- { 5, 3, 0, 2, 5, 0, 0, 54, 3, 48, 3 },
+ { 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, 1, 0, 0, 22, 9, 10, 3 },
- { 1, 0, 0, 1, 1, 0, 0, 19, 12, 7, 0 },
- { 1, 0, 0, 1, 1, 0, 0, 20, 11, 8, 1 },
- { 1, 0, 0, 1, 1, 0, 0, 20, 11, 8, 1 },
- { 1, 0, 0, 1, 1, 0, 0, 18, 11, 0, 7 },
+ { 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(&exp, 0, sizeof(exp)); /* clear accumulator */
+ memset(&actual, 0, sizeof(actual)); /* clear accumulator */
opts = test_options[i];
if (test_ab_or_cd[i] == 0)
@@ -150,17 +151,18 @@ void test_diff_tree__options(void)
cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, d, &diff));
cl_git_pass(git_diff_foreach(
- diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
-
- cl_assert(exp.files == test_expects[i].files);
- cl_assert(exp.file_adds == test_expects[i].file_adds);
- cl_assert(exp.file_dels == test_expects[i].file_dels);
- cl_assert(exp.file_mods == test_expects[i].file_mods);
- cl_assert(exp.hunks == test_expects[i].hunks);
- cl_assert(exp.lines == test_expects[i].lines);
- cl_assert(exp.line_ctxt == test_expects[i].line_ctxt);
- cl_assert(exp.line_adds == test_expects[i].line_adds);
- cl_assert(exp.line_dels == test_expects[i].line_dels);
+ 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;
diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c
new file mode 100644
index 000000000..7312a72a7
--- /dev/null
+++ b/tests-clar/diff/workdir.c
@@ -0,0 +1,230 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_workdir__initialize(void)
+{
+ cl_fixture_sandbox("status");
+ cl_git_pass(p_rename("status/.gitted", "status/.git"));
+ cl_git_pass(git_repository_open(&g_repo, "status/.git"));
+}
+
+void test_diff_workdir__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ cl_fixture_cleanup("status");
+}
+
+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_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/status/status_data.h b/tests-clar/status/status_data.h
index 1a68648f4..719d841f6 100644
--- a/tests-clar/status/status_data.h
+++ b/tests-clar/status/status_data.h
@@ -29,7 +29,7 @@ static const char *entry_paths0[] = {
static const unsigned int entry_statuses0[] = {
GIT_STATUS_WT_DELETED,
- GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_IGNORED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,
diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c
index d8e62a94d..7d730bb9b 100644
--- a/tests-clar/status/worktree.c
+++ b/tests-clar/status/worktree.c
@@ -143,7 +143,7 @@ void test_status_worktree__ignores(void)
for (i = 0; i < (int)entry_count0; i++) {
cl_git_pass(git_status_should_ignore(_repository, entry_paths0[i], &ignored));
- cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED));
+ cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_WT_IGNORED));
}
cl_git_pass(git_status_should_ignore(_repository, "nonexistent_file", &ignored));
diff --git a/tests/t18-status.c b/tests/t18-status.c
index 270aa7b46..8bccb7106 100644
--- a/tests/t18-status.c
+++ b/tests/t18-status.c
@@ -83,7 +83,7 @@ static const char *entry_paths0[] = {
static const unsigned int entry_statuses0[] = {
GIT_STATUS_WT_DELETED,
- GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_IGNORED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,
@@ -205,7 +205,7 @@ static const char *entry_paths2[] = {
static const unsigned int entry_statuses2[] = {
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
- GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_IGNORED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
@@ -291,7 +291,7 @@ static const unsigned int entry_statuses3[] = {
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
- GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_IGNORED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,