summaryrefslogtreecommitdiff
path: root/src/diff.c
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2012-04-13 15:00:29 -0700
committerRussell Belfer <rb@github.com>2012-04-13 15:00:29 -0700
commit14a513e05866721f5ceba18cf425568d2f6af143 (patch)
tree4c31553a2c971adf579d2c5c287749ce45949c8c /src/diff.c
parentd1f331564da9d6110b80b538a501da0c66e59142 (diff)
downloadlibgit2-14a513e05866721f5ceba18cf425568d2f6af143.tar.gz
Add support for pathspec to diff and status
This adds preliminary support for pathspecs to diff and status. The implementation is not very optimized (it still looks at every single file and evaluated the the pathspec match against them), but it works.
Diffstat (limited to 'src/diff.c')
-rw-r--r--src/diff.c97
1 files changed, 92 insertions, 5 deletions
diff --git a/src/diff.c b/src/diff.c
index fa841f717..c6a0088ec 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -9,6 +9,50 @@
#include "diff.h"
#include "fileops.h"
#include "config.h"
+#include "attr_file.h"
+
+static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
+{
+ const char *str;
+
+ if (pathspec == NULL || pathspec->count == 0)
+ return false;
+ if (pathspec->count > 1)
+ return true;
+
+ str = pathspec->strings[0];
+ if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
+ return false;
+ return true;
+}
+
+static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
+{
+ unsigned int i;
+ git_attr_fnmatch *match;
+
+ if (!diff->pathspec.length)
+ return true;
+
+ git_vector_foreach(&diff->pathspec, i, match) {
+ int result = git__fnmatch(match->pattern, path, 0);
+
+ /* if we didn't match, look for exact dirname prefix match */
+ if (result == GIT_ENOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
+ strncmp(path, match->pattern, match->length) == 0 &&
+ path[match->length] == '/')
+ result = 0;
+
+ if (result == 0)
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
+
+ if (result != GIT_ENOMATCH)
+ giterr_clear();
+ }
+
+ return false;
+}
static void diff_delta__free(git_diff_delta *delta)
{
@@ -143,6 +187,9 @@ static int diff_delta__from_one(
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return 0;
+ if (!diff_path_matches_pathspec(diff, entry->path))
+ return 0;
+
delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta);
@@ -246,6 +293,7 @@ static git_diff_list *git_diff_list_alloc(
git_repository *repo, const git_diff_options *opts)
{
git_config *cfg;
+ size_t i;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
if (diff == NULL)
return NULL;
@@ -269,6 +317,7 @@ static git_diff_list *git_diff_list_alloc(
return diff;
memcpy(&diff->opts, opts, sizeof(git_diff_options));
+ memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec));
diff->opts.src_prefix = diff_strdup_prefix(
opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT);
@@ -287,21 +336,45 @@ static git_diff_list *git_diff_list_alloc(
if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0)
goto fail;
- /* TODO: do something safe with the pathspec strarray */
+ /* only copy pathspec if it is "interesting" so we can test
+ * diff->pathspec.length > 0 to know if it is worth calling
+ * fnmatch as we iterate.
+ */
+ if (!diff_pathspec_is_interesting(&opts->pathspec))
+ return diff;
+
+ if (git_vector_init(&diff->pathspec, opts->pathspec.count, NULL) < 0)
+ goto fail;
+
+ for (i = 0; i < opts->pathspec.count; ++i) {
+ int ret;
+ const char *pattern = opts->pathspec.strings[i];
+ git_attr_fnmatch *match =
+ git__calloc(1, sizeof(git_attr_fnmatch));
+ if (!match)
+ goto fail;
+ ret = git_attr_fnmatch__parse(match, NULL, &pattern);
+ if (ret == GIT_ENOTFOUND) {
+ git__free(match);
+ continue;
+ } else if (ret < 0)
+ goto fail;
+
+ if (git_vector_insert(&diff->pathspec, match) < 0)
+ goto fail;
+ }
return diff;
fail:
- git_vector_free(&diff->deltas);
- git__free(diff->opts.src_prefix);
- git__free(diff->opts.dst_prefix);
- git__free(diff);
+ git_diff_list_free(diff);
return NULL;
}
void git_diff_list_free(git_diff_list *diff)
{
git_diff_delta *delta;
+ git_attr_fnmatch *match;
unsigned int i;
if (!diff)
@@ -312,6 +385,17 @@ void git_diff_list_free(git_diff_list *diff)
diff->deltas.contents[i] = NULL;
}
git_vector_free(&diff->deltas);
+
+ git_vector_foreach(&diff->pathspec, i, match) {
+ if (match != NULL) {
+ git__free(match->pattern);
+ match->pattern = NULL;
+ git__free(match);
+ diff->pathspec.contents[i] = NULL;
+ }
+ }
+ git_vector_free(&diff->pathspec);
+
git__free(diff->opts.src_prefix);
git__free(diff->opts.dst_prefix);
git__free(diff);
@@ -366,6 +450,9 @@ static int maybe_modified(
GIT_UNUSED(old);
+ if (!diff_path_matches_pathspec(diff, oitem->path))
+ 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))