diff options
102 files changed, 2300 insertions, 811 deletions
diff --git a/.gitignore b/.gitignore index 054249b20a..f817c509ec 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ /git-grep /git-hash-object /git-help +/git-hook /git-http-backend /git-http-fetch /git-http-push diff --git a/Documentation/RelNotes/2.35.1.txt b/Documentation/RelNotes/2.35.1.txt new file mode 100644 index 0000000000..726ba250ef --- /dev/null +++ b/Documentation/RelNotes/2.35.1.txt @@ -0,0 +1,6 @@ +Git v2.35.1 Release Notes +========================= + +Git 2.35 shipped with a regression that broke use of "rebase" and +"stash" in a secondary worktree. This maintenance release ought to +fix it. diff --git a/Documentation/RelNotes/2.36.0.txt b/Documentation/RelNotes/2.36.0.txt new file mode 100644 index 0000000000..94db2458c7 --- /dev/null +++ b/Documentation/RelNotes/2.36.0.txt @@ -0,0 +1,112 @@ +Git 2.36 Release Notes +====================== + +Updates since Git 2.35 +---------------------- + +Backward compatibility warts + + * "git name-rev --stdin" has been deprecated and issues a warning + when used; use "git name-rev --annotate-stdin" instead. + + +Note to those who build from the source + + * + + +UI, Workflows & Features + + * Assorted updates to "git cat-file", especially "-h". + + +Performance, Internal Implementation, Development Support etc. + + * "git apply" (ab)used the util pointer of the string-list to keep + track of how each symbolic link needs to be handled, which has been + simplified by using strset. + + * Fix a hand-rolled alloca() imitation that may have violated + alignment requirement of data being sorted in compatibility + implementation of qsort_s() and stable qsort(). + + * Use the parse-options API in "git reflog" command. + + * The conditional inclusion mechanism of configuration files using + "[includeIf <condition>]" learns to base its decision on the + URL of the remote repository the repository interacts with. + (merge 399b198489 jt/conditional-config-on-remote-url later to maint). + + * "git name-rev --stdin" does not behave like usual "--stdin" at + all. Start the process of renaming it to "--annotate-stdin". + (merge a2585719b3 jc/name-rev-stdin later to maint). + + +Fixes since v2.35 +----------------- + + * "rebase" and "stash" in secondary worktrees are broken in + Git 2.35.0, which has been corrected. + + * "git pull --rebase" ignored the rebase.autostash configuration + variable when the remote history is a descendant of our history, + which has been corrected. + (merge 3013d98d7a pb/pull-rebase-autostash-fix later to maint). + + * "git update-index --refresh" has been taught to deal better with + racy timestamps (just like "git status" already does). + (merge 2ede073fd2 ms/update-index-racy later to maint). + + * Avoid tests that are run under GIT_TRACE2 set from failing + unnecessarily. + (merge 944d808e42 js/test-unset-trace2-parents later to maint). + + * The merge-ort misbehaved when merge.renameLimit configuration is + set too low and failed to find all renames. + (merge 9ae39fef7f en/merge-ort-restart-optim-fix later to maint). + + * We explain that revs come first before the pathspec among command + line arguments, but did not spell out that dashed options come + before other args, which has been corrected. + (merge c11f95010c tl/doc-cli-options-first later to maint). + + * "git add -p" rewritten in C regressed hunk splitting in some cases, + which has been corrected. + (merge 7008ddc645 pw/add-p-hunk-split-fix later to maint). + + * "git fetch --negotiate-only" is an internal command used by "git + push" to figure out which part of our history is missing from the + other side. It should never recurse into submodules even when + fetch.recursesubmodules configuration variable is set, nor it + should trigger "gc". The code has been tightened up to ensure it + only does common ancestry discovery and nothing else. + (merge de4eaae63a gc/fetch-negotiate-only-early-return later to maint). + + * The code path that verifies signatures made with ssh were made to + work better on a system with CRLF line endings. + (merge caeef01ea7 fs/ssh-signing-crlf later to maint). + + * "git sparse-checkout init" failed to write into $GIT_DIR/info + directory when the repository was created without one, which has + been corrected to auto-create it. + (merge 7f44842ac1 jt/sparse-checkout-leading-dir-fix later to maint). + + * Cloning from a repository that does not yet have any branches or + tags but has other refs resulted in a "remote transport reported + error", which has been corrected. + (merge dccea605b6 jt/clone-not-quite-empty later to maint). + + * Mark in various places in the code that the sparse index and the + split index features are mutually incompatible. + (merge 451b66c533 js/sparse-vs-split-index later to maint). + + * Update the logic to compute alignment requirement for our mem-pool. + (merge e38bcc66d8 jc/mem-pool-alignment later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge cfc5cf428b jc/find-header later to maint). + (merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint). + (merge 727e6ea350 jh/p4-spawning-external-commands-cleanup later to maint). + (merge 0a6adc26e2 rs/grep-expr-cleanup later to maint). + (merge 4ed7dfa713 po/readme-mention-contributor-hints later to maint). + (merge 6046f7a91c en/plug-leaks-in-merge later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index b168f02dc3..bf3e512921 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -159,6 +159,33 @@ all branches that begin with `foo/`. This is useful if your branches are organized hierarchically and you would like to apply a configuration to all the branches in that hierarchy. +`hasconfig:remote.*.url:`:: + The data that follows this keyword is taken to + be a pattern with standard globbing wildcards and two + additional ones, `**/` and `/**`, that can match multiple + components. The first time this keyword is seen, the rest of + the config files will be scanned for remote URLs (without + applying any values). If there exists at least one remote URL + that matches this pattern, the include condition is met. ++ +Files included by this option (directly or indirectly) are not allowed +to contain remote URLs. ++ +Note that unlike other includeIf conditions, resolving this condition +relies on information that is not yet known at the point of reading the +condition. A typical use case is this option being present as a +system-level or global-level config, and the remote URL being in a +local-level config; hence the need to scan ahead when resolving this +condition. In order to avoid the chicken-and-egg problem in which +potentially-included files can affect whether such files are potentially +included, Git breaks the cycle by prohibiting these files from affecting +the resolution of these conditions (thus, prohibiting them from +declaring remote URLs). ++ +As for the naming of this keyword, it is for forwards compatibiliy with +a naming scheme that supports more variable-based include conditions, +but currently Git only supports the exact keyword described above. + A few more notes on matching via `gitdir` and `gitdir/i`: * Symlinks in `$GIT_DIR` are not resolved before matching. @@ -226,6 +253,14 @@ Example ; currently checked out [includeIf "onbranch:foo-branch"] path = foo.inc + +; include only if a remote with the given URL exists (note +; that such a URL may be provided later in a file or in a +; file read after this file is read, as seen in this example) +[includeIf "hasconfig:remote.*.url:https://example.com/**"] + path = foo.inc +[remote "origin"] + url = https://example.com/git ---- Values diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt index 0cb189a077..86892ada77 100644 --- a/Documentation/config/gpg.txt +++ b/Documentation/config/gpg.txt @@ -37,7 +37,7 @@ gpg.minTrustLevel:: gpg.ssh.defaultKeyCommand:: This command that will be run when user.signingkey is not set and a ssh signature is requested. On successful exit a valid ssh public key is - expected in the first line of its output. To automatically use the first + expected in the first line of its output. To automatically use the first available key from your ssh-agent set this to "ssh-add -L". gpg.ssh.allowedSignersFile:: @@ -66,7 +66,7 @@ This way only committers with an already valid key can add or change keys in the + Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after & valid-before options. Git will mark signatures as valid if the signing key was -valid at the time of the signatures creation. This allows users to change a +valid at the time of the signature's creation. This allows users to change a signing key without invalidating all previously made signatures. + Using a SSH CA key with the cert-authority option diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index e967ff1874..f903683189 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -71,6 +71,7 @@ configuration variables documented in linkgit:git-config[1], and the ancestors of the provided `--negotiation-tip=*` arguments, which we have in common with the server. + +This is incompatible with `--recurse-submodules=[yes|on-demand]`. Internally this is used to implement the `push.negotiate` option, see linkgit:git-config[1]. diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 27b27e2b30..bef76f4dd0 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -9,8 +9,14 @@ git-cat-file - Provide content or type and size information for repository objec SYNOPSIS -------- [verse] -'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object> -'git cat-file' (--batch[=<format>] | --batch-check[=<format>]) [ --textconv | --filters ] [--follow-symlinks] +'git cat-file' <type> <object> +'git cat-file' (-e | -p) <object> +'git cat-file' (-t | -s) [--allow-unknown-type] <object> +'git cat-file' (--batch | --batch-check) [--batch-all-objects] + [--buffer] [--follow-symlinks] [--unordered] + [--textconv | --filters] +'git cat-file' (--textconv | --filters) + [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>] DESCRIPTION ----------- diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt new file mode 100644 index 0000000000..77c3a8ad90 --- /dev/null +++ b/Documentation/git-hook.txt @@ -0,0 +1,45 @@ +git-hook(1) +=========== + +NAME +---- +git-hook - Run git hooks + +SYNOPSIS +-------- +[verse] +'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>] + +DESCRIPTION +----------- + +A command interface to running git hooks (see linkgit:githooks[5]), +for use by other scripted git commands. + +SUBCOMMANDS +----------- + +run:: + Run the `<hook-name>` hook. See linkgit:githooks[5] for + supported hook names. ++ + +Any positional arguments to the hook should be passed after a +mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See +linkgit:githooks[5] for arguments hooks might expect (if any). + +OPTIONS +------- + +--ignore-missing:: + Ignore any missing hook by quietly returning zero. Used for + tools that want to do a blind one-shot run of a hook that may + or may not be present. + +SEE ALSO +-------- +linkgit:githooks[5] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index 5cb0eb0855..ec8a27ce8b 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -42,11 +42,37 @@ OPTIONS --all:: List all commits reachable from all refs ---stdin:: +--annotate-stdin:: Transform stdin by substituting all the 40-character SHA-1 hexes (say $hex) with "$hex ($rev_name)". When used with --name-only, substitute with "$rev_name", omitting $hex - altogether. Intended for the scripter's use. + altogether. ++ +For example: ++ +----------- +$ cat sample.txt + +An abbreviated revision 2ae0a9cb82 will not be substituted. +The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907, +while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad + +$ git name-rev --annotate-stdin <sample.txt + +An abbreviated revision 2ae0a9cb82 will not be substituted. +The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907 (master), +while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad + +$ git name-rev --name-only --annotate-stdin <sample.txt + +An abbreviated revision 2ae0a9cb82 will not be substituted. +The full name after substitution is master, +while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad +----------- + +--stdin:: + This option is deprecated in favor of 'git name-rev --annotate-stdin'. + They are functionally equivalent. --name-only:: Instead of printing both the SHA-1 and the name, print only diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 83fd4e19a4..60984a4682 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -160,11 +160,12 @@ unspecified. ^^^^^ This attribute sets a specific line-ending style to be used in the -working directory. It enables end-of-line conversion without any -content checks, effectively setting the `text` attribute. Note that -setting this attribute on paths which are in the index with CRLF line -endings may make the paths to be considered dirty. Adding the path to -the index again will normalize the line endings in the index. +working directory. This attribute has effect only if the `text` +attribute is set or unspecified, or if it is set to `auto` and the file +is detected as text. Note that setting this attribute on paths which +are in the index with CRLF line endings may make the paths to be +considered dirty. Adding the path to the index again will normalize the +line endings in the index. Set to string value "crlf":: diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 92e4ba6a2f..1819a5a185 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -19,6 +19,15 @@ Many commands take revisions (most often "commits", but sometimes "tree-ish", depending on the context and command) and paths as their arguments. Here are the rules: + * Options come first and then args. + A subcommand may take dashed options (which may take their own + arguments, e.g. "--max-parents 2") and arguments. You SHOULD + give dashed options first and then arguments. Some commands may + accept dashed options after you have already gave non-option + arguments (which may make the command ambiguous), but you should + not rely on it (because eventually we may find a way to fix + these ambiguity by enforcing the "options then args" rule). + * Revisions come first and then paths. E.g. in `git diff v1.0 v2.0 arch/x86 include/asm-x86`, `v1.0` and `v2.0` are revisions and `arch/x86` and `include/asm-x86` @@ -72,24 +81,24 @@ you will. Here are the rules regarding the "flags" that you should follow when you are scripting Git: - * it's preferred to use the non-dashed form of Git commands, which means that + * It's preferred to use the non-dashed form of Git commands, which means that you should prefer `git foo` to `git-foo`. - * splitting short options to separate words (prefer `git foo -a -b` + * Splitting short options to separate words (prefer `git foo -a -b` to `git foo -ab`, the latter may not even work). - * when a command-line option takes an argument, use the 'stuck' form. In + * When a command-line option takes an argument, use the 'stuck' form. In other words, write `git foo -oArg` instead of `git foo -o Arg` for short options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg` for long options. An option that takes optional option-argument must be written in the 'stuck' form. - * when you give a revision parameter to a command, make sure the parameter is + * When you give a revision parameter to a command, make sure the parameter is not ambiguous with a name of a file in the work tree. E.g. do not write `git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work if you happen to have a file called `HEAD` in the work tree. - * many commands allow a long option `--option` to be abbreviated + * Many commands allow a long option `--option` to be abbreviated only to their unique prefix (e.g. if there is no other option whose name begins with `opt`, you may be able to spell `--opt` to invoke the `--option` flag), but you should fully spell them out diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index b51959ff94..a16e62bc8c 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -698,6 +698,10 @@ and "0" meaning they were not. Only one parameter should be set to "1" when the hook runs. The hook running passing "1", "1" should not be possible. +SEE ALSO +-------- +linkgit:git-hook[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1467b45c1d..f964f9ba7d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.35.0 +DEF_VER=v2.35.GIT LF=' ' @@ -234,6 +234,14 @@ all:: # Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support # the executable mode bit, but doesn't really do so. # +# Define CSPRNG_METHOD to "arc4random" if your system has arc4random and +# arc4random_buf, "libbsd" if your system has those functions from libbsd, +# "getrandom" if your system has getrandom, "getentropy" if your system has +# getentropy, "rtlgenrandom" for RtlGenRandom (Windows only), or "openssl" if +# you'd want to use the OpenSSL CSPRNG. You may set multiple options with +# spaces, in which case a suitable option will be chosen. If unset or set to +# anything else, defaults to using "/dev/urandom". +# # Define NEEDS_MODE_TRANSLATION if your OS strays from the typical file type # bits in mode values (e.g. z/OS defines I_SFMT to 0xFF000000 as opposed to the # usual 0xF000). @@ -693,6 +701,7 @@ TEST_BUILTINS_OBJS += test-bloom.o TEST_BUILTINS_OBJS += test-chmtime.o TEST_BUILTINS_OBJS += test-config.o TEST_BUILTINS_OBJS += test-crontab.o +TEST_BUILTINS_OBJS += test-csprng.o TEST_BUILTINS_OBJS += test-ctype.o TEST_BUILTINS_OBJS += test-date.o TEST_BUILTINS_OBJS += test-delta.o @@ -1109,6 +1118,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o BUILTIN_OBJS += builtin/grep.o BUILTIN_OBJS += builtin/hash-object.o BUILTIN_OBJS += builtin/help.o +BUILTIN_OBJS += builtin/hook.o BUILTIN_OBJS += builtin/index-pack.o BUILTIN_OBJS += builtin/init-db.o BUILTIN_OBJS += builtin/interpret-trailers.o @@ -1908,6 +1918,31 @@ ifdef HAVE_GETDELIM BASIC_CFLAGS += -DHAVE_GETDELIM endif +ifneq ($(findstring arc4random,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_ARC4RANDOM +endif + +ifneq ($(findstring libbsd,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_ARC4RANDOM_LIBBSD + EXTLIBS += -lbsd +endif + +ifneq ($(findstring getrandom,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_GETRANDOM +endif + +ifneq ($(findstring getentropy,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_GETENTROPY +endif + +ifneq ($(findstring rtlgenrandom,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_RTLGENRANDOM +endif + +ifneq ($(findstring openssl,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_OPENSSL_CSPRNG +endif + ifneq ($(PROCFS_EXECUTABLE_PATH),) procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH)) BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"' @@ -32,10 +32,16 @@ installed). The user discussion and development of Git take place on the Git mailing list -- everyone is welcome to post bug reports, feature requests, comments and patches to git@vger.kernel.org (read -[Documentation/SubmittingPatches][] for instructions on patch submission). +[Documentation/SubmittingPatches][] for instructions on patch submission +and [Documentation/CodingGuidelines][]). + +Those wishing to help with error message, usage and informational message +string translations (localization l10) should see [po/README.md][] +(a `po` file is a Portable Object file that holds the translations). + To subscribe to the list, send an email with just "subscribe git" in -the body to majordomo@vger.kernel.org. The mailing list archives are -available at <https://lore.kernel.org/git/>, +the body to majordomo@vger.kernel.org (not the Git list). The mailing +list archives are available at <https://lore.kernel.org/git/>, <http://marc.info/?l=git> and other archival sites. Issues which are security relevant should be disclosed privately to @@ -64,3 +70,5 @@ and the name as (depending on your mood): [Documentation/giteveryday.txt]: Documentation/giteveryday.txt [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt [Documentation/SubmittingPatches]: Documentation/SubmittingPatches +[Documentation/CodingGuidelines]: Documentation/CodingGuidelines +[po/README.md]: po/README.md @@ -1 +1 @@ -Documentation/RelNotes/2.35.0.txt
\ No newline at end of file +Documentation/RelNotes/2.36.0.txt
\ No newline at end of file diff --git a/add-patch.c b/add-patch.c index 573eef0cc4..55d719f784 100644 --- a/add-patch.c +++ b/add-patch.c @@ -383,6 +383,17 @@ static int is_octal(const char *p, size_t len) return 1; } +static void complete_file(char marker, struct hunk *hunk) +{ + if (marker == '-' || marker == '+') + /* + * Last hunk ended in non-context line (i.e. it + * appended lines to the file, so there are no + * trailing context lines). + */ + hunk->splittable_into++; +} + static int parse_diff(struct add_p_state *s, const struct pathspec *ps) { struct strvec args = STRVEC_INIT; @@ -472,6 +483,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) eol = pend; if (starts_with(p, "diff ")) { + complete_file(marker, hunk); ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1, file_diff_alloc); file_diff = s->file_diff + s->file_diff_nr - 1; @@ -598,13 +610,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) file_diff->hunk->colored_end = hunk->colored_end; } } - - if (marker == '-' || marker == '+') - /* - * Last hunk ended in non-context line (i.e. it appended lines - * to the file, so there are no trailing context lines). - */ - hunk->splittable_into++; + complete_file(marker, hunk); /* non-colored shorter than colored? */ if (colored_p != colored_pend) { @@ -103,7 +103,8 @@ int init_apply_state(struct apply_state *state, state->linenr = 1; string_list_init_nodup(&state->fn_table); string_list_init_nodup(&state->limit_by_name); - string_list_init_nodup(&state->symlink_changes); + strset_init(&state->removed_symlinks); + strset_init(&state->kept_symlinks); strbuf_init(&state->root, 0); git_apply_config(); @@ -117,7 +118,8 @@ int init_apply_state(struct apply_state *state, void clear_apply_state(struct apply_state *state) { string_list_clear(&state->limit_by_name, 0); - string_list_clear(&state->symlink_changes, 0); + strset_clear(&state->removed_symlinks); + strset_clear(&state->kept_symlinks); strbuf_release(&state->root); /* &state->fn_table is cleared at the end of apply_patch() */ @@ -3814,59 +3816,31 @@ static int check_to_create(struct apply_state *state, return 0; } -static uintptr_t register_symlink_changes(struct apply_state *state, - const char *path, - uintptr_t what) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&state->symlink_changes, path); - if (!ent) { - ent = string_list_insert(&state->symlink_changes, path); - ent->util = (void *)0; - } - ent->util = (void *)(what | ((uintptr_t)ent->util)); - return (uintptr_t)ent->util; -} - -static uintptr_t check_symlink_changes(struct apply_state *state, const char *path) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&state->symlink_changes, path); - if (!ent) - return 0; - return (uintptr_t)ent->util; -} - static void prepare_symlink_changes(struct apply_state *state, struct patch *patch) { for ( ; patch; patch = patch->next) { if ((patch->old_name && S_ISLNK(patch->old_mode)) && (patch->is_rename || patch->is_delete)) /* the symlink at patch->old_name is removed */ - register_symlink_changes(state, patch->old_name, APPLY_SYMLINK_GOES_AWAY); + strset_add(&state->removed_symlinks, patch->old_name); if (patch->new_name && S_ISLNK(patch->new_mode)) /* the symlink at patch->new_name is created or remains */ - register_symlink_changes(state, patch->new_name, APPLY_SYMLINK_IN_RESULT); + strset_add(&state->kept_symlinks, patch->new_name); } } static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *name) { do { - unsigned int change; - while (--name->len && name->buf[name->len] != '/') ; /* scan backwards */ if (!name->len) break; name->buf[name->len] = '\0'; - change = check_symlink_changes(state, name->buf); - if (change & APPLY_SYMLINK_IN_RESULT) + if (strset_contains(&state->kept_symlinks, name->buf)) return 1; - if (change & APPLY_SYMLINK_GOES_AWAY) + if (strset_contains(&state->removed_symlinks, name->buf)) /* * This cannot be "return 0", because we may * see a new one created at a higher level. @@ -4,6 +4,7 @@ #include "hash.h" #include "lockfile.h" #include "string-list.h" +#include "strmap.h" struct repository; @@ -25,20 +26,6 @@ enum apply_verbosity { verbosity_verbose = 1 }; -/* - * We need to keep track of how symlinks in the preimage are - * manipulated by the patches. A patch to add a/b/c where a/b - * is a symlink should not be allowed to affect the directory - * the symlink points at, but if the same patch removes a/b, - * it is perfectly fine, as the patch removes a/b to make room - * to create a directory a/b so that a/b/c can be created. - * - * See also "struct string_list symlink_changes" in "struct - * apply_state". - */ -#define APPLY_SYMLINK_GOES_AWAY 01 -#define APPLY_SYMLINK_IN_RESULT 02 - struct apply_state { const char *prefix; @@ -86,7 +73,16 @@ struct apply_state { /* Various "current state" */ int linenr; /* current line number */ - struct string_list symlink_changes; /* we have to track symlinks */ + /* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + */ + struct strset removed_symlinks; + struct strset kept_symlinks; /* * For "diff-stat" like behaviour, we keep track of the biggest change @@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); int cmd_grep(int argc, const char **argv, const char *prefix); int cmd_hash_object(int argc, const char **argv, const char *prefix); int cmd_help(int argc, const char **argv, const char *prefix); +int cmd_hook(int argc, const char **argv, const char *prefix); int cmd_index_pack(int argc, const char **argv, const char *prefix); int cmd_init_db(int argc, const char **argv, const char *prefix); int cmd_interpret_trailers(int argc, const char **argv, const char *prefix); diff --git a/builtin/am.c b/builtin/am.c index b6be1f1cb1..7de2c89ef2 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -474,7 +474,7 @@ static int run_applypatch_msg_hook(struct am_state *state) int ret; assert(state->msg); - ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); + ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL); if (!ret) { FREE_AND_NULL(state->msg); @@ -1636,7 +1636,7 @@ static void do_commit(const struct am_state *state) const char *reflog_msg, *author, *committer = NULL; struct strbuf sb = STRBUF_INIT; - if (run_hook_le(NULL, "pre-applypatch", NULL)) + if (run_hooks("pre-applypatch")) exit(1); if (write_cache_as_tree(&tree, 0, NULL)) @@ -1688,7 +1688,7 @@ static void do_commit(const struct am_state *state) fclose(fp); } - run_hook_le(NULL, "post-applypatch", NULL); + run_hooks("post-applypatch"); strbuf_release(&sb); } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index d94050e6c1..7b3f42950e 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -73,14 +73,17 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, struct object_info oi = OBJECT_INFO_INIT; struct strbuf sb = STRBUF_INIT; unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; + unsigned get_oid_flags = GET_OID_RECORD_PATH | GET_OID_ONLY_TO_DIE; const char *path = force_path; + const int opt_cw = (opt == 'c' || opt == 'w'); + if (!path && opt_cw) + get_oid_flags |= GET_OID_REQUIRE_PATH; if (unknown_type) flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; - if (get_oid_with_context(the_repository, obj_name, - GET_OID_RECORD_PATH, - &oid, &obj_context)) + if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid, + &obj_context)) die("Not a valid object name %s", obj_name); if (!path) @@ -112,9 +115,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, return !has_object_file(&oid); case 'w': - if (!path) - die("git cat-file --filters %s: <object> must be " - "<sha1:path>", obj_name); if (filter_object(path, obj_context.mode, &oid, &buf, &size)) @@ -122,10 +122,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 'c': - if (!path) - die("git cat-file --textconv %s: <object> must be <sha1:path>", - obj_name); - if (textconv_object(the_repository, path, obj_context.mode, &oid, 1, &buf, &size)) break; @@ -618,12 +614,6 @@ static int batch_objects(struct batch_options *opt) return retval; } -static const char * const cat_file_usage[] = { - N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"), - N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"), - NULL -}; - static int git_cat_file_config(const char *var, const char *value, void *cb) { if (userdiff_config(var, value) < 0) @@ -654,90 +644,138 @@ static int batch_option_callback(const struct option *opt, int cmd_cat_file(int argc, const char **argv, const char *prefix) { int opt = 0; + int opt_cw = 0; + int opt_epts = 0; const char *exp_type = NULL, *obj_name = NULL; struct batch_options batch = {0}; int unknown_type = 0; + const char * const usage[] = { + N_("git cat-file <type> <object>"), + N_("git cat-file (-e | -p) <object>"), + N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"), + N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n" + " [--buffer] [--follow-symlinks] [--unordered]\n" + " [--textconv | --filters]"), + N_("git cat-file (--textconv | --filters)\n" + " [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"), + NULL + }; const struct option options[] = { - OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), - OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), - OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + /* Simple queries */ + OPT_GROUP(N_("Check object existence or emit object contents")), OPT_CMDMODE('e', NULL, &opt, - N_("exit with zero when there's no error"), 'e'), - OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_CMDMODE(0, "textconv", &opt, - N_("for blob objects, run textconv on object's content"), 'c'), - OPT_CMDMODE(0, "filters", &opt, - N_("for blob objects, run filters on object's content"), 'w'), - OPT_STRING(0, "path", &force_path, N_("blob"), - N_("use a specific path for --textconv/--filters")), + N_("check if <object> exists"), 'e'), + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print <object> content"), 'p'), + + OPT_GROUP(N_("Emit [broken] object attributes")), + OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), - OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), - OPT_CALLBACK_F(0, "batch", &batch, "format", - N_("show info and content of objects fed from the standard input"), + /* Batch mode */ + OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), + OPT_CALLBACK_F(0, "batch", &batch, N_("format"), + N_("show full <object> or <rev> contents"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), - OPT_CALLBACK_F(0, "batch-check", &batch, "format", - N_("show info about objects fed from the standard input"), + OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"), + N_("like --batch, but don't emit <contents>"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), + OPT_CMDMODE(0, "batch-all-objects", &opt, + N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'), + /* Batch-specific options */ + OPT_GROUP(N_("Change or optimize batch output")), + OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, - N_("follow in-tree symlinks (used with --batch or --batch-check)")), - OPT_BOOL(0, "batch-all-objects", &batch.all_objects, - N_("show all objects with --batch or --batch-check")), + N_("follow in-tree symlinks")), OPT_BOOL(0, "unordered", &batch.unordered, - N_("do not order --batch-all-objects output")), + N_("do not order objects before emitting them")), + /* Textconv options, stand-ole*/ + OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")), + OPT_CMDMODE(0, "textconv", &opt, + N_("run textconv on object's content"), 'c'), + OPT_CMDMODE(0, "filters", &opt, + N_("run filters on object's content"), 'w'), + OPT_STRING(0, "path", &force_path, N_("blob|tree"), + N_("use a <path> for (--textconv | --filters); Not with 'batch'")), OPT_END() }; git_config(git_cat_file_config, NULL); batch.buffer_output = -1; - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); - - if (opt) { - if (batch.enabled && (opt == 'c' || opt == 'w')) - batch.cmdmode = opt; - else if (argc == 1) - obj_name = argv[0]; - else - usage_with_options(cat_file_usage, options); - } - if (!opt && !batch.enabled) { - if (argc == 2) { - exp_type = argv[0]; - obj_name = argv[1]; - } else - usage_with_options(cat_file_usage, options); - } - if (batch.enabled) { - if (batch.cmdmode != opt || argc) - usage_with_options(cat_file_usage, options); - if (batch.cmdmode && batch.all_objects) - die("--batch-all-objects cannot be combined with " - "--textconv nor with --filters"); - } - if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { - usage_with_options(cat_file_usage, options); - } + argc = parse_options(argc, argv, prefix, options, usage, 0); + opt_cw = (opt == 'c' || opt == 'w'); + opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); - if (force_path && opt != 'c' && opt != 'w') { - error("--path=<path> needs --textconv or --filters"); - usage_with_options(cat_file_usage, options); - } + /* --batch-all-objects? */ + if (opt == 'b') + batch.all_objects = 1; - if (force_path && batch.enabled) { - error("options '--path=<path>' and '--batch' cannot be used together"); - usage_with_options(cat_file_usage, options); - } + /* Option compatibility */ + if (force_path && !opt_cw) + usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"), + usage, options, + "--path", _("path|tree-ish"), "--filters", + "--textconv"); + /* Option compatibility with batch mode */ + if (batch.enabled) + ; + else if (batch.follow_symlinks) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--follow-symlinks"); + else if (batch.buffer_output >= 0) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--buffer"); + else if (batch.all_objects) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--batch-all-objects"); + + /* Batch defaults */ if (batch.buffer_output < 0) batch.buffer_output = batch.all_objects; - if (batch.enabled) + /* Return early if we're in batch mode? */ + if (batch.enabled) { + if (opt_cw) + batch.cmdmode = opt; + else if (opt && opt != 'b') + usage_msg_optf(_("'-%c' is incompatible with batch mode"), + usage, options, opt); + else if (argc) + usage_msg_opt(_("batch modes take no arguments"), usage, + options); + return batch_objects(&batch); + } + + if (opt) { + if (!argc && opt == 'c') + usage_msg_optf(_("<rev> required with '%s'"), + usage, options, "--textconv"); + else if (!argc && opt == 'w') + usage_msg_optf(_("<rev> required with '%s'"), + usage, options, "--filters"); + else if (!argc && opt_epts) + usage_msg_optf(_("<object> required with '-%c'"), + usage, options, opt); + else if (argc == 1) + obj_name = argv[0]; + else + usage_msg_opt(_("too many arguments"), usage, options); + } else if (!argc) { + usage_with_options(usage, options); + } else if (argc != 2) { + usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"), + usage, options, argc); + } else if (argc) { + exp_type = argv[0]; + obj_name = argv[1]; + } if (unknown_type && opt != 't' && opt != 's') die("git cat-file --allow-unknown-type: use with -s or -t"); diff --git a/builtin/checkout.c b/builtin/checkout.c index cc804ba8e1..8f010412a9 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -9,6 +9,7 @@ #include "config.h" #include "diff.h" #include "dir.h" +#include "hook.h" #include "ll-merge.h" #include "lockfile.h" #include "merge-recursive.h" @@ -114,7 +115,7 @@ static void branch_info_release(struct branch_info *info) static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { - return run_hook_le(NULL, "post-checkout", + return run_hooks_l("post-checkout", oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()), oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()), changed ? "1" : "0", NULL); diff --git a/builtin/clone.c b/builtin/clone.c index 727e16e0ae..9c29093b35 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -32,6 +32,7 @@ #include "connected.h" #include "packfile.h" #include "list-objects-filter-options.h" +#include "hook.h" /* * Overall FIXMEs: @@ -705,7 +706,7 @@ static int checkout(int submodule_progress) if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()), + err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()), oid_to_hex(&oid), "1", NULL); if (!err && (option_recurse_submodules.nr > 0)) { @@ -862,7 +863,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *refs, *remote_head; struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; - struct ref *mapped_refs; + struct ref *mapped_refs = NULL; const struct ref *ref; struct strbuf key = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; @@ -1184,7 +1185,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = transport_get_remote_refs(transport, &transport_ls_refs_options); - if (refs) { + if (refs) + mapped_refs = wanted_peer_refs(refs, &remote->fetch); + + if (mapped_refs) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); /* @@ -1193,8 +1197,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ initialize_repository_version(hash_algo, 1); repo_set_hash_algo(the_repository, hash_algo); - - mapped_refs = wanted_peer_refs(refs, &remote->fetch); /* * transport_get_remote_refs() may return refs with null sha-1 * in mapped_refs (see struct transport->get_refs_list @@ -1240,7 +1242,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_branch, remote_name); warning(_("You appear to have cloned an empty repository.")); - mapped_refs = NULL; our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -1271,7 +1272,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (is_local) clone_local(path, git_dir); - else if (refs && complete_refs_before_fetch) { + else if (mapped_refs && complete_refs_before_fetch) { if (transport_fetch_refs(transport, mapped_refs)) die(_("remote transport reported error")); } diff --git a/builtin/fetch.c b/builtin/fetch.c index 5f06b21f8e..5b3b18a72f 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -76,6 +76,7 @@ static struct transport *gtransport; static struct transport *gsecondary; static const char *submodule_prefix = ""; static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND; static int shown_url = 0; static struct refspec refmap = REFSPEC_INIT_FETCH; @@ -167,7 +168,7 @@ static struct option builtin_fetch_options[] = { N_("prune remote-tracking branches no longer on remote")), OPT_BOOL('P', "prune-tags", &prune_tags, N_("prune local tags no longer on remote and clobber changed tags")), - OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"), N_("control recursive fetching of submodules"), PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "dry-run", &dry_run, @@ -2019,6 +2020,28 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + + if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT) + recurse_submodules = recurse_submodules_cli; + + if (negotiate_only) { + switch (recurse_submodules_cli) { + case RECURSE_SUBMODULES_OFF: + case RECURSE_SUBMODULES_DEFAULT: + /* + * --negotiate-only should never recurse into + * submodules. Skip it by setting recurse_submodules to + * RECURSE_SUBMODULES_OFF. + */ + recurse_submodules = RECURSE_SUBMODULES_OFF; + break; + + default: + die(_("options '%s' and '%s' cannot be used together"), + "--negotiate-only", "--recurse-submodules"); + } + } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { int *sfjc = submodule_fetch_jobs_config == -1 ? &submodule_fetch_jobs_config : NULL; @@ -2100,7 +2123,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) gtransport->smart_options->acked_commits = &acked_commits; } else { warning(_("protocol does not support --negotiate-only, exiting")); - return 1; + result = 1; + goto cleanup; } if (server_options.nr) gtransport->server_options = &server_options; @@ -2156,7 +2180,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) strvec_clear(&options); } - string_list_clear(&list, 0); + /* + * Skip irrelevant tasks because we know objects were not + * fetched. + * + * NEEDSWORK: as a future optimization, we can return early + * whenever objects were not fetched e.g. if we already have all + * of them. + */ + if (negotiate_only) + goto cleanup; prepare_repo_settings(the_repository); if (fetch_write_commit_graph > 0 || @@ -2175,5 +2208,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (enable_auto_gc) run_auto_maintenance(verbosity < 0); + cleanup: + string_list_clear(&list, 0); return result; } diff --git a/builtin/gc.c b/builtin/gc.c index 8e60ef1eab..ffaf0daf5d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -32,6 +32,7 @@ #include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "hook.h" #define FAILED_RUN "failed to run %s" @@ -394,7 +395,7 @@ static int need_to_gc(void) else return 0; - if (run_hook_le(NULL, "pre-auto-gc", NULL)) + if (run_hooks("pre-auto-gc")) return 0; return 1; } diff --git a/builtin/hook.c b/builtin/hook.c new file mode 100644 index 0000000000..54e5c6ec93 --- /dev/null +++ b/builtin/hook.c @@ -0,0 +1,84 @@ +#include "cache.h" +#include "builtin.h" +#include "config.h" +#include "hook.h" +#include "parse-options.h" +#include "strbuf.h" +#include "strvec.h" + +#define BUILTIN_HOOK_RUN_USAGE \ + N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]") + +static const char * const builtin_hook_usage[] = { + BUILTIN_HOOK_RUN_USAGE, + NULL +}; + +static const char * const builtin_hook_run_usage[] = { + BUILTIN_HOOK_RUN_USAGE, + NULL +}; + +static int run(int argc, const char **argv, const char *prefix) +{ + int i; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + int ignore_missing = 0; + const char *hook_name; + struct option run_options[] = { + OPT_BOOL(0, "ignore-missing", &ignore_missing, + N_("silently ignore missing requested <hook-name>")), + OPT_END(), + }; + int ret; + + argc = parse_options(argc, argv, prefix, run_options, + builtin_hook_run_usage, + PARSE_OPT_KEEP_DASHDASH); + + if (!argc) + goto usage; + + /* + * Having a -- for "run" when providing <hook-args> is + * mandatory. + */ + if (argc > 1 && strcmp(argv[1], "--") && + strcmp(argv[1], "--end-of-options")) + goto usage; + + /* Add our arguments, start after -- */ + for (i = 2 ; i < argc; i++) + strvec_push(&opt.args, argv[i]); + + /* Need to take into account core.hooksPath */ + git_config(git_default_config, NULL); + + hook_name = argv[0]; + if (!ignore_missing) + opt.error_if_missing = 1; + ret = run_hooks_opt(hook_name, &opt); + if (ret < 0) /* error() return */ + ret = 1; + return ret; +usage: + usage_with_options(builtin_hook_run_usage, run_options); +} + +int cmd_hook(int argc, const char **argv, const char *prefix) +{ + struct option builtin_hook_options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, builtin_hook_options, + builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION); + if (!argc) + goto usage; + + if (!strcmp(argv[0], "run")) + return run(argc, argv, prefix); + +usage: + usage_with_options(builtin_hook_usage, builtin_hook_options); +} diff --git a/builtin/merge.c b/builtin/merge.c index 74e53cf20a..133831d42f 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -490,7 +490,7 @@ static void finish(struct commit *head_commit, } /* Run a post-merge hook */ - run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + run_hooks_l("post-merge", squash ? "1" : "0", NULL); apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); @@ -1273,7 +1273,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list *remoteheads, *p; + struct commit_list *remoteheads = NULL, *p; void *branch_to_free; int orig_argc = argc; @@ -1752,6 +1752,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ret = suggest_conflicts(); done: + if (!automerge_was_ok) { + free_commit_list(common); + free_commit_list(remoteheads); + } strbuf_release(&buf); free(branch_to_free); return ret; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 27f60153a6..138e3c30a2 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -527,7 +527,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; + int all = 0, annotate_stdin = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), @@ -538,7 +538,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), - OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")), + OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use annotate-stdin instead")), + OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), @@ -554,11 +555,19 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) init_commit_rev_name(&rev_names); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (all + transform_stdin + !!argc > 1) { + + if (transform_stdin) { + warning("--stdin is deprecated. Please use --annotate-stdin instead, " + "which is functionally equivalent.\n" + "This option will be removed in a future release."); + annotate_stdin = 1; + } + + if (all + annotate_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); } - if (all || transform_stdin) + if (all || annotate_stdin) cutoff = 0; for (; argc; argc--, argv++) { @@ -613,15 +622,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) for_each_ref(name_ref, &data); name_tips(); - if (transform_stdin) { - char buffer[2048]; + if (annotate_stdin) { + struct strbuf sb = STRBUF_INIT; - while (!feof(stdin)) { - char *p = fgets(buffer, sizeof(buffer), stdin); - if (!p) - break; - name_rev_line(p, &data); + while (strbuf_getline(&sb, stdin) != EOF) { + strbuf_addch(&sb, '\n'); + name_rev_line(sb.buf, &data); } + strbuf_release(&sb); } else if (all) { int i, max; diff --git a/builtin/pull.c b/builtin/pull.c index 100cbf9fb8..8f37880a48 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -1038,14 +1038,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix) oidclr(&orig_head); if (opt_rebase) { - int autostash = config_autostash; - if (opt_autostash != -1) - autostash = opt_autostash; + if (opt_autostash == -1) + opt_autostash = config_autostash; if (is_null_oid(&orig_head) && !is_cache_unborn()) die(_("Updating an unborn branch with changes added to the index.")); - if (!autostash) + if (!opt_autostash) require_clean_work_tree(the_repository, N_("pull with rebase"), _("please commit or stash them."), 1, 0); diff --git a/builtin/rebase.c b/builtin/rebase.c index 36490d06c8..2e6d8fa34e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -28,6 +28,7 @@ #include "sequencer.h" #include "rebase-interactive.h" #include "reset.h" +#include "hook.h" #define DEFAULT_REFLOG_ACTION "rebase" @@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && - run_hook_le(NULL, "pre-rebase", options.upstream_arg, + run_hooks_l("pre-rebase", options.upstream_arg, argc ? argv[0] : NULL, NULL)) die(_("The pre-rebase hook refused to rebase.")); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 9f4a0b816c..c427ca09aa 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -581,32 +581,19 @@ static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp) return strbuf_detach(&buf, NULL); } -/* - * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing - * after dropping "_commit" from its name and possibly moving it out - * of commit.c - */ static char *find_header(const char *msg, size_t len, const char *key, const char **next_line) { - int key_len = strlen(key); - const char *line = msg; - - while (line && line < msg + len) { - const char *eol = strchrnul(line, '\n'); - - if ((msg + len <= eol) || line == eol) - return NULL; - if (line + key_len < eol && - !memcmp(line, key, key_len) && line[key_len] == ' ') { - int offset = key_len + 1; - if (next_line) - *next_line = *eol ? eol + 1 : eol; - return xmemdupz(line + offset, (eol - line) - offset); - } - line = *eol ? eol + 1 : NULL; - } - return NULL; + size_t out_len; + const char *val = find_header_mem(msg, len, key, &out_len); + + if (!val) + return NULL; + + if (next_line) + *next_line = val + out_len + 1; + + return xmemdupz(val, out_len); } /* @@ -1424,9 +1411,12 @@ static const char *push_to_checkout(unsigned char *hash, struct strvec *env, const char *work_tree) { + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); - if (run_hook_le(env->v, push_to_checkout_hook, - hash_to_hex(hash), NULL)) + strvec_pushv(&opt.env, env->v); + strvec_push(&opt.args, hash_to_hex(hash)); + if (run_hooks_opt(push_to_checkout_hook, &opt)) return "push-to-checkout hook declined"; else return NULL; diff --git a/builtin/reflog.c b/builtin/reflog.c index a4b1dd27e1..85b838720c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -12,15 +12,6 @@ #include "reachable.h" #include "worktree.h" -/* NEEDSWORK: switch to using parse_options */ -static const char reflog_expire_usage[] = -N_("git reflog expire [--expire=<time>] " - "[--expire-unreachable=<time>] " - "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] " - "[--verbose] [--all] <refs>..."); -static const char reflog_delete_usage[] = -N_("git reflog delete [--rewrite] [--updateref] " - "[--dry-run | -n] [--verbose] <refs>..."); static const char reflog_exists_usage[] = N_("git reflog exists <ref>"); @@ -29,6 +20,7 @@ static timestamp_t default_reflog_expire_unreachable; struct cmd_reflog_expire_cb { int stalefix; + int explicit_expiry; timestamp_t expire_total; timestamp_t expire_unreachable; int recno; @@ -520,18 +512,18 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) return 0; } -static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref) +static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref) { struct reflog_expire_cfg *ent; - if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH)) + if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH)) return; /* both given explicitly -- nothing to tweak */ for (ent = reflog_expire_cfg; ent; ent = ent->next) { if (!wildmatch(ent->pattern, ref, 0)) { - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = ent->expire_total; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = ent->expire_unreachable; return; } @@ -541,29 +533,89 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c * If unconfigured, make stash never expire */ if (!strcmp(ref, "refs/stash")) { - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = 0; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = 0; return; } /* Nothing matched -- use the default value */ - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = default_reflog_expire; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = default_reflog_expire_unreachable; } +static const char * reflog_expire_usage[] = { + N_("git reflog expire [--expire=<time>] " + "[--expire-unreachable=<time>] " + "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] " + "[--verbose] [--all] <refs>..."), + NULL +}; + +static int expire_unreachable_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct cmd_reflog_expire_cb *cmd = opt->value; + + if (parse_expiry_date(arg, &cmd->expire_unreachable)) + die(_("invalid timestamp '%s' given to '--%s'"), + arg, opt->long_name); + + cmd->explicit_expiry |= EXPIRE_UNREACH; + return 0; +} + +static int expire_total_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct cmd_reflog_expire_cb *cmd = opt->value; + + if (parse_expiry_date(arg, &cmd->expire_total)) + die(_("invalid timestamp '%s' given to '--%s'"), + arg, opt->long_name); + + cmd->explicit_expiry |= EXPIRE_TOTAL; + return 0; +} + static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { struct cmd_reflog_expire_cb cmd = { 0 }; timestamp_t now = time(NULL); int i, status, do_all, all_worktrees = 1; - int explicit_expiry = 0; unsigned int flags = 0; int verbose = 0; reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; + const struct option options[] = { + OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), + EXPIRE_REFLOGS_DRY_RUN), + OPT_BIT(0, "rewrite", &flags, + N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"), + EXPIRE_REFLOGS_REWRITE), + OPT_BIT(0, "updateref", &flags, + N_("update the reference to the value of the top reflog entry"), + EXPIRE_REFLOGS_UPDATE_REF), + OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")), + OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"), + N_("prune entries older than the specified time"), + PARSE_OPT_NONEG, + expire_total_callback), + OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"), + N_("prune entries older than <time> that are not reachable from the current tip of the branch"), + PARSE_OPT_NONEG, + expire_unreachable_callback), + OPT_BOOL(0, "stale-fix", &cmd.stalefix, + N_("prune any reflog entries that point to broken commits")), + OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")), + OPT_BOOL(1, "single-worktree", &all_worktrees, + N_("limits processing to reflogs from the current worktree only.")), + OPT_END() + }; default_reflog_expire_unreachable = now - 30 * 24 * 3600; default_reflog_expire = now - 90 * 24 * 3600; @@ -572,45 +624,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; do_all = status = 0; + cmd.explicit_expiry = 0; cmd.expire_total = default_reflog_expire; cmd.expire_unreachable = default_reflog_expire_unreachable; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (skip_prefix(arg, "--expire=", &arg)) { - if (parse_expiry_date(arg, &cmd.expire_total)) - die(_("'%s' is not a valid timestamp"), arg); - explicit_expiry |= EXPIRE_TOTAL; - } - else if (skip_prefix(arg, "--expire-unreachable=", &arg)) { - if (parse_expiry_date(arg, &cmd.expire_unreachable)) - die(_("'%s' is not a valid timestamp"), arg); - explicit_expiry |= EXPIRE_UNREACH; - } - else if (!strcmp(arg, "--stale-fix")) - cmd.stalefix = 1; - else if (!strcmp(arg, "--rewrite")) - flags |= EXPIRE_REFLOGS_REWRITE; - else if (!strcmp(arg, "--updateref")) - flags |= EXPIRE_REFLOGS_UPDATE_REF; - else if (!strcmp(arg, "--all")) - do_all = 1; - else if (!strcmp(arg, "--single-worktree")) - all_worktrees = 0; - else if (!strcmp(arg, "--verbose")) - verbose = 1; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_expire_usage)); - else - break; - } + argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0); if (verbose) should_prune_fn = should_expire_reflog_ent_verbose; @@ -657,7 +675,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN), }; - set_reflog_expiry_param(&cb.cmd, explicit_expiry, item->string); + set_reflog_expiry_param(&cb.cmd, item->string); status |= reflog_expire(item->string, flags, reflog_expiry_prepare, should_prune_fn, @@ -667,7 +685,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) string_list_clear(&collected.reflogs, 0); } - for (; i < argc; i++) { + for (i = 0; i < argc; i++) { char *ref; struct expire_reflog_policy_cb cb = { .cmd = cmd }; @@ -675,7 +693,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) status |= error(_("%s points nowhere!"), argv[i]); continue; } - set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); + set_reflog_expiry_param(&cb.cmd, ref); status |= reflog_expire(ref, flags, reflog_expiry_prepare, should_prune_fn, @@ -696,6 +714,12 @@ static int count_reflog_ent(struct object_id *ooid, struct object_id *noid, return 0; } +static const char * reflog_delete_usage[] = { + N_("git reflog delete [--rewrite] [--updateref] " + "[--dry-run | -n] [--verbose] <refs>..."), + NULL +}; + static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { struct cmd_reflog_expire_cb cmd = { 0 }; @@ -703,34 +727,28 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) unsigned int flags = 0; int verbose = 0; reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (!strcmp(arg, "--rewrite")) - flags |= EXPIRE_REFLOGS_REWRITE; - else if (!strcmp(arg, "--updateref")) - flags |= EXPIRE_REFLOGS_UPDATE_REF; - else if (!strcmp(arg, "--verbose")) - verbose = 1; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_delete_usage)); - else - break; - } + const struct option options[] = { + OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), + EXPIRE_REFLOGS_DRY_RUN), + OPT_BIT(0, "rewrite", &flags, + N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"), + EXPIRE_REFLOGS_REWRITE), + OPT_BIT(0, "updateref", &flags, + N_("update the reference to the value of the top reflog entry"), + EXPIRE_REFLOGS_UPDATE_REF), + OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0); if (verbose) should_prune_fn = should_expire_reflog_ent_verbose; - if (argc - i < 1) + if (argc < 1) return error(_("no reflog specified to delete")); - for ( ; i < argc; i++) { + for (i = 0; i < argc; i++) { const char *spec = strstr(argv[i], "@{"); char *ep, *ref; int recno; diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 679c107036..2b0e1db2d2 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -471,6 +471,9 @@ static int sparse_checkout_init(int argc, const char **argv) FILE *fp; /* assume we are in a fresh repo, but update the sparse-checkout file */ + if (safe_create_leading_directories(sparse_filename)) + die(_("unable to create leading directories of %s"), + sparse_filename); fp = xfopen(sparse_filename, "w"); if (!fp) die(_("failed to open '%s'"), sparse_filename); diff --git a/builtin/stash.c b/builtin/stash.c index 1ef2017c59..9638c56303 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1539,8 +1539,12 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - if (startup_info->original_cwd) + if (startup_info->original_cwd) { cp.dir = startup_info->original_cwd; + strvec_pushf(&cp.env_array, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, + the_repository->worktree); + } strvec_pushl(&cp.args, "clean", "--force", "--quiet", "-d", ":/", NULL); if (include_untracked == INCLUDE_ALL_FILES) @@ -1815,8 +1819,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); else if (*argv[0] != '-') - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_usage, options); + usage_msg_optf(_("unknown subcommand: %s"), + git_stash_usage, options, argv[0]); /* Assume 'stash push' */ strvec_push(&args, "push"); diff --git a/builtin/update-index.c b/builtin/update-index.c index 187203e8bb..7e0a0d9bf8 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -787,6 +787,17 @@ static int refresh(struct refresh_params *o, unsigned int flag) setup_work_tree(); read_cache(); *o->has_errors |= refresh_cache(o->flags | flag); + if (has_racy_timestamp(&the_index)) { + /* + * Even if nothing else has changed, updating the file + * increases the chance that racy timestamps become + * non-racy, helping future run-time performance. + * We do that even in case of "errors" returned by + * refresh_cache() as these are no actual errors. + * cmd_status() does the same. + */ + active_cache_changed |= SOMETHING_CHANGED; + } return 0; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 2838254f7f..0d0809276f 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -382,21 +382,17 @@ done: * is_junk is cleared, but do return appropriate code when hook fails. */ if (!ret && opts->checkout) { - const char *hook = find_hook("post-checkout"); - if (hook) { - const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; - struct child_process cp = CHILD_PROCESS_INIT; - cp.no_stdin = 1; - cp.stdout_to_stderr = 1; - cp.dir = path; - strvec_pushv(&cp.env_array, env); - cp.trace2_hook_name = "post-checkout"; - strvec_pushl(&cp.args, absolute_path(hook), - oid_to_hex(null_oid()), - oid_to_hex(&commit->object.oid), - "1", NULL); - ret = run_command(&cp); - } + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + + strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); + strvec_pushl(&opt.args, + oid_to_hex(null_oid()), + oid_to_hex(&commit->object.oid), + "1", + NULL); + opt.dir = path; + + ret = run_hooks_opt("post-checkout", &opt); } strvec_clear(&child_env); @@ -889,6 +889,7 @@ void *read_blob_data_from_index(struct index_state *, const char *, unsigned lon #define CE_MATCH_IGNORE_FSMONITOR 0X20 int is_racy_timestamp(const struct index_state *istate, const struct cache_entry *ce); +int has_racy_timestamp(struct index_state *istate); int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int); int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int); @@ -1375,6 +1376,7 @@ struct object_context { #define GET_OID_FOLLOW_SYMLINKS 0100 #define GET_OID_RECORD_PATH 0200 #define GET_OID_ONLY_TO_DIE 04000 +#define GET_OID_REQUIRE_PATH 010000 #define GET_OID_DISAMBIGUATORS \ (GET_OID_COMMIT | GET_OID_COMMITTISH | \ diff --git a/command-list.txt b/command-list.txt index 675c28f0bd..9bd6f3c48f 100644 --- a/command-list.txt +++ b/command-list.txt @@ -103,6 +103,7 @@ git-grep mainporcelain info git-gui mainporcelain git-hash-object plumbingmanipulators git-help ancillaryinterrogators complete +git-hook purehelpers git-http-backend synchingrepositories git-http-fetch synchelpers git-http-push synchelpers @@ -21,6 +21,7 @@ #include "commit-reach.h" #include "run-command.h" #include "shallow.h" +#include "hook.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -1631,12 +1632,20 @@ struct commit_list **commit_list_append(struct commit *commit, return &new_commit->next; } -const char *find_commit_header(const char *msg, const char *key, size_t *out_len) +const char *find_header_mem(const char *msg, size_t len, + const char *key, size_t *out_len) { int key_len = strlen(key); const char *line = msg; - while (line) { + /* + * NEEDSWORK: It's possible for strchrnul() to scan beyond the range + * given by len. However, current callers are safe because they compute + * len by scanning a NUL-terminated block of memory starting at msg. + * Nonetheless, it would be better to ensure the function does not look + * at msg beyond the len provided by the caller. + */ + while (line && line < msg + len) { const char *eol = strchrnul(line, '\n'); if (line == eol) @@ -1653,6 +1662,10 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len return NULL; } +const char *find_commit_header(const char *msg, const char *key, size_t *out_len) +{ + return find_header_mem(msg, strlen(msg), key, out_len); +} /* * Inspect the given string and determine the true "end" of the log message, in * order to find where to put a new Signed-off-by trailer. Ignored are @@ -1702,22 +1715,22 @@ size_t ignore_non_trailer(const char *buf, size_t len) int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) { - struct strvec hook_env = STRVEC_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; va_list args; - int ret; + const char *arg; - strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); + strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file); /* * Let the hook know that no editor will be launched. */ if (!editor_is_used) - strvec_push(&hook_env, "GIT_EDITOR=:"); + strvec_push(&opt.env, "GIT_EDITOR=:"); va_start(args, name); - ret = run_hook_ve(hook_env.v, name, args); + while ((arg = va_arg(args, const char *))) + strvec_push(&opt.args, arg); va_end(args); - strvec_clear(&hook_env); - return ret; + return run_hooks_opt(name, &opt); } @@ -290,12 +290,17 @@ void free_commit_extra_headers(struct commit_extra_header *extra); /* * Search the commit object contents given by "msg" for the header "key". + * Reads up to "len" bytes of "msg". * Returns a pointer to the start of the header contents, or NULL. The length * of the header, up to the first newline, is returned via out_len. * * Note that some headers (like mergetag) may be multi-line. It is the caller's * responsibility to parse further in this case! */ +const char *find_header_mem(const char *msg, size_t len, + const char *key, + size_t *out_len); + const char *find_commit_header(const char *msg, const char *key, size_t *out_len); diff --git a/compat/qsort_s.c b/compat/qsort_s.c index 52d1f0a73d..0f7ff30f5f 100644 --- a/compat/qsort_s.c +++ b/compat/qsort_s.c @@ -49,21 +49,15 @@ int git_qsort_s(void *b, size_t n, size_t s, int (*cmp)(const void *, const void *, void *), void *ctx) { const size_t size = st_mult(n, s); - char buf[1024]; + char *tmp; if (!n) return 0; if (!b || !cmp) return -1; - if (size < sizeof(buf)) { - /* The temporary array fits on the small on-stack buffer. */ - msort_with_tmp(b, n, s, cmp, buf, ctx); - } else { - /* It's somewhat large, so malloc it. */ - char *tmp = xmalloc(size); - msort_with_tmp(b, n, s, cmp, tmp, ctx); - free(tmp); - } + tmp = xmalloc(size); + msort_with_tmp(b, n, s, cmp, tmp, ctx); + free(tmp); return 0; } diff --git a/compat/winansi.c b/compat/winansi.c index 4fceecf14c..936a80a5f0 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -3,6 +3,12 @@ */ #undef NOGDI + +/* + * Including the appropriate header file for RtlGenRandom causes MSVC to see a + * redefinition of types in an incompatible way when including headers below. + */ +#undef HAVE_RTLGENRANDOM #include "../git-compat-util.h" #include <wingdi.h> #include <winreg.h> @@ -120,6 +120,22 @@ static long config_buf_ftell(struct config_source *conf) return conf->u.buf.pos; } +struct config_include_data { + int depth; + config_fn_t fn; + void *data; + const struct config_options *opts; + struct git_config_source *config_source; + + /* + * All remote URLs discovered when reading all config files. + */ + struct string_list *remote_urls; +}; +#define CONFIG_INCLUDE_INIT { 0 } + +static int git_config_include(const char *var, const char *value, void *data); + #define MAX_INCLUDE_DEPTH 10 static const char include_depth_advice[] = N_( "exceeded maximum include depth (%d) while including\n" @@ -294,9 +310,92 @@ static int include_by_branch(const char *cond, size_t cond_len) return ret; } -static int include_condition_is_true(const struct config_options *opts, +static int add_remote_url(const char *var, const char *value, void *data) +{ + struct string_list *remote_urls = data; + const char *remote_name; + size_t remote_name_len; + const char *key; + + if (!parse_config_key(var, "remote", &remote_name, &remote_name_len, + &key) && + remote_name && + !strcmp(key, "url")) + string_list_append(remote_urls, value); + return 0; +} + +static void populate_remote_urls(struct config_include_data *inc) +{ + struct config_options opts; + + struct config_source *store_cf = cf; + struct key_value_info *store_kvi = current_config_kvi; + enum config_scope store_scope = current_parsing_scope; + + opts = *inc->opts; + opts.unconditional_remote_url = 1; + + cf = NULL; + current_config_kvi = NULL; + current_parsing_scope = 0; + + inc->remote_urls = xmalloc(sizeof(*inc->remote_urls)); + string_list_init_dup(inc->remote_urls); + config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts); + + cf = store_cf; + current_config_kvi = store_kvi; + current_parsing_scope = store_scope; +} + +static int forbid_remote_url(const char *var, const char *value, void *data) +{ + const char *remote_name; + size_t remote_name_len; + const char *key; + + if (!parse_config_key(var, "remote", &remote_name, &remote_name_len, + &key) && + remote_name && + !strcmp(key, "url")) + die(_("remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url")); + return 0; +} + +static int at_least_one_url_matches_glob(const char *glob, int glob_len, + struct string_list *remote_urls) +{ + struct strbuf pattern = STRBUF_INIT; + struct string_list_item *url_item; + int found = 0; + + strbuf_add(&pattern, glob, glob_len); + for_each_string_list_item(url_item, remote_urls) { + if (!wildmatch(pattern.buf, url_item->string, WM_PATHNAME)) { + found = 1; + break; + } + } + strbuf_release(&pattern); + return found; +} + +static int include_by_remote_url(struct config_include_data *inc, + const char *cond, size_t cond_len) +{ + if (inc->opts->unconditional_remote_url) + return 1; + if (!inc->remote_urls) + populate_remote_urls(inc); + return at_least_one_url_matches_glob(cond, cond_len, + inc->remote_urls); +} + +static int include_condition_is_true(struct config_include_data *inc, const char *cond, size_t cond_len) { + const struct config_options *opts = inc->opts; if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) return include_by_gitdir(opts, cond, cond_len, 0); @@ -304,12 +403,15 @@ static int include_condition_is_true(const struct config_options *opts, return include_by_gitdir(opts, cond, cond_len, 1); else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) return include_by_branch(cond, cond_len); + else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, + &cond_len)) + return include_by_remote_url(inc, cond, cond_len); /* unknown conditionals are always false */ return 0; } -int git_config_include(const char *var, const char *value, void *data) +static int git_config_include(const char *var, const char *value, void *data) { struct config_include_data *inc = data; const char *cond, *key; @@ -328,9 +430,15 @@ int git_config_include(const char *var, const char *value, void *data) ret = handle_path_include(value, inc); if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) && - (cond && include_condition_is_true(inc->opts, cond, cond_len)) && - !strcmp(key, "path")) + cond && include_condition_is_true(inc, cond, cond_len) && + !strcmp(key, "path")) { + config_fn_t old_fn = inc->fn; + + if (inc->opts->unconditional_remote_url) + inc->fn = forbid_remote_url; ret = handle_path_include(value, inc); + inc->fn = old_fn; + } return ret; } @@ -1929,11 +2037,13 @@ int config_with_options(config_fn_t fn, void *data, const struct config_options *opts) { struct config_include_data inc = CONFIG_INCLUDE_INIT; + int ret; if (opts->respect_includes) { inc.fn = fn; inc.data = data; inc.opts = opts; + inc.config_source = config_source; fn = git_config_include; data = &inc; } @@ -1946,17 +2056,23 @@ int config_with_options(config_fn_t fn, void *data, * regular lookup sequence. */ if (config_source && config_source->use_stdin) { - return git_config_from_stdin(fn, data); + ret = git_config_from_stdin(fn, data); } else if (config_source && config_source->file) { - return git_config_from_file(fn, config_source->file, data); + ret = git_config_from_file(fn, config_source->file, data); } else if (config_source && config_source->blob) { struct repository *repo = config_source->repo ? config_source->repo : the_repository; - return git_config_from_blob_ref(fn, repo, config_source->blob, + ret = git_config_from_blob_ref(fn, repo, config_source->blob, data); + } else { + ret = do_git_config_sequence(opts, fn, data); } - return do_git_config_sequence(opts, fn, data); + if (inc.remote_urls) { + string_list_clear(inc.remote_urls, 0); + FREE_AND_NULL(inc.remote_urls); + } + return ret; } static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) @@ -89,6 +89,15 @@ struct config_options { unsigned int ignore_worktree : 1; unsigned int ignore_cmdline : 1; unsigned int system_gently : 1; + + /* + * For internal use. Include all includeif.hasremoteurl paths without + * checking if the repo has that remote URL, and when doing so, verify + * that files included in this way do not configure any remote URLs + * themselves. + */ + unsigned int unconditional_remote_url : 1; + const char *commondir; const char *git_dir; config_parser_event_fn_t event_fn; @@ -126,6 +135,8 @@ int git_default_config(const char *, const char *, void *); /** * Read a specific file in git-config format. * This function takes the same callback and data parameters as `git_config`. + * + * Unlike git_config(), this function does not respect includes. */ int git_config_from_file(config_fn_t fn, const char *, void *); @@ -158,6 +169,8 @@ void read_very_early_config(config_fn_t cb, void *data); * will first feed the user-wide one to the callback, and then the * repo-specific one; by overwriting, the higher-priority repo-specific * value is left at the end). + * + * Unlike git_config_from_file(), this function respects includes. */ void git_config(config_fn_t fn, void *); @@ -338,39 +351,6 @@ const char *current_config_origin_type(void); const char *current_config_name(void); int current_config_line(void); -/** - * Include Directives - * ------------------ - * - * By default, the config parser does not respect include directives. - * However, a caller can use the special `git_config_include` wrapper - * callback to support them. To do so, you simply wrap your "real" callback - * function and data pointer in a `struct config_include_data`, and pass - * the wrapper to the regular config-reading functions. For example: - * - * ------------------------------------------- - * int read_file_with_include(const char *file, config_fn_t fn, void *data) - * { - * struct config_include_data inc = CONFIG_INCLUDE_INIT; - * inc.fn = fn; - * inc.data = data; - * return git_config_from_file(git_config_include, file, &inc); - * } - * ------------------------------------------- - * - * `git_config` respects includes automatically. The lower-level - * `git_config_from_file` does not. - * - */ -struct config_include_data { - int depth; - config_fn_t fn; - void *data; - const struct config_options *opts; -}; -#define CONFIG_INCLUDE_INIT { 0 } -int git_config_include(const char *name, const char *value, void *data); - /* * Match and parse a config key of the form: * diff --git a/config.mak.uname b/config.mak.uname index c48db45106..ff6485a0a4 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -146,6 +146,7 @@ ifeq ($(uname_S),Darwin) HAVE_BSD_SYSCTL = YesPlease FREAD_READS_DIRECTORIES = UnfortunatelyYes HAVE_NS_GET_EXECUTABLE_PATH = YesPlease + CSPRNG_METHOD = arc4random # Workaround for `gettext` being keg-only and not even being linked via # `brew link --force gettext`, should be obsolete as of @@ -261,6 +262,7 @@ ifeq ($(uname_S),FreeBSD) HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random PAGER_ENV = LESS=FRX LV=-c MORE=FRX FREAD_READS_DIRECTORIES = UnfortunatelyYes FILENO_IS_A_MACRO = UnfortunatelyYes @@ -279,6 +281,7 @@ ifeq ($(uname_S),OpenBSD) HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random PROCFS_EXECUTABLE_PATH = /proc/curproc/file FREAD_READS_DIRECTORIES = UnfortunatelyYes FILENO_IS_A_MACRO = UnfortunatelyYes @@ -290,6 +293,7 @@ ifeq ($(uname_S),MirBSD) NEEDS_LIBICONV = YesPlease HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -301,6 +305,7 @@ ifeq ($(uname_S),NetBSD) HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random PROCFS_EXECUTABLE_PATH = /proc/curproc/exe endif ifeq ($(uname_S),AIX) @@ -430,6 +435,7 @@ ifeq ($(uname_S),Windows) NO_STRTOUMAX = YesPlease NO_MKDTEMP = YesPlease NO_INTTYPES_H = YesPlease + CSPRNG_METHOD = rtlgenrandom # VS2015 with UCRT claims that snprintf and friends are C99 compliant, # so we don't need this: # @@ -599,6 +605,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL) NO_MMAP = YesPlease NO_POLL = YesPlease NO_INTPTR_T = UnfortunatelyYes + CSPRNG_METHOD = openssl SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin SHELL_PATH = /usr/coreutils/bin/bash endif @@ -634,6 +641,7 @@ ifeq ($(uname_S),MINGW) NO_POSIX_GOODIES = UnfortunatelyYes DEFAULT_HELP_FORMAT = html HAVE_PLATFORM_PROCINFO = YesPlease + CSPRNG_METHOD = rtlgenrandom BASIC_LDFLAGS += -municode COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 5100f56bb3..e44232f85d 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -260,7 +260,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") _CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0 USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP - UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET) + UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM) list(APPEND compat_SOURCES compat/mingw.c compat/winansi.c compat/win32/path-utils.c compat/win32/pthread.c compat/win32mmap.c compat/win32/syslog.c compat/win32/trace2_win32_process_info.c compat/win32/dirent.c diff --git a/git-compat-util.h b/git-compat-util.h index 1229c8296b..350b122221 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -197,6 +197,12 @@ #endif #include <windows.h> #define GIT_WINDOWS_NATIVE +#ifdef HAVE_RTLGENRANDOM +/* This is required to get access to RtlGenRandom. */ +#define SystemFunction036 NTAPI SystemFunction036 +#include <NTSecAPI.h> +#undef SystemFunction036 +#endif #endif #include <unistd.h> @@ -267,6 +273,12 @@ #else #include <stdint.h> #endif +#ifdef HAVE_ARC4RANDOM_LIBBSD +#include <bsd/stdlib.h> +#endif +#ifdef HAVE_GETRANDOM +#include <sys/random.h> +#endif #ifdef NO_INTPTR_T /* * On I16LP32, ILP32 and LP64 "long" is the safe bet, however @@ -1432,4 +1444,11 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset) void sleep_millisec(int millisec); +/* + * Generate len bytes from the system cryptographically secure PRNG. + * Returns 0 on success and -1 on error, setting errno. The inability to + * satisfy the full request is an error. + */ +int csprng_bytes(void *buf, size_t len); + #endif @@ -108,10 +108,7 @@ def p4_build_cmd(cmd): # Provide a way to not pass this option by setting git-p4.retries to 0 real_cmd += ["-r", str(retries)] - if not isinstance(cmd, list): - real_cmd = ' '.join(real_cmd) + ' ' + cmd - else: - real_cmd += cmd + real_cmd += cmd # now check that we can actually talk to the server global p4_access_checked @@ -223,153 +220,93 @@ def decode_path(path): def run_git_hook(cmd, param=[]): """Execute a hook if the hook exists.""" + args = ['git', 'hook', 'run', '--ignore-missing', cmd] + if param: + args.append("--") + for p in param: + args.append(p) + return subprocess.call(args) == 0 + +def write_pipe(c, stdin, *k, **kw): if verbose: - sys.stderr.write("Looking for hook: %s\n" % cmd) - sys.stderr.flush() - - hooks_path = gitConfig("core.hooksPath") - if len(hooks_path) <= 0: - hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks") - - if not isinstance(param, list): - param=[param] - - # resolve hook file name, OS depdenent - hook_file = os.path.join(hooks_path, cmd) - if platform.system() == 'Windows': - if not os.path.isfile(hook_file): - # look for the file with an extension - files = glob.glob(hook_file + ".*") - if not files: - return True - files.sort() - hook_file = files.pop() - while hook_file.upper().endswith(".SAMPLE"): - # The file is a sample hook. We don't want it - if len(files) > 0: - hook_file = files.pop() - else: - return True - - if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK): - return True - - return run_hook_command(hook_file, param) == 0 - -def run_hook_command(cmd, param): - """Executes a git hook command - cmd = the command line file to be executed. This can be - a file that is run by OS association. - - param = a list of parameters to pass to the cmd command - - On windows, the extension is checked to see if it should - be run with the Git for Windows Bash shell. If there - is no file extension, the file is deemed a bash shell - and will be handed off to sh.exe. Otherwise, Windows - will be called with the shell to handle the file assocation. - - For non Windows operating systems, the file is called - as an executable. - """ - cli = [cmd] + param - use_shell = False - if platform.system() == 'Windows': - (root,ext) = os.path.splitext(cmd) - if ext == "": - exe_path = os.environ.get("EXEPATH") - if exe_path is None: - exe_path = "" - else: - exe_path = os.path.join(exe_path, "bin") - cli = [os.path.join(exe_path, "SH.EXE")] + cli - else: - use_shell = True - return subprocess.call(cli, shell=use_shell) - - -def write_pipe(c, stdin): - if verbose: - sys.stderr.write('Writing pipe: %s\n' % str(c)) + sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c))) - expand = not isinstance(c, list) - p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand) + p = subprocess.Popen(c, stdin=subprocess.PIPE, *k, **kw) pipe = p.stdin val = pipe.write(stdin) pipe.close() if p.wait(): - die('Command failed: %s' % str(c)) + die('Command failed: {}'.format(' '.join(c))) return val -def p4_write_pipe(c, stdin): +def p4_write_pipe(c, stdin, *k, **kw): real_cmd = p4_build_cmd(c) if bytes is not str and isinstance(stdin, str): stdin = encode_text_stream(stdin) - return write_pipe(real_cmd, stdin) + return write_pipe(real_cmd, stdin, *k, **kw) -def read_pipe_full(c): +def read_pipe_full(c, *k, **kw): """ Read output from command. Returns a tuple of the return status, stdout text and stderr text. """ if verbose: - sys.stderr.write('Reading pipe: %s\n' % str(c)) + sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c))) - expand = not isinstance(c, list) - p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand) + p = subprocess.Popen( + c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *k, **kw) (out, err) = p.communicate() return (p.returncode, out, decode_text_stream(err)) -def read_pipe(c, ignore_error=False, raw=False): +def read_pipe(c, ignore_error=False, raw=False, *k, **kw): """ Read output from command. Returns the output text on success. On failure, terminates execution, unless ignore_error is True, when it returns an empty string. If raw is True, do not attempt to decode output text. """ - (retcode, out, err) = read_pipe_full(c) + (retcode, out, err) = read_pipe_full(c, *k, **kw) if retcode != 0: if ignore_error: out = "" else: - die('Command failed: %s\nError: %s' % (str(c), err)) + die('Command failed: {}\nError: {}'.format(' '.join(c), err)) if not raw: out = decode_text_stream(out) return out -def read_pipe_text(c): +def read_pipe_text(c, *k, **kw): """ Read output from a command with trailing whitespace stripped. On error, returns None. """ - (retcode, out, err) = read_pipe_full(c) + (retcode, out, err) = read_pipe_full(c, *k, **kw) if retcode != 0: return None else: return decode_text_stream(out).rstrip() -def p4_read_pipe(c, ignore_error=False, raw=False): +def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw): real_cmd = p4_build_cmd(c) - return read_pipe(real_cmd, ignore_error, raw=raw) + return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw) -def read_pipe_lines(c, raw=False): +def read_pipe_lines(c, raw=False, *k, **kw): if verbose: - sys.stderr.write('Reading pipe: %s\n' % str(c)) + sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c))) - expand = not isinstance(c, list) - p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand) + p = subprocess.Popen(c, stdout=subprocess.PIPE, *k, **kw) pipe = p.stdout lines = pipe.readlines() if not raw: lines = [decode_text_stream(line) for line in lines] if pipe.close() or p.wait(): - die('Command failed: %s' % str(c)) + die('Command failed: {}'.format(' '.join(c))) return lines -def p4_read_pipe_lines(c): +def p4_read_pipe_lines(c, *k, **kw): """Specifically invoke p4 on the command supplied. """ real_cmd = p4_build_cmd(c) - return read_pipe_lines(real_cmd) + return read_pipe_lines(real_cmd, *k, **kw) def p4_has_command(cmd): """Ask p4 for help on this command. If it returns an error, the @@ -400,23 +337,22 @@ def p4_has_move_command(): # assume it failed because @... was invalid changelist return True -def system(cmd, ignore_error=False): - expand = not isinstance(cmd, list) +def system(cmd, ignore_error=False, *k, **kw): if verbose: - sys.stderr.write("executing %s\n" % str(cmd)) - retcode = subprocess.call(cmd, shell=expand) + sys.stderr.write("executing {}\n".format( + ' '.join(cmd) if isinstance(cmd, list) else cmd)) + retcode = subprocess.call(cmd, *k, **kw) if retcode and not ignore_error: - raise CalledProcessError(retcode, cmd) + raise subprocess.CalledProcessError(retcode, cmd) return retcode -def p4_system(cmd): +def p4_system(cmd, *k, **kw): """Specifically invoke p4 as the system command. """ real_cmd = p4_build_cmd(cmd) - expand = not isinstance(real_cmd, list) - retcode = subprocess.call(real_cmd, shell=expand) + retcode = subprocess.call(real_cmd, *k, **kw) if retcode: - raise CalledProcessError(retcode, real_cmd) + raise subprocess.CalledProcessError(retcode, real_cmd) def die_bad_access(s): die("failure accessing depot: {0}".format(s.rstrip())) @@ -735,18 +671,11 @@ def isModeExecChanged(src_mode, dst_mode): return isModeExec(src_mode) != isModeExec(dst_mode) def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, - errors_as_exceptions=False): - - if not isinstance(cmd, list): - cmd = "-G " + cmd - expand = True - else: - cmd = ["-G"] + cmd - expand = False + errors_as_exceptions=False, *k, **kw): - cmd = p4_build_cmd(cmd) + cmd = p4_build_cmd(["-G"] + cmd) if verbose: - sys.stderr.write("Opening pipe: %s\n" % str(cmd)) + sys.stderr.write("Opening pipe: {}\n".format(' '.join(cmd))) # Use a temporary file to avoid deadlocks without # subprocess.communicate(), which would put another copy @@ -763,10 +692,8 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, stdin_file.flush() stdin_file.seek(0) - p4 = subprocess.Popen(cmd, - shell=expand, - stdin=stdin_file, - stdout=subprocess.PIPE) + p4 = subprocess.Popen( + cmd, stdin=stdin_file, stdout=subprocess.PIPE, *k, **kw) result = [] try: @@ -819,8 +746,8 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, return result -def p4Cmd(cmd): - list = p4CmdList(cmd) +def p4Cmd(cmd, *k, **kw): + list = p4CmdList(cmd, *k, **kw) result = {} for entry in list: result.update(entry) @@ -869,7 +796,7 @@ def isValidGitDir(path): return git_dir(path) != None def parseRevision(ref): - return read_pipe("git rev-parse %s" % ref).strip() + return read_pipe(["git", "rev-parse", ref]).strip() def branchExists(ref): rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref], @@ -975,11 +902,11 @@ def p4BranchesInGit(branchesAreInRemotes=True): branches = {} - cmdline = "git rev-parse --symbolic " + cmdline = ["git", "rev-parse", "--symbolic"] if branchesAreInRemotes: - cmdline += "--remotes" + cmdline.append("--remotes") else: - cmdline += "--branches" + cmdline.append("--branches") for line in read_pipe_lines(cmdline): line = line.strip() @@ -1044,7 +971,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent originPrefix = "origin/p4/" - for line in read_pipe_lines("git rev-parse --symbolic --remotes"): + for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]): line = line.strip() if (not line.startswith(originPrefix)) or line.endswith("HEAD"): continue @@ -1082,7 +1009,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent remoteHead, ','.join(settings['depot-paths']))) if update: - system("git update-ref %s %s" % (remoteHead, originHead)) + system(["git", "update-ref", remoteHead, originHead]) def originP4BranchesExist(): return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master") @@ -1196,7 +1123,7 @@ def getClientSpec(): """Look at the p4 client spec, create a View() object that contains all the mappings, and return it.""" - specList = p4CmdList("client -o") + specList = p4CmdList(["client", "-o"]) if len(specList) != 1: die('Output from "client -o" is %d lines, expecting 1' % len(specList)) @@ -1225,7 +1152,7 @@ def getClientSpec(): def getClientRoot(): """Grab the client directory.""" - output = p4CmdList("client -o") + output = p4CmdList(["client", "-o"]) if len(output) != 1: die('Output from "client -o" is %d lines, expecting 1' % len(output)) @@ -1480,7 +1407,7 @@ class P4UserMap: if self.myP4UserId: return self.myP4UserId - results = p4CmdList("user -o") + results = p4CmdList(["user", "-o"]) for r in results: if 'User' in r: self.myP4UserId = r['User'] @@ -1505,7 +1432,7 @@ class P4UserMap: self.users = {} self.emails = {} - for output in p4CmdList("users"): + for output in p4CmdList(["users"]): if "User" not in output: continue self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" @@ -1629,7 +1556,7 @@ class P4Submit(Command, P4UserMap): die("Large file system not supported for git-p4 submit command. Please remove it from config.") def check(self): - if len(p4CmdList("opened ...")) > 0: + if len(p4CmdList(["opened", "..."])) > 0: die("You have files opened with perforce! Close them before starting the sync.") def separate_jobs_from_description(self, message): @@ -1733,7 +1660,7 @@ class P4Submit(Command, P4UserMap): # then gets used to patch up the username in the change. If the same # client spec is being used by multiple processes then this might go # wrong. - results = p4CmdList("client -o") # find the current client + results = p4CmdList(["client", "-o"]) # find the current client client = None for r in results: if 'Client' in r: @@ -1749,7 +1676,7 @@ class P4Submit(Command, P4UserMap): def modifyChangelistUser(self, changelist, newUser): # fixup the user field of a changelist after it has been submitted. - changes = p4CmdList("change -o %s" % changelist) + changes = p4CmdList(["change", "-o", changelist]) if len(changes) != 1: die("Bad output from p4 change modifying %s to user %s" % (changelist, newUser)) @@ -1760,7 +1687,7 @@ class P4Submit(Command, P4UserMap): # p4 does not understand format version 3 and above input = marshal.dumps(c, 2) - result = p4CmdList("change -f -i", stdin=input) + result = p4CmdList(["change", "-f", "-i"], stdin=input) for r in result: if 'code' in r: if r['code'] == 'error': @@ -1866,7 +1793,7 @@ class P4Submit(Command, P4UserMap): if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""): editor = os.environ.get("P4EDITOR") else: - editor = read_pipe("git var GIT_EDITOR").strip() + editor = read_pipe(["git", "var", "GIT_EDITOR"]).strip() system(["sh", "-c", ('%s "$@"' % editor), editor, template_file]) # If the file was not saved, prompt to see if this patch should @@ -1924,7 +1851,8 @@ class P4Submit(Command, P4UserMap): (p4User, gitEmail) = self.p4UserForCommit(id) - diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id)) + diff = read_pipe_lines( + ["git", "diff-tree", "-r"] + self.diffOpts + ["{}^".format(id), id]) filesToAdd = set() filesToChangeType = set() filesToDelete = set() @@ -2060,7 +1988,7 @@ class P4Submit(Command, P4UserMap): # # Apply the patch for real, and do add/delete/+x handling. # - system(applyPatchCmd) + system(applyPatchCmd, shell=True) for f in filesToChangeType: p4_edit(f, "-t", "auto") @@ -2410,17 +2338,17 @@ class P4Submit(Command, P4UserMap): # if self.detectRenames: # command-line -M arg - self.diffOpts = "-M" + self.diffOpts = ["-M"] else: # If not explicitly set check the config variable detectRenames = gitConfig("git-p4.detectRenames") if detectRenames.lower() == "false" or detectRenames == "": - self.diffOpts = "" + self.diffOpts = [] elif detectRenames.lower() == "true": - self.diffOpts = "-M" + self.diffOpts = ["-M"] else: - self.diffOpts = "-M%s" % detectRenames + self.diffOpts = ["-M{}".format(detectRenames)] # no command-line arg for -C or --find-copies-harder, just # config variables @@ -2428,12 +2356,12 @@ class P4Submit(Command, P4UserMap): if detectCopies.lower() == "false" or detectCopies == "": pass elif detectCopies.lower() == "true": - self.diffOpts += " -C" + self.diffOpts.append("-C") else: - self.diffOpts += " -C%s" % detectCopies + self.diffOpts.append("-C{}".format(detectCopies)) if gitConfigBool("git-p4.detectCopiesHarder"): - self.diffOpts += " --find-copies-harder" + self.diffOpts.append("--find-copies-harder") num_shelves = len(self.update_shelve) if num_shelves > 0 and num_shelves != len(commits): @@ -3381,12 +3309,9 @@ class P4Sync(Command, P4UserMap): lostAndFoundBranches = set() user = gitConfig("git-p4.branchUser") - if len(user) > 0: - command = "branches -u %s" % user - else: - command = "branches" - for info in p4CmdList(command): + for info in p4CmdList( + ["branches"] + (["-u", user] if len(user) > 0 else [])): details = p4Cmd(["branch", "-o", info["branch"]]) viewIdx = 0 while "View%s" % viewIdx in details: @@ -3477,7 +3402,8 @@ class P4Sync(Command, P4UserMap): while True: if self.verbose: print("trying: earliest %s latest %s" % (earliestCommit, latestCommit)) - next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip() + next = read_pipe(["git", "rev-list", "--bisect", + latestCommit, earliestCommit]).strip() if len(next) == 0: if self.verbose: print("argh") @@ -3633,7 +3559,7 @@ class P4Sync(Command, P4UserMap): if self.hasOrigin: if not self.silent: print('Syncing with origin first, using "git fetch origin"') - system("git fetch origin") + system(["git", "fetch", "origin"]) def importHeadRevision(self, revision): print("Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)) @@ -3800,8 +3726,8 @@ class P4Sync(Command, P4UserMap): if len(self.branch) == 0: self.branch = self.refPrefix + "master" if gitBranchExists("refs/heads/p4") and self.importIntoRemotes: - system("git update-ref %s refs/heads/p4" % self.branch) - system("git branch -D p4") + system(["git", "update-ref", self.branch, "refs/heads/p4"]) + system(["git", "branch", "-D", "p4"]) # accept either the command-line option, or the configuration variable if self.useClientSpec: @@ -4004,7 +3930,7 @@ class P4Sync(Command, P4UserMap): # Cleanup temporary branches created during import if self.tempBranches != []: for branch in self.tempBranches: - read_pipe("git update-ref -d %s" % branch) + read_pipe(["git", "update-ref", "-d", branch]) os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation)) # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow @@ -4036,7 +3962,7 @@ class P4Rebase(Command): def rebase(self): if os.system("git update-index --refresh") != 0: die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash."); - if len(read_pipe("git diff-index HEAD --")) > 0: + if len(read_pipe(["git", "diff-index", "HEAD", "--"])) > 0: die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash."); [upstream, settings] = findUpstreamBranchPoint() @@ -4047,9 +3973,10 @@ class P4Rebase(Command): upstream = re.sub("~[0-9]+$", "", upstream) print("Rebasing the current branch onto %s" % upstream) - oldHead = read_pipe("git rev-parse HEAD").strip() - system("git rebase %s" % upstream) - system("git diff-tree --stat --summary -M %s HEAD --" % oldHead) + oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip() + system(["git", "rebase", upstream]) + system(["git", "diff-tree", "--stat", "--summary", "-M", oldHead, + "HEAD", "--"]) return True class P4Clone(P4Sync): @@ -4110,7 +4037,7 @@ class P4Clone(P4Sync): init_cmd.append("--bare") retcode = subprocess.call(init_cmd) if retcode: - raise CalledProcessError(retcode, init_cmd) + raise subprocess.CalledProcessError(retcode, init_cmd) if not P4Sync.run(self, depotPaths): return False @@ -4126,7 +4053,7 @@ class P4Clone(P4Sync): # auto-set this variable if invoked with --use-client-spec if self.useClientSpec_from_options: - system("git config --bool git-p4.useclientspec true") + system(["git", "config", "--bool", "git-p4.useclientspec", "true"]) return True @@ -4257,10 +4184,7 @@ class P4Branches(Command): if originP4BranchesExist(): createOrUpdateBranchesFromOrigin() - cmdline = "git rev-parse --symbolic " - cmdline += " --remotes" - - for line in read_pipe_lines(cmdline): + for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]): line = line.strip() if not line.startswith('p4/') or line == "p4/HEAD": @@ -4343,9 +4267,9 @@ def main(): cmd.gitdir = os.path.abspath(".git") if not isValidGitDir(cmd.gitdir): # "rev-parse --git-dir" without arguments will try $PWD/.git - cmd.gitdir = read_pipe("git rev-parse --git-dir").strip() + cmd.gitdir = read_pipe(["git", "rev-parse", "--git-dir"]).strip() if os.path.exists(cmd.gitdir): - cdup = read_pipe("git rev-parse --show-cdup").strip() + cdup = read_pipe(["git", "rev-parse", "--show-cdup"]).strip() if len(cdup) > 0: chdir(cdup); diff --git a/git-send-email.perl b/git-send-email.perl index 04087221aa..a98460bdb9 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -225,13 +225,13 @@ my $multiedit; my $editor; sub system_or_msg { - my ($args, $msg) = @_; + my ($args, $msg, $cmd_name) = @_; system(@$args); my $signalled = $? & 127; my $exit_code = $? >> 8; return unless $signalled or $exit_code; - my @sprintf_args = ($args->[0], $exit_code); + my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code); if (defined $msg) { # Quiet the 'redundant' warning category, except we # need to support down to Perl 5.8, so we can't do a @@ -2075,10 +2075,10 @@ sub validate_patch { my ($fn, $xfer_encoding) = @_; if ($repo) { + my $hook_name = 'sendemail-validate'; my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks'); require File::Spec; - my $validate_hook = File::Spec->catfile($hooks_path, - 'sendemail-validate'); + my $validate_hook = File::Spec->catfile($hooks_path, $hook_name); my $hook_error; if (-x $validate_hook) { require Cwd; @@ -2088,13 +2088,19 @@ sub validate_patch { chdir($repo->wc_path() or $repo->repo_path()) or die("chdir: $!"); local $ENV{"GIT_DIR"} = $repo->repo_path(); - $hook_error = system_or_msg([$validate_hook, $target]); + my @cmd = ("git", "hook", "run", "--ignore-missing", + $hook_name, "--"); + my @cmd_msg = (@cmd, "<patch>"); + my @cmd_run = (@cmd, $target); + $hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg"); chdir($cwd_save) or die("chdir: $!"); } if ($hook_error) { - die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" . - "%s\n" . - "warning: no patches were sent\n"), $fn, $hook_error); + $hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" . + $hook_error . "\n" . + "warning: no patches were sent\n"), + $fn, $hook_name); + die $hook_error; } } @@ -541,6 +541,7 @@ static struct cmd_struct commands[] = { { "grep", cmd_grep, RUN_SETUP_GENTLY }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, + { "hook", cmd_hook, RUN_SETUP }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, diff --git a/gpg-interface.c b/gpg-interface.c index b52eb0e2e0..17b1e44baa 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -433,7 +433,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc, struct tempfile *buffer_file; int ret = -1; const char *line; - size_t trust_size; char *principal; struct strbuf ssh_principals_out = STRBUF_INIT; struct strbuf ssh_principals_err = STRBUF_INIT; @@ -502,15 +501,30 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc, ret = -1; } else { /* Check every principal we found (one per line) */ - for (line = ssh_principals_out.buf; *line; - line = strchrnul(line + 1, '\n')) { - while (*line == '\n') - line++; - if (!*line) - break; - - trust_size = strcspn(line, "\n"); - principal = xmemdupz(line, trust_size); + const char *next; + for (line = ssh_principals_out.buf; + *line; + line = next) { + const char *end_of_text; + + next = end_of_text = strchrnul(line, '\n'); + + /* Did we find a LF, and did we have CR before it? */ + if (*end_of_text && + line < end_of_text && + end_of_text[-1] == '\r') + end_of_text--; + + /* Unless we hit NUL, skip over the LF we found */ + if (*next) + next++; + + /* Not all lines are data. Skip empty ones */ + if (line == end_of_text) + continue; + + /* We now know we have an non-empty line. Process it */ + principal = xmemdupz(line, end_of_text - line); child_process_init(&ssh_keygen); strbuf_release(&ssh_keygen_out); @@ -595,6 +595,35 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) } } +static struct grep_expr *grep_not_expr(struct grep_expr *expr) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = GREP_NODE_NOT; + z->u.unary = expr; + return z; +} + +static struct grep_expr *grep_binexp(enum grep_expr_node kind, + struct grep_expr *left, + struct grep_expr *right) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = kind; + z->u.binary.left = left; + z->u.binary.right = right; + return z; +} + +static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right) +{ + return grep_binexp(GREP_NODE_OR, left, right); +} + +static struct grep_expr *grep_and_expr(struct grep_expr *left, struct grep_expr *right) +{ + return grep_binexp(GREP_NODE_AND, left, right); +} + static struct grep_expr *compile_pattern_or(struct grep_pat **); static struct grep_expr *compile_pattern_atom(struct grep_pat **list) { @@ -638,12 +667,10 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) if (!p->next) die("--not not followed by pattern expression"); *list = p->next; - CALLOC_ARRAY(x, 1); - x->node = GREP_NODE_NOT; - x->u.unary = compile_pattern_not(list); - if (!x->u.unary) + x = compile_pattern_not(list); + if (!x) die("--not followed by non pattern expression"); - return x; + return grep_not_expr(x); default: return compile_pattern_atom(list); } @@ -652,7 +679,7 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) static struct grep_expr *compile_pattern_and(struct grep_pat **list) { struct grep_pat *p; - struct grep_expr *x, *y, *z; + struct grep_expr *x, *y; x = compile_pattern_not(list); p = *list; @@ -665,11 +692,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) y = compile_pattern_and(list); if (!y) die("--and not followed by pattern expression"); - CALLOC_ARRAY(z, 1); - z->node = GREP_NODE_AND; - z->u.binary.left = x; - z->u.binary.right = y; - return z; + return grep_and_expr(x, y); } return x; } @@ -677,7 +700,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) static struct grep_expr *compile_pattern_or(struct grep_pat **list) { struct grep_pat *p; - struct grep_expr *x, *y, *z; + struct grep_expr *x, *y; x = compile_pattern_and(list); p = *list; @@ -685,11 +708,7 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) y = compile_pattern_or(list); if (!y) die("not a pattern expression %s", p->pattern); - CALLOC_ARRAY(z, 1); - z->node = GREP_NODE_OR; - z->u.binary.left = x; - z->u.binary.right = y; - return z; + return grep_or_expr(x, y); } return x; } @@ -699,14 +718,6 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list) return compile_pattern_or(list); } -static struct grep_expr *grep_not_expr(struct grep_expr *expr) -{ - struct grep_expr *z = xcalloc(1, sizeof(*z)); - z->node = GREP_NODE_NOT; - z->u.unary = expr; - return z; -} - static struct grep_expr *grep_true_expr(void) { struct grep_expr *z = xcalloc(1, sizeof(*z)); @@ -714,15 +725,6 @@ static struct grep_expr *grep_true_expr(void) return z; } -static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right) -{ - struct grep_expr *z = xcalloc(1, sizeof(*z)); - z->node = GREP_NODE_OR; - z->u.binary.left = left; - z->u.binary.right = right; - return z; -} - static struct grep_expr *prep_header_patterns(struct grep_opt *opt) { struct grep_pat *p; @@ -1,6 +1,7 @@ #include "cache.h" #include "hook.h" #include "run-command.h" +#include "config.h" const char *find_hook(const char *name) { @@ -40,3 +41,133 @@ int hook_exists(const char *name) { return !!find_hook(name); } + +static int pick_next_hook(struct child_process *cp, + struct strbuf *out, + void *pp_cb, + void **pp_task_cb) +{ + struct hook_cb_data *hook_cb = pp_cb; + const char *hook_path = hook_cb->hook_path; + + if (!hook_path) + return 0; + + cp->no_stdin = 1; + strvec_pushv(&cp->env_array, hook_cb->options->env.v); + cp->stdout_to_stderr = 1; + cp->trace2_hook_name = hook_cb->hook_name; + cp->dir = hook_cb->options->dir; + + 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 + * done. + */ + hook_cb->hook_path = NULL; + + return 1; +} + +static int notify_start_failure(struct strbuf *out, + void *pp_cb, + 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; +} + +static int notify_hook_finished(int result, + struct strbuf *out, + void *pp_cb, + void *pp_task_cb) +{ + struct hook_cb_data *hook_cb = pp_cb; + + hook_cb->rc |= result; + + return 0; +} + +static void run_hooks_opt_clear(struct run_hooks_opt *options) +{ + strvec_clear(&options->env); + strvec_clear(&options->args); +} + +int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options) +{ + struct strbuf abs_path = STRBUF_INIT; + struct hook_cb_data cb_data = { + .rc = 0, + .hook_name = hook_name, + .options = options, + }; + const char *const hook_path = find_hook(hook_name); + int jobs = 1; + int ret = 0; + + if (!options) + BUG("a struct run_hooks_opt must be provided to run_hooks"); + + if (!hook_path && !options->error_if_missing) + goto cleanup; + + if (!hook_path) { + ret = error("cannot find a hook named %s", hook_name); + goto cleanup; + } + + cb_data.hook_path = hook_path; + if (options->dir) { + strbuf_add_absolute_path(&abs_path, hook_path); + cb_data.hook_path = abs_path.buf; + } + + run_processes_parallel_tr2(jobs, + pick_next_hook, + notify_start_failure, + notify_hook_finished, + &cb_data, + "hook", + hook_name); + ret = cb_data.rc; +cleanup: + strbuf_release(&abs_path); + run_hooks_opt_clear(options); + return ret; +} + +int run_hooks(const char *hook_name) +{ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + + return run_hooks_opt(hook_name, &opt); +} + +int run_hooks_l(const char *hook_name, ...) +{ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + va_list ap; + const char *arg; + + va_start(ap, hook_name); + while ((arg = va_arg(ap, const char *))) + strvec_push(&opt.args, arg); + va_end(ap); + + return run_hooks_opt(hook_name, &opt); +} @@ -1,5 +1,37 @@ #ifndef HOOK_H #define HOOK_H +#include "strvec.h" + +struct run_hooks_opt +{ + /* Environment vars to be set for each hook */ + struct strvec env; + + /* Args to be passed to each hook */ + struct strvec args; + + /* Emit an error if the hook is missing */ + unsigned int error_if_missing:1; + + /** + * An optional initial working directory for the hook, + * translates to "struct child_process"'s "dir" member. + */ + const char *dir; +}; + +#define RUN_HOOKS_OPT_INIT { \ + .env = STRVEC_INIT, \ + .args = STRVEC_INIT, \ +} + +struct hook_cb_data { + /* rc reflects the cumulative failure state */ + int rc; + const char *hook_name; + const char *hook_path; + struct run_hooks_opt *options; +}; /* * Returns the path to the hook file, or NULL if the hook is missing @@ -13,4 +45,29 @@ const char *find_hook(const char *name); */ int hook_exists(const char *hookname); +/** + * Takes a `hook_name`, resolves it to a path with find_hook(), and + * runs the hook for you with the options specified in "struct + * run_hooks opt". Will free memory associated with the "struct run_hooks_opt". + * + * Returns the status code of the run hook, or a negative value on + * error(). + */ +int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options); + +/** + * A wrapper for run_hooks_opt() which provides a dummy "struct + * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT". + */ +int run_hooks(const char *hook_name); + +/** + * Like run_hooks(), a wrapper for run_hooks_opt(). + * + * In addition to the wrapping behavior provided by run_hooks(), this + * wrapper takes a list of strings terminated by a NULL + * argument. These things will be used as positional arguments to the + * hook. This function behaves like the old run_hook_le() API. + */ +int run_hooks_l(const char *hook_name, ...); #endif diff --git a/mem-pool.c b/mem-pool.c index ccdcad2e3d..599d8e895f 100644 --- a/mem-pool.c +++ b/mem-pool.c @@ -8,6 +8,26 @@ #define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block)) /* + * The inner union is an approximation for C11's max_align_t, and the + * struct + offsetof computes _Alignof. This can all just be replaced + * with _Alignof(max_align_t) if/when C11 is part of the baseline. + * Note that _Alignof(X) need not be the same as sizeof(X); it's only + * required to be a (possibly trivial) factor. They are the same for + * most architectures, but m68k for example has only 2-byte alignment + * for its 4-byte and 8-byte types, so using sizeof would waste space. + * + * Add more types to the union if the current set is insufficient. + */ +struct git_max_alignment { + char unalign; + union { + uintmax_t max_align_uintmax; + void *max_align_pointer; + } aligned; +}; +#define GIT_MAX_ALIGNMENT offsetof(struct git_max_alignment, aligned) + +/* * Allocate a new mp_block and insert it after the block specified in * `insert_after`. If `insert_after` is NULL, then insert block at the * head of the linked list. @@ -69,9 +89,9 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len) struct mp_block *p = NULL; void *r; - /* round up to a 'uintmax_t' alignment */ - if (len & (sizeof(uintmax_t) - 1)) - len += sizeof(uintmax_t) - (len & (sizeof(uintmax_t) - 1)); + /* round up to a 'GIT_MAX_ALIGNMENT' alignment */ + if (len & (GIT_MAX_ALIGNMENT - 1)) + len += GIT_MAX_ALIGNMENT - (len & (GIT_MAX_ALIGNMENT - 1)); if (pool->mp_block && pool->mp_block->end - pool->mp_block->next_free >= len) diff --git a/merge-ort.c b/merge-ort.c index c319797021..d455fca7d2 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -3060,6 +3060,10 @@ static int detect_and_process_renames(struct merge_options *opt, trace2_region_enter("merge", "regular renames", opt->repo); detection_run |= detect_regular_renames(opt, MERGE_SIDE1); detection_run |= detect_regular_renames(opt, MERGE_SIDE2); + if (renames->needed_limit) { + renames->cached_pairs_valid_side = 0; + renames->redo_after_renames = 0; + } if (renames->redo_after_renames && detection_run) { int i, side; struct diff_filepair *p; @@ -4575,7 +4579,7 @@ static void merge_ort_internal(struct merge_options *opt, struct commit *h2, struct merge_result *result) { - struct commit_list *iter; + struct commit *next; struct commit *merged_merge_bases; const char *ancestor_name; struct strbuf merge_base_abbrev = STRBUF_INIT; @@ -4604,7 +4608,8 @@ static void merge_ort_internal(struct merge_options *opt, ancestor_name = merge_base_abbrev.buf; } - for (iter = merge_bases; iter; iter = iter->next) { + for (next = pop_commit(&merge_bases); next; + next = pop_commit(&merge_bases)) { const char *saved_b1, *saved_b2; struct commit *prev = merged_merge_bases; @@ -4621,7 +4626,7 @@ static void merge_ort_internal(struct merge_options *opt, saved_b2 = opt->branch2; opt->branch1 = "Temporary merge branch 1"; opt->branch2 = "Temporary merge branch 2"; - merge_ort_internal(opt, NULL, prev, iter->item, result); + merge_ort_internal(opt, NULL, prev, next, result); if (result->clean < 0) return; opt->branch1 = saved_b1; @@ -4632,8 +4637,7 @@ static void merge_ort_internal(struct merge_options *opt, result->tree, "merged tree"); commit_list_insert(prev, &merged_merge_bases->parents); - commit_list_insert(iter->item, - &merged_merge_bases->parents->next); + commit_list_insert(next, &merged_merge_bases->parents->next); clear_or_reinit_internal_opts(opt->priv, 1); } diff --git a/object-name.c b/object-name.c index fdff4601b2..92862eeb1a 100644 --- a/object-name.c +++ b/object-name.c @@ -1795,13 +1795,13 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, const char *cp; int only_to_die = flags & GET_OID_ONLY_TO_DIE; - if (only_to_die) - flags |= GET_OID_QUIETLY; - memset(oc, 0, sizeof(*oc)); oc->mode = S_IFINVALID; strbuf_init(&oc->symlink_path, 0); ret = get_oid_1(repo, name, namelen, oid, flags); + if (!ret && flags & GET_OID_REQUIRE_PATH) + die(_("<object>:<path> required, only <object> '%s' given"), + name); if (!ret) return ret; /* @@ -1932,7 +1932,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r, { struct object_context oc; struct object_id oid; - get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE, + get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY, prefix, &oid, &oc); } diff --git a/parse-options.c b/parse-options.c index a8283037be..2437ad3bcd 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1079,3 +1079,16 @@ void NORETURN usage_msg_opt(const char *msg, die_message("%s\n", msg); /* The extra \n is intentional */ usage_with_options(usagestr, options); } + +void NORETURN usage_msg_optf(const char * const fmt, + const char * const *usagestr, + const struct option *options, ...) +{ + struct strbuf msg = STRBUF_INIT; + va_list ap; + va_start(ap, options); + strbuf_vaddf(&msg, fmt, ap); + va_end(ap); + + usage_msg_opt(msg.buf, usagestr, options); +} diff --git a/parse-options.h b/parse-options.h index e22846d3b7..659a4c28b2 100644 --- a/parse-options.h +++ b/parse-options.h @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * token to explain the kind of argument this option wants. Does not * begin in capital letter, and does not end with a full stop. * Should be wrapped by N_() for translation. + * Is automatically enclosed in brackets when printed, unless it + * contains any of the following characters: ()<>[]| + * E.g. "name" is shown as "<name>" to indicate that a name value + * needs to be supplied, not the literal string "name", but + * "<start>,<end>" and "(this|that)" are printed verbatim. * * `help`:: * the short help associated to what the option does. @@ -225,6 +230,16 @@ NORETURN void usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options); +/** + * usage_msg_optf() is like usage_msg_opt() except that the first + * argument is a format string, and optional format arguments follow + * after the 3rd option. + */ +__attribute__((format (printf,1,4))) +void NORETURN usage_msg_optf(const char *fmt, + const char * const *usagestr, + const struct option *options, ...); + /* * Use these assertions for callbacks that expect to be called with NONEG and * NOARG respectively, and do not otherwise handle the "unset" and "arg" diff --git a/read-cache.c b/read-cache.c index cbe73f14e5..63a792e578 100644 --- a/read-cache.c +++ b/read-cache.c @@ -28,6 +28,7 @@ #include "sparse-index.h" #include "csum-file.h" #include "promisor-remote.h" +#include "hook.h" /* Mask for the name length in ce_flags in the on-disk index */ @@ -2775,7 +2776,7 @@ static int repo_verify_index(struct repository *repo) return verify_index_from(repo->index, repo->index_file); } -static int has_racy_timestamp(struct index_state *istate) +int has_racy_timestamp(struct index_state *istate) { int entries = istate->cache_nr; int i; @@ -3009,6 +3010,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, !is_null_oid(&istate->split_index->base_oid)) { struct strbuf sb = STRBUF_INIT; + if (istate->sparse_index) + die(_("cannot write split index for a sparse index")); + err = write_link_extension(&sb, istate) < 0 || write_index_ext_header(f, eoie_c, CACHE_EXT_LINK, sb.len) < 0; @@ -3150,7 +3154,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l else ret = close_lock_file_gently(lock); - run_hook_le(NULL, "post-index-change", + run_hooks_l("post-index-change", istate->updated_workdir ? "1" : "0", istate->updated_skipworktree ? "1" : "0", NULL); istate->updated_workdir = 0; @@ -269,10 +269,9 @@ char *refs_resolve_refdup(struct ref_store *refs, struct object_id *oid, int *flags) { const char *result; - int ignore_errno; result = refs_resolve_ref_unsafe(refs, refname, resolve_flags, - oid, flags, &ignore_errno); + oid, flags); return xstrdup_or_null(result); } @@ -294,11 +293,10 @@ struct ref_filter { int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags) { - int ignore_errno; struct ref_store *refs = get_main_ref_store(the_repository); if (refs_resolve_ref_unsafe(refs, refname, resolve_flags, - oid, flags, &ignore_errno)) + oid, flags)) return 0; return -1; } @@ -310,9 +308,8 @@ int read_ref(const char *refname, struct object_id *oid) int refs_ref_exists(struct ref_store *refs, const char *refname) { - int ignore_errno; return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, - NULL, NULL, &ignore_errno); + NULL, NULL); } int ref_exists(const char *refname) @@ -656,15 +653,13 @@ int expand_ref(struct repository *repo, const char *str, int len, struct object_id *this_result; int flag; struct ref_store *refs = get_main_ref_store(repo); - int ignore_errno; this_result = refs_found ? &oid_from_ref : oid; strbuf_reset(&fullref); strbuf_addf(&fullref, *p, len, str); r = refs_resolve_ref_unsafe(refs, fullref.buf, RESOLVE_REF_READING, - this_result, &flag, - &ignore_errno); + this_result, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); @@ -693,14 +688,12 @@ int repo_dwim_log(struct repository *r, const char *str, int len, for (p = ref_rev_parse_rules; *p; p++) { struct object_id hash; const char *ref, *it; - int ignore_errno; strbuf_reset(&path); strbuf_addf(&path, *p, len, str); ref = refs_resolve_ref_unsafe(refs, path.buf, RESOLVE_REF_READING, - oid ? &hash : NULL, NULL, - &ignore_errno); + oid ? &hash : NULL, NULL); if (!ref) continue; if (refs_reflog_exists(refs, path.buf)) @@ -1390,10 +1383,9 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { struct object_id oid; int flag; - int ignore_errno; if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING, - &oid, &flag, &ignore_errno)) + &oid, &flag)) return fn("HEAD", &oid, flag, cb_data); return 0; @@ -1682,15 +1674,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, const char *refname, int resolve_flags, struct object_id *oid, - int *flags, int *failure_errno) + int *flags) { static struct strbuf sb_refname = STRBUF_INIT; struct object_id unused_oid; int unused_flags; int symref_count; - assert(failure_errno); - if (!oid) oid = &unused_oid; if (!flags) @@ -1700,10 +1690,8 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(refname)) { - *failure_errno = EINVAL; + !refname_is_safe(refname)) return NULL; - } /* * dwim_ref() uses REF_ISBROKEN to distinguish between @@ -1718,9 +1706,10 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) { unsigned int read_flags = 0; + int failure_errno; if (refs_read_raw_ref(refs, refname, oid, &sb_refname, - &read_flags, failure_errno)) { + &read_flags, &failure_errno)) { *flags |= read_flags; /* In reading mode, refs must eventually resolve */ @@ -1732,9 +1721,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, * may show errors besides ENOENT if there are * similarly-named refs. */ - if (*failure_errno != ENOENT && - *failure_errno != EISDIR && - *failure_errno != ENOTDIR) + if (failure_errno != ENOENT && + failure_errno != EISDIR && + failure_errno != ENOTDIR) return NULL; oidclr(oid); @@ -1760,16 +1749,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, } if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(refname)) { - *failure_errno = EINVAL; + !refname_is_safe(refname)) return NULL; - } *flags |= REF_ISBROKEN | REF_BAD_NAME; } } - *failure_errno = ELOOP; return NULL; } @@ -1784,10 +1770,8 @@ int refs_init_db(struct strbuf *err) const char *resolve_ref_unsafe(const char *refname, int resolve_flags, struct object_id *oid, int *flags) { - int ignore_errno; - return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname, - resolve_flags, oid, flags, &ignore_errno); + resolve_flags, oid, flags); } int resolve_gitlink_ref(const char *submodule, const char *refname, @@ -1795,15 +1779,14 @@ int resolve_gitlink_ref(const char *submodule, const char *refname, { struct ref_store *refs; int flags; - int ignore_errno; refs = get_submodule_ref_store(submodule); if (!refs) return -1; - if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags, - &ignore_errno) || is_null_oid(oid)) + if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) || + is_null_oid(oid)) return -1; return 0; } @@ -58,11 +58,6 @@ struct worktree; * resolved. The function returns NULL for such ref names. * Caps and underscores refers to the special refs, such as HEAD, * FETCH_HEAD and friends, that all live outside of the refs/ directory. - * - * Callers should not inspect "errno" on failure, but rather pass in a - * "failure_errno" parameter, on failure the "errno" will indicate the - * type of failure encountered, but not necessarily one that came from - * a syscall. We might have faked it up. */ #define RESOLVE_REF_READING 0x01 #define RESOLVE_REF_NO_RECURSE 0x02 @@ -72,7 +67,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, const char *refname, int resolve_flags, struct object_id *oid, - int *flags, int *failure_errno); + int *flags); const char *resolve_ref_unsafe(const char *refname, int resolve_flags, struct object_id *oid, int *flags); diff --git a/refs/files-backend.c b/refs/files-backend.c index 43a3b882d7..a40267b3ae 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -277,11 +277,10 @@ static void loose_fill_ref_dir(struct ref_store *ref_store, create_dir_entry(dir->cache, refname.buf, refname.len)); } else { - int ignore_errno; if (!refs_resolve_ref_unsafe(&refs->base, refname.buf, RESOLVE_REF_READING, - &oid, &flag, &ignore_errno)) { + &oid, &flag)) { oidclr(&oid); flag |= REF_ISBROKEN; } else if (is_null_oid(&oid)) { @@ -1006,7 +1005,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, { struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; - int ignore_errno; files_assert_main_repository(refs, "lock_ref_oid_basic"); assert(err); @@ -1034,7 +1032,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, } if (!refs_resolve_ref_unsafe(&refs->base, lock->ref_name, 0, - &lock->old_oid, NULL, &ignore_errno)) + &lock->old_oid, NULL)) oidclr(&lock->old_oid); goto out; @@ -1399,7 +1397,6 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, struct strbuf tmp_renamed_log = STRBUF_INIT; int log, ret; struct strbuf err = STRBUF_INIT; - int ignore_errno; files_reflog_path(refs, &sb_oldref, oldrefname); files_reflog_path(refs, &sb_newref, newrefname); @@ -1413,7 +1410,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, if (!refs_resolve_ref_unsafe(&refs->base, oldrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &orig_oid, &flag, &ignore_errno)) { + &orig_oid, &flag)) { ret = error("refname %s not found", oldrefname); goto out; } @@ -1459,7 +1456,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, */ if (!copy && refs_resolve_ref_unsafe(&refs->base, newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - NULL, NULL, &ignore_errno) && + NULL, NULL) && refs_delete_ref(&refs->base, NULL, newrefname, NULL, REF_NO_DEREF)) { if (errno == EISDIR) { @@ -1828,12 +1825,10 @@ static int commit_ref_update(struct files_ref_store *refs, */ int head_flag; const char *head_ref; - int ignore_errno; head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD", RESOLVE_REF_READING, - NULL, &head_flag, - &ignore_errno); + NULL, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) { struct strbuf log_err = STRBUF_INIT; @@ -1877,12 +1872,10 @@ static void update_symref_reflog(struct files_ref_store *refs, { struct strbuf err = STRBUF_INIT; struct object_id new_oid; - int ignore_errno; if (logmsg && refs_resolve_ref_unsafe(&refs->base, target, - RESOLVE_REF_READING, &new_oid, NULL, - &ignore_errno) && + RESOLVE_REF_READING, &new_oid, NULL) && files_log_ref_write(refs, refname, &lock->old_oid, &new_oid, logmsg, 0, &err)) { error("%s", err.buf); @@ -2156,7 +2149,6 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) (struct files_reflog_iterator *)ref_iterator; struct dir_iterator *diter = iter->dir_iterator; int ok; - int ignore_errno; while ((ok = dir_iterator_advance(diter)) == ITER_OK) { int flags; @@ -2170,8 +2162,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) if (!refs_resolve_ref_unsafe(iter->ref_store, diter->relative_path, 0, - &iter->oid, &flags, - &ignore_errno)) { + &iter->oid, &flags)) { error("bad ref for %s", diter->path.buf); continue; } @@ -2515,11 +2506,9 @@ static int lock_ref_for_update(struct files_ref_store *refs, * the transaction, so we have to read it here * to record and possibly check old_oid: */ - int ignore_errno; if (!refs_resolve_ref_unsafe(&refs->base, referent.buf, 0, - &lock->old_oid, NULL, - &ignore_errno)) { + &lock->old_oid, NULL)) { if (update->flags & REF_HAVE_OLD) { strbuf_addf(err, "cannot lock ref '%s': " "error reading reference", @@ -3208,14 +3197,12 @@ static int files_reflog_expire(struct ref_store *ref_store, if ((expire_flags & EXPIRE_REFLOGS_UPDATE_REF) && !is_null_oid(&cb.last_kept_oid)) { - int ignore_errno; int type; const char *ref; ref = refs_resolve_ref_unsafe(&refs->base, refname, RESOLVE_REF_NO_RECURSE, - NULL, &type, - &ignore_errno); + NULL, &type); update = !!(ref && !(type & REF_ISSYMREF)); } @@ -508,9 +508,8 @@ static void read_config(struct repository *repo) repo->remote_state->current_branch = NULL; if (startup_info->have_repository) { - int ignore_errno; const char *head_ref = refs_resolve_ref_unsafe( - get_main_ref_store(repo), "HEAD", 0, NULL, &flag, &ignore_errno); + get_main_ref_store(repo), "HEAD", 0, NULL, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { repo->remote_state->current_branch = make_branch( @@ -7,6 +7,7 @@ #include "tree-walk.h" #include "tree.h" #include "unpack-trees.h" +#include "hook.h" int reset_head(struct repository *r, struct object_id *oid, const char *action, const char *switch_to_branch, unsigned flags, @@ -127,7 +128,7 @@ reset_head_refs: reflog_head); } if (run_hook) - run_hook_le(NULL, "post-checkout", + run_hooks_l("post-checkout", oid_to_hex(orig ? orig : null_oid()), oid_to_hex(oid), "1", NULL); diff --git a/run-command.c b/run-command.c index 69dde42f1e..a8501e38ce 100644 --- a/run-command.c +++ b/run-command.c @@ -1307,39 +1307,6 @@ int async_with_fork(void) #endif } -int run_hook_ve(const char *const *env, const char *name, va_list args) -{ - struct child_process hook = CHILD_PROCESS_INIT; - const char *p; - - p = find_hook(name); - if (!p) - return 0; - - strvec_push(&hook.args, p); - while ((p = va_arg(args, const char *))) - strvec_push(&hook.args, p); - if (env) - strvec_pushv(&hook.env_array, (const char **)env); - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - hook.trace2_hook_name = name; - - return run_command(&hook); -} - -int run_hook_le(const char *const *env, const char *name, ...) -{ - va_list args; - int ret; - - va_start(args, name); - ret = run_hook_ve(env, name, args); - va_end(args); - - return ret; -} - struct io_pump { /* initialized by caller */ int fd; diff --git a/run-command.h b/run-command.h index 2be5f5d642..07bed6c31b 100644 --- a/run-command.h +++ b/run-command.h @@ -220,23 +220,6 @@ int finish_command_in_signal(struct child_process *); */ int run_command(struct child_process *); -/** - * Run a hook. - * The first argument is a pathname to an index file, or NULL - * if the hook uses the default index file or no index is needed. - * The second argument is the name of the hook. - * The further arguments correspond to the hook arguments. - * The last argument has to be NULL to terminate the arguments list. - * If the hook does not exist or is not executable, the return - * value will be zero. - * If it is executable, the hook will be executed and the exit - * status of the hook is returned. - * On execution, .stdout_to_stderr and .no_stdin will be set. - */ -LAST_ARG_MUST_BE_NULL -int run_hook_le(const char *const *env, const char *name, ...); -int run_hook_ve(const char *const *env, const char *name, va_list args); - /* * Trigger an auto-gc */ diff --git a/sequencer.c b/sequencer.c index 6abd72160c..fb978a53a0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1281,7 +1281,6 @@ void print_commit_summary(struct repository *r, struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; struct ref_store *refs; - int resolve_errno; commit = lookup_commit(r, oid); if (!commit) @@ -1332,12 +1331,9 @@ void print_commit_summary(struct repository *r, diff_setup_done(&rev.diffopt); refs = get_main_ref_store(the_repository); - head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL, - &resolve_errno); - if (!head) { - errno = resolve_errno; - die_errno(_("unable to resolve HEAD after creating commit")); - } + head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL); + if (!head) + die(_("unable to resolve HEAD after creating commit")); if (!strcmp(head, "HEAD")) head = _("detached HEAD"); else @@ -4223,8 +4219,11 @@ static int run_git_checkout(struct repository *r, struct replay_opts *opts, cmd.git_cmd = 1; - if (startup_info->original_cwd) + if (startup_info->original_cwd) { cmd.dir = startup_info->original_cwd; + strvec_pushf(&cmd.env_array, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, r->worktree); + } strvec_push(&cmd.args, "checkout"); strvec_push(&cmd.args, commit); strvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action); diff --git a/sparse-index.c b/sparse-index.c index a1d505d50e..08f54747bb 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -136,7 +136,7 @@ static int is_sparse_index_allowed(struct index_state *istate, int flags) /* * The sparse index is not (yet) integrated with a split index. */ - if (istate->split_index) + if (istate->split_index || git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) return 0; /* * The GIT_TEST_SPARSE_INDEX environment variable triggers the diff --git a/split-index.c b/split-index.c index 8e52e891c3..9d0ccc30d0 100644 --- a/split-index.c +++ b/split-index.c @@ -5,6 +5,9 @@ struct split_index *init_split_index(struct index_state *istate) { if (!istate->split_index) { + if (istate->sparse_index) + die(_("cannot use split index with a sparse index")); + CALLOC_ARRAY(istate->split_index, 1); istate->split_index->refcount = 1; } diff --git a/stable-qsort.c b/stable-qsort.c index 6cbaf39f7b..7ff12467cd 100644 --- a/stable-qsort.c +++ b/stable-qsort.c @@ -48,15 +48,9 @@ void git_stable_qsort(void *b, size_t n, size_t s, int (*cmp)(const void *, const void *)) { const size_t size = st_mult(n, s); - char buf[1024]; - - if (size < sizeof(buf)) { - /* The temporary array fits on the small on-stack buffer. */ - msort_with_tmp(b, n, s, cmp, buf); - } else { - /* It's somewhat large, so malloc it. */ - char *tmp = xmalloc(size); - msort_with_tmp(b, n, s, cmp, tmp); - free(tmp); - } + char *tmp; + + tmp = xmalloc(size); + msort_with_tmp(b, n, s, cmp, tmp); + free(tmp); } diff --git a/t/helper/test-csprng.c b/t/helper/test-csprng.c new file mode 100644 index 0000000000..65d14973c5 --- /dev/null +++ b/t/helper/test-csprng.c @@ -0,0 +1,29 @@ +#include "test-tool.h" +#include "git-compat-util.h" + + +int cmd__csprng(int argc, const char **argv) +{ + unsigned long count; + unsigned char buf[1024]; + + if (argc > 2) { + fprintf(stderr, "usage: %s [<size>]\n", argv[0]); + return 2; + } + + count = (argc == 2) ? strtoul(argv[1], NULL, 0) : -1L; + + while (count) { + unsigned long chunk = count < sizeof(buf) ? count : sizeof(buf); + if (csprng_bytes(buf, chunk) < 0) { + perror("failed to read"); + return 5; + } + if (fwrite(buf, chunk, 1, stdout) != chunk) + return 1; + count -= chunk; + } + + return 0; +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 3e4ddaee70..9646d85fc8 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -180,10 +180,9 @@ static int cmd_resolve_ref(struct ref_store *refs, const char **argv) int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags); int flags; const char *ref; - int ignore_errno; ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags, - &oid, &flags, &ignore_errno); + &oid, &flags); printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags); return ref ? 0 : 1; } diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 338a57b104..e6ec69cf32 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -20,6 +20,7 @@ static struct test_cmd cmds[] = { { "chmtime", cmd__chmtime }, { "config", cmd__config }, { "crontab", cmd__crontab }, + { "csprng", cmd__csprng }, { "ctype", cmd__ctype }, { "date", cmd__date }, { "delta", cmd__delta }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 48cee1f4a2..20756eefdd 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -10,6 +10,7 @@ int cmd__bloom(int argc, const char **argv); int cmd__chmtime(int argc, const char **argv); int cmd__config(int argc, const char **argv); int cmd__crontab(int argc, const char **argv); +int cmd__csprng(int argc, const char **argv); int cmd__ctype(int argc, const char **argv); int cmd__date(int argc, const char **argv); int cmd__delta(int argc, const char **argv); diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index 4a5c5c602c..c5f7ac63b0 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -597,6 +597,12 @@ do # auto: core.autocrlf=false and core.eol unset(or native) uses native eol checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul + # core.autocrlf false, .gitattributes sets eol + checkout_files "" "$id" "lf" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files "" "$id" "crlf" false "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + # core.autocrlf true, .gitattributes sets eol + checkout_files "" "$id" "lf" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files "" "$id" "crlf" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul done # The rest of the tests are unique; do the usual linting. diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 39382fa195..145eee11df 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -4,6 +4,98 @@ test_description='git cat-file' . ./test-lib.sh +test_cmdmode_usage () { + test_expect_code 129 "$@" 2>err && + grep "^error:.*is incompatible with" err +} + +for switches in \ + '-e -p' \ + '-p -t' \ + '-t -s' \ + '-s --textconv' \ + '--textconv --filters' \ + '--batch-all-objects -e' +do + test_expect_success "usage: cmdmode $switches" ' + test_cmdmode_usage git cat-file $switches + ' +done + +test_incompatible_usage () { + test_expect_code 129 "$@" 2>err && + grep -E "^(fatal|error):.*(requires|incompatible with|needs)" err +} + +for opt in --batch --batch-check +do + test_expect_success "usage: incompatible options: --path with $opt" ' + test_incompatible_usage git cat-file --path=foo $opt + ' +done + +test_missing_usage () { + test_expect_code 129 "$@" 2>err && + grep -E "^fatal:.*required" err +} + +short_modes="-e -p -t -s" +cw_modes="--textconv --filters" + +for opt in $cw_modes +do + test_expect_success "usage: $opt requires another option" ' + test_missing_usage git cat-file $opt + ' +done + +for opt in $short_modes +do + test_expect_success "usage: $opt requires another option" ' + test_missing_usage git cat-file $opt + ' + + for opt2 in --batch \ + --batch-check \ + --follow-symlinks \ + "--path=foo HEAD:some-path.txt" + do + test_expect_success "usage: incompatible options: $opt and $opt2" ' + test_incompatible_usage git cat-file $opt $opt2 + ' + done +done + +test_too_many_arguments () { + test_expect_code 129 "$@" 2>err && + grep -E "^fatal: too many arguments$" err +} + +for opt in $short_modes $cw_modes +do + args="one two three" + test_expect_success "usage: too many arguments: $opt $args" ' + test_too_many_arguments git cat-file $opt $args + ' + + for opt2 in --buffer --follow-symlinks + do + test_expect_success "usage: incompatible arguments: $opt with batch option $opt2" ' + test_incompatible_usage git cat-file $opt $opt2 + ' + done +done + +for opt in --buffer \ + --follow-symlinks \ + --batch-all-objects +do + test_expect_success "usage: bad option combination: $opt without batch mode" ' + test_incompatible_usage git cat-file $opt && + test_incompatible_usage git cat-file $opt commit HEAD + ' +done + echo_without_newline () { printf '%s' "$*" } diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 42776984fe..3592d12442 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -5,6 +5,9 @@ test_description='sparse checkout builtin tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +GIT_TEST_SPLIT_INDEX=false +export GIT_TEST_SPLIT_INDEX + . ./test-lib.sh list_files() { @@ -79,6 +82,12 @@ test_expect_success 'git sparse-checkout init' ' check_files repo a ' +test_expect_success 'git sparse-checkout init in empty repo' ' + test_when_finished rm -rf empty-repo blank-template && + git init --template= empty-repo && + git -C empty-repo sparse-checkout init +' + test_expect_success 'git sparse-checkout list after init' ' git -C repo sparse-checkout list >actual && cat >expect <<-\EOF && @@ -228,36 +237,31 @@ test_expect_success 'sparse-checkout disable' ' ' test_expect_success 'sparse-index enabled and disabled' ' - ( - sane_unset GIT_TEST_SPLIT_INDEX && - git -C repo update-index --no-split-index && - - git -C repo sparse-checkout init --cone --sparse-index && - test_cmp_config -C repo true index.sparse && - git -C repo ls-files --sparse >sparse && - git -C repo sparse-checkout disable && - git -C repo ls-files --sparse >full && - - cat >expect <<-\EOF && - @@ -1,4 +1,7 @@ - a - -deep/ - -folder1/ - -folder2/ - +deep/a - +deep/deeper1/a - +deep/deeper1/deepest/a - +deep/deeper2/a - +folder1/a - +folder2/a - EOF + git -C repo sparse-checkout init --cone --sparse-index && + test_cmp_config -C repo true index.sparse && + git -C repo ls-files --sparse >sparse && + git -C repo sparse-checkout disable && + git -C repo ls-files --sparse >full && - diff -u sparse full | tail -n +3 >actual && - test_cmp expect actual && + cat >expect <<-\EOF && + @@ -1,4 +1,7 @@ + a + -deep/ + -folder1/ + -folder2/ + +deep/a + +deep/deeper1/a + +deep/deeper1/deepest/a + +deep/deeper2/a + +folder1/a + +folder2/a + EOF + + diff -u sparse full | tail -n +3 >actual && + test_cmp expect actual && - git -C repo config --list >config && - ! grep index.sparse config - ) + git -C repo config --list >config && + ! grep index.sparse config ' test_expect_success 'cone mode: init and set' ' diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 78359f1f4a..7dd9b325d9 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2388,4 +2388,122 @@ test_expect_success '--get and --get-all with --fixed-value' ' test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent ' +test_expect_success 'includeIf.hasconfig:remote.*.url' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-this <<-\EOF && + [user] + this = this-is-included + EOF + cat >dont-include-that <<-\EOF && + [user] + that = that-is-not-included + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-this" + [includeIf "hasconfig:remote.*.url:barurl"] + path = "$(pwd)/dont-include-that" + [remote "foo"] + url = foourl + EOF + + echo this-is-included >expect-this && + git -C hasremoteurlTest config --get user.this >actual-this && + test_cmp expect-this actual-this && + + test_must_fail git -C hasremoteurlTest config --get user.that +' + +test_expect_success 'includeIf.hasconfig:remote.*.url respects last-config-wins' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-two-three <<-\EOF && + [user] + two = included-config + three = included-config + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [remote "foo"] + url = foourl + [user] + one = main-config + two = main-config + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-two-three" + [user] + three = main-config + EOF + + echo main-config >expect-main-config && + echo included-config >expect-included-config && + + git -C hasremoteurlTest config --get user.one >actual && + test_cmp expect-main-config actual && + + git -C hasremoteurlTest config --get user.two >actual && + test_cmp expect-included-config actual && + + git -C hasremoteurlTest config --get user.three >actual && + test_cmp expect-main-config actual +' + +test_expect_success 'includeIf.hasconfig:remote.*.url globs' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + printf "[user]\ndss = yes\n" >double-star-start && + printf "[user]\ndse = yes\n" >double-star-end && + printf "[user]\ndsm = yes\n" >double-star-middle && + printf "[user]\nssm = yes\n" >single-star-middle && + printf "[user]\nno = no\n" >no && + + cat >>hasremoteurlTest/.git/config <<-EOF && + [remote "foo"] + url = https://foo/bar/baz + [includeIf "hasconfig:remote.*.url:**/baz"] + path = "$(pwd)/double-star-start" + [includeIf "hasconfig:remote.*.url:**/nomatch"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https:/**"] + path = "$(pwd)/double-star-end" + [includeIf "hasconfig:remote.*.url:nomatch:/**"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https:/**/baz"] + path = "$(pwd)/double-star-middle" + [includeIf "hasconfig:remote.*.url:https:/**/nomatch"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https://*/bar/baz"] + path = "$(pwd)/single-star-middle" + [includeIf "hasconfig:remote.*.url:https://*/baz"] + path = "$(pwd)/no" + EOF + + git -C hasremoteurlTest config --get user.dss && + git -C hasremoteurlTest config --get user.dse && + git -C hasremoteurlTest config --get user.dsm && + git -C hasremoteurlTest config --get user.ssm && + test_must_fail git -C hasremoteurlTest config --get user.no +' + +test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-with-url <<-\EOF && + [remote "bar"] + url = barurl + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-with-url" + EOF + + # test with any Git command + test_must_fail git -C hasremoteurlTest status 2>err && + grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err +' + test_done diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh new file mode 100755 index 0000000000..29718aa991 --- /dev/null +++ b/t/t1800-hook.sh @@ -0,0 +1,134 @@ +#!/bin/sh + +test_description='git-hook command' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'git hook usage' ' + test_expect_code 129 git hook && + test_expect_code 129 git hook run && + test_expect_code 129 git hook run -h && + test_expect_code 129 git hook run --unknown 2>err && + grep "unknown option" err +' + +test_expect_success 'git hook run: nonexistent hook' ' + cat >stderr.expect <<-\EOF && + error: cannot find a hook named test-hook + EOF + test_expect_code 1 git hook run test-hook 2>stderr.actual && + test_cmp stderr.expect stderr.actual +' + +test_expect_success 'git hook run: nonexistent hook with --ignore-missing' ' + git hook run --ignore-missing does-not-exist 2>stderr.actual && + test_must_be_empty stderr.actual +' + +test_expect_success 'git hook run: basic' ' + write_script .git/hooks/test-hook <<-EOF && + echo Test hook + EOF + + cat >expect <<-\EOF && + Test hook + EOF + git hook run test-hook 2>actual && + test_cmp expect actual +' + +test_expect_success 'git hook run: stdout and stderr both write to our stderr' ' + write_script .git/hooks/test-hook <<-EOF && + echo >&1 Will end up on stderr + echo >&2 Will end up on stderr + EOF + + cat >stderr.expect <<-\EOF && + Will end up on stderr + Will end up on stderr + EOF + git hook run test-hook >stdout.actual 2>stderr.actual && + test_cmp stderr.expect stderr.actual && + test_must_be_empty stdout.actual +' + +test_expect_success 'git hook run: exit codes are passed along' ' + write_script .git/hooks/test-hook <<-EOF && + exit 1 + EOF + + test_expect_code 1 git hook run test-hook && + + write_script .git/hooks/test-hook <<-EOF && + exit 2 + EOF + + test_expect_code 2 git hook run test-hook && + + write_script .git/hooks/test-hook <<-EOF && + exit 128 + EOF + + test_expect_code 128 git hook run test-hook && + + write_script .git/hooks/test-hook <<-EOF && + exit 129 + EOF + + test_expect_code 129 git hook run test-hook +' + +test_expect_success 'git hook run arg u ments without -- is not allowed' ' + test_expect_code 129 git hook run test-hook arg u ments +' + +test_expect_success 'git hook run -- pass arguments' ' + write_script .git/hooks/test-hook <<-\EOF && + echo $1 + echo $2 + EOF + + cat >expect <<-EOF && + arg + u ments + EOF + + git hook run test-hook -- arg "u ments" 2>actual && + test_cmp expect actual +' + +test_expect_success 'git hook run -- out-of-repo runs excluded' ' + write_script .git/hooks/test-hook <<-EOF && + echo Test hook + EOF + + nongit test_must_fail git hook run test-hook +' + +test_expect_success 'git -c core.hooksPath=<PATH> hook run' ' + mkdir my-hooks && + write_script my-hooks/test-hook <<-\EOF && + echo Hook ran $1 >>actual + EOF + + cat >expect <<-\EOF && + Test hook + Hook ran one + Hook ran two + Hook ran three + Hook ran four + EOF + + # Test various ways of specifying the path. See also + # t1350-config-hooks-path.sh + >actual && + git hook run test-hook -- ignored 2>>actual && + git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual && + git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual && + git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual && + git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual && + test_cmp expect actual +' + +test_done diff --git a/t/t2108-update-index-refresh-racy.sh b/t/t2108-update-index-refresh-racy.sh new file mode 100755 index 0000000000..bc5f2886fa --- /dev/null +++ b/t/t2108-update-index-refresh-racy.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +test_description='update-index refresh tests related to racy timestamps' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +reset_files () { + echo content >file && + echo content >other && + test_set_magic_mtime file && + test_set_magic_mtime other +} + +update_assert_changed () { + test_set_magic_mtime .git/index && + test_might_fail git update-index "$1" && + ! test_is_magic_mtime .git/index +} + +test_expect_success 'setup' ' + reset_files && + # we are calling reset_files() a couple of times during tests; + # test-tool chmtime does not change the ctime; to not weaken + # or even break our tests, disable ctime-checks entirely + git config core.trustctime false && + git add file other && + git commit -m "initial import" +' + +test_expect_success '--refresh has no racy timestamps to fix' ' + reset_files && + # set the index time far enough to the future; + # it must be at least 3 seconds for VFAT + test_set_magic_mtime .git/index +60 && + git update-index --refresh && + test_is_magic_mtime .git/index +60 +' + +test_expect_success '--refresh should fix racy timestamp' ' + reset_files && + update_assert_changed --refresh +' + +test_expect_success '--really-refresh should fix racy timestamp' ' + reset_files && + update_assert_changed --really-refresh +' + +test_expect_success '--refresh should fix racy timestamp if other file needs update' ' + reset_files && + echo content2 >other && + test_set_magic_mtime other && + update_assert_changed --refresh +' + +test_expect_success '--refresh should fix racy timestamp if racy file needs update' ' + reset_files && + echo content2 >file && + test_set_magic_mtime file && + update_assert_changed --refresh +' + +test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 23dbd3c82e..71b1735e1d 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -416,4 +416,25 @@ test_expect_success MINGW,SYMLINKS_WINDOWS 'rebase when .git/logs is a symlink' mv actual_logs .git/logs ' +test_expect_success 'rebase when inside worktree subdirectory' ' + git init main-wt && + ( + cd main-wt && + git commit --allow-empty -m "initial" && + mkdir -p foo/bar && + test_commit foo/bar/baz && + mkdir -p a/b && + test_commit a/b/c && + # create another branch for our other worktree + git branch other && + git worktree add ../other-wt other && + cd ../other-wt && + # create and cd into a subdirectory + mkdir -p random/dir && + cd random/dir && + # now do the rebase + git rebase --onto HEAD^^ HEAD^ # drops the HEAD^ commit + ) +' + test_done diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 19c6f4acbf..1e9f7833dd 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -11,7 +11,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME log_with_names () { git rev-list --topo-order --parents --pretty="tformat:%s" HEAD | - git name-rev --stdin --name-only --refs=refs/heads/$1 + git name-rev --annotate-stdin --name-only --refs=refs/heads/$1 } diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 207714655f..94537a6b40 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -326,7 +326,9 @@ test_expect_success 'correct message when there is nothing to do' ' test_expect_success 'setup again' ' git reset --hard && test_chmod +x file && - echo content >>file + echo content >>file && + test_write_lines A B C D>file2 && + git add file2 ' # Write the patch file with a new line at the top and bottom @@ -341,13 +343,27 @@ test_expect_success 'setup patch' ' content +lastline \ No newline at end of file + diff --git a/file2 b/file2 + index 8422d40..35b930a 100644 + --- a/file2 + +++ b/file2 + @@ -1,4 +1,5 @@ + -A + +Z + B + +Y + C + -D + +X EOF ' # Expected output, diff is similar to the patch but w/ diff at the top test_expect_success 'setup expected' ' echo diff --git a/file b/file >expected && - cat patch |sed "/^index/s/ 100644/ 100755/" >>expected && + sed -e "/^index 180b47c/s/ 100644/ 100755/" \ + -e /1,5/s//1,4/ \ + -e /Y/d patch >>expected && cat >expected-output <<-\EOF --- a/file +++ b/file @@ -366,6 +382,28 @@ test_expect_success 'setup expected' ' content +lastline \ No newline at end of file + --- a/file2 + +++ b/file2 + @@ -1,4 +1,5 @@ + -A + +Z + B + +Y + C + -D + +X + @@ -1,2 +1,2 @@ + -A + +Z + B + @@ -2,2 +2,3 @@ + B + +Y + C + @@ -3,2 +4,2 @@ + C + -D + +X EOF ' @@ -373,9 +411,9 @@ test_expect_success 'setup expected' ' test_expect_success 'add first line works' ' git commit -am "clear local changes" && git apply patch && - printf "%s\n" s y y | git add -p file 2>error | - sed -n -e "s/^([1-2]\/[1-2]) Stage this hunk[^@]*\(@@ .*\)/\1/" \ - -e "/^[-+@ \\\\]"/p >output && + test_write_lines s y y s y n y | git add -p 2>error >raw-output && + sed -n -e "s/^([1-9]\/[1-9]) Stage this hunk[^@]*\(@@ .*\)/\1/" \ + -e "/^[-+@ \\\\]"/p raw-output >output && test_must_be_empty error && git diff --cached >diff && diff_cmp expected diff && diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 5049559861..dc884107de 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -659,7 +659,7 @@ EOF test_expect_success 'log --graph with full output' ' git log --graph --date-order --pretty=short | - git name-rev --name-only --stdin | + git name-rev --name-only --annotate-stdin | sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual && test_cmp expect actual ' diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2f04cf9a1c..87881726ed 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -229,6 +229,18 @@ test_expect_success 'push with negotiation proceeds anyway even if negotiation f test_i18ngrep "push negotiation failed" err ' +test_expect_success 'push with negotiation does not attempt to fetch submodules' ' + mk_empty submodule_upstream && + test_commit -C submodule_upstream submodule_commit && + git submodule add ./submodule_upstream submodule && + mk_empty testrepo && + git push testrepo $the_first_commit:refs/remotes/origin/first_commit && + test_commit -C testrepo unrelated_commit && + git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && + git -c submodule.recurse=true -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err && + ! grep "Fetching submodule" err +' + test_expect_success 'push without wildcard' ' mk_empty testrepo && diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 93ecfcdd24..081808009b 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -330,6 +330,19 @@ test_expect_success '--rebase --autostash fast forward' ' test_cmp_rev HEAD to-rebase-ff ' +test_expect_success '--rebase with rebase.autostash succeeds on ff' ' + test_when_finished "rm -fr src dst actual" && + git init src && + test_commit -C src "initial" file "content" && + git clone src dst && + test_commit -C src --printf "more_content" file "more content\ncontent\n" && + echo "dirty" >>dst/file && + test_config -C dst rebase.autostash true && + git -C dst pull --rebase >actual 2>&1 && + grep -q "Fast-forward" actual && + grep -q "Applied autostash." actual +' + test_expect_success '--rebase with conflicts shows advice' ' test_when_finished "git rebase --abort; git checkout -f to-rebase" && git checkout -b seq && diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh index 468bd3e13e..6c8d4c6cf1 100755 --- a/t/t5700-protocol-v1.sh +++ b/t/t5700-protocol-v1.sh @@ -149,6 +149,21 @@ test_expect_success 'push with file:// using protocol v1' ' grep "push< version 1" log ' +test_expect_success 'cloning branchless tagless but not refless remote' ' + rm -rf server client && + + git -c init.defaultbranch=main init server && + echo foo >server/foo.txt && + git -C server add foo.txt && + git -C server commit -m "message" && + git -C server update-ref refs/notbranch/alsonottag HEAD && + git -C server checkout --detach && + git -C server branch -D main && + git -C server symbolic-ref HEAD refs/heads/nonexistentbranch && + + git -c protocol.version=1 clone "file://$(pwd)/server" client +' + # Test protocol v1 with 'ssh://' transport # test_expect_success 'setup ssh wrapper' ' diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 710f33e2aa..900254947f 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -628,6 +628,18 @@ test_expect_success 'usage: --negotiate-only without --negotiation-tip' ' test_cmp err.expect err.actual ' +test_expect_success 'usage: --negotiate-only with --recurse-submodules' ' + cat >err.expect <<-\EOF && + fatal: options '\''--negotiate-only'\'' and '\''--recurse-submodules'\'' cannot be used together + EOF + + test_must_fail git -c protocol.version=2 -C client fetch \ + --negotiate-only \ + --recurse-submodules \ + origin 2>err.actual && + test_cmp err.expect err.actual +' + test_expect_success 'file:// --negotiate-only' ' SERVER="server" && URI="file://$(pwd)/server" && diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index aebe4b69e1..6f3e543977 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -58,7 +58,7 @@ EOF test_expect_success '--left-right' ' git rev-list --left-right B...C > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -78,14 +78,14 @@ EOF test_expect_success '--cherry-pick bar does not come up empty' ' git rev-list --left-right --cherry-pick B...C -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' test_expect_success 'bar does not come up empty' ' git rev-list --left-right B...C -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -97,14 +97,14 @@ EOF test_expect_success '--cherry-pick bar does not come up empty (II)' ' git rev-list --left-right --cherry-pick F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' test_expect_success 'name-rev multiple --refs combine inclusive' ' git rev-list --left-right --cherry-pick F...E -- bar >actual && - git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \ + git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" \ <actual >actual.named && test_cmp expect actual.named ' @@ -116,7 +116,7 @@ EOF test_expect_success 'name-rev --refs excludes non-matched patterns' ' git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && git rev-list --left-right --cherry-pick F...E -- bar >actual && - git name-rev --stdin --name-only --refs="*tags/F" \ + git name-rev --annotate-stdin --name-only --refs="*tags/F" \ <actual >actual.named && test_cmp expect actual.named ' @@ -128,14 +128,14 @@ EOF test_expect_success 'name-rev --exclude excludes matched patterns' ' git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && git rev-list --left-right --cherry-pick F...E -- bar >actual && - git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" --exclude="*E" \ <actual >actual.named && test_cmp expect actual.named ' test_expect_success 'name-rev --no-refs clears the refs list' ' git rev-list --left-right --cherry-pick F...E -- bar >expect && - git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \ + git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \ <expect >actual && test_cmp expect actual ' @@ -149,7 +149,7 @@ EOF test_expect_success '--cherry-mark' ' git rev-list --cherry-mark F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -163,7 +163,7 @@ EOF test_expect_success '--cherry-mark --left-right' ' git rev-list --cherry-mark --left-right F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -174,14 +174,14 @@ EOF test_expect_success '--cherry-pick --right-only' ' git rev-list --cherry-pick --right-only F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' test_expect_success '--cherry-pick --left-only' ' git rev-list --cherry-pick --left-only E...F -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -193,7 +193,7 @@ EOF test_expect_success '--cherry' ' git rev-list --cherry F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 4f7fa8b6c0..4fedc614fa 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -12,7 +12,7 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" + git name-rev --tags --annotate-stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" } # diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh index e07b6070e0..90ff141640 100755 --- a/t/t6111-rev-list-treesame.sh +++ b/t/t6111-rev-list-treesame.sh @@ -23,7 +23,8 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g" + git name-rev --tags --annotate-stdin | \ + sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g" } test_expect_success setup ' diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index d8af2bb9d2..9781b92aed 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -270,7 +270,7 @@ test_expect_success 'name-rev --all' ' test_cmp expect actual ' -test_expect_success 'name-rev --stdin' ' +test_expect_success 'name-rev --annotate-stdin' ' >expect.unsorted && for rev in $(git rev-list --all) do @@ -278,11 +278,16 @@ test_expect_success 'name-rev --stdin' ' echo "$rev ($name)" >>expect.unsorted || return 1 done && sort <expect.unsorted >expect && - git rev-list --all | git name-rev --stdin >actual.unsorted && + git rev-list --all | git name-rev --annotate-stdin >actual.unsorted && sort <actual.unsorted >actual && test_cmp expect actual ' +test_expect_success 'name-rev --stdin deprecated' " + git rev-list --all | git name-rev --stdin 2>actual && + grep -E 'warning: --stdin is deprecated' actual +" + test_expect_success 'describe --contains with the exact tags' ' echo "A^0" >expect && tag_object=$(git rev-parse refs/tags/A) && diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index 035edc40b1..f2bc8a7d2a 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh @@ -697,4 +697,71 @@ test_expect_success 'caching renames only on upstream side, part 2' ' ) ' +# +# The following testcase just creates two simple renames (slightly modified +# on both sides but without conflicting changes), and a directory full of +# files that are otherwise uninteresting. The setup is as follows: +# +# base: unrelated/<BUNCH OF FILES> +# numbers +# values +# upstream: modify: numbers +# modify: values +# topic: add: unrelated/foo +# modify: numbers +# modify: values +# rename: numbers -> sequence +# rename: values -> progression +# +# This is a trivial rename case, but we're curious what happens with a very +# low renameLimit interacting with the restart optimization trying to notice +# that unrelated/ looks like a trivial merge candidate. +# +test_expect_success 'avoid assuming we detected renames' ' + git init redo-weirdness && + ( + cd redo-weirdness && + + mkdir unrelated && + for i in $(test_seq 1 10) + do + >unrelated/$i + done && + test_seq 2 10 >numbers && + test_seq 12 20 >values && + git add numbers values unrelated/ && + git commit -m orig && + + git branch upstream && + git branch topic && + + git switch upstream && + test_seq 1 10 >numbers && + test_seq 11 20 >values && + git add numbers && + git commit -m "Some tweaks" && + + git switch topic && + + >unrelated/foo && + test_seq 2 12 >numbers && + test_seq 12 22 >values && + git add numbers values unrelated/ && + git mv numbers sequence && + git mv values progression && + git commit -m A && + + # + # Actual testing + # + + git switch --detach topic^0 && + + test_must_fail git -c merge.renameLimit=1 rebase upstream && + + git ls-files -u >actual && + ! test_file_is_empty actual + ) +' + test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 05c6c02435..2b7ef6c41a 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1647,13 +1647,33 @@ test_expect_success '"Initial commit" should not be noted in commit template' ' ' test_expect_success '--no-optional-locks prevents index update' ' - test-tool chmtime =1234567890 .git/index && + test_set_magic_mtime .git/index && git --no-optional-locks status && - test-tool chmtime --get .git/index >out && - grep ^1234567890 out && + test_is_magic_mtime .git/index && git status && - test-tool chmtime --get .git/index >out && - ! grep ^1234567890 out + ! test_is_magic_mtime .git/index +' + +test_expect_success 'racy timestamps will be fixed for clean worktree' ' + echo content >racy-dirty && + echo content >racy-racy && + git add racy* && + git commit -m "racy test files" && + # let status rewrite the index, if necessary; after that we expect + # no more index writes unless caused by racy timestamps; note that + # timestamps may already be racy now (depending on previous tests) + git status && + test_set_magic_mtime .git/index && + git status && + ! test_is_magic_mtime .git/index +' + +test_expect_success 'racy timestamps will be fixed for dirty worktree' ' + echo content2 >racy-dirty && + git status && + test_set_magic_mtime .git/index && + git status && + ! test_is_magic_mtime .git/index ' test_done diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index eacd49ade6..b067983ba1 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -19,6 +19,48 @@ test_expect_success 'setup ' ' GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' +test_expect_success 'usage: <bad rev>' ' + cat >expect <<-\EOF && + fatal: Not a valid object name HEAD2 + EOF + test_must_fail git cat-file --textconv HEAD2 2>actual && + test_cmp expect actual +' + +test_expect_success 'usage: <bad rev>:<bad path>' ' + cat >expect <<-\EOF && + fatal: invalid object name '\''HEAD2'\''. + EOF + test_must_fail git cat-file --textconv HEAD2:two.bin 2>actual && + test_cmp expect actual +' + +test_expect_success 'usage: <rev>:<bad path>' ' + cat >expect <<-\EOF && + fatal: path '\''two.bin'\'' does not exist in '\''HEAD'\'' + EOF + test_must_fail git cat-file --textconv HEAD:two.bin 2>actual && + test_cmp expect actual +' + + +test_expect_success 'usage: <rev> with no <path>' ' + cat >expect <<-\EOF && + fatal: <object>:<path> required, only <object> '\''HEAD'\'' given + EOF + test_must_fail git cat-file --textconv HEAD 2>actual && + test_cmp expect actual +' + + +test_expect_success 'usage: <bad rev>:<good (in HEAD) path>' ' + cat >expect <<-\EOF && + fatal: invalid object name '\''HEAD2'\''. + EOF + test_must_fail git cat-file --textconv HEAD2:one.bin 2>actual && + test_cmp expect actual +' + cat >expected <<EOF bin: test version 2 EOF diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index aa0c20499b..84d0f40d76 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" ' test_path_is_file my-hooks.ran && cat >expect <<-EOF && fatal: longline.patch: rejected by sendemail-validate hook - fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1 + fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1 warning: no patches were sent EOF test_cmp expect actual @@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" ' test_path_is_file my-hooks.ran && cat >expect <<-EOF && fatal: longline.patch: rejected by sendemail-validate hook - fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1 + fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1 warning: no patches were sent EOF test_cmp expect actual diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index c3d38aaccb..85385d2ede 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1840,3 +1840,36 @@ test_region () { test_readlink () { perl -le 'print readlink($_) for @ARGV' "$@" } + +# Set mtime to a fixed "magic" timestamp in mid February 2009, before we +# run an operation that may or may not touch the file. If the file was +# touched, its timestamp will not accidentally have such an old timestamp, +# as long as your filesystem clock is reasonably correct. To verify the +# timestamp, follow up with test_is_magic_mtime. +# +# An optional increment to the magic timestamp may be specified as second +# argument. +test_set_magic_mtime () { + local inc=${2:-0} && + local mtime=$((1234567890 + $inc)) && + test-tool chmtime =$mtime "$1" && + test_is_magic_mtime "$1" $inc +} + +# Test whether the given file has the "magic" mtime set. This is meant to +# be used in combination with test_set_magic_mtime. +# +# An optional increment to the magic timestamp may be specified as second +# argument. Usually, this should be the same increment which was used for +# the associated test_set_magic_mtime. +test_is_magic_mtime () { + local inc=${2:-0} && + local mtime=$((1234567890 + $inc)) && + echo $mtime >.git/test-mtime-expect && + test-tool chmtime --get "$1" >.git/test-mtime-actual && + test_cmp .git/test-mtime-expect .git/test-mtime-actual + local ret=$? + rm -f .git/test-mtime-expect + rm -f .git/test-mtime-actual + return $ret +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 0f7a137c7d..e4716b0b86 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -449,6 +449,8 @@ unset VISUAL EMAIL LANGUAGE $("$PERL_PATH" -e ' unset XDG_CACHE_HOME unset XDG_CONFIG_HOME unset GITPERLLIB +unset GIT_TRACE2_PARENT_NAME +unset GIT_TRACE2_PARENT_SID TEST_AUTHOR_LOCALNAME=author TEST_AUTHOR_DOMAIN=example.com GIT_AUTHOR_EMAIL=${TEST_AUTHOR_LOCALNAME}@${TEST_AUTHOR_DOMAIN} diff --git a/worktree.c b/worktree.c index 6f598dcfcd..e8f6f6ae6f 100644 --- a/worktree.c +++ b/worktree.c @@ -28,13 +28,11 @@ static void add_head_info(struct worktree *wt) { int flags; const char *target; - int ignore_errno; target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt), "HEAD", 0, - &wt->head_oid, &flags, - &ignore_errno); + &wt->head_oid, &flags); if (!target) return; @@ -416,7 +414,6 @@ const struct worktree *find_shared_symref(struct worktree **worktrees, const char *symref_target; struct ref_store *refs; int flags; - int ignore_errno; if (wt->is_bare) continue; @@ -434,8 +431,7 @@ const struct worktree *find_shared_symref(struct worktree **worktrees, refs = get_worktree_ref_store(wt); symref_target = refs_resolve_ref_unsafe(refs, symref, 0, - NULL, &flags, - &ignore_errno); + NULL, &flags); if ((flags & REF_ISSYMREF) && symref_target && !strcmp(symref_target, target)) { existing = wt; @@ -563,7 +559,6 @@ int other_head_refs(each_ref_fn fn, void *cb_data) struct worktree *wt = *p; struct object_id oid; int flag; - int ignore_errno; if (wt->is_current) continue; @@ -573,7 +568,7 @@ int other_head_refs(each_ref_fn fn, void *cb_data) if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname.buf, RESOLVE_REF_READING, - &oid, &flag, &ignore_errno)) + &oid, &flag)) ret = fn(refname.buf, &oid, flag, cb_data); if (ret) break; @@ -463,8 +463,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) static const int num_letters = ARRAY_SIZE(letters) - 1; static const char x_pattern[] = "XXXXXX"; static const int num_x = ARRAY_SIZE(x_pattern) - 1; - uint64_t value; - struct timeval tv; char *filename_template; size_t len; int fd, count; @@ -485,12 +483,13 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) * Replace pattern's XXXXXX characters with randomness. * Try TMP_MAX different filenames. */ - gettimeofday(&tv, NULL); - value = ((uint64_t)tv.tv_usec << 16) ^ tv.tv_sec ^ getpid(); filename_template = &pattern[len - num_x - suffix_len]; for (count = 0; count < TMP_MAX; ++count) { - uint64_t v = value; int i; + uint64_t v; + if (csprng_bytes(&v, sizeof(v)) < 0) + return error_errno("unable to get random bytes for temporary file"); + /* Fill in the random bits. */ for (i = 0; i < num_x; i++) { filename_template[i] = letters[v % num_letters]; @@ -506,12 +505,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) */ if (errno != EEXIST) break; - /* - * This is a random value. It is only necessary that - * the next TMP_MAX values generated by adding 7777 to - * VALUE are different with (module 2^32). - */ - value += 7777; } /* We return the null string if we can't find a unique file name. */ pattern[0] = '\0'; @@ -702,3 +695,69 @@ int open_nofollow(const char *path, int flags) return open(path, flags); #endif } + +int csprng_bytes(void *buf, size_t len) +{ +#if defined(HAVE_ARC4RANDOM) || defined(HAVE_ARC4RANDOM_LIBBSD) + /* This function never returns an error. */ + arc4random_buf(buf, len); + return 0; +#elif defined(HAVE_GETRANDOM) + ssize_t res; + char *p = buf; + while (len) { + res = getrandom(p, len, 0); + if (res < 0) + return -1; + len -= res; + p += res; + } + return 0; +#elif defined(HAVE_GETENTROPY) + int res; + char *p = buf; + while (len) { + /* getentropy has a maximum size of 256 bytes. */ + size_t chunk = len < 256 ? len : 256; + res = getentropy(p, chunk); + if (res < 0) + return -1; + len -= chunk; + p += chunk; + } + return 0; +#elif defined(HAVE_RTLGENRANDOM) + if (!RtlGenRandom(buf, len)) + return -1; + return 0; +#elif defined(HAVE_OPENSSL_CSPRNG) + int res = RAND_bytes(buf, len); + if (res == 1) + return 0; + if (res == -1) + errno = ENOTSUP; + else + errno = EIO; + return -1; +#else + ssize_t res; + char *p = buf; + int fd, err; + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + while (len) { + res = xread(fd, p, len); + if (res < 0) { + err = errno; + close(fd); + errno = err; + return -1; + } + len -= res; + p += res; + } + close(fd); + return 0; +#endif +} |