]> git.ipfire.org Git - thirdparty/git.git/commitdiff
attr: add flag `--source` to work with tree-ish
authorKarthik Nayak <karthik.188@gmail.com>
Sat, 14 Jan 2023 08:30:38 +0000 (09:30 +0100)
committerJunio C Hamano <gitster@pobox.com>
Sat, 14 Jan 2023 16:49:55 +0000 (08:49 -0800)
The contents of the .gitattributes files may evolve over time, but "git
check-attr" always checks attributes against them in the working tree
and/or in the index. It may be beneficial to optionally allow the users
to check attributes taken from a commit other than HEAD against paths.

Add a new flag `--source` which will allow users to check the
attributes against a commit (actually any tree-ish would do). When the
user uses this flag, we go through the stack of .gitattributes files but
instead of checking the current working tree and/or in the index, we
check the blobs from the provided tree-ish object. This allows the
command to also be used in bare repositories.

Since we use a tree-ish object, the user can pass "--source
HEAD:subdirectory" and all the attributes will be looked up as if
subdirectory was the root directory of the repository.

We cannot simply use the `<rev>:<path>` syntax without the `--source`
flag, similar to how it is used in `git show` because any non-flag
parameter before `--` is treated as an attribute and any parameter after
`--` is treated as a pathname.

The change involves creating a new function `read_attr_from_blob`, which
given the path reads the blob for the path against the provided source and
parses the attributes line by line. This function is plugged into
`read_attr()` function wherein we go through the stack of attributes
files.

Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Toon Claes <toon@iotcl.com>
Co-authored-by: toon@iotcl.com
Signed-off-by: Junio C Hamano <gitster@pobox.com>
12 files changed:
Documentation/git-check-attr.txt
archive.c
attr.c
attr.h
builtin/check-attr.c
builtin/pack-objects.c
convert.c
ll-merge.c
pathspec.c
t/t0003-attributes.sh
userdiff.c
ws.c

index 84f41a8e82590f54bca103a067a95348093844b5..6e4f3aaf34c9579004be4c3f8d644287913f1505 100644 (file)
@@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information
 SYNOPSIS
 --------
 [verse]
-'git check-attr' [-a | --all | <attr>...] [--] <pathname>...
-'git check-attr' --stdin [-z] [-a | --all | <attr>...]
+'git check-attr' [--source <tree-ish>] [-a | --all | <attr>...] [--] <pathname>...
+'git check-attr' --stdin [-z] [--source <tree-ish>] [-a | --all | <attr>...]
 
 DESCRIPTION
 -----------
@@ -36,6 +36,11 @@ OPTIONS
        If `--stdin` is also given, input paths are separated
        with a NUL character instead of a linefeed character.
 
+--source=<tree-ish>::
+       Check attributes against the specified tree-ish. It is common to
+       specify the source tree by naming a commit, branch or tag associated
+       with it.
+
 \--::
        Interpret all preceding arguments as attributes and all following
        arguments as path names.
index 941495f5d7840715dd781ac675f32e3c24ce4cbe..81ff76fce99ed2c6eb569f033ab927e91f6632bc 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -120,7 +120,7 @@ static const struct attr_check *get_archive_attrs(struct index_state *istate,
        static struct attr_check *check;
        if (!check)
                check = attr_check_initl("export-ignore", "export-subst", NULL);
-       git_check_attr(istate, path, check);
+       git_check_attr(istate, NULL, path, check);
        return check;
 }
 
diff --git a/attr.c b/attr.c
index 42ad6de8c7c61432e4a3c950d940eee433680a74..9a1dcac470a16b10ef3f13dd71fd5344b19fd681 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -13,6 +13,8 @@
 #include "dir.h"
 #include "utf8.h"
 #include "quote.h"
+#include "revision.h"
+#include "object-store.h"
 #include "thread-utils.h"
 
 const char git_attr__true[] = "(builtin)true";
@@ -729,14 +731,62 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)
        return res;
 }
 
