aboutsummaryrefslogtreecommitdiffstats
path: root/tree-diff.c
diff options
context:
space:
mode:
authorJeff King <peff@peff.net>2025-08-07 22:52:58 +0200
committerJunio C Hamano <gitster@pobox.com>2025-08-07 15:29:35 -0700
commita1dfa5448d583bbfd1ec45642a4495ad499970c9 (patch)
treec57bd65d0f3e6013e0d808fbecaa0244c42672f2 /tree-diff.c
parentwithin_depth: fix return for empty path (diff)
downloadgit-a1dfa5448d583bbfd1ec45642a4495ad499970c9.tar.gz
git-a1dfa5448d583bbfd1ec45642a4495ad499970c9.zip
diff: teach tree-diff a max-depth parameter
When you are doing a tree-diff, there are basically two options: do not recurse into subtrees at all, or recurse indefinitely. While most callers would want to always recurse and see full pathnames, some may want the efficiency of looking only at a particular level of the tree. This is currently easy to do for the top-level (just turn off recursion), but you cannot say "show me what changed in subdir/, but do not recurse". This patch adds a max-depth parameter which is measured from the closest pathspec match, so that you can do: git log --raw --max-depth=1 -- a/b/c and see the raw output for a/b/c/, but not those of a/b/c/d/ (instead of the raw output you would see for a/b/c/d). Co-authored-by: Toon Claes <toon@iotcl.com> Signed-off-by: Toon Claes <toon@iotcl.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'tree-diff.c')
-rw-r--r--tree-diff.c78
1 files changed, 75 insertions, 3 deletions
diff --git a/tree-diff.c b/tree-diff.c
index e00fc2f450..5988148b60 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -13,6 +13,7 @@
#include "tree-walk.h"
#include "environment.h"
#include "repository.h"
+#include "dir.h"
/*
* Some mode bits are also used internally for computations.
@@ -48,6 +49,73 @@
free((x)); \
} while(0)
+/* Returns true if and only if "dir" is a leading directory of "path" */
+static int is_dir_prefix(const char *path, const char *dir, int dirlen)
+{
+ return !strncmp(path, dir, dirlen) &&
+ (!path[dirlen] || path[dirlen] == '/');
+}
+
+static int check_recursion_depth(const struct strbuf *name,
+ const struct pathspec *ps,
+ int max_depth)
+{
+ int i;
+
+ if (!ps->nr)
+ return within_depth(name->buf, name->len, 1, max_depth);
+
+ /*
+ * We look through the pathspecs in reverse-sorted order, because we
+ * want to find the longest match first (e.g., "a/b" is better for
+ * checking depth than "a/b/c").
+ */
+ for (i = ps->nr - 1; i >= 0; i--) {
+ const struct pathspec_item *item = ps->items+i;
+
+ /*
+ * If the name to match is longer than the pathspec, then we
+ * are only interested if the pathspec matches and we are
+ * within the allowed depth.
+ */
+ if (name->len >= item->len) {
+ if (!is_dir_prefix(name->buf, item->match, item->len))
+ continue;
+ return within_depth(name->buf + item->len,
+ name->len - item->len,
+ 1, max_depth);
+ }
+
+ /*
+ * Otherwise, our name is shorter than the pathspec. We need to
+ * check if it is a prefix of the pathspec; if so, we must
+ * always recurse in order to process further (the resulting
+ * paths we find might or might not match our pathspec, but we
+ * cannot know until we recurse).
+ */
+ if (is_dir_prefix(item->match, name->buf, name->len))
+ return 1;
+ }
+ return 0;
+}
+
+static int should_recurse(const struct strbuf *name, struct diff_options *opt)
+{
+ if (!opt->flags.recursive)
+ return 0;
+ if (!opt->max_depth_valid)
+ return 1;
+
+ /*
+ * We catch this during diff_setup_done, but let's double-check
+ * against any internal munging.
+ */
+ if (opt->pathspec.has_wildcard)
+ BUG("wildcard pathspecs are incompatible with max-depth");
+
+ return check_recursion_depth(name, &opt->pathspec, opt->max_depth);
+}
+
static void ll_diff_tree_paths(
struct combine_diff_path ***tail, const struct object_id *oid,
const struct object_id **parents_oid, int nparent,
@@ -170,9 +238,13 @@ static void emit_path(struct combine_diff_path ***tail,
mode = 0;
}
- if (opt->flags.recursive && isdir) {
- recurse = 1;
- emitthis = opt->flags.tree_in_recursive;
+ if (isdir) {
+ strbuf_add(base, path, pathlen);
+ if (should_recurse(base, opt)) {
+ recurse = 1;
+ emitthis = opt->flags.tree_in_recursive;
+ }
+ strbuf_setlen(base, old_baselen);
}
if (emitthis) {