diff options
39 files changed, 566 insertions, 144 deletions
diff --git a/Documentation/RelNotes/2.37.3.txt b/Documentation/RelNotes/2.37.3.txt new file mode 100644 index 0000000000..d66689e598 --- /dev/null +++ b/Documentation/RelNotes/2.37.3.txt @@ -0,0 +1,46 @@ +Git 2.37.3 Release Notes +======================== + +This primarily is to backport various fixes accumulated on the 'master' +front since 2.37.2. + +Fixes since v2.37.2 +------------------- + + * The build procedure for Windows that uses CMake has been updated to + pick up the shell interpreter from local installation location. + + * Conditionally allow building Python interpreter on Windows + + * Fix to lstat() emulation on Windows. + + * Older gcc with -Wall complains about the universal zero initializer + "struct s = { 0 };" idiom, which makes developers' lives + inconvenient (as -Werror is enabled by DEVELOPER=YesPlease). The + build procedure has been tweaked to help these compilers. + + * Plug memory leaks in the failure code path in the "merge-ort" merge + strategy backend. + + * Avoid repeatedly running getconf to ask libc version in the test + suite, and instead just as it once per script. + + * Platform-specific code that determines if a directory is OK to use + as a repository has been taught to report more details, especially + on Windows. + + * "vimdiff3" regression has been corrected. + + * "git fsck" reads mode from tree objects but canonicalizes the mode + before passing it to the logic to check object sanity, which has + hid broken tree objects from the checking logic. This has been + corrected, but to help exiting projects with broken tree objects + that they cannot fix retroactively, the severity of anomalies this + code detects has been demoted to "info" for now. + + * Fixes to sparse index compatibility work for "reset" and "checkout" + commands. + + * Documentation for "git add --renormalize" has been improved. + +Also contains other minor documentation updates and code clean-ups. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 11eb70f16c..9b37f35654 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -188,7 +188,9 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files. forcibly add them again to the index. This is useful after changing `core.autocrlf` configuration or the `text` attribute in order to correct files added with wrong CRLF/LF line endings. - This option implies `-u`. + This option implies `-u`. Lone CR characters are untouched, thus + while a CRLF cleans to LF, a CRCRLF sequence is only partially + cleaned to CRLF. --chmod=(+|-)x:: Override the executable bit of the added files. The executable diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 5ced7ad4f8..db9d46edfa 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -22,7 +22,7 @@ depending on the subcommand: [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...] 'git reflog delete' [--rewrite] [--updateref] - [--dry-run | -n] [--verbose] <ref>@\{<specifier>\}... + [--dry-run | -n] [--verbose] <ref>@{<specifier>}... 'git reflog exists' <ref> Reference logs, or "reflogs", record when the tips of branches and diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index e2d51b505d..fb16249620 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.37.2 +DEF_VER=v2.37.3 LF=' ' @@ -910,6 +910,7 @@ LIB_OBJS += combine-diff.o LIB_OBJS += commit-graph.o LIB_OBJS += commit-reach.o LIB_OBJS += commit.o +LIB_OBJS += compat/nonblock.o LIB_OBJS += compat/obstack.o LIB_OBJS += compat/terminal.o LIB_OBJS += compat/zlib-uncompress2.o @@ -1 +1 @@ -Documentation/RelNotes/2.37.2.txt
\ No newline at end of file +Documentation/RelNotes/2.37.3.txt
\ No newline at end of file diff --git a/builtin/checkout.c b/builtin/checkout.c index df3f1663d7..cdd96cd9c6 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -626,6 +626,7 @@ static void show_local_changes(struct object *head, repo_init_revisions(the_repository, &rev, NULL); rev.diffopt.flags = opts->flags; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; + rev.diffopt.flags.recursive = 1; diff_setup_done(&rev.diffopt); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); @@ -831,6 +831,15 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na int index_name_pos(struct index_state *, const char *name, int namelen); /* + * Like index_name_pos, returns the position of an entry of the given name in + * the index if one exists, otherwise returns a negative value where the negated + * value minus 1 is the position where the index entry would be inserted. Unlike + * index_name_pos, however, a sparse index is not expanded to find an entry + * inside a sparse directory. + */ +int index_name_pos_sparse(struct index_state *, const char *name, int namelen); + +/* * Determines whether an entry with the given name exists within the * given index. The return value is 1 if an exact match is found, otherwise * it is 0. Note that, unlike index_name_pos, this function does not expand diff --git a/compat/mingw.c b/compat/mingw.c index b5502997e2..901375d584 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1,6 +1,7 @@ #include "../git-compat-util.h" #include "win32.h" #include <aclapi.h> +#include <sddl.h> #include <conio.h> #include <wchar.h> #include "../strbuf.h" @@ -768,8 +769,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename) wfilename[n] = L'\0'; attributes = GetFileAttributesW(wfilename); wfilename[n] = c; - if (attributes == FILE_ATTRIBUTE_DIRECTORY || - attributes == FILE_ATTRIBUTE_DEVICE) + if (attributes & + (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) return 1; if (attributes == INVALID_FILE_ATTRIBUTES) switch (GetLastError()) { @@ -2670,7 +2671,22 @@ static PSID get_current_user_sid(void) return result; } -int is_path_owned_by_current_sid(const char *path) +static int acls_supported(const char *path) +{ + size_t offset = offset_1st_component(path); + WCHAR wroot[MAX_PATH]; + DWORD file_system_flags; + + if (offset && + xutftowcsn(wroot, path, MAX_PATH, offset) > 0 && + GetVolumeInformationW(wroot, NULL, 0, NULL, NULL, + &file_system_flags, NULL, 0)) + return !!(file_system_flags & FILE_PERSISTENT_ACLS); + + return 0; +} + +int is_path_owned_by_current_sid(const char *path, struct strbuf *report) { WCHAR wpath[MAX_PATH]; PSID sid = NULL; @@ -2709,6 +2725,7 @@ int is_path_owned_by_current_sid(const char *path) else if (sid && IsValidSid(sid)) { /* Now, verify that the SID matches the current user's */ static PSID current_user_sid; + BOOL is_member; if (!current_user_sid) current_user_sid = get_current_user_sid(); @@ -2717,6 +2734,46 @@ int is_path_owned_by_current_sid(const char *path) IsValidSid(current_user_sid) && EqualSid(sid, current_user_sid)) result = 1; + else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) && + CheckTokenMembership(NULL, sid, &is_member) && + is_member) + /* + * If owned by the Administrators group, and the + * current user is an administrator, we consider that + * okay, too. + */ + result = 1; + else if (report && + IsWellKnownSid(sid, WinWorldSid) && + !acls_supported(path)) { + /* + * On FAT32 volumes, ownership is not actually recorded. + */ + strbuf_addf(report, "'%s' is on a file system that does" + "not record ownership\n", path); + } else if (report) { + LPSTR str1, str2, to_free1 = NULL, to_free2 = NULL; + + if (ConvertSidToStringSidA(sid, &str1)) + to_free1 = str1; + else + str1 = "(inconvertible)"; + + if (!current_user_sid) + str2 = "(none)"; + else if (!IsValidSid(current_user_sid)) + str2 = "(invalid)"; + else if (ConvertSidToStringSidA(current_user_sid, &str2)) + to_free2 = str2; + else + str2 = "(inconvertible)"; + strbuf_addf(report, + "'%s' is owned by:\n" + "\t'%s'\nbut the current user is:\n" + "\t'%s'\n", path, str1, str2); + LocalFree(to_free1); + LocalFree(to_free2); + } } /* diff --git a/compat/mingw.h b/compat/mingw.h index a74da68f31..209cf7ceba 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -463,7 +463,7 @@ char *mingw_query_user_email(void); * Verifies that the specified path is owned by the user running the * current process. */ -int is_path_owned_by_current_sid(const char *path); +int is_path_owned_by_current_sid(const char *path, struct strbuf *report); #define is_path_owned_by_current_user is_path_owned_by_current_sid /** diff --git a/compat/nonblock.c b/compat/nonblock.c new file mode 100644 index 0000000000..9694ebdb1d --- /dev/null +++ b/compat/nonblock.c @@ -0,0 +1,50 @@ +#include "git-compat-util.h" +#include "nonblock.h" + +#ifdef O_NONBLOCK + +int enable_pipe_nonblock(int fd) +{ + int flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -1; + flags |= O_NONBLOCK; + return fcntl(fd, F_SETFL, flags); +} + +#elif defined(GIT_WINDOWS_NATIVE) + +#include "win32.h" + +int enable_pipe_nonblock(int fd) +{ + HANDLE h = (HANDLE)_get_osfhandle(fd); + DWORD mode; + DWORD type = GetFileType(h); + if (type == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) { + errno = EBADF; + return -1; + } + if (type != FILE_TYPE_PIPE) + BUG("unsupported file type: %lu", type); + if (!GetNamedPipeHandleState(h, &mode, NULL, NULL, NULL, NULL, 0)) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + mode |= PIPE_NOWAIT; + if (!SetNamedPipeHandleState(h, &mode, NULL, NULL)) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + return 0; +} + +#else + +int enable_pipe_nonblock(int fd) +{ + errno = ENOSYS; + return -1; +} + +#endif diff --git a/compat/nonblock.h b/compat/nonblock.h new file mode 100644 index 0000000000..af1a331301 --- /dev/null +++ b/compat/nonblock.h @@ -0,0 +1,9 @@ +#ifndef COMPAT_NONBLOCK_H +#define COMPAT_NONBLOCK_H + +/* + * Enable non-blocking I/O for the pipe specified by the passed-in descriptor. + */ +int enable_pipe_nonblock(int fd); + +#endif diff --git a/config.mak.dev b/config.mak.dev index 335efd4620..4fa19d361b 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -59,9 +59,13 @@ endif # uninitialized warnings on gcc 4.9.2 in xdiff/xdiffi.c and config.c # not worth fixing since newer compilers correctly stop complaining +# +# Likewise, gcc older than 4.9 complains about initializing a +# struct-within-a-struct using just "{ 0 }" ifneq ($(filter gcc4,$(COMPILER_FEATURES)),) ifeq ($(filter gcc5,$(COMPILER_FEATURES)),) DEVELOPER_CFLAGS += -Wno-uninitialized +DEVELOPER_CFLAGS += -Wno-missing-braces endif endif diff --git a/config.mak.uname b/config.mak.uname index ce83cad47a..d63629fe80 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -656,7 +656,6 @@ ifeq ($(uname_S),MINGW) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease - NO_PYTHON = YesPlease ETAGS_TARGET = ETAGS NO_POSIX_GOODIES = UnfortunatelyYes DEFAULT_HELP_FORMAT = html @@ -686,6 +685,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) INTERNAL_QSORT = YesPlease HAVE_LIBCHARSET_H = YesPlease NO_GETTEXT = YesPlease + NO_PYTHON = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS else ifneq ($(shell expr "$(uname_R)" : '1\.'),2) @@ -717,10 +717,8 @@ else INSTALL = /bin/install INTERNAL_QSORT = YesPlease HAVE_LIBCHARSET_H = YesPlease - NO_GETTEXT = USE_GETTEXT_SCHEME = fallthrough USE_LIBPCRE = YesPlease - NO_CURL = USE_NED_ALLOCATOR = YesPlease ifeq (/mingw64,$(subst 32,64,$(prefix))) # Move system config into top-level /etc/ @@ -730,6 +728,7 @@ else else COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO NO_CURL = YesPlease + NO_PYTHON = YesPlease endif endif endif diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 1b23f2440d..2237109b57 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -77,7 +77,7 @@ if(USE_VCPKG) set(CMAKE_TOOLCHAIN_FILE ${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file") endif() -find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin") +find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin" "$ENV{LOCALAPPDATA}/Programs/Git/bin") if(NOT SH_EXE) message(FATAL_ERROR "sh: shell interpreter was not found in your path, please install one." "On Windows, you can get it as part of 'Git for Windows' install at https://gitforwindows.org/") diff --git a/diff-lib.c b/diff-lib.c index 7eb66a417a..2edea41a23 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -466,6 +466,11 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something removed from the tree? */ if (!idx) { + if (S_ISSPARSEDIR(tree->ce_mode)) { + diff_tree_oid(&tree->oid, NULL, tree->name, &revs->diffopt); + return; + } + diff_index_show_file(revs, "-", tree, &tree->oid, 1, tree->ce_mode, 0); return; @@ -308,7 +308,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op return -1; name = fsck_get_object_name(options, &tree->object.oid); - if (init_tree_desc_gently(&desc, tree->buffer, tree->size)) + if (init_tree_desc_gently(&desc, tree->buffer, tree->size, 0)) return -1; while (tree_entry_gently(&desc, &entry)) { struct object *obj; @@ -578,7 +578,7 @@ static int fsck_tree(const struct object_id *tree_oid, const char *o_name; struct name_stack df_dup_candidates = { NULL }; - if (init_tree_desc_gently(&desc, buffer, size)) { + if (init_tree_desc_gently(&desc, buffer, size, TREE_DESC_RAW_MODES)) { retval += report(options, tree_oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); @@ -56,7 +56,6 @@ enum fsck_msg_type { FUNC(GITMODULES_PATH, ERROR) \ FUNC(GITMODULES_UPDATE, ERROR) \ /* warnings */ \ - FUNC(BAD_FILEMODE, WARN) \ FUNC(EMPTY_NAME, WARN) \ FUNC(FULL_PATHNAME, WARN) \ FUNC(HAS_DOT, WARN) \ @@ -66,6 +65,7 @@ enum fsck_msg_type { FUNC(ZERO_PADDED_FILEMODE, WARN) \ FUNC(NUL_IN_COMMIT, WARN) \ /* infos (reported as warnings, but ignored by default) */ \ + FUNC(BAD_FILEMODE, INFO) \ FUNC(GITMODULES_PARSE, INFO) \ FUNC(GITIGNORE_SYMLINK, INFO) \ FUNC(GITATTRIBUTES_SYMLINK, INFO) \ diff --git a/git-compat-util.h b/git-compat-util.h index 58d7708296..6aee4d92e7 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -23,6 +23,9 @@ #include <crtdbg.h> #endif +struct strbuf; + + #define _FILE_OFFSET_BITS 64 @@ -487,7 +490,7 @@ static inline void extract_id_from_env(const char *env, uid_t *id) } } -static inline int is_path_owned_by_current_uid(const char *path) +static inline int is_path_owned_by_current_uid(const char *path, struct strbuf *report) { struct stat st; uid_t euid; @@ -995,6 +998,28 @@ static inline unsigned long cast_size_t_to_ulong(size_t a) return (unsigned long)a; } +/* + * Limit size of IO chunks, because huge chunks only cause pain. OS X + * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in + * the absence of bugs, large chunks can result in bad latencies when + * you decide to kill the process. + * + * We pick 8 MiB as our default, but if the platform defines SSIZE_MAX + * that is smaller than that, clip it to SSIZE_MAX, as a call to + * read(2) or write(2) larger than that is allowed to fail. As the last + * resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value" + * to override this, if the definition of SSIZE_MAX given by the platform + * is broken. + */ +#ifndef MAX_IO_SIZE +# define MAX_IO_SIZE_DEFAULT (8*1024*1024) +# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT) +# define MAX_IO_SIZE SSIZE_MAX +# else +# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT +# endif +#endif + #ifdef HAVE_ALLOCA_H # include <alloca.h> # define xalloca(size) (alloca(size)) @@ -62,9 +62,6 @@ static int pick_next_hook(struct child_process *cp, strvec_push(&cp->args, hook_path); strvec_pushv(&cp->args, hook_cb->options->args.v); - /* Provide context for errors if necessary */ - *pp_task_cb = (char *)hook_path; - /* * This pick_next_hook() will be called again, we're only * running one hook, so indicate that no more work will be @@ -80,13 +77,9 @@ static int notify_start_failure(struct strbuf *out, void *pp_task_cp) { struct hook_cb_data *hook_cb = pp_cb; - const char *hook_path = pp_task_cp; hook_cb->rc |= 1; - strbuf_addf(out, _("Couldn't start hook '%s'\n"), - hook_path); - return 1; } diff --git a/merge-ort.c b/merge-ort.c index 8c4927f0e1..ebb9a75425 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -4294,6 +4294,8 @@ void merge_switch_to_result(struct merge_options *opt, if (checkout(opt, head, result->tree)) { /* failure to function */ result->clean = -1; + merge_finalize(opt, result); + trace2_region_leave("merge", "checkout", opt->repo); return; } trace2_region_leave("merge", "checkout", opt->repo); @@ -4304,6 +4306,9 @@ void merge_switch_to_result(struct merge_options *opt, /* failure to function */ opt->priv = NULL; result->clean = -1; + merge_finalize(opt, result); + trace2_region_leave("merge", "record_conflicted", + opt->repo); return; } opt->priv = NULL; diff --git a/mergetools/vimdiff b/mergetools/vimdiff index f770b8fe24..06937acbf5 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -29,8 +29,8 @@ ################################################################################ debug_print () { - # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF is set - # to "true" + # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF_DEBUG + # is set. if test -n "$GIT_MERGETOOL_VIMDIFF_DEBUG" then @@ -66,11 +66,6 @@ gen_cmd_aux () { debug_print "LAYOUT : $LAYOUT" debug_print "CMD : $CMD" - if test -z "$CMD" - then - CMD="echo" # vim "nop" operator - fi - start=0 end=${#LAYOUT} @@ -144,11 +139,10 @@ gen_cmd_aux () { # Step 2: # - # Search for all valid separators ("+", "/" or ",") which are *not* + # Search for all valid separators ("/" or ",") which are *not* # inside parenthesis. Save the index at which each of them makes the # first appearance. - index_new_tab="" index_horizontal_split="" index_vertical_split="" @@ -182,14 +176,7 @@ gen_cmd_aux () { then current=$c - if test "$current" = "+" - then - if test -z "$index_new_tab" - then - index_new_tab=$i - fi - - elif test "$current" = "/" + if test "$current" = "/" then if test -z "$index_horizontal_split" then @@ -219,14 +206,7 @@ gen_cmd_aux () { terminate="false" - if ! test -z "$index_new_tab" - then - before="-tabnew" - after="tabnext" - index=$index_new_tab - terminate="true" - - elif ! test -z "$index_horizontal_split" + if ! test -z "$index_horizontal_split" then before="leftabove split" after="wincmd j" @@ -333,25 +313,31 @@ gen_cmd () { # Obtain the first part of vim "-c" option to obtain the desired layout - CMD=$(gen_cmd_aux "$LAYOUT") - - - # Adjust the just obtained script depending on whether more than one - # windows are visible or not + CMD= + oldIFS=$IFS + IFS=+ + for tab in $LAYOUT + do + if test -z "$CMD" + then + CMD="echo" # vim "nop" operator + else + CMD="$CMD | tabnew" + fi - if echo "$LAYOUT" | grep ",\|/" >/dev/null - then - CMD="$CMD | tabdo windo diffthis" - else - CMD="$CMD | bufdo diffthis" - fi + # If this is a single window diff with all the buffers + if ! echo "$tab" | grep ",\|/" >/dev/null + then + CMD="$CMD | silent execute 'bufdo diffthis'" + fi + CMD=$(gen_cmd_aux "$tab" "$CMD") + done + IFS=$oldIFS - # Add an extra "-c" option to move to the first tab (notice that we - # can't simply append the command to the previous "-c" string as - # explained here: https://github.com/vim/vim/issues/9076 + CMD="$CMD | execute 'tabdo windo diffthis'" - FINAL_CMD="-c \"$CMD\" -c \"tabfirst\"" + FINAL_CMD="-c \"set hidden diffopt-=hiddenoff | $CMD | tabfirst\"" } @@ -555,22 +541,22 @@ run_unit_tests () { TEST_CASE_15=" (( (LOCAL , BASE , REMOTE) / MERGED)) +(BASE) , LOCAL+ BASE , REMOTE+ (((LOCAL / BASE / REMOTE)) , MERGED ) " TEST_CASE_16="LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL / BASE / REMOTE),MERGED" - EXPECTED_CMD_01="-c \"echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_02="-c \"echo | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_03="-c \"echo | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 4b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_04="-c \"echo | 4b | bufdo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_05="-c \"echo | leftabove split | 1b | wincmd j | leftabove split | 4b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_06="-c \"echo | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_07="-c \"echo | leftabove vertical split | 4b | wincmd l | leftabove split | 1b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_08="-c \"echo | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_09="-c \"echo | leftabove split | 4b | wincmd j | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_10="-c \"echo | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_11="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_12="-c \"echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_13="-c \"echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_14="-c \"echo | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | 2b | wincmd l | 1b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_15="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" - EXPECTED_CMD_16="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\"" + EXPECTED_CMD_01="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_02="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 1b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_03="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 4b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_04="-c \"set hidden diffopt-=hiddenoff | echo | silent execute 'bufdo diffthis' | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_05="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | 1b | wincmd j | leftabove split | 4b | wincmd j | 3b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_06="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_07="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 4b | wincmd l | leftabove split | 1b | wincmd j | 3b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_08="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_09="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | 4b | wincmd j | leftabove vertical split | 1b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_10="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_11="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_12="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_13="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_14="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_15="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_16="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" EXPECTED_TARGET_01="MERGED" EXPECTED_TARGET_02="LOCAL" @@ -635,9 +621,7 @@ run_unit_tests () { cat >expect <<-\EOF -f -c - echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | quit | wincmd l | 2b | wincmd j | 3b | tabdo windo diffthis - -c - tabfirst + set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | quit | wincmd l | 2b | wincmd j | 3b | execute 'tabdo windo diffthis' | tabfirst lo cal ' ' mer ged diff --git a/packfile.c b/packfile.c index 8e812a84a3..75c254e0e0 100644 --- a/packfile.c +++ b/packfile.c @@ -2229,7 +2229,17 @@ static int add_promisor_object(const struct object_id *oid, void *set_) { struct oidset *set = set_; - struct object *obj = parse_object(the_repository, oid); + struct object *obj; + int we_parsed_object; + + obj = lookup_object(the_repository, oid); + if (obj && obj->parsed) { + we_parsed_object = 0; + } else { + we_parsed_object = 1; + obj = parse_object(the_repository, oid); + } + if (!obj) return 1; @@ -2243,7 +2253,7 @@ static int add_promisor_object(const struct object_id *oid, struct tree *tree = (struct tree *)obj; struct tree_desc desc; struct name_entry entry; - if (init_tree_desc_gently(&desc, tree->buffer, tree->size)) + if (init_tree_desc_gently(&desc, tree->buffer, tree->size, 0)) /* * Error messages are given when packs are * verified, so do not print any here. @@ -2251,7 +2261,8 @@ static int add_promisor_object(const struct object_id *oid, return 0; while (tree_entry_gently(&desc, &entry)) oidset_insert(set, &entry.oid); - free_tree_buffer(tree); + if (we_parsed_object) + free_tree_buffer(tree); } else if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; struct commit_list *parents = commit->parents; diff --git a/preload-index.c b/preload-index.c index e5529a5863..100f7a374d 100644 --- a/preload-index.c +++ b/preload-index.c @@ -151,6 +151,12 @@ void preload_index(struct index_state *index, } stop_progress(&pd.progress); + if (pathspec) { + /* earlier we made deep copies for each thread to work with */ + for (i = 0; i < threads; i++) + clear_pathspec(&data[i].pathspec); + } + trace_performance_leave("preload index"); trace2_data_intmax("index", NULL, "preload/sum_lstat", t2_sum_lstat); diff --git a/promisor-remote.c b/promisor-remote.c index 5b33f88bca..68f46f5ec7 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -146,7 +146,7 @@ static void promisor_remote_init(struct repository *r) if (r->promisor_remote_config) return; config = r->promisor_remote_config = - xcalloc(sizeof(*r->promisor_remote_config), 1); + xcalloc(1, sizeof(*r->promisor_remote_config)); config->promisors_tail = &config->promisors; repo_config(r, promisor_remote_config, config); diff --git a/read-cache.c b/read-cache.c index 4de207752d..b09128b188 100644 --- a/read-cache.c +++ b/read-cache.c @@ -620,6 +620,11 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen) return index_name_stage_pos(istate, name, namelen, 0, EXPAND_SPARSE); } +int index_name_pos_sparse(struct index_state *istate, const char *name, int namelen) +{ + return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE); +} + int index_entry_exists(struct index_state *istate, const char *name, int namelen) { return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE) >= 0; diff --git a/run-command.c b/run-command.c index 14f17830f5..5ec3a46dcc 100644 --- a/run-command.c +++ b/run-command.c @@ -10,6 +10,7 @@ #include "config.h" #include "packfile.h" #include "hook.h" +#include "compat/nonblock.h" void child_process_init(struct child_process *child) { @@ -1364,12 +1365,25 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd) continue; if (io->type == POLLOUT) { - ssize_t len = xwrite(io->fd, - io->u.out.buf, io->u.out.len); + ssize_t len; + + /* + * Don't use xwrite() here. It loops forever on EAGAIN, + * and we're in our own poll() loop here. + * + * Note that we lose xwrite()'s handling of MAX_IO_SIZE + * and EINTR, so we have to implement those ourselves. + */ + len = write(io->fd, io->u.out.buf, + io->u.out.len <= MAX_IO_SIZE ? + io->u.out.len : MAX_IO_SIZE); if (len < 0) { - io->error = errno; - close(io->fd); - io->fd = -1; + if (errno != EINTR && errno != EAGAIN && + errno != ENOSPC) { + io->error = errno; + close(io->fd); + io->fd = -1; + } } else { io->u.out.buf += len; io->u.out.len -= len; @@ -1438,6 +1452,15 @@ int pipe_command(struct child_process *cmd, return -1; if (in) { + if (enable_pipe_nonblock(cmd->in) < 0) { + error_errno("unable to make pipe non-blocking"); + close(cmd->in); + if (out) + close(cmd->out); + if (err) + close(cmd->err); + return -1; + } io[nr].fd = cmd->in; io[nr].type = POLLOUT; io[nr].u.out.buf = in; @@ -1138,16 +1138,17 @@ static int safe_directory_cb(const char *key, const char *value, void *d) * added, for bare ones their git directory. */ static int ensure_valid_ownership(const char *gitfile, - const char *worktree, const char *gitdir) + const char *worktree, const char *gitdir, + struct strbuf *report) { struct safe_directory_data data = { .path = worktree ? worktree : gitdir }; if (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) && - (!gitfile || is_path_owned_by_current_user(gitfile)) && - (!worktree || is_path_owned_by_current_user(worktree)) && - (!gitdir || is_path_owned_by_current_user(gitdir))) + (!gitfile || is_path_owned_by_current_user(gitfile, report)) && + (!worktree || is_path_owned_by_current_user(worktree, report)) && + (!gitdir || is_path_owned_by_current_user(gitdir, report))) return 1; /* @@ -1187,6 +1188,7 @@ enum discovery_result { */ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, struct strbuf *gitdir, + struct strbuf *report, int die_on_error) { const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); @@ -1271,10 +1273,11 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, strbuf_setlen(dir, offset); if (gitdirenv) { enum discovery_result ret; + const char *gitdir_candidate = + gitdir_path ? gitdir_path : gitdirenv; - if (ensure_valid_ownership(gitfile, - dir->buf, - (gitdir_path ? gitdir_path : gitdirenv))) { + if (ensure_valid_ownership(gitfile, dir->buf, + gitdir_candidate, report)) { strbuf_addstr(gitdir, gitdirenv); ret = GIT_DIR_DISCOVERED; } else @@ -1297,7 +1300,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, } if (is_git_directory(dir->buf)) { - if (!ensure_valid_ownership(NULL, NULL, dir->buf)) + if (!ensure_valid_ownership(NULL, NULL, dir->buf, report)) return GIT_DIR_INVALID_OWNERSHIP; strbuf_addstr(gitdir, "."); return GIT_DIR_BARE; @@ -1330,7 +1333,7 @@ int discover_git_directory(struct strbuf *commondir, return -1; cwd_len = dir.len; - if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) { + if (setup_git_directory_gently_1(&dir, gitdir, NULL, 0) <= 0) { strbuf_release(&dir); return -1; } @@ -1377,7 +1380,7 @@ int discover_git_directory(struct strbuf *commondir, const char *setup_git_directory_gently(int *nongit_ok) { static struct strbuf cwd = STRBUF_INIT; - struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT; + struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; @@ -1402,7 +1405,7 @@ const char *setup_git_directory_gently(int *nongit_ok) die_errno(_("Unable to read current working directory")); strbuf_addbuf(&dir, &cwd); - switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) { + switch (setup_git_directory_gently_1(&dir, &gitdir, &report, 1)) { case GIT_DIR_EXPLICIT: prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok); break; @@ -1434,12 +1437,14 @@ const char *setup_git_directory_gently(int *nongit_ok) if (!nongit_ok) { struct strbuf quoted = STRBUF_INIT; + strbuf_complete(&report, '\n'); sq_quote_buf_pretty("ed, dir.buf); die(_("detected dubious ownership in repository at '%s'\n" + "%s" "To add an exception for this directory, call:\n" "\n" "\tgit config --global --add safe.directory %s"), - dir.buf, quoted.buf); + dir.buf, report.buf, quoted.buf); } *nongit_ok = 1; break; @@ -1518,6 +1523,7 @@ const char *setup_git_directory_gently(int *nongit_ok) strbuf_release(&dir); strbuf_release(&gitdir); + strbuf_release(&report); clear_repository_format(&repo_fmt); return prefix; diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index f9f8c988bb..5dced39889 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -372,6 +372,14 @@ test_expect_success 'deep changes during checkout' ' test_all_match git checkout base ' +test_expect_success 'checkout with modified sparse directory' ' + init_repos && + + test_all_match git checkout rename-in-to-out -- . && + test_sparse_match git sparse-checkout reapply && + test_all_match git checkout base +' + test_expect_success 'add outside sparse cone' ' init_repos && @@ -687,6 +695,23 @@ test_expect_success 'reset with wildcard pathspec' ' test_all_match git ls-files -s -- folder1 ' +test_expect_success 'reset hard with removed sparse dir' ' + init_repos && + + run_on_all git rm -r --sparse folder1 && + test_all_match git status --porcelain=v2 && + + test_all_match git reset --hard && + test_all_match git status --porcelain=v2 && + + cat >expect <<-\EOF && + folder1/ + EOF + + git -C sparse-index ls-files --sparse folder1 >out && + test_cmp expect out +' + test_expect_success 'update-index modify outside sparse definition' ' init_repos && diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index ab7f31f1dc..53c2aa10b7 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -364,6 +364,20 @@ test_expect_success 'tree entry with type mismatch' ' test_i18ngrep ! "dangling blob" out ' +test_expect_success 'tree entry with bogus mode' ' + test_when_finished "remove_object \$blob" && + test_when_finished "remove_object \$tree" && + blob=$(echo blob | git hash-object -w --stdin) && + blob_oct=$(echo $blob | hex2oct) && + tree=$(printf "100000 foo\0${blob_oct}" | + git hash-object -t tree --stdin -w --literally) && + git fsck 2>err && + cat >expect <<-EOF && + warning in tree $tree: badFilemode: contains bad file modes + EOF + test_cmp expect err +' + test_expect_success 'tag pointing to nonexistent' ' badoid=$(test_oid deadbeef) && cat >invalid-tag <<-EOF && diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 210f429887..64096adac7 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -151,4 +151,30 @@ test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' ' test_hook_tty commit -m"B.new" ' +test_expect_success 'git hook run a hook with a bad shebang' ' + test_when_finished "rm -rf bad-hooks" && + mkdir bad-hooks && + write_script bad-hooks/test-hook "/bad/path/no/spaces" </dev/null && + + # TODO: We should emit the same (or at least a more similar) + # error on Windows and !Windows. See the OS-specific code in + # start_command() + if test_have_prereq !WINDOWS + then + cat >expect <<-\EOF + fatal: cannot run bad-hooks/test-hook: ... + EOF + else + cat >expect <<-\EOF + error: cannot spawn bad-hooks/test-hook: ... + EOF + fi && + test_expect_code 1 git \ + -c core.hooksPath=bad-hooks \ + hook run test-hook >out 2>err && + test_must_be_empty out && + sed -e "s/test-hook: .*/test-hook: .../" <err >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b354fb39de..3b7df9bed5 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -766,6 +766,19 @@ test_expect_success 'detect bogus diffFilter output' ' force_color test_must_fail git add -p <y ' +test_expect_success 'handle very large filtered diff' ' + git reset --hard && + # The specific number here is not important, but it must + # be large enough that the output of "git diff --color" + # fills up the pipe buffer. 10,000 results in ~200k of + # colored output. + test_seq 10000 >test && + test_config interactive.diffFilter cat && + printf y >y && + force_color git add -p >output 2>&1 <y && + git diff-files --exit-code -- test +' + test_expect_success 'diff.algorithm is passed to `git diff-files`' ' git reset --hard && diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index b0b795aca9..ac4099ca89 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -352,4 +352,21 @@ test_expect_success \ grep "Cannot demote unterminatedheader" act ' +test_expect_success 'badFilemode is not a strict error' ' + git init --bare badmode.git && + tree=$( + cd badmode.git && + blob=$(echo blob | git hash-object -w --stdin | hex2oct) && + printf "123456 foo\0${blob}" | + git hash-object -t tree --stdin -w --literally + ) && + + rm -rf dst.git && + git init --bare dst.git && + git -C dst.git config transfer.fsckObjects true && + + git -C badmode.git push ../dst.git $tree:refs/tags/tree 2>err && + grep "$tree: badFilemode" err +' + test_done diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 4a3778d04a..9aeacc2f6a 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -49,6 +49,13 @@ test_expect_success 'do partial clone 1' ' test "$(git -C pc1 config --local remote.origin.partialclonefilter)" = "blob:none" ' +test_expect_success 'rev-list --missing=allow-promisor on partial clone' ' + git -C pc1 rev-list --objects --missing=allow-promisor HEAD >actual && + git -C pc1 rev-list --objects --missing=print HEAD >expect.raw && + grep -v "^?" expect.raw >expect && + test_cmp expect actual +' + test_expect_success 'verify that .promisor file contains refs fetched' ' ls pc1/.git/objects/pack/pack-*.promisor >promisorlist && test_line_count = 1 promisorlist && diff --git a/t/test-lib.sh b/t/test-lib.sh index 8cabb4d10f..120f11812c 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -557,14 +557,19 @@ then : nothing } else + _USE_GLIBC_TUNABLES= + if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) && + _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} && + expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null + then + _USE_GLIBC_TUNABLES=YesPlease + fi setup_malloc_check () { local g local t MALLOC_CHECK_=3 MALLOC_PERTURB_=165 export MALLOC_CHECK_ MALLOC_PERTURB_ - if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) && - _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} && - expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null + if test -n "$_USE_GLIBC_TUNABLES" then g= LD_PRELOAD="libc_malloc_debug.so.0" diff --git a/tree-walk.c b/tree-walk.c index 506234b4b8..74f4d710e8 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -47,17 +47,20 @@ static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned l /* Initialize the descriptor entry */ desc->entry.path = path; - desc->entry.mode = canon_mode(mode); + desc->entry.mode = (desc->flags & TREE_DESC_RAW_MODES) ? mode : canon_mode(mode); desc->entry.pathlen = len - 1; oidread(&desc->entry.oid, (const unsigned char *)path + len); return 0; } -static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, unsigned long size, struct strbuf *err) +static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, + unsigned long size, struct strbuf *err, + enum tree_desc_flags flags) { desc->buffer = buffer; desc->size = size; + desc->flags = flags; if (size) return decode_tree_entry(desc, buffer, size, err); return 0; @@ -66,15 +69,16 @@ static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, u void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size) { struct strbuf err = STRBUF_INIT; - if (init_tree_desc_internal(desc, buffer, size, &err)) + if (init_tree_desc_internal(desc, buffer, size, &err, 0)) die("%s", err.buf); strbuf_release(&err); } -int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size) +int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size, + enum tree_desc_flags flags) { struct strbuf err = STRBUF_INIT; - int result = init_tree_desc_internal(desc, buffer, size, &err); + int result = init_tree_desc_internal(desc, buffer, size, &err, flags); if (result) error("%s", err.buf); strbuf_release(&err); diff --git a/tree-walk.h b/tree-walk.h index a5058469e9..6305d53150 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -34,6 +34,11 @@ struct tree_desc { /* counts the number of bytes left in the `buffer`. */ unsigned int size; + + /* option flags passed via init_tree_desc_gently() */ + enum tree_desc_flags { + TREE_DESC_RAW_MODES = (1 << 0), + } flags; }; /** @@ -79,7 +84,8 @@ int update_tree_entry_gently(struct tree_desc *); */ void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size); -int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long size); +int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long size, + enum tree_desc_flags flags); /* * Visit the next entry in a tree. Returns 1 when there are more entries diff --git a/unpack-trees.c b/unpack-trees.c index 8a454e03bf..90b92114be 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1070,6 +1070,67 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, } /* + * Determine whether the path specified by 'p' should be unpacked as a new + * sparse directory in a sparse index. A new sparse directory 'A/': + * - must be outside the sparse cone. + * - must not already be in the index (i.e., no index entry with name 'A/' + * exists). + * - must not have any child entries in the index (i.e., no index entry + * 'A/<something>' exists). + * If 'p' meets the above requirements, return 1; otherwise, return 0. + */ +static int entry_is_new_sparse_dir(const struct traverse_info *info, + const struct name_entry *p) +{ + int res, pos; + struct strbuf dirpath = STRBUF_INIT; + struct unpack_trees_options *o = info->data; + + if (!S_ISDIR(p->mode)) + return 0; + + /* + * If the path is inside the sparse cone, it can't be a sparse directory. + */ + strbuf_add(&dirpath, info->traverse_path, info->pathlen); + strbuf_add(&dirpath, p->path, p->pathlen); + strbuf_addch(&dirpath, '/'); + if (path_in_cone_mode_sparse_checkout(dirpath.buf, o->src_index)) { + res = 0; + goto cleanup; + } + + pos = index_name_pos_sparse(o->src_index, dirpath.buf, dirpath.len); + if (pos >= 0) { + /* Path is already in the index, not a new sparse dir */ + res = 0; + goto cleanup; + } + + /* Where would this sparse dir be inserted into the index? */ + pos = -pos - 1; + if (pos >= o->src_index->cache_nr) { + /* + * Sparse dir would be inserted at the end of the index, so we + * know it has no child entries. + */ + res = 1; + goto cleanup; + } + + /* + * If the dir has child entries in the index, the first would be at the + * position the sparse directory would be inserted. If the entry at this + * position is inside the dir, not a new sparse dir. + */ + res = strncmp(o->src_index->cache[pos]->name, dirpath.buf, dirpath.len); + +cleanup: + strbuf_release(&dirpath); + return res; +} + +/* * Note that traverse_by_cache_tree() duplicates some logic in this function * without actually calling it. If you change the logic here you may need to * check and change there as well. @@ -1078,21 +1139,44 @@ static int unpack_single_entry(int n, unsigned long mask, unsigned long dirmask, struct cache_entry **src, const struct name_entry *names, - const struct traverse_info *info) + const struct traverse_info *info, + int *is_new_sparse_dir) { int i; struct unpack_trees_options *o = info->data; unsigned long conflicts = info->df_conflicts | dirmask; + const struct name_entry *p = names; - if (mask == dirmask && !src[0]) - return 0; + *is_new_sparse_dir = 0; + if (mask == dirmask && !src[0]) { + /* + * If we're not in a sparse index, we can't unpack a directory + * without recursing into it, so we return. + */ + if (!o->src_index->sparse_index) + return 0; + + /* Find first entry with a real name (we could use "mask" too) */ + while (!p->mode) + p++; + + /* + * If the directory is completely missing from the index but + * would otherwise be a sparse directory, we should unpack it. + * If not, we'll return and continue recursively traversing the + * tree. + */ + *is_new_sparse_dir = entry_is_new_sparse_dir(info, p); + if (!*is_new_sparse_dir) + return 0; + } /* - * When we have a sparse directory entry for src[0], - * then this isn't necessarily a directory-file conflict. + * When we are unpacking a sparse directory, then this isn't necessarily + * a directory-file conflict. */ - if (mask == dirmask && src[0] && - S_ISSPARSEDIR(src[0]->ce_mode)) + if (mask == dirmask && + (*is_new_sparse_dir || (src[0] && S_ISSPARSEDIR(src[0]->ce_mode)))) conflicts = 0; /* @@ -1352,7 +1436,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma { struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; struct unpack_trees_options *o = info->data; - int ret; + int ret, is_new_sparse_dir; assert(o->merge); @@ -1376,7 +1460,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma * "index" tree (i.e., names[0]) and adjust 'names', 'n', 'mask', and * 'dirmask' accordingly. */ - ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info); + ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info, &is_new_sparse_dir); if (src[0]) discard_cache_entry(src[0]); @@ -1394,6 +1478,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; struct unpack_trees_options *o = info->data; const struct name_entry *p = names; + int is_new_sparse_dir; /* Find first entry with a real name (we could use "mask" too) */ while (!p->mode) @@ -1440,7 +1525,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str } } - if (unpack_single_entry(n, mask, dirmask, src, names, info) < 0) + if (unpack_single_entry(n, mask, dirmask, src, names, info, &is_new_sparse_dir)) return -1; if (o->merge && src[0]) { @@ -1478,6 +1563,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str } if (!is_sparse_directory_entry(src[0], names, info) && + !is_new_sparse_dir && traverse_trees_recursive(n, dirmask, mask & ~dirmask, names, info) < 0) { return -1; @@ -161,28 +161,6 @@ void xsetenv(const char *name, const char *value, int overwrite) die_errno(_("could not setenv '%s'"), name ? name : "(null)"); } -/* - * Limit size of IO chunks, because huge chunks only cause pain. OS X - * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in - * the absence of bugs, large chunks can result in bad latencies when - * you decide to kill the process. - * - * We pick 8 MiB as our default, but if the platform defines SSIZE_MAX - * that is smaller than that, clip it to SSIZE_MAX, as a call to - * read(2) or write(2) larger than that is allowed to fail. As the last - * resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value" - * to override this, if the definition of SSIZE_MAX given by the platform - * is broken. - */ -#ifndef MAX_IO_SIZE -# define MAX_IO_SIZE_DEFAULT (8*1024*1024) -# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT) -# define MAX_IO_SIZE SSIZE_MAX -# else -# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT -# endif -#endif - /** * xopen() is the same as open(), but it die()s if the open() fails. */ |