-static struct attr_stack *read_attr_from_index(struct index_state *istate,
-                                              const char *path,
-                                              unsigned flags)
+static struct attr_stack *read_attr_from_buf(char *buf, const char *path,
+                                            unsigned flags)
 {
        struct attr_stack *res;
-       char *buf, *sp;
+       char *sp;
        int lineno = 0;
 
+       if (!buf)
+               return NULL;
+
+       CALLOC_ARRAY(res, 1);
+       for (sp = buf; *sp;) {
+               char *ep;
+               int more;
+
+               ep = strchrnul(sp, '\n');
+               more = (*ep == '\n');
+               *ep = '\0';
+               handle_attr_line(res, sp, path, ++lineno, flags);
+               sp = ep + more;
+       }
+       free(buf);
+
+       return res;
+}
+
+static struct attr_stack *read_attr_from_blob(struct index_state *istate,
+                                             const struct object_id *tree_oid,
+                                             const char *path, unsigned flags)
+{
+       struct object_id oid;
+       unsigned long sz;
+       enum object_type type;
+       void *buf;
+       unsigned short mode;
+
+       if (!tree_oid)
+               return NULL;
+
+       if (get_tree_entry(istate->repo, tree_oid, path, &oid, &mode))
+               return NULL;
+
+       buf = repo_read_object_file(istate->repo, &oid, &type, &sz);
+       if (!buf || type != OBJ_BLOB) {
+               free(buf);
+               return NULL;
+       }
+
+       return read_attr_from_buf(buf, path, flags);
+}
+
+static struct attr_stack *read_attr_from_index(struct index_state *istate,
+                                              const char *path, unsigned flags)
+{
+       char *buf;
+
        if (!istate)
                return NULL;
 
@@ -758,28 +808,19 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
        if (!buf)
                return NULL;
 
-       CALLOC_ARRAY(res, 1);
-       for (sp = buf; *sp; ) {
-               char *ep;
-               int more;
-
-               ep = strchrnul(sp, '\n');
-               more = (*ep == '\n');
-               *ep = '\0';
-               handle_attr_line(res, sp, path, ++lineno, flags);
-               sp = ep + more;
-       }
-       free(buf);
-       return res;
+       return read_attr_from_buf(buf, path, flags);
 }
 
 static struct attr_stack *read_attr(struct index_state *istate,
+                                   const struct object_id *tree_oid,
                                    const char *path, unsigned flags)
 {
        struct attr_stack *res = NULL;
 
        if (direction == GIT_ATTR_INDEX) {
                res = read_attr_from_index(istate, path, flags);
+       } else if (tree_oid) {
+               res = read_attr_from_blob(istate, tree_oid, path, flags);
        } else if (!is_bare_repository()) {
                if (direction == GIT_ATTR_CHECKOUT) {
                        res = read_attr_from_index(istate, path, flags);
@@ -839,6 +880,7 @@ static void push_stack(struct attr_stack **attr_stack_p,
 }
 
 static void bootstrap_attr_stack(struct index_state *istate,
+                                const struct object_id *tree_oid,
                                 struct attr_stack **stack)
 {
        struct attr_stack *e;
@@ -864,7 +906,7 @@ static void bootstrap_attr_stack(struct index_state *istate,
        }
 
        /* root directory */
-       e = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);
+       e = read_attr(istate, tree_oid, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);
        push_stack(stack, e, xstrdup(""), 0);
 
        /* info frame */
@@ -878,6 +920,7 @@ static void bootstrap_attr_stack(struct index_state *istate,
 }
 
 static void prepare_attr_stack(struct index_state *istate,
+                              const struct object_id *tree_oid,
                               const char *path, int dirlen,
                               struct attr_stack **stack)
 {
@@ -899,7 +942,7 @@ static void prepare_attr_stack(struct index_state *istate,
         * .gitattributes in deeper directories to shallower ones,
         * and finally use the built-in set as the default.
         */
-       bootstrap_attr_stack(istate, stack);
+       bootstrap_attr_stack(istate, tree_oid, stack);
 
        /*
         * Pop the "info" one that is always at the top of the stack.
@@ -954,7 +997,7 @@ static void prepare_attr_stack(struct index_state *istate,
                strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));
                strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE);
 
-               next = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW);
+               next = read_attr(istate, tree_oid, pathbuf.buf, READ_ATTR_NOFOLLOW);
 
                /* reset the pathbuf to not include "/.gitattributes" */
                strbuf_setlen(&pathbuf, len);
@@ -1074,8 +1117,8 @@ static void determine_macros(struct all_attrs_item *all_attrs,
  * Otherwise all attributes are collected.
  */
 static void collect_some_attrs(struct index_state *istate,
-                              const char *path,
-                              struct attr_check *check)
+                              const struct object_id *tree_oid,
+                              const char *path, struct attr_check *check)
 {
        int pathlen, rem, dirlen;
        const char *cp, *last_slash = NULL;
@@ -1094,7 +1137,7 @@ static void collect_some_attrs(struct index_state *istate,
                dirlen = 0;
        }
 
-       prepare_attr_stack(istate, path, dirlen, &check->stack);
+       prepare_attr_stack(istate, tree_oid, path, dirlen, &check->stack);
        all_attrs_init(&g_attr_hashmap, check);
        determine_macros(check->all_attrs, check->stack);
 
@@ -1103,12 +1146,12 @@ static void collect_some_attrs(struct index_state *istate,
 }
 
 void git_check_attr(struct index_state *istate,
-                   const char *path,
+                   const struct object_id *tree_oid, const char *path,
                    struct attr_check *check)
 {
        int i;
 
-       collect_some_attrs(istate, path, check);
+       collect_some_attrs(istate, tree_oid, path, check);
 
        for (i = 0; i < check->nr; i++) {
                size_t n = check->items[i].attr->attr_nr;
@@ -1119,13 +1162,13 @@ void git_check_attr(struct index_state *istate,
        }
 }
 
-void git_all_attrs(struct index_state *istate,
+void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid,
                   const char *path, struct attr_check *check)
 {
        int i;
 
        attr_check_reset(check);
-       collect_some_attrs(istate, path, check);
+       collect_some_attrs(istate, tree_oid, path, check);
 
        for (i = 0; i < check->all_attrs_nr; i++) {
                const char *name = check->all_attrs[i].attr->name;
diff --git a/attr.h b/attr.h
index 3fb40cced08359b0fa93f6da155917e62668558e..fca6c30430462abefb456ff51313c5f2dfbd8f25 100644 (file)
--- a/attr.h
+++ b/attr.h
  */
 
 struct index_state;
+struct object_id;
 
 /**
  * An attribute is an opaque object that is identified by its name. Pass the
@@ -190,13 +191,14 @@ void attr_check_free(struct attr_check *check);
 const char *git_attr_name(const struct git_attr *);
 
 void git_check_attr(struct index_state *istate,
-                   const char *path, struct attr_check *check);
+                   const struct object_id *tree_oid, const char *path,
+                   struct attr_check *check);
 
 /*
  * Retrieve all attributes that apply to the specified path.
  * check holds the attributes and their values.
  */
-void git_all_attrs(struct index_state *istate,
+void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid,
                   const char *path, struct attr_check *check);
 
 enum git_attr_direction {
index 0fef10eb6bc7dafb6e5e30ef8b988d1921f99f98..d7a40e674ca049e414d1443b2e5a78d85c392ab2 100644 (file)
@@ -9,9 +9,10 @@
 static int all_attrs;
 static int cached_attrs;
 static int stdin_paths;
+static char *source;
 static const char * const check_attr_usage[] = {
-N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."),
-N_("git check-attr --stdin [-z] [-a | --all | <attr>...]"),
+N_("git check-attr [--source <tree-ish>] [-a | --all | <attr>...] [--] <pathname>..."),
+N_("git check-attr --stdin [-z] [--source <tree-ish>] [-a | --all | <attr>...]"),
 NULL
 };
 
@@ -23,6 +24,7 @@ static const struct option check_attr_options[] = {
        OPT_BOOL(0 , "stdin", &stdin_paths, N_("read file names from stdin")),
        OPT_BOOL('z', NULL, &nul_term_line,
                 N_("terminate input and output records by a NUL character")),
+       OPT_STRING(0, "source", &source, N_("<tree-ish>"), N_("which tree-ish to check attributes at")),
        OPT_END()
 };
 
@@ -55,27 +57,26 @@ static void output_attr(struct attr_check *check, const char *file)
        }
 }
 
-static void check_attr(const char *prefix,
-                      struct attr_check *check,
-                      int collect_all,
+static void check_attr(const char *prefix, struct attr_check *check,
+                      const struct object_id *tree_oid, int collect_all,
                       const char *file)
+
 {
        char *full_path =
                prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
 
        if (collect_all) {
-               git_all_attrs(&the_index, full_path, check);
+               git_all_attrs(&the_index, tree_oid, full_path, check);
        } else {
-               git_check_attr(&the_index, full_path, check);
+               git_check_attr(&the_index, tree_oid, full_path, check);
        }
        output_attr(check, file);
 
        free(full_path);
 }
 
-static void check_attr_stdin_paths(const char *prefix,
-                                  struct attr_check *check,
-                                  int collect_all)
+static void check_attr_stdin_paths(const char *prefix, struct attr_check *check,
+                                  const struct object_id *tree_oid, int collect_all)
 {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf unquoted = STRBUF_INIT;
@@ -89,7 +90,7 @@ static void check_attr_stdin_paths(const char *prefix,
                                die("line is badly quoted");
                        strbuf_swap(&buf, &unquoted);
                }
-               check_attr(prefix, check, collect_all, buf.buf);
+               check_attr(prefix, check, tree_oid, collect_all, buf.buf);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
        strbuf_release(&buf);
@@ -105,6 +106,8 @@ static NORETURN void error_with_usage(const char *msg)
 int cmd_check_attr(int argc, const char **argv, const char *prefix)
 {
        struct attr_check *check;
+       struct object_id *tree_oid = NULL;
+       struct object_id initialized_oid;
        int cnt, i, doubledash, filei;
 
        if (!is_bare_repository())
@@ -176,11 +179,17 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                }
        }
 
+       if (source) {
+               if (repo_get_oid_tree(the_repository, source, &initialized_oid))
+                       die("%s: not a valid tree-ish source", source);
+               tree_oid = &initialized_oid;
+       }
+
        if (stdin_paths)
-               check_attr_stdin_paths(prefix, check, all_attrs);
+               check_attr_stdin_paths(prefix, check, tree_oid, all_attrs);
        else {
                for (i = filei; i < argc; i++)
-                       check_attr(prefix, check, all_attrs, argv[i]);
+                       check_attr(prefix, check, tree_oid, all_attrs, argv[i]);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
 
index 2193f80b897270deda441885fcfe82af4997ad91..cabace4abbe41f00ad6fd7caa0c2251a8364935a 100644 (file)
@@ -1318,7 +1318,7 @@ static int no_try_delta(const char *path)
 
        if (!check)
                check = attr_check_initl("delta", NULL);
-       git_check_attr(the_repository->index, path, check);
+       git_check_attr(the_repository->index, NULL, path, check);
        if (ATTR_FALSE(check->items[0].value))
                return 1;
        return 0;
index 9b67649032092f63fff857df3c78f17a3a566ca8..a54d1690c083178fd0c1356c8c8c8bee5cfdbda1 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1308,7 +1308,7 @@ void convert_attrs(struct index_state *istate,
                git_config(read_convert_config, NULL);
        }
 
-       git_check_attr(istate, path, check);
+       git_check_attr(istate, NULL, path, check);
        ccheck = check->items;
        ca->crlf_action = git_path_check_crlf(ccheck + 4);
        if (ca->crlf_action == CRLF_UNDEFINED)
index 22a603e8af402d11fff97172d3f3ded3a9849cd4..130d26501c69cd47b762f13b46ff4835cfa6261a 100644 (file)
@@ -391,7 +391,7 @@ enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
                normalize_file(theirs, path, istate);
        }
 
-       git_check_attr(istate, path, check);
+       git_check_attr(istate, NULL, path, check);
        ll_driver_name = check->items[0].value;
        if (check->items[1].value) {
                marker_size = atoi(check->items[1].value);
@@ -419,7 +419,7 @@ int ll_merge_marker_size(struct index_state *istate, const char *path)
 
        if (!check)
                check = attr_check_initl("conflict-marker-size", NULL);
-       git_check_attr(istate, path, check);
+       git_check_attr(istate, NULL, path, check);
        if (check->items[0].value) {
                marker_size = atoi(check->items[0].value);
                if (marker_size <= 0)
index 46e77a85fee9d86e6e16e9f466ecfc3db893fd16..48dec2c7098152d9552985ecd58e16a827dca206 100644 (file)
@@ -732,7 +732,7 @@ int match_pathspec_attrs(struct index_state *istate,
        if (name[namelen])
                name = to_free = xmemdupz(name, namelen);
 
-       git_check_attr(istate, name, item->attr_check);
+       git_check_attr(istate, NULL, name, item->attr_check);
 
        free(to_free);
 
index b3aabb8aa32e3f7377e7a959d6f3503742e2c82e..6ae30ab080946a08a53274da53e60bcbd5a3ef13 100755 (executable)
@@ -25,7 +25,15 @@ attr_check_quote () {
        git check-attr test -- "$path" >actual &&
        echo "\"$quoted_path\": test: $expect" >expect &&
        test_cmp expect actual
+}
+
+attr_check_source () {
+       path="$1" expect="$2" source="$3" git_opts="$4" &&
 
+       git $git_opts check-attr --source $source test -- "$path" >actual 2>err &&
+       echo "$path: test: $expect" >expect &&
+       test_cmp expect actual &&
+       test_must_be_empty err
 }
 
 test_expect_success 'open-quoted pathname' '
@@ -33,7 +41,6 @@ test_expect_success 'open-quoted pathname' '
        attr_check a unspecified
 '
 
-
 test_expect_success 'setup' '
        mkdir -p a/b/d a/c b &&
        (
@@ -80,12 +87,23 @@ test_expect_success 'setup' '
        EOF
 '
 
+test_expect_success 'setup branches' '
+       mkdir -p foo/bar &&
+       test_commit --printf "add .gitattributes" foo/bar/.gitattributes \
+               "f test=f\na/i test=n\n" tag-1 &&
+       test_commit --printf "add .gitattributes" foo/bar/.gitattributes \
+               "g test=g\na/i test=m\n" tag-2 &&
+       rm foo/bar/.gitattributes
+'
+
 test_expect_success 'command line checks' '
        test_must_fail git check-attr &&
        test_must_fail git check-attr -- &&
        test_must_fail git check-attr test &&
        test_must_fail git check-attr test -- &&
        test_must_fail git check-attr -- f &&
+       test_must_fail git check-attr --source &&
+       test_must_fail git check-attr --source not-a-valid-ref &&
        echo "f" | test_must_fail git check-attr --stdin &&
        echo "f" | test_must_fail git check-attr --stdin -- f &&
        echo "f" | test_must_fail git check-attr --stdin test -- f &&
@@ -287,6 +305,15 @@ test_expect_success 'using --git-dir and --work-tree' '
        )
 '
 
+test_expect_success 'using --source' '
+       attr_check_source foo/bar/f f tag-1 &&
+       attr_check_source foo/bar/a/i n tag-1 &&
+       attr_check_source foo/bar/f unspecified tag-2 &&
+       attr_check_source foo/bar/a/i m tag-2 &&
+       attr_check_source foo/bar/g g tag-2 &&
+       attr_check_source foo/bar/g unspecified tag-1
+'
+
 test_expect_success 'setup bare' '
        git clone --template= --bare . bare.git
 '
@@ -306,6 +333,18 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '
        )
 '
 
+test_expect_success 'bare repository: with --source' '
+       (
+               cd bare.git &&
+               attr_check_source foo/bar/f f tag-1 &&
+               attr_check_source foo/bar/a/i n tag-1 &&
+               attr_check_source foo/bar/f unspecified tag-2 &&
+               attr_check_source foo/bar/a/i m tag-2 &&
+               attr_check_source foo/bar/g g tag-2 &&
+               attr_check_source foo/bar/g unspecified tag-1
+       )
+'
+
 test_expect_success 'bare repository: check that --cached honors index' '
        (
                cd bare.git &&
index e25356a06124dae49a486884ccdd2872cb429a29..d71b82feb74704b1c6b3027f84d2d478403d6e42 100644 (file)
@@ -413,7 +413,7 @@ struct userdiff_driver *userdiff_find_by_path(struct index_state *istate,
                check = attr_check_initl("diff", NULL);
        if (!path)
                return NULL;
-       git_check_attr(istate, path, check);
+       git_check_attr(istate, NULL, path, check);
 
        if (ATTR_TRUE(check->items[0].value))
                return &driver_true;
diff --git a/ws.c b/ws.c
index 46a77bcad6670eacb793c9e07334f61a4b93f242..9ea4baaf5f4c4e72d466ecb395ffdff11454cf5e 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -78,7 +78,7 @@ unsigned whitespace_rule(struct index_state *istate, const char *pathname)
        if (!attr_whitespace_rule)
                attr_whitespace_rule = attr_check_initl("whitespace", NULL);
 
-       git_check_attr(istate, pathname, attr_whitespace_rule);
+       git_check_attr(istate, NULL, pathname, attr_whitespace_rule);
        value = attr_whitespace_rule->items[0].value;
        if (ATTR_TRUE(value)) {
                /* true (whitespace) */