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