diff: support bare repositories when reading gitattributes for diff algorithm
cover diff: support bare repositories when reading gitattributes for diff algorithm
This patch series adds support for bare repositories for the feature added in [1]. When using a bare repository, by default we will look for gitattributes from HEAD. When the --attr-source option is passed, we will try to read gitattributes from the commit.
A side effect of this patch series is that custom drivers will now also work with bare repositories.
1. (a4cf900ee7 diff: teach diff to read algorithm from diff driver,
2022-02-20)
John Cai (2):
diff: use HEAD for attributes when using bare repository
diff: add --attr-source to read gitattributes from a commit
Documentation/diff-options.txt | 4 ++++ Documentation/gitattributes.txt | 8 +++++++ diff.c | 37 ++++++++++++++++++++++++++++++--- diff.h | 1 + t/lib-diff-alternative.sh | 33 ++++++++++++++++++++++++----- t/t4018-diff-funcname.sh | 29 ++++++++++++++++++++++++++ userdiff.c | 9 +++++++- userdiff.h | 4 ++++ 8 files changed, 116 insertions(+), 9 deletions(-)
base-commit: d15644fe0226af7ffc874572d968598564a230dd
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1459%2Fjohn-cai%2Fjc%2Fdiff-attr-bare-repo-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1459/john-cai/jc/diff-attr-bare-repo-v1
Pull-Request: https://github.com/git/git/pull/1459
Signature · 1 line
gitgitgadget
PATCH 1/2 diff: use HEAD for attributes when using bare repository
From: John Cai <[email protected]>
(a4cf900e diff: teach diff to read algorithm from diff driver, 2022-02-20) does not support bare repositories. Since running diff on bare repositories is often done on Git servers, it would be useful to allow bare repositories to also take advantage of setting the algorithm via the diff driver.
Teach diff to check against the attributes from HEAD if a bare repository is being operated on. This change also allows custom diff drivers to work with bare repositories.
--- Documentation/gitattributes.txt | 4 ++++ diff.c | 33 +++++++++++++++++++++++++++++---- t/lib-diff-alternative.sh | 10 ++++++++++ t/t4018-diff-funcname.sh | 11 +++++++++++ userdiff.c | 9 ++++++++- userdiff.h | 4 ++++ 6 files changed, 66 insertions(+), 5 deletions(-)
Documentation/gitattributes.txt +4 -0
@@ -758,6 +758,8 @@ with the above configuration, i.e. `j-c-diff`, with 7 parameters, just like `GIT_EXTERNAL_DIFF` program is called. See linkgit:git[1] for details. +When using a bare repository, the gitattributes from HEAD will be used. + Setting the internal diff algorithm ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -785,6 +787,8 @@ This diff algorithm applies to user facing diff output like git-diff(1), git-show(1) and is used for the `--stat` output as well. The merge machinery will not use the diff algorithm set through this method. +When using a bare repository, the gitattributes from HEAD will be used. + NOTE: If `diff.<name>.command` is defined for path with the `diff=<name>` attribute, it is executed as an external diff driver (see above), and adding `diff.<name>.algorithm` has no effect, as the
diff.c +29 -4
@@ -29,6 +29,7 @@#include "promisor-remote.h" #include "dir.h" #include "strmap.h" +#include "tree.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0
@@ -4443,6 +4444,27 @@ static void fill_metainfo(struct strbuf *msg, } } +static void get_userdiff (struct diff_options *o, +struct userdiff_driver **drv, +const char *attr_path) +{ +const char *commit ="HEAD" ; +struct object_id *tree_oid =NULL ; + +if (is_bare_repository () && o->repo->gitdir) { +struct object_id oid; + +if (!get_oid (commit, &oid)) { +struct tree *t =parse_tree_indirect (&oid); + +if (t) + tree_oid = &t->object.oid; + } + } + + *drv =userdiff_find_by_tree_and_path (o->repo->index, tree_oid, attr_path); +} + static void run_diff_cmd(const char *pgm, const char *name, const char *other,
@@ -4458,8 +4480,10 @@ static void run_diff_cmd(const char *pgm,int must_show_header =0 ;struct userdiff_driver *drv =NULL ; -if (o->flags.allow_external || !o->ignore_driver_algorithm) - drv =userdiff_find_by_path (o->repo->index, attr_path); +if (o->flags.allow_external || !o->ignore_driver_algorithm) { + +get_userdiff (o, &drv, attr_path); + }if (o->flags.allow_external && drv && drv->external) pgm = drv->external;
@@ -4586,8 +4610,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,const char *other;if (!o->ignore_driver_algorithm) { -struct userdiff_driver *drv =userdiff_find_by_path (o->repo->index, - p->one->path); +struct userdiff_driver *drv =NULL ; + +get_userdiff (o, &drv, p->one->path);if (drv && drv->algorithm)set_diff_algorithm (o, drv->algorithm);
t/lib-diff-alternative.sh +10 -0
@@ -121,6 +121,16 @@ EOF test_cmp expect output ' + test_expect_success "$STRATEGY diff from attributes with bare repo" ' + echo "file* diff=driver" >.gitattributes && + git add file1 file2 .gitattributes && + git commit -m "adding files" && + git clone --bare --no-local . bare.git && + git -C bare.git config diff.driver.algorithm "$STRATEGY" && + git -C bare.git diff HEAD:file1 HEAD:file2 > output && + test_cmp expect output + ' + test_expect_success "$STRATEGY diff from attributes has valid diffstat" ' echo "file* diff=driver" >.gitattributes && git config diff.driver.algorithm "$STRATEGY" &&
t/t4018-diff-funcname.sh +11 -0
@@ -63,6 +63,17 @@ do test_i18ngrep ! fatal msg && test_i18ngrep ! error msg ' + test_expect_success "builtin $p pattern compiles on bare repo" ' + test_when_finished "rm -rf bare.git" && + echo "*.java diff=$p" >.gitattributes && + git add . && + git commit -am "adding files" && + git clone --bare --no-local . bare.git && + test_expect_code 1 git -C bare.git diff --exit-code \ + HEAD:A.java HEAD:B.java 2>msg && + test_i18ngrep ! fatal msg && + test_i18ngrep ! error msg + ' done test_expect_success 'last regexp must not be negated' '
userdiff.c +8 -1
@@ -408,6 +408,13 @@ struct userdiff_driver *userdiff_find_by_name(const char *name)struct userdiff_driver *userdiff_find_by_path (struct index_state *istate,const char *path) +{ +return userdiff_find_by_tree_and_path (istate,NULL , path); +} + +struct userdiff_driver *userdiff_find_by_tree_and_path (struct index_state *istate, +const struct object_id *tree_oid, +const char *path) {static struct attr_check *check;
@@ -415,7 +422,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,NULL , path, check); +git_check_attr (istate, tree_oid, path, check);if (ATTR_TRUE (check->items[0 ].value))return &driver_true;
userdiff.h +4 -0
@@ -33,6 +33,10 @@ int userdiff_config(const char *k, const char *v);struct userdiff_driver *userdiff_find_by_name (const char *name);struct userdiff_driver *userdiff_find_by_path (struct index_state *istate,const char *path); +struct userdiff_driver *userdiff_find_by_tree_and_path (struct index_state *istate, +const struct object_id *tree_oid, +const char *path); +/* * Initialize any textconv-related fields in the driver and return it, or NULL
Signature · 1 line
gitgitgadget
PATCH 2/2 diff: add --attr-source to read gitattributes from a commit
In reply to John Cai via GitGitGadgetFrom: John Cai <[email protected]>
The previous commit allows the diff machinery to read gitattributes from HEAD when a bare repository is being used.
Some users may want to specify which commit to read gitattributes from. Teach diff a new --attr-source that can be used to pass in a commit.
--- Documentation/diff-options.txt | 4 ++++ Documentation/gitattributes.txt | 8 ++++++-- diff.c | 16 ++++++++++----- diff.h | 1 + t/lib-diff-alternative.sh | 35 ++++++++++++++++++++++----------- t/t4018-diff-funcname.sh | 18 +++++++++++++++++ 6 files changed, 64 insertions(+), 18 deletions(-)
Documentation/diff-options.txt +4 -0
@@ -173,6 +173,10 @@ endif::git-log[] --anchored=<text>:: Generate a diff using the "anchored diff" algorithm. + +--attr-source=<commit>:: + Specify a commit to read gitattributes from when using a bare + repository when defining external drivers, or internal algorithms. + This option may be specified more than once. +
Documentation/gitattributes.txt +6 -2
@@ -758,7 +758,9 @@ with the above configuration, i.e. `j-c-diff`, with 7 parameters, just like `GIT_EXTERNAL_DIFF` program is called. See linkgit:git[1] for details. -When using a bare repository, the gitattributes from HEAD will be used. +When using a bare repository, the gitattributes from HEAD will be used, unless +the --attr-source option is used to specify a commit from which the +gitattributes will be read. Setting the internal diff algorithm ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -787,7 +789,9 @@ This diff algorithm applies to user facing diff output like git-diff(1), git-show(1) and is used for the `--stat` output as well. The merge machinery will not use the diff algorithm set through this method. -When using a bare repository, the gitattributes from HEAD will be used. +When using a bare repository, the gitattributes from HEAD will be used, unless +the --attr-source option is used to specify a commit from which the +gitattributes will be read. NOTE: If `diff.<name>.command` is defined for path with the `diff=<name>` attribute, it is executed as an external diff driver
diff.c +11 -5
@@ -4448,10 +4448,13 @@ static void get_userdiff(struct diff_options *o,struct userdiff_driver **drv, const char *attr_path) { - const char *commit = "HEAD"; + const char *commit = o->attr_source;struct object_id *tree_oid =NULL ; -if (is_bare_repository () && o->repo->gitdir) { +if (!commit) + commit ="HEAD" ; + +if ((o->attr_source ||is_bare_repository ()) && o->repo->gitdir) {struct object_id oid;if (!get_oid (commit, &oid)) {
@@ -4459,6 +4462,8 @@ static void get_userdiff(struct diff_options *o,if (t) tree_oid = &t->object.oid; + }else if (o->attr_source) { +die (_ ("%s is not a valid object" ), commit); } }
@@ -4480,10 +4485,8 @@ static void run_diff_cmd(const char *pgm,int must_show_header =0 ;struct userdiff_driver *drv =NULL ; -if (o->flags.allow_external || !o->ignore_driver_algorithm) { - +if (o->flags.allow_external || !o->ignore_driver_algorithm)get_userdiff (o, &drv, attr_path); - }if (o->flags.allow_external && drv && drv->external) pgm = drv->external;
@@ -5699,6 +5702,9 @@ struct option *add_diff_options(const struct option *opts, N_("disable all output of the program")),OPT_BOOL (0 ,"ext-diff" , &options->flags.allow_external,N_ ("allow an external diff helper to be executed" )), +OPT_STRING (0 ,"attr-source" , &options->attr_source, +N_ ("attributes-source" ), +N_ ("the commit to read attributes from" )),OPT_CALLBACK_F (0 ,"textconv" , options,NULL ,N_ ("run external text conversion filters when comparing binary files" ), PARSE_OPT_NOARG, diff_opt_textconv),
diff.h +1 -0
@@ -334,6 +334,7 @@ struct diff_options {const char *stat_sep;int xdl_opts;int ignore_driver_algorithm; +const char *attr_source;/* see Documentation/diff-options.txt */ char **anchors;
t/lib-diff-alternative.sh +24 -11
@@ -112,25 +112,38 @@ EOF STRATEGY=$1 - test_expect_success "$STRATEGY diff from attributes" ' + test_expect_success "setup attributes files for tests with $STRATEGY" ' + git checkout -b master && echo "file* diff=driver" >.gitattributes && - git config diff.driver.algorithm "$STRATEGY" && - test_must_fail git diff --no-index file1 file2 > output && - cat expect && - cat output && + git add file1 file2 .gitattributes && + git commit -m "adding files" && + git checkout -b branchA && + echo "file* diff=driverA" >.gitattributes && + git add .gitattributes && + git commit -m "adding driverA as diff driver" && + git checkout master && + git clone --bare --no-local . bare.git + ' + + test_expect_success "$STRATEGY diff from attributes" ' + test_must_fail git -c diff.driver.algorithm=$STRATEGY diff --no-index file1 file2 > output && test_cmp expect output ' test_expect_success "$STRATEGY diff from attributes with bare repo" ' - echo "file* diff=driver" >.gitattributes && - git add file1 file2 .gitattributes && - git commit -m "adding files" && - git clone --bare --no-local . bare.git && - git -C bare.git config diff.driver.algorithm "$STRATEGY" && - git -C bare.git diff HEAD:file1 HEAD:file2 > output && + git -C bare.git -c diff.driver.algorithm=$STRATEGY diff HEAD:file1 HEAD:file2 >output && + test_cmp expect output + ' + + test_expect_success "diff from attributes with bare repo when branch different than HEAD" ' + git -C bare.git -c diff.driver.algorithm=myers -c diff.driverA.algorithm=$STRATEGY diff --attr-source branchA HEAD:file1 HEAD:file2 >output && test_cmp expect output ' + test_expect_success "diff from attributes with bare repo with invalid source" ' + test_must_fail git -C bare.git diff --attr-source invalid-branch HEAD:file1 HEAD:file2 + ' + test_expect_success "$STRATEGY diff from attributes has valid diffstat" ' echo "file* diff=driver" >.gitattributes && git config diff.driver.algorithm "$STRATEGY" &&
t/t4018-diff-funcname.sh +18 -0
@@ -63,6 +63,7 @@ do test_i18ngrep ! fatal msg && test_i18ngrep ! error msg ' + test_expect_success "builtin $p pattern compiles on bare repo" ' test_when_finished "rm -rf bare.git" && echo "*.java diff=$p" >.gitattributes &&
@@ -74,6 +75,23 @@ do test_i18ngrep ! fatal msg && test_i18ngrep ! error msg ' + test_expect_success "builtin $p pattern compiles on bare repo with --attr-source" ' + test_when_finished "rm -rf bare.git" && + git checkout -B master && + echo "*.java diff=notexist" > .gitattributes && + git add .gitattributes && + git commit -am "changing gitattributes" && + git checkout -B branchA && + echo "*.java diff=$p" >.gitattributes && + git add .gitattributes && + git commit -am "changing gitattributes" && + git clone --bare --no-local . bare.git && + git -C bare.git symbolic-ref HEAD refs/heads/master && + test_expect_code 1 git -C bare.git diff --exit-code \ + --attr-source branchA HEAD:A.java HEAD:B.java 2>msg && + test_i18ngrep ! fatal msg && + test_i18ngrep ! error msg + ' done test_expect_success 'last regexp must not be negated' '
Signature · 1 line
gitgitgadget
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
In reply to John Cai via GitGitGadgetQuoted 4 lines · John Cai via GitGitGadget
From: John Cai <[email protected]>
(
a4cf900ediff: teach diff to read algorithm from diff driver, 2022-02-20) does not support bare repositories.
Wrong placement of opening parenthesis, i.e. "a4cf900e (diff: teach diff to read algorithm from diff driver, 2023-02-20)". The date is also wrong. Use "git show -s --format=reference a4cf900e". Insert its output directly to your editor instead of transcribing manually.
I do not think the commit is to blame in the first place for two reasons.
* Even in a bare repository, the attributes defined in the $GIT_DIR/info/attributes file are in effect, and the new feature added by a4cf900e should work just fine with it.
* By definition, there is no working tree in a bare repository, and it always has been naturally expected we won't read attributes from working tree files. This is in no way limited to the diff driver feature recently added.
So the above does not look like a good first sentence to explain this change.
Quoted 4 lines
Since running diff
on bare repositories is often done on Git servers, it would be useful to allow bare repositories to also take advantage of setting the algorithm via the diff driver.
Since referring to in-tree attributes is often useful with any command, not limited to "diff", I wonder if it is feasible to add support for the --attr-source=<tree> option globally, instead of implementing such an option piecemeal. If your "git diff" in a bare repository starts honoring attributes recorded in HEAD, don't your users expect your "git log" and "git show" to also honor them?
Hi John,
Le 2023-03-13 à 21:53, John Cai via GitGitGadget a écrit :
Quoted 4 lines
This patch series adds support for bare repositories for the feature added in [1]. When using a bare repository, by default we will look for gitattributes from HEAD. When the --attr-source option is passed, we will try to read gitattributes from the commit.
When I read that I immediately thought of the config settings 'mailmap.file', 'mailmap.blob' which allow the same sort of thing, but for the mailmap.
For the sake of consistency of the whole system, maybe we would want to support 'attr.file' and 'attr.blob' ? In that case, maybe we don't need a new command line option at all...
Cheers,
Philippe.
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
In reply to Junio C HamanoQuoted 6 lines · Junio C Hamano
Since referring to in-tree attributes is often useful with any command, not limited to "diff", I wonder if it is feasible to add support for the --attr-source=<tree> option globally, instead of implementing such an option piecemeal. If your "git diff" in a bare repository starts honoring attributes recorded in HEAD, don't your users expect your "git log" and "git show" to also honor them?
Just for illustration, here is one way to do so.
The implementation goes in the opposite direction from the more recent trend, which is why I am not making it an official patch, but with this you can do things like:
$ git --attr-source=e83c5163 check-attr whitespace cache.h cache.h: whitespace: unspecified $ git --attr-source=e2f6331a142^ check-attr whitespace cache.h cache.h: whitespace: set $ git --attr-source=HEAD check-attr whitespace cache.h cache.h: whitespace: indent,trail,space
where e83c5163 is the very first version of Git from 2005 where .gitattributes did not exist, e2f6331a142^ is the last version before we set whitespace to indent,trail,space, and HEAD is a more recent version of our source tree.
In the following illustration patch, the attr-source tree object name is kept as a string as long as possible and at the very last minute when we call git_check_attr() for the first time we turn it into an object id. This is because at the very early stage when we parse the global option we may not even know where our repository is (hence do not have enough information to parse HEAD). We also figure out is_bare_repository() late in the process for the same reason.
attr.c | 29 +++++++++++++++++++++++++++++ attr.h | 6 ++++++ git.c | 3 +++ 3 files changed, 38 insertions(+)
attr.c +29 -0
@@ -1165,12 +1165,41 @@ static void collect_some_attrs(struct index_state *istate,fill (path, pathlen, basename_offset, check->stack, check->all_attrs, rem); } +static const char *default_attr_source_tree_object_name; + +void set_git_attr_source (const char *tree_object_name) +{ + default_attr_source_tree_object_name =xstrdup (tree_object_name); +} + + +static struct object_id *default_attr_source (void ) +{ +static struct object_id attr_source; + +if (is_null_oid (&attr_source)) { +if (!default_attr_source_tree_object_name && +is_bare_repository ()) + default_attr_source_tree_object_name ="HEAD" ; + +if (default_attr_source_tree_object_name && +is_null_oid (&attr_source)) +get_oid (default_attr_source_tree_object_name, &attr_source); + } +if (is_null_oid (&attr_source)) +return NULL ; +return &attr_source; +} +void git_check_attr (struct index_state *istate,const struct object_id *tree_oid,const char *path,struct attr_check *check) {int i; +if (!tree_oid) + tree_oid =default_attr_source (); +collect_some_attrs (istate, tree_oid, path, check);for (i =0 ; i < check->nr; i++) {
attr.h +6 -0
@@ -135,6 +135,12 @@ struct git_attr;struct all_attrs_item ;struct attr_stack ; +/* +* The textual object name for the tree-ish used by git_check_attr() +* when NULL is given to tree_oid. +*/ +void set_git_attr_source (const char *); +/* * Given a string, return the gitattribute object that * corresponds to it.
git.c +3 -0
@@ -4,6 +4,7 @@#include "help.h" #include "run-command.h" #include "alias.h" +#include "attr.h" #include "shallow.h" #define RUN_SETUP (1<<0)
@@ -307,6 +308,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) }else {exit (list_cmds (cmd)); } + }else if (skip_prefix (cmd,"--attr-source=" , &cmd)) { +set_git_attr_source (cmd); }else {fprintf (stderr,_ ("unknown option: %s\n" ), cmd);usage (git_usage_string);
Quoted 1 line · Philippe Blain
Hi John,
Hi Philippe,
Quoted 7 lines
Le 2023-03-13 à 21:53, John Cai via GitGitGadget a écrit :
Quoted 4 lines
This patch series adds support for bare repositories for the feature added in [1]. When using a bare repository, by default we will look for gitattributes from HEAD. When the --attr-source option is passed, we will try to read gitattributes from the commit.
Quoted 6 lines
When I read that I immediately thought of the config settings 'mailmap.file', 'mailmap.blob' which allow the same sort of thing, but for the mailmap.
For the sake of consistency of the whole system, maybe we would want to support 'attr.file' and 'attr.blob' ? In that case, maybe we don't need a new command line option at all...
I wasn't aware of those config options. Indeed that's a good option! My only concern with that is that there is already some precedence for passing a <tree-ish> as a source for attributes in [1], so I thought adding a command line option would be somewhat consistent.
But I can also see the benefit of using a config value since there is also precedence because of mailmap.file and mailmap.blob. Not sure what others think, but this may be the less invasive way to go.
1. https://git-scm.com/docs/git-check-attr#Documentation/git-check-attr.txt---sourcelttree-ishgt
Quoted 4 lines
Cheers,
Philippe.
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
In reply to Junio C HamanoQuoted 1 line · Junio C Hamano
Junio C Hamano <[email protected]> writes:
Hi Junio,
Quoted 8 lines
Quoted 7 lines
Since referring to in-tree attributes is often useful with any command, not limited to "diff", I wonder if it is feasible to add support for the --attr-source=<tree> option globally, instead of implementing such an option piecemeal. If your "git diff" in a bare repository starts honoring attributes recorded in HEAD, don't your users expect your "git log" and "git show" to also honor them?
Quoted 4 lines
Just for illustration, here is one way to do so.
The implementation goes in the opposite direction from the more recent trend, which is why I am not making it an official patch, but
Could you explain why this goes against the "more recent trend" for my understanding?
Quoted 8 lines
with this you can do things like:
$ git --attr-source=e83c5163 check-attr whitespace cache.h cache.h: whitespace: unspecified $ git --attr-source=e2f6331a142^ check-attr whitespace cache.h cache.h: whitespace: set $ git --attr-source=HEAD check-attr whitespace cache.h cache.h: whitespace: indent,trail,space
I like the idea of an option that is global. For git-check-attr however, we already have a --source option. Not sure how big of a deal that is. If we wanted to, we could put --source on a deprecation path by making it hidden for a while, etc.
Quoted 105 lines
where
e83c5163is the very first version of Git from 2005 where .gitattributes did not exist, e2f6331a142^ is the last version before we set whitespace to indent,trail,space, and HEAD is a more recent version of our source tree.In the following illustration patch, the attr-source tree object name is kept as a string as long as possible and at the very last minute when we call
git_check_attr()for the first time we turn it into an object id. This is because at the very early stage when we parse the global option we may not even know where our repository is (hence do not have enough information to parse HEAD). We also figure outis_bare_repository()late in the process for the same reason.attr.c | 29 +++++++++++++++++++++++++++++ attr.h | 6 ++++++ git.c | 3 +++ 3 files changed, 38 insertions(+)attr.c +29
-0@@ -1165,12 +1165,41 @@ static void collect_some_attrs(struct index_state *istate,fill (path, pathlen, basename_offset, check->stack, check->all_attrs, rem); } +static const char *default_attr_source_tree_object_name; + +void set_git_attr_source (const char *tree_object_name) +{ + default_attr_source_tree_object_name =xstrdup (tree_object_name); +} + + +static struct object_id *default_attr_source (void ) +{ +static struct object_id attr_source; + +if (is_null_oid (&attr_source)) { +if (!default_attr_source_tree_object_name && +is_bare_repository ()) + default_attr_source_tree_object_name ="HEAD" ; + +if (default_attr_source_tree_object_name && +is_null_oid (&attr_source)) +get_oid (default_attr_source_tree_object_name, &attr_source); + } +if (is_null_oid (&attr_source)) +return NULL ; +return &attr_source; +} +void git_check_attr (struct index_state *istate,const struct object_id *tree_oid,const char *path,struct attr_check *check) {int i; +if (!tree_oid) + tree_oid =default_attr_source (); +collect_some_attrs (istate, tree_oid, path, check);for (i =0 ; i < check->nr; i++) {attr.h +6
-0@@ -135,6 +135,12 @@ struct git_attr;struct all_attrs_item ;struct attr_stack ; +/* +* The textual object name for the tree-ish used by git_check_attr() +* when NULL is given to tree_oid. +*/ +void set_git_attr_source (const char *); +/* * Given a string, return the gitattribute object that * corresponds to it. git.c +3
-0@@ -4,6 +4,7 @@#include "help.h" #include "run-command.h" #include "alias.h" +#include "attr.h" #include "shallow.h" #define RUN_SETUP (1<<0)@@ -307,6 +308,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) }else {exit (list_cmds (cmd)); } + }else if (skip_prefix (cmd,"--attr-source=" , &cmd)) { +set_git_attr_source (cmd); }else {fprintf (stderr,_ ("unknown option: %s\n" ), cmd);usage (git_usage_string);
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
Quoted 7 lines · John Cai
Quoted 4 lines
Just for illustration, here is one way to do so.
The implementation goes in the opposite direction from the more recent trend, which is why I am not making it an official patch, but
Could you explain why this goes against the "more recent trend" for my understanding?
The illustration uses a global state.
The recent trend is to reduce reliance on global states and use the repository object and others that hold such state through the callchain.
But a new global variable that holds the fallback tree-ish object name was a so convenient way to illustrate the core of the idea, without having to change many callchains.
Quoted 10 lines
Quoted 8 lines
with this you can do things like:
$ git --attr-source=e83c5163 check-attr whitespace cache.h cache.h: whitespace: unspecified $ git --attr-source=e2f6331a142^ check-attr whitespace cache.h cache.h: whitespace: set $ git --attr-source=HEAD check-attr whitespace cache.h cache.h: whitespace: indent,trail,spaceI like the idea of an option that is global. For git-check-attr however, we
I guess I shouldn't have used check-attr to avoid confusion.
The point is that the internal mechanisms introduced by 47cfc9bd (attr: add flag --source to work with tree-ish, 2023-01-14), which taught check-attr the --source option and is reused by this illustration patch, was a good idea, but its UI was a mistake. We do not need per-command --source option the commit adds if we did the global option from day one. Yes, I think we can deprecate the "--source" option from there, if we all prefer the global option avenue. I _think_ git_all_attrs() needs to be told about the default attr-source trick (which I didn't touch in my illustration patch) before it happens, though.
If you used the mechanism in the illustration patch I gave you, and adjusted the test part of your patch to match (i.e. "diff" does not learn "--attr-source" option, but "git --attr-source=... diff" is how you make it read attributes from a tree-ish), would the result work well, or do we need more work to make it usable? How well do other commands (e.g. "git show") work in the same test repository you created in your version?
Thanks.
Quoted 8 lines · John Cai
I wasn't aware of those config options. Indeed that's a good option! My only concern with that is that there is already some precedence for passing a <tree-ish> as a source for attributes in [1], so I thought adding a command line option would be somewhat consistent.
But I can also see the benefit of using a config value since there is also precedence because of mailmap.file and mailmap.blob. Not sure what others think, but this may be the less invasive way to go.
I actually hate these one-off variables, and I wish we didn't do mailmap.file or mailmap.blob at all. Instead we could have taught the machinery to read from $GIT_DIR/info/mailmap just like the exclude and the attribute machinery already knows to read excludea and attributes files in the directory.
As your "per filetype (determined by the attribute) diff algorithm in a bare repository" feature already depends on being able to use config (as the look-up is "attributes determines the diff filetype name, and then diff.<filetype>.algo is looked up in the config") to configure, it does not sound too bad to add the attribute info in the $GIT_DIR/info/attributes file in your bare repository (and you'd set the algorithm in $GIT_DIR/config file there), and it would just work without any new "read from HEAD" feature, I would presume?
Thanks.
Quoted 11 lines · Junio C Hamano
Quoted 8 lines · John Cai
I wasn't aware of those config options. Indeed that's a good option! My only concern with that is that there is already some precedence for passing a <tree-ish> as a source for attributes in [1], so I thought adding a command line option would be somewhat consistent.
But I can also see the benefit of using a config value since there is also precedence because of mailmap.file and mailmap.blob. Not sure what others think, but this may be the less invasive way to go.
Quoted 5 lines
I actually hate these one-off variables, and I wish we didn't do mailmap.file or mailmap.blob at all. Instead we could have taught the machinery to read from $GIT_DIR/info/mailmap just like the exclude and the attribute machinery already knows to read excludea and attributes files in the directory.
I don't think $GIT_DIR/info/mailmap is a good substitute for mailmap.blob. The point there is to pull it from the repository, and you'd have to do:
git cat-file HEAD:.mailmap >.git/info/mailmap
before each command to get the equivalent. It is even worse for attributes, because the in-tree representation is not even a single file that is compatible with $GIT_DIR/ (there can be many attributes spread throughout the tree).
It's true that mailmap.file, when specified in the local config, is redundant with a repo-level info/mailmap file. I always assumed that people put it in their ~/.gitconfig, though, to cover multiple projects (just like we have core.attributesFile in addition to the in-repo meta-file).
Quoted 8 lines
As your "per filetype (determined by the attribute) diff algorithm in a bare repository" feature already depends on being able to use config (as the look-up is "attributes determines the diff filetype name, and then diff.<filetype>.algo is looked up in the config") to configure, it does not sound too bad to add the attribute info in the $GIT_DIR/info/attributes file in your bare repository (and you'd set the algorithm in $GIT_DIR/config file there), and it would just work without any new "read from HEAD" feature, I would presume?
I don't think that's quite a substitute.
If you have a static list of attributes, that is OK (though you are presumably better off with /etc/gitattributes or similar that covers all repos). But if you want to respect in-repo ones, you need to look at the whole tree (and can't even just dump them out).
It does seem at first glance that you are limited to a static list of attributes, because any diff "type" you mention has to have a corresponding entry in the config to do anything useful. But these lists are only loosely coupled. For example, you could have static entries like this:
# in config [diff "json"] algorithm = patience
# in attributes *.json diff=json
but still tell users that "json" is a well-known name, and if they want to get the same treatment for an arbitrary file "foo" that they would get for "foo.json", they have to add attributes mapping it, like:
foo diff=json
inside their repo.
-Peff
Quoted 11 lines · Jeff King
If you have a static list of attributes, that is OK (though you are presumably better off with
/etc/gitattributesor similar that covers all repos).
...
but still tell users that "json" is a well-known name, and if they want to get the same treatment for an arbitrary file "foo" that they would get for "foo.json", they have to add attributes mapping it, like:foo diff=json
inside their repo.
Yes, you described the reason very well why we didn't add "global" or "system-wide" attribute files.
Thanks for commenting on my somewhat tongue-in-cheek "devil's advocate" remark.
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
In reply to Junio C HamanoHi Junio,
Quoted 7 lines · Junio C Hamano
Quoted 5 lines · John Cai
Quoted 4 lines
Just for illustration, here is one way to do so.
The implementation goes in the opposite direction from the more recent trend, which is why I am not making it an official patch, but
Quoted 8 lines
Quoted 2 lines
Could you explain why this goes against the "more recent trend" for my understanding?
The illustration uses a global state.
The recent trend is to reduce reliance on global states and use the repository object and others that hold such state through the callchain.
Ah I see--in that case, what would be a good object to put this state into? Mabe repo_settings?
Quoted 18 lines
But a new global variable that holds the fallback tree-ish object name was a so convenient way to illustrate the core of the idea, without having to change many callchains.
Quoted 10 lines
Quoted 8 lines
with this you can do things like:
$ git --attr-source=e83c5163 check-attr whitespace cache.h cache.h: whitespace: unspecified $ git --attr-source=e2f6331a142^ check-attr whitespace cache.h cache.h: whitespace: set $ git --attr-source=HEAD check-attr whitespace cache.h cache.h: whitespace: indent,trail,spaceI like the idea of an option that is global. For git-check-attr however, we
I guess I shouldn't have used check-attr to avoid confusion.
Quoted 10 lines
The point is that the internal mechanisms introduced by
47cfc9bd(attr: add flag--sourceto work with tree-ish, 2023-01-14), which taught check-attr the --source option and is reused by this illustration patch, was a good idea, but its UI was a mistake. We do not need per-command --source option the commit adds if we did the global option from day one. Yes, I think we can deprecate the "--source" option from there, if we all prefer the global option avenue. I _think_git_all_attrs()needs to be told about the default attr-source trick (which I didn't touch in my illustration patch) before it happens, though.
Good point. I think to have a global flag would be a better user experience.
Quoted 8 lines
If you used the mechanism in the illustration patch I gave you, and adjusted the test part of your patch to match (i.e. "diff" does not learn "--attr-source" option, but "git --attr-source=... diff" is how you make it read attributes from a tree-ish), would the result work well, or do we need more work to make it usable? How well do other commands (e.g. "git show") work in the same test repository you created in your version?
I think it works pretty smoothly. I adjusted my tests to work with this git flag, and I also tested git show and it works well.
In fact, your proposal would serve the needs of my patch independently of any of the diff code, which is nice.
Quoted 2 lines
Thanks.
Thanks,
John
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
Quoted 2 lines · John Cai
Ah I see--in that case, what would be a good object to put this state into? Mabe
repo_settings?
I personally think a global is good enough for a case like this. It is not like the code in the attribute subsystem is structured to work in multiple repositories at the same time in the first place, so in repo_settings, the_repository, or elsewhere is just as good as having it in a plain old file-scope static, and even after moving it into the_repository or something, the access will initially be done by referring to the_repository global (which may not even exist by the time "git --attr-source=<tree>" gets parsed), and not by plumbing a "struct repository *" parameter through the callchain.
Another thing to note is that there needs a mechanism to convey the tree object used to read the attributes from down to subprocesses, e.g. exporting the GIT_ATTR_SOURCE environment variable and make Git behave as if it saw the "git --attr-source=<tree>" option when such an environment variable exists, or something. Otherwise, the custom attribute source would only take effect inside built-in commands.
In any case, here is what I have now, stealing some tests from your patch. I do not think I'll be working on it further at least for now, so feel free to run with it. I am not adding the "in a bare repository, always read from HEAD unless told otherwise", as I do not see a good way to countermand it from a command line.
----- >8 ----- cut here ----- >8 ----- cut here ----- >8 -----
Subject: [PATCH] attr: teach "--attr-source=<tree>" global option to "git"
Earlier, 47cfc9bd (attr: add flag --source to work with tree-ish, 2023-01-14) taught "git check-attr" the "--source=<tree>" option to allow it to read attribute files from a tree-ish, but did so only for the command. Just like "check-attr" users wanted a way to use attributes from a tree-ish and not from the working tree files, users of other commands (like "git diff") would benefit from the same.
Undo most of the UI change the commit made, while keeping the internal logic to read attributes from a given tree-ish. Expose the internal logic via a new "--attr-source=<tree>" command line option given to "git", so that it can be used with any git command that runs internally.
The tests were stolen and adjusted from John Cai's effort where only "git diff" learned the "--attr-source" option to read from a tree-ish.
--- archive.c | 2 +- attr.c | 34 ++++++++++++++++++++++++++++++++-- attr.h | 13 +++++++++---- builtin/check-attr.c | 17 ++++++++--------- builtin/pack-objects.c | 2 +- convert.c | 2 +- git.c | 3 +++ ll-merge.c | 4 ++-- pathspec.c | 2 +- t/lib-diff-alternative.sh | 31 ++++++++++++++++++++++++++----- t/t0003-attributes.sh | 7 ++++++- t/t4018-diff-funcname.sh | 19 +++++++++++++++++++ userdiff.c | 2 +- ws.c | 2 +- 14 files changed, 111 insertions(+), 29 deletions(-)
archive.c +1 -1
@@ -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,NULL , path, check); +git_check_attr (istate, path, check);return check; }
attr.c +32 -2
@@ -1165,11 +1165,40 @@ static void collect_some_attrs(struct index_state *istate,fill (path, pathlen, basename_offset, check->stack, check->all_attrs, rem); } +static const char *default_attr_source_tree_object_name; + +void set_git_attr_source (const char *tree_object_name) +{ + default_attr_source_tree_object_name =xstrdup (tree_object_name); +} + + +static void compute_default_attr_source (struct object_id *attr_source) +{ +if (!default_attr_source_tree_object_name || !is_null_oid (attr_source)) +return ; + +if (get_oid_treeish (default_attr_source_tree_object_name, attr_source)) +die (_ ("bad --attr-source object" )); +} + +static struct object_id *default_attr_source (void ) +{ +static struct object_id attr_source; + +if (is_null_oid (&attr_source)) +compute_default_attr_source (&attr_source); +if (is_null_oid (&attr_source)) +return NULL ; +return &attr_source; +} +void git_check_attr (struct index_state *istate, -const struct object_id *tree_oid,const char *path, +const char *path,struct attr_check *check) {int i; +const struct object_id *tree_oid =default_attr_source ();collect_some_attrs (istate, tree_oid, path, check);
@@ -1182,10 +1211,11 @@ void git_check_attr(struct index_state *istate, } } -void git_all_attrs (struct index_state *istate,const struct object_id *tree_oid, +void git_all_attrs (struct index_state *istate,const char *path,struct attr_check *check) {int i; +const struct object_id *tree_oid =default_attr_source ();attr_check_reset (check);collect_some_attrs (istate, tree_oid, path, check);
attr.h +9 -4
@@ -45,7 +45,7 @@* const char *path; * * setup_check(); -* git_check_attr(&the_index, tree_oid, path, check); +* git_check_attr(&the_index, path, check); * ------------ * * - Act on `.value` member of the result, left in `check->items[]`:
@@ -120,7 +120,6 @@#define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024)struct index_state ; -struct object_id ;/** * An attribute is an opaque object that is identified by its name. Pass the
@@ -135,6 +134,12 @@ struct git_attr;struct all_attrs_item ;struct attr_stack ; +/* +* The textual object name for the tree-ish used by git_check_attr() +* to read attributes from (instead of from the working tree). +*/ +void set_git_attr_source (const char *); +/* * Given a string, return the gitattribute object that * corresponds to it.
@@ -203,14 +208,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 struct object_id *tree_oid,const char *path, +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,const struct object_id *tree_oid, +void git_all_attrs (struct index_state *istate,const char *path,struct attr_check *check);enum git_attr_direction {
builtin/check-attr.c +8 -9
@@ -58,7 +58,7 @@ static void output_attr(struct attr_check *check, const char *file) }static void check_attr (const char *prefix,struct attr_check *check, -const struct object_id *tree_oid,int collect_all, +int collect_all,const char *file) {
@@ -66,9 +66,9 @@ static void check_attr(const char *prefix, struct attr_check *check,prefix_path (prefix, prefix ?strlen (prefix) :0 , file);if (collect_all) { -git_all_attrs (&the_index, tree_oid, full_path, check); +git_all_attrs (&the_index, full_path, check); }else { -git_check_attr (&the_index, tree_oid, full_path, check); +git_check_attr (&the_index, full_path, check); }output_attr (check, file);
@@ -76,7 +76,7 @@ static void check_attr(const char *prefix, struct attr_check *check, }static void check_attr_stdin_paths (const char *prefix,struct attr_check *check, -const struct object_id *tree_oid,int collect_all) +int collect_all) {struct strbuf buf =STRBUF_INIT ;struct strbuf unquoted =STRBUF_INIT ;
@@ -90,7 +90,7 @@ static void check_attr_stdin_paths(const char *prefix, struct attr_check *check,die ("line is badly quoted" );strbuf_swap (&buf, &unquoted); } -check_attr (prefix, check, tree_oid, collect_all, buf.buf); +check_attr (prefix, check, collect_all, buf.buf);maybe_flush_or_die (stdout,"attribute to stdout" ); }strbuf_release (&buf);
@@ -106,7 +106,6 @@ 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;
@@ -182,14 +181,14 @@ 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; +set_git_attr_source (source); }if (stdin_paths) -check_attr_stdin_paths (prefix, check, tree_oid, all_attrs); +check_attr_stdin_paths (prefix, check, all_attrs);else {for (i = filei; i < argc; i++) -check_attr (prefix, check, tree_oid, all_attrs, argv[i]); +check_attr (prefix, check, all_attrs, argv[i]);maybe_flush_or_die (stdout,"attribute to stdout" ); }
builtin/pack-objects.c +1 -1
@@ -1320,7 +1320,7 @@ static int no_try_delta(const char *path)if (!check) check =attr_check_initl ("delta" ,NULL ); -git_check_attr (the_repository->index,NULL , path, check); +git_check_attr (the_repository->index, path, check);if (ATTR_FALSE (check->items[0 ].value))return 1 ;return 0 ;
convert.c +1 -1
@@ -1308,7 +1308,7 @@ void convert_attrs(struct index_state *istate,git_config (read_convert_config,NULL ); } -git_check_attr (istate,NULL , path, check); +git_check_attr (istate, path, check); ccheck = check->items; ca->crlf_action =git_path_check_crlf (ccheck +4 );if (ca->crlf_action ==CRLF_UNDEFINED )
git.c +3 -0
@@ -4,6 +4,7 @@#include "help.h" #include "run-command.h" #include "alias.h" +#include "attr.h" #include "shallow.h" #define RUN_SETUP (1<<0)
@@ -307,6 +308,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) }else {exit (list_cmds (cmd)); } + }else if (skip_prefix (cmd,"--attr-source=" , &cmd)) { +set_git_attr_source (cmd); }else {fprintf (stderr,_ ("unknown option: %s\n" ), cmd);usage (git_usage_string);
ll-merge.c +2 -2
@@ -391,7 +391,7 @@ enum ll_merge_result ll_merge(mmbuffer_t *result_buf,normalize_file (theirs, path, istate); } -git_check_attr (istate,NULL , path, check); +git_check_attr (istate, 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,NULL , path, check); +git_check_attr (istate, path, check);if (check->items[0 ].value) { marker_size =atoi (check->items[0 ].value);if (marker_size <=0 )
pathspec.c +1 -1
@@ -730,7 +730,7 @@ int match_pathspec_attrs(struct index_state *istate,if (name[namelen]) name = to_free =xmemdupz (name, namelen); -git_check_attr (istate,NULL , name, item->attr_check); +git_check_attr (istate, name, item->attr_check);free (to_free);
t/lib-diff-alternative.sh +26 -5
@@ -112,15 +112,36 @@ EOF STRATEGY=$1 - test_expect_success "$STRATEGY diff from attributes" ' + test_expect_success "setup attributes files for tests with $STRATEGY" ' + git checkout -b master && echo "file* diff=driver" >.gitattributes && - git config diff.driver.algorithm "$STRATEGY" && - test_must_fail git diff --no-index file1 file2 > output && - cat expect && - cat output && + git add file1 file2 .gitattributes && + git commit -m "adding files" && + git checkout -b branchA && + echo "file* diff=driverA" >.gitattributes && + git add .gitattributes && + git commit -m "adding driverA as diff driver" && + git checkout master && + git clone --bare --no-local . bare.git + ' + + test_expect_success "$STRATEGY diff from attributes" ' + test_must_fail git -c diff.driver.algorithm=$STRATEGY diff --no-index file1 file2 > output && test_cmp expect output ' + test_expect_success "diff from attributes with bare repo when branch different than HEAD" ' + git -C bare.git --attr-source=branchA -c diff.driver.algorithm=myers \ + -c diff.driverA.algorithm=$STRATEGY \ + diff HEAD:file1 HEAD:file2 >output && + test_cmp expect output + ' + + test_expect_success "diff from attributes with bare repo with invalid source" ' + test_must_fail git -C bare.git --attr-source=invalid-branch diff \ + HEAD:file1 HEAD:file2 + ' + test_expect_success "$STRATEGY diff from attributes has valid diffstat" ' echo "file* diff=driver" >.gitattributes && git config diff.driver.algorithm "$STRATEGY" &&
t/t0003-attributes.sh +6 -1
@@ -30,8 +30,13 @@ attr_check_quote () { 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 && + + git $git_opts check-attr --source $source test -- "$path" >actual 2>err && + test_cmp expect actual && + test_must_be_empty err && + + git $git_opts --attr-source="$source" check-attr test -- "$path" >actual 2>err && test_cmp expect actual && test_must_be_empty err }
t/t4018-diff-funcname.sh +19 -0
@@ -63,6 +63,25 @@ do test_i18ngrep ! fatal msg && test_i18ngrep ! error msg ' + + test_expect_success "builtin $p pattern compiles on bare repo with --attr-source" ' + test_when_finished "rm -rf bare.git" && + git checkout -B master && + git add . && + echo "*.java diff=notexist" >.gitattributes && + git add .gitattributes && + git commit -am "changing gitattributes" && + git checkout -B branchA && + echo "*.java diff=$p" >.gitattributes && + git add .gitattributes && + git commit -am "changing gitattributes" && + git clone --bare --no-local . bare.git && + git -C bare.git symbolic-ref HEAD refs/heads/master && + test_expect_code 1 git -C bare.git --attr-source=branchA \ + diff --exit-code HEAD:A.java HEAD:B.java 2>msg && + test_i18ngrep ! fatal msg && + test_i18ngrep ! error msg + ' done test_expect_success 'last regexp must not be negated' '
userdiff.c +1 -1
@@ -415,7 +415,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,NULL , path, check); +git_check_attr (istate, path, check);if (ATTR_TRUE (check->items[0 ].value))return &driver_true;
ws.c +1 -1
@@ -79,7 +79,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,NULL , pathname, attr_whitespace_rule); +git_check_attr (istate, pathname, attr_whitespace_rule); value = attr_whitespace_rule->items[0 ].value;if (ATTR_TRUE (value)) {/* true (whitespace) */
Re: [PATCH 1/2] diff: use HEAD for attributes when using bare repository
Hi Junio,
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · John Cai
Ah I see--in that case, what would be a good object to put this state into? Mabe
repo_settings?
Quoted 9 lines
I personally think a global is good enough for a case like this. It is not like the code in the attribute subsystem is structured to work in multiple repositories at the same time in the first place, so in
repo_settings,the_repository, or elsewhere is just as good as having it in a plain old file-scope static, and even after moving it intothe_repositoryor something, the access will initially be done by referring tothe_repositoryglobal (which may not even exist by the time "git --attr-source=<tree>" gets parsed), and not by plumbing a "struct repository *" parameter through the callchain.
Makes sense.
Quoted 8 lines
Another thing to note is that there needs a mechanism to convey the tree object used to read the attributes from down to subprocesses, e.g. exporting the
GIT_ATTR_SOURCEenvironment variable and make Git behave as if it saw the "git --attr-source=<tree>" option when such an environment variable exists, or something. Otherwise, the custom attribute source would only take effect inside built-in commands.
Quoted 5 lines
In any case, here is what I have now, stealing some tests from your patch. I do not think I'll be working on it further at least for now, so feel free to run with it. I am not adding the "in a bare repository, always read from HEAD unless told otherwise", as I do not see a good way to countermand it from a command line.
Thanks for the patch! Looks like you took it most of the way. I'll take a look and make some additions if necessary.
thanks
John
Quoted 435 lines
----- >8 ----- cut here ----- >8 ----- cut here ----- >8 -----
Subject: [PATCH] attr: teach "--attr-source=<tree>" global option to "git"Earlier,
47cfc9bd(attr: add flag--sourceto work with tree-ish, 2023-01-14) taught "git check-attr" the "--source=<tree>" option to allow it to read attribute files from a tree-ish, but did so only for the command. Just like "check-attr" users wanted a way to use attributes from a tree-ish and not from the working tree files, users of other commands (like "git diff") would benefit from the same.Undo most of the UI change the commit made, while keeping the internal logic to read attributes from a given tree-ish. Expose the internal logic via a new "--attr-source=<tree>" command line option given to "git", so that it can be used with any git command that runs internally.
The tests were stolen and adjusted from John Cai's effort where only "git diff" learned the "--attr-source" option to read from a tree-ish.
Signed-off-by Junio C Hamano <[email protected]>--- archive.c | 2 +- attr.c | 34 ++++++++++++++++++++++++++++++++-- attr.h | 13 +++++++++---- builtin/check-attr.c | 17 ++++++++--------- builtin/pack-objects.c | 2 +- convert.c | 2 +- git.c | 3 +++ ll-merge.c | 4 ++-- pathspec.c | 2 +- t/lib-diff-alternative.sh | 31 ++++++++++++++++++++++++++----- t/t0003-attributes.sh | 7 ++++++- t/t4018-diff-funcname.sh | 19 +++++++++++++++++++ userdiff.c | 2 +- ws.c | 2 +- 14 files changed, 111 insertions(+), 29 deletions(-)archive.c +1
-1@@ -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,NULL , path, check); +git_check_attr (istate, path, check);return check; }attr.c +32
-2@@ -1165,11 +1165,40 @@ static void collect_some_attrs(struct index_state *istate,fill (path, pathlen, basename_offset, check->stack, check->all_attrs, rem); } +static const char *default_attr_source_tree_object_name; + +void set_git_attr_source (const char *tree_object_name) +{ + default_attr_source_tree_object_name =xstrdup (tree_object_name); +} + + +static void compute_default_attr_source (struct object_id *attr_source) +{ +if (!default_attr_source_tree_object_name || !is_null_oid (attr_source)) +return ; + +if (get_oid_treeish (default_attr_source_tree_object_name, attr_source)) +die (_ ("bad --attr-source object" )); +} + +static struct object_id *default_attr_source (void ) +{ +static struct object_id attr_source; + +if (is_null_oid (&attr_source)) +compute_default_attr_source (&attr_source); +if (is_null_oid (&attr_source)) +return NULL ; +return &attr_source; +} +void git_check_attr (struct index_state *istate, -const struct object_id *tree_oid,const char *path, +const char *path,struct attr_check *check) {int i; +const struct object_id *tree_oid =default_attr_source ();collect_some_attrs (istate, tree_oid, path, check);@@ -1182,10 +1211,11 @@ void git_check_attr(struct index_state *istate, } } -void git_all_attrs (struct index_state *istate,const struct object_id *tree_oid, +void git_all_attrs (struct index_state *istate,const char *path,struct attr_check *check) {int i; +const struct object_id *tree_oid =default_attr_source ();attr_check_reset (check);collect_some_attrs (istate, tree_oid, path, check);attr.h +9
-4@@ -45,7 +45,7 @@* const char *path; * * setup_check(); -* git_check_attr(&the_index, tree_oid, path, check); +* git_check_attr(&the_index, path, check); * ------------ * * - Act on `.value` member of the result, left in `check->items[]`: @@ -120,7 +120,6 @@#define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024)struct index_state ; -struct object_id ;/** * An attribute is an opaque object that is identified by its name. Pass the @@ -135,6 +134,12 @@ struct git_attr;struct all_attrs_item ;struct attr_stack ; +/* +* The textual object name for the tree-ish used by git_check_attr() +* to read attributes from (instead of from the working tree). +*/ +void set_git_attr_source (const char *); +/* * Given a string, return the gitattribute object that * corresponds to it. @@ -203,14 +208,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 struct object_id *tree_oid,const char *path, +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,const struct object_id *tree_oid, +void git_all_attrs (struct index_state *istate,const char *path,struct attr_check *check);enum git_attr_direction {builtin/check-attr.c +8
-9@@ -58,7 +58,7 @@ static void output_attr(struct attr_check *check, const char *file) }static void check_attr (const char *prefix,struct attr_check *check, -const struct object_id *tree_oid,int collect_all, +int collect_all,const char *file) {@@ -66,9 +66,9 @@ static void check_attr(const char *prefix, struct attr_check *check,prefix_path (prefix, prefix ?strlen (prefix) :0 , file);if (collect_all) { -git_all_attrs (&the_index, tree_oid, full_path, check); +git_all_attrs (&the_index, full_path, check); }else { -git_check_attr (&the_index, tree_oid, full_path, check); +git_check_attr (&the_index, full_path, check); }output_attr (check, file);@@ -76,7 +76,7 @@ static void check_attr(const char *prefix, struct attr_check *check, }static void check_attr_stdin_paths (const char *prefix,struct attr_check *check, -const struct object_id *tree_oid,int collect_all) +int collect_all) {struct strbuf buf =STRBUF_INIT ;struct strbuf unquoted =STRBUF_INIT ;@@ -90,7 +90,7 @@ static void check_attr_stdin_paths(const char *prefix, struct attr_check *check,die ("line is badly quoted" );strbuf_swap (&buf, &unquoted); } -check_attr (prefix, check, tree_oid, collect_all, buf.buf); +check_attr (prefix, check, collect_all, buf.buf);maybe_flush_or_die (stdout,"attribute to stdout" ); }strbuf_release (&buf);@@ -106,7 +106,6 @@ 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;@@ -182,14 +181,14 @@ 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; +set_git_attr_source (source); }if (stdin_paths) -check_attr_stdin_paths (prefix, check, tree_oid, all_attrs); +check_attr_stdin_paths (prefix, check, all_attrs);else {for (i = filei; i < argc; i++) -check_attr (prefix, check, tree_oid, all_attrs, argv[i]); +check_attr (prefix, check, all_attrs, argv[i]);maybe_flush_or_die (stdout,"attribute to stdout" ); }builtin/pack-objects.c +1
-1@@ -1320,7 +1320,7 @@ static int no_try_delta(const char *path)if (!check) check =attr_check_initl ("delta" ,NULL ); -git_check_attr (the_repository->index,NULL , path, check); +git_check_attr (the_repository->index, path, check);if (ATTR_FALSE (check->items[0 ].value))return 1 ;return 0 ;convert.c +1
-1@@ -1308,7 +1308,7 @@ void convert_attrs(struct index_state *istate,git_config (read_convert_config,NULL ); } -git_check_attr (istate,NULL , path, check); +git_check_attr (istate, path, check); ccheck = check->items; ca->crlf_action =git_path_check_crlf (ccheck +4 );if (ca->crlf_action ==CRLF_UNDEFINED )git.c +3
-0@@ -4,6 +4,7 @@#include "help.h" #include "run-command.h" #include "alias.h" +#include "attr.h" #include "shallow.h" #define RUN_SETUP (1<<0)@@ -307,6 +308,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) }else {exit (list_cmds (cmd)); } + }else if (skip_prefix (cmd,"--attr-source=" , &cmd)) { +set_git_attr_source (cmd); }else {fprintf (stderr,_ ("unknown option: %s\n" ), cmd);usage (git_usage_string);ll-merge.c +2
-2@@ -391,7 +391,7 @@ enum ll_merge_result ll_merge(mmbuffer_t *result_buf,normalize_file (theirs, path, istate); } -git_check_attr (istate,NULL , path, check); +git_check_attr (istate, 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,NULL , path, check); +git_check_attr (istate, path, check);if (check->items[0 ].value) { marker_size =atoi (check->items[0 ].value);if (marker_size <=0 )pathspec.c +1
-1@@ -730,7 +730,7 @@ int match_pathspec_attrs(struct index_state *istate,if (name[namelen]) name = to_free =xmemdupz (name, namelen); -git_check_attr (istate,NULL , name, item->attr_check); +git_check_attr (istate, name, item->attr_check);free (to_free);t/lib-diff-alternative.sh +26
-5@@ -112,15 +112,36 @@ EOF STRATEGY=$1 - test_expect_success "$STRATEGY diff from attributes" ' + test_expect_success "setup attributes files for tests with $STRATEGY" ' + git checkout -b master && echo "file* diff=driver" >.gitattributes && - git config diff.driver.algorithm "$STRATEGY" && - test_must_fail git diff --no-index file1 file2 > output && - cat expect && - cat output && + git add file1 file2 .gitattributes && + git commit -m "adding files" && + git checkout -b branchA && + echo "file* diff=driverA" >.gitattributes && + git add .gitattributes && + git commit -m "adding driverA as diff driver" && + git checkout master && + git clone --bare --no-local . bare.git + ' + + test_expect_success "$STRATEGY diff from attributes" ' + test_must_fail git -c diff.driver.algorithm=$STRATEGY diff --no-index file1 file2 > output && test_cmp expect output ' + test_expect_success "diff from attributes with bare repo when branch different than HEAD" ' + git -C bare.git --attr-source=branchA -c diff.driver.algorithm=myers \ + -c diff.driverA.algorithm=$STRATEGY \ + diff HEAD:file1 HEAD:file2 >output && + test_cmp expect output + ' + + test_expect_success "diff from attributes with bare repo with invalid source" ' + test_must_fail git -C bare.git --attr-source=invalid-branch diff \ + HEAD:file1 HEAD:file2 + ' + test_expect_success "$STRATEGY diff from attributes has valid diffstat" ' echo "file* diff=driver" >.gitattributes && git config diff.driver.algorithm "$STRATEGY" &&t/t0003-attributes.sh +6
-1@@ -30,8 +30,13 @@ attr_check_quote () { 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 && + + git $git_opts check-attr --source $source test -- "$path" >actual 2>err && + test_cmp expect actual && + test_must_be_empty err && + + git $git_opts --attr-source="$source" check-attr test -- "$path" >actual 2>err && test_cmp expect actual && test_must_be_empty err }t/t4018-diff-funcname.sh +19
-0@@ -63,6 +63,25 @@ do test_i18ngrep ! fatal msg && test_i18ngrep ! error msg ' + + test_expect_success "builtin $p pattern compiles on bare repo with --attr-source" ' + test_when_finished "rm -rf bare.git" && + git checkout -B master && + git add . && + echo "*.java diff=notexist" >.gitattributes && + git add .gitattributes && + git commit -am "changing gitattributes" && + git checkout -B branchA && + echo "*.java diff=$p" >.gitattributes && + git add .gitattributes && + git commit -am "changing gitattributes" && + git clone --bare --no-local . bare.git && + git -C bare.git symbolic-ref HEAD refs/heads/master && + test_expect_code 1 git -C bare.git --attr-source=branchA \ + diff --exit-code HEAD:A.java HEAD:B.java 2>msg && + test_i18ngrep ! fatal msg && + test_i18ngrep ! error msg + ' done test_expect_success 'last regexp must not be negated' 'userdiff.c +1
-1@@ -415,7 +415,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,NULL , path, check); +git_check_attr (istate, path, check);if (ATTR_TRUE (check->items[0 ].value))return &driver_true;ws.c +1
-1@@ -79,7 +79,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,NULL , pathname, attr_whitespace_rule); +git_check_attr (istate, pathname, attr_whitespace_rule); value = attr_whitespace_rule->items[0 ].value;if (ATTR_TRUE (value)) {/* true (whitespace) */