summaryrefslogtreecommitdiff
path: root/src/diff.c
diff options
context:
space:
mode:
authorVicent Martí <tanoku@gmail.com>2012-04-11 12:38:45 +0200
committerVicent Martí <tanoku@gmail.com>2012-04-11 12:38:45 +0200
commitdcfdb958e2033aa59beb624da4263ce031fbb21e (patch)
treef17365bd6314e8318d143a8f2e57659b31037519 /src/diff.c
parent73fe6a8e20ffbc18ad667ff519c0fb8adf85fc3e (diff)
parentefef3795a2d29f6b99bb9575585bb3fc19c3ed79 (diff)
downloadlibgit2-dcfdb958e2033aa59beb624da4263ce031fbb21e.tar.gz
Merge branch 'new-error-handling' of github.com:libgit2/libgit2 into new-error-handling
Diffstat (limited to 'src/diff.c')
-rw-r--r--src/diff.c171
1 files changed, 131 insertions, 40 deletions
diff --git a/src/diff.c b/src/diff.c
index 69c944c63..54e8dd166 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -8,6 +8,7 @@
#include "git2/diff.h"
#include "diff.h"
#include "fileops.h"
+#include "config.h"
static void diff_delta__free(git_diff_delta *delta)
{
@@ -132,7 +133,17 @@ static int diff_delta__from_one(
git_delta_t status,
const git_index_entry *entry)
{
- git_diff_delta *delta = diff_delta__alloc(diff, status, entry->path);
+ git_diff_delta *delta;
+
+ if (status == GIT_DELTA_IGNORED &&
+ (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ return 0;
+
+ if (status == GIT_DELTA_UNTRACKED &&
+ (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ return 0;
+
+ delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta);
/* This fn is just for single-sided diffs */
@@ -168,6 +179,10 @@ static int diff_delta__from_two(
{
git_diff_delta *delta;
+ if (status == GIT_DELTA_UNMODIFIED &&
+ (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ return 0;
+
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
const git_index_entry *temp = old;
old = new;
@@ -219,15 +234,38 @@ static int diff_delta__cmp(const void *a, const void *b)
return val ? val : ((int)da->status - (int)db->status);
}
+static int config_bool(git_config *cfg, const char *name, int defvalue)
+{
+ int val = defvalue;
+ if (git_config_get_bool(cfg, name, &val) < 0)
+ giterr_clear();
+ return val;
+}
+
static git_diff_list *git_diff_list_alloc(
git_repository *repo, const git_diff_options *opts)
{
+ git_config *cfg;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
if (diff == NULL)
return NULL;
diff->repo = repo;
+ /* load config values that affect diff behavior */
+ if (git_repository_config(&cfg, repo) < 0)
+ goto fail;
+ if (config_bool(cfg, "core.symlinks", 1))
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
+ if (config_bool(cfg, "core.ignorestat", 0))
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
+ if (config_bool(cfg, "core.filemode", 1))
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
+ if (config_bool(cfg, "core.trustctime", 1))
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
+ /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
+ git_config_free(cfg);
+
if (opts == NULL)
return diff;
@@ -238,10 +276,8 @@ static git_diff_list *git_diff_list_alloc(
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.src_prefix || !diff->opts.dst_prefix)
+ goto fail;
if (diff->opts.flags & GIT_DIFF_REVERSE) {
char *swap = diff->opts.src_prefix;
@@ -249,16 +285,19 @@ static git_diff_list *git_diff_list_alloc(
diff->opts.dst_prefix = swap;
}
- if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0) {
- git__free(diff->opts.src_prefix);
- git__free(diff->opts.dst_prefix);
- git__free(diff);
- return NULL;
- }
+ if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0)
+ goto fail;
/* TODO: do something safe with the pathspec strarray */
return diff;
+
+fail:
+ git_vector_free(&diff->deltas);
+ git__free(diff->opts.src_prefix);
+ git__free(diff->opts.dst_prefix);
+ git__free(diff);
+ return NULL;
}
void git_diff_list_free(git_diff_list *diff)
@@ -312,6 +351,8 @@ static int oid_for_workdir_item(
return result;
}
+#define EXEC_BIT_MASK 0000111
+
static int maybe_modified(
git_iterator *old,
const git_index_entry *oitem,
@@ -320,53 +361,92 @@ static int maybe_modified(
git_diff_list *diff)
{
git_oid noid, *use_noid = NULL;
+ git_delta_t status = GIT_DELTA_MODIFIED;
+ unsigned int omode = oitem->mode;
+ unsigned int nmode = nitem->mode;
GIT_UNUSED(old);
- /* 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 0;
+ /* on platforms with no symlinks, promote plain files to symlinks */
+ if (S_ISLNK(omode) && S_ISREG(nmode) &&
+ !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
+ nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
+
+ /* on platforms with no execmode, clear exec bit from comparisons */
+ if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
+ omode = omode & ~EXEC_BIT_MASK;
+ nmode = nmode & ~EXEC_BIT_MASK;
+ }
+
+ /* support "assume unchanged" (badly, b/c we still stat everything) */
+ if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
+ status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
+ GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
- if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) {
+ /* support "skip worktree" index bit */
+ else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if basic type of file changed, then split into delete and add */
+ else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
return -1;
return 0;
}
- if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
- oitem->mode == nitem->mode)
- return 0;
+ /* if oids and modes match, then file is unmodified */
+ else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
+ omode == nmode)
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if we have a workdir item with an unknown oid, check deeper */
+ else if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) {
+ /* TODO: add check against index file st_mtime to avoid racy-git */
- 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 &&
+ (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
+ (oitem->ctime.seconds == nitem->ctime.seconds)) &&
oitem->mtime.seconds == nitem->mtime.seconds &&
- oitem->dev == nitem->dev &&
+ (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) ||
+ (oitem->dev == nitem->dev)) &&
oitem->ino == nitem->ino &&
oitem->uid == nitem->uid &&
oitem->gid == nitem->gid)
- return 0;
+ status = GIT_DELTA_UNMODIFIED;
+
+ else if (S_ISGITLINK(nmode)) {
+ git_submodule *sub;
+
+ if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
+ status = GIT_DELTA_UNMODIFIED;
+ else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
+ return -1;
+ else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL)
+ status = GIT_DELTA_UNMODIFIED;
+ else {
+ /* TODO: support other GIT_SUBMODULE_IGNORE values */
+ status = GIT_DELTA_UNMODIFIED;
+ }
+ }
/* TODO: check git attributes so we will not have to read the file
* in if it is marked binary.
*/
- if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
+ else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
return -1;
- if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
- oitem->mode == nitem->mode)
- return 0;
+ else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
+ omode == nmode)
+ status = GIT_DELTA_UNMODIFIED;
/* store calculated oid so we don't have to recalc later */
use_noid = &noid;
}
- return diff_delta__from_two(
- diff, GIT_DELTA_MODIFIED, oitem, nitem, use_noid);
+ return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
}
static int diff_from_iterators(
@@ -418,7 +498,12 @@ static int diff_from_iterators(
is_ignored = git_iterator_current_is_ignored(new);
if (S_ISDIR(nitem->mode)) {
- if (git__prefixcmp(oitem->path, nitem->path) == 0) {
+ /* recurse into directory if explicitly requested or
+ * if there are tracked items inside the directory
+ */
+ if ((diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) ||
+ (oitem && git__prefixcmp(oitem->path, nitem->path) == 0))
+ {
if (is_ignored)
ignore_prefix = nitem->path;
if (git_iterator_advance_into_directory(new, &nitem) < 0)
@@ -538,23 +623,27 @@ int git_diff_merge(
const git_diff_list *from)
{
int error = 0;
- unsigned int i = 0, j = 0;
git_vector onto_new;
git_diff_delta *delta;
+ unsigned int i, j;
+
+ assert(onto && from);
+
+ if (!from->deltas.length)
+ return 0;
if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0)
return -1;
- while (!error && (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;
+ for (i = 0, j = 0; 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(&from->deltas, j);
+ int cmp = !f ? -1 : !o ? 1 : strcmp(o->old.path, f->old.path);
- if (opath && (!fpath || strcmp(opath, fpath) < 0)) {
+ if (cmp < 0) {
delta = diff_delta__dup(o);
i++;
- } else if (fpath && (!opath || strcmp(opath, fpath) > 0)) {
+ } else if (cmp > 0) {
delta = diff_delta__dup(f);
j++;
} else {
@@ -563,10 +652,11 @@ int git_diff_merge(
j++;
}
- error = !delta ? -1 : git_vector_insert(&onto_new, delta);
+ if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
+ break;
}
- if (error == 0) {
+ if (!error) {
git_vector_swap(&onto->deltas, &onto_new);
onto->new_src = from->new_src;
}
@@ -577,3 +667,4 @@ int git_diff_merge(
return error;
}
+