Git fork

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 as part of the main git process.

Additionally, add an environment variable GIT_ATTR_SOURCE that is set
when --attr-source is passed in, so that subprocesses use the same value
for the attributes source tree.

Signed-off-by: John Cai <johncai86@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

John Cai and committed by
Junio C Hamano
44451a2e 48d89b51

+140 -29
+8
Documentation/git.txt
··· 212 nohelpers (exclude helper commands), alias and config 213 (retrieve command list from config variable completion.commands) 214 215 GIT COMMANDS 216 ------------ 217 ··· 685 Setting and exporting this environment variable to any value 686 tells Git not to verify the SSL certificate when fetching or 687 pushing over HTTPS. 688 689 `GIT_ASKPASS`:: 690 If this environment variable is set, then Git commands which need to
··· 212 nohelpers (exclude helper commands), alias and config 213 (retrieve command list from config variable completion.commands) 214 215 + --attr-source=<tree-ish>:: 216 + Read gitattributes from <tree-ish> instead of the worktree. See 217 + linkgit:gitattributes[5]. This is equivalent to setting the 218 + `GIT_ATTR_SOURCE` environment variable. 219 + 220 GIT COMMANDS 221 ------------ 222 ··· 690 Setting and exporting this environment variable to any value 691 tells Git not to verify the SSL certificate when fetching or 692 pushing over HTTPS. 693 + 694 + `GIT_ATTR_SOURCE`:: 695 + Sets the treeish that gitattributes will be read from. 696 697 `GIT_ASKPASS`:: 698 If this environment variable is set, then Git commands which need to
+1 -1
archive.c
··· 128 static struct attr_check *check; 129 if (!check) 130 check = attr_check_initl("export-ignore", "export-subst", NULL); 131 - git_check_attr(istate, NULL, path, check); 132 return check; 133 } 134
··· 128 static struct attr_check *check; 129 if (!check) 130 check = attr_check_initl("export-ignore", "export-subst", NULL); 131 + git_check_attr(istate, path, check); 132 return check; 133 } 134
+35 -2
attr.c
··· 20 #include "object-store.h" 21 #include "setup.h" 22 #include "thread-utils.h" 23 24 const char git_attr__true[] = "(builtin)true"; 25 const char git_attr__false[] = "\0(builtin)false"; ··· 1169 fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem); 1170 } 1171 1172 void git_check_attr(struct index_state *istate, 1173 - const struct object_id *tree_oid, const char *path, 1174 struct attr_check *check) 1175 { 1176 int i; 1177 1178 collect_some_attrs(istate, tree_oid, path, check); 1179 ··· 1186 } 1187 } 1188 1189 - void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid, 1190 const char *path, struct attr_check *check) 1191 { 1192 int i; 1193 1194 attr_check_reset(check); 1195 collect_some_attrs(istate, tree_oid, path, check);
··· 20 #include "object-store.h" 21 #include "setup.h" 22 #include "thread-utils.h" 23 + #include "object-name.h" 24 25 const char git_attr__true[] = "(builtin)true"; 26 const char git_attr__false[] = "\0(builtin)false"; ··· 1170 fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem); 1171 } 1172 1173 + static const char *default_attr_source_tree_object_name; 1174 + 1175 + void set_git_attr_source(const char *tree_object_name) 1176 + { 1177 + default_attr_source_tree_object_name = xstrdup(tree_object_name); 1178 + } 1179 + 1180 + static void compute_default_attr_source(struct object_id *attr_source) 1181 + { 1182 + if (!default_attr_source_tree_object_name) 1183 + default_attr_source_tree_object_name = getenv(GIT_ATTR_SOURCE_ENVIRONMENT); 1184 + 1185 + if (!default_attr_source_tree_object_name || !is_null_oid(attr_source)) 1186 + return; 1187 + 1188 + if (repo_get_oid_treeish(the_repository, default_attr_source_tree_object_name, attr_source)) 1189 + die(_("bad --attr-source or GIT_ATTR_SOURCE")); 1190 + } 1191 + 1192 + static struct object_id *default_attr_source(void) 1193 + { 1194 + static struct object_id attr_source; 1195 + 1196 + if (is_null_oid(&attr_source)) 1197 + compute_default_attr_source(&attr_source); 1198 + if (is_null_oid(&attr_source)) 1199 + return NULL; 1200 + return &attr_source; 1201 + } 1202 + 1203 void git_check_attr(struct index_state *istate, 1204 + const char *path, 1205 struct attr_check *check) 1206 { 1207 int i; 1208 + const struct object_id *tree_oid = default_attr_source(); 1209 1210 collect_some_attrs(istate, tree_oid, path, check); 1211 ··· 1218 } 1219 } 1220 1221 + void git_all_attrs(struct index_state *istate, 1222 const char *path, struct attr_check *check) 1223 { 1224 int i; 1225 + const struct object_id *tree_oid = default_attr_source(); 1226 1227 attr_check_reset(check); 1228 collect_some_attrs(istate, tree_oid, path, check);
+9 -4
attr.h
··· 45 * const char *path; 46 * 47 * setup_check(); 48 - * git_check_attr(&the_index, tree_oid, path, check); 49 * ------------ 50 * 51 * - Act on `.value` member of the result, left in `check->items[]`: ··· 120 #define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024) 121 122 struct index_state; 123 - struct object_id; 124 125 /** 126 * An attribute is an opaque object that is identified by its name. Pass the ··· 136 struct attr_stack; 137 138 /* 139 * Given a string, return the gitattribute object that 140 * corresponds to it. 141 */ ··· 203 const char *git_attr_name(const struct git_attr *); 204 205 void git_check_attr(struct index_state *istate, 206 - const struct object_id *tree_oid, const char *path, 207 struct attr_check *check); 208 209 /* 210 * Retrieve all attributes that apply to the specified path. 211 * check holds the attributes and their values. 212 */ 213 - void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid, 214 const char *path, struct attr_check *check); 215 216 enum git_attr_direction {
··· 45 * const char *path; 46 * 47 * setup_check(); 48 + * git_check_attr(&the_index, path, check); 49 * ------------ 50 * 51 * - Act on `.value` member of the result, left in `check->items[]`: ··· 120 #define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024) 121 122 struct index_state; 123 124 /** 125 * An attribute is an opaque object that is identified by its name. Pass the ··· 135 struct attr_stack; 136 137 /* 138 + * The textual object name for the tree-ish used by git_check_attr() 139 + * to read attributes from (instead of from the working tree). 140 + */ 141 + void set_git_attr_source(const char *); 142 + 143 + /* 144 * Given a string, return the gitattribute object that 145 * corresponds to it. 146 */ ··· 208 const char *git_attr_name(const struct git_attr *); 209 210 void git_check_attr(struct index_state *istate, 211 + const char *path, 212 struct attr_check *check); 213 214 /* 215 * Retrieve all attributes that apply to the specified path. 216 * check holds the attributes and their values. 217 */ 218 + void git_all_attrs(struct index_state *istate, 219 const char *path, struct attr_check *check); 220 221 enum git_attr_direction {
+8 -9
builtin/check-attr.c
··· 63 } 64 65 static void check_attr(const char *prefix, struct attr_check *check, 66 - const struct object_id *tree_oid, int collect_all, 67 const char *file) 68 69 { ··· 71 prefix_path(prefix, prefix ? strlen(prefix) : 0, file); 72 73 if (collect_all) { 74 - git_all_attrs(&the_index, tree_oid, full_path, check); 75 } else { 76 - git_check_attr(&the_index, tree_oid, full_path, check); 77 } 78 output_attr(check, file); 79 ··· 81 } 82 83 static void check_attr_stdin_paths(const char *prefix, struct attr_check *check, 84 - const struct object_id *tree_oid, int collect_all) 85 { 86 struct strbuf buf = STRBUF_INIT; 87 struct strbuf unquoted = STRBUF_INIT; ··· 95 die("line is badly quoted"); 96 strbuf_swap(&buf, &unquoted); 97 } 98 - check_attr(prefix, check, tree_oid, collect_all, buf.buf); 99 maybe_flush_or_die(stdout, "attribute to stdout"); 100 } 101 strbuf_release(&buf); ··· 111 int cmd_check_attr(int argc, const char **argv, const char *prefix) 112 { 113 struct attr_check *check; 114 - struct object_id *tree_oid = NULL; 115 struct object_id initialized_oid; 116 int cnt, i, doubledash, filei; 117 ··· 187 if (source) { 188 if (repo_get_oid_tree(the_repository, source, &initialized_oid)) 189 die("%s: not a valid tree-ish source", source); 190 - tree_oid = &initialized_oid; 191 } 192 193 if (stdin_paths) 194 - check_attr_stdin_paths(prefix, check, tree_oid, all_attrs); 195 else { 196 for (i = filei; i < argc; i++) 197 - check_attr(prefix, check, tree_oid, all_attrs, argv[i]); 198 maybe_flush_or_die(stdout, "attribute to stdout"); 199 } 200
··· 63 } 64 65 static void check_attr(const char *prefix, struct attr_check *check, 66 + int collect_all, 67 const char *file) 68 69 { ··· 71 prefix_path(prefix, prefix ? strlen(prefix) : 0, file); 72 73 if (collect_all) { 74 + git_all_attrs(&the_index, full_path, check); 75 } else { 76 + git_check_attr(&the_index, full_path, check); 77 } 78 output_attr(check, file); 79 ··· 81 } 82 83 static void check_attr_stdin_paths(const char *prefix, struct attr_check *check, 84 + int collect_all) 85 { 86 struct strbuf buf = STRBUF_INIT; 87 struct strbuf unquoted = STRBUF_INIT; ··· 95 die("line is badly quoted"); 96 strbuf_swap(&buf, &unquoted); 97 } 98 + check_attr(prefix, check, collect_all, buf.buf); 99 maybe_flush_or_die(stdout, "attribute to stdout"); 100 } 101 strbuf_release(&buf); ··· 111 int cmd_check_attr(int argc, const char **argv, const char *prefix) 112 { 113 struct attr_check *check; 114 struct object_id initialized_oid; 115 int cnt, i, doubledash, filei; 116 ··· 186 if (source) { 187 if (repo_get_oid_tree(the_repository, source, &initialized_oid)) 188 die("%s: not a valid tree-ish source", source); 189 + set_git_attr_source(source); 190 } 191 192 if (stdin_paths) 193 + check_attr_stdin_paths(prefix, check, all_attrs); 194 else { 195 for (i = filei; i < argc; i++) 196 + check_attr(prefix, check, all_attrs, argv[i]); 197 maybe_flush_or_die(stdout, "attribute to stdout"); 198 } 199
+1 -1
builtin/pack-objects.c
··· 1331 1332 if (!check) 1333 check = attr_check_initl("delta", NULL); 1334 - git_check_attr(the_repository->index, NULL, path, check); 1335 if (ATTR_FALSE(check->items[0].value)) 1336 return 1; 1337 return 0;
··· 1331 1332 if (!check) 1333 check = attr_check_initl("delta", NULL); 1334 + git_check_attr(the_repository->index, path, check); 1335 if (ATTR_FALSE(check->items[0].value)) 1336 return 1; 1337 return 0;
+1 -1
convert.c
··· 1314 git_config(read_convert_config, NULL); 1315 } 1316 1317 - git_check_attr(istate, NULL, path, check); 1318 ccheck = check->items; 1319 ca->crlf_action = git_path_check_crlf(ccheck + 4); 1320 if (ca->crlf_action == CRLF_UNDEFINED)
··· 1314 git_config(read_convert_config, NULL); 1315 } 1316 1317 + git_check_attr(istate, path, check); 1318 ccheck = check->items; 1319 ca->crlf_action = git_path_check_crlf(ccheck + 4); 1320 if (ca->crlf_action == CRLF_UNDEFINED)
+1
environment.h
··· 55 #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH" 56 #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS" 57 #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR" 58 59 /* 60 * Environment variable used in handshaking the wire protocol.
··· 55 #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH" 56 #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS" 57 #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR" 58 + #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE" 59 60 /* 61 * Environment variable used in handshaking the wire protocol.
+16
git.c
··· 9 #include "alias.h" 10 #include "replace-object.h" 11 #include "setup.h" 12 #include "shallow.h" 13 #include "trace.h" 14 #include "trace2.h" ··· 314 } else { 315 exit(list_cmds(cmd)); 316 } 317 } else { 318 fprintf(stderr, _("unknown option: %s\n"), cmd); 319 usage(git_usage_string);
··· 9 #include "alias.h" 10 #include "replace-object.h" 11 #include "setup.h" 12 + #include "attr.h" 13 #include "shallow.h" 14 #include "trace.h" 15 #include "trace2.h" ··· 315 } else { 316 exit(list_cmds(cmd)); 317 } 318 + } else if (!strcmp(cmd, "--attr-source")) { 319 + if (*argc < 2) { 320 + fprintf(stderr, _("no attribute source given for --attr-source\n" )); 321 + usage(git_usage_string); 322 + } 323 + setenv(GIT_ATTR_SOURCE_ENVIRONMENT, (*argv)[1], 1); 324 + if (envchanged) 325 + *envchanged = 1; 326 + (*argv)++; 327 + (*argc)--; 328 + } else if (skip_prefix(cmd, "--attr-source=", &cmd)) { 329 + set_git_attr_source(cmd); 330 + setenv(GIT_ATTR_SOURCE_ENVIRONMENT, cmd, 1); 331 + if (envchanged) 332 + *envchanged = 1; 333 } else { 334 fprintf(stderr, _("unknown option: %s\n"), cmd); 335 usage(git_usage_string);
+2 -2
ll-merge.c
··· 393 normalize_file(theirs, path, istate); 394 } 395 396 - git_check_attr(istate, NULL, path, check); 397 ll_driver_name = check->items[0].value; 398 if (check->items[1].value) { 399 marker_size = atoi(check->items[1].value); ··· 421 422 if (!check) 423 check = attr_check_initl("conflict-marker-size", NULL); 424 - git_check_attr(istate, NULL, path, check); 425 if (check->items[0].value) { 426 marker_size = atoi(check->items[0].value); 427 if (marker_size <= 0)
··· 393 normalize_file(theirs, path, istate); 394 } 395 396 + git_check_attr(istate, path, check); 397 ll_driver_name = check->items[0].value; 398 if (check->items[1].value) { 399 marker_size = atoi(check->items[1].value); ··· 421 422 if (!check) 423 check = attr_check_initl("conflict-marker-size", NULL); 424 + git_check_attr(istate, path, check); 425 if (check->items[0].value) { 426 marker_size = atoi(check->items[0].value); 427 if (marker_size <= 0)
+1 -1
pathspec.c
··· 734 if (name[namelen]) 735 name = to_free = xmemdupz(name, namelen); 736 737 - git_check_attr(istate, NULL, name, item->attr_check); 738 739 free(to_free); 740
··· 734 if (name[namelen]) 735 name = to_free = xmemdupz(name, namelen); 736 737 + git_check_attr(istate, name, item->attr_check); 738 739 free(to_free); 740
+26 -5
t/lib-diff-alternative.sh
··· 112 113 STRATEGY=$1 114 115 test_expect_success "$STRATEGY diff from attributes" ' 116 - echo "file* diff=driver" >.gitattributes && 117 - git config diff.driver.algorithm "$STRATEGY" && 118 - test_must_fail git diff --no-index file1 file2 > output && 119 - cat expect && 120 - cat output && 121 test_cmp expect output 122 ' 123 124 test_expect_success "$STRATEGY diff from attributes has valid diffstat" '
··· 112 113 STRATEGY=$1 114 115 + test_expect_success "setup attributes files for tests with $STRATEGY" ' 116 + git checkout -b master && 117 + echo "file* diff=driver" >.gitattributes && 118 + git add file1 file2 .gitattributes && 119 + git commit -m "adding files" && 120 + git checkout -b branchA && 121 + echo "file* diff=driverA" >.gitattributes && 122 + git add .gitattributes && 123 + git commit -m "adding driverA as diff driver" && 124 + git checkout master && 125 + git clone --bare --no-local . bare.git 126 + ' 127 + 128 test_expect_success "$STRATEGY diff from attributes" ' 129 + test_must_fail git -c diff.driver.algorithm=$STRATEGY diff --no-index file1 file2 > output && 130 + test_cmp expect output 131 + ' 132 + 133 + test_expect_success "diff from attributes with bare repo with source" ' 134 + git -C bare.git --attr-source=branchA -c diff.driver.algorithm=myers \ 135 + -c diff.driverA.algorithm=$STRATEGY \ 136 + diff HEAD:file1 HEAD:file2 >output && 137 test_cmp expect output 138 + ' 139 + 140 + test_expect_success "diff from attributes with bare repo with invalid source" ' 141 + test_must_fail git -C bare.git --attr-source=invalid-branch diff \ 142 + HEAD:file1 HEAD:file2 143 ' 144 145 test_expect_success "$STRATEGY diff from attributes has valid diffstat" '
+10 -1
t/t0003-attributes.sh
··· 30 attr_check_source () { 31 path="$1" expect="$2" source="$3" git_opts="$4" && 32 33 git $git_opts check-attr --source $source test -- "$path" >actual 2>err && 34 - echo "$path: test: $expect" >expect && 35 test_cmp expect actual && 36 test_must_be_empty err 37 }
··· 30 attr_check_source () { 31 path="$1" expect="$2" source="$3" git_opts="$4" && 32 33 + echo "$path: test: $expect" >expect && 34 + 35 git $git_opts check-attr --source $source test -- "$path" >actual 2>err && 36 + test_cmp expect actual && 37 + test_must_be_empty err && 38 + 39 + git $git_opts --attr-source="$source" check-attr test -- "$path" >actual 2>err && 40 + test_cmp expect actual && 41 + test_must_be_empty err 42 + 43 + GIT_ATTR_SOURCE="$source" git $git_opts check-attr test -- "$path" >actual 2>err && 44 test_cmp expect actual && 45 test_must_be_empty err 46 }
+19
t/t4018-diff-funcname.sh
··· 63 test_i18ngrep ! fatal msg && 64 test_i18ngrep ! error msg 65 ' 66 done 67 68 test_expect_success 'last regexp must not be negated' '
··· 63 test_i18ngrep ! fatal msg && 64 test_i18ngrep ! error msg 65 ' 66 + 67 + test_expect_success "builtin $p pattern compiles on bare repo with --attr-source" ' 68 + test_when_finished "rm -rf bare.git" && 69 + git checkout -B master && 70 + git add . && 71 + echo "*.java diff=notexist" >.gitattributes && 72 + git add .gitattributes && 73 + git commit -am "changing gitattributes" && 74 + git checkout -B branchA && 75 + echo "*.java diff=$p" >.gitattributes && 76 + git add .gitattributes && 77 + git commit -am "changing gitattributes" && 78 + git clone --bare --no-local . bare.git && 79 + git -C bare.git symbolic-ref HEAD refs/heads/master && 80 + test_expect_code 1 git -C bare.git --attr-source=branchA \ 81 + diff --exit-code HEAD:A.java HEAD:B.java 2>msg && 82 + test_i18ngrep ! fatal msg && 83 + test_i18ngrep ! error msg 84 + ' 85 done 86 87 test_expect_success 'last regexp must not be negated' '
+1 -1
userdiff.c
··· 444 check = attr_check_initl("diff", NULL); 445 if (!path) 446 return NULL; 447 - git_check_attr(istate, NULL, path, check); 448 449 if (ATTR_TRUE(check->items[0].value)) 450 return &driver_true;
··· 444 check = attr_check_initl("diff", NULL); 445 if (!path) 446 return NULL; 447 + git_check_attr(istate, path, check); 448 449 if (ATTR_TRUE(check->items[0].value)) 450 return &driver_true;
+1 -1
ws.c
··· 79 if (!attr_whitespace_rule) 80 attr_whitespace_rule = attr_check_initl("whitespace", NULL); 81 82 - git_check_attr(istate, NULL, pathname, attr_whitespace_rule); 83 value = attr_whitespace_rule->items[0].value; 84 if (ATTR_TRUE(value)) { 85 /* true (whitespace) */
··· 79 if (!attr_whitespace_rule) 80 attr_whitespace_rule = attr_check_initl("whitespace", NULL); 81 82 + git_check_attr(istate, pathname, attr_whitespace_rule); 83 value = attr_whitespace_rule->items[0].value; 84 if (ATTR_TRUE(value)) { 85 /* true (whitespace) */