diff options
208 files changed, 3286 insertions, 861 deletions
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index ed4e443a3c..390ceece52 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -91,8 +91,6 @@ For shell scripts specifically (not exhaustive): - No shell arrays. - - No strlen ${#parameter}. - - No pattern replacement ${parameter/pattern/string}. - We use Arithmetic Expansion $(( ... )). diff --git a/Documentation/Makefile b/Documentation/Makefile index 8fe829cc1b..15d9d04f31 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -30,6 +30,7 @@ MAN7_TXT += gitcredentials.txt MAN7_TXT += gitcvs-migration.txt MAN7_TXT += gitdiffcore.txt MAN7_TXT += giteveryday.txt +MAN7_TXT += gitfaq.txt MAN7_TXT += gitglossary.txt MAN7_TXT += gitnamespaces.txt MAN7_TXT += gitremote-helpers.txt @@ -149,32 +150,9 @@ endif -include ../config.mak.autogen -include ../config.mak -# -# For docbook-xsl ... -# -1.68.1, no extra settings are needed? -# 1.69.0, set ASCIIDOC_ROFF? -# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP? -# 1.71.1, set ASCIIDOC_ROFF? -# 1.72.0, set DOCBOOK_XSL_172. -# 1.73.0-, no extra settings are needed -# - -ifdef DOCBOOK_XSL_172 -ASCIIDOC_EXTRA += -a git-asciidoc-no-roff -MANPAGE_XSL = manpage-1.72.xsl -else - ifndef ASCIIDOC_ROFF - # docbook-xsl after 1.72 needs the regular XSL, but will not - # pass-thru raw roff codes from asciidoc.conf, so turn them off. - ASCIIDOC_EXTRA += -a git-asciidoc-no-roff - endif -endif ifndef NO_MAN_BOLD_LITERAL XMLTO_EXTRA += -m manpage-bold-literal.xsl endif -ifdef DOCBOOK_SUPPRESS_SP -XMLTO_EXTRA += -m manpage-suppress-sp.xsl -endif # Newer DocBook stylesheet emits warning cruft in the output when # this is not set, and if set it shows an absolute link. Older diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt index aa828dfdc4..c3f2d1a831 100644 --- a/Documentation/MyFirstObjectWalk.txt +++ b/Documentation/MyFirstObjectWalk.txt @@ -357,9 +357,6 @@ static void walken_commit_walk(struct rev_info *rev) ... while ((commit = get_revision(rev))) { - if (!commit) - continue; - strbuf_reset(&prettybuf); pp_commit_easy(CMIT_FMT_ONELINE, commit, &prettybuf); puts(prettybuf.buf); diff --git a/Documentation/RelNotes/2.17.4.txt b/Documentation/RelNotes/2.17.4.txt new file mode 100644 index 0000000000..7d794ca01a --- /dev/null +++ b/Documentation/RelNotes/2.17.4.txt @@ -0,0 +1,16 @@ +Git v2.17.4 Release Notes +========================= + +This release is to address the security issue: CVE-2020-5260 + +Fixes since v2.17.3 +------------------- + + * With a crafted URL that contains a newline in it, the credential + helper machinery can be fooled to give credential information for + a wrong host. The attack has been made impossible by forbidding + a newline character in any value passed via the credential + protocol. + +Credit for finding the vulnerability goes to Felix Wilhelm of Google +Project Zero. diff --git a/Documentation/RelNotes/2.17.5.txt b/Documentation/RelNotes/2.17.5.txt new file mode 100644 index 0000000000..2abb821a73 --- /dev/null +++ b/Documentation/RelNotes/2.17.5.txt @@ -0,0 +1,22 @@ +Git v2.17.5 Release Notes +========================= + +This release is to address a security issue: CVE-2020-11008 + +Fixes since v2.17.4 +------------------- + + * With a crafted URL that contains a newline or empty host, or lacks + a scheme, the credential helper machinery can be fooled into + providing credential information that is not appropriate for the + protocol in use and host being contacted. + + Unlike the vulnerability CVE-2020-5260 fixed in v2.17.4, the + credentials are not for a host of the attacker's choosing; instead, + they are for some unspecified host (based on how the configured + credential helper handles an absent "host" parameter). + + The attack has been made impossible by refusing to work with + under-specified credential patterns. + +Credit for finding the vulnerability goes to Carlo Arenas. diff --git a/Documentation/RelNotes/2.18.3.txt b/Documentation/RelNotes/2.18.3.txt new file mode 100644 index 0000000000..25143f0cec --- /dev/null +++ b/Documentation/RelNotes/2.18.3.txt @@ -0,0 +1,5 @@ +Git v2.18.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.18.4.txt b/Documentation/RelNotes/2.18.4.txt new file mode 100644 index 0000000000..e8ef858a00 --- /dev/null +++ b/Documentation/RelNotes/2.18.4.txt @@ -0,0 +1,5 @@ +Git v2.18.4 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.19.4.txt b/Documentation/RelNotes/2.19.4.txt new file mode 100644 index 0000000000..35d0ae561b --- /dev/null +++ b/Documentation/RelNotes/2.19.4.txt @@ -0,0 +1,5 @@ +Git v2.19.4 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.19.5.txt b/Documentation/RelNotes/2.19.5.txt new file mode 100644 index 0000000000..18a4dcbfd6 --- /dev/null +++ b/Documentation/RelNotes/2.19.5.txt @@ -0,0 +1,5 @@ +Git v2.19.5 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.20.3.txt b/Documentation/RelNotes/2.20.3.txt new file mode 100644 index 0000000000..f6eccd103b --- /dev/null +++ b/Documentation/RelNotes/2.20.3.txt @@ -0,0 +1,5 @@ +Git v2.20.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.20.4.txt b/Documentation/RelNotes/2.20.4.txt new file mode 100644 index 0000000000..5a9e24e470 --- /dev/null +++ b/Documentation/RelNotes/2.20.4.txt @@ -0,0 +1,5 @@ +Git v2.20.4 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.21.2.txt b/Documentation/RelNotes/2.21.2.txt new file mode 100644 index 0000000000..a0fb83bb53 --- /dev/null +++ b/Documentation/RelNotes/2.21.2.txt @@ -0,0 +1,5 @@ +Git v2.21.2 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.21.3.txt b/Documentation/RelNotes/2.21.3.txt new file mode 100644 index 0000000000..2ca0aa5c62 --- /dev/null +++ b/Documentation/RelNotes/2.21.3.txt @@ -0,0 +1,5 @@ +Git v2.21.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.22.3.txt b/Documentation/RelNotes/2.22.3.txt new file mode 100644 index 0000000000..57296f6d17 --- /dev/null +++ b/Documentation/RelNotes/2.22.3.txt @@ -0,0 +1,5 @@ +Git v2.22.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.22.4.txt b/Documentation/RelNotes/2.22.4.txt new file mode 100644 index 0000000000..8b5f3e3f37 --- /dev/null +++ b/Documentation/RelNotes/2.22.4.txt @@ -0,0 +1,5 @@ +Git v2.22.4 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.23.2.txt b/Documentation/RelNotes/2.23.2.txt new file mode 100644 index 0000000000..b697cbe0e3 --- /dev/null +++ b/Documentation/RelNotes/2.23.2.txt @@ -0,0 +1,5 @@ +Git v2.23.2 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.23.3.txt b/Documentation/RelNotes/2.23.3.txt new file mode 100644 index 0000000000..2e35490137 --- /dev/null +++ b/Documentation/RelNotes/2.23.3.txt @@ -0,0 +1,5 @@ +Git v2.23.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.24.2.txt b/Documentation/RelNotes/2.24.2.txt new file mode 100644 index 0000000000..0049f65503 --- /dev/null +++ b/Documentation/RelNotes/2.24.2.txt @@ -0,0 +1,5 @@ +Git v2.24.2 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.24.3.txt b/Documentation/RelNotes/2.24.3.txt new file mode 100644 index 0000000000..5302e0f73b --- /dev/null +++ b/Documentation/RelNotes/2.24.3.txt @@ -0,0 +1,5 @@ +Git v2.24.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.25.3.txt b/Documentation/RelNotes/2.25.3.txt new file mode 100644 index 0000000000..15f7f21f10 --- /dev/null +++ b/Documentation/RelNotes/2.25.3.txt @@ -0,0 +1,5 @@ +Git v2.25.3 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.25.4.txt b/Documentation/RelNotes/2.25.4.txt new file mode 100644 index 0000000000..0dbb5daeec --- /dev/null +++ b/Documentation/RelNotes/2.25.4.txt @@ -0,0 +1,5 @@ +Git v2.25.4 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.26.1.txt b/Documentation/RelNotes/2.26.1.txt new file mode 100644 index 0000000000..1b4ecb3fdc --- /dev/null +++ b/Documentation/RelNotes/2.26.1.txt @@ -0,0 +1,5 @@ +Git v2.26.1 Release Notes +========================= + +This release merges the security fix that appears in v2.17.4; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.26.2.txt b/Documentation/RelNotes/2.26.2.txt new file mode 100644 index 0000000000..d434d0c695 --- /dev/null +++ b/Documentation/RelNotes/2.26.2.txt @@ -0,0 +1,5 @@ +Git v2.26.2 Release Notes +========================= + +This release merges the security fix that appears in v2.17.5; see +the release notes for that version for details. diff --git a/Documentation/RelNotes/2.27.0.txt b/Documentation/RelNotes/2.27.0.txt index 7a5c7fff80..0bd2dc713d 100644 --- a/Documentation/RelNotes/2.27.0.txt +++ b/Documentation/RelNotes/2.27.0.txt @@ -47,6 +47,35 @@ UI, Workflows & Features exists, and neither --[no-]rebase nor --ff-only is given (which would result a merge). + * "git p4" learned four new hooks and also "--no-verify" option to + bypass them (and the existing "p4-pre-submit" hook). + + * "git pull" shares many options with underlying "git fetch", but + some of them were not documented and some of those that would make + sense to pass down were not passed down. + + * "git rebase" learned the "--no-gpg-sign" option to countermand + commit.gpgSign the user may have. + + * The output from "git format-patch" uses RFC 2047 encoding for + non-ASCII letters on From: and Subject: headers, so that it can + directly be fed to e-mail programs. A new option has been added + to produce these headers in raw. + + * "git log" learned "--show-pulls" that helps pathspec limited + history views; a merge commit that takes the whole change from a + side branch, which is normally omitted from the output, is shown + in addition to the commits that introduce real changes. + + * The interactive input from various codepaths are consolidated and + any prompt possibly issued earlier are fflush()ed before we read. + + * Allow "git rebase" to reapply all local commits, even if the may be + already in the upstream, without checking first. + + * The 'pack.useSparse' configuration variable now defaults to 'true', + enabling an optimization that has been experimental since Git 2.21. + Performance, Internal Implementation, Development Support etc. @@ -60,6 +89,19 @@ Performance, Internal Implementation, Development Support etc. * "git stash" has kept an escape hatch to use the scripted version for a few releases, which got stale. It has been removed. + * Enable tests that require GnuPG on Windows. + + * Minor test usability improvement. + + * Trace2 enhancement to allow logging of the environment variables. + + * Test clean-up continues. + + * Perf-test update. + + * A Windows-specific test element has been made more robust against + misuse from both user's environment and programmer's errors. + Fixes since v2.26 ----------------- @@ -85,6 +127,74 @@ Fixes since v2.26 was rewritten in C back in 2.20 era, which has been corrected. (merge f08132f889 at/rebase-fork-point-regression-fix later to maint). + * The import-tars importer (in contrib/fast-import/) used to create + phony files at the top-level of the repository when the archive + contains global PAX headers, which made its own logic to detect and + omit the common leading directory ineffective, which has been + corrected. + (merge c839fcff65 js/import-tars-do-not-make-phony-files-from-pax-headers later to maint). + + * Simplify the commit ancestry connectedness check in a partial clone + repository in which "promised" objects are assumed to be obtainable + lazily on-demand from promisor remote repositories. + (merge 2b98478c6f jt/connectivity-check-optim-in-partial-clone later to maint). + + * The server-end of the v2 protocol to serve "git clone" and "git + fetch" was not prepared to see a delim packets at unexpected + places, which led to a crash. + (merge cacae4329f jk/harden-protocol-v2-delim-handling later to maint). + + * When fed a midx that records no objects, some codepaths tried to + loop from 0 through (num_objects-1), which, due to integer + arithmetic wrapping around, made it nonsense operation with out of + bounds array accesses. The code has been corrected to reject such + an midx file. + (merge 796d61cdc0 dr/midx-avoid-int-underflow later to maint). + + * Utitiles run via the run_command() API were not spawned correctly + on Cygwin, when the paths to them are given as a full path with + backslashes. + (merge 05ac8582bc ak/run-command-on-cygwin-fix later to maint). + + * "git pull --rebase" tried to run a rebase even after noticing that + the pull results in a fast-forward and no rebase is needed nor + sensible, for the past few years due to a mistake nobody noticed. + (merge fbae70ddc6 en/pull-do-not-rebase-after-fast-forwarding later to maint). + + * "git rebase" with the merge backend did not work well when the + rebase.abbreviateCommands configuration was set. + (merge de9f1d3ef4 ag/rebase-merge-allow-ff-under-abbrev-command later to maint). + + * The logic to auto-follow tags by "git clone --single-branch" was + not careful to avoid lazy-fetching unnecessary tags, which has been + corrected. + (merge 167a575e2d jk/use-quick-lookup-in-clone-for-tag-following later to maint). + + * "git rebase -i" did not leave the reflog entries correctly. + (merge 1f6965f994 en/sequencer-reflog-action later to maint). + + * The more aggressive updates to remote-tracking branches we had for + the past 7 years or so were not reflected in the documentation, + which has been corrected. + (merge a44088435c pb/pull-fetch-doc later to maint). + + * We've left the command line parsing of "git log :/a/b/" broken for + about a full year without anybody noticing, which has been + corrected. + (merge 0220461071 jc/missing-ref-store-fix later to maint). + + * Misc fixes for Windows. + (merge 3efc128cd5 js/mingw-fixes later to maint). + + * "git rebase" (again) learns to honor "--no-keep-empty", which lets + the user to discard commits that are empty from the beginning (as + opposed to the ones that become empty because of rebasing). The + interactive rebase also marks commits that are empty in the todo. + (merge 50ed76148a en/rebase-no-keep-empty later to maint). + + * Parsing the host part out of URL for the credential helper has been corrected. + (merge 4c5971e18a jk/credential-parsing-end-of-host-in-URL later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 564956f358 jc/maintain-doc later to maint). (merge 7422b2a0a1 sg/commit-slab-clarify-peek later to maint). @@ -92,3 +202,16 @@ Fixes since v2.26 (merge 757c2ba3e2 en/oidset-uninclude-hashmap later to maint). (merge 8312aa7d74 jc/config-tar later to maint). (merge d00a5bdd50 ss/submodule-foreach-cb later to maint). + (merge 64d1022e14 ar/test-style-fixes later to maint). + (merge 4a465443a6 ds/doc-clone-filter later to maint). + (merge bb2dbe301b jk/t3419-drop-expensive-tests later to maint). + (merge d3507cc712 js/test-junit-finalization-fix later to maint). + (merge 2149b6748f bc/faq later to maint). + (merge 12dc0879f1 jk/test-cleanup later to maint). + (merge 344420bf0f pb/rebase-doc-typofix later to maint). + (merge 7cd54d37dc dl/wrapper-fix-indentation later to maint). + (merge 78725ebda9 jc/allow-strlen-substitution-in-shell-scripts later to maint). + (merge 2ecfcdecc6 jm/gitweb-fastcgi-utf8 later to maint). + (merge 0740d0a5d3 jk/oid-array-cleanups later to maint). + (merge a1aba0c95c js/t0007-typofix later to maint). + (merge 76ba7fa225 ma/config-doc-fix later to maint). diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 8fc4b67081..3e4c13971b 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -31,24 +31,6 @@ ifdef::backend-docbook[] endif::backend-docbook[] ifdef::backend-docbook[] -ifndef::git-asciidoc-no-roff[] -# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. -# v1.72 breaks with this because it replaces dots not in roff requests. -[listingblock] -<example><title>{title}</title> -<literallayout class="monospaced"> -ifdef::doctype-manpage[] - .ft C -endif::doctype-manpage[] -| -ifdef::doctype-manpage[] - .ft -endif::doctype-manpage[] -</literallayout> -{title#}</example> -endif::git-asciidoc-no-roff[] - -ifdef::git-asciidoc-no-roff[] ifdef::doctype-manpage[] # The following two small workarounds insert a simple paragraph after screen [listingblock] @@ -67,7 +49,6 @@ ifdef::doctype-manpage[] {title#}</para></formalpara> {title%}<simpara></simpara> endif::doctype-manpage[] -endif::git-asciidoc-no-roff[] endif::backend-docbook[] ifdef::doctype-manpage[] diff --git a/Documentation/config.txt b/Documentation/config.txt index 2450589a0e..74009d5402 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -220,12 +220,12 @@ Example ; affected by the condition [includeIf "gitdir:/path/to/group/"] path = foo.inc ----- - ; include only if we are in a worktree where foo-branch is - ; currently checked out - [includeIf "onbranch:foo-branch"] - path = foo.inc +; include only if we are in a worktree where foo-branch is +; currently checked out +[includeIf "onbranch:foo-branch"] + path = foo.inc +---- Values ~~~~~~ diff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt index 875f8c8a66..4e3a5c0ceb 100644 --- a/Documentation/config/feature.txt +++ b/Documentation/config/feature.txt @@ -12,9 +12,6 @@ feature.experimental:: setting if you are interested in providing feedback on experimental features. The new default values are: + -* `pack.useSparse=true` uses a new algorithm when constructing a pack-file -which can improve `git push` performance in repos with many files. -+ * `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by skipping more commits at a time, reducing the number of round trips. + diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt index 45c7bd5a8f..564e8091ba 100644 --- a/Documentation/config/format.txt +++ b/Documentation/config/format.txt @@ -57,6 +57,11 @@ format.suffix:: `.patch`. Use this variable to change that suffix (make sure to include the dot if you want it). +format.encodeEmailHeaders:: + Encode email headers that have non-ASCII characters with + "Q-encoding" (described in RFC 2047) for email transmission. + Defaults to true. + format.pretty:: The default pretty format for log/show/whatchanged command, See linkgit:git-log[1], linkgit:git-show[1], diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt index 0dac580581..837f1b1679 100644 --- a/Documentation/config/pack.txt +++ b/Documentation/config/pack.txt @@ -119,8 +119,8 @@ pack.useSparse:: objects. This can have significant performance benefits when computing a pack to send a small change. However, it is possible that extra objects are added to the pack-file if the included - commits contain certain types of direct renames. Default is `false` - unless `feature.experimental` is enabled. + commits contain certain types of direct renames. Default is + `true`. pack.writeBitmaps (deprecated):: This is a deprecated synonym for `repack.writeBitmaps`. diff --git a/Documentation/config/trace2.txt b/Documentation/config/trace2.txt index 4ce0b9a6d1..01d3afd8a8 100644 --- a/Documentation/config/trace2.txt +++ b/Documentation/config/trace2.txt @@ -48,6 +48,15 @@ trace2.configParams:: May be overridden by the `GIT_TRACE2_CONFIG_PARAMS` environment variable. Unset by default. +trace2.envVars:: + A comma-separated list of "important" environment variables that should + be recorded in the trace2 output. For example, + `GIT_HTTP_USER_AGENT,GIT_CONFIG` would cause the trace2 output to + contain events listing the overrides for HTTP user agent and the + location of the Git configuration file (assuming any are set). May be + overriden by the `GIT_TRACE2_ENV_VARS` environment variable. Unset by + default. + trace2.destinationDebug:: Boolean. When true Git will print error messages when a trace target destination cannot be opened for writing. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 833f935cd0..6e2a160a47 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -205,7 +205,6 @@ ifndef::git-pull[] recursion (such as settings in linkgit:gitmodules[5] and linkgit:git-config[1]) override this option, as does specifying --[no-]recurse-submodules directly. -endif::git-pull[] -u:: --update-head-ok:: @@ -215,6 +214,7 @@ endif::git-pull[] to communicate with 'git fetch', and unless you are implementing your own Porcelain you are not supposed to use it. +endif::git-pull[] --upload-pack <upload-pack>:: When given, and the repository to fetch from is handled diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index ab5754e05d..38c0852139 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -148,9 +148,12 @@ default. You can use `--no-utf8` to override this. -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign commits. The `keyid` argument is optional and defaults to the committer identity; if specified, it must be - stuck to the option without a space. + stuck to the option without a space. `--no-gpg-sign` is useful to + countermand both `commit.gpgSign` configuration variable, and + earlier `--gpg-sign`. --continue:: -r:: diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 83ce51aedf..75feeef08a 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -109,9 +109,12 @@ effect to your index in a row. -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign commits. The `keyid` argument is optional and defaults to the committer identity; if specified, it must be - stuck to the option without a space. + stuck to the option without a space. `--no-gpg-sign` is useful to + countermand both `commit.gpgSign` configuration variable, and + earlier `--gpg-sign`. --ff:: If the current HEAD is the same as the parent of the diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index bf24f1813a..08d6045c4a 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -15,7 +15,8 @@ SYNOPSIS [--dissociate] [--separate-git-dir <git dir>] [--depth <depth>] [--[no-]single-branch] [--no-tags] [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules] - [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--] <repository> + [--[no-]remote-submodules] [--jobs <n>] [--sparse] + [--filter=<filter>] [--] <repository> [<directory>] DESCRIPTION @@ -162,6 +163,16 @@ objects from the source repository into a pack in the cloned repository. of the repository. The sparse-checkout file can be modified to grow the working directory as needed. +--filter=<filter-spec>:: + Use the partial clone feature and request that the server sends + a subset of reachable objects according to a given object filter. + When using `--filter`, the supplied `<filter-spec>` is used for + the partial clone filter. For example, `--filter=blob:none` will + filter out all blobs (file contents) until needed by Git. Also, + `--filter=blob:limit=<size>` will filter out all blobs of size + at least `<size>`. For more details on filter specifications, see + the `--filter` option in linkgit:git-rev-list[1]. + --mirror:: Set up a mirror of the source repository. This implies `--bare`. Compared to `--bare`, `--mirror` not only maps local branches of the diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index ec15ee8d6f..2e2c581098 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -61,13 +61,11 @@ OPTIONS -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign commits. The `keyid` argument is optional and defaults to the committer identity; if specified, it must be - stuck to the option without a space. - ---no-gpg-sign:: - Do not GPG-sign commit, to countermand a `--gpg-sign` option - given earlier on the command line. + stuck to the option without a space. `--no-gpg-sign` is useful to + countermand a `--gpg-sign` option given earlier on the command line. Commit Information ------------------ diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 13f653989f..a3baea32ae 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -348,13 +348,12 @@ changes to tracked files. -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign commits. The `keyid` argument is optional and defaults to the committer identity; if specified, it must be - stuck to the option without a space. - ---no-gpg-sign:: - Countermand `commit.gpgSign` configuration variable that is - set to force each and every commit to be signed. + stuck to the option without a space. `--no-gpg-sign` is useful to + countermand both `commit.gpgSign` configuration variable, and + earlier `--gpg-sign`. \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 0d4f8951bb..0f81d0437b 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -24,6 +24,7 @@ SYNOPSIS [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] [--[no-]cover-letter] [--quiet] + [--[no-]encode-email-headers] [--no-notes | --notes[=<ref>]] [--interdiff=<previous>] [--range-diff=<previous> [--creation-factor=<percent>]] @@ -253,6 +254,13 @@ feeding the result to `git send-email`. containing the branch description, shortlog and the overall diffstat. You can fill in a description in the file before sending it out. +--encode-email-headers:: +--no-encode-email-headers:: + Encode email headers that have non-ASCII characters with + "Q-encoding" (described in RFC 2047), instead of outputting the + headers verbatim. Defaults to the value of the + `format.encodeEmailHeaders` configuration variable. + --interdiff=<previous>:: As a reviewer aid, insert an interdiff into the cover letter, or as commentary of the lone patch of a 1-patch series, showing diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index bed09bb09e..619577f23b 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -49,6 +49,7 @@ OPTIONS Print out the ref name given on the command line by which each commit was reached. +--[no-]mailmap:: --[no-]use-mailmap:: Use mailmap file to map author and committer names and email addresses to canonical real names and email addresses. See diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 3494a1db3e..dab9609013 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -374,14 +374,55 @@ These options can be used to modify 'git p4 submit' behavior. been submitted. Implies --disable-rebase. Can also be set with git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible. -Hook for submit -~~~~~~~~~~~~~~~ +Hooks for submit +---------------- + +p4-pre-submit +~~~~~~~~~~~~~ + The `p4-pre-submit` hook is executed if it exists and is executable. The hook takes no parameters and nothing from standard input. Exiting with non-zero status from this script prevents `git-p4 submit` from launching. +It can be bypassed with the `--no-verify` command line option. One usage scenario is to run unit tests in the hook. +p4-prepare-changelist +~~~~~~~~~~~~~~~~~~~~~ + +The `p4-prepare-changelist` hook is executed right after preparing +the default changelist message and before the editor is started. +It takes one parameter, the name of the file that contains the +changelist text. Exiting with a non-zero status from the script +will abort the process. + +The purpose of the hook is to edit the message file in place, +and it is not supressed by the `--no-verify` option. This hook +is called even if `--prepare-p4-only` is set. + +p4-changelist +~~~~~~~~~~~~~ + +The `p4-changelist` hook is executed after the changelist +message has been edited by the user. It can be bypassed with the +`--no-verify` option. It takes a single parameter, the name +of the file that holds the proposed changelist text. Exiting +with a non-zero status causes the command to abort. + +The hook is allowed to edit the changelist file and can be used +to normalize the text into some project standard format. It can +also be used to refuse the Submit after inspect the message file. + +p4-post-changelist +~~~~~~~~~~~~~~~~~~ + +The `p4-post-changelist` hook is invoked after the submit has +successfully occured in P4. It takes no parameters and is meant +primarily for notification and cannot affect the outcome of the +git p4 submit action. + + + Rebase options ~~~~~~~~~~~~~~ These options can be used to modify 'git p4 rebase' behavior. diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index fecdf2600c..eaa2f2a404 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -14,7 +14,7 @@ SYNOPSIS [--local] [--incremental] [--window=<n>] [--depth=<n>] [--revs [--unpacked | --all]] [--keep-pack=<pack-name>] [--stdout [--filter=<filter-spec>] | base-name] - [--shallow] [--keep-true-parents] [--sparse] < object-list + [--shallow] [--keep-true-parents] [--[no-]sparse] < object-list DESCRIPTION @@ -196,14 +196,16 @@ depth is 4095. Add --no-reuse-object if you want to force a uniform compression level on all data no matter the source. ---sparse:: - Use the "sparse" algorithm to determine which objects to include in +--[no-]sparse:: + Toggle the "sparse" algorithm to determine which objects to include in the pack, when combined with the "--revs" option. This algorithm only walks trees that appear in paths that introduce new objects. This can have significant performance benefits when computing a pack to send a small change. However, it is possible that extra objects are added to the pack-file if the included commits contain - certain types of direct renames. + certain types of direct renames. If this option is not included, + it defaults to the value of `pack.useSparse`, which is true unless + otherwise specified. --thin:: Create a "thin" pack by omitting the common objects between a diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index b336df00c7..9c7ab2d48f 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -230,9 +230,9 @@ branch.<name>.merge options; see linkgit:git-config[1] for details. $ git pull origin next ------------------------------------------------ + -This leaves a copy of `next` temporarily in FETCH_HEAD, but -does not update any remote-tracking branches. Using remote-tracking -branches, the same can be done by invoking fetch and merge: +This leaves a copy of `next` temporarily in FETCH_HEAD, and +updates the remote-tracking branch `origin/next`. +The same can be done by invoking fetch and merge: + ------------------------------------------------ $ git fetch origin diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index f7a6033607..2de8620683 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -277,20 +277,51 @@ See also INCOMPATIBLE OPTIONS below. Other options, like --exec, will use the default of drop unless -i/--interactive is explicitly specified. + -Note that commits which start empty are kept, and commits which are -clean cherry-picks (as determined by `git log --cherry-mark ...`) are -always dropped. +Note that commits which start empty are kept (unless --no-keep-empty +is specified), and commits which are clean cherry-picks (as determined +by `git log --cherry-mark ...`) are detected and dropped as a +preliminary step (unless --reapply-cherry-picks is passed). + See also INCOMPATIBLE OPTIONS below. +--no-keep-empty:: --keep-empty:: - No-op. Rebasing commits that started empty (had no change - relative to their parent) used to fail and this option would - override that behavior, allowing commits with empty changes to - be rebased. Now commits with no changes do not cause rebasing - to halt. + Do not keep commits that start empty before the rebase + (i.e. that do not change anything from its parent) in the + result. The default is to keep commits which start empty, + since creating such commits requires passing the --allow-empty + override flag to `git commit`, signifying that a user is very + intentionally creating such a commit and thus wants to keep + it. + -See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below. +Usage of this flag will probably be rare, since you can get rid of +commits that start empty by just firing up an interactive rebase and +removing the lines corresponding to the commits you don't want. This +flag exists as a convenient shortcut, such as for cases where external +tools generate many empty commits and you want them all removed. ++ +For commits which do not start empty but become empty after rebasing, +see the --empty flag. ++ +See also INCOMPATIBLE OPTIONS below. + +--reapply-cherry-picks:: +--no-reapply-cherry-picks:: + Reapply all clean cherry-picks of any upstream commit instead + of preemptively dropping them. (If these commits then become + empty after rebasing, because they contain a subset of already + upstream changes, the behavior towards them is controlled by + the `--empty` flag.) ++ +By default (or if `--no-reapply-cherry-picks` is given), these commits +will be automatically dropped. Because this necessitates reading all +upstream commits, this can be expensive in repos with a large number +of upstream commits that need to be read. ++ +`--reapply-cherry-picks` allows rebase to forgo reading all upstream +commits, potentially improving performance. ++ +See also INCOMPATIBLE OPTIONS below. --allow-empty-message:: No-op. Rebasing commits with an empty message used to fail @@ -354,9 +385,12 @@ See also INCOMPATIBLE OPTIONS below. -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign commits. The `keyid` argument is optional and defaults to the committer identity; if specified, it must be - stuck to the option without a space. + stuck to the option without a space. `--no-gpg-sign` is useful to + countermand both `commit.gpgSign` configuration variable, and + earlier `--gpg-sign`. -q:: --quiet:: @@ -587,8 +621,9 @@ are incompatible with the following options: * --preserve-merges * --interactive * --exec - * --keep-empty + * --no-keep-empty * --empty= + * --reapply-cherry-picks * --edit-todo * --root when used in combination with --onto @@ -605,7 +640,7 @@ BEHAVIORAL DIFFERENCES ----------------------- git rebase has two primary backends: apply and merge. (The apply -backend used to known as the 'am' backend, but the name led to +backend used to be known as the 'am' backend, but the name led to confusion as it looks like a verb instead of a noun. Also, the merge backend used to be known as the interactive backend, but it is now used for non-interactive cases as well. Both were renamed based on @@ -620,12 +655,15 @@ commits that started empty, though these are rare in practice. It also drops commits that become empty and has no option for controlling this behavior. -The merge backend keeps intentionally empty commits. Similar to the -apply backend, by default the merge backend drops commits that become -empty unless -i/--interactive is specified (in which case it stops and -asks the user what to do). The merge backend also has an ---empty={drop,keep,ask} option for changing the behavior of handling -commits that become empty. +The merge backend keeps intentionally empty commits by default (though +with -i they are marked as empty in the todo list editor, or they can +be dropped automatically with --no-keep-empty). + +Similar to the apply backend, by default the merge backend drops +commits that become empty unless -i/--interactive is specified (in +which case it stops and asks the user what to do). The merge backend +also has an --empty={drop,keep,ask} option for changing the behavior +of handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -684,9 +722,17 @@ Hooks ~~~~~ The apply backend has not traditionally called the post-commit hook, -while the merge backend has. However, this was by accident of -implementation rather than by design. Both backends should have the -same behavior, though it is not clear which one is correct. +while the merge backend has. Both have called the post-checkout hook, +though the merge backend has squelched its output. Further, both +backends only call the post-checkout hook with the starting point +commit of the rebase, not the intermediate commits nor the final +commit. In each case, the calling of these hooks was by accident of +implementation rather than by design (both backends were originally +implemented as shell scripts and happened to invoke other commands +like 'git checkout' or 'git commit' that would call the hooks). Both +backends should have the same behavior, though it is not entirely +clear which, if any, is correct. We will likely make rebase stop +calling either of these hooks in the future. Interruptability ~~~~~~~~~~~~~~~~ @@ -1002,7 +1048,8 @@ Only works if the changes (patch IDs based on the diff contents) on 'subsystem' did. In that case, the fix is easy because 'git rebase' knows to skip -changes that are already present in the new upstream. So if you say +changes that are already present in the new upstream (unless +`--reapply-cherry-picks` is given). So if you say (assuming you're on 'topic') ------------ $ git rebase subsystem diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 9d22270757..044276e9da 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -90,9 +90,12 @@ effect to your index in a row. -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign commits. The `keyid` argument is optional and defaults to the committer identity; if specified, it must be - stuck to the option without a space. + stuck to the option without a space. `--no-gpg-sign` is useful to + countermand both `commit.gpgSign` configuration variable, and + earlier `--gpg-sign`. -s:: --signoff:: diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt new file mode 100644 index 0000000000..1cf83df118 --- /dev/null +++ b/Documentation/gitfaq.txt @@ -0,0 +1,337 @@ +gitfaq(7) +========= + +NAME +---- +gitfaq - Frequently asked questions about using Git + +SYNOPSIS +-------- +gitfaq + +DESCRIPTION +----------- + +The examples in this FAQ assume a standard POSIX shell, like `bash` or `dash`, +and a user, A U Thor, who has the account `author` on the hosting provider +`git.example.org`. + +Configuration +------------- + +[[user-name]] +What should I put in `user.name`?:: + You should put your personal name, generally a form using a given name + and family name. For example, the current maintainer of Git uses "Junio + C Hamano". This will be the name portion that is stored in every commit + you make. ++ +This configuration doesn't have any effect on authenticating to remote services; +for that, see `credential.username` in linkgit:git-config[1]. + +[[http-postbuffer]] +What does `http.postBuffer` really do?:: + This option changes the size of the buffer that Git uses when pushing + data to a remote over HTTP or HTTPS. If the data is larger than this + size, libcurl, which handles the HTTP support for Git, will use chunked + transfer encoding since it isn't known ahead of time what the size of + the pushed data will be. ++ +Leaving this value at the default size is fine unless you know that either the +remote server or a proxy in the middle doesn't support HTTP/1.1 (which +introduced the chunked transfer encoding) or is known to be broken with chunked +data. This is often (erroneously) suggested as a solution for generic push +problems, but since almost every server and proxy supports at least HTTP/1.1, +raising this value usually doesn't solve most push problems. A server or proxy +that didn't correctly support HTTP/1.1 and chunked transfer encoding wouldn't be +that useful on the Internet today, since it would break lots of traffic. ++ +Note that increasing this value will increase the memory used on every relevant +push that Git does over HTTP or HTTPS, since the entire buffer is allocated +regardless of whether or not it is all used. Thus, it's best to leave it at the +default unless you are sure you need a different value. + +[[configure-editor]] +How do I configure a different editor?:: + If you haven't specified an editor specifically for Git, it will by default + use the editor you've configured using the `VISUAL` or `EDITOR` environment + variables, or if neither is specified, the system default (which is usually + `vi`). Since some people find `vi` difficult to use or prefer a different + editor, it may be desirable to change the editor used. ++ +If you want to configure a general editor for most programs which need one, you +can edit your shell configuration (e.g., `~/.bashrc` or `~/.zshenv`) to contain +a line setting the `EDITOR` or `VISUAL` environment variable to an appropriate +value. For example, if you prefer the editor `nano`, then you could write the +following: ++ +---- +export VISUAL=nano +---- ++ +If you want to configure an editor specifically for Git, you can either set the +`core.editor` configuration value or the `GIT_EDITOR` environment variable. You +can see linkgit:git-var[1] for details on the order in which these options are +consulted. ++ +Note that in all cases, the editor value will be passed to the shell, so any +arguments containing spaces should be appropriately quoted. Additionally, if +your editor normally detaches from the terminal when invoked, you should specify +it with an argument that makes it not do that, or else Git will not see any +changes. An example of a configuration addressing both of these issues on +Windows would be the configuration `"C:\Program Files\Vim\gvim.exe" --nofork`, +which quotes the filename with spaces and specifies the `--nofork` option to +avoid backgrounding the process. + +Credentials +----------- + +[[http-credentials]] +How do I specify my credentials when pushing over HTTP?:: + The easiest way to do this is to use a credential helper via the + `credential.helper` configuration. Most systems provide a standard + choice to integrate with the system credential manager. For example, + Git for Windows provides the `wincred` credential manager, macOS has the + `osxkeychain` credential manager, and Unix systems with a standard + desktop environment can use the `libsecret` credential manager. All of + these store credentials in an encrypted store to keep your passwords or + tokens secure. ++ +In addition, you can use the `store` credential manager which stores in a file +in your home directory, or the `cache` credential manager, which does not +permanently store your credentials, but does prevent you from being prompted for +them for a certain period of time. ++ +You can also just enter your password when prompted. While it is possible to +place the password (which must be percent-encoded) in the URL, this is not +particularly secure and can lead to accidental exposure of credentials, so it is +not recommended. + +[[http-credentials-environment]] +How do I read a password or token from an environment variable?:: + The `credential.helper` configuration option can also take an arbitrary + shell command that produces the credential protocol on standard output. + This is useful when passing credentials into a container, for example. ++ +Such a shell command can be specified by starting the option value with an +exclamation point. If your password or token were stored in the `GIT_TOKEN`, +you could run the following command to set your credential helper: ++ +---- +$ git config credential.helper \ + '!f() { echo username=author; echo "password=$GIT_TOKEN"; };f' +---- + +[[http-reset-credentials]] +How do I change the password or token I've saved in my credential manager?:: + Usually, if the password or token is invalid, Git will erase it and + prompt for a new one. However, there are times when this doesn't always + happen. To change the password or token, you can erase the existing + credentials and then Git will prompt for new ones. To erase + credentials, use a syntax like the following (substituting your username + and the hostname): ++ +---- +$ echo url=https://author@git.example.org | git credential reject +---- + +[[multiple-accounts-http]] +How do I use multiple accounts with the same hosting provider using HTTP?:: + Usually the easiest way to distinguish between these accounts is to use + the username in the URL. For example, if you have the accounts `author` + and `committer` on `git.example.org`, you can use the URLs + https://author@git.example.org/org1/project1.git and + https://committer@git.example.org/org2/project2.git. This way, when you + use a credential helper, it will automatically try to look up the + correct credentials for your account. If you already have a remote set + up, you can change the URL with something like `git remote set-url + origin https://author@git.example.org/org1/project1.git` (see + linkgit:git-remote[1] for details). + +[[multiple-accounts-ssh]] +How do I use multiple accounts with the same hosting provider using SSH?:: + With most hosting providers that support SSH, a single key pair uniquely + identifies a user. Therefore, to use multiple accounts, it's necessary + to create a key pair for each account. If you're using a reasonably + modern OpenSSH version, you can create a new key pair with something + like `ssh-keygen -t ed25519 -f ~/.ssh/id_committer`. You can then + register the public key (in this case, `~/.ssh/id_committer.pub`; note + the `.pub`) with the hosting provider. ++ +Most hosting providers use a single SSH account for pushing; that is, all users +push to the `git` account (e.g., `git@git.example.org`). If that's the case for +your provider, you can set up multiple aliases in SSH to make it clear which key +pair to use. For example, you could write something like the following in +`~/.ssh/config`, substituting the proper private key file: ++ +---- +# This is the account for author on git.example.org. +Host example_author + HostName git.example.org + User git + # This is the key pair registered for author with git.example.org. + IdentityFile ~/.ssh/id_author + IdentitiesOnly yes +# This is the account for committer on git.example.org. +Host example_committer + HostName git.example.org + User git + # This is the key pair registered for committer with git.example.org. + IdentityFile ~/.ssh/id_committer + IdentitiesOnly yes +---- ++ +Then, you can adjust your push URL to use `git@example_author` or +`git@example_committer` instead of `git@example.org` (e.g., `git remote set-url +git@example_author:org1/project1.git`). + +Common Issues +------------- + +[[last-commit-amend]] +I've made a mistake in the last commit. How do I change it?:: + You can make the appropriate change to your working tree, run `git add + <file>` or `git rm <file>`, as appropriate, to stage it, and then `git + commit --amend`. Your change will be included in the commit, and you'll + be prompted to edit the commit message again; if you wish to use the + original message verbatim, you can use the `--no-edit` option to `git + commit` in addition, or just save and quit when your editor opens. + +[[undo-previous-change]] +I've made a change with a bug and it's been included in the main branch. How should I undo it?:: + The usual way to deal with this is to use `git revert`. This preserves + the history that the original change was made and was a valuable + contribution, but also introduces a new commit that undoes those changes + because the original had a problem. The commit message of the revert + indicates the commit which was reverted and is usually edited to include + an explanation as to why the revert was made. + +[[ignore-tracked-files]] +How do I ignore changes to a tracked file?:: + Git doesn't provide a way to do this. The reason is that if Git needs + to overwrite this file, such as during a checkout, it doesn't know + whether the changes to the file are precious and should be kept, or + whether they are irrelevant and can safely be destroyed. Therefore, it + has to take the safe route and always preserve them. ++ +It's tempting to try to use certain features of `git update-index`, namely the +assume-unchanged and skip-worktree bits, but these don't work properly for this +purpose and shouldn't be used this way. ++ +If your goal is to modify a configuration file, it can often be helpful to have +a file checked into the repository which is a template or set of defaults which +can then be copied alongside and modified as appropriate. This second, modified +file is usually ignored to prevent accidentally committing it. + +Hooks +----- + +[[restrict-with-hooks]] +How do I use hooks to prevent users from making certain changes?:: + The only safe place to make these changes is on the remote repository + (i.e., the Git server), usually in the `pre-receive` hook or in a + continuous integration (CI) system. These are the locations in which + policy can be enforced effectively. ++ +It's common to try to use `pre-commit` hooks (or, for commit messages, +`commit-msg` hooks) to check these things, which is great if you're working as a +solo developer and want the tooling to help you. However, using hooks on a +developer machine is not effective as a policy control because a user can bypass +these hooks with `--no-verify` without being noticed (among various other ways). +Git assumes that the user is in control of their local repositories and doesn't +try to prevent this or tattle on the user. ++ +In addition, some advanced users find `pre-commit` hooks to be an impediment to +workflows that use temporary commits to stage work in progress or that create +fixup commits, so it's better to push these kinds of checks to the server +anyway. + +Cross-Platform Issues +--------------------- + +[[windows-text-binary]] +I'm on Windows and my text files are detected as binary.:: + Git works best when you store text files as UTF-8. Many programs on + Windows support UTF-8, but some do not and only use the little-endian + UTF-16 format, which Git detects as binary. If you can't use UTF-8 with + your programs, you can specify a working tree encoding that indicates + which encoding your files should be checked out with, while still + storing these files as UTF-8 in the repository. This allows tools like + linkgit:git-diff[1] to work as expected, while still allowing your tools + to work. ++ +To do so, you can specify a linkgit:gitattributes[5] pattern with the +`working-tree-encoding` attribute. For example, the following pattern sets all +C files to use UTF-16LE-BOM, which is a common encoding on Windows: ++ +---- +*.c working-tree-encoding=UTF-16LE-BOM +---- ++ +You will need to run `git add --renormalize` to have this take effect. Note +that if you are making these changes on a project that is used across platforms, +you'll probably want to make it in a per-user configuration file or in the one +in `$GIT_DIR/info/attributes`, since making it in a `.gitattributes` file in the +repository will apply to all users of the repository. ++ +See the following entry for information about normalizing line endings as well, +and see linkgit:gitattributes[5] for more information about attribute files. + +[[windows-diff-control-m]] +I'm on Windows and git diff shows my files as having a `^M` at the end.:: + By default, Git expects files to be stored with Unix line endings. As such, + the carriage return (`^M`) that is part of a Windows line ending is shown + because it is considered to be trailing whitespace. Git defaults to showing + trailing whitespace only on new lines, not existing ones. ++ +You can store the files in the repository with Unix line endings and convert +them automatically to your platform's line endings. To do that, set the +configuration option `core.eol` to `native` and see the following entry for +information about how to configure files as text or binary. ++ +You can also control this behavior with the `core.whitespace` setting if you +don't wish to remove the carriage returns from your line endings. + +[[recommended-storage-settings]] +What's the recommended way to store files in Git?:: + While Git can store and handle any file of any type, there are some + settings that work better than others. In general, we recommend that + text files be stored in UTF-8 without a byte-order mark (BOM) with LF + (Unix-style) endings. We also recommend the use of UTF-8 (again, + without BOM) in commit messages. These are the settings that work best + across platforms and with tools such as `git diff` and `git merge`. ++ +Additionally, if you have a choice between storage formats that are text based +or non-text based, we recommend storing files in the text format and, if +necessary, transforming them into the other format. For example, a text-based +SQL dump with one record per line will work much better for diffing and merging +than an actual database file. Similarly, text-based formats such as Markdown +and AsciiDoc will work better than binary formats such as Microsoft Word and +PDF. ++ +Similarly, storing binary dependencies (e.g., shared libraries or JAR files) or +build products in the repository is generally not recommended. Dependencies and +build products are best stored on an artifact or package server with only +references, URLs, and hashes stored in the repository. ++ +We also recommend setting a linkgit:gitattributes[5] file to explicitly mark +which files are text and which are binary. If you want Git to guess, you can +set the attribute `text=auto`. For example, the following might be appropriate +in some projects: ++ +---- +# By default, guess. +* text=auto +# Mark all C files as text. +*.c text +# Mark all JPEG files as binary. +*.jpg binary +---- ++ +These settings help tools pick the right format for output such as patches and +result in files being checked out in the appropriate line ending for the +platform. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 3dccab5375..81f2a87e88 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -522,12 +522,61 @@ The exit status determines whether git will use the data from the hook to limit its search. On error, it will fall back to verifying all files and folders. +p4-changelist +~~~~~~~~~~~~~ + +This hook is invoked by `git-p4 submit`. + +The `p4-changelist` hook is executed after the changelist +message has been edited by the user. It can be bypassed with the +`--no-verify` option. It takes a single parameter, the name +of the file that holds the proposed changelist text. Exiting +with a non-zero status causes the command to abort. + +The hook is allowed to edit the changelist file and can be used +to normalize the text into some project standard format. It can +also be used to refuse the Submit after inspect the message file. + +Run `git-p4 submit --help` for details. + +p4-prepare-changelist +~~~~~~~~~~~~~~~~~~~~~ + +This hook is invoked by `git-p4 submit`. + +The `p4-prepare-changelist` hook is executed right after preparing +the default changelist message and before the editor is started. +It takes one parameter, the name of the file that contains the +changelist text. Exiting with a non-zero status from the script +will abort the process. + +The purpose of the hook is to edit the message file in place, +and it is not supressed by the `--no-verify` option. This hook +is called even if `--prepare-p4-only` is set. + +Run `git-p4 submit --help` for details. + +p4-post-changelist +~~~~~~~~~~~~~~~~~~ + +This hook is invoked by `git-p4 submit`. + +The `p4-post-changelist` hook is invoked after the submit has +successfully occured in P4. It takes no parameters and is meant +primarily for notification and cannot affect the outcome of the +git p4 submit action. + +Run `git-p4 submit --help` for details. + p4-pre-submit ~~~~~~~~~~~~~ This hook is invoked by `git-p4 submit`. It takes no parameters and nothing from standard input. Exiting with non-zero status from this script prevent -`git-p4 submit` from launching. Run `git-p4 submit --help` for details. +`git-p4 submit` from launching. It can be bypassed with the `--no-verify` +command line option. Run `git-p4 submit --help` for details. + + post-index-change ~~~~~~~~~~~~~~~~~ diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl deleted file mode 100644 index b4d315cb8c..0000000000 --- a/Documentation/manpage-1.72.xsl +++ /dev/null @@ -1,14 +0,0 @@ -<!-- manpage-1.72.xsl: - special settings for manpages rendered from asciidoc+docbook - handles peculiarities in docbook-xsl 1.72.0 --> -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" - version="1.0"> - -<xsl:import href="manpage-base.xsl"/> - -<!-- these are the special values for the roff control characters - needed for docbook-xsl 1.72.0 --> -<xsl:param name="git.docbook.backslash">▓</xsl:param> -<xsl:param name="git.docbook.dot" >⌂</xsl:param> - -</xsl:stylesheet> diff --git a/Documentation/manpage-base.xsl b/Documentation/manpage-base.xsl deleted file mode 100644 index a264fa6160..0000000000 --- a/Documentation/manpage-base.xsl +++ /dev/null @@ -1,35 +0,0 @@ -<!-- manpage-base.xsl: - special formatting for manpages rendered from asciidoc+docbook --> -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" - version="1.0"> - -<!-- these params silence some output from xmlto --> -<xsl:param name="man.output.quietly" select="1"/> -<xsl:param name="refentry.meta.get.quietly" select="1"/> - -<!-- convert asciidoc callouts to man page format; - git.docbook.backslash and git.docbook.dot params - must be supplied by another XSL file or other means --> -<xsl:template match="co"> - <xsl:value-of select="concat( - $git.docbook.backslash,'fB(', - substring-after(@id,'-'),')', - $git.docbook.backslash,'fR')"/> -</xsl:template> -<xsl:template match="calloutlist"> - <xsl:value-of select="$git.docbook.dot"/> - <xsl:text>sp </xsl:text> - <xsl:apply-templates/> - <xsl:text> </xsl:text> -</xsl:template> -<xsl:template match="callout"> - <xsl:value-of select="concat( - $git.docbook.backslash,'fB', - substring-after(@arearefs,'-'), - '. ',$git.docbook.backslash,'fR')"/> - <xsl:apply-templates/> - <xsl:value-of select="$git.docbook.dot"/> - <xsl:text>br </xsl:text> -</xsl:template> - -</xsl:stylesheet> diff --git a/Documentation/manpage-bold-literal.xsl b/Documentation/manpage-bold-literal.xsl index 94d6c1b545..e13db85693 100644 --- a/Documentation/manpage-bold-literal.xsl +++ b/Documentation/manpage-bold-literal.xsl @@ -8,11 +8,9 @@ this makes literal text easier to distinguish in manpages viewed on a tty --> <xsl:template match="literal|d:literal"> - <xsl:value-of select="$git.docbook.backslash"/> - <xsl:text>fB</xsl:text> + <xsl:text>\fB</xsl:text> <xsl:apply-templates/> - <xsl:value-of select="$git.docbook.backslash"/> - <xsl:text>fR</xsl:text> + <xsl:text>\fR</xsl:text> </xsl:template> </xsl:stylesheet> diff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl index a48f5b11f3..a9c7ec69f4 100644 --- a/Documentation/manpage-normal.xsl +++ b/Documentation/manpage-normal.xsl @@ -1,13 +1,26 @@ <!-- manpage-normal.xsl: - special settings for manpages rendered from asciidoc+docbook - handles anything we want to keep away from docbook-xsl 1.72.0 --> + special settings for manpages rendered from asciidoc+docbook --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> -<xsl:import href="manpage-base.xsl"/> -<!-- these are the normal values for the roff control characters --> -<xsl:param name="git.docbook.backslash">\</xsl:param> -<xsl:param name="git.docbook.dot" >.</xsl:param> +<!-- these params silence some output from xmlto --> +<xsl:param name="man.output.quietly" select="1"/> +<xsl:param name="refentry.meta.get.quietly" select="1"/> + +<!-- convert asciidoc callouts to man page format --> +<xsl:template match="co"> + <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/> +</xsl:template> +<xsl:template match="calloutlist"> + <xsl:text>.sp </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> +<xsl:template match="callout"> + <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/> + <xsl:apply-templates/> + <xsl:text>.br </xsl:text> +</xsl:template> </xsl:stylesheet> diff --git a/Documentation/manpage-suppress-sp.xsl b/Documentation/manpage-suppress-sp.xsl deleted file mode 100644 index a63c7632a8..0000000000 --- a/Documentation/manpage-suppress-sp.xsl +++ /dev/null @@ -1,21 +0,0 @@ -<!-- manpage-suppress-sp.xsl: - special settings for manpages rendered from asciidoc+docbook - handles erroneous, inline .sp in manpage output of some - versions of docbook-xsl --> -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" - version="1.0"> - -<!-- attempt to work around spurious .sp at the tail of the line - that some versions of docbook stylesheets seem to add --> -<xsl:template match="simpara"> - <xsl:variable name="content"> - <xsl:apply-templates/> - </xsl:variable> - <xsl:value-of select="normalize-space($content)"/> - <xsl:if test="not(ancestor::authorblurb) and - not(ancestor::personblurb)"> - <xsl:text> </xsl:text> - </xsl:if> -</xsl:template> - -</xsl:stylesheet> diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 40dc4f5e8c..fb3a6e8d42 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -61,9 +61,12 @@ When not possible, refuse to merge and exit with a non-zero status. -S[<keyid>]:: --gpg-sign[=<keyid>]:: +--no-gpg-sign:: GPG-sign the resulting merge commit. The `keyid` argument is optional and defaults to the committer identity; if specified, - it must be stuck to the option without a space. + it must be stuck to the option without a space. `--no-gpg-sign` + is useful to countermand both `commit.gpgSign` configuration variable, + and earlier `--gpg-sign`. --log[=<n>]:: --no-log:: diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index 7d3a60f5b9..95ea849902 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -19,7 +19,8 @@ ifndef::git-pull[] (see <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> below). endif::git-pull[] ifdef::git-pull[] - (see linkgit:git-fetch[1]). + (see the section "CONFIGURED REMOTE-TRACKING BRANCHES" + in linkgit:git-fetch[1]). endif::git-pull[] + The format of a <refspec> parameter is an optional plus diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index bfd02ade99..04ad7dd36e 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -342,6 +342,12 @@ Default mode:: branches if the end result is the same (i.e. merging branches with the same content) +--show-pulls:: + Include all commits from the default mode, but also any merge + commits that are not TREESAME to the first parent but are + TREESAME to a later parent. This mode is helpful for showing + the merge commits that "first introduced" a change to a branch. + --full-history:: Same as the default mode, but does not prune some history. @@ -534,7 +540,7 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`: parent and is TREESAME. -- -Finally, there is a fifth simplification mode available: +There is another simplification mode available: --ancestry-path:: Limit the displayed commits to those directly on the ancestry @@ -573,6 +579,132 @@ option does. Applied to the 'D..M' range, it results in: L--M ----------------------------------------------------------------------- +Before discussing another option, `--show-pulls`, we need to +create a new example history. ++ +A common problem users face when looking at simplified history is that a +commit they know changed a file somehow does not appear in the file's +simplified history. Let's demonstrate a new example and show how options +such as `--full-history` and `--simplify-merges` works in that case: ++ +----------------------------------------------------------------------- + .-A---M-----C--N---O---P + / / \ \ \/ / / + I B \ R-'`-Z' / + \ / \/ / + \ / /\ / + `---X--' `---Y--' +----------------------------------------------------------------------- ++ +For this example, suppose `I` created `file.txt` which was modified by +`A`, `B`, and `X` in different ways. The single-parent commits `C`, `Z`, +and `Y` do not change `file.txt`. The merge commit `M` was created by +resolving the merge conflict to include both changes from `A` and `B` +and hence is not TREESAME to either. The merge commit `R`, however, was +created by ignoring the contents of `file.txt` at `M` and taking only +the contents of `file.txt` at `X`. Hence, `R` is TREESAME to `X` but not +`M`. Finally, the natural merge resolution to create `N` is to take the +contents of `file.txt` at `R`, so `N` is TREESAME to `R` but not `C`. +The merge commits `O` and `P` are TREESAME to their first parents, but +not to their second parents, `Z` and `Y` respectively. ++ +When using the default mode, `N` and `R` both have a TREESAME parent, so +those edges are walked and the others are ignored. The resulting history +graph is: ++ +----------------------------------------------------------------------- + I---X +----------------------------------------------------------------------- ++ +When using `--full-history`, Git walks every edge. This will discover +the commits `A` and `B` and the merge `M`, but also will reveal the +merge commits `O` and `P`. With parent rewriting, the resulting graph is: ++ +----------------------------------------------------------------------- + .-A---M--------N---O---P + / / \ \ \/ / / + I B \ R-'`--' / + \ / \/ / + \ / /\ / + `---X--' `------' +----------------------------------------------------------------------- ++ +Here, the merge commits `O` and `P` contribute extra noise, as they did +not actually contribute a change to `file.txt`. They only merged a topic +that was based on an older version of `file.txt`. This is a common +issue in repositories using a workflow where many contributors work in +parallel and merge their topic branches along a single trunk: manu +unrelated merges appear in the `--full-history` results. ++ +When using the `--simplify-merges` option, the commits `O` and `P` +disappear from the results. This is because the rewritten second parents +of `O` and `P` are reachable from their first parents. Those edges are +removed and then the commits look like single-parent commits that are +TREESAME to their parent. This also happens to the commit `N`, resulting +in a history view as follows: ++ +----------------------------------------------------------------------- + .-A---M--. + / / \ + I B R + \ / / + \ / / + `---X--' +----------------------------------------------------------------------- ++ +In this view, we see all of the important single-parent changes from +`A`, `B`, and `X`. We also see the carefully-resolved merge `M` and the +not-so-carefully-resolved merge `R`. This is usually enough information +to determine why the commits `A` and `B` "disappeared" from history in +the default view. However, there are a few issues with this approach. ++ +The first issue is performance. Unlike any previous option, the +`--simplify-merges` option requires walking the entire commit history +before returning a single result. This can make the option difficult to +use for very large repositories. ++ +The second issue is one of auditing. When many contributors are working +on the same repository, it is important which merge commits introduced +a change into an important branch. The problematic merge `R` above is +not likely to be the merge commit that was used to merge into an +important branch. Instead, the merge `N` was used to merge `R` and `X` +into the important branch. This commit may have information about why +the change `X` came to override the changes from `A` and `B` in its +commit message. ++ +The `--show-pulls` option helps with both of these issues by adding more +merge commits to the history results. If a merge is not TREESAME to its +first parent but is TREESAME to a later parent, then that merge is +treated as if it "pulled" the change from another branch. When using +`--show-pulls` on this example (and no other options) the resulting +graph is: ++ +----------------------------------------------------------------------- + I---X---R---N +----------------------------------------------------------------------- ++ +Here, the merge commits `R` and `N` are included because they pulled +the commits `X` and `R` into the base branch, respectively. These +merges are the reason the commits `A` and `B` do not appear in the +default history. ++ +When `--show-pulls` is paired with `--simplify-merges`, the +graph includes all of the necessary information: ++ +----------------------------------------------------------------------- + .-A---M--. N + / / \ / + I B R + \ / / + \ / / + `---X--' +----------------------------------------------------------------------- ++ +Notice that since `M` is reachable from `R`, the edge from `N` to `M` +was simplified away. However, `N` still appears in the history as an +important commit because it "pulled" the change `R` into the main +branch. + The `--simplify-by-decoration` option allows you to view only the big picture of the topology of the history, by omitting commits that are not referenced by tags. Commits are marked as !TREESAME diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt index 4f07ceadcb..6b6085585d 100644 --- a/Documentation/technical/api-trace2.txt +++ b/Documentation/technical/api-trace2.txt @@ -656,7 +656,8 @@ The "exec_id" field is a command-unique id and is only useful if the ------------ `"def_param"`:: - This event is generated to log a global parameter. + This event is generated to log a global parameter, such as a config + setting, command-line flag, or environment variable. + ------------ { diff --git a/Documentation/user-manual.conf b/Documentation/user-manual.conf index d87294de2f..0148f126dc 100644 --- a/Documentation/user-manual.conf +++ b/Documentation/user-manual.conf @@ -9,13 +9,3 @@ tilde=~ [linkgit-inlinemacro] <ulink url="{target}.html">{target}{0?({0})}</ulink> - -ifdef::backend-docbook[] -# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. -[listingblock] -<example><title>{title}</title> -<literallayout class="monospaced"> -| -</literallayout> -{title#}</example> -endif::backend-docbook[] @@ -206,9 +206,7 @@ Issues of note: clone two separate git-htmldocs and git-manpages repositories next to the clone of git itself. - It has been reported that docbook-xsl version 1.72 and 1.73 are - buggy; 1.72 misformats manual pages for callouts, and 1.73 needs - the patch in contrib/patches/docbook-xsl-manpages-charmap.patch + The minimum supported version of docbook-xsl is 1.74. Users attempting to build the documentation on Cygwin may need to ensure that the /etc/xml/catalog file looks something like this: @@ -282,12 +282,6 @@ all:: # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks # field that counts the on-disk footprint in 512-byte blocks. # -# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72 -# (not v1.73 or v1.71). -# -# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives -# (versions 1.68.1 through v1.72). -# # Define GNU_ROFF if your target system uses GNU groff. This forces # apostrophes to be ASCII so that cut&pasting examples to the shell # will work. @@ -738,7 +732,7 @@ TEST_BUILTINS_OBJS += test-run-command.o TEST_BUILTINS_OBJS += test-scrap-cache-tree.o TEST_BUILTINS_OBJS += test-serve-v2.o TEST_BUILTINS_OBJS += test-sha1.o -TEST_BUILTINS_OBJS += test-sha1-array.o +TEST_BUILTINS_OBJS += test-oid-array.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o TEST_BUILTINS_OBJS += test-strcmp-offset.o @@ -929,6 +923,7 @@ LIB_OBJS += notes-utils.o LIB_OBJS += object.o LIB_OBJS += oidmap.o LIB_OBJS += oidset.o +LIB_OBJS += oid-array.o LIB_OBJS += packfile.o LIB_OBJS += pack-bitmap.o LIB_OBJS += pack-bitmap-write.o @@ -978,7 +973,6 @@ LIB_OBJS += sequencer.o LIB_OBJS += serve.o LIB_OBJS += server-info.o LIB_OBJS += setup.o -LIB_OBJS += sha1-array.o LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1-file.o LIB_OBJS += sha1-name.o diff --git a/add-interactive.c b/add-interactive.c index 4a9bf85cac..29cd2fe020 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -9,6 +9,7 @@ #include "lockfile.h" #include "dir.h" #include "run-command.h" +#include "prompt.h" static void init_color(struct repository *r, struct add_i_state *s, const char *slot_name, char *dst, @@ -289,13 +290,12 @@ static ssize_t list_and_choose(struct add_i_state *s, fputs(singleton ? "> " : ">> ", stdout); fflush(stdout); - if (strbuf_getline(&input, stdin) == EOF) { + if (git_read_line_interactively(&input) == EOF) { putchar('\n'); if (immediate) res = LIST_AND_CHOOSE_QUIT; break; } - strbuf_trim(&input); if (!input.len) break; diff --git a/add-patch.c b/add-patch.c index d8dafa8168..d8bfe379be 100644 --- a/add-patch.c +++ b/add-patch.c @@ -7,6 +7,7 @@ #include "color.h" #include "diff.h" #include "compat/terminal.h" +#include "prompt.h" enum prompt_mode_type { PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK, @@ -1158,9 +1159,8 @@ static int read_single_character(struct add_p_state *s) return res; } - if (strbuf_getline(&s->answer, stdin) == EOF) + if (git_read_line_interactively(&s->answer) == EOF) return EOF; - strbuf_trim_trailing_newline(&s->answer); return 0; } diff --git a/archive-tar.c b/archive-tar.c index 5a77701a15..5ceec3684b 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -364,7 +364,7 @@ static struct archiver **tar_filters; static int nr_tar_filters; static int alloc_tar_filters; -static struct archiver *find_tar_filter(const char *name, int len) +static struct archiver *find_tar_filter(const char *name, size_t len) { int i; for (i = 0; i < nr_tar_filters; i++) { @@ -380,7 +380,7 @@ static int tar_filter_config(const char *var, const char *value, void *data) struct archiver *ar; const char *name; const char *type; - int namelen; + size_t namelen; if (parse_config_key(var, "tar", &name, &namelen, &type) < 0 || !name) return 0; @@ -10,7 +10,7 @@ #include "run-command.h" #include "log-tree.h" #include "bisect.h" -#include "sha1-array.h" +#include "oid-array.h" #include "argv-array.h" #include "commit-slab.h" #include "commit-reach.h" @@ -473,7 +473,7 @@ static void read_bisect_paths(struct argv_array *array) fclose(fp); } -static char *join_sha1_array_hex(struct oid_array *array, char delim) +static char *join_oid_array_hex(struct oid_array *array, char delim) { struct strbuf joined_hexs = STRBUF_INIT; int i; @@ -765,7 +765,7 @@ static enum bisect_error handle_bad_merge_base(void) { if (is_expected_rev(current_bad_oid)) { char *bad_hex = oid_to_hex(current_bad_oid); - char *good_hex = join_sha1_array_hex(&good_revs, ' '); + char *good_hex = join_oid_array_hex(&good_revs, ' '); if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) { fprintf(stderr, _("The merge base %s is bad.\n" "This means the bug has been fixed " @@ -796,7 +796,7 @@ static void handle_skipped_merge_base(const struct object_id *mb) { char *mb_hex = oid_to_hex(mb); char *bad_hex = oid_to_hex(current_bad_oid); - char *good_hex = join_sha1_array_hex(&good_revs, ' '); + char *good_hex = join_oid_array_hex(&good_revs, ' '); warning(_("the merge base between %s and [%s] " "must be skipped.\n" diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 6ecc8ee6dc..0d03fdac6e 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -12,7 +12,7 @@ #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" -#include "sha1-array.h" +#include "oid-array.h" #include "packfile.h" #include "object-store.h" #include "promisor-remote.h" diff --git a/builtin/clean.c b/builtin/clean.c index 5abf087e7c..c8c011d2dd 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -18,6 +18,7 @@ #include "color.h" #include "pathspec.h" #include "help.h" +#include "prompt.h" static int force = -1; /* unset */ static int interactive; @@ -420,7 +421,6 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff) return found; } - /* * Parse user input, and return choice(s) for menu (menu_stuff). * @@ -580,9 +580,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) clean_get_color(CLEAN_COLOR_RESET)); } - if (strbuf_getline_lf(&choice, stdin) != EOF) { - strbuf_trim(&choice); - } else { + if (git_read_line_interactively(&choice) == EOF) { eof = 1; break; } @@ -662,9 +660,7 @@ static int filter_by_patterns_cmd(void) clean_print_color(CLEAN_COLOR_PROMPT); printf(_("Input ignore patterns>> ")); clean_print_color(CLEAN_COLOR_RESET); - if (strbuf_getline_lf(&confirm, stdin) != EOF) - strbuf_trim(&confirm); - else + if (git_read_line_interactively(&confirm) == EOF) putchar('\n'); /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ @@ -760,9 +756,7 @@ static int ask_each_cmd(void) qname = quote_path_relative(item->string, NULL, &buf); /* TRANSLATORS: Make sure to keep [y/N] as is */ printf(_("Remove %s [y/N]? "), qname); - if (strbuf_getline_lf(&confirm, stdin) != EOF) { - strbuf_trim(&confirm); - } else { + if (git_read_line_interactively(&confirm) == EOF) { putchar('\n'); eof = 1; } diff --git a/builtin/clone.c b/builtin/clone.c index d8b1f413aa..cb48a291ca 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -102,10 +102,10 @@ static struct option builtin_clone_options[] = { N_("don't use local hardlinks, always copy")), OPT_BOOL('s', "shared", &option_shared, N_("setup as shared repository")), - OPT_ALIAS(0, "recursive", "recurse-submodules"), { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, N_("pathspec"), N_("initialize submodules in the clone"), PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, + OPT_ALIAS(0, "recursive", "recurse-submodules"), OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), @@ -643,7 +643,9 @@ static void write_followtags(const struct ref *refs, const char *msg) continue; if (ends_with(ref->name, "^{}")) continue; - if (!has_object_file(&ref->old_oid)) + if (!has_object_file_with_flags(&ref->old_oid, + OBJECT_INFO_QUICK | + OBJECT_INFO_SKIP_FETCH_OBJECT)) continue; update_ref(msg, ref->name, &ref->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); @@ -676,8 +678,7 @@ static void update_remote_refs(const struct ref *refs, const char *branch_top, const char *msg, struct transport *transport, - int check_connectivity, - int check_refs_are_promisor_objects_only) + int check_connectivity) { const struct ref *rm = mapped_refs; @@ -686,8 +687,6 @@ static void update_remote_refs(const struct ref *refs, opt.transport = transport; opt.progress = transport->progress; - opt.check_refs_are_promisor_objects_only = - !!check_refs_are_promisor_objects_only; if (check_connected(iterate_ref_map, &rm, &opt)) die(_("remote did not send all necessary objects")); @@ -1282,7 +1281,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) update_remote_refs(refs, mapped_refs, remote_head_points_at, branch_top.buf, reflog_msg.buf, transport, - !is_local, filter_options.choice); + !is_local); update_head(our_head_points_at, remote_head, reflog_msg.buf); diff --git a/builtin/diff.c b/builtin/diff.c index 42ac803091..8537b17bd5 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -17,7 +17,7 @@ #include "log-tree.h" #include "builtin.h" #include "submodule.h" -#include "sha1-array.h" +#include "oid-array.h" #define DIFF_NO_INDEX_EXPLICIT 1 #define DIFF_NO_INDEX_IMPLICIT 2 diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index dc1485c8aa..4771100072 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -3,7 +3,7 @@ #include "fetch-pack.h" #include "remote.h" #include "connect.h" -#include "sha1-array.h" +#include "oid-array.h" #include "protocol.h" static const char fetch_pack_usage[] = diff --git a/builtin/fetch.c b/builtin/fetch.c index bf6bab80fa..1097e1e512 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -908,13 +908,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, if (!connectivity_checked) { struct check_connected_options opt = CHECK_CONNECTED_INIT; - if (filter_options.choice) - /* - * Since a filter is specified, objects indirectly - * referenced by refs are allowed to be absent. - */ - opt.check_refs_are_promisor_objects_only = 1; - rm = ref_map; if (check_connected(iterate_ref_map, &rm, &opt)) { rc = error(_("%s did not send all necessary objects\n"), url); diff --git a/builtin/help.c b/builtin/help.c index e5590d7787..c024110531 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -242,7 +242,7 @@ static int add_man_viewer_cmd(const char *name, static int add_man_viewer_info(const char *var, const char *value) { const char *name, *subkey; - int namelen; + size_t namelen; if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name) return 0; diff --git a/builtin/log.c b/builtin/log.c index 83a4a6188e..390b6ca2ce 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -46,6 +46,7 @@ static int default_abbrev_commit; static int default_show_root = 1; static int default_follow; static int default_show_signature; +static int default_encode_email_headers = 1; static int decoration_style; static int decoration_given; static int use_mailmap_config = 1; @@ -151,6 +152,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; rev->show_signature = default_show_signature; + rev->encode_email_headers = default_encode_email_headers; rev->diffopt.flags.allow_textconv = 1; if (default_date_mode) @@ -173,6 +175,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_ALIAS(0, "mailmap", "use-mailmap"), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match <pattern>")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, @@ -438,6 +441,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "format.encodeemailheaders")) { + default_encode_email_headers = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.abbrevcommit")) { default_abbrev_commit = git_config_bool(var, value); return 0; @@ -1719,6 +1726,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.show_notes = show_notes; memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; + rev.encode_email_headers = default_encode_email_headers; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; diff --git a/builtin/merge.c b/builtin/merge.c index d127d2225f..df83ba2a80 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -597,10 +597,12 @@ static void parse_branch_merge_options(char *bmo) static int git_merge_config(const char *k, const char *v, void *cb) { int status; + const char *str; - if (branch && starts_with(k, "branch.") && - starts_with(k + 7, branch) && - !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { + if (branch && + skip_prefix(k, "branch.", &str) && + skip_prefix(str, branch, &str) && + !strcmp(str, ".mergeoptions")) { free(branch_mergeoptions); branch_mergeoptions = xstrdup(v); return 0; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 4c2bb170c6..fdd18c7ccb 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -26,7 +26,7 @@ #include "pack-bitmap.h" #include "delta-islands.h" #include "reachable.h" -#include "sha1-array.h" +#include "oid-array.h" #include "argv-array.h" #include "list.h" #include "packfile.h" @@ -3469,9 +3469,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) read_replace_refs = 0; - sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0); + sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1); prepare_repo_settings(the_repository); - if (!sparse && the_repository->settings.pack_use_sparse != -1) + if (sparse < 0) sparse = the_repository->settings.pack_use_sparse; reset_pack_idx_option(&pack_idx_opts); diff --git a/builtin/pull.c b/builtin/pull.c index e42665b681..b5d51ea74f 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -12,7 +12,7 @@ #include "parse-options.h" #include "exec-cmd.h" #include "run-command.h" -#include "sha1-array.h" +#include "oid-array.h" #include "remote.h" #include "dir.h" #include "rebase.h" @@ -110,6 +110,7 @@ static char *opt_ipv4; static char *opt_ipv6; static int opt_show_forced_updates = -1; static char *set_upstream; +static struct argv_array opt_fetch = ARGV_ARRAY_INIT; static struct option pull_options[] = { /* Shared options */ @@ -207,6 +208,15 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), N_("deepen history of shallow clone"), 0), + OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), + N_("deepen history of shallow repository based on time"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"), + N_("deepen history of shallow clone, excluding rev"), + 0), + OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), + N_("deepen history of shallow clone"), + 0), OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, N_("convert to a complete repository"), PARSE_OPT_NONEG | PARSE_OPT_NOARG), @@ -216,12 +226,19 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG), + OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, + N_("server-specific"), + N_("option to transmit"), + 0), OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, N_("use IPv4 addresses only"), PARSE_OPT_NOARG), OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, N_("use IPv6 addresses only"), PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + N_("report that we have only objects reachable from this object"), + 0), OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, @@ -567,6 +584,7 @@ static int run_fetch(const char *repo, const char **refspecs) argv_array_push(&args, "--no-show-forced-updates"); if (set_upstream) argv_array_push(&args, set_upstream); + argv_array_pushv(&args, opt_fetch.argv); if (repo) { argv_array_push(&args, repo); @@ -992,6 +1010,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase) { int ret = 0; + int ran_ff = 0; if ((recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) && submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head)) @@ -1008,10 +1027,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (is_descendant_of(merge_head, list)) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; + ran_ff = 1; ret = run_merge(); } } - ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + if (!ran_ff) + ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) diff --git a/builtin/rebase.c b/builtin/rebase.c index 27a07d4e78..c466923869 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -85,6 +85,7 @@ struct rebase_options { const char *action; int signoff; int allow_rerere_autoupdate; + int keep_empty; int autosquash; char *gpg_sign_opt; int autostash; @@ -95,11 +96,13 @@ struct rebase_options { struct strbuf git_format_patch_opt; int reschedule_failed_exec; int use_legacy_rebase; + int reapply_cherry_picks; }; #define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ + .keep_empty = 1, \ .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ @@ -379,11 +382,13 @@ static int run_sequencer_rebase(struct rebase_options *opts, git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); + flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0; flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; + flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0; switch (command) { case ACTION_NONE: { @@ -442,6 +447,7 @@ static int run_sequencer_rebase(struct rebase_options *opts, return ret; } +static void imply_merge(struct rebase_options *opts, const char *option); static int parse_opt_keep_empty(const struct option *opt, const char *arg, int unset) { @@ -449,10 +455,8 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg, BUG_ON_OPT_ARG(arg); - /* - * If we ever want to remap --keep-empty to --empty=keep, insert: - * opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP; - */ + imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty"); + opts->keep_empty = !unset; opts->type = REBASE_MERGE; return 0; } @@ -471,7 +475,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), REBASE_FORCE), { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, - N_("(DEPRECATED) keep empty commits"), + N_("keep commits which start empty"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, parse_opt_keep_empty }, OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, @@ -559,7 +563,7 @@ static void imply_merge(struct rebase_options *opts, const char *option) { switch (opts->type) { case REBASE_APPLY: - die(_("%s requires an interactive rebase"), option); + die(_("%s requires the merge backend"), option); break; case REBASE_MERGE: case REBASE_PRESERVE_MERGES: @@ -1163,6 +1167,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) opts->allow_rerere_autoupdate ? opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ? "--rerere-autoupdate" : "--no-rerere-autoupdate" : ""); + add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(&script_snippet, "cmd", opts->cmd); @@ -1548,7 +1553,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("how to handle commits that become empty"), PARSE_OPT_NONEG, parse_opt_empty), { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, - N_("(DEPRECATED) keep empty commits"), + N_("keep commits which start empty"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, parse_opt_keep_empty }, OPT_BOOL(0, "autosquash", &options.autosquash, @@ -1583,6 +1588,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "reschedule-failed-exec", &reschedule_failed_exec, N_("automatically re-schedule any `exec` that fails")), + OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks, + N_("apply all changes, even those already present upstream")), OPT_END(), }; int i; @@ -1593,6 +1600,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.allow_empty_message = 1; git_config(rebase_config, &options); + /* options.gpg_sign_opt will be either "-S" or NULL */ + gpg_sign = options.gpg_sign_opt ? "" : NULL; + FREE_AND_NULL(options.gpg_sign_opt); if (options.use_legacy_rebase || !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1)) @@ -1823,10 +1833,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.empty != EMPTY_UNSPECIFIED) imply_merge(&options, "--empty"); - if (gpg_sign) { - free(options.gpg_sign_opt); + if (options.reapply_cherry_picks) + imply_merge(&options, "--reapply-cherry-picks"); + + if (gpg_sign) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); - } if (exec.nr) { int i; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 2cc18bbffd..239094d2dc 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -13,7 +13,7 @@ #include "remote.h" #include "connect.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "connected.h" #include "argv-array.h" #include "version.h" @@ -499,12 +499,27 @@ static char *find_header(const char *msg, size_t len, const char *key, return NULL; } +/* + * Return zero if a and b are equal up to n bytes and nonzero if they are not. + * This operation is guaranteed to run in constant time to avoid leaking data. + */ +static int constant_memequal(const char *a, const char *b, size_t n) +{ + int res = 0; + size_t i; + + for (i = 0; i < n; i++) + res |= a[i] ^ b[i]; + return res; +} + static const char *check_nonce(const char *buf, size_t len) { char *nonce = find_header(buf, len, "nonce", NULL); timestamp_t stamp, ostamp; char *bohmac, *expect = NULL; const char *retval = NONCE_BAD; + size_t noncelen; if (!nonce) { retval = NONCE_MISSING; @@ -546,8 +561,14 @@ static const char *check_nonce(const char *buf, size_t len) goto leave; } + noncelen = strlen(nonce); expect = prepare_push_cert_nonce(service_dir, stamp); - if (strcmp(expect, nonce)) { + if (noncelen != strlen(expect)) { + /* This is not even the right size. */ + retval = NONCE_BAD; + goto leave; + } + if (constant_memequal(expect, nonce, noncelen)) { /* Not what we would have signed earlier */ retval = NONCE_BAD; goto leave; diff --git a/builtin/reflog.c b/builtin/reflog.c index 81dfd563c0..52ecf6d43c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -459,7 +459,7 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) static int reflog_expire_config(const char *var, const char *value, void *cb) { const char *pattern, *key; - int pattern_len; + size_t pattern_len; timestamp_t expire; int slot; struct reflog_expire_cfg *ent; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 098ebf22d0..f2c5a34402 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,7 +11,7 @@ #include "quote.h" #include "transport.h" #include "version.h" -#include "sha1-array.h" +#include "oid-array.h" #include "gpg-interface.h" #include "gettext.h" #include "protocol.h" diff --git a/builtin/tag.c b/builtin/tag.c index cc30d346f5..dd160b49c7 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,7 +17,7 @@ #include "diff.h" #include "revision.h" #include "gpg-interface.h" -#include "sha1-array.h" +#include "oid-array.h" #include "column.h" #include "ref-filter.h" @@ -14,7 +14,7 @@ #include "pack-revindex.h" #include "hash.h" #include "path.h" -#include "sha1-array.h" +#include "oid-array.h" #include "repository.h" #include "mem-pool.h" diff --git a/combine-diff.c b/combine-diff.c index d5c4d839dc..002e0e5438 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -10,7 +10,7 @@ #include "log-tree.h" #include "refs.h" #include "userdiff.h" -#include "sha1-array.h" +#include "oid-array.h" #include "revision.h" static int compare_paths(const struct combine_diff_path *one, diff --git a/compat/mingw.c b/compat/mingw.c index d14065d60e..8ee0b6408e 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -460,8 +460,21 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...) handle = CreateFileW(wfilename, FILE_APPEND_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, create, FILE_ATTRIBUTE_NORMAL, NULL); - if (handle == INVALID_HANDLE_VALUE) - return errno = err_win_to_posix(GetLastError()), -1; + if (handle == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + + /* + * Some network storage solutions (e.g. Isilon) might return + * ERROR_INVALID_PARAMETER instead of expected error + * ERROR_PATH_NOT_FOUND, which results in an unknown error. If + * so, let's turn the error to ERROR_PATH_NOT_FOUND instead. + */ + if (err == ERROR_INVALID_PARAMETER) + err = ERROR_PATH_NOT_FOUND; + + errno = err_win_to_posix(err); + return -1; + } /* * No O_APPEND here, because the CRT uses it only to reset the @@ -964,7 +977,16 @@ revert_attrs: size_t mingw_strftime(char *s, size_t max, const char *format, const struct tm *tm) { - size_t ret = strftime(s, max, format, tm); + /* a pointer to the original strftime in case we can't find the UCRT version */ + static size_t (*fallback)(char *, size_t, const char *, const struct tm *) = strftime; + size_t ret; + DECLARE_PROC_ADDR(ucrtbase.dll, size_t, strftime, char *, size_t, + const char *, const struct tm *); + + if (INIT_PROC_ADDR(strftime)) + ret = strftime(s, max, format, tm); + else + ret = fallback(s, max, format, tm); if (!ret && errno == EINVAL) die("invalid strftime format: '%s'", format); @@ -1479,6 +1501,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen const char *(*quote_arg)(const char *arg) = is_msys2_sh(cmd ? cmd : *argv) ? quote_arg_msys2 : quote_arg_msvc; + const char *strace_env; /* Make sure to override previous errors, if any */ errno = 0; @@ -1562,6 +1585,31 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen free(quoted); } + strace_env = getenv("GIT_STRACE_COMMANDS"); + if (strace_env) { + char *p = path_lookup("strace.exe", 1); + if (!p) + return error("strace not found!"); + if (xutftowcs_path(wcmd, p) < 0) { + free(p); + return -1; + } + free(p); + if (!strcmp("1", strace_env) || + !strcasecmp("yes", strace_env) || + !strcasecmp("true", strace_env)) + strbuf_insert(&args, 0, "strace ", 7); + else { + const char *quoted = quote_arg(strace_env); + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "strace -o %s ", quoted); + if (quoted != strace_env) + free((char *)quoted); + strbuf_insert(&args, 0, buf.buf, buf.len); + strbuf_release(&buf); + } + } + ALLOC_ARRAY(wargs, st_add(st_mult(2, args.len), 1)); xutftowcs(wargs, args.buf, 2 * args.len + 1); strbuf_release(&args); @@ -2581,12 +2629,14 @@ not_a_reserved_name: continue; } break; - case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */ + case 'c': case 'C': + /* COM1 ... COM9, CON, CONIN$, CONOUT$ */ if ((c = path[++i]) != 'o' && c != 'O') goto not_a_reserved_name; c = path[++i]; - if (c == 'm' || c == 'M') { /* COM<N> */ - if (!isdigit(path[++i])) + if (c == 'm' || c == 'M') { /* COM1 ... COM9 */ + c = path[++i]; + if (c < '1' || c > '9') goto not_a_reserved_name; } else if (c == 'n' || c == 'N') { /* CON */ c = path[i + 1]; diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h index f2e70872cd..bba2b64408 100644 --- a/compat/win32/path-utils.h +++ b/compat/win32/path-utils.h @@ -20,6 +20,17 @@ static inline char *win32_find_last_dir_sep(const char *path) return ret; } #define find_last_dir_sep win32_find_last_dir_sep +static inline int win32_has_dir_sep(const char *path) +{ + /* + * See how long the non-separator part of the given path is, and + * if and only if it covers the whole path (i.e. path[len] is NUL), + * there is no separator in the path---otherwise there is a separator. + */ + size_t len = strcspn(path, "/\\"); + return !!path[len]; +} +#define has_dir_sep(path) win32_has_dir_sep(path) int win32_offset_1st_component(const char *path); #define offset_1st_component win32_offset_1st_component @@ -37,6 +37,7 @@ struct config_source { enum config_error_action default_error_action; int linenr; int eof; + size_t total_len; struct strbuf value; struct strbuf var; unsigned subsection_case_sensitive : 1; @@ -309,7 +310,7 @@ int git_config_include(const char *var, const char *value, void *data) { struct config_include_data *inc = data; const char *cond, *key; - int cond_len; + size_t cond_len; int ret; /* @@ -358,12 +359,13 @@ static inline int iskeychar(int c) * * store_key - pointer to char* which will hold a copy of the key with * lowercase section and variable name - * baselen - pointer to int which will hold the length of the + * baselen - pointer to size_t which will hold the length of the * section + subsection part, can be NULL */ -static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet) +static int git_config_parse_key_1(const char *key, char **store_key, size_t *baselen_, int quiet) { - int i, dot, baselen; + size_t i, baselen; + int dot; const char *last_dot = strrchr(key, '.'); /* @@ -425,7 +427,7 @@ out_free_ret_1: return -CONFIG_INVALID_KEY; } -int git_config_parse_key(const char *key, char **store_key, int *baselen) +int git_config_parse_key(const char *key, char **store_key, size_t *baselen) { return git_config_parse_key_1(key, store_key, baselen, 0); } @@ -523,6 +525,19 @@ static int get_next_char(void) c = '\r'; } } + + if (c != EOF && ++cf->total_len > INT_MAX) { + /* + * This is an absurdly long config file; refuse to parse + * further in order to protect downstream code from integer + * overflows. Note that we can't return an error specifically, + * but we can mark EOF and put trash in the return value, + * which will trigger a parse error. + */ + cf->eof = 1; + return 0; + } + if (c == '\n') cf->linenr++; if (c == EOF) { @@ -728,7 +743,7 @@ static int git_parse_source(config_fn_t fn, void *data, const struct config_options *opts) { int comment = 0; - int baselen = 0; + size_t baselen = 0; struct strbuf *var = &cf->var; int error_return = 0; char *error_msg = NULL; @@ -1539,6 +1554,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data, top->prev = cf; top->linenr = 1; top->eof = 0; + top->total_len = 0; strbuf_init(&top->value, 1024); strbuf_init(&top->var, 1024); cf = top; @@ -2383,7 +2399,7 @@ void git_die_config(const char *key, const char *err, ...) */ struct config_store_data { - int baselen; + size_t baselen; char *key; int do_not_match; regex_t *value_regex; @@ -2509,7 +2525,7 @@ static struct strbuf store_create_section(const char *key, const struct config_store_data *store) { const char *dot; - int i; + size_t i; struct strbuf sb = STRBUF_INIT; dot = memchr(key, '.', store->baselen); @@ -2522,7 +2538,9 @@ static struct strbuf store_create_section(const char *key, } strbuf_addstr(&sb, "\"]\n"); } else { - strbuf_addf(&sb, "[%.*s]\n", store->baselen, key); + strbuf_addch(&sb, '['); + strbuf_add(&sb, key, store->baselen); + strbuf_addstr(&sb, "]\n"); } return sb; @@ -2545,7 +2563,6 @@ static ssize_t write_pair(int fd, const char *key, const char *value, { int i; ssize_t ret; - int length = strlen(key + store->baselen + 1); const char *quote = ""; struct strbuf sb = STRBUF_INIT; @@ -2564,8 +2581,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value, if (i && value[i - 1] == ' ') quote = "\""; - strbuf_addf(&sb, "\t%.*s = %s", - length, key + store->baselen + 1, quote); + strbuf_addf(&sb, "\t%s = %s", key + store->baselen + 1, quote); for (i = 0; value[i]; i++) switch (value[i]) { @@ -3238,7 +3254,7 @@ int config_error_nonbool(const char *var) int parse_config_key(const char *var, const char *section, - const char **subsection, int *subsection_len, + const char **subsection, size_t *subsection_len, const char **key) { const char *dot; @@ -254,7 +254,7 @@ int git_config_set_gently(const char *, const char *); */ void git_config_set(const char *, const char *); -int git_config_parse_key(const char *, char **, int *); +int git_config_parse_key(const char *, char **, size_t *); int git_config_key_is_valid(const char *key); int git_config_set_multivar_gently(const char *, const char *, const char *, int); void git_config_set_multivar(const char *, const char *, const char *, int); @@ -359,7 +359,7 @@ int git_config_include(const char *name, const char *value, void *data); */ int parse_config_key(const char *var, const char *section, - const char **subsection, int *subsection_len, + const char **subsection, size_t *subsection_len, const char **key); /** @@ -9,7 +9,7 @@ #include "connect.h" #include "url.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "transport.h" #include "strbuf.h" #include "version.h" diff --git a/connected.c b/connected.c index ac52b07b47..3135b71e19 100644 --- a/connected.c +++ b/connected.c @@ -52,7 +52,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data, strbuf_release(&idx_file); } - if (opt->check_refs_are_promisor_objects_only) { + if (has_promisor_remote()) { /* * For partial clones, we don't want to have to do a regular * connectivity check because we have to enumerate and exclude @@ -75,13 +75,18 @@ int check_connected(oid_iterate_fn fn, void *cb_data, if (find_pack_entry_one(oid.hash, p)) goto promisor_pack_found; } - return 1; + /* + * Fallback to rev-list with oid and the rest of the + * object IDs provided by fn. + */ + goto no_promisor_pack_found; promisor_pack_found: ; } while (!fn(cb_data, &oid)); return 0; } +no_promisor_pack_found: if (opt->shallow_file) { argv_array_push(&rev_list.args, "--shallow-file"); argv_array_push(&rev_list.args, opt->shallow_file); diff --git a/connected.h b/connected.h index eba5c261ba..8d5a6b3ad6 100644 --- a/connected.h +++ b/connected.h @@ -46,15 +46,6 @@ struct check_connected_options { * during a fetch. */ unsigned is_deepening_fetch : 1; - - /* - * If non-zero, only check that the top-level objects referenced by the - * wanted refs (passed in as cb_data) are promisor objects. This is - * useful for partial clones, where enumerating and excluding all - * promisor objects is very slow and the commit-walk itself becomes a - * no-op. - */ - unsigned check_refs_are_promisor_objects_only : 1; }; #define CHECK_CONNECTED_INIT { 0 } diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index e800d9f5c9..d50ce26d5d 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -139,6 +139,8 @@ foreach my $tar_file (@ARGV) print FI "\n"; } + next if ($typeflag eq 'g'); # ignore global header + my $path; if ($prefix) { $path = "$prefix/$name"; @@ -1018,7 +1018,7 @@ static int apply_filter(const char *path, const char *src, size_t len, static int read_convert_config(const char *var, const char *value, void *cb) { const char *key, *name; - int namelen; + size_t namelen; struct convert_driver *drv; /* diff --git a/credential.c b/credential.c index 77dfde44e3..064e25e5d5 100644 --- a/credential.c +++ b/credential.c @@ -88,6 +88,11 @@ static void credential_apply_config(struct credential *c) struct urlmatch_config config = { STRING_LIST_INIT_DUP }; struct strbuf url = STRBUF_INIT; + if (!c->host) + die(_("refusing to work with credential missing host field")); + if (!c->protocol) + die(_("refusing to work with credential missing protocol field")); + if (c->configured) return; @@ -222,20 +227,25 @@ int credential_read(struct credential *c, FILE *fp) return 0; } -static void credential_write_item(FILE *fp, const char *key, const char *value) +static void credential_write_item(FILE *fp, const char *key, const char *value, + int required) { + if (!value && required) + BUG("credential value for %s is missing", key); if (!value) return; + if (strchr(value, '\n')) + die("credential value for %s contains newline", key); fprintf(fp, "%s=%s\n", key, value); } void credential_write(const struct credential *c, FILE *fp) { - credential_write_item(fp, "protocol", c->protocol); - credential_write_item(fp, "host", c->host); - credential_write_item(fp, "path", c->path); - credential_write_item(fp, "username", c->username); - credential_write_item(fp, "password", c->password); + credential_write_item(fp, "protocol", c->protocol, 1); + credential_write_item(fp, "host", c->host, 1); + credential_write_item(fp, "path", c->path, 0); + credential_write_item(fp, "username", c->username, 0); + credential_write_item(fp, "password", c->password, 0); } static int run_credential_helper(struct credential *c, @@ -353,7 +363,22 @@ void credential_reject(struct credential *c) c->approved = 0; } -void credential_from_url(struct credential *c, const char *url) +static int check_url_component(const char *url, int quiet, + const char *name, const char *value) +{ + if (!value) + return 0; + if (!strchr(value, '\n')) + return 0; + + if (!quiet) + warning(_("url contains a newline in its %s component: %s"), + name, url); + return -1; +} + +int credential_from_url_gently(struct credential *c, const char *url, + int quiet) { const char *at, *colon, *cp, *slash, *host, *proto_end; @@ -366,12 +391,22 @@ void credential_from_url(struct credential *c, const char *url) * (3) proto://<user>:<pass>@<host>/... */ proto_end = strstr(url, "://"); - if (!proto_end) - return; + if (!proto_end || proto_end == url) { + if (!quiet) + warning(_("url has no scheme: %s"), url); + return -1; + } cp = proto_end + 3; at = strchr(cp, '@'); colon = strchr(cp, ':'); - slash = strchrnul(cp, '/'); + + /* + * A query or fragment marker before the slash ends the host portion. + * We'll just continue to call this "slash" for simplicity. Notably our + * "trim leading slashes" part won't skip over this part of the path, + * but that's what we'd want. + */ + slash = cp + strcspn(cp, "/?#"); if (!at || slash <= at) { /* Case (1) */ @@ -392,10 +427,8 @@ void credential_from_url(struct credential *c, const char *url) host = at + 1; } - if (proto_end - url > 0) - c->protocol = xmemdupz(url, proto_end - url); - if (slash - host > 0) - c->host = url_decode_mem(host, slash - host); + c->protocol = xmemdupz(url, proto_end - url); + c->host = url_decode_mem(host, slash - host); /* Trim leading and trailing slashes from path */ while (*slash == '/') slash++; @@ -406,4 +439,19 @@ void credential_from_url(struct credential *c, const char *url) while (p > c->path && *p == '/') *p-- = '\0'; } + + if (check_url_component(url, quiet, "username", c->username) < 0 || + check_url_component(url, quiet, "password", c->password) < 0 || + check_url_component(url, quiet, "protocol", c->protocol) < 0 || + check_url_component(url, quiet, "host", c->host) < 0 || + check_url_component(url, quiet, "path", c->path) < 0) + return -1; + + return 0; +} + +void credential_from_url(struct credential *c, const char *url) +{ + if (credential_from_url_gently(c, url, 0) < 0) + die(_("credential url cannot be parsed: %s"), url); } diff --git a/credential.h b/credential.h index fec7815dd0..d99ec42b2a 100644 --- a/credential.h +++ b/credential.h @@ -173,8 +173,21 @@ void credential_reject(struct credential *); int credential_read(struct credential *, FILE *); void credential_write(const struct credential *, FILE *); -/* Parse a URL into broken-down credential fields. */ +/* + * Parse a url into a credential struct, replacing any existing contents. + * + * If the url can't be parsed (e.g., a missing "proto://" component), the + * resulting credential will be empty but we'll still return success from the + * "gently" form. + * + * If we encounter a component which cannot be represented as a credential + * value (e.g., because it contains a newline), the "gently" form will return + * an error but leave the broken state in the credential object for further + * examination. The non-gentle form will issue a warning to stderr and return + * an empty credential. + */ void credential_from_url(struct credential *, const char *url); +int credential_from_url_gently(struct credential *, const char *url, int quiet); int credential_match(const struct credential *have, const struct credential *want); diff --git a/delta-islands.c b/delta-islands.c index 09dbd3cf72..aa98b2e541 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -17,7 +17,7 @@ #include "pack-bitmap.h" #include "pack-objects.h" #include "delta-islands.h" -#include "sha1-array.h" +#include "oid-array.h" #include "config.h" KHASH_INIT(str, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) diff --git a/fast-import.c b/fast-import.c index 202dda11a6..c98970274c 100644 --- a/fast-import.c +++ b/fast-import.c @@ -39,12 +39,28 @@ struct object_entry { struct pack_idx_entry idx; - struct object_entry *next; + struct hashmap_entry ent; uint32_t type : TYPE_BITS, pack_id : PACK_ID_BITS, depth : DEPTH_BITS; }; +static int object_entry_hashcmp(const void *map_data, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct object_id *oid = keydata; + const struct object_entry *e1, *e2; + + e1 = container_of(eptr, const struct object_entry, ent); + if (oid) + return oidcmp(&e1->idx.oid, oid); + + e2 = container_of(entry_or_key, const struct object_entry, ent); + return oidcmp(&e1->idx.oid, &e2->idx.oid); +} + struct object_entry_pool { struct object_entry_pool *next_pool; struct object_entry *next_free; @@ -178,7 +194,7 @@ static off_t pack_size; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 5000; static struct object_entry_pool *blocks; -static struct object_entry *object_table[1 << 16]; +static struct hashmap object_table; static struct mark_set *marks; static const char *export_marks_file; static const char *import_marks_file; @@ -455,44 +471,37 @@ static struct object_entry *new_object(struct object_id *oid) static struct object_entry *find_object(struct object_id *oid) { - unsigned int h = oid->hash[0] << 8 | oid->hash[1]; - struct object_entry *e; - for (e = object_table[h]; e; e = e->next) - if (oideq(oid, &e->idx.oid)) - return e; - return NULL; + return hashmap_get_entry_from_hash(&object_table, oidhash(oid), oid, + struct object_entry, ent); } static struct object_entry *insert_object(struct object_id *oid) { - unsigned int h = oid->hash[0] << 8 | oid->hash[1]; - struct object_entry *e = object_table[h]; + struct object_entry *e; + unsigned int hash = oidhash(oid); - while (e) { - if (oideq(oid, &e->idx.oid)) - return e; - e = e->next; + e = hashmap_get_entry_from_hash(&object_table, hash, oid, + struct object_entry, ent); + if (!e) { + e = new_object(oid); + e->idx.offset = 0; + hashmap_entry_init(&e->ent, hash); + hashmap_add(&object_table, &e->ent); } - e = new_object(oid); - e->next = object_table[h]; - e->idx.offset = 0; - object_table[h] = e; return e; } static void invalidate_pack_id(unsigned int id) { - unsigned int h; unsigned long lu; struct tag *t; + struct hashmap_iter iter; + struct object_entry *e; - for (h = 0; h < ARRAY_SIZE(object_table); h++) { - struct object_entry *e; - - for (e = object_table[h]; e; e = e->next) - if (e->pack_id == id) - e->pack_id = MAX_PACK_ID; + hashmap_for_each_entry(&object_table, &iter, e, ent) { + if (e->pack_id == id) + e->pack_id = MAX_PACK_ID; } for (lu = 0; lu < branch_table_sz; lu++) { @@ -3511,6 +3520,8 @@ int cmd_main(int argc, const char **argv) avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); + hashmap_init(&object_table, object_entry_hashcmp, NULL, 0); + /* * We don't parse most options until after we've seen the set of * "feature" lines at the start of the stream (which allows the command diff --git a/fetch-pack.c b/fetch-pack.c index 1734a573b0..0b07b3ee73 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -15,7 +15,7 @@ #include "connect.h" #include "transport.h" #include "version.h" -#include "sha1-array.h" +#include "oid-array.h" #include "oidset.h" #include "packfile.h" #include "object-store.h" @@ -9,12 +9,14 @@ #include "tag.h" #include "fsck.h" #include "refs.h" +#include "url.h" #include "utf8.h" #include "decorate.h" #include "oidset.h" #include "packfile.h" #include "submodule-config.h" #include "config.h" +#include "credential.h" #include "help.h" static struct oidset gitmodules_found = OIDSET_INIT; @@ -910,6 +912,149 @@ done: return ret; } +/* + * Like builtin/submodule--helper.c's starts_with_dot_slash, but without + * relying on the platform-dependent is_dir_sep helper. + * + * This is for use in checking whether a submodule URL is interpreted as + * relative to the current directory on any platform, since \ is a + * directory separator on Windows but not on other platforms. + */ +static int starts_with_dot_slash(const char *str) +{ + return str[0] == '.' && (str[1] == '/' || str[1] == '\\'); +} + +/* + * Like starts_with_dot_slash, this is a variant of submodule--helper's + * helper of the same name with the twist that it accepts backslash as a + * directory separator even on non-Windows platforms. + */ +static int starts_with_dot_dot_slash(const char *str) +{ + return str[0] == '.' && starts_with_dot_slash(str + 1); +} + +static int submodule_url_is_relative(const char *url) +{ + return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url); +} + +/* + * Count directory components that a relative submodule URL should chop + * from the remote_url it is to be resolved against. + * + * In other words, this counts "../" components at the start of a + * submodule URL. + * + * Returns the number of directory components to chop and writes a + * pointer to the next character of url after all leading "./" and + * "../" components to out. + */ +static int count_leading_dotdots(const char *url, const char **out) +{ + int result = 0; + while (1) { + if (starts_with_dot_dot_slash(url)) { + result++; + url += strlen("../"); + continue; + } + if (starts_with_dot_slash(url)) { + url += strlen("./"); + continue; + } + *out = url; + return result; + } +} +/* + * Check whether a transport is implemented by git-remote-curl. + * + * If it is, returns 1 and writes the URL that would be passed to + * git-remote-curl to the "out" parameter. + * + * Otherwise, returns 0 and leaves "out" untouched. + * + * Examples: + * http::https://example.com/repo.git -> 1, https://example.com/repo.git + * https://example.com/repo.git -> 1, https://example.com/repo.git + * git://example.com/repo.git -> 0 + * + * This is for use in checking for previously exploitable bugs that + * required a submodule URL to be passed to git-remote-curl. + */ +static int url_to_curl_url(const char *url, const char **out) +{ + /* + * We don't need to check for case-aliases, "http.exe", and so + * on because in the default configuration, is_transport_allowed + * prevents URLs with those schemes from being cloned + * automatically. + */ + if (skip_prefix(url, "http::", out) || + skip_prefix(url, "https::", out) || + skip_prefix(url, "ftp::", out) || + skip_prefix(url, "ftps::", out)) + return 1; + if (starts_with(url, "http://") || + starts_with(url, "https://") || + starts_with(url, "ftp://") || + starts_with(url, "ftps://")) { + *out = url; + return 1; + } + return 0; +} + +static int check_submodule_url(const char *url) +{ + const char *curl_url; + + if (looks_like_command_line_option(url)) + return -1; + + if (submodule_url_is_relative(url)) { + char *decoded; + const char *next; + int has_nl; + + /* + * This could be appended to an http URL and url-decoded; + * check for malicious characters. + */ + decoded = url_decode(url); + has_nl = !!strchr(decoded, '\n'); + + free(decoded); + if (has_nl) + return -1; + + /* + * URLs which escape their root via "../" can overwrite + * the host field and previous components, resolving to + * URLs like https::example.com/submodule.git and + * https:///example.com/submodule.git that were + * susceptible to CVE-2020-11008. + */ + if (count_leading_dotdots(url, &next) > 0 && + (*next == ':' || *next == '/')) + return -1; + } + + else if (url_to_curl_url(url, &curl_url)) { + struct credential c = CREDENTIAL_INIT; + int ret = 0; + if (credential_from_url_gently(&c, curl_url, 1) || + !*c.host) + ret = -1; + credential_clear(&c); + return ret; + } + + return 0; +} + struct fsck_gitmodules_data { const struct object_id *oid; struct fsck_options *options; @@ -920,7 +1065,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) { struct fsck_gitmodules_data *data = vdata; const char *subsection, *key; - int subsection_len; + size_t subsection_len; char *name; if (parse_config_key(var, "submodule", &subsection, &subsection_len, &key) < 0 || @@ -935,7 +1080,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) "disallowed submodule name: %s", name); if (!strcmp(key, "url") && value && - looks_like_command_line_option(value)) + check_submodule_url(value) < 0) data->ret |= report(data->options, data->oid, OBJ_BLOB, FSCK_MSG_GITMODULES_URL, diff --git a/git-compat-util.h b/git-compat-util.h index aed0b5d4f9..8ba576e81e 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -389,6 +389,14 @@ static inline char *git_find_last_dir_sep(const char *path) #define find_last_dir_sep git_find_last_dir_sep #endif +#ifndef has_dir_sep +static inline int git_has_dir_sep(const char *path) +{ + return !!strchr(path, '/'); +} +#define has_dir_sep(path) git_has_dir_sep(path) +#endif + #ifndef query_user_email #define query_user_email() NULL #endif @@ -34,6 +34,7 @@ import zipfile import zlib import ctypes import errno +import glob # On python2.7 where raw_input() and input() are both availble, # we want raw_input's semantics, but aliased to input for python3 @@ -165,7 +166,10 @@ def prompt(prompt_text): """ choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text)) while True: - response = input(prompt_text).strip().lower() + sys.stderr.flush() + sys.stdout.write(prompt_text) + sys.stdout.flush() + response=sys.stdin.readline().strip().lower() if not response: continue response = response[0] @@ -202,6 +206,73 @@ def decode_path(path): print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path)) return path +def run_git_hook(cmd, param=[]): + """Execute a hook if the hook exists.""" + 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)) @@ -1567,13 +1638,39 @@ class P4Submit(Command, P4UserMap): "work from a local git branch that is not master"), optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true", help="Skip Perforce sync of p4/master after submit or shelve"), + optparse.make_option("--no-verify", dest="no_verify", action="store_true", + help="Bypass p4-pre-submit and p4-changelist hooks"), ] self.description = """Submit changes from git to the perforce depot.\n - The `p4-pre-submit` hook is executed if it exists and is executable. - The hook takes no parameters and nothing from standard input. Exiting with - non-zero status from this script prevents `git-p4 submit` from launching. - - One usage scenario is to run unit tests in the hook.""" + The `p4-pre-submit` hook is executed if it exists and is executable. It + can be bypassed with the `--no-verify` command line option. The hook takes + no parameters and nothing from standard input. Exiting with a non-zero status + from this script prevents `git-p4 submit` from launching. + + One usage scenario is to run unit tests in the hook. + + The `p4-prepare-changelist` hook is executed right after preparing the default + changelist message and before the editor is started. It takes one parameter, + the name of the file that contains the changelist text. Exiting with a non-zero + status from the script will abort the process. + + The purpose of the hook is to edit the message file in place, and it is not + supressed by the `--no-verify` option. This hook is called even if + `--prepare-p4-only` is set. + + The `p4-changelist` hook is executed after the changelist message has been + edited by the user. It can be bypassed with the `--no-verify` option. It + takes a single parameter, the name of the file that holds the proposed + changelist text. Exiting with a non-zero status causes the command to abort. + + The hook is allowed to edit the changelist file and can be used to normalize + the text into some project standard format. It can also be used to refuse the + Submit after inspect the message file. + + The `p4-post-changelist` hook is invoked after the submit has successfully + occured in P4. It takes no parameters and is meant primarily for notification + and cannot affect the outcome of the git p4 submit action. + """ self.usage += " [name of git branch to submit into perforce depot]" self.origin = "" @@ -1591,6 +1688,7 @@ class P4Submit(Command, P4UserMap): self.exportLabels = False self.p4HasMoveCommand = p4_has_move_command() self.branch = None + self.no_verify = False if gitConfig('git-p4.largeFileSystem'): die("Large file system not supported for git-p4 submit command. Please remove it from config.") @@ -1978,6 +2076,9 @@ class P4Submit(Command, P4UserMap): applyPatchCmd = patchcmd + "--check --apply -" patch_succeeded = True + if verbose: + print("TryPatch: %s" % tryPatchCmd) + if os.system(tryPatchCmd) != 0: fixed_rcs_keywords = False patch_succeeded = False @@ -2017,6 +2118,7 @@ class P4Submit(Command, P4UserMap): print("Retrying the patch with RCS keywords cleaned up") if os.system(tryPatchCmd) == 0: patch_succeeded = True + print("Patch succeesed this time with RCS keywords cleaned") if not patch_succeeded: for f in editedFiles: @@ -2077,55 +2179,73 @@ class P4Submit(Command, P4UserMap): tmpFile.write(encode_text_stream(submitTemplate)) tmpFile.close() - if self.prepare_p4_only: - # - # Leave the p4 tree prepared, and the submit template around - # and let the user decide what to do next - # - print() - print("P4 workspace prepared for submission.") - print("To submit or revert, go to client workspace") - print(" " + self.clientPath) - print() - print("To submit, use \"p4 submit\" to write a new description,") - print("or \"p4 submit -i <%s\" to use the one prepared by" \ - " \"git p4\"." % fileName) - print("You can delete the file \"%s\" when finished." % fileName) - - if self.preserveUser and p4User and not self.p4UserIsMe(p4User): - print("To preserve change ownership by user %s, you must\n" \ - "do \"p4 change -f <change>\" after submitting and\n" \ - "edit the User field.") - if pureRenameCopy: - print("After submitting, renamed files must be re-synced.") - print("Invoke \"p4 sync -f\" on each of these files:") - for f in pureRenameCopy: - print(" " + f) - - print() - print("To revert the changes, use \"p4 revert ...\", and delete") - print("the submit template file \"%s\"" % fileName) - if filesToAdd: - print("Since the commit adds new files, they must be deleted:") - for f in filesToAdd: - print(" " + f) - print() - return True - - # - # Let the user edit the change description, then submit it. - # submitted = False try: + # Allow the hook to edit the changelist text before presenting it + # to the user. + if not run_git_hook("p4-prepare-changelist", [fileName]): + return False + + if self.prepare_p4_only: + # + # Leave the p4 tree prepared, and the submit template around + # and let the user decide what to do next + # + submitted = True + print("") + print("P4 workspace prepared for submission.") + print("To submit or revert, go to client workspace") + print(" " + self.clientPath) + print("") + print("To submit, use \"p4 submit\" to write a new description,") + print("or \"p4 submit -i <%s\" to use the one prepared by" \ + " \"git p4\"." % fileName) + print("You can delete the file \"%s\" when finished." % fileName) + + if self.preserveUser and p4User and not self.p4UserIsMe(p4User): + print("To preserve change ownership by user %s, you must\n" \ + "do \"p4 change -f <change>\" after submitting and\n" \ + "edit the User field.") + if pureRenameCopy: + print("After submitting, renamed files must be re-synced.") + print("Invoke \"p4 sync -f\" on each of these files:") + for f in pureRenameCopy: + print(" " + f) + + print("") + print("To revert the changes, use \"p4 revert ...\", and delete") + print("the submit template file \"%s\"" % fileName) + if filesToAdd: + print("Since the commit adds new files, they must be deleted:") + for f in filesToAdd: + print(" " + f) + print("") + sys.stdout.flush() + return True + if self.edit_template(fileName): + if not self.no_verify: + if not run_git_hook("p4-changelist", [fileName]): + print("The p4-changelist hook failed.") + sys.stdout.flush() + return False + # read the edited message and submit tmpFile = open(fileName, "rb") message = decode_text_stream(tmpFile.read()) tmpFile.close() if self.isWindows: message = message.replace("\r\n", "\n") - submitTemplate = message[:message.index(separatorLine)] + if message.find(separatorLine) != -1: + submitTemplate = message[:message.index(separatorLine)] + else: + submitTemplate = message + + if len(submitTemplate.strip()) == 0: + print("Changelist is empty, aborting this changelist.") + sys.stdout.flush() + return False if update_shelve: p4_write_pipe(['shelve', '-r', '-i'], submitTemplate) @@ -2148,20 +2268,23 @@ class P4Submit(Command, P4UserMap): submitted = True + run_git_hook("p4-post-changelist") finally: - # skip this patch + # Revert changes if we skip this patch if not submitted or self.shelve: if self.shelve: print ("Reverting shelved files.") else: print ("Submission cancelled, undoing p4 changes.") + sys.stdout.flush() for f in editedFiles | filesToDelete: p4_revert(f) for f in filesToAdd: p4_revert(f) os.remove(f) - os.remove(fileName) + if not self.prepare_p4_only: + os.remove(fileName) return submitted # Export git tags as p4 labels. Create a p4 label and then tag @@ -2385,13 +2508,17 @@ class P4Submit(Command, P4UserMap): sys.exit("number of commits (%d) must match number of shelved changelist (%d)" % (len(commits), num_shelves)) - hooks_path = gitConfig("core.hooksPath") - if len(hooks_path) <= 0: - hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks") - - hook_file = os.path.join(hooks_path, "p4-pre-submit") - if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0: - sys.exit(1) + if not self.no_verify: + try: + if not run_git_hook("p4-pre-submit"): + print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip " \ + "this pre-submission check by adding\nthe command line option '--no-verify', " \ + "however,\nthis will also skip the p4-changelist hook as well.") + sys.exit(1) + except Exception as e: + print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "\ + "with the error '{0}'".format(e.message) ) + sys.exit(1) # # Apply the commits, one at a time. On failure, ask if should @@ -4205,7 +4332,6 @@ commands = { "unshelve" : P4Unshelve, } - def main(): if len(sys.argv[1:]) == 0: printUsage(commands.keys()) diff --git a/git-submodule.sh b/git-submodule.sh index 89f915cae9..08e0439df0 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -48,6 +48,8 @@ depth= progress= dissociate= single_branch= +jobs= +recommend_shallow= die_if_unmatched () { @@ -351,6 +351,7 @@ static int handle_alias(int *argcp, const char ***argv) trace2_cmd_alias(alias_command, child.args.argv); trace2_cmd_list_config(); + trace2_cmd_list_env_vars(); trace2_cmd_name("_run_shell_alias_"); ret = run_command(&child); @@ -388,6 +389,7 @@ static int handle_alias(int *argcp, const char ***argv) trace2_cmd_alias(alias_command, new_argv); trace2_cmd_list_config(); + trace2_cmd_list_env_vars(); *argv = new_argv; *argcp += count - 1; @@ -439,6 +441,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) trace_argv_printf(argv, "trace: built-in: git"); trace2_cmd_name(p->cmd); trace2_cmd_list_config(); + trace2_cmd_list_env_vars(); validate_cache_entries(the_repository->index); status = p->fn(argc, argv, prefix); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 65a3a9e62e..1a02a1242d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1291,9 +1291,23 @@ our $is_last_request = sub { 1 }; our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook); our $CGI = 'CGI'; our $cgi; +our $FCGI_Stream_PRINT_raw = \&FCGI::Stream::PRINT; sub configure_as_fcgi { require CGI::Fast; our $CGI = 'CGI::Fast'; + # FCGI is not Unicode aware hence the UTF-8 encoding must be done manually. + # However no encoding must be done within git_blob_plain() and git_snapshot() + # which must still output in raw binary mode. + no warnings 'redefine'; + my $enc = Encode::find_encoding('UTF-8'); + *FCGI::Stream::PRINT = sub { + my @OUTPUT = @_; + for (my $i = 1; $i < @_; $i++) { + $OUTPUT[$i] = $enc->encode($_[$i], Encode::FB_CROAK|Encode::LEAVE_SRC); + } + @_ = @OUTPUT; + goto $FCGI_Stream_PRINT_raw; + }; my $request_number = 0; # let each child service 100 requests @@ -7079,6 +7093,7 @@ sub git_blob_plain { ($sandbox ? 'attachment' : 'inline') . '; filename="' . $save_as . '"'); local $/ = undef; + local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw; binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi @@ -7417,6 +7432,7 @@ sub git_snapshot { open my $fd, "-|", $cmd or die_error(500, "Execute git-archive failed"); + local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw; binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi @@ -579,6 +579,7 @@ static int has_cert_password(void) return 0; if (!cert_auth.password) { cert_auth.protocol = xstrdup("cert"); + cert_auth.host = xstrdup(""); cert_auth.username = xstrdup(""); cert_auth.path = xstrdup(ssl_cert); credential_fill(&cert_auth); @@ -593,6 +594,7 @@ static int has_proxy_cert_password(void) return 0; if (!proxy_cert_auth.password) { proxy_cert_auth.protocol = xstrdup("cert"); + proxy_cert_auth.host = xstrdup(""); proxy_cert_auth.username = xstrdup(""); proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert); credential_fill(&proxy_cert_auth); diff --git a/ll-merge.c b/ll-merge.c index d65a8971db..1ec0b959e0 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -247,7 +247,7 @@ static int read_merge_config(const char *var, const char *value, void *cb) { struct ll_merge_driver *fn; const char *key, *name; - int namelen; + size_t namelen; if (!strcmp(var, "merge.default")) return git_config_string(&default_ll_merge, var, value); diff --git a/log-tree.c b/log-tree.c index 897a90233e..0064788b25 100644 --- a/log-tree.c +++ b/log-tree.c @@ -693,6 +693,7 @@ void show_log(struct rev_info *opt) ctx.abbrev = opt->diffopt.abbrev; ctx.after_subject = extra_headers; ctx.preserve_subject = opt->preserve_subject; + ctx.encode_email_headers = opt->encode_email_headers; ctx.reflog_info = opt->reflog_info; ctx.fmt = opt->commit_format; ctx.mailmap = opt->mailmap; @@ -93,7 +93,7 @@ int ls_refs(struct repository *r, struct argv_array *keys, git_config(ls_refs_config, NULL); - while (packet_reader_read(request) != PACKET_READ_FLUSH) { + while (packet_reader_read(request) == PACKET_READ_NORMAL) { const char *arg = request->line; const char *out; @@ -105,6 +105,9 @@ int ls_refs(struct repository *r, struct argv_array *keys, argv_array_push(&data.prefixes, out); } + if (request->status != PACKET_READ_FLUSH) + die(_("expected flush after ls-refs arguments")); + head_ref_namespaced(send_ref, &data); for_each_namespaced_ref(send_ref, &data); packet_flush(1); @@ -923,6 +923,12 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * cur_chunk = 0; num_chunks = large_offsets_needed ? 5 : 4; + if (packs.nr - dropped_packs == 0) { + error(_("no pack files to index.")); + result = 1; + goto cleanup; + } + written = write_midx_header(f, num_chunks, packs.nr - dropped_packs); chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES; @@ -1124,6 +1130,15 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag i, oid_fanout1, oid_fanout2, i + 1); } + if (m->num_objects == 0) { + midx_report(_("the midx contains no oid")); + /* + * Remaining tests assume that we have objects, so we can + * return here. + */ + return verify_midx_error; + } + if (flags & MIDX_PROGRESS) progress = start_sparse_progress(_("Verifying OID order in multi-pack-index"), m->num_objects - 1); diff --git a/object-store.h b/object-store.h index be72fee7d5..d1e490f203 100644 --- a/object-store.h +++ b/object-store.h @@ -4,7 +4,7 @@ #include "cache.h" #include "oidmap.h" #include "list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "strbuf.h" #include "thread-utils.h" @@ -59,7 +59,7 @@ struct object_array { /* * object flag allocation: - * revision.h: 0---------10 25----28 + * revision.h: 0---------10 15 25----28 * fetch-pack.c: 01 * negotiator/default.c: 2--5 * walker.c: 0-2 diff --git a/sha1-array.c b/oid-array.c index 3eeadfede9..8657a5cedf 100644 --- a/sha1-array.c +++ b/oid-array.c @@ -1,5 +1,5 @@ #include "cache.h" -#include "sha1-array.h" +#include "oid-array.h" #include "sha1-lookup.h" void oid_array_append(struct oid_array *array, const struct object_id *oid) @@ -46,9 +46,9 @@ int oid_array_for_each(struct oid_array *array, for_each_oid_fn fn, void *data) { - int i; + size_t i; - /* No oid_array_sort() here! See sha1-array.h */ + /* No oid_array_sort() here! See oid-array.h */ for (i = 0; i < array->nr; i++) { int ret = fn(array->oid + i, data); @@ -62,7 +62,7 @@ int oid_array_for_each_unique(struct oid_array *array, for_each_oid_fn fn, void *data) { - int i; + size_t i; if (!array->sorted) oid_array_sort(array); @@ -82,7 +82,7 @@ void oid_array_filter(struct oid_array *array, for_each_oid_fn want, void *cb_data) { - unsigned nr = array->nr, src, dst; + size_t nr = array->nr, src, dst; struct object_id *oids = array->oid; for (src = dst = 0; src < nr; src++) { diff --git a/sha1-array.h b/oid-array.h index dc1bca9c9a..f28d322c90 100644 --- a/sha1-array.h +++ b/oid-array.h @@ -19,7 +19,7 @@ * * void some_func(void) * { - * struct sha1_array hashes = OID_ARRAY_INIT; + * struct oid_array hashes = OID_ARRAY_INIT; * struct object_id oid; * * // Read objects into our set @@ -49,8 +49,8 @@ */ struct oid_array { struct object_id *oid; - int nr; - int alloc; + size_t nr; + size_t alloc; int sorted; }; @@ -4,7 +4,7 @@ #include "khash.h" /** - * This API is similar to sha1-array, in that it maintains a set of object ids + * This API is similar to oid-array, in that it maintains a set of object ids * in a memory-efficient way. The major differences are: * * 1. It uses a hash, so we can do online duplicate removal, rather than diff --git a/parse-options-cb.c b/parse-options-cb.c index a28b55be48..86cd393013 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -5,7 +5,7 @@ #include "color.h" #include "string-list.h" #include "argv-array.h" -#include "sha1-array.h" +#include "oid-array.h" /*----- some often used options -----*/ diff --git a/parse-options.c b/parse-options.c index 63d6bab60c..c57618d537 100644 --- a/parse-options.c +++ b/parse-options.c @@ -648,6 +648,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, int short_name; const char *long_name; const char *source; + struct strbuf help = STRBUF_INIT; int j; if (newopt[i].type != OPTION_ALIAS) @@ -659,6 +660,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, if (!long_name) BUG("An alias must have long option name"); + strbuf_addf(&help, _("alias of --%s"), source); for (j = 0; j < nr; j++) { const char *name = options[j].long_name; @@ -669,15 +671,10 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, if (options[j].type == OPTION_ALIAS) BUG("No please. Nested aliases are not supported."); - /* - * NEEDSWORK: this is a bit inconsistent because - * usage_with_options() on the original options[] will print - * help string as "alias of %s" but "git cmd -h" will - * print the original help string. - */ memcpy(newopt + i, options + j, sizeof(*newopt)); newopt[i].short_name = short_name; newopt[i].long_name = long_name; + newopt[i].help = strbuf_detach(&help, NULL); break; } @@ -474,7 +474,8 @@ void pp_user_info(struct pretty_print_context *pp, } strbuf_addstr(sb, "From: "); - if (needs_rfc2047_encoding(namebuf, namelen)) { + if (pp->encode_email_headers && + needs_rfc2047_encoding(namebuf, namelen)) { add_rfc2047(sb, namebuf, namelen, encoding, RFC2047_ADDRESS); max_length = 76; /* per rfc2047 */ @@ -1767,7 +1768,8 @@ void pp_title_line(struct pretty_print_context *pp, if (pp->print_email_subject) { if (pp->rev) fmt_output_email_subject(sb, pp->rev); - if (needs_rfc2047_encoding(title.buf, title.len)) + if (pp->encode_email_headers && + needs_rfc2047_encoding(title.buf, title.len)) add_rfc2047(sb, title.buf, title.len, encoding, RFC2047_SUBJECT); else @@ -43,6 +43,7 @@ struct pretty_print_context { struct string_list *mailmap; int color; struct ident_split *from_ident; + unsigned encode_email_headers:1; /* * Fields below here are manipulated internally by pp_* functions and diff --git a/promisor-remote.c b/promisor-remote.c index 9f338c945f..ff8721fd56 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -101,7 +101,7 @@ static void promisor_remote_move_to_tail(struct promisor_remote *r, static int promisor_remote_config(const char *var, const char *value, void *data) { const char *name; - int namelen; + size_t namelen; const char *subkey; if (!strcmp(var, "core.partialclonefilter")) @@ -74,3 +74,15 @@ char *git_prompt(const char *prompt, int flags) } return r; } + +int git_read_line_interactively(struct strbuf *line) +{ + int ret; + + fflush(stdout); + ret = strbuf_getline_lf(line, stdin); + if (ret != EOF) + strbuf_trim_trailing_newline(line); + + return ret; +} @@ -6,4 +6,6 @@ char *git_prompt(const char *prompt, int flags); +int git_read_line_interactively(struct strbuf *line); + #endif /* PROMPT_H */ diff --git a/ref-filter.c b/ref-filter.c index b1812cb69a..35776838f4 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1976,10 +1976,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, } /* - * Given a ref (sha1, refname), check if the ref belongs to the array - * of sha1s. If the given ref is a tag, check if the given tag points - * at one of the sha1s in the given sha1 array. - * the given sha1_array. + * Given a ref (oid, refname), check if the ref belongs to the array + * of oids. If the given ref is a tag, check if the given tag points + * at one of the oids in the given oid array. * NEEDSWORK: * 1. Only a single level of inderection is obtained, we might want to * change this to account for multiple levels (e.g. annotated tags diff --git a/ref-filter.h b/ref-filter.h index f1dcff4c6e..64330e9601 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -1,7 +1,7 @@ #ifndef REF_FILTER_H #define REF_FILTER_H -#include "sha1-array.h" +#include "oid-array.h" #include "refs.h" #include "commit.h" #include "parse-options.h" @@ -1852,14 +1852,14 @@ static struct ref_store *ref_store_init(const char *gitdir, struct ref_store *get_main_ref_store(struct repository *r) { - if (r->refs) - return r->refs; + if (r->refs_private) + return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); - r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS); - return r->refs; + r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS); + return r->refs_private; } /* diff --git a/remote-curl.c b/remote-curl.c index e4cd321844..1c9aa3d0ab 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -12,7 +12,7 @@ #include "sideband.h" #include "argv-array.h" #include "credential.h" -#include "sha1-array.h" +#include "oid-array.h" #include "send-pack.h" #include "protocol.h" #include "quote.h" @@ -174,54 +174,43 @@ static void add_merge(struct branch *branch, const char *name) branch->merge_name[branch->merge_nr++] = name; } -static struct branch *make_branch(const char *name, int len) +static struct branch *make_branch(const char *name, size_t len) { struct branch *ret; int i; for (i = 0; i < branches_nr; i++) { - if (len ? (!strncmp(name, branches[i]->name, len) && - !branches[i]->name[len]) : - !strcmp(name, branches[i]->name)) + if (!strncmp(name, branches[i]->name, len) && + !branches[i]->name[len]) return branches[i]; } ALLOC_GROW(branches, branches_nr + 1, branches_alloc); ret = xcalloc(1, sizeof(struct branch)); branches[branches_nr++] = ret; - if (len) - ret->name = xstrndup(name, len); - else - ret->name = xstrdup(name); + ret->name = xstrndup(name, len); ret->refname = xstrfmt("refs/heads/%s", ret->name); return ret; } -static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len) +static struct rewrite *make_rewrite(struct rewrites *r, + const char *base, size_t len) { struct rewrite *ret; int i; for (i = 0; i < r->rewrite_nr; i++) { - if (len - ? (len == r->rewrite[i]->baselen && - !strncmp(base, r->rewrite[i]->base, len)) - : !strcmp(base, r->rewrite[i]->base)) + if (len == r->rewrite[i]->baselen && + !strncmp(base, r->rewrite[i]->base, len)) return r->rewrite[i]; } ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc); ret = xcalloc(1, sizeof(struct rewrite)); r->rewrite[r->rewrite_nr++] = ret; - if (len) { - ret->base = xstrndup(base, len); - ret->baselen = len; - } - else { - ret->base = xstrdup(base); - ret->baselen = strlen(base); - } + ret->base = xstrndup(base, len); + ret->baselen = len; return ret; } @@ -316,7 +305,7 @@ static void read_branches_file(struct remote *remote) static int handle_config(const char *key, const char *value, void *cb) { const char *name; - int namelen; + size_t namelen; const char *subkey; struct remote *remote; struct branch *branch; @@ -470,7 +459,7 @@ static void read_config(void) const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { - current_branch = make_branch(head_ref, 0); + current_branch = make_branch(head_ref, strlen(head_ref)); } } git_config(handle_config, NULL); @@ -1584,7 +1573,7 @@ struct branch *branch_get(const char *name) if (!name || !*name || !strcmp(name, "HEAD")) ret = current_branch; else - ret = make_branch(name, 0); + ret = make_branch(name, strlen(name)); set_merge(ret); return ret; } diff --git a/repo-settings.c b/repo-settings.c index a703e407a3..dc6817daa9 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -45,6 +45,8 @@ void prepare_repo_settings(struct repository *r) if (!repo_config_get_bool(r, "pack.usesparse", &value)) r->settings.pack_use_sparse = value; + UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1); + if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) { UPDATE_DEFAULT_BOOL(r->settings.index_version, 4); UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE); @@ -52,7 +54,6 @@ void prepare_repo_settings(struct repository *r) if (!repo_config_get_bool(r, "fetch.writecommitgraph", &value)) r->settings.fetch_write_commit_graph = value; if (!repo_config_get_bool(r, "feature.experimental", &value) && value) { - UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1); UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING); UPDATE_DEFAULT_BOOL(r->settings.fetch_write_commit_graph, 1); } diff --git a/repository.h b/repository.h index 040057dea6..6534fbb7b3 100644 --- a/repository.h +++ b/repository.h @@ -67,8 +67,12 @@ struct repository { */ struct parsed_object_pool *parsed_objects; - /* The store in which the refs are held. */ - struct ref_store *refs; + /* + * The store in which the refs are held. This should generally only be + * accessed via get_main_ref_store(), as that will lazily initialize + * the ref object. + */ + struct ref_store *refs_private; /* * Contains path to often used file names. diff --git a/revision.c b/revision.c index 8136929e23..5bc96444b6 100644 --- a/revision.c +++ b/revision.c @@ -870,7 +870,19 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) } parent->next = NULL; commit->parents = parent; - commit->object.flags |= TREESAME; + + /* + * A merge commit is a "diversion" if it is not + * TREESAME to its first parent but is TREESAME + * to a later parent. In the simplified history, + * we "divert" the history walk to the later + * parent. These commits are shown when "show_pulls" + * is enabled, so do not mark the object as + * TREESAME here. + */ + if (!revs->show_pulls || !nth_parent) + commit->object.flags |= TREESAME; + return; case REV_TREE_NEW: @@ -897,6 +909,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) relevant_change = 1; else irrelevant_change = 1; + + if (!nth_parent) + commit->object.flags |= PULL_MERGE; + continue; } die("bad tree compare for commit %s", oid_to_hex(&commit->object.oid)); @@ -2241,6 +2257,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->topo_order = 1; revs->rewrite_parents = 1; revs->graph = graph_init(revs); + } else if (!strcmp(arg, "--encode-email-headers")) { + revs->encode_email_headers = 1; + } else if (!strcmp(arg, "--no-encode-email-headers")) { + revs->encode_email_headers = 0; } else if (!strcmp(arg, "--root")) { revs->show_root_diff = 1; } else if (!strcmp(arg, "--no-commit-id")) { @@ -2265,6 +2285,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--full-diff")) { revs->diff = 1; revs->full_diff = 1; + } else if (!strcmp(arg, "--show-pulls")) { + revs->show_pulls = 1; } else if (!strcmp(arg, "--full-history")) { revs->simplify_history = 0; } else if (!strcmp(arg, "--relative-date")) { @@ -3019,7 +3041,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c if (!cnt || (commit->object.flags & UNINTERESTING) || !(commit->object.flags & TREESAME) || - (parent = one_relevant_parent(revs, commit->parents)) == NULL) + (parent = one_relevant_parent(revs, commit->parents)) == NULL || + (revs->show_pulls && (commit->object.flags & PULL_MERGE))) st->simplified = commit; else { pst = locate_simplify_state(revs, parent); @@ -3602,6 +3625,10 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi /* drop merges unless we want parenthood */ if (!want_ancestry(revs)) return commit_ignore; + + if (revs->show_pulls && (commit->object.flags & PULL_MERGE)) + return commit_show; + /* * If we want ancestry, then need to keep any merges * between relevant commits to tie together topology. diff --git a/revision.h b/revision.h index 475f048fb6..c1af164b30 100644 --- a/revision.h +++ b/revision.h @@ -34,6 +34,9 @@ #define SYMMETRIC_LEFT (1u<<8) #define PATCHSAME (1u<<9) #define BOTTOM (1u<<10) + +/* WARNING: This is also used as REACHABLE in commit-graph.c. */ +#define PULL_MERGE (1u<<15) /* * Indicates object was reached by traversal. i.e. not given by user on * command-line or stdin. @@ -43,7 +46,7 @@ */ #define NOT_USER_GIVEN (1u<<25) #define TRACK_LINEAR (1u<<26) -#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR) +#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE) #define TOPO_WALK_EXPLORED (1u<<27) #define TOPO_WALK_INDEGREE (1u<<28) @@ -129,6 +132,7 @@ struct rev_info { no_walk:2, remove_empty_trees:1, simplify_history:1, + show_pulls:1, topo_order:1, simplify_merges:1, simplify_by_decoration:1, @@ -203,7 +207,8 @@ struct rev_info { use_terminator:1, missing_newline:1, date_mode_explicit:1, - preserve_subject:1; + preserve_subject:1, + encode_email_headers:1; unsigned int disable_stdin:1; /* --show-linear-break */ unsigned int track_linear:1, diff --git a/run-command.c b/run-command.c index f5e1149f9b..0f41af3b55 100644 --- a/run-command.c +++ b/run-command.c @@ -421,12 +421,12 @@ static int prepare_cmd(struct argv_array *out, const struct child_process *cmd) } /* - * If there are no '/' characters in the command then perform a path - * lookup and use the resolved path as the command to exec. If there - * are '/' characters, we have exec attempt to invoke the command - * directly. + * If there are no dir separator characters in the command then perform + * a path lookup and use the resolved path as the command to exec. If + * there are dir separator characters, we have exec attempt to invoke + * the command directly. */ - if (!strchr(out->argv[1], '/')) { + if (!has_dir_sep(out->argv[1])) { char *program = locate_in_PATH(out->argv[1]); if (program) { free((char *)out->argv[1]); diff --git a/send-pack.c b/send-pack.c index 0407841ae8..da4741ce4a 100644 --- a/send-pack.c +++ b/send-pack.c @@ -12,7 +12,7 @@ #include "quote.h" #include "transport.h" #include "version.h" -#include "sha1-array.h" +#include "oid-array.h" #include "gpg-interface.h" #include "cache.h" diff --git a/sequencer.c b/sequencer.c index 6fd2674632..f30bb73c70 100644 --- a/sequencer.c +++ b/sequencer.c @@ -946,6 +946,8 @@ static int run_git_commit(struct repository *r, argv_array_push(&cmd.args, "--amend"); if (opts->gpg_sign) argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); + else + argv_array_push(&cmd.args, "--no-gpg-sign"); if (defmsg) argv_array_pushl(&cmd.args, "-F", defmsg, NULL); else if (!(flags & EDIT_MSG)) @@ -1578,7 +1580,7 @@ static const char *command_to_string(const enum todo_command command) static char command_to_char(const enum todo_command command) { - if (command < TODO_COMMENT && todo_command_info[command].c) + if (command < TODO_COMMENT) return todo_command_info[command].c; return comment_line_char; } @@ -3128,7 +3130,7 @@ static int do_exec(struct repository *r, const char *command_line) const char *child_argv[] = { NULL, NULL }; int dirty, status; - fprintf(stderr, "Executing: %s\n", command_line); + fprintf(stderr, _("Executing: %s\n"), command_line); child_argv[0] = command_line; argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir())); argv_array_pushf(&child_env, "GIT_WORK_TREE=%s", @@ -3724,10 +3726,11 @@ static const char *reflog_message(struct replay_opts *opts, { va_list ap; static struct strbuf buf = STRBUF_INIT; + char *reflog_action = getenv(GIT_REFLOG_ACTION); va_start(ap, fmt); strbuf_reset(&buf); - strbuf_addstr(&buf, action_name(opts)); + strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts)); if (sub_action) strbuf_addf(&buf, " (%s)", sub_action); if (fmt) { @@ -3815,8 +3818,11 @@ static int pick_commits(struct repository *r, struct replay_opts *opts) { int res = 0, reschedule = 0; + char *prev_reflog_action; + /* Note that 0 for 3rd parameter of setenv means set only if not set */ setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION)); if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); @@ -3841,7 +3847,7 @@ static int pick_commits(struct repository *r, fclose(f); } if (!opts->quiet) - fprintf(stderr, "Rebasing (%d/%d)%s", + fprintf(stderr, _("Rebasing (%d/%d)%s"), todo_list->done_nr, todo_list->total_nr, opts->verbose ? "\n" : "\r"); @@ -3861,12 +3867,14 @@ static int pick_commits(struct repository *r, } if (item->command <= TODO_SQUASH) { if (is_rebase_i(opts)) - setenv("GIT_REFLOG_ACTION", reflog_message(opts, + setenv(GIT_REFLOG_ACTION, reflog_message(opts, command_to_string(item->command), NULL), 1); res = do_pick_commit(r, item->command, item->commit, opts, is_final_fixup(todo_list), &check_todo); + if (is_rebase_i(opts)) + setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1); if (is_rebase_i(opts) && res < 0) { /* Reschedule */ advise(_(rescheduled_advice), @@ -4093,7 +4101,7 @@ cleanup_head_ref: if (!opts->verbose) term_clear_line(); fprintf(stderr, - "Successfully rebased and updated %s.\n", + _("Successfully rebased and updated %s.\n"), head_ref.buf); } @@ -4607,6 +4615,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, struct rev_info *revs, struct strbuf *out, unsigned flags) { + int keep_empty = flags & TODO_LIST_KEEP_EMPTY; int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS; int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO; struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT; @@ -4661,6 +4670,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, is_empty = is_original_commit_empty(commit); if (!is_empty && (commit->object.flags & PATCHSAME)) continue; + if (is_empty && !keep_empty) + continue; strbuf_reset(&oneline); pretty_print_commit(pp, commit, &oneline); @@ -4672,6 +4683,9 @@ static int make_script_with_merges(struct pretty_print_context *pp, strbuf_addf(&buf, "%s %s %s", cmd_pick, oid_to_hex(&commit->object.oid), oneline.buf); + if (is_empty) + strbuf_addf(&buf, " %c empty", + comment_line_char); FLEX_ALLOC_STR(entry, string, buf.buf); oidcpy(&entry->entry.oid, &commit->object.oid); @@ -4835,14 +4849,16 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, struct pretty_print_context pp = {0}; struct rev_info revs; struct commit *commit; + int keep_empty = flags & TODO_LIST_KEEP_EMPTY; const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; int rebase_merges = flags & TODO_LIST_REBASE_MERGES; + int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS; repo_init_revisions(r, &revs, NULL); revs.verbose_header = 1; if (!rebase_merges) revs.max_parents = 1; - revs.cherry_mark = 1; + revs.cherry_mark = !reapply_cherry_picks; revs.limited = 1; revs.reverse = 1; revs.right_only = 1; @@ -4874,9 +4890,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, if (!is_empty && (commit->object.flags & PATCHSAME)) continue; + if (is_empty && !keep_empty) + continue; strbuf_addf(out, "%s %s ", insn, oid_to_hex(&commit->object.oid)); pretty_print_commit(&pp, commit, out); + if (is_empty) + strbuf_addf(out, " %c empty", comment_line_char); strbuf_addch(out, '\n'); } return 0; @@ -4963,6 +4983,8 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis max = num; for (item = todo_list->items, i = 0; i < max; i++, item++) { + char cmd; + /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { strbuf_addf(buf, "%.*s\n", item->arg_len, @@ -4971,8 +4993,9 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis } /* add command to the buffer */ - if (flags & TODO_LIST_ABBREVIATE_CMDS) - strbuf_addch(buf, command_to_char(item->command)); + cmd = command_to_char(item->command); + if ((flags & TODO_LIST_ABBREVIATE_CMDS) && cmd) + strbuf_addch(buf, cmd); else strbuf_addstr(buf, command_to_string(item->command)); diff --git a/sequencer.h b/sequencer.h index 0bee85093e..9611605711 100644 --- a/sequencer.h +++ b/sequencer.h @@ -134,7 +134,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts); int sequencer_skip(struct repository *repo, struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); -/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */ +#define TODO_LIST_KEEP_EMPTY (1U << 0) #define TODO_LIST_SHORTEN_IDS (1U << 1) #define TODO_LIST_ABBREVIATE_CMDS (1U << 2) #define TODO_LIST_REBASE_MERGES (1U << 3) @@ -150,7 +150,7 @@ int sequencer_remove_state(struct replay_opts *opts); * `--onto`, we do not want to re-generate the root commits. */ #define TODO_LIST_ROOT_WITH_ONTO (1U << 6) - +#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7) int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, const char **argv, unsigned flags); diff --git a/sha1-file.c b/sha1-file.c index 6926851724..ccd34dd9e8 100644 --- a/sha1-file.c +++ b/sha1-file.c @@ -881,9 +881,7 @@ void prepare_alt_odb(struct repository *r) /* Returns 1 if we have successfully freshened the file, 0 otherwise. */ static int freshen_file(const char *fn) { - struct utimbuf t; - t.actime = t.modtime = time(NULL); - return !utime(fn, &t); + return !utime(fn, NULL); } /* diff --git a/sha1-name.c b/sha1-name.c index 5bb006e5a9..0b8cb5247a 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -8,7 +8,7 @@ #include "refs.h" #include "remote.h" #include "dir.h" -#include "sha1-array.h" +#include "oid-array.h" #include "packfile.h" #include "object-store.h" #include "repository.h" @@ -1815,8 +1815,8 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, cb.repo = repo; cb.list = &list; - refs_for_each_ref(repo->refs, handle_one_ref, &cb); - refs_head_ref(repo->refs, handle_one_ref, &cb); + refs_for_each_ref(get_main_ref_store(repo), handle_one_ref, &cb); + refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb); commit_list_sort_by_date(&list); return get_oid_oneline(repo, name + 2, oid, list); } @@ -8,7 +8,7 @@ #include "pkt-line.h" #include "remote.h" #include "refs.h" -#include "sha1-array.h" +#include "oid-array.h" #include "diff.h" #include "revision.h" #include "commit-slab.h" @@ -4,6 +4,7 @@ #include "strbuf.h" #include "run-command.h" #include "alias.h" +#include "prompt.h" #define COMMAND_DIR "git-shell-commands" #define HELP_COMMAND COMMAND_DIR "/help" @@ -76,12 +77,11 @@ static void run_shell(void) int count; fprintf(stderr, "git> "); - if (strbuf_getline_lf(&line, stdin) == EOF) { + if (git_read_line_interactively(&line) == EOF) { fprintf(stderr, "\n"); strbuf_release(&line); break; } - strbuf_trim(&line); rawargs = strbuf_detach(&line, NULL); split_args = xstrdup(rawargs); count = split_cmdline(split_args, &argv); diff --git a/submodule-config.c b/submodule-config.c index 4d1c92d582..e175dfbc38 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -225,7 +225,8 @@ static int name_and_item_from_var(const char *var, struct strbuf *name, struct strbuf *item) { const char *subsection, *key; - int subsection_len, parse; + size_t subsection_len; + int parse; parse = parse_config_key(var, "submodule", &subsection, &subsection_len, &key); if (parse < 0 || !subsection) diff --git a/submodule.c b/submodule.c index c3aadf3fff..e2ef5698c8 100644 --- a/submodule.c +++ b/submodule.c @@ -12,7 +12,7 @@ #include "diffcore.h" #include "refs.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "argv-array.h" #include "blob.h" #include "thread-utils.h" @@ -69,7 +69,8 @@ You can also run each test individually from command line, like this: You can pass --verbose (or -v), --debug (or -d), and --immediate (or -i) command line argument to the test, or by setting GIT_TEST_OPTS -appropriately before running "make". +appropriately before running "make". Short options can be bundled, i.e. +'-d -v' is the same as '-dv'. -v:: --verbose:: @@ -386,9 +387,9 @@ GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path for the index version specified. Can be set to any valid version (currently 2, 3, or 4). -GIT_TEST_PACK_SPARSE=<boolean> if enabled will default the pack-objects -builtin to use the sparse object walk. This can still be overridden by -the --no-sparse command-line argument. +GIT_TEST_PACK_SPARSE=<boolean> if disabled will default the pack-objects +builtin to use the non-sparse object walk. This can still be overridden by +the --sparse command-line argument. GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path by overriding the minimum number of cache entries required per thread. @@ -546,6 +547,41 @@ Here are the "do's:" reports "ok" or "not ok" to the end user running the tests. Under --verbose, they are shown to help debug the tests. + - Be careful when you loop + + You may need to verify multiple things in a loop, but the + following does not work correctly: + + test_expect_success 'test three things' ' + for i in one two three + do + test_something "$i" + done && + test_something_else + ' + + Because the status of the loop itself is the exit status of the + test_something in the last round, the loop does not fail when + "test_something" for "one" or "two" fails. This is not what you + want. + + Instead, you can break out of the loop immediately when you see a + failure. Because all test_expect_* snippets are executed inside + a function, "return 1" can be used to fail the test immediately + upon a failure: + + test_expect_success 'test three things' ' + for i in one two three + do + test_something "$i" || return 1 + done && + test_something_else + ' + + Note that we still &&-chain the loop to propagate failures from + earlier commands. + + And here are the "don'ts:" - Don't exit() within a <script> part. diff --git a/t/helper/test-sha1-array.c b/t/helper/test-oid-array.c index ad5e69f9d3..ce9fd5f091 100644 --- a/t/helper/test-sha1-array.c +++ b/t/helper/test-oid-array.c @@ -1,6 +1,6 @@ #include "test-tool.h" #include "cache.h" -#include "sha1-array.h" +#include "oid-array.h" static int print_oid(const struct object_id *oid, void *data) { @@ -8,7 +8,7 @@ static int print_oid(const struct object_id *oid, void *data) return 0; } -int cmd__sha1_array(int argc, const char **argv) +int cmd__oid_array(int argc, const char **argv) { struct oid_array array = OID_ARRAY_INIT; struct strbuf line = STRBUF_INIT; @@ -19,11 +19,11 @@ int cmd__sha1_array(int argc, const char **argv) if (skip_prefix(line.buf, "append ", &arg)) { if (get_oid_hex(arg, &oid)) - die("not a hexadecimal SHA1: %s", arg); + die("not a hexadecimal oid: %s", arg); oid_array_append(&array, &oid); } else if (skip_prefix(line.buf, "lookup ", &arg)) { if (get_oid_hex(arg, &oid)) - die("not a hexadecimal SHA1: %s", arg); + die("not a hexadecimal oid: %s", arg); printf("%d\n", oid_array_lookup(&array, &oid)); } else if (!strcmp(line.buf, "clear")) oid_array_clear(&array); diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c index 282d536384..12ca698e17 100644 --- a/t/helper/test-pkt-line.c +++ b/t/helper/test-pkt-line.c @@ -67,7 +67,7 @@ static void unpack_sideband(void) case PACKET_READ_NORMAL: band = reader.line[0] & 0xff; if (band < 1 || band > 2) - die("unexpected side band %d", band); + continue; /* skip non-sideband packets */ fd = band; write_or_die(fd, reader.line + 1, reader.pktlen - 1); diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 31eedcd241..2ece4d1ebf 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -37,6 +37,7 @@ static struct test_cmd cmds[] = { { "match-trees", cmd__match_trees }, { "mergesort", cmd__mergesort }, { "mktemp", cmd__mktemp }, + { "oid-array", cmd__oid_array }, { "oidmap", cmd__oidmap }, { "online-cpus", cmd__online_cpus }, { "parse-options", cmd__parse_options }, @@ -57,7 +58,6 @@ static struct test_cmd cmds[] = { { "scrap-cache-tree", cmd__scrap_cache_tree }, { "serve-v2", cmd__serve_v2 }, { "sha1", cmd__sha1 }, - { "sha1-array", cmd__sha1_array }, { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, { "strcmp-offset", cmd__strcmp_offset }, @@ -112,6 +112,7 @@ int cmd_main(int argc, const char **argv) argc--; trace2_cmd_name(cmds[i].name); trace2_cmd_list_config(); + trace2_cmd_list_env_vars(); return cmds[i].fn(argc, argv); } } diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 4eb5e6609e..1cbaec02f3 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -47,7 +47,7 @@ int cmd__run_command(int argc, const char **argv); int cmd__scrap_cache_tree(int argc, const char **argv); int cmd__serve_v2(int argc, const char **argv); int cmd__sha1(int argc, const char **argv); -int cmd__sha1_array(int argc, const char **argv); +int cmd__oid_array(int argc, const char **argv); int cmd__sha256(int argc, const char **argv); int cmd__sigchain(int argc, const char **argv); int cmd__strcmp_offset(int argc, const char **argv); diff --git a/t/lib-credential.sh b/t/lib-credential.sh index 937b831ea6..dea2cbef51 100755..100644 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -1,4 +1,5 @@ -#!/bin/sh +# Shell library for testing credential handling including helpers. See t0302 +# for an example of testing a specific helper. # Try a set of credential helpers; the expected stdin, # stdout and stderr should be provided on stdin, @@ -19,7 +20,7 @@ check() { false fi && test_cmp expect-stdout stdout && - test_cmp expect-stderr stderr + test_i18ncmp expect-stderr stderr } read_chunk() { diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 8d28652b72..9fc5241228 100755..100644 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -1,14 +1,25 @@ -#!/bin/sh +# We always set GNUPGHOME, even if no usable GPG was found, as +# +# - It does not hurt, and +# +# - we cannot set global environment variables in lazy prereqs because they are +# executed in an eval'ed subshell that changes the working directory to a +# temporary one. + +GNUPGHOME="$PWD/gpghome" +export GNUPGHOME + +test_lazy_prereq GPG ' + gpg_version=$(gpg --version 2>&1) + test $? != 127 || exit 1 -gpg_version=$(gpg --version 2>&1) -if test $? != 127 -then # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 - # the gpg version 1.0.6 didn't parse trust packets correctly, so for + # the gpg version 1.0.6 did not parse trust packets correctly, so for # that version, creation of signed tags using the generated key fails. case "$gpg_version" in - 'gpg (GnuPG) 1.0.6'*) + "gpg (GnuPG) 1.0.6"*) say "Your version of gpg (1.0.6) is too buggy for testing" + exit 1 ;; *) # Available key info: @@ -27,55 +38,54 @@ then # To export ownertrust: # gpg --homedir /tmp/gpghome --export-ownertrust \ # > lib-gpg/ownertrust - mkdir ./gpghome && - chmod 0700 ./gpghome && - GNUPGHOME="$(pwd)/gpghome" && - export GNUPGHOME && - (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) && - gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \ + mkdir "$GNUPGHOME" && + chmod 0700 "$GNUPGHOME" && + (gpgconf --kill gpg-agent || : ) && + gpg --homedir "${GNUPGHOME}" --import \ "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && - gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \ + gpg --homedir "${GNUPGHOME}" --import-ownertrust \ "$TEST_DIRECTORY"/lib-gpg/ownertrust && - gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null 2>&1 \ - --sign -u committer@example.com && - test_set_prereq GPG && - # Available key info: - # * see t/lib-gpg/gpgsm-gen-key.in - # To generate new certificate: - # * no passphrase - # gpgsm --homedir /tmp/gpghome/ \ - # -o /tmp/gpgsm.crt.user \ - # --generate-key \ - # --batch t/lib-gpg/gpgsm-gen-key.in - # To import certificate: - # gpgsm --homedir /tmp/gpghome/ \ - # --import /tmp/gpgsm.crt.user - # To export into a .p12 we can later import: - # gpgsm --homedir /tmp/gpghome/ \ - # -o t/lib-gpg/gpgsm_cert.p12 \ - # --export-secret-key-p12 "committer@example.com" - echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \ - --passphrase-fd 0 --pinentry-mode loopback \ - --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 && - - gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K | - grep fingerprint: | - cut -d" " -f4 | - tr -d '\n' >"${GNUPGHOME}/trustlist.txt" && - - echo " S relax" >>"${GNUPGHOME}/trustlist.txt" && - echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \ - -u committer@example.com -o /dev/null --sign - 2>&1 && - test_set_prereq GPGSM + gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null \ + --sign -u committer@example.com ;; esac -fi +' + +test_lazy_prereq GPGSM ' + test_have_prereq GPG && + # Available key info: + # * see t/lib-gpg/gpgsm-gen-key.in + # To generate new certificate: + # * no passphrase + # gpgsm --homedir /tmp/gpghome/ \ + # -o /tmp/gpgsm.crt.user \ + # --generate-key \ + # --batch t/lib-gpg/gpgsm-gen-key.in + # To import certificate: + # gpgsm --homedir /tmp/gpghome/ \ + # --import /tmp/gpgsm.crt.user + # To export into a .p12 we can later import: + # gpgsm --homedir /tmp/gpghome/ \ + # -o t/lib-gpg/gpgsm_cert.p12 \ + # --export-secret-key-p12 "committer@example.com" + echo | gpgsm --homedir "${GNUPGHOME}" \ + --passphrase-fd 0 --pinentry-mode loopback \ + --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 && + + gpgsm --homedir "${GNUPGHOME}" -K | + grep fingerprint: | + cut -d" " -f4 | + tr -d "\\n" >"${GNUPGHOME}/trustlist.txt" && + + echo " S relax" >>"${GNUPGHOME}/trustlist.txt" && + echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \ + -u committer@example.com -o /dev/null --sign - +' -if test_have_prereq GPG && - echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null 2>&1 -then - test_set_prereq RFC1991 -fi +test_lazy_prereq RFC1991 ' + test_have_prereq GPG && + echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null +' sanitize_pgp() { perl -ne ' diff --git a/t/lib-log-graph.sh b/t/lib-log-graph.sh index 1184cceef2..1184cceef2 100755..100644 --- a/t/lib-log-graph.sh +++ b/t/lib-log-graph.sh diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 64fc6487dd..64fc6487dd 100755..100644 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index 7743f4f4c9..80c53edca7 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -31,10 +31,6 @@ test_perf 'simulated fetch' ' } | git pack-objects --revs --stdout >/dev/null ' -test_perf 'pack to file' ' - git pack-objects --all pack1 </dev/null >/dev/null -' - test_perf 'pack to file (bitmap)' ' git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null ' diff --git a/t/perf/p9300-fast-import-export.sh b/t/perf/p9300-fast-import-export.sh new file mode 100755 index 0000000000..586161e9ad --- /dev/null +++ b/t/perf/p9300-fast-import-export.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='test fast-import and fast-export performance' +. ./perf-lib.sh + +test_perf_default_repo + +# Use --no-data here to produce a vastly smaller export file. +# This is much cheaper to work with but should still exercise +# fast-import pretty well (we'll still process all commits and +# trees, which account for 60% or more of objects in most repos). +# +# Use --reencode to avoid the default of aborting on non-utf8 commits, +# which lets this test run against a wider variety of sample repos. +test_perf 'export (no-blobs)' ' + git fast-export --reencode=yes --no-data HEAD >export +' + +test_perf 'import (no-blobs)' ' + git fast-import --force <export +' + +test_done diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 3e440c078d..b859721620 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -833,6 +833,19 @@ then exit 1 fi +test_expect_success 'lazy prereqs do not turn off tracing' " + run_sub_test_lib_test lazy-prereq-and-tracing \ + 'lazy prereqs and -x' -v -x <<-\\EOF && + test_lazy_prereq LAZY true + + test_expect_success lazy 'test_have_prereq LAZY && echo trace' + + test_done + EOF + + grep 'echo trace' lazy-prereq-and-tracing/err +" + test_expect_success 'tests clean up even on failures' " run_sub_test_lib_test_err \ failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF && diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 26f8206326..1edd5aeb8f 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -392,13 +392,6 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' ' test_path_is_dir realgitdir/refs ' -# Tests for the hidden file attribute on windows -is_hidden () { - # Use the output of `attrib`, ignore the absolute path - case "$(attrib "$1")" in *H*?:*) return 0;; esac - return 1 -} - test_expect_success MINGW '.git hidden' ' rm -rf newdir && ( @@ -406,7 +399,7 @@ test_expect_success MINGW '.git hidden' ' mkdir newdir && cd newdir && git init && - is_hidden .git + test_path_is_hidden .git ) && check_config newdir/.git false unset ' diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh index 1f600e2cae..88b9ae8158 100755 --- a/t/t0007-git-var.sh +++ b/t/t0007-git-var.sh @@ -17,7 +17,7 @@ test_expect_success 'get GIT_COMMITTER_IDENT' ' test_cmp expect actual ' -test_expect_success !FAIL_PREREQS,!AUTOIDENT 'requested identites are strict' ' +test_expect_success !FAIL_PREREQS,!AUTOIDENT 'requested identities are strict' ' ( sane_unset GIT_COMMITTER_NAME && sane_unset GIT_COMMITTER_EMAIL && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 3483b72db4..f8178ee4e3 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -54,7 +54,7 @@ Alias -A, --alias-source <string> get a string -Z, --alias-target <string> - get a string + alias of --alias-source EOF diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 2ea2d00c39..56db5c8aba 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -476,6 +476,7 @@ test_expect_success MINGW 'is_valid_path() on Windows' ' C:\\git \ comm \ conout.c \ + com0.c \ lptN \ \ --not \ @@ -488,6 +489,7 @@ test_expect_success MINGW 'is_valid_path() on Windows' ' "AUX.c" \ "abc/conOut\$ .xyz/test" \ lpt8 \ + com9.c \ "lpt*" \ Nul \ "PRN./abc" diff --git a/t/t0064-sha1-array.sh b/t/t0064-sha1-array.sh index 5dda570b9a..45685af2fd 100755 --- a/t/t0064-sha1-array.sh +++ b/t/t0064-sha1-array.sh @@ -18,7 +18,7 @@ test_expect_success 'ordered enumeration' ' { echoid append 88 44 aa 55 && echo for_each_unique - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && test_cmp expect actual ' @@ -28,7 +28,7 @@ test_expect_success 'ordered enumeration with duplicate suppression' ' echoid append 88 44 aa 55 && echoid append 88 44 aa 55 && echo for_each_unique - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && test_cmp expect actual ' @@ -36,7 +36,7 @@ test_expect_success 'lookup' ' { echoid append 88 44 aa 55 && echoid lookup 55 - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && n=$(cat actual) && test "$n" -eq 1 ' @@ -45,7 +45,7 @@ test_expect_success 'lookup non-existing entry' ' { echoid append 88 44 aa 55 && echoid lookup 33 - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && n=$(cat actual) && test "$n" -lt 0 ' @@ -55,7 +55,7 @@ test_expect_success 'lookup with duplicates' ' echoid append 88 44 aa 55 && echoid append 88 44 aa 55 && echoid lookup 55 - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && n=$(cat actual) && test "$n" -ge 2 && test "$n" -le 3 @@ -66,7 +66,7 @@ test_expect_success 'lookup non-existing entry with duplicates' ' echoid append 88 44 aa 55 && echoid append 88 44 aa 55 && echoid lookup 66 - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && n=$(cat actual) && test "$n" -lt 0 ' @@ -81,7 +81,7 @@ test_expect_success 'lookup with almost duplicate values' ' echo "append $id1" && echo "append $id2" && echoid lookup 55 - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && n=$(cat actual) && test "$n" -eq 0 ' @@ -90,7 +90,7 @@ test_expect_success 'lookup with single duplicate value' ' { echoid append 55 55 && echoid lookup 55 - } | test-tool sha1-array >actual && + } | test-tool oid-array >actual && n=$(cat actual) && test "$n" -ge 0 && test "$n" -le 1 diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh index 7065a1b937..1529155cf0 100755 --- a/t/t0212-trace2-event.sh +++ b/t/t0212-trace2-event.sh @@ -199,6 +199,43 @@ test_expect_success JSON_PP 'event stream, list config' ' test_cmp expect actual ' +# Test listing of all "interesting" environment variables. + +test_expect_success JSON_PP 'event stream, list env vars' ' + test_when_finished "rm trace.event actual expect" && + GIT_TRACE2_EVENT="$(pwd)/trace.event" \ + GIT_TRACE2_ENV_VARS="A_VAR,OTHER_VAR,MISSING" \ + A_VAR=1 OTHER_VAR="hello world" test-tool trace2 001return 0 && + perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual && + sed -e "s/^|//" >expect <<-EOF && + |VAR1 = { + | "_SID0_":{ + | "argv":[ + | "_EXE_", + | "trace2", + | "001return", + | "0" + | ], + | "exit_code":0, + | "hierarchy":"trace2", + | "name":"trace2", + | "params":[ + | { + | "param":"A_VAR", + | "value":"1" + | }, + | { + | "param":"OTHER_VAR", + | "value":"hello world" + | } + | ], + | "version":"$V" + | } + |}; + EOF + test_cmp expect actual +' + test_expect_success JSON_PP 'basic trace2_data' ' test_when_finished "rm trace.event actual expect" && GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool trace2 006data test_category k1 v1 test_category k2 v2 && diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 39f097ea9e..48484cbcf6 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -22,6 +22,11 @@ test_expect_success 'setup helper scripts' ' exit 0 EOF + write_script git-credential-quit <<-\EOF && + . ./dump + echo quit=1 + EOF + write_script git-credential-verbatim <<-\EOF && user=$1; shift pass=$1; shift @@ -35,43 +40,71 @@ test_expect_success 'setup helper scripts' ' test_expect_success 'credential_fill invokes helper' ' check fill "verbatim foo bar" <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=foo password=bar -- verbatim: get + verbatim: protocol=http + verbatim: host=example.com EOF ' test_expect_success 'credential_fill invokes multiple helpers' ' check fill useless "verbatim foo bar" <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=foo password=bar -- useless: get + useless: protocol=http + useless: host=example.com verbatim: get + verbatim: protocol=http + verbatim: host=example.com EOF ' test_expect_success 'credential_fill stops when we get a full response' ' check fill "verbatim one two" "verbatim three four" <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=one password=two -- verbatim: get + verbatim: protocol=http + verbatim: host=example.com EOF ' test_expect_success 'credential_fill continues through partial response' ' check fill "verbatim one \"\"" "verbatim two three" <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=two password=three -- verbatim: get + verbatim: protocol=http + verbatim: host=example.com verbatim: get + verbatim: protocol=http + verbatim: host=example.com verbatim: username=one EOF ' @@ -97,14 +130,20 @@ test_expect_success 'credential_fill passes along metadata' ' test_expect_success 'credential_approve calls all helpers' ' check approve useless "verbatim one two" <<-\EOF + protocol=http + host=example.com username=foo password=bar -- -- useless: store + useless: protocol=http + useless: host=example.com useless: username=foo useless: password=bar verbatim: store + verbatim: protocol=http + verbatim: host=example.com verbatim: username=foo verbatim: password=bar EOF @@ -112,6 +151,8 @@ test_expect_success 'credential_approve calls all helpers' ' test_expect_success 'do not bother storing password-less credential' ' check approve useless <<-\EOF + protocol=http + host=example.com username=foo -- -- @@ -121,14 +162,20 @@ test_expect_success 'do not bother storing password-less credential' ' test_expect_success 'credential_reject calls all helpers' ' check reject useless "verbatim one two" <<-\EOF + protocol=http + host=example.com username=foo password=bar -- -- useless: erase + useless: protocol=http + useless: host=example.com useless: username=foo useless: password=bar verbatim: erase + verbatim: protocol=http + verbatim: host=example.com verbatim: username=foo verbatim: password=bar EOF @@ -136,33 +183,49 @@ test_expect_success 'credential_reject calls all helpers' ' test_expect_success 'usernames can be preserved' ' check fill "verbatim \"\" three" <<-\EOF + protocol=http + host=example.com username=one -- + protocol=http + host=example.com username=one password=three -- verbatim: get + verbatim: protocol=http + verbatim: host=example.com verbatim: username=one EOF ' test_expect_success 'usernames can be overridden' ' check fill "verbatim two three" <<-\EOF + protocol=http + host=example.com username=one -- + protocol=http + host=example.com username=two password=three -- verbatim: get + verbatim: protocol=http + verbatim: host=example.com verbatim: username=one EOF ' test_expect_success 'do not bother completing already-full credential' ' check fill "verbatim three four" <<-\EOF + protocol=http + host=example.com username=one password=two -- + protocol=http + host=example.com username=one password=two -- @@ -174,23 +237,31 @@ test_expect_success 'do not bother completing already-full credential' ' # askpass helper is run, we know the internal getpass is working. test_expect_success 'empty helper list falls back to internal getpass' ' check fill <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=askpass-username password=askpass-password -- - askpass: Username: - askpass: Password: + askpass: Username for '\''http://example.com'\'': + askpass: Password for '\''http://askpass-username@example.com'\'': EOF ' test_expect_success 'internal getpass does not ask for known username' ' check fill <<-\EOF + protocol=http + host=example.com username=foo -- + protocol=http + host=example.com username=foo password=askpass-password -- - askpass: Password: + askpass: Password for '\''http://foo@example.com'\'': EOF ' @@ -202,7 +273,11 @@ HELPER="!f() { test_expect_success 'respect configured credentials' ' test_config credential.helper "$HELPER" && check fill <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=foo password=bar -- @@ -419,21 +494,119 @@ test_expect_success 'context uses urlmatch' ' test_expect_success 'helpers can abort the process' ' test_must_fail git \ - -c credential.helper="!f() { echo quit=1; }; f" \ + -c credential.helper=quit \ -c credential.helper="verbatim foo bar" \ - credential fill >stdout && - test_must_be_empty stdout + credential fill >stdout 2>stderr <<-\EOF && + protocol=http + host=example.com + EOF + test_must_be_empty stdout && + cat >expect <<-\EOF && + quit: get + quit: protocol=http + quit: host=example.com + fatal: credential helper '\''quit'\'' told us to quit + EOF + test_i18ncmp expect stderr ' test_expect_success 'empty helper spec resets helper list' ' test_config credential.helper "verbatim file file" && check fill "" "verbatim cmdline cmdline" <<-\EOF + protocol=http + host=example.com -- + protocol=http + host=example.com username=cmdline password=cmdline -- verbatim: get + verbatim: protocol=http + verbatim: host=example.com + EOF +' + +test_expect_success 'url parser rejects embedded newlines' ' + test_must_fail git credential fill 2>stderr <<-\EOF && + url=https://one.example.com?%0ahost=two.example.com/ + EOF + cat >expect <<-\EOF && + warning: url contains a newline in its path component: https://one.example.com?%0ahost=two.example.com/ + fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/ + EOF + test_i18ncmp expect stderr +' + +test_expect_success 'host-less URLs are parsed as empty host' ' + check fill "verbatim foo bar" <<-\EOF + url=cert:///path/to/cert.pem + -- + protocol=cert + host= + path=path/to/cert.pem + username=foo + password=bar + -- + verbatim: get + verbatim: protocol=cert + verbatim: host= + verbatim: path=path/to/cert.pem + EOF +' + +test_expect_success 'credential system refuses to work with missing host' ' + test_must_fail git credential fill 2>stderr <<-\EOF && + protocol=http + EOF + cat >expect <<-\EOF && + fatal: refusing to work with credential missing host field + EOF + test_i18ncmp expect stderr +' + +test_expect_success 'credential system refuses to work with missing protocol' ' + test_must_fail git credential fill 2>stderr <<-\EOF && + host=example.com + EOF + cat >expect <<-\EOF && + fatal: refusing to work with credential missing protocol field + EOF + test_i18ncmp expect stderr +' + +# usage: check_host_and_path <url> <expected-host> <expected-path> +check_host_and_path () { + # we always parse the path component, but we need this to make sure it + # is passed to the helper + test_config credential.useHTTPPath true && + check fill "verbatim user pass" <<-EOF + url=$1 + -- + protocol=https + host=$2 + path=$3 + username=user + password=pass + -- + verbatim: get + verbatim: protocol=https + verbatim: host=$2 + verbatim: path=$3 EOF +} + +test_expect_success 'url parser handles bare query marker' ' + check_host_and_path https://example.com?foo.git example.com ?foo.git +' + +test_expect_success 'url parser handles bare fragment marker' ' + check_host_and_path https://example.com#foo.git example.com "#foo.git" +' + +test_expect_success 'url parser not confused by encoded markers' ' + check_host_and_path https://example.com%23%3f%2f/foo.git \ + "example.com#?/" foo.git ' test_done diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh index 69ffe865b4..52585ec2aa 100755 --- a/t/t2402-worktree-list.sh +++ b/t/t2402-worktree-list.sh @@ -152,7 +152,7 @@ test_expect_success 'linked worktrees are sorted' ' ' test_expect_success 'worktree path when called in .git directory' ' - git worktree list >list1&& + git worktree list >list1 && git -C .git worktree list >list2 && test_cmp list1 list2 ' diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index a1ec501a87..6e032716a6 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -162,4 +162,81 @@ test_expect_success 'rebase --skip works with two conflicts in a row' ' git rebase --skip ' +test_expect_success '--reapply-cherry-picks' ' + git init repo && + + # O(1-10) -- O(1-11) -- O(0-10) master + # \ + # -- O(1-11) -- O(1-12) otherbranch + + printf "Line %d\n" $(test_seq 1 10) >repo/file.txt && + git -C repo add file.txt && + git -C repo commit -m "base commit" && + + printf "Line %d\n" $(test_seq 1 11) >repo/file.txt && + git -C repo commit -a -m "add 11" && + + printf "Line %d\n" $(test_seq 0 10) >repo/file.txt && + git -C repo commit -a -m "add 0 delete 11" && + + git -C repo checkout -b otherbranch HEAD^^ && + printf "Line %d\n" $(test_seq 1 11) >repo/file.txt && + git -C repo commit -a -m "add 11 in another branch" && + + printf "Line %d\n" $(test_seq 1 12) >repo/file.txt && + git -C repo commit -a -m "add 12 in another branch" && + + # Regular rebase fails, because the 1-11 commit is deduplicated + test_must_fail git -C repo rebase --merge master 2> err && + test_i18ngrep "error: could not apply.*add 12 in another branch" err && + git -C repo rebase --abort && + + # With --reapply-cherry-picks, it works + git -C repo rebase --merge --reapply-cherry-picks master +' + +test_expect_success '--reapply-cherry-picks refrains from reading unneeded blobs' ' + git init server && + + # O(1-10) -- O(1-11) -- O(1-12) master + # \ + # -- O(0-10) otherbranch + + printf "Line %d\n" $(test_seq 1 10) >server/file.txt && + git -C server add file.txt && + git -C server commit -m "merge base" && + + printf "Line %d\n" $(test_seq 1 11) >server/file.txt && + git -C server commit -a -m "add 11" && + + printf "Line %d\n" $(test_seq 1 12) >server/file.txt && + git -C server commit -a -m "add 12" && + + git -C server checkout -b otherbranch HEAD^^ && + printf "Line %d\n" $(test_seq 0 10) >server/file.txt && + git -C server commit -a -m "add 0" && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + + git clone --filter=blob:none "file://$(pwd)/server" client && + git -C client checkout origin/master && + git -C client checkout origin/otherbranch && + + # Sanity check to ensure that the blobs from the merge base and "add + # 11" are missing + git -C client rev-list --objects --all --missing=print >missing_list && + MERGE_BASE_BLOB=$(git -C server rev-parse master^^:file.txt) && + ADD_11_BLOB=$(git -C server rev-parse master^:file.txt) && + grep "[?]$MERGE_BASE_BLOB" missing_list && + grep "[?]$ADD_11_BLOB" missing_list && + + git -C client rebase --merge --reapply-cherry-picks origin/master && + + # The blob from the merge base had to be fetched, but not "add 11" + git -C client rev-list --objects --all --missing=print >missing_list && + ! grep "[?]$MERGE_BASE_BLOB" missing_list && + grep "[?]$ADD_11_BLOB" missing_list +' + test_done diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index 61b76f3301..927a4f4a4e 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -89,22 +89,22 @@ test_expect_success 'GIT_REFLOG_ACTION' ' git checkout -b reflog-topic start && test_commit reflog-to-rebase && - git rebase --apply reflog-onto && + git rebase reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && - rebase finished: returning to refs/heads/reflog-topic - rebase: reflog-to-rebase - rebase: checkout reflog-onto + rebase (finish): returning to refs/heads/reflog-topic + rebase (pick): reflog-to-rebase + rebase (start): checkout reflog-onto EOF test_cmp expect actual && git checkout -b reflog-prefix reflog-to-rebase && - GIT_REFLOG_ACTION=change-the-reflog git rebase --apply reflog-onto && + GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && - rebase finished: returning to refs/heads/reflog-prefix - change-the-reflog: reflog-to-rebase - change-the-reflog: checkout reflog-onto + change-the-reflog (finish): returning to refs/heads/reflog-prefix + change-the-reflog (pick): reflog-to-rebase + change-the-reflog (start): checkout reflog-onto EOF test_cmp expect actual ' diff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh index e85cdc7037..946e92f8da 100755 --- a/t/t3417-rebase-whitespace-fix.sh +++ b/t/t3417-rebase-whitespace-fix.sh @@ -52,7 +52,7 @@ test_expect_success 'blank line at end of file; extend at end of file' ' git commit --allow-empty -m "Initial empty commit" && git add file && git commit -m first && mv second file && - git add file && git commit -m second && + git add file && git commit -m second && git rebase --whitespace=fix HEAD^^ && git diff --exit-code HEAD^:file expect-first && test_cmp expect-second file @@ -118,7 +118,7 @@ test_expect_success 'at beginning of file' ' for i in 1 2 3 4 5; do echo $i done >> file && - git commit -m more file && + git commit -m more file && git rebase --whitespace=fix HEAD^^ && test_cmp expect-beginning file ' diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index d934583776..1f32faa4a4 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -4,15 +4,6 @@ test_description='git rebase - test patch id computation' . ./test-lib.sh -count () { - i=0 - while test $i -lt $1 - do - echo "$i" - i=$(($i+1)) - done -} - scramble () { i=0 while read x @@ -26,76 +17,55 @@ scramble () { mv -f "$1.new" "$1" } -run () { - echo \$ "$@" - /usr/bin/time "$@" >/dev/null -} - test_expect_success 'setup' ' git commit --allow-empty -m initial && git tag root ' -do_tests () { - nlines=$1 pr=${2-} - - test_expect_success $pr "setup: $nlines lines" " - rm -f .gitattributes && - git checkout -q -f master && - git reset --hard root && - count $nlines >file && - git add file && - git commit -q -m initial && - git branch -f other && - - scramble file && - git add file && - git commit -q -m 'change big file' && - - git checkout -q other && - : >newfile && - git add newfile && - git commit -q -m 'add small file' && - - git cherry-pick master >/dev/null 2>&1 - " - - test_debug " - run git diff master^\! - " - - test_expect_success $pr 'setup attributes' " - echo 'file binary' >.gitattributes - " - - test_debug " - run git format-patch --stdout master && - run git format-patch --stdout --ignore-if-in-upstream master - " +test_expect_success 'setup: 500 lines' ' + rm -f .gitattributes && + git checkout -q -f master && + git reset --hard root && + test_seq 500 >file && + git add file && + git commit -q -m initial && + git branch -f other && + + scramble file && + git add file && + git commit -q -m "change big file" && + + git checkout -q other && + : >newfile && + git add newfile && + git commit -q -m "add small file" && + + git cherry-pick master >/dev/null 2>&1 +' - test_expect_success $pr 'detect upstream patch' ' - git checkout -q master && - scramble file && - git add file && - git commit -q -m "change big file again" && - git checkout -q other^{} && - git rebase master && - git rev-list master...HEAD~ >revs && - test_must_be_empty revs - ' +test_expect_success 'setup attributes' ' + echo "file binary" >.gitattributes +' - test_expect_success $pr 'do not drop patch' ' - git branch -f squashed master && - git checkout -q -f squashed && - git reset -q --soft HEAD~2 && - git commit -q -m squashed && - git checkout -q other^{} && - test_must_fail git rebase squashed && - git rebase --quit - ' -} +test_expect_success 'detect upstream patch' ' + git checkout -q master && + scramble file && + git add file && + git commit -q -m "change big file again" && + git checkout -q other^{} && + git rebase master && + git rev-list master...HEAD~ >revs && + test_must_be_empty revs +' -do_tests 500 -do_tests 50000 EXPENSIVE +test_expect_success 'do not drop patch' ' + git branch -f squashed master && + git checkout -q -f squashed && + git reset -q --soft HEAD~2 && + git commit -q -m squashed && + git checkout -q other^{} && + test_must_fail git rebase squashed && + git rebase --quit +' test_done diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index cf8dfd6c20..4a9204b4b6 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -220,14 +220,13 @@ test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 shift - test_expect_$result "rebase $* --keep-empty" " + test_expect_$result "rebase $* --no-keep-empty drops begin-empty commits" " reset_rebase && - git rebase $* --keep-empty c l && - test_cmp_rev c HEAD~3 && - test_linear_range 'd k l' c.. + git rebase $* --no-keep-empty c l && + test_cmp_rev c HEAD~2 && + test_linear_range 'd l' c.. " } -test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p @@ -242,7 +241,6 @@ test_run_rebase () { test_linear_range 'd k l' j.. " } -test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh index e1e30517ea..5e1045a0af 100755 --- a/t/t3424-rebase-empty.sh +++ b/t/t3424-rebase-empty.sh @@ -123,6 +123,42 @@ test_expect_success 'rebase --interactive uses default of --empty=ask' ' test_cmp expect actual ' +test_expect_success 'rebase --merge --empty=drop --keep-empty' ' + git checkout -B testing localmods && + git rebase --merge --empty=drop --keep-empty upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=drop --no-keep-empty' ' + git checkout -B testing localmods && + git rebase --merge --empty=drop --no-keep-empty upstream && + + test_write_lines C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=keep --keep-empty' ' + git checkout -B testing localmods && + git rebase --merge --empty=keep --keep-empty upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=keep --no-keep-empty' ' + git checkout -B testing localmods && + git rebase --merge --empty=keep --no-keep-empty upstream && + + test_write_lines C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + test_expect_success 'rebase --merge does not leave state laying around' ' git checkout -B testing localmods~2 && git rebase --merge upstream && diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 6c9d4a1375..6f0452c0ea 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -28,10 +28,12 @@ test_rebase_same_head () { shift && cmp_f="$1" && shift && - test_rebase_same_head_ $status_n $what_n $cmp_n " --apply" "$*" && - test_rebase_same_head_ $status_f $what_f $cmp_f " --apply --no-ff" "$*" - test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" && - test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n 0 " --apply" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f 0 " --apply --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n 0 " --merge" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f 0 " --merge --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n 1 " --merge" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f 1 " --merge --no-ff" "$*" } test_rebase_same_head_ () { @@ -41,9 +43,21 @@ test_rebase_same_head_ () { shift && cmp="$1" && shift && + abbreviate="$1" && + shift && flag="$1" shift && - test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" " + if test $abbreviate -eq 1 + then + msg="git rebase$flag $* (rebase.abbreviateCommands = true) with $changes is $what with $cmp HEAD" + else + msg="git rebase$flag $* with $changes is $what with $cmp HEAD" + fi && + test_expect_$status "$msg" " + if test $abbreviate -eq 1 + then + test_config rebase.abbreviateCommands true + fi && oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && cp .git/logs/HEAD expect && diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh new file mode 100755 index 0000000000..b47c59c190 --- /dev/null +++ b/t/t3435-rebase-gpg-sign.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# +# Copyright (c) 2020 Doan Tran Cong Danh +# + +test_description='test rebase --[no-]gpg-sign' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-rebase.sh" +. "$TEST_DIRECTORY/lib-gpg.sh" + +if ! test_have_prereq GPG +then + skip_all='skip all test rebase --[no-]gpg-sign, gpg not available' + test_done +fi + +test_rebase_gpg_sign () { + local must_fail= will=will fake_editor= + if test "x$1" = "x!" + then + must_fail=test_must_fail + will="won't" + shift + fi + conf=$1 + shift + test_expect_success "rebase $* with commit.gpgsign=$conf $will sign commit" " + git reset two && + git config commit.gpgsign $conf && + set_fake_editor && + FAKE_LINES='r 1 p 2' git rebase --force-rebase --root $* && + $must_fail git verify-commit HEAD^ && + $must_fail git verify-commit HEAD + " +} + +test_expect_success 'setup' ' + test_commit one && + test_commit two && + test_must_fail git verify-commit HEAD && + test_must_fail git verify-commit HEAD^ +' + +test_expect_success 'setup: merge commit' ' + test_commit fork-point && + git switch -c side && + test_commit three && + git switch master && + git merge --no-ff side && + git tag merged +' + +test_rebase_gpg_sign ! false +test_rebase_gpg_sign true +test_rebase_gpg_sign ! true --no-gpg-sign +test_rebase_gpg_sign ! true --gpg-sign --no-gpg-sign +test_rebase_gpg_sign false --no-gpg-sign --gpg-sign +test_rebase_gpg_sign true -i +test_rebase_gpg_sign ! true -i --no-gpg-sign +test_rebase_gpg_sign ! true -i --gpg-sign --no-gpg-sign +test_rebase_gpg_sign false -i --no-gpg-sign --gpg-sign + +test_expect_failure 'rebase -p --no-gpg-sign override commit.gpgsign' ' + git reset --hard merged && + git config commit.gpgsign true && + git rebase -p --no-gpg-sign --onto=one fork-point master && + test_must_fail git verify-commit HEAD +' + +test_done diff --git a/t/t3514-cherry-pick-revert-gpg.sh b/t/t3514-cherry-pick-revert-gpg.sh new file mode 100755 index 0000000000..5b2e250eaa --- /dev/null +++ b/t/t3514-cherry-pick-revert-gpg.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# +# Copyright (c) 2020 Doan Tran Cong Danh +# + +test_description='test {cherry-pick,revert} --[no-]gpg-sign' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +if ! test_have_prereq GPG +then + skip_all='skip all test {cherry-pick,revert} --[no-]gpg-sign, gpg not available' + test_done +fi + +test_gpg_sign () { + local must_fail= will=will fake_editor= + if test "x$1" = "x!" + then + must_fail=test_must_fail + will="won't" + shift + fi + conf=$1 + cmd=$2 + cmit=$3 + shift 3 + test_expect_success "$cmd $* $cmit with commit.gpgsign=$conf $will sign commit" " + git reset --hard tip && + git config commit.gpgsign $conf && + git $cmd $* $cmit && + git rev-list tip.. >rev-list && + $must_fail git verify-commit \$(cat rev-list) + " +} + +test_expect_success 'setup' ' + test_commit one && + git switch -c side && + test_commit side1 && + test_commit side2 && + git switch - && + test_commit two && + test_commit three && + test_commit tip +' + +test_gpg_sign ! false cherry-pick side +test_gpg_sign ! false cherry-pick ..side +test_gpg_sign true cherry-pick side +test_gpg_sign true cherry-pick ..side +test_gpg_sign ! true cherry-pick side --no-gpg-sign +test_gpg_sign ! true cherry-pick ..side --no-gpg-sign +test_gpg_sign ! true cherry-pick side --gpg-sign --no-gpg-sign +test_gpg_sign ! true cherry-pick ..side --gpg-sign --no-gpg-sign +test_gpg_sign false cherry-pick side --no-gpg-sign --gpg-sign +test_gpg_sign false cherry-pick ..side --no-gpg-sign --gpg-sign +test_gpg_sign true cherry-pick side --edit +test_gpg_sign true cherry-pick ..side --edit +test_gpg_sign ! true cherry-pick side --edit --no-gpg-sign +test_gpg_sign ! true cherry-pick ..side --edit --no-gpg-sign +test_gpg_sign ! true cherry-pick side --edit --gpg-sign --no-gpg-sign +test_gpg_sign ! true cherry-pick ..side --edit --gpg-sign --no-gpg-sign +test_gpg_sign false cherry-pick side --edit --no-gpg-sign --gpg-sign +test_gpg_sign false cherry-pick ..side --edit --no-gpg-sign --gpg-sign + +test_gpg_sign ! false revert HEAD --edit +test_gpg_sign ! false revert two.. --edit +test_gpg_sign true revert HEAD --edit +test_gpg_sign true revert two.. --edit +test_gpg_sign ! true revert HEAD --edit --no-gpg-sign +test_gpg_sign ! true revert two.. --edit --no-gpg-sign +test_gpg_sign ! true revert HEAD --edit --gpg-sign --no-gpg-sign +test_gpg_sign ! true revert two.. --edit --gpg-sign --no-gpg-sign +test_gpg_sign false revert HEAD --edit --no-gpg-sign --gpg-sign +test_gpg_sign false revert two.. --edit --no-gpg-sign --gpg-sign +test_gpg_sign true revert HEAD --no-edit +test_gpg_sign true revert two.. --no-edit +test_gpg_sign ! true revert HEAD --no-edit --no-gpg-sign +test_gpg_sign ! true revert two.. --no-edit --no-gpg-sign +test_gpg_sign ! true revert HEAD --no-edit --gpg-sign --no-gpg-sign +test_gpg_sign ! true revert two.. --no-edit --gpg-sign --no-gpg-sign +test_gpg_sign false revert HEAD --no-edit --no-gpg-sign --gpg-sign + +test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 88bc799807..b7d4ba608c 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -192,7 +192,7 @@ test_expect_success 'git add --refresh with pathspec' ' test_must_be_empty actual && git diff-files --name-only >actual && - ! grep bar actual&& + ! grep bar actual && grep baz actual ' diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 5bae6e50f1..b3d8bb7577 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -780,7 +780,7 @@ test_expect_success 'add -p patch editing works with pathological context lines' test_expect_success 'checkout -p works with pathological context lines' ' test_write_lines a a a a a a >a && git add a && - test_write_lines a b a b a b a b a b a > a&& + test_write_lines a b a b a b a b a b a >a && test_write_lines s n n y q | git checkout -p && test_write_lines a b a b a a b a b a >expect && test_cmp expect a diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index b653dd7d44..db7e733af9 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1161,6 +1161,59 @@ test_expect_success 'format-patch wraps extremely long from-header (rfc2047)' ' ' cat >expect <<'EOF' +From: Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar + Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo + Bar Foo Bar Foo Bar Foo Bar <author@example.com> +EOF +test_expect_success 'format-patch wraps extremely long from-header (non-ASCII without Q-encoding)' ' + echo content >>file && + git add file && + GIT_AUTHOR_NAME="Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar" \ + git commit -m author-check && + git format-patch --no-encode-email-headers --stdout -1 >patch && + sed -n "/^From: /p; /^ /p; /^$/q" patch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Subject: [PATCH] Foö +EOF +test_expect_success 'subject lines are unencoded with --no-encode-email-headers' ' + echo content >>file && + git add file && + git commit -m "Foö" && + git format-patch --no-encode-email-headers -1 --stdout >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Subject: [PATCH] Foö +EOF +test_expect_success 'subject lines are unencoded with format.encodeEmailHeaders=false' ' + echo content >>file && + git add file && + git commit -m "Foö" && + git config format.encodeEmailHeaders false && + git format-patch -1 --stdout >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Subject: [PATCH] =?UTF-8?q?Fo=C3=B6?= +EOF +test_expect_success '--encode-email-headers overrides format.encodeEmailHeaders' ' + echo content >>file && + git add file && + git commit -m "Foö" && + git config format.encodeEmailHeaders false && + git format-patch --encode-email-headers -1 --stdout >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' Subject: header with . in it EOF test_expect_success 'subject lines do not have 822 atom-quoting' ' diff --git a/t/t4057-diff-combined-paths.sh b/t/t4057-diff-combined-paths.sh index 4f4b541658..0b78573733 100755 --- a/t/t4057-diff-combined-paths.sh +++ b/t/t4057-diff-combined-paths.sh @@ -14,7 +14,7 @@ diffc_verify () { test_expect_success 'trivial merge - combine-diff empty' ' for i in $(test_seq 1 9) do - echo $i >$i.txt && + echo $i >$i.txt && git add $i.txt done && git commit -m "init" && diff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh index 2affd7a100..0f7a6d97a8 100755 --- a/t/t4061-diff-indent.sh +++ b/t/t4061-diff-indent.sh @@ -17,7 +17,7 @@ compare_diff () { # Compare blame output using the expectation for a diff as reference. # Only look for the lines coming from non-boundary commits. compare_blame () { - sed -n -e "1,4d" -e "s/^\+//p" <"$1" >.tmp-1 + sed -n -e "1,4d" -e "s/^+//p" <"$1" >.tmp-1 sed -ne "s/^[^^][^)]*) *//p" <"$2" >.tmp-2 test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 } diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 971a5a7512..0ca29821ec 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -52,6 +52,13 @@ test_fix () { # find touched lines $DIFF file target | sed -n -e "s/^> //p" >fixed + # busybox's diff(1) doesn't output normal format + if ! test -s fixed + then + $DIFF -u file target | + grep -v '^+++ target' | + sed -ne "/^+/s/+//p" >fixed + fi # the changed lines are all expected to change fixed_cnt=$(wc -l <fixed) diff --git a/t/t4150-am.sh b/t/t4150-am.sh index cb45271457..bda4586a79 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -166,7 +166,7 @@ test_expect_success setup ' test_tick && git commit -m third && - git format-patch --stdout first >patch2 && + git format-patch --stdout first >patch2 && git checkout -b lorem && sed -n -e "11,\$p" msg >file && diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh index 4c8f3b8e1b..6cdbe4747a 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -55,6 +55,10 @@ test_expect_success '"git log -- :/a" should not be ambiguous' ' git log -- :/a ' +test_expect_success '"git log :/any/path/" should not segfault' ' + test_must_fail git log :/any/path/ +' + # This differs from the ":/a" check above in that :/in looks like a pathspec, # but doesn't match an actual file. test_expect_success '"git log :/in" should not be ambiguous' ' diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 106eddbd85..3b76d2eb65 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -7,12 +7,12 @@ test_description='git archive --format=zip test' SUBSTFORMAT=%H%n test_lazy_prereq UNZIP_SYMLINKS ' - ( - mkdir unzip-symlinks && - cd unzip-symlinks && - "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip && - test -h symlink - ) + "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip && + test -h symlink +' + +test_lazy_prereq UNZIP_CONVERT ' + "$GIT_UNZIP" -a "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip ' check_zip() { @@ -39,33 +39,33 @@ check_zip() { extracted=${dir_with_prefix}a original=a - test_expect_success UNZIP " extract ZIP archive with EOL conversion" ' + test_expect_success UNZIP_CONVERT " extract ZIP archive with EOL conversion" ' (mkdir $dir && cd $dir && "$GIT_UNZIP" -a ../$zipfile) ' - test_expect_success UNZIP " validate that text files are converted" " + test_expect_success UNZIP_CONVERT " validate that text files are converted" " test_cmp_bin $extracted/text.cr $extracted/text.crlf && test_cmp_bin $extracted/text.cr $extracted/text.lf " - test_expect_success UNZIP " validate that binary files are unchanged" " + test_expect_success UNZIP_CONVERT " validate that binary files are unchanged" " test_cmp_bin $original/binary.cr $extracted/binary.cr && test_cmp_bin $original/binary.crlf $extracted/binary.crlf && test_cmp_bin $original/binary.lf $extracted/binary.lf " - test_expect_success UNZIP " validate that diff files are converted" " + test_expect_success UNZIP_CONVERT " validate that diff files are converted" " test_cmp_bin $extracted/diff.cr $extracted/diff.crlf && test_cmp_bin $extracted/diff.cr $extracted/diff.lf " - test_expect_success UNZIP " validate that -diff files are unchanged" " + test_expect_success UNZIP_CONVERT " validate that -diff files are unchanged" " test_cmp_bin $original/nodiff.cr $extracted/nodiff.cr && test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf && test_cmp_bin $original/nodiff.lf $extracted/nodiff.lf " - test_expect_success UNZIP " validate that custom diff is unchanged " " + test_expect_success UNZIP_CONVERT " validate that custom diff is unchanged " " test_cmp_bin $original/custom.cr $extracted/custom.cr && test_cmp_bin $original/custom.crlf $extracted/custom.crlf && test_cmp_bin $original/custom.lf $extracted/custom.lf diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 43a7a66c9d..22240fd30b 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -42,10 +42,15 @@ test_expect_success 'setup' ' EOF ' -test_expect_success 'write midx with no packs' ' - test_when_finished rm -f pack/multi-pack-index && - git multi-pack-index --object-dir=. write && - midx_read_expect 0 0 4 . +test_expect_success "don't write midx with no packs" ' + test_must_fail git multi-pack-index --object-dir=. write && + test_path_is_missing pack/multi-pack-index +' + +test_expect_success "Warn if a midx contains no oid" ' + cp "$TEST_DIRECTORY"/t5319/no-objects.midx $objdir/pack/multi-pack-index && + test_must_fail git multi-pack-index verify && + rm $objdir/pack/multi-pack-index ' generate_objects () { diff --git a/t/t5319/no-objects.midx b/t/t5319/no-objects.midx Binary files differnew file mode 100644 index 0000000000..e466b8e086 --- /dev/null +++ b/t/t5319/no-objects.midx diff --git a/t/t5322-pack-objects-sparse.sh b/t/t5322-pack-objects-sparse.sh index 7124b5581a..a581eaf529 100755 --- a/t/t5322-pack-objects-sparse.sh +++ b/t/t5322-pack-objects-sparse.sh @@ -105,14 +105,16 @@ test_expect_success 'non-sparse pack-objects' ' test_cmp required_objects.txt nonsparse_required_objects.txt ' +# --sparse is enabled by default by pack.useSparse test_expect_success 'sparse pack-objects' ' + GIT_TEST_PACK_SPARSE=-1 && git rev-parse \ topic1 \ topic1^{tree} \ topic1:f3 \ topic1:f3/f4 \ topic1:f3/f4/data.txt | sort >expect_sparse_objects.txt && - git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack && + git pack-objects --stdout --revs <packinput.txt >sparse.pack && git index-pack -o sparse.idx sparse.pack && git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt && test_cmp expect_sparse_objects.txt sparse_objects.txt diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 04b35402c7..e98c3a0174 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -4,6 +4,14 @@ test_description='git ls-remote' . ./test-lib.sh +generate_references () { + for ref + do + oid=$(git rev-parse "$ref") && + printf '%s\t%s\n' "$oid" "$ref" || return 1 + done +} + test_expect_success setup ' >file && git add file && @@ -13,11 +21,11 @@ test_expect_success setup ' git tag mark1.1 && git tag mark1.2 && git tag mark1.10 && - git show-ref --tags -d | sed -e "s/ / /" >expected.tag && - ( - echo "$(git rev-parse HEAD) HEAD" && - git show-ref -d | sed -e "s/ / /" - ) >expected.all && + git show-ref --tags -d >expected.tag.raw && + sed -e "s/ / /" expected.tag.raw >expected.tag && + generate_references HEAD >expected.all && + git show-ref -d >refs && + sed -e "s/ / /" refs >>expected.all && git remote add self "$(pwd)/.git" ' @@ -43,34 +51,31 @@ test_expect_success 'ls-remote self' ' ' test_expect_success 'ls-remote --sort="version:refname" --tags self' ' - cat >expect <<-EOF && - $(git rev-parse mark) refs/tags/mark - $(git rev-parse mark1.1) refs/tags/mark1.1 - $(git rev-parse mark1.2) refs/tags/mark1.2 - $(git rev-parse mark1.10) refs/tags/mark1.10 - EOF + generate_references \ + refs/tags/mark \ + refs/tags/mark1.1 \ + refs/tags/mark1.2 \ + refs/tags/mark1.10 >expect && git ls-remote --sort="version:refname" --tags self >actual && test_cmp expect actual ' test_expect_success 'ls-remote --sort="-version:refname" --tags self' ' - cat >expect <<-EOF && - $(git rev-parse mark1.10) refs/tags/mark1.10 - $(git rev-parse mark1.2) refs/tags/mark1.2 - $(git rev-parse mark1.1) refs/tags/mark1.1 - $(git rev-parse mark) refs/tags/mark - EOF + generate_references \ + refs/tags/mark1.10 \ + refs/tags/mark1.2 \ + refs/tags/mark1.1 \ + refs/tags/mark >expect && git ls-remote --sort="-version:refname" --tags self >actual && test_cmp expect actual ' test_expect_success 'ls-remote --sort="-refname" --tags self' ' - cat >expect <<-EOF && - $(git rev-parse mark1.2) refs/tags/mark1.2 - $(git rev-parse mark1.10) refs/tags/mark1.10 - $(git rev-parse mark1.1) refs/tags/mark1.1 - $(git rev-parse mark) refs/tags/mark - EOF + generate_references \ + refs/tags/mark1.2 \ + refs/tags/mark1.10 \ + refs/tags/mark1.1 \ + refs/tags/mark >expect && git ls-remote --sort="-refname" --tags self >actual && test_cmp expect actual ' @@ -92,7 +97,7 @@ test_expect_success 'use "origin" when no remote specified' ' test_expect_success 'suppress "From <url>" with -q' ' git ls-remote -q 2>actual_err && - test_must_fail test_cmp exp_err actual_err + ! test_cmp exp_err actual_err ' test_expect_success 'use branch.<name>.remote if possible' ' @@ -180,8 +185,8 @@ do test_config $configsection.hiderefs refs/tags && git ls-remote . >actual && test_unconfig $configsection.hiderefs && - git ls-remote . | - sed -e "/ refs\/tags\//d" >expect && + git ls-remote . >expect.raw && + sed -e "/ refs\/tags\//d" expect.raw >expect && test_cmp expect actual ' @@ -212,17 +217,18 @@ test_expect_success 'protocol v2 supports hiderefs' ' test_expect_success 'ls-remote --symref' ' git fetch origin && - cat >expect <<-EOF && - ref: refs/heads/master HEAD - $(git rev-parse HEAD) HEAD - $(git rev-parse refs/heads/master) refs/heads/master - $(git rev-parse HEAD) refs/remotes/origin/HEAD - $(git rev-parse refs/remotes/origin/master) refs/remotes/origin/master - $(git rev-parse refs/tags/mark) refs/tags/mark - $(git rev-parse refs/tags/mark1.1) refs/tags/mark1.1 - $(git rev-parse refs/tags/mark1.10) refs/tags/mark1.10 - $(git rev-parse refs/tags/mark1.2) refs/tags/mark1.2 - EOF + echo "ref: refs/heads/master HEAD" >expect && + generate_references \ + HEAD \ + refs/heads/master >>expect && + oid=$(git rev-parse HEAD) && + echo "$oid refs/remotes/origin/HEAD" >>expect && + generate_references \ + refs/remotes/origin/master \ + refs/tags/mark \ + refs/tags/mark1.1 \ + refs/tags/mark1.10 \ + refs/tags/mark1.2 >>expect && # Protocol v2 supports sending symrefs for refs other than HEAD, so use # protocol v0 here. GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref >actual && diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index 4f681dbbe1..b57209c84f 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -131,7 +131,7 @@ test_expect_success 'fetch that requires changes in .git/shallow is filtered' ' git init notshallow && ( cd notshallow && - git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/*&& + git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/* && git for-each-ref --format="%(refname)" >actual.refs && cat <<EOF >expect.refs && refs/remotes/shallow/no-shallow diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index b811d89cfd..50485300eb 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -248,9 +248,7 @@ test_expect_success 'fetch can handle previously-fetched .idx files' ' ' test_expect_success 'did not use upload-pack service' ' - test_might_fail grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act && - : >exp && - test_cmp exp act + ! grep "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log" ' test_expect_success 'git client shows text/plain errors' ' @@ -321,11 +319,17 @@ test_expect_success 'git client does not send an empty Accept-Language' ' ' test_expect_success 'remote-http complains cleanly about malformed urls' ' - # do not actually issue "list" or other commands, as we do not - # want to rely on what curl would actually do with such a broken - # URL. This is just about making sure we do not segfault during - # initialization. - test_must_fail git remote-http http::/example.com/repo.git + test_must_fail git remote-http http::/example.com/repo.git 2>stderr && + test_i18ngrep "url has no scheme" stderr +' + +# NEEDSWORK: Writing commands to git-remote-curl can race against the latter +# erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has +# learned to handle early remote helper failures more cleanly. +test_expect_success 'remote-http complains cleanly about empty scheme' ' + test_must_fail ok=sigpipe git ls-remote \ + http::${HTTPD_URL#http}/dumb/repo.git 2>stderr && + test_i18ngrep "url has no scheme" stderr ' test_expect_success 'redirects can be forbidden/allowed' ' diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh index 4a110b307e..3f4ac71f83 100755 --- a/t/t5562-http-backend-content-length.sh +++ b/t/t5562-http-backend-content-length.sh @@ -53,15 +53,20 @@ test_expect_success 'setup' ' test_commit c1 && hash_head=$(git rev-parse HEAD) && hash_prev=$(git rev-parse HEAD~1) && - printf "want %s" "$hash_head" | packetize >fetch_body && - printf 0000 >>fetch_body && - printf "have %s" "$hash_prev" | packetize >>fetch_body && - printf done | packetize >>fetch_body && + { + packetize "want $hash_head" && + printf 0000 && + packetize "have $hash_prev" && + packetize "done" + } >fetch_body && test_copy_bytes 10 <fetch_body >fetch_body.trunc && hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) && - printf "%s %s refs/heads/newbranch\\0report-status\\n" "$ZERO_OID" "$hash_next" | packetize >push_body && - printf 0000 >>push_body && - echo "$hash_next" | git pack-objects --stdout >>push_body && + { + printf "%s %s refs/heads/newbranch\\0report-status\\n" \ + "$ZERO_OID" "$hash_next" | packetize && + printf 0000 && + echo "$hash_next" | git pack-objects --stdout + } >push_body && test_copy_bytes 10 <push_body >push_body.trunc && : >empty_body ' diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 0c74b4e21a..2f7be23044 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -175,7 +175,7 @@ test_expect_success 'clone using repo pointed at by gitfile as reference' ' test_expect_success 'clone and dissociate from reference' ' git init P && ( - cd P && test_commit one + cd P && test_commit one ) && git clone P Q && ( diff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh index 9108ff6fbd..6d5a977fcb 100755 --- a/t/t5607-clone-bundle.sh +++ b/t/t5607-clone-bundle.sh @@ -16,7 +16,7 @@ test_expect_success 'setup' ' test_expect_success '"verify" needs a worktree' ' git bundle create tip.bundle -1 master && - test_must_fail nongit git bundle verify ../tip.bundle 2>err && + nongit test_must_fail git bundle verify ../tip.bundle 2>err && test_i18ngrep "need a repository" err ' diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh index 60c1ba951b..8e0fd39823 100755 --- a/t/t5611-clone-config.sh +++ b/t/t5611-clone-config.sh @@ -92,24 +92,17 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' ' test_cmp expect actual ' -# Tests for the hidden file attribute on windows -is_hidden () { - # Use the output of `attrib`, ignore the absolute path - case "$(attrib "$1")" in *H*?:*) return 0;; esac - return 1 -} - test_expect_success MINGW 'clone -c core.hideDotFiles' ' test_commit attributes .gitattributes "" && rm -rf child && git clone -c core.hideDotFiles=false . child && - ! is_hidden child/.gitattributes && + ! test_path_is_hidden child/.gitattributes && rm -rf child && git clone -c core.hideDotFiles=dotGitOnly . child && - ! is_hidden child/.gitattributes && + ! test_path_is_hidden child/.gitattributes && rm -rf child && git clone -c core.hideDotFiles=true . child && - is_hidden child/.gitattributes + test_path_is_hidden child/.gitattributes ' test_done diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh index e36ac01661..e3b436d8ae 100755 --- a/t/t5612-clone-refspec.sh +++ b/t/t5612-clone-refspec.sh @@ -71,9 +71,9 @@ test_expect_success 'by default all branches will be kept updated' ' ( cd dir_all && git fetch && - git for-each-ref refs/remotes/origin | + git for-each-ref refs/remotes/origin >refs && sed -e "/HEAD$/d" \ - -e "s|/remotes/origin/|/heads/|" >../actual + -e "s|/remotes/origin/|/heads/|" refs >../actual ) && # follow both master and side git for-each-ref refs/heads >expect && @@ -87,7 +87,7 @@ test_expect_success 'by default no tags will be kept updated' ' git for-each-ref refs/tags >../actual ) && git for-each-ref refs/tags >expect && - test_must_fail test_cmp expect actual && + ! test_cmp expect actual && test_line_count = 2 actual ' @@ -104,9 +104,9 @@ test_expect_success '--single-branch while HEAD pointing at master' ' ( cd dir_master && git fetch --force && - git for-each-ref refs/remotes/origin | + git for-each-ref refs/remotes/origin >refs && sed -e "/HEAD$/d" \ - -e "s|/remotes/origin/|/heads/|" >../actual + -e "s|/remotes/origin/|/heads/|" refs >../actual ) && # only follow master git for-each-ref refs/heads/master >expect && @@ -126,9 +126,9 @@ test_expect_success '--single-branch while HEAD pointing at master and --no-tags ( cd dir_master_no_tags && git fetch && - git for-each-ref refs/remotes/origin | + git for-each-ref refs/remotes/origin >refs && sed -e "/HEAD$/d" \ - -e "s|/remotes/origin/|/heads/|" >../actual + -e "s|/remotes/origin/|/heads/|" refs >../actual ) && # only follow master git for-each-ref refs/heads/master >expect && @@ -156,9 +156,9 @@ test_expect_success '--single-branch while HEAD pointing at side' ' ( cd dir_side && git fetch && - git for-each-ref refs/remotes/origin | + git for-each-ref refs/remotes/origin >refs && sed -e "/HEAD$/d" \ - -e "s|/remotes/origin/|/heads/|" >../actual + -e "s|/remotes/origin/|/heads/|" refs >../actual ) && # only follow side git for-each-ref refs/heads/side >expect && @@ -169,9 +169,9 @@ test_expect_success '--single-branch with explicit --branch side' ' ( cd dir_side2 && git fetch && - git for-each-ref refs/remotes/origin | + git for-each-ref refs/remotes/origin >refs && sed -e "/HEAD$/d" \ - -e "s|/remotes/origin/|/heads/|" >../actual + -e "s|/remotes/origin/|/heads/|" refs >../actual ) && # only follow side git for-each-ref refs/heads/side >expect && @@ -223,9 +223,9 @@ test_expect_success '--single-branch with detached' ' ( cd dir_detached && git fetch && - git for-each-ref refs/remotes/origin | + git for-each-ref refs/remotes/origin >refs && sed -e "/HEAD$/d" \ - -e "s|/remotes/origin/|/heads/|" >../actual + -e "s|/remotes/origin/|/heads/|" refs >../actual ) && # nothing test_must_be_empty actual diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 77bb91e976..88002b24af 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -49,7 +49,7 @@ test_expect_success 'do partial clone 1' ' test_expect_success 'verify that .promisor file contains refs fetched' ' ls pc1/.git/objects/pack/pack-*.promisor >promisorlist && test_line_count = 1 promisorlist && - git -C srv.bare rev-list HEAD >headhash && + git -C srv.bare rev-parse --verify HEAD >headhash && grep "$(cat headhash) HEAD" $(cat promisorlist) && grep "$(cat headhash) refs/heads/master" $(cat promisorlist) ' @@ -415,6 +415,14 @@ test_expect_success 'verify fetch downloads only one pack when updating refs' ' test_line_count = 3 pack-list ' +test_expect_success 'single-branch tag following respects partial clone' ' + git clone --single-branch -b B --filter=blob:none \ + "file://$(pwd)/srv.bare" single && + git -C single rev-parse --verify refs/tags/B && + git -C single rev-parse --verify refs/tags/A && + test_must_fail git -C single rev-parse --verify refs/tags/C +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index 7fba3063bf..a34460f7d8 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -13,10 +13,7 @@ get_actual_refs () { } get_actual_commits () { - sed -n -e '/packfile/,/0000/{ - /packfile/d - p - }' <out | test-tool pkt-line unpack-sideband >o.pack && + test-tool pkt-line unpack-sideband <out >o.pack && git index-pack o.pack && git verify-pack -v o.idx >objs && grep commit objs | cut -d" " -f1 | sort >actual_commits diff --git a/t/t5704-protocol-violations.sh b/t/t5704-protocol-violations.sh new file mode 100755 index 0000000000..950cfb21fe --- /dev/null +++ b/t/t5704-protocol-violations.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +test_description='Test responses to violations of the network protocol. In most +of these cases it will generally be acceptable for one side to break off +communications if the other side says something unexpected. We are mostly +making sure that we do not segfault or otherwise behave badly.' +. ./test-lib.sh + +test_expect_success 'extra delim packet in v2 ls-refs args' ' + { + packetize command=ls-refs && + printf 0001 && + # protocol expects 0000 flush here + printf 0001 + } >input && + test_must_fail env GIT_PROTOCOL=version=2 \ + git upload-pack . <input 2>err && + test_i18ngrep "expected flush after ls-refs arguments" err +' + +test_expect_success 'extra delim packet in v2 fetch args' ' + { + packetize command=fetch && + printf 0001 && + # protocol expects 0000 flush here + printf 0001 + } >input && + test_must_fail env GIT_PROTOCOL=version=2 \ + git upload-pack . <input 2>err && + test_i18ngrep "expected flush after fetch arguments" err +' + +test_done diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index 121e5c6edb..0f04b6cddb 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -11,9 +11,15 @@ test_description='Test remote-helper import and export commands' PATH="$TEST_DIRECTORY/t5801:$PATH" compare_refs() { + fail= && + if test "x$1" = 'x!' + then + fail='!' && + shift + fi && git --git-dir="$1/.git" rev-parse --verify $2 >expect && git --git-dir="$3/.git" rev-parse --verify $4 >actual && - test_cmp expect actual + eval $fail test_cmp expect actual } test_expect_success 'setup repository' ' @@ -189,7 +195,7 @@ test_expect_success GPG 'push signed tag' ' git push origin signed-tag ) && compare_refs local signed-tag^{} server signed-tag^{} && - test_must_fail compare_refs local signed-tag server signed-tag + compare_refs ! local signed-tag server signed-tag ' test_expect_success GPG 'push signed tag with signed-tags capability' ' diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index a10f0df02b..b6fa43ace0 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -154,4 +154,124 @@ test_expect_success '--full-diff is not affected by --parents' ' test_cmp expected actual ' +# +# Create a new history to demonstrate the value of --show-pulls +# with respect to the subtleties of simplified history, --full-history, +# and --simplify-merges. +# +# .-A---M-----C--N---O---P +# / / \ \ \/ / / +# I B \ R-'`-Z' / +# \ / \/ / +# \ / /\ / +# `---X--' `---Y--' +# +# This example is explained in Documentation/rev-list-options.txt + +test_expect_success 'rebuild repo' ' + rm -rf .git * && + git init && + git switch -c main && + + echo base >file && + git add file && + test_commit I && + + echo A >file && + git add file && + test_commit A && + + git switch -c branchB I && + echo B >file && + git add file && + test_commit B && + + git switch main && + test_must_fail git merge -m "M" B && + echo A >file && + echo B >>file && + git add file && + git merge --continue && + note M && + + echo C >other && + git add other && + test_commit C && + + git switch -c branchX I && + echo X >file && + git add file && + test_commit X && + + git switch -c branchR M && + git merge -m R -Xtheirs X && + note R && + + git switch main && + git merge -m N R && + note N && + + git switch -c branchY M && + echo Y >y && + git add y && + test_commit Y && + + git switch -c branchZ C && + echo Z >z && + git add z && + test_commit Z && + + git switch main && + git merge -m O Z && + note O && + + git merge -m P Y && + note P +' + +check_result 'X I' -- file +check_result 'N R X I' --show-pulls -- file + +check_result 'P O N R X M B A I' --full-history --topo-order -- file +check_result 'N R X M B A I' --simplify-merges --topo-order --show-pulls -- file +check_result 'R X M B A I' --simplify-merges --topo-order -- file +check_result 'N M A I' --first-parent -- file +check_result 'N M A I' --first-parent --show-pulls -- file + +# --ancestry-path implies --full-history +check_result 'P O N R M' --topo-order \ + --ancestry-path A..HEAD -- file +check_result 'P O N R M' --topo-order \ + --show-pulls \ + --ancestry-path A..HEAD -- file +check_result 'P O N R M' --topo-order \ + --full-history \ + --ancestry-path A..HEAD -- file +check_result 'R M' --topo-order \ + --simplify-merges \ + --ancestry-path A..HEAD -- file +check_result 'N R M' --topo-order \ + --simplify-merges --show-pulls \ + --ancestry-path A..HEAD -- file + +test_expect_success 'log --graph --simplify-merges --show-pulls' ' + cat >expect <<-\EOF && + * N + * R + |\ + | * X + * | M + |\ \ + | * | B + | |/ + * / A + |/ + * I + EOF + git log --graph --pretty="%s" \ + --simplify-merges --show-pulls \ + -- file >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index 190ae149cf..6738497ea7 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -18,7 +18,7 @@ GIT_FORCE_UNTRACKED_CACHE=true export GIT_FORCE_UNTRACKED_CACHE sync_mtime () { - find . -type d -ls >/dev/null + find . -type d -exec ls -ld {} + >/dev/null } avoid_racy() { diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index e3e2aab3b0..956e17abb3 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -938,7 +938,7 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano echo "repo" >expect && test_must_fail git config -f .gitmodules submodule.repo.path && git config -f .gitmodules submodule.repo_new.path >actual && - test_cmp expect actual&& + test_cmp expect actual && echo "$submodurl/repo" >expect && test_must_fail git config -f .gitmodules submodule.repo.url && echo "$submodurl/bare.git" >expect && @@ -1010,7 +1010,7 @@ test_expect_success 'submodule add with an existing name fails unless forced' ' test -d repo && echo "repo" >expect && git config -f .gitmodules submodule.repo_new.path >actual && - test_cmp expect actual&& + test_cmp expect actual && echo "$submodurl/repo.git" >expect && git config -f .gitmodules submodule.repo_new.url >actual && test_cmp expect actual && diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh index 5ba041f537..eec96e0ba9 100755 --- a/t/t7416-submodule-dash-url.sh +++ b/t/t7416-submodule-dash-url.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='check handling of .gitmodule url with dash' +test_description='check handling of disallowed .gitmodule urls' . ./test-lib.sh test_expect_success 'create submodule with protected dash in url' ' @@ -60,4 +60,145 @@ test_expect_success 'trailing backslash is handled correctly' ' test_i18ngrep ! "unknown option" err ' +test_expect_success 'fsck rejects missing URL scheme' ' + git checkout --orphan missing-scheme && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = http::one.example.com/foo.git + EOF + git add .gitmodules && + test_tick && + git commit -m "gitmodules with missing URL scheme" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck rejects relative URL resolving to missing scheme' ' + git checkout --orphan relative-missing-scheme && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = "..\\../.\\../:one.example.com/foo.git" + EOF + git add .gitmodules && + test_tick && + git commit -m "gitmodules with relative URL that strips off scheme" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck rejects empty URL scheme' ' + git checkout --orphan empty-scheme && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = http::://one.example.com/foo.git + EOF + git add .gitmodules && + test_tick && + git commit -m "gitmodules with empty URL scheme" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck rejects relative URL resolving to empty scheme' ' + git checkout --orphan relative-empty-scheme && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = ../../../:://one.example.com/foo.git + EOF + git add .gitmodules && + test_tick && + git commit -m "relative gitmodules URL resolving to empty scheme" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck rejects empty hostname' ' + git checkout --orphan empty-host && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = http:///one.example.com/foo.git + EOF + git add .gitmodules && + test_tick && + git commit -m "gitmodules with extra slashes" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck rejects relative url that produced empty hostname' ' + git checkout --orphan messy-relative && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = ../../..//one.example.com/foo.git + EOF + git add .gitmodules && + test_tick && + git commit -m "gitmodules abusing relative_path" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck permits embedded newline with unrecognized scheme' ' + git checkout --orphan newscheme && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = "data://acjbkd%0akajfdickajkd" + EOF + git add .gitmodules && + git commit -m "gitmodules with unrecognized scheme" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + git push dst HEAD +' + +test_expect_success 'fsck rejects embedded newline in url' ' + # create an orphan branch to avoid existing .gitmodules objects + git checkout --orphan newline && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = "https://one.example.com?%0ahost=two.example.com/foo.git" + EOF + git add .gitmodules && + git commit -m "gitmodules with newline" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + +test_expect_success 'fsck rejects embedded newline in relative url' ' + git checkout --orphan relative-newline && + cat >.gitmodules <<-\EOF && + [submodule "foo"] + url = "./%0ahost=two.example.com/foo.git" + EOF + git add .gitmodules && + git commit -m "relative url with newline" && + test_when_finished "rm -rf dst" && + git init --bare dst && + git -C dst config transfer.fsckObjects true && + test_must_fail git push dst HEAD 2>err && + grep gitmodulesUrl err +' + test_done diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh index e90413204e..5c8894d94f 100755 --- a/t/t7609-merge-co-error-msgs.sh +++ b/t/t7609-merge-co-error-msgs.sh @@ -126,7 +126,7 @@ test_expect_success 'not_uptodate_dir porcelain checkout error' ' git rm rep2 -r && >rep && >rep2 && - git add rep rep2&& + git add rep rep2 && git commit -m "added test as a file" && git checkout master && >rep/untracked-file && diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index c90fdc5c89..83f8f5cacb 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -486,7 +486,7 @@ test_expect_success 'NUL in property value' ' { properties \ unimportant "something with a NUL (Q)" \ - svn:log "commit message"&& + svn:log "commit message" && echo PROPS-END } | q_to_nul >props && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index cc8d463e01..267ddc997d 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -53,7 +53,7 @@ test_expect_success \ test_expect_success \ 'Make initial commit' \ - 'echo "Not an empty file." > file && + 'echo "Not an empty file." >file && git add file && git commit -a -m "Initial commit." && git branch b' @@ -139,7 +139,7 @@ test_expect_success \ test_expect_success \ 'commitdiff(0): file added' \ - 'echo "New file" > new_file && + 'echo "New file" >new_file && git add new_file && git commit -a -m "File added." && gitweb_run "p=.git;a=commitdiff"' @@ -179,7 +179,7 @@ test_expect_success \ test_expect_success \ 'commitdiff(0): mode change and modified' \ - 'echo "New line" >> file2 && + 'echo "New line" >>file2 && test_chmod +x file2 && git commit -a -m "Mode change and modification." && gitweb_run "p=.git;a=commitdiff"' @@ -197,14 +197,14 @@ propter nomen suum. EOF git commit -a -m "File added." && git mv file2 file3 && - echo "Propter nomen suum." >> file3 && + echo "Propter nomen suum." >>file3 && git commit -a -m "File rename and modification." && gitweb_run "p=.git;a=commitdiff"' test_expect_success \ 'commitdiff(0): renamed, mode change and modified' \ 'git mv file3 file2 && - echo "Propter nomen suum." >> file2 && + echo "Propter nomen suum." >>file2 && test_chmod +x file2 && git commit -a -m "File rename, mode change and modification." && gitweb_run "p=.git;a=commitdiff"' @@ -213,8 +213,8 @@ test_expect_success \ # commitdiff testing (taken from t4114-apply-typechange.sh) test_expect_success 'setup typechange commits' ' - echo "hello world" > foo && - echo "hi planet" > bar && + echo "hello world" >foo && + echo "hi planet" >bar && git update-index --add foo bar && git commit -m initial && git branch initial && @@ -223,18 +223,18 @@ test_expect_success 'setup typechange commits' ' git commit -m "foo symlinked to bar" && git branch foo-symlinked-to-bar && rm -f foo && - echo "how far is the sun?" > foo && + echo "how far is the sun?" >foo && git update-index foo && git commit -m "foo back to file" && git branch foo-back-to-file && rm -f foo && git update-index --remove foo && mkdir foo && - echo "if only I knew" > foo/baz && + echo "if only I knew" >foo/baz && git update-index --add foo/baz && git commit -m "foo becomes a directory" && git branch "foo-becomes-a-directory" && - echo "hello world" > foo/baz && + echo "hello world" >foo/baz && git update-index foo/baz && git commit -m "foo/baz is the original foo" && git branch foo-baz-renamed-from-foo @@ -324,7 +324,7 @@ test_expect_success 'commitdiff(1): removal of incomplete line' ' test_expect_success \ 'Create a merge' \ 'git checkout b && - echo "Branch" >> b && + echo "Branch" >>b && git add b && git commit -a -m "On branch" && git checkout master && @@ -342,26 +342,26 @@ test_expect_success \ test_expect_success \ 'Prepare large commit' \ 'git checkout b && - echo "To be changed" > 01-change && - echo "To be renamed" > 02-pure-rename-from && - echo "To be deleted" > 03-delete && - echo "To be renamed and changed" > 04-rename-from && - echo "To have mode changed" > 05-mode-change && - echo "File to symlink" > 06-file-or-symlink && - echo "To be changed and have mode changed" > 07-change-mode-change && + echo "To be changed" >01-change && + echo "To be renamed" >02-pure-rename-from && + echo "To be deleted" >03-delete && + echo "To be renamed and changed" >04-rename-from && + echo "To have mode changed" >05-mode-change && + echo "File to symlink" >06-file-or-symlink && + echo "To be changed and have mode changed" >07-change-mode-change && git add 0* && git commit -a -m "Prepare large commit" && - echo "Changed" > 01-change && + echo "Changed" >01-change && git mv 02-pure-rename-from 02-pure-rename-to && git rm 03-delete && rm -f 03-delete && - echo "A new file" > 03-new && + echo "A new file" >03-new && git add 03-new && git mv 04-rename-from 04-rename-to && - echo "Changed" >> 04-rename-to && + echo "Changed" >>04-rename-to && test_chmod +x 05-mode-change && rm -f 06-file-or-symlink && test_ln_s_add 01-change 06-file-or-symlink && - echo "Changed and have mode changed" > 07-change-mode-change && + echo "Changed and have mode changed" >07-change-mode-change && test_chmod +x 07-change-mode-change && git commit -a -m "Large commit" && git checkout master' @@ -444,7 +444,7 @@ test_expect_success \ test_expect_success \ 'logs: history (implicit HEAD, deleted file)' \ 'git checkout master && - echo "to be deleted" > deleted_file && + echo "to be deleted" >deleted_file && git add deleted_file && git commit -m "Add file to be deleted" && git rm deleted_file && @@ -522,7 +522,7 @@ test_expect_success \ '. "$TEST_DIRECTORY"/t3901/utf8.txt && test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && - echo "UTF-8" >> file && + echo "UTF-8" >>file && git add file && git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt && gitweb_run "p=.git;a=commit"' @@ -532,7 +532,7 @@ test_expect_success \ '. "$TEST_DIRECTORY"/t3901/8859-1.txt && test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && - echo "ISO-8859-1" >> file && + echo "ISO-8859-1" >>file && git add file && test_config i18n.commitencoding ISO-8859-1 && git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt && @@ -675,8 +675,8 @@ test_expect_success \ test_expect_success \ 'README.html with non-ASCII characters (utf-8)' \ - 'echo "<b>UTF-8 example:</b><br />" > .git/README.html && - cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >> .git/README.html && + 'echo "<b>UTF-8 example:</b><br />" >.git/README.html && + cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >>.git/README.html && gitweb_run "p=.git;a=summary"' # ---------------------------------------------------------------------- @@ -704,7 +704,7 @@ test_expect_success HIGHLIGHT \ test_expect_success HIGHLIGHT \ 'syntax highlighting (highlighted, shell script)' \ 'git config gitweb.highlight yes && - echo "#!/usr/bin/sh" > test.sh && + echo "#!/usr/bin/sh" >test.sh && git add test.sh && git commit -m "Add test.sh" && gitweb_run "p=.git;a=blob;f=test.sh"' @@ -712,7 +712,7 @@ test_expect_success HIGHLIGHT \ test_expect_success HIGHLIGHT \ 'syntax highlighting (highlighter language autodetection)' \ 'git config gitweb.highlight yes && - echo "#!/usr/bin/perl" > test && + echo "#!/usr/bin/perl" >test && git add test && git commit -m "Add test" && gitweb_run "p=.git;a=blob;f=test"' @@ -729,11 +729,11 @@ test_expect_success \ 'git init --bare foo.git && git --git-dir=foo.git --work-tree=. add file && git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && - echo "foo" > foo.git/description && + echo "foo" >foo.git/description && mkdir -p foo && (cd foo && git clone --shared --bare ../foo.git foo-forked.git && - echo "fork of foo" > foo-forked.git/description)' + echo "fork of foo" >foo-forked.git/description)' test_expect_success \ 'forks: projects list' \ @@ -754,8 +754,8 @@ EOF test_expect_success \ 'ctags: tag cloud in projects list' \ 'mkdir .git/ctags && - echo "2" > .git/ctags/foo && - echo "1" > .git/ctags/bar && + echo "2" >.git/ctags/foo && + echo "1" >.git/ctags/bar && gitweb_run' test_expect_success \ @@ -769,8 +769,8 @@ test_expect_success \ test_expect_success \ 'ctags: malformed tag weights' \ 'mkdir -p .git/ctags && - echo "not-a-number" > .git/ctags/nan && - echo "not-a-number-2" > .git/ctags/nan2 && + echo "not-a-number" >.git/ctags/nan && + echo "not-a-number-2" >.git/ctags/nan2 && echo "0.1" >.git/ctags/floating-point && gitweb_run' diff --git a/t/t9831-git-p4-triggers.sh b/t/t9831-git-p4-triggers.sh index d743ca33ee..ff6c0352e6 100755 --- a/t/t9831-git-p4-triggers.sh +++ b/t/t9831-git-p4-triggers.sh @@ -58,7 +58,7 @@ test_expect_success 'import with extra info lines from verbose p4 trigger' ' ( cd "$git" && git p4 sync - )&& + ) && ( p4 triggers -i <<-EOF Triggers: diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 352c213d52..3103be8a32 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -905,7 +905,7 @@ test_expect_code () { # - not all diff versions understand "-u" test_cmp() { - $GIT_TEST_CMP "$@" + eval "$GIT_TEST_CMP" '"$@"' } # Check that the given config key has the expected value. @@ -1362,14 +1362,22 @@ nongit () { ) } 7>&2 2>&4 -# convert stdin to pktline representation; note that empty input becomes an -# empty packet, not a flush packet (for that you can just print 0000 yourself). +# convert function arguments or stdin (if not arguments given) to pktline +# representation. If multiple arguments are given, they are separated by +# whitespace and put in a single packet. Note that data containing NULs must be +# given on stdin, and that empty input becomes an empty packet, not a flush +# packet (for that you can just print 0000 yourself). packetize() { - cat >packetize.tmp && - len=$(wc -c <packetize.tmp) && - printf '%04x%s' "$(($len + 4))" && - cat packetize.tmp && - rm -f packetize.tmp + if test $# -gt 0 + then + packet="$*" + printf '%04x%s' "$((4 + ${#packet}))" "$packet" + else + perl -e ' + my $packet = do { local $/; <STDIN> }; + printf "%04x%s", 4 + length($packet), $packet; + ' + fi } # Parse the input as a series of pktlines, writing the result to stdout. @@ -1543,3 +1551,13 @@ test_bitmap_traversal () { test_cmp "$1.normalized" "$2.normalized" && rm -f "$1.normalized" "$2.normalized" } + +# Tests for the hidden file attribute on Windows +test_path_is_hidden () { + test_have_prereq MINGW || + BUG "test_path_is_hidden can only be used on Windows" + + # Use the output of `attrib`, ignore the absolute path + case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac + return 1 +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 9fe390bd5a..0bb1105ec3 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -78,20 +78,23 @@ then exit 1 fi -# Parse options while taking care to leave $@ intact, so we will still -# have all the original command line options when executing the test -# script again for '--tee' and '--verbose-log' below. store_arg_to= -prev_opt= -for opt -do - if test -n "$store_arg_to" +opt_required_arg= +# $1: option string +# $2: name of the var where the arg will be stored +mark_option_requires_arg () { + if test -n "$opt_required_arg" then - eval $store_arg_to=\$opt - store_arg_to= - prev_opt= - continue + echo "error: options that require args cannot be bundled" \ + "together: '$opt_required_arg' and '$1'" >&2 + exit 1 fi + opt_required_arg=$1 + store_arg_to=$2 +} + +parse_option () { + local opt="$1" case "$opt" in -d|--d|--de|--deb|--debu|--debug) @@ -101,7 +104,7 @@ do -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) GIT_TEST_LONG=t; export GIT_TEST_LONG ;; -r) - store_arg_to=run_list + mark_option_requires_arg "$opt" run_list ;; --run=*) run_list=${opt#--*=} ;; @@ -185,12 +188,42 @@ do *) echo "error: unknown test option '$opt'" >&2; exit 1 ;; esac +} + +# Parse options while taking care to leave $@ intact, so we will still +# have all the original command line options when executing the test +# script again for '--tee' and '--verbose-log' later. +for opt +do + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + opt_required_arg= + continue + fi - prev_opt=$opt + case "$opt" in + --*|-?) + parse_option "$opt" ;; + -?*) + # bundled short options must be fed separately to parse_option + opt=${opt#-} + while test -n "$opt" + do + extra=${opt#?} + this=${opt%$extra} + opt=$extra + parse_option "-$this" + done + ;; + *) + echo "error: unknown test option '$opt'" >&2; exit 1 ;; + esac done if test -n "$store_arg_to" then - echo "error: $prev_opt requires an argument" >&2 + echo "error: $opt_required_arg requires an argument" >&2 exit 1 fi @@ -867,6 +900,7 @@ maybe_setup_valgrind () { fi } +trace_level_=0 want_trace () { test "$trace" = t && { test "$verbose" = t || test "$verbose_log" = t @@ -880,7 +914,7 @@ want_trace () { test_eval_inner_ () { # Do not add anything extra (including LF) after '$*' eval " - want_trace && set -x + want_trace && trace_level_=$(($trace_level_+1)) && set -x $*" } @@ -911,7 +945,8 @@ test_eval_ () { test_eval_ret_=$? if want_trace then - set +x + test 1 = $trace_level_ && set +x + trace_level_=$(($trace_level_-1)) fi } 2>/dev/null 4>&2 @@ -1070,6 +1105,7 @@ finalize_junit_xml () { junit_time=$(test-tool date getnanos $junit_suite_start) sed -e "s/\(<testsuite.*\) time=\"[^\"]*\"/\1/" \ -e "s/<testsuite [^>]*/& time=\"$junit_time\"/" \ + -e '/^ *<\/testsuite/d' \ <"$junit_xml_path" >"$junit_xml_path.new" mv "$junit_xml_path.new" "$junit_xml_path" @@ -121,6 +121,7 @@ static void tr2main_atexit_handler(void) tr2_sid_release(); tr2_cmd_name_release(); tr2_cfg_free_patterns(); + tr2_cfg_free_env_vars(); tr2_sysenv_release(); trace2_enabled = 0; @@ -311,6 +312,14 @@ void trace2_cmd_list_config_fl(const char *file, int line) tr2_cfg_list_config_fl(file, line); } +void trace2_cmd_list_env_vars_fl(const char *file, int line) +{ + if (!trace2_enabled) + return; + + tr2_list_env_vars_fl(file, line); +} + void trace2_cmd_set_config_fl(const char *file, int line, const char *key, const char *value) { @@ -183,6 +183,19 @@ void trace2_cmd_list_config_fl(const char *file, int line); #define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__) /* + * Emit one or more 'def_param' events for "important" environment variables. + * + * Use the TR2_SYSENV_ENV_VARS setting to register a comma-separated list of + * environment variables considered important. For example: + * git config --system trace2.envVars 'GIT_HTTP_USER_AGENT,GIT_CONFIG' + * or: + * GIT_TRACE2_ENV_VARS="GIT_HTTP_USER_AGENT,GIT_CONFIG" + */ +void trace2_cmd_list_env_vars_fl(const char *file, int line); + +#define trace2_cmd_list_env_vars() trace2_cmd_list_env_vars_fl(__FILE__, __LINE__) + +/* * Emit a "def_param" event for the given config key/value pair IF * we consider the key to be "important". * diff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c index caa7f06948..ec9ac1a6ef 100644 --- a/trace2/tr2_cfg.c +++ b/trace2/tr2_cfg.c @@ -7,6 +7,10 @@ static struct strbuf **tr2_cfg_patterns; static int tr2_cfg_count_patterns; static int tr2_cfg_loaded; +static struct strbuf **tr2_cfg_env_vars; +static int tr2_cfg_env_vars_count; +static int tr2_cfg_env_vars_loaded; + /* * Parse a string containing a comma-delimited list of config keys * or wildcard patterns into a list of strbufs. @@ -46,6 +50,45 @@ void tr2_cfg_free_patterns(void) tr2_cfg_loaded = 0; } +/* + * Parse a string containing a comma-delimited list of environment variable + * names into a list of strbufs. + */ +static int tr2_load_env_vars(void) +{ + struct strbuf **s; + const char *varlist; + + if (tr2_cfg_env_vars_loaded) + return tr2_cfg_env_vars_count; + tr2_cfg_env_vars_loaded = 1; + + varlist = tr2_sysenv_get(TR2_SYSENV_ENV_VARS); + if (!varlist || !*varlist) + return tr2_cfg_env_vars_count; + + tr2_cfg_env_vars = strbuf_split_buf(varlist, strlen(varlist), ',', -1); + for (s = tr2_cfg_env_vars; *s; s++) { + struct strbuf *buf = *s; + + if (buf->len && buf->buf[buf->len - 1] == ',') + strbuf_setlen(buf, buf->len - 1); + strbuf_trim_trailing_newline(*s); + strbuf_trim(*s); + } + + tr2_cfg_env_vars_count = s - tr2_cfg_env_vars; + return tr2_cfg_env_vars_count; +} + +void tr2_cfg_free_env_vars(void) +{ + if (tr2_cfg_env_vars) + strbuf_list_free(tr2_cfg_env_vars); + tr2_cfg_env_vars_count = 0; + tr2_cfg_env_vars_loaded = 0; +} + struct tr2_cfg_data { const char *file; int line; @@ -79,6 +122,21 @@ void tr2_cfg_list_config_fl(const char *file, int line) read_early_config(tr2_cfg_cb, &data); } +void tr2_list_env_vars_fl(const char *file, int line) +{ + struct strbuf **s; + + if (tr2_load_env_vars() <= 0) + return; + + for (s = tr2_cfg_env_vars; *s; s++) { + struct strbuf *buf = *s; + const char *val = getenv(buf->buf); + if (val && *val) + trace2_def_param_fl(file, line, buf->buf, val); + } +} + void tr2_cfg_set_fl(const char *file, int line, const char *key, const char *value) { diff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h index d9c98f64dd..a11d71feb5 100644 --- a/trace2/tr2_cfg.h +++ b/trace2/tr2_cfg.h @@ -8,6 +8,12 @@ void tr2_cfg_list_config_fl(const char *file, int line); /* + * Iterate over all "interesting" environment variables and emit 'def_param' + * events for them to TRACE2. + */ +void tr2_list_env_vars_fl(const char *file, int line); + +/* * Emit a "def_param" event for the given key/value pair IF we consider * the key to be "interesting". */ @@ -16,4 +22,6 @@ void tr2_cfg_set_fl(const char *file, int line, const char *key, void tr2_cfg_free_patterns(void); +void tr2_cfg_free_env_vars(void); + #endif /* TR2_CFG_H */ diff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c index 3c3792eca2..a380dcf910 100644 --- a/trace2/tr2_sysenv.c +++ b/trace2/tr2_sysenv.c @@ -29,6 +29,8 @@ struct tr2_sysenv_entry { static struct tr2_sysenv_entry tr2_sysenv_settings[] = { [TR2_SYSENV_CFG_PARAM] = { "GIT_TRACE2_CONFIG_PARAMS", "trace2.configparams" }, + [TR2_SYSENV_ENV_VARS] = { "GIT_TRACE2_ENV_VARS", + "trace2.envvars" }, [TR2_SYSENV_DST_DEBUG] = { "GIT_TRACE2_DST_DEBUG", "trace2.destinationdebug" }, diff --git a/trace2/tr2_sysenv.h b/trace2/tr2_sysenv.h index d4364a7b85..3292ee15bc 100644 --- a/trace2/tr2_sysenv.h +++ b/trace2/tr2_sysenv.h @@ -11,6 +11,7 @@ */ enum tr2_sysenv_variable { TR2_SYSENV_CFG_PARAM = 0, + TR2_SYSENV_ENV_VARS, TR2_SYSENV_DST_DEBUG, diff --git a/transport.c b/transport.c index 1fdc7dac1a..471c5bd339 100644 --- a/transport.c +++ b/transport.c @@ -16,7 +16,7 @@ #include "url.h" #include "submodule.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "sigchain.h" #include "transport-internal.h" #include "protocol.h" diff --git a/upload-pack.c b/upload-pack.c index c53249cac1..902d0ad5e1 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -1252,7 +1252,7 @@ static void process_args(struct packet_reader *request, struct upload_pack_data *data, struct object_array *want_obj) { - while (packet_reader_read(request) != PACKET_READ_FLUSH) { + while (packet_reader_read(request) == PACKET_READ_NORMAL) { const char *arg = request->line; const char *p; @@ -1321,6 +1321,9 @@ static void process_args(struct packet_reader *request, /* ignore unknown lines maybe? */ die("unexpected line: '%s'", arg); } + + if (request->status != PACKET_READ_FLUSH) + die(_("expected flush after fetch arguments")); } static int process_haves(struct oid_array *haves, struct oid_array *common, diff --git a/userdiff.c b/userdiff.c index efbe05e5a5..30ab42df8e 100644 --- a/userdiff.c +++ b/userdiff.c @@ -222,7 +222,7 @@ static struct userdiff_driver driver_false = { { NULL, 0 } }; -static struct userdiff_driver *userdiff_find_by_namelen(const char *k, int len) +static struct userdiff_driver *userdiff_find_by_namelen(const char *k, size_t len) { int i; for (i = 0; i < ndrivers; i++) { @@ -266,7 +266,7 @@ int userdiff_config(const char *k, const char *v) { struct userdiff_driver *drv; const char *name, *type; - int namelen; + size_t namelen; if (parse_config_key(k, "diff", &name, &namelen, &type) || !name) return 0; @@ -218,7 +218,7 @@ ssize_t xread(int fd, void *buf, size_t len) { ssize_t nr; if (len > MAX_IO_SIZE) - len = MAX_IO_SIZE; + len = MAX_IO_SIZE; while (1) { nr = read(fd, buf, len); if (nr < 0) { @@ -240,7 +240,7 @@ ssize_t xwrite(int fd, const void *buf, size_t len) { ssize_t nr; if (len > MAX_IO_SIZE) - len = MAX_IO_SIZE; + len = MAX_IO_SIZE; while (1) { nr = write(fd, buf, len); if (nr < 0) { |