{"thread":{"id":"59025","subject":"[PATCH v5 0/2] check-attr: add support to work with tree-ish","startedAt":"2023-01-02T11:04:19Z","lastAt":"2023-01-14T08:27:53Z","messageCount":10,"participants":["Karthik Nayak","Junio C Hamano","Phillip Wood","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":5,"patchTotal":2},"messages":[{"id":"469747","messageId":"cover.1671793109.git.karthik.188@gmail.com","threadId":"59025","inReplyTo":"https://lore.kernel.org/git/cover.1671630304.git.karthik.188@gmail.com/","subject":"[PATCH v5 0/2] check-attr: add support to work with tree-ish","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-02T11:04:04Z","receivedAt":"2023-01-02T11:04:19Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"v1: https://lore.kernel.org/git/20221206103736.53909-1-karthik.188@gmail.com/\nv2: https://lore.kernel.org/git/CAOLa=ZSsFGBw3ta1jWN8cmUch2ca=zTEjp1xMA6Linafx9W53g@mail.gmail.com/T/#t\nv3: https://lore.kernel.org/git/20221216093552.3171319-1-karthik.188@gmail.com/\nv4: https://lore.kernel.org/git/cover.1671630304.git.karthik.188@gmail.com\n\nGiven a pathname, git-check-attr(1) will list the attributes which apply to that\npathname by reading all relevant gitattributes files. Currently there is no way\nto specify a tree-ish to read the gitattributes from.\n\nThis is specifically useful in bare repositories wherein the gitattributes are\nonly present in the git working tree but not available directly on the\nfilesystem.\n\nThis series aims to add a new flag `--source` to git-check-attr(1) which\nallows us to read gitattributes from the specified tree-ish.\n\nChanges since version 4:\n- Changed the flag from `--revision` to `--source`\n- Removed uneeded header imports\n- Using a pre-initialized variable instead of malloc for the tree_oid\n- Using `die()` instead of `error()` for bad tree-ish provided\n\nRange-diff against v4:\n\n1:  6224754179 = 1:  6224754179 t0003: move setup for `--all` into new block\n2:  a161dbdf8b ! 2:  d835d989ad attr: add flag `--revision` to work with revisions\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    attr: add flag `--revision` to work with revisions\n    +    attr: add flag `--source` to work with tree-ish\n     \n         The contents of the .gitattributes files may evolve over time, but \"git\n         check-attr\" always checks attributes against them in the working tree\n         and/or in the index. It may be beneficial to optionally allow the users\n         to check attributes taken from a commit other than HEAD against paths.\n     \n    -    Add a new flag `--revision` which will allow users to check the\n    +    Add a new flag `--source` which will allow users to check the\n         attributes against a commit (actually any tree-ish would do). When the\n         user uses this flag, we go through the stack of .gitattributes files but\n         instead of checking the current working tree and/or in the index, we\n         check the blobs from the provided tree-ish object. This allows the\n         command to also be used in bare repositories.\n     \n    -    Since we use a tree-ish object, the user can pass \"--revision\n    +    Since we use a tree-ish object, the user can pass \"--source\n         HEAD:subdirectory\" and all the attributes will be looked up as if\n         subdirectory was the root directory of the repository.\n     \n    -    We cannot simply use the `<rev>:<path>` syntax without the `--revision`\n    +    We cannot simply use the `<rev>:<path>` syntax without the `--source`\n         flag, similar to how it is used in `git show` because any non-flag\n         parameter before `--` is treated as an attribute and any parameter after\n         `--` is treated as a pathname.\n     \n         The change involves creating a new function `read_attr_from_blob`, which\n    -    given the path reads the blob for the path against the provided revision and\n    +    given the path reads the blob for the path against the provided source and\n         parses the attributes line by line. This function is plugged into\n         `read_attr()` function wherein we go through the stack of attributes\n         files.\n    @@ Documentation/git-check-attr.txt: git-check-attr - Display gitattributes informa\n      [verse]\n     -'git check-attr' [-a | --all | <attr>...] [--] <pathname>...\n     -'git check-attr' --stdin [-z] [-a | --all | <attr>...]\n    -+'git check-attr' [--revision <revision>] [-a | --all | <attr>...] [--] <pathname>...\n    -+'git check-attr' --stdin [-z] [--revision <revision>] [-a | --all | <attr>...]\n    ++'git check-attr' [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\n    ++'git check-attr' --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\n      \n      DESCRIPTION\n      -----------\n    @@ Documentation/git-check-attr.txt: OPTIONS\n      \tIf `--stdin` is also given, input paths are separated\n      \twith a NUL character instead of a linefeed character.\n      \n    -+--revision=<revision>::\n    -+\tCheck attributes against the specified commit. All the attributes will\n    -+\tbe checked against the provided revision. Paths provided as part of the\n    -+\trevision will be treated as the root directory.\n    ++--source=<tree>::\n    ++\tCheck attributes against the specified tree-ish. Paths provided as part\n    ++\tof the revision will be treated as the root directory. It is common to\n    ++\tspecify the source tree by naming a commit, branch or tag associated\n    ++\twith it.\n     +\n      \\--::\n      \tInterpret all preceding arguments as attributes and all following\n    @@ archive.c: static const struct attr_check *get_archive_attrs(struct index_state\n     \n      ## attr.c ##\n     @@\n    - #include \"exec-cmd.h\"\n    - #include \"attr.h\"\n      #include \"dir.h\"\n    -+#include \"strbuf.h\"\n    -+#include \"tree-walk.h\"\n      #include \"utf8.h\"\n      #include \"quote.h\"\n     +#include \"revision.h\"\n    @@ attr.c: void git_check_attr(struct index_state *istate,\n      \t\tconst char *name = check->all_attrs[i].attr->name;\n     \n      ## attr.h ##\n    -@@\n    - #ifndef ATTR_H\n    - #define ATTR_H\n    - \n    -+#include \"hash.h\"\n    -+\n    - /**\n    -  * gitattributes mechanism gives a uniform way to associate various attributes\n    -  * to set of paths.\n     @@ attr.h: void attr_check_free(struct attr_check *check);\n      const char *git_attr_name(const struct git_attr *);\n      \n    @@ attr.h: void attr_check_free(struct attr_check *check);\n      enum git_attr_direction {\n     \n      ## builtin/check-attr.c ##\n    -@@\n    -+#include \"repository.h\"\n    - #define USE_THE_INDEX_VARIABLE\n    - #include \"builtin.h\"\n    - #include \"cache.h\"\n     @@\n      static int all_attrs;\n      static int cached_attrs;\n      static int stdin_paths;\n    -+static char *revision;\n    ++static char *source;\n      static const char * const check_attr_usage[] = {\n     -N_(\"git check-attr [-a | --all | <attr>...] [--] <pathname>...\"),\n     -N_(\"git check-attr --stdin [-z] [-a | --all | <attr>...]\"),\n    -+N_(\"git check-attr [--revision <revision>] [-a | --all | <attr>...] [--] <pathname>...\"),\n    -+N_(\"git check-attr --stdin [-z] [--revision <revision>] [-a | --all | <attr>...]\"),\n    ++N_(\"git check-attr [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\"),\n    ++N_(\"git check-attr --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\"),\n      NULL\n      };\n      \n    @@ builtin/check-attr.c: static const struct option check_attr_options[] = {\n      \tOPT_BOOL(0 , \"stdin\", &stdin_paths, N_(\"read file names from stdin\")),\n      \tOPT_BOOL('z', NULL, &nul_term_line,\n      \t\t N_(\"terminate input and output records by a NUL character\")),\n    -+\tOPT_STRING(0, \"revision\", &revision, N_(\"revision\"), N_(\"check attributes at this revision\")),\n    ++\tOPT_STRING(0, \"source\", &source, N_(\"<tree-ish>\"), N_(\"which tree-ish to check attributes at\")),\n      \tOPT_END()\n      };\n      \n    @@ builtin/check-attr.c: static NORETURN void error_with_usage(const char *msg)\n      {\n      \tstruct attr_check *check;\n     +\tstruct object_id *tree_oid = NULL;\n    ++\tstruct object_id initialized_oid;\n      \tint cnt, i, doubledash, filei;\n      \n      \tif (!is_bare_repository())\n    @@ builtin/check-attr.c: int cmd_check_attr(int argc, const char **argv, const char\n      \t\t}\n      \t}\n      \n    -+\tif (revision) {\n    -+\t\ttree_oid = xmalloc(sizeof(struct object_id));\n    -+\n    -+\t\tif (repo_get_oid_tree(the_repository, revision, tree_oid))\n    -+\t\t\terror(\"%s: not a valid revision\", revision);\n    ++\tif (source) {\n    ++\t\tif (repo_get_oid_tree(the_repository, source, &initialized_oid))\n    ++\t\t\tdie(\"%s: not a valid tree-ish source\", source);\n    ++\t\ttree_oid = &initialized_oid;\n     +\t}\n     +\n      \tif (stdin_paths)\n    @@ t/t0003-attributes.sh: attr_check_quote () {\n      \ttest_cmp expect actual\n     +}\n     +\n    -+attr_check_revision () {\n    -+\tpath=\"$1\" expect=\"$2\" revision=\"$3\" git_opts=\"$4\" &&\n    ++attr_check_source () {\n    ++\tpath=\"$1\" expect=\"$2\" source=\"$3\" git_opts=\"$4\" &&\n      \n    -+\tgit $git_opts check-attr --revision $revision test -- \"$path\" >actual 2>err &&\n    ++\tgit $git_opts check-attr --source $source test -- \"$path\" >actual 2>err &&\n     +\techo \"$path: test: $expect\" >expect &&\n     +\ttest_cmp expect actual\n    ++\ttest_must_be_empty err\n      }\n      \n      test_expect_success 'open-quoted pathname' '\n    @@ t/t0003-attributes.sh: test_expect_success 'setup' '\n      '\n      \n     +test_expect_success 'setup branches' '\n    -+\t(\n    -+\t\techo \"f\ttest=f\" &&\n    -+\t\techo \"a/i test=n\"\n    -+\t) | git hash-object -w --stdin >id &&\n    -+\tgit update-index --add --cacheinfo 100644,$(cat id),foo/bar/.gitattributes &&\n    -+\tgit write-tree >id &&\n    -+\ttree_id=$(cat id) &&\n    -+\tgit commit-tree $tree_id -m \"random commit message\" >id &&\n    -+\tcommit_id=$(cat id) &&\n    -+\tgit update-ref refs/heads/branch1 $commit_id &&\n    ++\tmkdir -p foo/bar &&\n    ++\ttest_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n    ++\t\t\"f test=f\\na/i test=n\\n\" tag-1 &&\n     +\n    -+\t(\n    -+\t\techo \"g test=g\" &&\n    -+\t\techo \"a/i test=m\"\n    -+\t) | git hash-object -w --stdin >id &&\n    -+\tgit update-index --add --cacheinfo 100644,$(cat id),foo/bar/.gitattributes &&\n    -+\tgit write-tree >id &&\n    -+\ttree_id=$(cat id) &&\n    -+\tgit commit-tree $tree_id -m \"random commit message\" >id &&\n    -+\tcommit_id=$(cat id) &&\n    -+\tgit update-ref refs/heads/branch2 $commit_id\n    ++\tmkdir -p foo/bar &&\n    ++\ttest_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n    ++\t\t\"g test=g\\na/i test=m\\n\" tag-2\n     +'\n     +\n      test_expect_success 'command line checks' '\n    @@ t/t0003-attributes.sh: test_expect_success 'setup' '\n      \ttest_must_fail git check-attr test &&\n      \ttest_must_fail git check-attr test -- &&\n      \ttest_must_fail git check-attr -- f &&\n    -+\ttest_must_fail git check-attr --revision &&\n    -+\ttest_must_fail git check-attr --revision not-a-valid-ref &&\n    ++\ttest_must_fail git check-attr --source &&\n    ++\ttest_must_fail git check-attr --source not-a-valid-ref &&\n      \techo \"f\" | test_must_fail git check-attr --stdin &&\n      \techo \"f\" | test_must_fail git check-attr --stdin -- f &&\n      \techo \"f\" | test_must_fail git check-attr --stdin test -- f &&\n    @@ t/t0003-attributes.sh: test_expect_success 'using --git-dir and --work-tree' '\n      \t)\n      '\n      \n    -+test_expect_success 'using --revision' '\n    -+\tattr_check_revision foo/bar/f f branch1 &&\n    -+\tattr_check_revision foo/bar/a/i n branch1 &&\n    -+\tattr_check_revision foo/bar/f unspecified branch2 &&\n    -+\tattr_check_revision foo/bar/a/i m branch2 &&\n    -+\tattr_check_revision foo/bar/g g branch2 &&\n    -+\tattr_check_revision foo/bar/g unspecified branch1\n    ++test_expect_success 'using --source' '\n    ++\tattr_check_source foo/bar/f f tag-1 &&\n    ++\tattr_check_source foo/bar/a/i n tag-1 &&\n    ++\tattr_check_source foo/bar/f unspecified tag-2 &&\n    ++\tattr_check_source foo/bar/a/i m tag-2 &&\n    ++\tattr_check_source foo/bar/g g tag-2 &&\n    ++\tattr_check_source foo/bar/g unspecified tag-1\n     +'\n     +\n      test_expect_success 'setup bare' '\n    @@ t/t0003-attributes.sh: test_expect_success 'bare repository: check that .gitattr\n      \t)\n      '\n      \n    -+test_expect_success 'bare repository: with --revision' '\n    ++test_expect_success 'bare repository: with --source' '\n     +\t(\n     +\t\tcd bare.git &&\n    -+\t\t(\n    -+\t\t\techo \"f\ttest=f\" &&\n    -+\t\t\techo \"a/i test=a/i\"\n    -+\t\t) | git hash-object -w --stdin >id &&\n    -+\t\tgit update-index --add --cacheinfo 100644 $(cat id) .gitattributes &&\n    -+\t\tgit write-tree >id &&\n    -+\t\ttree_id=$(cat id) &&\n    -+\t\tgit commit-tree $tree_id -m \"random commit message\" >id &&\n    -+\t\tcommit_id=$(cat id) &&\n    -+\t\tgit update-ref refs/heads/master $commit_id &&\n    -+\t\tattr_check_revision f f HEAD &&\n    -+\t\tattr_check_revision a/f f HEAD &&\n    -+\t\tattr_check_revision a/c/f f HEAD &&\n    -+\t\tattr_check_revision a/i a/i HEAD &&\n    -+\t\tattr_check_revision subdir/a/i unspecified HEAD\n    ++\t\tattr_check_source foo/bar/f f tag-1 &&\n    ++\t\tattr_check_source foo/bar/a/i n tag-1 &&\n    ++\t\tattr_check_source foo/bar/f unspecified tag-2 &&\n    ++\t\tattr_check_source foo/bar/a/i m tag-2 &&\n    ++\t\tattr_check_source foo/bar/g g tag-2 &&\n    ++\t\tattr_check_source foo/bar/g unspecified tag-1\n     +\t)\n     +'\n     +\n\n\nKarthik Nayak (2):\n  t0003: move setup for `--all` into new block\n  attr: add flag `--source` to work with tree-ish\n\n Documentation/git-check-attr.txt | 10 +++-\n archive.c                        |  2 +-\n attr.c                           | 97 +++++++++++++++++++++++---------\n attr.h                           |  5 +-\n builtin/check-attr.c             | 35 +++++++-----\n builtin/pack-objects.c           |  2 +-\n convert.c                        |  2 +-\n ll-merge.c                       |  4 +-\n pathspec.c                       |  2 +-\n t/t0003-attributes.sh            | 49 +++++++++++++++-\n userdiff.c                       |  2 +-\n ws.c                             |  2 +-\n 12 files changed, 157 insertions(+), 55 deletions(-)\n\n-- \n2.39.0\n\n"},{"id":"469748","messageId":"622475417916cf48b8fb48e3587f6c87750d43ed.1671793109.git.karthik.188@gmail.com","threadId":"59025","inReplyTo":"cover.1671793109.git.karthik.188@gmail.com","subject":"[PATCH v5 1/2] t0003: move setup for `--all` into new block","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-02T11:04:05Z","receivedAt":"2023-01-02T11:04:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"There is some setup code which is used by multiple tests being setup in\n`attribute test: --all option`. This means when we run \"sh\n./t0003-attributes.sh --run=setup,<num>\" there is a chance of failing\nsince we missed this setup block.\n\nSo to ensure that setups are independent of test logic, move this to a\nnew setup block.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\nCo-authored-by: toon@iotcl.com\n---\n t/t0003-attributes.sh | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\nindex f7ee2f2ff0..b3aabb8aa3 100755\n--- a/t/t0003-attributes.sh\n+++ b/t/t0003-attributes.sh\n@@ -203,9 +203,12 @@ test_expect_success 'attribute test: read paths from stdin' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'attribute test: --all option' '\n+test_expect_success 'setup --all option' '\n \tgrep -v unspecified <expect-all | sort >specified-all &&\n-\tsed -e \"s/:.*//\" <expect-all | uniq >stdin-all &&\n+\tsed -e \"s/:.*//\" <expect-all | uniq >stdin-all\n+'\n+\n+test_expect_success 'attribute test: --all option' '\n \tgit check-attr --stdin --all <stdin-all >tmp &&\n \tsort tmp >actual &&\n \ttest_cmp specified-all actual\n-- \n2.39.0\n\n"},{"id":"469749","messageId":"23813496fc73b7e5cb9f09b166e05c9a02bac43c.1671793109.git.karthik.188@gmail.com","threadId":"59025","inReplyTo":"cover.1671793109.git.karthik.188@gmail.com","subject":"[PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-02T11:04:06Z","receivedAt":"2023-01-02T11:04:26Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"The contents of the .gitattributes files may evolve over time, but \"git\ncheck-attr\" always checks attributes against them in the working tree\nand/or in the index. It may be beneficial to optionally allow the users\nto check attributes taken from a commit other than HEAD against paths.\n\nAdd a new flag `--source` which will allow users to check the\nattributes against a commit (actually any tree-ish would do). When the\nuser uses this flag, we go through the stack of .gitattributes files but\ninstead of checking the current working tree and/or in the index, we\ncheck the blobs from the provided tree-ish object. This allows the\ncommand to also be used in bare repositories.\n\nSince we use a tree-ish object, the user can pass \"--source\nHEAD:subdirectory\" and all the attributes will be looked up as if\nsubdirectory was the root directory of the repository.\n\nWe cannot simply use the `<rev>:<path>` syntax without the `--source`\nflag, similar to how it is used in `git show` because any non-flag\nparameter before `--` is treated as an attribute and any parameter after\n`--` is treated as a pathname.\n\nThe change involves creating a new function `read_attr_from_blob`, which\ngiven the path reads the blob for the path against the provided source and\nparses the attributes line by line. This function is plugged into\n`read_attr()` function wherein we go through the stack of attributes\nfiles.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\nSigned-off-by: Toon Claes <toon@iotcl.com>\nCo-authored-by: toon@iotcl.com\n---\n Documentation/git-check-attr.txt | 10 +++-\n archive.c                        |  2 +-\n attr.c                           | 97 +++++++++++++++++++++++---------\n attr.h                           |  5 +-\n builtin/check-attr.c             | 35 +++++++-----\n builtin/pack-objects.c           |  2 +-\n convert.c                        |  2 +-\n ll-merge.c                       |  4 +-\n pathspec.c                       |  2 +-\n t/t0003-attributes.sh            | 42 +++++++++++++-\n userdiff.c                       |  2 +-\n ws.c                             |  2 +-\n 12 files changed, 152 insertions(+), 53 deletions(-)\n\ndiff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt\nindex 84f41a8e82..fb15c44598 100644\n--- a/Documentation/git-check-attr.txt\n+++ b/Documentation/git-check-attr.txt\n@@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information\n SYNOPSIS\n --------\n [verse]\n-'git check-attr' [-a | --all | <attr>...] [--] <pathname>...\n-'git check-attr' --stdin [-z] [-a | --all | <attr>...]\n+'git check-attr' [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\n+'git check-attr' --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\n \n DESCRIPTION\n -----------\n@@ -36,6 +36,12 @@ OPTIONS\n \tIf `--stdin` is also given, input paths are separated\n \twith a NUL character instead of a linefeed character.\n \n+--source=<tree>::\n+\tCheck attributes against the specified tree-ish. Paths provided as part\n+\tof the revision will be treated as the root directory. It is common to\n+\tspecify the source tree by naming a commit, branch or tag associated\n+\twith it.\n+\n \\--::\n \tInterpret all preceding arguments as attributes and all following\n \targuments as path names.\ndiff --git a/archive.c b/archive.c\nindex 941495f5d7..81ff76fce9 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -120,7 +120,7 @@ static const struct attr_check *get_archive_attrs(struct index_state *istate,\n \tstatic struct attr_check *check;\n \tif (!check)\n \t\tcheck = attr_check_initl(\"export-ignore\", \"export-subst\", NULL);\n-\tgit_check_attr(istate, path, check);\n+\tgit_check_attr(istate, NULL, path, check);\n \treturn check;\n }\n \ndiff --git a/attr.c b/attr.c\nindex 42ad6de8c7..a63f267187 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -13,6 +13,8 @@\n #include \"dir.h\"\n #include \"utf8.h\"\n #include \"quote.h\"\n+#include \"revision.h\"\n+#include \"object-store.h\"\n #include \"thread-utils.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n@@ -729,14 +731,62 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)\n \treturn res;\n }\n \n-static struct attr_stack *read_attr_from_index(struct index_state *istate,\n-\t\t\t\t\t       const char *path,\n-\t\t\t\t\t       unsigned flags)\n+static struct attr_stack *read_attr_from_buf(char *buf, const char *path,\n+\t\t\t\t\t     unsigned flags)\n {\n \tstruct attr_stack *res;\n-\tchar *buf, *sp;\n+\tchar *sp;\n \tint lineno = 0;\n \n+\tif (!buf)\n+\t\treturn NULL;\n+\n+\tCALLOC_ARRAY(res, 1);\n+\tfor (sp = buf; *sp;) {\n+\t\tchar *ep;\n+\t\tint more;\n+\n+\t\tep = strchrnul(sp, '\\n');\n+\t\tmore = (*ep == '\\n');\n+\t\t*ep = '\\0';\n+\t\thandle_attr_line(res, sp, path, ++lineno, flags);\n+\t\tsp = ep + more;\n+\t}\n+\tfree(buf);\n+\n+\treturn res;\n+}\n+\n+static struct attr_stack *read_attr_from_blob(struct index_state *istate,\n+\t\t\t\t\t      const struct object_id *tree_oid,\n+\t\t\t\t\t      const char *path, unsigned flags)\n+{\n+\tstruct object_id oid;\n+\tunsigned long sz;\n+\tenum object_type type;\n+\tvoid *buf;\n+\tunsigned short mode;\n+\n+\tif (!tree_oid)\n+\t\treturn NULL;\n+\n+\tif (get_tree_entry(istate->repo, tree_oid, path, &oid, &mode))\n+\t\treturn NULL;\n+\n+\tbuf = read_object_file(&oid, &type, &sz);\n+\tif (!buf || type != OBJ_BLOB) {\n+\t\tfree(buf);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn read_attr_from_buf(buf, path, flags);\n+}\n+\n+static struct attr_stack *read_attr_from_index(struct index_state *istate,\n+\t\t\t\t\t       const char *path, unsigned flags)\n+{\n+\tchar *buf;\n+\n \tif (!istate)\n \t\treturn NULL;\n \n@@ -758,28 +808,19 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,\n \tif (!buf)\n \t\treturn NULL;\n \n-\tCALLOC_ARRAY(res, 1);\n-\tfor (sp = buf; *sp; ) {\n-\t\tchar *ep;\n-\t\tint more;\n-\n-\t\tep = strchrnul(sp, '\\n');\n-\t\tmore = (*ep == '\\n');\n-\t\t*ep = '\\0';\n-\t\thandle_attr_line(res, sp, path, ++lineno, flags);\n-\t\tsp = ep + more;\n-\t}\n-\tfree(buf);\n-\treturn res;\n+\treturn read_attr_from_buf(buf, path, flags);\n }\n \n static struct attr_stack *read_attr(struct index_state *istate,\n+\t\t\t\t    const struct object_id *tree_oid,\n \t\t\t\t    const char *path, unsigned flags)\n {\n \tstruct attr_stack *res = NULL;\n \n \tif (direction == GIT_ATTR_INDEX) {\n \t\tres = read_attr_from_index(istate, path, flags);\n+\t} else if (tree_oid) {\n+\t\tres = read_attr_from_blob(istate, tree_oid, path, flags);\n \t} else if (!is_bare_repository()) {\n \t\tif (direction == GIT_ATTR_CHECKOUT) {\n \t\t\tres = read_attr_from_index(istate, path, flags);\n@@ -839,6 +880,7 @@ static void push_stack(struct attr_stack **attr_stack_p,\n }\n \n static void bootstrap_attr_stack(struct index_state *istate,\n+\t\t\t\t const struct object_id *tree_oid,\n \t\t\t\t struct attr_stack **stack)\n {\n \tstruct attr_stack *e;\n@@ -864,7 +906,7 @@ static void bootstrap_attr_stack(struct index_state *istate,\n \t}\n \n \t/* root directory */\n-\te = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);\n+\te = read_attr(istate, tree_oid, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);\n \tpush_stack(stack, e, xstrdup(\"\"), 0);\n \n \t/* info frame */\n@@ -878,6 +920,7 @@ static void bootstrap_attr_stack(struct index_state *istate,\n }\n \n static void prepare_attr_stack(struct index_state *istate,\n+\t\t\t       const struct object_id *tree_oid,\n \t\t\t       const char *path, int dirlen,\n \t\t\t       struct attr_stack **stack)\n {\n@@ -899,7 +942,7 @@ static void prepare_attr_stack(struct index_state *istate,\n \t * .gitattributes in deeper directories to shallower ones,\n \t * and finally use the built-in set as the default.\n \t */\n-\tbootstrap_attr_stack(istate, stack);\n+\tbootstrap_attr_stack(istate, tree_oid, stack);\n \n \t/*\n \t * Pop the \"info\" one that is always at the top of the stack.\n@@ -954,7 +997,7 @@ static void prepare_attr_stack(struct index_state *istate,\n \t\tstrbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));\n \t\tstrbuf_addf(&pathbuf, \"/%s\", GITATTRIBUTES_FILE);\n \n-\t\tnext = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW);\n+\t\tnext = read_attr(istate, tree_oid, pathbuf.buf, READ_ATTR_NOFOLLOW);\n \n \t\t/* reset the pathbuf to not include \"/.gitattributes\" */\n \t\tstrbuf_setlen(&pathbuf, len);\n@@ -1074,8 +1117,8 @@ static void determine_macros(struct all_attrs_item *all_attrs,\n  * Otherwise all attributes are collected.\n  */\n static void collect_some_attrs(struct index_state *istate,\n-\t\t\t       const char *path,\n-\t\t\t       struct attr_check *check)\n+\t\t\t       const struct object_id *tree_oid,\n+\t\t\t       const char *path, struct attr_check *check)\n {\n \tint pathlen, rem, dirlen;\n \tconst char *cp, *last_slash = NULL;\n@@ -1094,7 +1137,7 @@ static void collect_some_attrs(struct index_state *istate,\n \t\tdirlen = 0;\n \t}\n \n-\tprepare_attr_stack(istate, path, dirlen, &check->stack);\n+\tprepare_attr_stack(istate, tree_oid, path, dirlen, &check->stack);\n \tall_attrs_init(&g_attr_hashmap, check);\n \tdetermine_macros(check->all_attrs, check->stack);\n \n@@ -1103,12 +1146,12 @@ static void collect_some_attrs(struct index_state *istate,\n }\n \n void git_check_attr(struct index_state *istate,\n-\t\t    const char *path,\n+\t\t    const struct object_id *tree_oid, const char *path,\n \t\t    struct attr_check *check)\n {\n \tint i;\n \n-\tcollect_some_attrs(istate, path, check);\n+\tcollect_some_attrs(istate, tree_oid, path, check);\n \n \tfor (i = 0; i < check->nr; i++) {\n \t\tsize_t n = check->items[i].attr->attr_nr;\n@@ -1119,13 +1162,13 @@ void git_check_attr(struct index_state *istate,\n \t}\n }\n \n-void git_all_attrs(struct index_state *istate,\n+void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid,\n \t\t   const char *path, struct attr_check *check)\n {\n \tint i;\n \n \tattr_check_reset(check);\n-\tcollect_some_attrs(istate, path, check);\n+\tcollect_some_attrs(istate, tree_oid, path, check);\n \n \tfor (i = 0; i < check->all_attrs_nr; i++) {\n \t\tconst char *name = check->all_attrs[i].attr->name;\ndiff --git a/attr.h b/attr.h\nindex 3fb40cced0..84a3279215 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -190,13 +190,14 @@ void attr_check_free(struct attr_check *check);\n const char *git_attr_name(const struct git_attr *);\n \n void git_check_attr(struct index_state *istate,\n-\t\t    const char *path, struct attr_check *check);\n+\t\t    const struct object_id *tree_oid, const char *path,\n+\t\t    struct attr_check *check);\n \n /*\n  * Retrieve all attributes that apply to the specified path.\n  * check holds the attributes and their values.\n  */\n-void git_all_attrs(struct index_state *istate,\n+void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid,\n \t\t   const char *path, struct attr_check *check);\n \n enum git_attr_direction {\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex 0fef10eb6b..4ffef29d9c 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -9,9 +9,10 @@\n static int all_attrs;\n static int cached_attrs;\n static int stdin_paths;\n+static char *source;\n static const char * const check_attr_usage[] = {\n-N_(\"git check-attr [-a | --all | <attr>...] [--] <pathname>...\"),\n-N_(\"git check-attr --stdin [-z] [-a | --all | <attr>...]\"),\n+N_(\"git check-attr [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\"),\n+N_(\"git check-attr --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\"),\n NULL\n };\n \n@@ -23,6 +24,7 @@ static const struct option check_attr_options[] = {\n \tOPT_BOOL(0 , \"stdin\", &stdin_paths, N_(\"read file names from stdin\")),\n \tOPT_BOOL('z', NULL, &nul_term_line,\n \t\t N_(\"terminate input and output records by a NUL character\")),\n+\tOPT_STRING(0, \"source\", &source, N_(\"<tree-ish>\"), N_(\"which tree-ish to check attributes at\")),\n \tOPT_END()\n };\n \n@@ -55,27 +57,26 @@ static void output_attr(struct attr_check *check, const char *file)\n \t}\n }\n \n-static void check_attr(const char *prefix,\n-\t\t       struct attr_check *check,\n-\t\t       int collect_all,\n+static void check_attr(const char *prefix, struct attr_check *check,\n+\t\t       const struct object_id *tree_oid, int collect_all,\n \t\t       const char *file)\n+\n {\n \tchar *full_path =\n \t\tprefix_path(prefix, prefix ? strlen(prefix) : 0, file);\n \n \tif (collect_all) {\n-\t\tgit_all_attrs(&the_index, full_path, check);\n+\t\tgit_all_attrs(&the_index, tree_oid, full_path, check);\n \t} else {\n-\t\tgit_check_attr(&the_index, full_path, check);\n+\t\tgit_check_attr(&the_index, tree_oid, full_path, check);\n \t}\n \toutput_attr(check, file);\n \n \tfree(full_path);\n }\n \n-static void check_attr_stdin_paths(const char *prefix,\n-\t\t\t\t   struct attr_check *check,\n-\t\t\t\t   int collect_all)\n+static void check_attr_stdin_paths(const char *prefix, struct attr_check *check,\n+\t\t\t\t   const struct object_id *tree_oid, int collect_all)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf unquoted = STRBUF_INIT;\n@@ -89,7 +90,7 @@ static void check_attr_stdin_paths(const char *prefix,\n \t\t\t\tdie(\"line is badly quoted\");\n \t\t\tstrbuf_swap(&buf, &unquoted);\n \t\t}\n-\t\tcheck_attr(prefix, check, collect_all, buf.buf);\n+\t\tcheck_attr(prefix, check, tree_oid, collect_all, buf.buf);\n \t\tmaybe_flush_or_die(stdout, \"attribute to stdout\");\n \t}\n \tstrbuf_release(&buf);\n@@ -105,6 +106,8 @@ static NORETURN void error_with_usage(const char *msg)\n int cmd_check_attr(int argc, const char **argv, const char *prefix)\n {\n \tstruct attr_check *check;\n+\tstruct object_id *tree_oid = NULL;\n+\tstruct object_id initialized_oid;\n \tint cnt, i, doubledash, filei;\n \n \tif (!is_bare_repository())\n@@ -176,11 +179,17 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\tif (source) {\n+\t\tif (repo_get_oid_tree(the_repository, source, &initialized_oid))\n+\t\t\tdie(\"%s: not a valid revision\", source);\n+\t\ttree_oid = &initialized_oid;\n+\t}\n+\n \tif (stdin_paths)\n-\t\tcheck_attr_stdin_paths(prefix, check, all_attrs);\n+\t\tcheck_attr_stdin_paths(prefix, check, tree_oid, all_attrs);\n \telse {\n \t\tfor (i = filei; i < argc; i++)\n-\t\t\tcheck_attr(prefix, check, all_attrs, argv[i]);\n+\t\t\tcheck_attr(prefix, check, tree_oid, all_attrs, argv[i]);\n \t\tmaybe_flush_or_die(stdout, \"attribute to stdout\");\n \t}\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 573d0b20b7..89535cfa6a 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1318,7 +1318,7 @@ static int no_try_delta(const char *path)\n \n \tif (!check)\n \t\tcheck = attr_check_initl(\"delta\", NULL);\n-\tgit_check_attr(the_repository->index, path, check);\n+\tgit_check_attr(the_repository->index, NULL, path, check);\n \tif (ATTR_FALSE(check->items[0].value))\n \t\treturn 1;\n \treturn 0;\ndiff --git a/convert.c b/convert.c\nindex 9b67649032..a54d1690c0 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1308,7 +1308,7 @@ void convert_attrs(struct index_state *istate,\n \t\tgit_config(read_convert_config, NULL);\n \t}\n \n-\tgit_check_attr(istate, path, check);\n+\tgit_check_attr(istate, NULL, path, check);\n \tccheck = check->items;\n \tca->crlf_action = git_path_check_crlf(ccheck + 4);\n \tif (ca->crlf_action == CRLF_UNDEFINED)\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 22a603e8af..130d26501c 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -391,7 +391,7 @@ enum ll_merge_result ll_merge(mmbuffer_t *result_buf,\n \t\tnormalize_file(theirs, path, istate);\n \t}\n \n-\tgit_check_attr(istate, path, check);\n+\tgit_check_attr(istate, NULL, path, check);\n \tll_driver_name = check->items[0].value;\n \tif (check->items[1].value) {\n \t\tmarker_size = atoi(check->items[1].value);\n@@ -419,7 +419,7 @@ int ll_merge_marker_size(struct index_state *istate, const char *path)\n \n \tif (!check)\n \t\tcheck = attr_check_initl(\"conflict-marker-size\", NULL);\n-\tgit_check_attr(istate, path, check);\n+\tgit_check_attr(istate, NULL, path, check);\n \tif (check->items[0].value) {\n \t\tmarker_size = atoi(check->items[0].value);\n \t\tif (marker_size <= 0)\ndiff --git a/pathspec.c b/pathspec.c\nindex 46e77a85fe..48dec2c709 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -732,7 +732,7 @@ int match_pathspec_attrs(struct index_state *istate,\n \tif (name[namelen])\n \t\tname = to_free = xmemdupz(name, namelen);\n \n-\tgit_check_attr(istate, name, item->attr_check);\n+\tgit_check_attr(istate, NULL, name, item->attr_check);\n \n \tfree(to_free);\n \ndiff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\nindex b3aabb8aa3..78e9f47dbf 100755\n--- a/t/t0003-attributes.sh\n+++ b/t/t0003-attributes.sh\n@@ -25,7 +25,15 @@ attr_check_quote () {\n \tgit check-attr test -- \"$path\" >actual &&\n \techo \"\\\"$quoted_path\\\": test: $expect\" >expect &&\n \ttest_cmp expect actual\n+}\n+\n+attr_check_source () {\n+\tpath=\"$1\" expect=\"$2\" source=\"$3\" git_opts=\"$4\" &&\n \n+\tgit $git_opts check-attr --source $source test -- \"$path\" >actual 2>err &&\n+\techo \"$path: test: $expect\" >expect &&\n+\ttest_cmp expect actual\n+\ttest_must_be_empty err\n }\n \n test_expect_success 'open-quoted pathname' '\n@@ -33,7 +41,6 @@ test_expect_success 'open-quoted pathname' '\n \tattr_check a unspecified\n '\n \n-\n test_expect_success 'setup' '\n \tmkdir -p a/b/d a/c b &&\n \t(\n@@ -80,12 +87,24 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'setup branches' '\n+\tmkdir -p foo/bar &&\n+\ttest_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n+\t\t\"f test=f\\na/i test=n\\n\" tag-1 &&\n+\n+\tmkdir -p foo/bar &&\n+\ttest_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n+\t\t\"g test=g\\na/i test=m\\n\" tag-2\n+'\n+\n test_expect_success 'command line checks' '\n \ttest_must_fail git check-attr &&\n \ttest_must_fail git check-attr -- &&\n \ttest_must_fail git check-attr test &&\n \ttest_must_fail git check-attr test -- &&\n \ttest_must_fail git check-attr -- f &&\n+\ttest_must_fail git check-attr --source &&\n+\ttest_must_fail git check-attr --source not-a-valid-ref &&\n \techo \"f\" | test_must_fail git check-attr --stdin &&\n \techo \"f\" | test_must_fail git check-attr --stdin -- f &&\n \techo \"f\" | test_must_fail git check-attr --stdin test -- f &&\n@@ -287,6 +306,15 @@ test_expect_success 'using --git-dir and --work-tree' '\n \t)\n '\n \n+test_expect_success 'using --source' '\n+\tattr_check_source foo/bar/f f tag-1 &&\n+\tattr_check_source foo/bar/a/i n tag-1 &&\n+\tattr_check_source foo/bar/f unspecified tag-2 &&\n+\tattr_check_source foo/bar/a/i m tag-2 &&\n+\tattr_check_source foo/bar/g g tag-2 &&\n+\tattr_check_source foo/bar/g unspecified tag-1\n+'\n+\n test_expect_success 'setup bare' '\n \tgit clone --template= --bare . bare.git\n '\n@@ -306,6 +334,18 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '\n \t)\n '\n \n+test_expect_success 'bare repository: with --source' '\n+\t(\n+\t\tcd bare.git &&\n+\t\tattr_check_source foo/bar/f f tag-1 &&\n+\t\tattr_check_source foo/bar/a/i n tag-1 &&\n+\t\tattr_check_source foo/bar/f unspecified tag-2 &&\n+\t\tattr_check_source foo/bar/a/i m tag-2 &&\n+\t\tattr_check_source foo/bar/g g tag-2 &&\n+\t\tattr_check_source foo/bar/g unspecified tag-1\n+\t)\n+'\n+\n test_expect_success 'bare repository: check that --cached honors index' '\n \t(\n \t\tcd bare.git &&\ndiff --git a/userdiff.c b/userdiff.c\nindex 151d9a5278..b66f090a0b 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -412,7 +412,7 @@ struct userdiff_driver *userdiff_find_by_path(struct index_state *istate,\n \t\tcheck = attr_check_initl(\"diff\", NULL);\n \tif (!path)\n \t\treturn NULL;\n-\tgit_check_attr(istate, path, check);\n+\tgit_check_attr(istate, NULL, path, check);\n \n \tif (ATTR_TRUE(check->items[0].value))\n \t\treturn &driver_true;\ndiff --git a/ws.c b/ws.c\nindex 6e69877f25..eadbbe5667 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -78,7 +78,7 @@ unsigned whitespace_rule(struct index_state *istate, const char *pathname)\n \tif (!attr_whitespace_rule)\n \t\tattr_whitespace_rule = attr_check_initl(\"whitespace\", NULL);\n \n-\tgit_check_attr(istate, pathname, attr_whitespace_rule);\n+\tgit_check_attr(istate, NULL, pathname, attr_whitespace_rule);\n \tvalue = attr_whitespace_rule->items[0].value;\n \tif (ATTR_TRUE(value)) {\n \t\t/* true (whitespace) */\n-- \n2.39.0\n\n"},{"id":"469752","messageId":"xmqq1qocwlr9.fsf@gitster.g","threadId":"59025","inReplyTo":"23813496fc73b7e5cb9f09b166e05c9a02bac43c.1671793109.git.karthik.188@gmail.com","subject":"Re: [PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-01-02T23:38:02Z","receivedAt":"2023-01-02T23:38:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> The contents of the .gitattributes files may evolve over time, but \"git\n> check-attr\" always checks attributes against them in the working tree\n> and/or in the index. It may be beneficial to optionally allow the users\n> to check attributes taken from a commit other than HEAD against paths.\n>\n> Add a new flag `--source` which will allow users to check the\n> attributes against a commit (actually any tree-ish would do). When the\n> user uses this flag, we go through the stack of .gitattributes files but\n> instead of checking the current working tree and/or in the index, we\n> check the blobs from the provided tree-ish object. This allows the\n> command to also be used in bare repositories.\n>\n> Since we use a tree-ish object, the user can pass \"--source\n> HEAD:subdirectory\" and all the attributes will be looked up as if\n> subdirectory was the root directory of the repository.\n>\n> We cannot simply use the `<rev>:<path>` syntax without the `--source`\n> flag, similar to how it is used in `git show` because any non-flag\n> parameter before `--` is treated as an attribute and any parameter after\n> `--` is treated as a pathname.\n>\n> The change involves creating a new function `read_attr_from_blob`, which\n> given the path reads the blob for the path against the provided source and\n> parses the attributes line by line. This function is plugged into\n> `read_attr()` function wherein we go through the stack of attributes\n> files.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> Signed-off-by: Toon Claes <toon@iotcl.com>\n> Co-authored-by: toon@iotcl.com\n> ---\n>  Documentation/git-check-attr.txt | 10 +++-\n>  archive.c                        |  2 +-\n>  attr.c                           | 97 +++++++++++++++++++++++---------\n>  attr.h                           |  5 +-\n>  builtin/check-attr.c             | 35 +++++++-----\n>  builtin/pack-objects.c           |  2 +-\n>  convert.c                        |  2 +-\n>  ll-merge.c                       |  4 +-\n>  pathspec.c                       |  2 +-\n>  t/t0003-attributes.sh            | 42 +++++++++++++-\n>  userdiff.c                       |  2 +-\n>  ws.c                             |  2 +-\n>  12 files changed, 152 insertions(+), 53 deletions(-)\n>\n> diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt\n> index 84f41a8e82..fb15c44598 100644\n> --- a/Documentation/git-check-attr.txt\n> +++ b/Documentation/git-check-attr.txt\n> @@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git check-attr' [-a | --all | <attr>...] [--] <pathname>...\n> -'git check-attr' --stdin [-z] [-a | --all | <attr>...]\n> +'git check-attr' [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\n> +'git check-attr' --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -36,6 +36,12 @@ OPTIONS\n>  \tIf `--stdin` is also given, input paths are separated\n>  \twith a NUL character instead of a linefeed character.\n>  \n> +--source=<tree>::\n> +\tCheck attributes against the specified tree-ish. Paths provided as part\n> +\tof the revision will be treated as the root directory. It is common to\n> +\tspecify the source tree by naming a commit, branch or tag associated\n> +\twith it.\n> +\n>  \\--::\n>  \tInterpret all preceding arguments as attributes and all following\n>  \targuments as path names.\n> diff --git a/archive.c b/archive.c\n> index 941495f5d7..81ff76fce9 100644\n> --- a/archive.c\n> +++ b/archive.c\n> @@ -120,7 +120,7 @@ static const struct attr_check *get_archive_attrs(struct index_state *istate,\n>  \tstatic struct attr_check *check;\n>  \tif (!check)\n>  \t\tcheck = attr_check_initl(\"export-ignore\", \"export-subst\", NULL);\n> -\tgit_check_attr(istate, path, check);\n> +\tgit_check_attr(istate, NULL, path, check);\n>  \treturn check;\n>  }\n>  \n> diff --git a/attr.c b/attr.c\n> index 42ad6de8c7..a63f267187 100644\n> --- a/attr.c\n> +++ b/attr.c\n> @@ -13,6 +13,8 @@\n>  #include \"dir.h\"\n>  #include \"utf8.h\"\n>  #include \"quote.h\"\n> +#include \"revision.h\"\n> +#include \"object-store.h\"\n>  #include \"thread-utils.h\"\n>  \n>  const char git_attr__true[] = \"(builtin)true\";\n> @@ -729,14 +731,62 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)\n>  \treturn res;\n>  }\n>  \n> -static struct attr_stack *read_attr_from_index(struct index_state *istate,\n> -\t\t\t\t\t       const char *path,\n> -\t\t\t\t\t       unsigned flags)\n> +static struct attr_stack *read_attr_from_buf(char *buf, const char *path,\n> +\t\t\t\t\t     unsigned flags)\n>  {\n>  \tstruct attr_stack *res;\n> -\tchar *buf, *sp;\n> +\tchar *sp;\n>  \tint lineno = 0;\n>  \n> +\tif (!buf)\n> +\t\treturn NULL;\n> +\n> +\tCALLOC_ARRAY(res, 1);\n> +\tfor (sp = buf; *sp;) {\n> +\t\tchar *ep;\n> +\t\tint more;\n> +\n> +\t\tep = strchrnul(sp, '\\n');\n> +\t\tmore = (*ep == '\\n');\n> +\t\t*ep = '\\0';\n> +\t\thandle_attr_line(res, sp, path, ++lineno, flags);\n> +\t\tsp = ep + more;\n> +\t}\n> +\tfree(buf);\n> +\n> +\treturn res;\n> +}\n> +\n> +static struct attr_stack *read_attr_from_blob(struct index_state *istate,\n> +\t\t\t\t\t      const struct object_id *tree_oid,\n> +\t\t\t\t\t      const char *path, unsigned flags)\n> +{\n> +\tstruct object_id oid;\n> +\tunsigned long sz;\n> +\tenum object_type type;\n> +\tvoid *buf;\n> +\tunsigned short mode;\n> +\n> +\tif (!tree_oid)\n> +\t\treturn NULL;\n> +\n> +\tif (get_tree_entry(istate->repo, tree_oid, path, &oid, &mode))\n> +\t\treturn NULL;\n> +\n> +\tbuf = read_object_file(&oid, &type, &sz);\n> +\tif (!buf || type != OBJ_BLOB) {\n> +\t\tfree(buf);\n> +\t\treturn NULL;\n> +\t}\n> +\n> +\treturn read_attr_from_buf(buf, path, flags);\n> +}\n> +\n> +static struct attr_stack *read_attr_from_index(struct index_state *istate,\n> +\t\t\t\t\t       const char *path, unsigned flags)\n> +{\n> +\tchar *buf;\n> +\n>  \tif (!istate)\n>  \t\treturn NULL;\n>  \n> @@ -758,28 +808,19 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,\n>  \tif (!buf)\n>  \t\treturn NULL;\n>  \n> -\tCALLOC_ARRAY(res, 1);\n> -\tfor (sp = buf; *sp; ) {\n> -\t\tchar *ep;\n> -\t\tint more;\n> -\n> -\t\tep = strchrnul(sp, '\\n');\n> -\t\tmore = (*ep == '\\n');\n> -\t\t*ep = '\\0';\n> -\t\thandle_attr_line(res, sp, path, ++lineno, flags);\n> -\t\tsp = ep + more;\n> -\t}\n> -\tfree(buf);\n> -\treturn res;\n> +\treturn read_attr_from_buf(buf, path, flags);\n>  }\n>  \n>  static struct attr_stack *read_attr(struct index_state *istate,\n> +\t\t\t\t    const struct object_id *tree_oid,\n>  \t\t\t\t    const char *path, unsigned flags)\n>  {\n>  \tstruct attr_stack *res = NULL;\n>  \n>  \tif (direction == GIT_ATTR_INDEX) {\n>  \t\tres = read_attr_from_index(istate, path, flags);\n> +\t} else if (tree_oid) {\n> +\t\tres = read_attr_from_blob(istate, tree_oid, path, flags);\n>  \t} else if (!is_bare_repository()) {\n>  \t\tif (direction == GIT_ATTR_CHECKOUT) {\n>  \t\t\tres = read_attr_from_index(istate, path, flags);\n> @@ -839,6 +880,7 @@ static void push_stack(struct attr_stack **attr_stack_p,\n>  }\n>  \n>  static void bootstrap_attr_stack(struct index_state *istate,\n> +\t\t\t\t const struct object_id *tree_oid,\n>  \t\t\t\t struct attr_stack **stack)\n>  {\n>  \tstruct attr_stack *e;\n> @@ -864,7 +906,7 @@ static void bootstrap_attr_stack(struct index_state *istate,\n>  \t}\n>  \n>  \t/* root directory */\n> -\te = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);\n> +\te = read_attr(istate, tree_oid, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);\n>  \tpush_stack(stack, e, xstrdup(\"\"), 0);\n>  \n>  \t/* info frame */\n> @@ -878,6 +920,7 @@ static void bootstrap_attr_stack(struct index_state *istate,\n>  }\n>  \n>  static void prepare_attr_stack(struct index_state *istate,\n> +\t\t\t       const struct object_id *tree_oid,\n>  \t\t\t       const char *path, int dirlen,\n>  \t\t\t       struct attr_stack **stack)\n>  {\n> @@ -899,7 +942,7 @@ static void prepare_attr_stack(struct index_state *istate,\n>  \t * .gitattributes in deeper directories to shallower ones,\n>  \t * and finally use the built-in set as the default.\n>  \t */\n> -\tbootstrap_attr_stack(istate, stack);\n> +\tbootstrap_attr_stack(istate, tree_oid, stack);\n>  \n>  \t/*\n>  \t * Pop the \"info\" one that is always at the top of the stack.\n> @@ -954,7 +997,7 @@ static void prepare_attr_stack(struct index_state *istate,\n>  \t\tstrbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));\n>  \t\tstrbuf_addf(&pathbuf, \"/%s\", GITATTRIBUTES_FILE);\n>  \n> -\t\tnext = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW);\n> +\t\tnext = read_attr(istate, tree_oid, pathbuf.buf, READ_ATTR_NOFOLLOW);\n>  \n>  \t\t/* reset the pathbuf to not include \"/.gitattributes\" */\n>  \t\tstrbuf_setlen(&pathbuf, len);\n> @@ -1074,8 +1117,8 @@ static void determine_macros(struct all_attrs_item *all_attrs,\n>   * Otherwise all attributes are collected.\n>   */\n>  static void collect_some_attrs(struct index_state *istate,\n> -\t\t\t       const char *path,\n> -\t\t\t       struct attr_check *check)\n> +\t\t\t       const struct object_id *tree_oid,\n> +\t\t\t       const char *path, struct attr_check *check)\n>  {\n>  \tint pathlen, rem, dirlen;\n>  \tconst char *cp, *last_slash = NULL;\n> @@ -1094,7 +1137,7 @@ static void collect_some_attrs(struct index_state *istate,\n>  \t\tdirlen = 0;\n>  \t}\n>  \n> -\tprepare_attr_stack(istate, path, dirlen, &check->stack);\n> +\tprepare_attr_stack(istate, tree_oid, path, dirlen, &check->stack);\n>  \tall_attrs_init(&g_attr_hashmap, check);\n>  \tdetermine_macros(check->all_attrs, check->stack);\n>  \n> @@ -1103,12 +1146,12 @@ static void collect_some_attrs(struct index_state *istate,\n>  }\n>  \n>  void git_check_attr(struct index_state *istate,\n> -\t\t    const char *path,\n> +\t\t    const struct object_id *tree_oid, const char *path,\n>  \t\t    struct attr_check *check)\n>  {\n>  \tint i;\n>  \n> -\tcollect_some_attrs(istate, path, check);\n> +\tcollect_some_attrs(istate, tree_oid, path, check);\n>  \n>  \tfor (i = 0; i < check->nr; i++) {\n>  \t\tsize_t n = check->items[i].attr->attr_nr;\n> @@ -1119,13 +1162,13 @@ void git_check_attr(struct index_state *istate,\n>  \t}\n>  }\n>  \n> -void git_all_attrs(struct index_state *istate,\n> +void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid,\n>  \t\t   const char *path, struct attr_check *check)\n>  {\n>  \tint i;\n>  \n>  \tattr_check_reset(check);\n> -\tcollect_some_attrs(istate, path, check);\n> +\tcollect_some_attrs(istate, tree_oid, path, check);\n>  \n>  \tfor (i = 0; i < check->all_attrs_nr; i++) {\n>  \t\tconst char *name = check->all_attrs[i].attr->name;\n> diff --git a/attr.h b/attr.h\n> index 3fb40cced0..84a3279215 100644\n> --- a/attr.h\n> +++ b/attr.h\n> @@ -190,13 +190,14 @@ void attr_check_free(struct attr_check *check);\n>  const char *git_attr_name(const struct git_attr *);\n>  \n>  void git_check_attr(struct index_state *istate,\n> -\t\t    const char *path, struct attr_check *check);\n> +\t\t    const struct object_id *tree_oid, const char *path,\n> +\t\t    struct attr_check *check);\n\nFYI, this breaks \"make hdr-check\".\n\n>  /*\n>   * Retrieve all attributes that apply to the specified path.\n>   * check holds the attributes and their values.\n>   */\n> -void git_all_attrs(struct index_state *istate,\n> +void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid,\n>  \t\t   const char *path, struct attr_check *check);\n\nLikewise.\n\nPerhaps squash something like this in in the next iteration?\n\n attr.h | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git i/attr.h w/attr.h\nindex 84a3279215..fca6c30430 100644\n--- i/attr.h\n+++ w/attr.h\n@@ -108,6 +108,7 @@\n  */\n \n struct index_state;\n+struct object_id;\n \n /**\n  * An attribute is an opaque object that is identified by its name. Pass the\n"},{"id":"469784","messageId":"CAOLa=ZStKKgyaHGjGVpQ5jsSpkdqXuH2RhvWOgrU2qKxGbQFGA@mail.gmail.com","threadId":"59025","inReplyTo":"xmqq1qocwlr9.fsf@gitster.g","subject":"Re: [PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-04T10:25:21Z","receivedAt":"2023-01-04T10:26:09Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Tue, Jan 3, 2023 at 12:38 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Likewise.\n>\n> Perhaps squash something like this in in the next iteration?\n>\n>  attr.h | 1 +\n>  1 file changed, 1 insertion(+)\n>\n> diff --git i/attr.h w/attr.h\n> index 84a3279215..fca6c30430 100644\n> --- i/attr.h\n> +++ w/attr.h\n> @@ -108,6 +108,7 @@\n>   */\n>\n>  struct index_state;\n> +struct object_id;\n>\n>  /**\n>   * An attribute is an opaque object that is identified by its name. Pass the\n\nThanks Junio, I'll squash this in, waiting for more reviews before\nrolling out a new version.\n\n-- \n- Karthik\n"},{"id":"469957","messageId":"CAOLa=ZQxx4J8B4wOeuz3C19WD4wF7N4bcDwsNUUJWhd+vQvQgw@mail.gmail.com","threadId":"59025","inReplyTo":"cover.1671793109.git.karthik.188@gmail.com","subject":"Re: [PATCH v5 0/2] check-attr: add support to work with tree-ish","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-09T09:10:37Z","receivedAt":"2023-01-09T09:15:44Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Bumping for reviews, before I re-roll with the fix for `make hdr-check`\n\n\n- Karthik\n\nOn Mon, Jan 2, 2023 at 12:04 PM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> v1: https://lore.kernel.org/git/20221206103736.53909-1-karthik.188@gmail.com/\n> v2: https://lore.kernel.org/git/CAOLa=ZSsFGBw3ta1jWN8cmUch2ca=zTEjp1xMA6Linafx9W53g@mail.gmail.com/T/#t\n> v3: https://lore.kernel.org/git/20221216093552.3171319-1-karthik.188@gmail.com/\n> v4: https://lore.kernel.org/git/cover.1671630304.git.karthik.188@gmail.com\n>\n> Given a pathname, git-check-attr(1) will list the attributes which apply to that\n> pathname by reading all relevant gitattributes files. Currently there is no way\n> to specify a tree-ish to read the gitattributes from.\n>\n> This is specifically useful in bare repositories wherein the gitattributes are\n> only present in the git working tree but not available directly on the\n> filesystem.\n>\n> This series aims to add a new flag `--source` to git-check-attr(1) which\n> allows us to read gitattributes from the specified tree-ish.\n>\n> Changes since version 4:\n> - Changed the flag from `--revision` to `--source`\n> - Removed uneeded header imports\n> - Using a pre-initialized variable instead of malloc for the tree_oid\n> - Using `die()` instead of `error()` for bad tree-ish provided\n>\n> Range-diff against v4:\n>\n> 1:  6224754179 = 1:  6224754179 t0003: move setup for `--all` into new block\n> 2:  a161dbdf8b ! 2:  d835d989ad attr: add flag `--revision` to work with revisions\n>     @@ Metadata\n>      Author: Karthik Nayak <karthik.188@gmail.com>\n>\n>       ## Commit message ##\n>     -    attr: add flag `--revision` to work with revisions\n>     +    attr: add flag `--source` to work with tree-ish\n>\n>          The contents of the .gitattributes files may evolve over time, but \"git\n>          check-attr\" always checks attributes against them in the working tree\n>          and/or in the index. It may be beneficial to optionally allow the users\n>          to check attributes taken from a commit other than HEAD against paths.\n>\n>     -    Add a new flag `--revision` which will allow users to check the\n>     +    Add a new flag `--source` which will allow users to check the\n>          attributes against a commit (actually any tree-ish would do). When the\n>          user uses this flag, we go through the stack of .gitattributes files but\n>          instead of checking the current working tree and/or in the index, we\n>          check the blobs from the provided tree-ish object. This allows the\n>          command to also be used in bare repositories.\n>\n>     -    Since we use a tree-ish object, the user can pass \"--revision\n>     +    Since we use a tree-ish object, the user can pass \"--source\n>          HEAD:subdirectory\" and all the attributes will be looked up as if\n>          subdirectory was the root directory of the repository.\n>\n>     -    We cannot simply use the `<rev>:<path>` syntax without the `--revision`\n>     +    We cannot simply use the `<rev>:<path>` syntax without the `--source`\n>          flag, similar to how it is used in `git show` because any non-flag\n>          parameter before `--` is treated as an attribute and any parameter after\n>          `--` is treated as a pathname.\n>\n>          The change involves creating a new function `read_attr_from_blob`, which\n>     -    given the path reads the blob for the path against the provided revision and\n>     +    given the path reads the blob for the path against the provided source and\n>          parses the attributes line by line. This function is plugged into\n>          `read_attr()` function wherein we go through the stack of attributes\n>          files.\n>     @@ Documentation/git-check-attr.txt: git-check-attr - Display gitattributes informa\n>       [verse]\n>      -'git check-attr' [-a | --all | <attr>...] [--] <pathname>...\n>      -'git check-attr' --stdin [-z] [-a | --all | <attr>...]\n>     -+'git check-attr' [--revision <revision>] [-a | --all | <attr>...] [--] <pathname>...\n>     -+'git check-attr' --stdin [-z] [--revision <revision>] [-a | --all | <attr>...]\n>     ++'git check-attr' [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\n>     ++'git check-attr' --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\n>\n>       DESCRIPTION\n>       -----------\n>     @@ Documentation/git-check-attr.txt: OPTIONS\n>         If `--stdin` is also given, input paths are separated\n>         with a NUL character instead of a linefeed character.\n>\n>     -+--revision=<revision>::\n>     -+  Check attributes against the specified commit. All the attributes will\n>     -+  be checked against the provided revision. Paths provided as part of the\n>     -+  revision will be treated as the root directory.\n>     ++--source=<tree>::\n>     ++  Check attributes against the specified tree-ish. Paths provided as part\n>     ++  of the revision will be treated as the root directory. It is common to\n>     ++  specify the source tree by naming a commit, branch or tag associated\n>     ++  with it.\n>      +\n>       \\--::\n>         Interpret all preceding arguments as attributes and all following\n>     @@ archive.c: static const struct attr_check *get_archive_attrs(struct index_state\n>\n>       ## attr.c ##\n>      @@\n>     - #include \"exec-cmd.h\"\n>     - #include \"attr.h\"\n>       #include \"dir.h\"\n>     -+#include \"strbuf.h\"\n>     -+#include \"tree-walk.h\"\n>       #include \"utf8.h\"\n>       #include \"quote.h\"\n>      +#include \"revision.h\"\n>     @@ attr.c: void git_check_attr(struct index_state *istate,\n>                 const char *name = check->all_attrs[i].attr->name;\n>\n>       ## attr.h ##\n>     -@@\n>     - #ifndef ATTR_H\n>     - #define ATTR_H\n>     -\n>     -+#include \"hash.h\"\n>     -+\n>     - /**\n>     -  * gitattributes mechanism gives a uniform way to associate various attributes\n>     -  * to set of paths.\n>      @@ attr.h: void attr_check_free(struct attr_check *check);\n>       const char *git_attr_name(const struct git_attr *);\n>\n>     @@ attr.h: void attr_check_free(struct attr_check *check);\n>       enum git_attr_direction {\n>\n>       ## builtin/check-attr.c ##\n>     -@@\n>     -+#include \"repository.h\"\n>     - #define USE_THE_INDEX_VARIABLE\n>     - #include \"builtin.h\"\n>     - #include \"cache.h\"\n>      @@\n>       static int all_attrs;\n>       static int cached_attrs;\n>       static int stdin_paths;\n>     -+static char *revision;\n>     ++static char *source;\n>       static const char * const check_attr_usage[] = {\n>      -N_(\"git check-attr [-a | --all | <attr>...] [--] <pathname>...\"),\n>      -N_(\"git check-attr --stdin [-z] [-a | --all | <attr>...]\"),\n>     -+N_(\"git check-attr [--revision <revision>] [-a | --all | <attr>...] [--] <pathname>...\"),\n>     -+N_(\"git check-attr --stdin [-z] [--revision <revision>] [-a | --all | <attr>...]\"),\n>     ++N_(\"git check-attr [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\"),\n>     ++N_(\"git check-attr --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\"),\n>       NULL\n>       };\n>\n>     @@ builtin/check-attr.c: static const struct option check_attr_options[] = {\n>         OPT_BOOL(0 , \"stdin\", &stdin_paths, N_(\"read file names from stdin\")),\n>         OPT_BOOL('z', NULL, &nul_term_line,\n>                  N_(\"terminate input and output records by a NUL character\")),\n>     -+  OPT_STRING(0, \"revision\", &revision, N_(\"revision\"), N_(\"check attributes at this revision\")),\n>     ++  OPT_STRING(0, \"source\", &source, N_(\"<tree-ish>\"), N_(\"which tree-ish to check attributes at\")),\n>         OPT_END()\n>       };\n>\n>     @@ builtin/check-attr.c: static NORETURN void error_with_usage(const char *msg)\n>       {\n>         struct attr_check *check;\n>      +  struct object_id *tree_oid = NULL;\n>     ++  struct object_id initialized_oid;\n>         int cnt, i, doubledash, filei;\n>\n>         if (!is_bare_repository())\n>     @@ builtin/check-attr.c: int cmd_check_attr(int argc, const char **argv, const char\n>                 }\n>         }\n>\n>     -+  if (revision) {\n>     -+          tree_oid = xmalloc(sizeof(struct object_id));\n>     -+\n>     -+          if (repo_get_oid_tree(the_repository, revision, tree_oid))\n>     -+                  error(\"%s: not a valid revision\", revision);\n>     ++  if (source) {\n>     ++          if (repo_get_oid_tree(the_repository, source, &initialized_oid))\n>     ++                  die(\"%s: not a valid tree-ish source\", source);\n>     ++          tree_oid = &initialized_oid;\n>      +  }\n>      +\n>         if (stdin_paths)\n>     @@ t/t0003-attributes.sh: attr_check_quote () {\n>         test_cmp expect actual\n>      +}\n>      +\n>     -+attr_check_revision () {\n>     -+  path=\"$1\" expect=\"$2\" revision=\"$3\" git_opts=\"$4\" &&\n>     ++attr_check_source () {\n>     ++  path=\"$1\" expect=\"$2\" source=\"$3\" git_opts=\"$4\" &&\n>\n>     -+  git $git_opts check-attr --revision $revision test -- \"$path\" >actual 2>err &&\n>     ++  git $git_opts check-attr --source $source test -- \"$path\" >actual 2>err &&\n>      +  echo \"$path: test: $expect\" >expect &&\n>      +  test_cmp expect actual\n>     ++  test_must_be_empty err\n>       }\n>\n>       test_expect_success 'open-quoted pathname' '\n>     @@ t/t0003-attributes.sh: test_expect_success 'setup' '\n>       '\n>\n>      +test_expect_success 'setup branches' '\n>     -+  (\n>     -+          echo \"f test=f\" &&\n>     -+          echo \"a/i test=n\"\n>     -+  ) | git hash-object -w --stdin >id &&\n>     -+  git update-index --add --cacheinfo 100644,$(cat id),foo/bar/.gitattributes &&\n>     -+  git write-tree >id &&\n>     -+  tree_id=$(cat id) &&\n>     -+  git commit-tree $tree_id -m \"random commit message\" >id &&\n>     -+  commit_id=$(cat id) &&\n>     -+  git update-ref refs/heads/branch1 $commit_id &&\n>     ++  mkdir -p foo/bar &&\n>     ++  test_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n>     ++          \"f test=f\\na/i test=n\\n\" tag-1 &&\n>      +\n>     -+  (\n>     -+          echo \"g test=g\" &&\n>     -+          echo \"a/i test=m\"\n>     -+  ) | git hash-object -w --stdin >id &&\n>     -+  git update-index --add --cacheinfo 100644,$(cat id),foo/bar/.gitattributes &&\n>     -+  git write-tree >id &&\n>     -+  tree_id=$(cat id) &&\n>     -+  git commit-tree $tree_id -m \"random commit message\" >id &&\n>     -+  commit_id=$(cat id) &&\n>     -+  git update-ref refs/heads/branch2 $commit_id\n>     ++  mkdir -p foo/bar &&\n>     ++  test_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n>     ++          \"g test=g\\na/i test=m\\n\" tag-2\n>      +'\n>      +\n>       test_expect_success 'command line checks' '\n>     @@ t/t0003-attributes.sh: test_expect_success 'setup' '\n>         test_must_fail git check-attr test &&\n>         test_must_fail git check-attr test -- &&\n>         test_must_fail git check-attr -- f &&\n>     -+  test_must_fail git check-attr --revision &&\n>     -+  test_must_fail git check-attr --revision not-a-valid-ref &&\n>     ++  test_must_fail git check-attr --source &&\n>     ++  test_must_fail git check-attr --source not-a-valid-ref &&\n>         echo \"f\" | test_must_fail git check-attr --stdin &&\n>         echo \"f\" | test_must_fail git check-attr --stdin -- f &&\n>         echo \"f\" | test_must_fail git check-attr --stdin test -- f &&\n>     @@ t/t0003-attributes.sh: test_expect_success 'using --git-dir and --work-tree' '\n>         )\n>       '\n>\n>     -+test_expect_success 'using --revision' '\n>     -+  attr_check_revision foo/bar/f f branch1 &&\n>     -+  attr_check_revision foo/bar/a/i n branch1 &&\n>     -+  attr_check_revision foo/bar/f unspecified branch2 &&\n>     -+  attr_check_revision foo/bar/a/i m branch2 &&\n>     -+  attr_check_revision foo/bar/g g branch2 &&\n>     -+  attr_check_revision foo/bar/g unspecified branch1\n>     ++test_expect_success 'using --source' '\n>     ++  attr_check_source foo/bar/f f tag-1 &&\n>     ++  attr_check_source foo/bar/a/i n tag-1 &&\n>     ++  attr_check_source foo/bar/f unspecified tag-2 &&\n>     ++  attr_check_source foo/bar/a/i m tag-2 &&\n>     ++  attr_check_source foo/bar/g g tag-2 &&\n>     ++  attr_check_source foo/bar/g unspecified tag-1\n>      +'\n>      +\n>       test_expect_success 'setup bare' '\n>     @@ t/t0003-attributes.sh: test_expect_success 'bare repository: check that .gitattr\n>         )\n>       '\n>\n>     -+test_expect_success 'bare repository: with --revision' '\n>     ++test_expect_success 'bare repository: with --source' '\n>      +  (\n>      +          cd bare.git &&\n>     -+          (\n>     -+                  echo \"f test=f\" &&\n>     -+                  echo \"a/i test=a/i\"\n>     -+          ) | git hash-object -w --stdin >id &&\n>     -+          git update-index --add --cacheinfo 100644 $(cat id) .gitattributes &&\n>     -+          git write-tree >id &&\n>     -+          tree_id=$(cat id) &&\n>     -+          git commit-tree $tree_id -m \"random commit message\" >id &&\n>     -+          commit_id=$(cat id) &&\n>     -+          git update-ref refs/heads/master $commit_id &&\n>     -+          attr_check_revision f f HEAD &&\n>     -+          attr_check_revision a/f f HEAD &&\n>     -+          attr_check_revision a/c/f f HEAD &&\n>     -+          attr_check_revision a/i a/i HEAD &&\n>     -+          attr_check_revision subdir/a/i unspecified HEAD\n>     ++          attr_check_source foo/bar/f f tag-1 &&\n>     ++          attr_check_source foo/bar/a/i n tag-1 &&\n>     ++          attr_check_source foo/bar/f unspecified tag-2 &&\n>     ++          attr_check_source foo/bar/a/i m tag-2 &&\n>     ++          attr_check_source foo/bar/g g tag-2 &&\n>     ++          attr_check_source foo/bar/g unspecified tag-1\n>      +  )\n>      +'\n>      +\n>\n>\n> Karthik Nayak (2):\n>   t0003: move setup for `--all` into new block\n>   attr: add flag `--source` to work with tree-ish\n>\n>  Documentation/git-check-attr.txt | 10 +++-\n>  archive.c                        |  2 +-\n>  attr.c                           | 97 +++++++++++++++++++++++---------\n>  attr.h                           |  5 +-\n>  builtin/check-attr.c             | 35 +++++++-----\n>  builtin/pack-objects.c           |  2 +-\n>  convert.c                        |  2 +-\n>  ll-merge.c                       |  4 +-\n>  pathspec.c                       |  2 +-\n>  t/t0003-attributes.sh            | 49 +++++++++++++++-\n>  userdiff.c                       |  2 +-\n>  ws.c                             |  2 +-\n>  12 files changed, 157 insertions(+), 55 deletions(-)\n>\n> --\n> 2.39.0\n>\n"},{"id":"470088","messageId":"4fc57003-3f29-2f1c-fe18-94b5f9f1bf2b@dunelm.org.uk","threadId":"59025","inReplyTo":"23813496fc73b7e5cb9f09b166e05c9a02bac43c.1671793109.git.karthik.188@gmail.com","subject":"Re: [PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-01-11T11:30:20Z","receivedAt":"2023-01-11T11:30:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 02/01/2023 11:04, Karthik Nayak wrote:\n> The contents of the .gitattributes files may evolve over time, but \"git\n> check-attr\" always checks attributes against them in the working tree\n> and/or in the index. It may be beneficial to optionally allow the users\n> to check attributes taken from a commit other than HEAD against paths.\n> \n> Add a new flag `--source` which will allow users to check the\n> attributes against a commit (actually any tree-ish would do). When the\n> user uses this flag, we go through the stack of .gitattributes files but\n> instead of checking the current working tree and/or in the index, we\n> check the blobs from the provided tree-ish object. This allows the\n> command to also be used in bare repositories.\n> \n> Since we use a tree-ish object, the user can pass \"--source\n> HEAD:subdirectory\" and all the attributes will be looked up as if\n> subdirectory was the root directory of the repository.\n\nI think changing to --source is a good idea. I've left a few comments \nbelow - the tests are broken at the moment. I didn't look very closely \nat the implementation beyond scanning the range-diff as it looks like \nthere are not any significant changes there.\n\n> We cannot simply use the `<rev>:<path>` syntax without the `--source`\n> flag, similar to how it is used in `git show` because any non-flag\n> parameter before `--` is treated as an attribute and any parameter after\n> `--` is treated as a pathname.\n> \n> The change involves creating a new function `read_attr_from_blob`, which\n> given the path reads the blob for the path against the provided source and\n> parses the attributes line by line. This function is plugged into\n> `read_attr()` function wherein we go through the stack of attributes\n> files.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> Signed-off-by: Toon Claes <toon@iotcl.com>\n> Co-authored-by: toon@iotcl.com\n> ---\n> -'git check-attr' [-a | --all | <attr>...] [--] <pathname>...\n> -'git check-attr' --stdin [-z] [-a | --all | <attr>...]\n> +'git check-attr' [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\n> +'git check-attr' --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\n\nI think \"<tree>\" would be better as \"<tree-ish>\" (see my comments on the \n--source option implementation below)\n>   \n>   DESCRIPTION\n>   -----------\n> @@ -36,6 +36,12 @@ OPTIONS\n>   \tIf `--stdin` is also given, input paths are separated\n>   \twith a NUL character instead of a linefeed character.\n>   \n> +--source=<tree>::\n> +\tCheck attributes against the specified tree-ish. Paths provided as part\n> +\tof the revision will be treated as the root directory. It is common to\n> +\tspecify the source tree by naming a commit, branch or tag associated\n> +\twith it.\n\nI think it is confusing to keep the reference to \"revision\" here, we \ncould just drop that sentence.\n\n> -N_(\"git check-attr [-a | --all | <attr>...] [--] <pathname>...\"),\n> -N_(\"git check-attr --stdin [-z] [-a | --all | <attr>...]\"),\n> +N_(\"git check-attr [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\"),\n> +N_(\"git check-attr --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\"),\n\nI think we should use \"<tree-ish>\" rather than \"<tree>\" so it is clear \none can specify a commit or tag. That's what \"git restore\" does.\n\n> diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\n> index b3aabb8aa3..78e9f47dbf 100755\n> --- a/t/t0003-attributes.sh\n> +++ b/t/t0003-attributes.sh\n> @@ -25,7 +25,15 @@ attr_check_quote () {\n>   \tgit check-attr test -- \"$path\" >actual &&\n>   \techo \"\\\"$quoted_path\\\": test: $expect\" >expect &&\n>   \ttest_cmp expect actual\n> +}\n> +\n> +attr_check_source () {\n> +\tpath=\"$1\" expect=\"$2\" source=\"$3\" git_opts=\"$4\" &&\n>   \n> +\tgit $git_opts check-attr --source $source test -- \"$path\" >actual 2>err &&\n> +\techo \"$path: test: $expect\" >expect &&\n> +\ttest_cmp expect actual\n\nThis is missing && which means we miss some test failures later\n\n> +\ttest_must_be_empty err\n>   }\n>   \n\n> +test_expect_success 'setup branches' '\n> +\tmkdir -p foo/bar &&\n> +\ttest_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n\nThe file should be foo/bar/.gitattributes (not .gitattribute). We're \nmissing failures due to this because of the missing && above\n\n> +\t\t\"f test=f\\na/i test=n\\n\" tag-1 &&\n> +\n> +\tmkdir -p foo/bar &&\n\nWe don't need to make the directory again here\n\n> +\ttest_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n> +\t\t\"g test=g\\na/i test=m\\n\" tag-2\n\nI think it would be worth either removing foo/bar/.gitattributes or \ndonig test_write_lines to change it. That way we can be sure all the \n--source tests are actually using the tree-ish we give it and not just \nreading from the filesystem.\n\nBest Wishes\n\nPhillip\n\n> +'\n> +\n>   test_expect_success 'command line checks' '\n>   \ttest_must_fail git check-attr &&\n>   \ttest_must_fail git check-attr -- &&\n>   \ttest_must_fail git check-attr test &&\n>   \ttest_must_fail git check-attr test -- &&\n>   \ttest_must_fail git check-attr -- f &&\n> +\ttest_must_fail git check-attr --source &&\n> +\ttest_must_fail git check-attr --source not-a-valid-ref &&\n>   \techo \"f\" | test_must_fail git check-attr --stdin &&\n>   \techo \"f\" | test_must_fail git check-attr --stdin -- f &&\n>   \techo \"f\" | test_must_fail git check-attr --stdin test -- f &&\n> @@ -287,6 +306,15 @@ test_expect_success 'using --git-dir and --work-tree' '\n>   \t)\n>   '\n>   \n> +test_expect_success 'using --source' '\n> +\tattr_check_source foo/bar/f f tag-1 &&\n> +\tattr_check_source foo/bar/a/i n tag-1 &&\n> +\tattr_check_source foo/bar/f unspecified tag-2 &&\n> +\tattr_check_source foo/bar/a/i m tag-2 &&\n> +\tattr_check_source foo/bar/g g tag-2 &&\n> +\tattr_check_source foo/bar/g unspecified tag-1\n> +'\n> +\n>   test_expect_success 'setup bare' '\n>   \tgit clone --template= --bare . bare.git\n>   '\n> @@ -306,6 +334,18 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '\n>   \t)\n>   '\n>   \n> +test_expect_success 'bare repository: with --source' '\n> +\t(\n> +\t\tcd bare.git &&\n> +\t\tattr_check_source foo/bar/f f tag-1 &&\n> +\t\tattr_check_source foo/bar/a/i n tag-1 &&\n> +\t\tattr_check_source foo/bar/f unspecified tag-2 &&\n> +\t\tattr_check_source foo/bar/a/i m tag-2 &&\n> +\t\tattr_check_source foo/bar/g g tag-2 &&\n> +\t\tattr_check_source foo/bar/g unspecified tag-1\n> +\t)\n> +'\n> +\n>   test_expect_success 'bare repository: check that --cached honors index' '\n>   \t(\n>   \t\tcd bare.git &&\n> diff --git a/userdiff.c b/userdiff.c\n> index 151d9a5278..b66f090a0b 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -412,7 +412,7 @@ struct userdiff_driver *userdiff_find_by_path(struct index_state *istate,\n>   \t\tcheck = attr_check_initl(\"diff\", NULL);\n>   \tif (!path)\n>   \t\treturn NULL;\n> -\tgit_check_attr(istate, path, check);\n> +\tgit_check_attr(istate, NULL, path, check);\n>   \n>   \tif (ATTR_TRUE(check->items[0].value))\n>   \t\treturn &driver_true;\n> diff --git a/ws.c b/ws.c\n> index 6e69877f25..eadbbe5667 100644\n> --- a/ws.c\n> +++ b/ws.c\n> @@ -78,7 +78,7 @@ unsigned whitespace_rule(struct index_state *istate, const char *pathname)\n>   \tif (!attr_whitespace_rule)\n>   \t\tattr_whitespace_rule = attr_check_initl(\"whitespace\", NULL);\n>   \n> -\tgit_check_attr(istate, pathname, attr_whitespace_rule);\n> +\tgit_check_attr(istate, NULL, pathname, attr_whitespace_rule);\n>   \tvalue = attr_whitespace_rule->items[0].value;\n>   \tif (ATTR_TRUE(value)) {\n>   \t\t/* true (whitespace) */\n"},{"id":"470162","messageId":"CAOLa=ZREB8dSALk96DgHqQnLvyXZieA-k33b30miypB8SemavQ@mail.gmail.com","threadId":"59025","inReplyTo":"4fc57003-3f29-2f1c-fe18-94b5f9f1bf2b@dunelm.org.uk","subject":"Re: [PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-12T10:53:25Z","receivedAt":"2023-01-12T11:02:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Wed, Jan 11, 2023 at 12:30 PM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Karthik\n>\n\nHello Philip!\n\n> On 02/01/2023 11:04, Karthik Nayak wrote:\n> > The contents of the .gitattributes files may evolve over time, but \"git\n> > check-attr\" always checks attributes against them in the working tree\n> > and/or in the index. It may be beneficial to optionally allow the users\n> > to check attributes taken from a commit other than HEAD against paths.\n> >\n> > Add a new flag `--source` which will allow users to check the\n> > attributes against a commit (actually any tree-ish would do). When the\n> > user uses this flag, we go through the stack of .gitattributes files but\n> > instead of checking the current working tree and/or in the index, we\n> > check the blobs from the provided tree-ish object. This allows the\n> > command to also be used in bare repositories.\n> >\n> > Since we use a tree-ish object, the user can pass \"--source\n> > HEAD:subdirectory\" and all the attributes will be looked up as if\n> > subdirectory was the root directory of the repository.\n>\n> I think changing to --source is a good idea. I've left a few comments\n> below - the tests are broken at the moment. I didn't look very closely\n> at the implementation beyond scanning the range-diff as it looks like\n> there are not any significant changes there.\n>\n\nThanks for the review!\n\n> > We cannot simply use the `<rev>:<path>` syntax without the `--source`\n> > flag, similar to how it is used in `git show` because any non-flag\n> > parameter before `--` is treated as an attribute and any parameter after\n> > `--` is treated as a pathname.\n> >\n> > The change involves creating a new function `read_attr_from_blob`, which\n> > given the path reads the blob for the path against the provided source and\n> > parses the attributes line by line. This function is plugged into\n> > `read_attr()` function wherein we go through the stack of attributes\n> > files.\n> >\n> > Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> > Signed-off-by: Toon Claes <toon@iotcl.com>\n> > Co-authored-by: toon@iotcl.com\n> > ---\n> > -'git check-attr' [-a | --all | <attr>...] [--] <pathname>...\n> > -'git check-attr' --stdin [-z] [-a | --all | <attr>...]\n> > +'git check-attr' [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\n> > +'git check-attr' --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\n>\n> I think \"<tree>\" would be better as \"<tree-ish>\" (see my comments on the\n> --source option implementation below)\n\nWill change!\n\n> >\n> >   DESCRIPTION\n> >   -----------\n> > @@ -36,6 +36,12 @@ OPTIONS\n> >       If `--stdin` is also given, input paths are separated\n> >       with a NUL character instead of a linefeed character.\n> >\n> > +--source=<tree>::\n> > +     Check attributes against the specified tree-ish. Paths provided as part\n> > +     of the revision will be treated as the root directory. It is common to\n> > +     specify the source tree by naming a commit, branch or tag associated\n> > +     with it.\n>\n> I think it is confusing to keep the reference to \"revision\" here, we\n> could just drop that sentence.\n>\n\nWill do!\n\n> > -N_(\"git check-attr [-a | --all | <attr>...] [--] <pathname>...\"),\n> > -N_(\"git check-attr --stdin [-z] [-a | --all | <attr>...]\"),\n> > +N_(\"git check-attr [--source <tree>] [-a | --all | <attr>...] [--] <pathname>...\"),\n> > +N_(\"git check-attr --stdin [-z] [--source <tree>] [-a | --all | <attr>...]\"),\n>\n> I think we should use \"<tree-ish>\" rather than \"<tree>\" so it is clear\n> one can specify a commit or tag. That's what \"git restore\" does.\n>\n\nYeah sure!\n\n> > diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\n> > index b3aabb8aa3..78e9f47dbf 100755\n> > --- a/t/t0003-attributes.sh\n> > +++ b/t/t0003-attributes.sh\n> > @@ -25,7 +25,15 @@ attr_check_quote () {\n> >       git check-attr test -- \"$path\" >actual &&\n> >       echo \"\\\"$quoted_path\\\": test: $expect\" >expect &&\n> >       test_cmp expect actual\n> > +}\n> > +\n> > +attr_check_source () {\n> > +     path=\"$1\" expect=\"$2\" source=\"$3\" git_opts=\"$4\" &&\n> >\n> > +     git $git_opts check-attr --source $source test -- \"$path\" >actual 2>err &&\n> > +     echo \"$path: test: $expect\" >expect &&\n> > +     test_cmp expect actual\n>\n> This is missing && which means we miss some test failures later\n>\n\nGood catch on this!\n\n> > +     test_must_be_empty err\n> >   }\n> >\n>\n> > +test_expect_success 'setup branches' '\n> > +     mkdir -p foo/bar &&\n> > +     test_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n>\n> The file should be foo/bar/.gitattributes (not .gitattribute). We're\n> missing failures due to this because of the missing && above\n>\n\nYup, this fixes the test. Thanks!\n\n> > +             \"f test=f\\na/i test=n\\n\" tag-1 &&\n> > +\n> > +     mkdir -p foo/bar &&\n>\n> We don't need to make the directory again here\n>\n> > +     test_commit --printf \"add .gitattributes\" foo/bar/.gitattribute \\\n> > +             \"g test=g\\na/i test=m\\n\" tag-2\n>\n> I think it would be worth either removing foo/bar/.gitattributes or\n> donig test_write_lines to change it. That way we can be sure all the\n> --source tests are actually using the tree-ish we give it and not just\n> reading from the filesystem.\n>\n\nWill do a `rm` on the file.\n"},{"id":"470189","messageId":"230112.86lem728ig.gmgdl@evledraar.gmail.com","threadId":"59025","inReplyTo":"23813496fc73b7e5cb9f09b166e05c9a02bac43c.1671793109.git.karthik.188@gmail.com","subject":"Re: [PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-01-12T13:20:56Z","receivedAt":"2023-01-12T13:22:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jan 02 2023, Karthik Nayak wrote:\n\n> +static struct attr_stack *read_attr_from_blob(struct index_state *istate,\n> +\t\t\t\t\t      const struct object_id *tree_oid,\n> +\t\t\t\t\t      const char *path, unsigned flags)\n> +{\n> +\tstruct object_id oid;\n> +\tunsigned long sz;\n> +\tenum object_type type;\n> +\tvoid *buf;\n> +\tunsigned short mode;\n> +\n> +\tif (!tree_oid)\n> +\t\treturn NULL;\n> +\n> +\tif (get_tree_entry(istate->repo, tree_oid, path, &oid, &mode))\n> +\t\treturn NULL;\n> +\n> +\tbuf = read_object_file(&oid, &type, &sz);\n\nHere you flip-flop between istate->repo and \"the_repository\". I think\nyou want to use repo_read_object_file(istate->repo, ...) instead.\n"},{"id":"470326","messageId":"CAOLa=ZQdNy-gQ0-gUdQM8z+k0fpLjXcRY3fAC4v2PnEvSRS2PQ@mail.gmail.com","threadId":"59025","inReplyTo":"230112.86lem728ig.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 2/2] attr: add flag `--source` to work with tree-ish","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2023-01-14T08:26:04Z","receivedAt":"2023-01-14T08:27:53Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Jan 12, 2023 at 2:21 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n>\n> On Mon, Jan 02 2023, Karthik Nayak wrote:\n>\n> > +static struct attr_stack *read_attr_from_blob(struct index_state *istate,\n> > +                                           const struct object_id *tree_oid,\n> > +                                           const char *path, unsigned flags)\n> > +{\n> > +     struct object_id oid;\n> > +     unsigned long sz;\n> > +     enum object_type type;\n> > +     void *buf;\n> > +     unsigned short mode;\n> > +\n> > +     if (!tree_oid)\n> > +             return NULL;\n> > +\n> > +     if (get_tree_entry(istate->repo, tree_oid, path, &oid, &mode))\n> > +             return NULL;\n> > +\n> > +     buf = read_object_file(&oid, &type, &sz);\n>\n> Here you flip-flop between istate->repo and \"the_repository\". I think\n> you want to use repo_read_object_file(istate->repo, ...) instead.\n\nLet me add this to v7! Thanks\n"}]}