Git fork

Merge branch 'fixes/2.45.1/2.44' into maint-2.44

* fixes/2.45.1/2.44:
Revert "fsck: warn about symlink pointing inside a gitdir"
Revert "Add a helper function to compare file contents"
clone: drop the protections where hooks aren't run
tests: verify that `clone -c core.hooksPath=/dev/null` works again
Revert "core.hooksPath: add some protection while cloning"
init: use the correct path of the templates directory again
hook: plug a new memory leak
ci: stop installing "gcc-13" for osx-gcc
ci: avoid bare "gcc" for osx-gcc job
ci: drop mention of BREW_INSTALL_PACKAGES variable
send-email: avoid creating more than one Term::ReadLine object
send-email: drop FakeTerm hack

+11 -366
+1 -2
.github/workflows/main.yml
··· 278 278 cc: clang 279 279 pool: macos-13 280 280 - jobname: osx-gcc 281 - cc: gcc 282 - cc_package: gcc-13 281 + cc: gcc-13 283 282 pool: macos-13 284 283 - jobname: linux-gcc-default 285 284 cc: gcc
-12
Documentation/fsck-msgids.txt
··· 164 164 `nullSha1`:: 165 165 (WARN) Tree contains entries pointing to a null sha1. 166 166 167 - `symlinkPointsToGitDir`:: 168 - (WARN) Symbolic link points inside a gitdir. 169 - 170 - `symlinkTargetBlob`:: 171 - (ERROR) A non-blob found instead of a symbolic link's target. 172 - 173 - `symlinkTargetLength`:: 174 - (WARN) Symbolic link target longer than maximum path length. 175 - 176 - `symlinkTargetMissing`:: 177 - (ERROR) Unable to read symbolic link target's blob. 178 - 179 167 `treeNotSorted`:: 180 168 (ERROR) A tree is not properly sorted. 181 169
+2 -11
builtin/clone.c
··· 965 965 int hash_algo; 966 966 unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; 967 967 const int do_not_override_repo_unix_permissions = -1; 968 - const char *template_dir; 969 - char *template_dir_dup = NULL; 970 968 971 969 struct transport_ls_refs_options transport_ls_refs_options = 972 970 TRANSPORT_LS_REFS_OPTIONS_INIT; ··· 985 983 if (argc == 0) 986 984 usage_msg_opt(_("You must specify a repository to clone."), 987 985 builtin_clone_usage, builtin_clone_options); 988 - 989 - xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */); 990 - template_dir = get_template_dir(option_template); 991 - if (*template_dir && !is_absolute_path(template_dir)) 992 - template_dir = template_dir_dup = 993 - absolute_pathdup(template_dir); 994 - xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1); 995 986 996 987 if (option_depth || option_since || option_not.nr) 997 988 deepen = 1; ··· 1154 1145 * repository, and reference backends may persist that information into 1155 1146 * their on-disk data structures. 1156 1147 */ 1157 - init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN, 1148 + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, 1158 1149 ref_storage_format, NULL, 1159 1150 do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); 1160 1151 ··· 1498 1489 free(dir); 1499 1490 free(path); 1500 1491 free(repo_to_free); 1501 - free(template_dir_dup); 1492 + UNLEAK(repo); 1502 1493 junk_mode = JUNK_LEAVE_ALL; 1503 1494 1504 1495 transport_ls_refs_options_release(&transport_ls_refs_options);
-2
ci/install-dependencies.sh
··· 34 34 export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 35 35 # Uncomment this if you want to run perf tests: 36 36 # brew install gnu-time 37 - test -z "$BREW_INSTALL_PACKAGES" || 38 - brew install $BREW_INSTALL_PACKAGES 39 37 brew link --force gettext 40 38 41 39 mkdir -p "$P4_PATH"
+1 -12
config.c
··· 1411 1411 if (!strcmp(var, "core.attributesfile")) 1412 1412 return git_config_pathname(&git_attributes_file, var, value); 1413 1413 1414 - if (!strcmp(var, "core.hookspath")) { 1415 - if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL && 1416 - git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0)) 1417 - die(_("active `core.hooksPath` found in the local " 1418 - "repository config:\n\t%s\nFor security " 1419 - "reasons, this is disallowed by default.\nIf " 1420 - "this is intentional and the hook should " 1421 - "actually be run, please\nrun the command " 1422 - "again with " 1423 - "`GIT_CLONE_PROTECTION_ACTIVE=false`"), 1424 - value); 1414 + if (!strcmp(var, "core.hookspath")) 1425 1415 return git_config_pathname(&git_hooks_path, var, value); 1426 - } 1427 1416 1428 1417 if (!strcmp(var, "core.bare")) { 1429 1418 is_bare_repository_cfg = git_config_bool(var, value);
-58
copy.c
··· 70 70 return copy_times(dst, src); 71 71 return status; 72 72 } 73 - 74 - static int do_symlinks_match(const char *path1, const char *path2) 75 - { 76 - struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT; 77 - int ret = 0; 78 - 79 - if (!strbuf_readlink(&buf1, path1, 0) && 80 - !strbuf_readlink(&buf2, path2, 0)) 81 - ret = !strcmp(buf1.buf, buf2.buf); 82 - 83 - strbuf_release(&buf1); 84 - strbuf_release(&buf2); 85 - return ret; 86 - } 87 - 88 - int do_files_match(const char *path1, const char *path2) 89 - { 90 - struct stat st1, st2; 91 - int fd1 = -1, fd2 = -1, ret = 1; 92 - char buf1[8192], buf2[8192]; 93 - 94 - if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 || 95 - fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) { 96 - if (fd1 < 0 && errno == ELOOP) 97 - /* maybe this is a symbolic link? */ 98 - return do_symlinks_match(path1, path2); 99 - ret = 0; 100 - } else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 || 101 - fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) { 102 - ret = 0; 103 - } 104 - 105 - if (ret) 106 - /* to match, neither must be executable, or both */ 107 - ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111); 108 - 109 - if (ret) 110 - ret = st1.st_size == st2.st_size; 111 - 112 - while (ret) { 113 - ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1)); 114 - ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2)); 115 - 116 - if (len1 < 0 || len2 < 0 || len1 != len2) 117 - ret = 0; /* read error or different file size */ 118 - else if (!len1) /* len2 is also 0; hit EOF on both */ 119 - break; /* ret is still true */ 120 - else 121 - ret = !memcmp(buf1, buf2, len1); 122 - } 123 - 124 - if (fd1 >= 0) 125 - close(fd1); 126 - if (fd2 >= 0) 127 - close(fd2); 128 - 129 - return ret; 130 - }
-14
copy.h
··· 7 7 int copy_file(const char *dst, const char *src, int mode); 8 8 int copy_file_with_time(const char *dst, const char *src, int mode); 9 9 10 - /* 11 - * Compare the file mode and contents of two given files. 12 - * 13 - * If both files are actually symbolic links, the function returns 1 if the link 14 - * targets are identical or 0 if they are not. 15 - * 16 - * If any of the two files cannot be accessed or in case of read failures, this 17 - * function returns 0. 18 - * 19 - * If the file modes and contents are identical, the function returns 1, 20 - * otherwise it returns 0. 21 - */ 22 - int do_files_match(const char *path1, const char *path2); 23 - 24 10 #endif /* COPY_H */
-56
fsck.c
··· 656 656 retval += report(options, tree_oid, OBJ_TREE, 657 657 FSCK_MSG_MAILMAP_SYMLINK, 658 658 ".mailmap is a symlink"); 659 - oidset_insert(&options->symlink_targets_found, 660 - entry_oid); 661 659 } 662 660 663 661 if ((backslash = strchr(name, '\\'))) { ··· 1166 1164 } 1167 1165 } 1168 1166 1169 - if (oidset_contains(&options->symlink_targets_found, oid)) { 1170 - const char *ptr = buf; 1171 - const struct object_id *reported = NULL; 1172 - 1173 - oidset_insert(&options->symlink_targets_done, oid); 1174 - 1175 - if (!buf || size > PATH_MAX) { 1176 - /* 1177 - * A missing buffer here is a sign that the caller found the 1178 - * blob too gigantic to load into memory. Let's just consider 1179 - * that an error. 1180 - */ 1181 - return report(options, oid, OBJ_BLOB, 1182 - FSCK_MSG_SYMLINK_TARGET_LENGTH, 1183 - "symlink target too long"); 1184 - } 1185 - 1186 - while (!reported && ptr) { 1187 - const char *p = ptr; 1188 - char c, *slash = strchrnul(ptr, '/'); 1189 - char *backslash = memchr(ptr, '\\', slash - ptr); 1190 - 1191 - c = *slash; 1192 - *slash = '\0'; 1193 - 1194 - while (!reported && backslash) { 1195 - *backslash = '\0'; 1196 - if (is_ntfs_dotgit(p)) 1197 - ret |= report(options, reported = oid, OBJ_BLOB, 1198 - FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR, 1199 - "symlink target points to git dir"); 1200 - *backslash = '\\'; 1201 - p = backslash + 1; 1202 - backslash = memchr(p, '\\', slash - p); 1203 - } 1204 - if (!reported && is_ntfs_dotgit(p)) 1205 - ret |= report(options, reported = oid, OBJ_BLOB, 1206 - FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR, 1207 - "symlink target points to git dir"); 1208 - 1209 - if (!reported && is_hfs_dotgit(ptr)) 1210 - ret |= report(options, reported = oid, OBJ_BLOB, 1211 - FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR, 1212 - "symlink target points to git dir"); 1213 - 1214 - *slash = c; 1215 - ptr = c ? slash + 1 : NULL; 1216 - } 1217 - } 1218 - 1219 1167 return ret; 1220 1168 } 1221 1169 ··· 1313 1261 ret |= fsck_blobs(&options->gitattributes_found, &options->gitattributes_done, 1314 1262 FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB, 1315 1263 options, ".gitattributes"); 1316 - 1317 - ret |= fsck_blobs(&options->symlink_targets_found, &options->symlink_targets_done, 1318 - FSCK_MSG_SYMLINK_TARGET_MISSING, FSCK_MSG_SYMLINK_TARGET_BLOB, 1319 - options, "<symlink-target>"); 1320 1264 1321 1265 return ret; 1322 1266 }
-12
fsck.h
··· 64 64 FUNC(GITATTRIBUTES_LARGE, ERROR) \ 65 65 FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \ 66 66 FUNC(GITATTRIBUTES_BLOB, ERROR) \ 67 - FUNC(SYMLINK_TARGET_MISSING, ERROR) \ 68 - FUNC(SYMLINK_TARGET_BLOB, ERROR) \ 69 67 /* warnings */ \ 70 68 FUNC(EMPTY_NAME, WARN) \ 71 69 FUNC(FULL_PATHNAME, WARN) \ ··· 76 74 FUNC(ZERO_PADDED_FILEMODE, WARN) \ 77 75 FUNC(NUL_IN_COMMIT, WARN) \ 78 76 FUNC(LARGE_PATHNAME, WARN) \ 79 - FUNC(SYMLINK_TARGET_LENGTH, WARN) \ 80 - FUNC(SYMLINK_POINTS_TO_GIT_DIR, WARN) \ 81 77 /* infos (reported as warnings, but ignored by default) */ \ 82 78 FUNC(BAD_FILEMODE, INFO) \ 83 79 FUNC(GITMODULES_PARSE, INFO) \ ··· 145 141 struct oidset gitmodules_done; 146 142 struct oidset gitattributes_found; 147 143 struct oidset gitattributes_done; 148 - struct oidset symlink_targets_found; 149 - struct oidset symlink_targets_done; 150 144 kh_oid_map_t *object_names; 151 145 }; 152 146 ··· 156 150 .gitmodules_done = OIDSET_INIT, \ 157 151 .gitattributes_found = OIDSET_INIT, \ 158 152 .gitattributes_done = OIDSET_INIT, \ 159 - .symlink_targets_found = OIDSET_INIT, \ 160 - .symlink_targets_done = OIDSET_INIT, \ 161 153 .error_func = fsck_error_function \ 162 154 } 163 155 #define FSCK_OPTIONS_STRICT { \ ··· 166 158 .gitmodules_done = OIDSET_INIT, \ 167 159 .gitattributes_found = OIDSET_INIT, \ 168 160 .gitattributes_done = OIDSET_INIT, \ 169 - .symlink_targets_found = OIDSET_INIT, \ 170 - .symlink_targets_done = OIDSET_INIT, \ 171 161 .error_func = fsck_error_function, \ 172 162 } 173 163 #define FSCK_OPTIONS_MISSING_GITMODULES { \ ··· 176 166 .gitmodules_done = OIDSET_INIT, \ 177 167 .gitattributes_found = OIDSET_INIT, \ 178 168 .gitattributes_done = OIDSET_INIT, \ 179 - .symlink_targets_found = OIDSET_INIT, \ 180 - .symlink_targets_done = OIDSET_INIT, \ 181 169 .error_func = fsck_error_cb_print_missing_gitmodules, \ 182 170 } 183 171
-33
hook.c
··· 9 9 #include "strbuf.h" 10 10 #include "environment.h" 11 11 #include "setup.h" 12 - #include "copy.h" 13 - 14 - static int identical_to_template_hook(const char *name, const char *path) 15 - { 16 - const char *env = getenv("GIT_CLONE_TEMPLATE_DIR"); 17 - const char *template_dir = get_template_dir(env && *env ? env : NULL); 18 - struct strbuf template_path = STRBUF_INIT; 19 - int found_template_hook, ret; 20 - 21 - strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name); 22 - found_template_hook = access(template_path.buf, X_OK) >= 0; 23 - #ifdef STRIP_EXTENSION 24 - if (!found_template_hook) { 25 - strbuf_addstr(&template_path, STRIP_EXTENSION); 26 - found_template_hook = access(template_path.buf, X_OK) >= 0; 27 - } 28 - #endif 29 - if (!found_template_hook) 30 - return 0; 31 - 32 - ret = do_files_match(template_path.buf, path); 33 - 34 - strbuf_release(&template_path); 35 - return ret; 36 - } 37 12 38 13 const char *find_hook(const char *name) 39 14 { ··· 70 45 } 71 46 return NULL; 72 47 } 73 - if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) && 74 - !identical_to_template_hook(name, path.buf)) 75 - die(_("active `%s` hook found during `git clone`:\n\t%s\n" 76 - "For security reasons, this is disallowed by default.\n" 77 - "If this is intentional and the hook should actually " 78 - "be run, please\nrun the command again with " 79 - "`GIT_CLONE_PROTECTION_ACTIVE=false`"), 80 - name, path.buf); 81 48 return path.buf; 82 49 } 83 50
-10
t/helper/test-path-utils.c
··· 501 501 return !!res; 502 502 } 503 503 504 - if (argc == 4 && !strcmp(argv[1], "do_files_match")) { 505 - int ret = do_files_match(argv[2], argv[3]); 506 - 507 - if (ret) 508 - printf("equal\n"); 509 - else 510 - printf("different\n"); 511 - return !ret; 512 - } 513 - 514 504 fprintf(stderr, "%s: unknown function name: %s\n", argv[0], 515 505 argv[1] ? argv[1] : "(there was none)"); 516 506 return 1;
-41
t/t0060-path-utils.sh
··· 610 610 test_cmp expect actual 611 611 ' 612 612 613 - test_expect_success 'do_files_match()' ' 614 - test_seq 0 10 >0-10.txt && 615 - test_seq -1 10 >-1-10.txt && 616 - test_seq 1 10 >1-10.txt && 617 - test_seq 1 9 >1-9.txt && 618 - test_seq 0 8 >0-8.txt && 619 - 620 - test-tool path-utils do_files_match 0-10.txt 0-10.txt >out && 621 - 622 - assert_fails() { 623 - test_must_fail \ 624 - test-tool path-utils do_files_match "$1" "$2" >out && 625 - grep different out 626 - } && 627 - 628 - assert_fails 0-8.txt 1-9.txt && 629 - assert_fails -1-10.txt 0-10.txt && 630 - assert_fails 1-10.txt 1-9.txt && 631 - assert_fails 1-10.txt .git && 632 - assert_fails does-not-exist 1-10.txt && 633 - 634 - if test_have_prereq FILEMODE 635 - then 636 - cp 0-10.txt 0-10.x && 637 - chmod a+x 0-10.x && 638 - assert_fails 0-10.txt 0-10.x 639 - fi && 640 - 641 - if test_have_prereq SYMLINKS 642 - then 643 - ln -sf 0-10.txt symlink && 644 - ln -s 0-10.txt another-symlink && 645 - ln -s over-the-ocean yet-another-symlink && 646 - ln -s "$PWD/0-10.txt" absolute-symlink && 647 - assert_fails 0-10.txt symlink && 648 - test-tool path-utils do_files_match symlink another-symlink && 649 - assert_fails symlink yet-another-symlink && 650 - assert_fails symlink absolute-symlink 651 - fi 652 - ' 653 - 654 613 test_done
+7
t/t1350-config-hooks-path.sh
··· 41 41 test .git/custom-hooks/abc = "$(cat actual)" 42 42 ' 43 43 44 + test_expect_success 'core.hooksPath=/dev/null' ' 45 + git clone -c core.hooksPath=/dev/null . no-templates && 46 + value="$(git -C no-templates config --local core.hooksPath)" && 47 + # The Bash used by Git for Windows rewrites `/dev/null` to `nul` 48 + { test /dev/null = "$value" || test nul = "$value"; } 49 + ' 50 + 44 51 test_done
-37
t/t1450-fsck.sh
··· 1060 1060 test_cmp expect actual 1061 1061 ' 1062 1062 1063 - test_expect_success 'fsck warning on symlink target with excessive length' ' 1064 - symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) && 1065 - test_when_finished "remove_object $symlink_target" && 1066 - tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) && 1067 - test_when_finished "remove_object $tree" && 1068 - cat >expected <<-EOF && 1069 - warning in blob $symlink_target: symlinkTargetLength: symlink target too long 1070 - EOF 1071 - git fsck --no-dangling >actual 2>&1 && 1072 - test_cmp expected actual 1073 - ' 1074 - 1075 - test_expect_success 'fsck warning on symlink target pointing inside git dir' ' 1076 - gitdir=$(printf ".git" | git hash-object -w --stdin) && 1077 - ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) && 1078 - hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) && 1079 - inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) && 1080 - benign_target=$(printf "legit/config" | git hash-object -w --stdin) && 1081 - tree=$(printf "120000 blob %s\t%s\n" \ 1082 - $benign_target benign_target \ 1083 - $gitdir gitdir \ 1084 - $hfs_gitdir hfs_gitdir \ 1085 - $inside_gitdir inside_gitdir \ 1086 - $ntfs_gitdir ntfs_gitdir | 1087 - git mktree) && 1088 - for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree 1089 - do 1090 - test_when_finished "remove_object $o" || return 1 1091 - done && 1092 - printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \ 1093 - $gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir | 1094 - sort >expected && 1095 - git fsck --no-dangling >actual 2>&1 && 1096 - sort actual >actual.sorted && 1097 - test_cmp expected actual.sorted 1098 - ' 1099 - 1100 1063 test_done
-15
t/t1800-hook.sh
··· 185 185 test_cmp expect actual 186 186 ' 187 187 188 - test_expect_success 'clone protections' ' 189 - test_config core.hooksPath "$(pwd)/my-hooks" && 190 - mkdir -p my-hooks && 191 - write_script my-hooks/test-hook <<-\EOF && 192 - echo Hook ran $1 193 - EOF 194 - 195 - git hook run test-hook 2>err && 196 - test_grep "Hook ran" err && 197 - test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \ 198 - git hook run test-hook 2>err && 199 - test_grep "active .core.hooksPath" err && 200 - test_grep ! "Hook ran" err 201 - ' 202 - 203 188 test_done
-51
t/t5601-clone.sh
··· 788 788 git clone --filter=blob:limit=0 "file://$(pwd)/server" client 789 789 ' 790 790 791 - test_expect_success 'clone with init.templatedir runs hooks' ' 792 - git init tmpl/hooks && 793 - write_script tmpl/hooks/post-checkout <<-EOF && 794 - echo HOOK-RUN >&2 795 - echo I was here >hook.run 796 - EOF 797 - git -C tmpl/hooks add . && 798 - test_tick && 799 - git -C tmpl/hooks commit -m post-checkout && 800 - 801 - test_when_finished "git config --global --unset init.templateDir || :" && 802 - test_when_finished "git config --unset init.templateDir || :" && 803 - ( 804 - sane_unset GIT_TEMPLATE_DIR && 805 - NO_SET_GIT_TEMPLATE_DIR=t && 806 - export NO_SET_GIT_TEMPLATE_DIR && 807 - 808 - git -c core.hooksPath="$(pwd)/tmpl/hooks" \ 809 - clone tmpl/hooks hook-run-hookspath 2>err && 810 - test_grep ! "active .* hook found" err && 811 - test_path_is_file hook-run-hookspath/hook.run && 812 - 813 - git -c init.templateDir="$(pwd)/tmpl" \ 814 - clone tmpl/hooks hook-run-config 2>err && 815 - test_grep ! "active .* hook found" err && 816 - test_path_is_file hook-run-config/hook.run && 817 - 818 - git clone --template=tmpl tmpl/hooks hook-run-option 2>err && 819 - test_grep ! "active .* hook found" err && 820 - test_path_is_file hook-run-option/hook.run && 821 - 822 - git config --global init.templateDir "$(pwd)/tmpl" && 823 - git clone tmpl/hooks hook-run-global-config 2>err && 824 - git config --global --unset init.templateDir && 825 - test_grep ! "active .* hook found" err && 826 - test_path_is_file hook-run-global-config/hook.run && 827 - 828 - # clone ignores local `init.templateDir`; need to create 829 - # a new repository because we deleted `.git/` in the 830 - # `setup` test case above 831 - git init local-clone && 832 - cd local-clone && 833 - 834 - git config init.templateDir "$(pwd)/../tmpl" && 835 - git clone ../tmpl/hooks hook-run-local-config 2>err && 836 - git config --unset init.templateDir && 837 - test_grep ! "active .* hook found" err && 838 - test_path_is_missing hook-run-local-config/hook.run 839 - ) 840 - ' 841 - 842 791 . "$TEST_DIRECTORY"/lib-httpd.sh 843 792 start_httpd 844 793